learn-classification-regression-with-phoebe / Session 8 of 10
Learn Classification & Regression with Phoebe · Session 8 of 10

Support vector machines - the widest street

The last animal in the zoo is the most elegant. Among all the lines that could separate two classes, the SVM picks the one with the widest empty street around it - and it turns out only a handful of training points, the support vectors, decide where that street goes. Then kernels let the street bend without ever leaving linear algebra. This is also the session with the most honest ending in the course: on Lumen, the fancy kernel loses to the straight line, and you will know exactly why. Margin intuition, the kernel trick, the C and gamma dials, and the discipline to test whether fancy actually beats simple - that is today.

🟠 Getting real Builders · DA / DE / DS Python · scikit-learn 45 min
0-3 · Setup 3-18 · The widest street + kernels 18-40 · Build: SVM meets Lumen 40-45 · Wrap
Part 0

The most elegant idea in the zoo

Every classifier so far drew a boundary as a side effect of something else - logistic regression maximizes likelihood, trees maximize purity, kNN just votes. The SVM is the only animal that optimizes the boundary itself: make the gap between the classes as wide as possible, and let the geometry do the generalizing. It is beautiful math with a practical sting in the tail - and today you will feel both. This is the session where "test whether the fancy thing actually helps" stops being advice and becomes a measured result.

Live - built in session Self-study - run after ★ The one big idea The case: Lumen Skincare
★ What you walk out with today Margin and kernel intuition you can sketch on a whiteboard - the widest street, the curb-touching support vectors, the lift-then-cut kernel trick - plus the two dials (C and gamma) that control it, and the discipline of the honesty demo: LinearSVC ties logistic at ~0.69 ROC-AUC on Lumen while the default RBF kernel manages only ~0.55-0.56. Kernels are a hypothesis about shape, not magic - and you will have the numbers to say so.
Part 1 · the margin

The widest street 8 min live

Picture two separable classes and every straight line that could split them - there are infinitely many. Which one generalizes best? The SVM's answer: the line with the widest margin, the biggest empty street between the classes. New points land with maximum room for error. The few training points that touch the street's curbs are the support vectors - they alone pin the boundary in place. And because real data is never cleanly separable, the soft margin lets a few points violate the street for a price, with a dial (C) that sets how expensive violations are.

The widest street - only the curb points matter The street solid line = the boundary, dashed lines = the curbs. Wider street = safer calls. Support vectors the circled points touching the curbs - they alone define where the street goes. Soft margin real data overlaps, so a few points may stand in the street. C prices each violation. Only the curb-touching points matter - delete the rest of the training data and nothing changes.
🔍 Click to zoom - the widest street, its curbs, and the circled support vectors that pin it
LiveSupport vectors - the model that forgets almost everything3 min▶

After training, the SVM keeps only the support vectors - the curb-touching points - and throws the rest of the training set away. On a clean problem that can mean keeping dozens of points out of thousands. This is why session 1's zoo map gave the SVM "a foot in both camps": it learns a boundary like a model-based method, but that boundary is defined by remembered training points, like an instance-based method.

The practical payoff is memory efficiency: unlike kNN, which ships the whole training set to production, a fitted SVM ships only its support vectors. The conceptual payoff is bigger - it tells you what the SVM believes: the boundary should be decided by the hardest, closest cases, not by the easy points deep inside each class.

Whiteboard line: "an SVM is a model that remembers only its most awkward customers - because the awkward ones are the ones that define the edge of the business."
LiveC - the street-strictness dial3 min▶

C prices margin violations, and the scikit-learn guide gives the cleanest reading of it. Low C: violations are cheap, so the model accepts a wide, tolerant street - a smoother, more regularized boundary that may misclassify a few training points on purpose. High C: violations are expensive, so the model narrows the street until it fits every training point it can - and starts bending around noise. High C is the SVM's overfitting mode.

The sklearn practical-use tip worth memorizing: if your data is noisy, decrease C. Noisy labels plus a strict street means the street gets dragged around by mislabeled points. Lumen's ~3% conversion signal is exactly this kind of noisy, overlapping data - which is a hint about how today's demo will go.

Notice the pattern from the whole course: C is this animal's complexity knob, exactly as degree was for polynomials, k for kNN, and depth for trees. Same U-curve, new dial.

Self-studyStatQuest's framing - why soft margins exist at all2 min▶

StatQuest's SVM Part 1 tells the story in two beats. Beat one: the maximal margin classifier - the widest street with zero violations allowed - is lovely on clean data and terrible in practice, because one outlier standing near the other class drags the entire street toward it. A single weird point should not redraw your boundary.

Beat two: allow violations, price them, and pick the width that cross-validates best - the soft margin. That is the actual "support vector classifier" everyone uses. Watch Parts 1-3 after the session; the margin story, the polynomial kernel, and the RBF kernel each get their own episode, and Josh's hand-drawn streets are the best in the business.

Part 2 · kernels

Kernels - bending the street 9 min live

A straight street fails when the true boundary curves - one class wrapped inside another, and no line helps. The SVM's move: lift the data into a higher dimension where a straight cut works, cut there, and let the cut project back down as a curve. The famous kernel trick makes this affordable: the math only ever needs dot products between points in the lifted space, and a kernel function computes those dot products directly - it never builds the lifted features at all. The polynomial kernel lifts with powers up to degree d; the RBF kernel lifts with similarity bumps, where gamma sets how far one point's influence reaches - and a large gamma means short reach, a wiggly point-wrapping boundary, and overfitting.

The lift - inseparable in 1D, trivial in 2D One feature x cyan sits between the magenta groups - no single cut works add x² x² x Now one straight line separates them The kernel computes the lifted dot product without ever building the lift - that is the whole trick.
🔍 Click to zoom - the lift that turns an impossible 1D cut into an easy 2D one
LivePlay: RBF on the moons - drag gamma4 min▶

Same boundary explorer as sessions 1, 3, 5 and 7 - now with the RBF kernel. Drag gamma on the moons and watch the boundary go from almost-linear (small gamma, long reach, smooth) to wrapping individual points (large gamma, short reach, memorization).

Then switch to Lumen real and notice how little the bending buys you on an overlapping cloud - a preview of the demo's honest ending.

LiveGamma, C, and the exponential grid3 min▶

Gamma is a reach dial. Small gamma: each support vector's influence stretches far, many points blend into one smooth opinion, the boundary stays calm. Large gamma: influence dies off within a whisker, each point becomes its own island of opinion, and the boundary wiggles around individual training points - the RBF's overfitting mode, the exact cousin of k=1 and depth=None.

C and gamma overlap. Both control model complexity, and their effects interact - a strict street with long-reach bumps can behave like a tolerant street with short-reach bumps. The sklearn guidance: tune them together, on an exponential grid (powers of 10, then refine), never one at a time on a linear grid. In session 9 the fair harness does exactly this with GridSearchCV.

Self-studyThe kernel zoo in one line each2 min▶

scikit-learn ships four built-in kernels for SVC, and one line covers each:

KernelOne-liner
linearno lift at all - the plain widest street (prefer LinearSVC for speed)
polylifts with feature powers and products up to degree d - global curves
rbflifts with similarity bumps around each point - local curves, the default
sigmoida tanh-shaped similarity - historic neural-net flavored, rarely the right pick

Default to linear-first thinking on tabular data, rbf when you have reason to believe the boundary curves. Poly and sigmoid are for when you can articulate why.

Part 3 · the fine print

SVM in practice 8 min live

The SVM is the zoo's highest-maintenance animal. It demands scaled features, its kernel solver gets slow fast, and its most tempting parameter (probability=True) is a trap the library itself is now removing. Four pieces of fine print, straight from the sklearn user guide, before you ever put one near production.

LiveScaling is mandatory - not a suggestion2 min▶

The sklearn docs say it plainly: support vector machines are "not scale invariant" and scaling is "highly recommended". Margins are measured in feature units - if prior_30d_spend runs 0-500 while pages_viewed runs 1-20, the street's width is effectively measured in spend and pages barely counts. You validated the same failure on kNN in session 5 (0.64 scaled vs 0.58 unscaled AUC); distance-based models share this Achilles heel.

The idiom to make it un-forgettable: make_pipeline(StandardScaler(), SVC()) - the scaler lives inside the pipeline, fit on train only, forever. There is no legitimate SVM workflow without it.

Real worldA team once "proved" SVMs were useless on their pricing data - every kernel scored like a coin flip. One StandardScaler later, the same model was their best single classifier. The postmortem line became team folklore: "we did not test the SVM, we tested our preprocessing."
LiveComplexity - why 30,000 rows is a problem3 min▶

Kernel SVC (libsvm under the hood) solves a quadratic program whose cost scales roughly O(n²) to O(n³) with the number of rows. Double the data, pay four to eight times the compute. On Lumen's 30,000 training rows a default RBF fit stops being interactive - so today's demo subsamples 6,000 rows for the RBF round and says so out loud. Quietly subsampling is how leaderboards lie; loudly subsampling is engineering.

LinearSVC is the escape hatch. It uses liblinear, not libsvm - no kernel machinery, near-linear scaling, happy on millions of rows. When the boundary you want is straight, you get the full 30k rows AND a faster fit. That asymmetry (linear = all the data, kernel = a sample) is itself a fairness caveat to keep in mind when you read today's results.

Self-studyThe probability=True trap2 min▶

SVMs do not natively produce probabilities - they produce signed distances to the street (decision_function). probability=True bolts probabilities on by running an expensive internal cross-validation (Platt scaling) after the fit, and the sklearn docs warn the results can disagree with decision_function - the argmax of the probabilities can differ from the side of the street the point is on.

It is also on its way out: deprecated in sklearn 1.9, removed in 1.11. For ranking metrics like ROC-AUC you never needed it anyway - decision_function scores rank points exactly as well, for free. That is what today's demo uses.

Self-studyLinearSVC quirks + the imbalance dial2 min▶

LinearSVC is almost-but-not-exactly a linear-kernel SVC: it penalizes the intercept along with the weights (SVC does not), and it optimizes squared hinge loss by default. In practice the differences are small on scaled data, but when the two disagree, this is why - worth knowing before you file a "bug".

For imbalanced targets like Lumen's ~3% conversion, class_weight="balanced" re-weights the margin violations so the rare class's mistakes cost more - the SVM family's version of the imbalance handling you met on logistic regression. Try it in the homework grid and see whether it moves the needle.

Build-along

SVM meets Lumen - the honesty demo ★ 18 min · everyone builds

Two rounds. Round one: LinearSVC, scaled, full features, all 30k training rows - the widest straight street money can buy. Round two: SVC with the RBF kernel on a 6,000-row subsample (the QP cost is why), defaults first, then a couple of gamma/C values. Then we read the result like adults.

Fit the straight street. LinearSVC inside the standard Lumen pipeline. Score with decision_function + ROC-AUC - no probabilities needed for ranking.

Fit the bendy street. SVC(kernel="rbf") on 6,000 subsampled training rows, defaults. Say the subsample out loud - it is a documented cost decision, not a secret.

Poke the dials. Try a couple of gamma/C values on the RBF. Watch how little moves.

Read the ending. The straight line ties logistic. The kernel loses. Explain why before you leave the room.

svm_lumen.py - the straight street vs the bendy oneimport pandas as pd from sklearn.model_selection import train_test_split from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.pipeline import Pipeline from sklearn.svm import LinearSVC, SVC from sklearn.metrics import roc_auc_score df = pd.read_parquet("lumen_sessions.parquet") cat = ["channel", "device", "new_vs_returning", "product_category"] num = ["pages_viewed", "session_hour", "prior_30d_spend"] X, y = df[cat + num], df["converted"] X_tr, X_te, y_tr, y_te = train_test_split( X, y, test_size=0.25, random_state=42, stratify=y) prep = ColumnTransformer([ ("cat", OneHotEncoder(handle_unknown="ignore"), cat), ("num", StandardScaler(), num)]) # scaling is mandatory for SVMs # Round 1 - LinearSVC (liblinear): scales fine, all 30k training rows lin = Pipeline([("prep", prep), ("model", LinearSVC())]).fit(X_tr, y_tr) lin_scores = lin.decision_function(X_te) # NOT probability=True print(f"LinearSVC ROC-AUC {roc_auc_score(y_te, lin_scores):.2f}") # LinearSVC ROC-AUC 0.69 - ties logistic: same linear-boundary family # Round 2 - kernel SVC (libsvm QP is O(n^2)-O(n^3)): subsample 6,000 rows sub_idx = X_tr.sample(6000, random_state=42).index rbf = Pipeline([("prep", prep), ("model", SVC(kernel="rbf"))]) rbf.fit(X_tr.loc[sub_idx], y_tr.loc[sub_idx]) rbf_scores = rbf.decision_function(X_te) print(f"SVC rbf (6k) ROC-AUC {roc_auc_score(y_te, rbf_scores):.2f}") # SVC rbf (6k) ROC-AUC ~0.55-0.56 with defaults # Poke the dials - does bending help? for gamma in [0.01, 0.05]: for C in [1, 10]: m = Pipeline([("prep", prep), ("model", SVC(kernel="rbf", gamma=gamma, C=C))]) m.fit(X_tr.loc[sub_idx], y_tr.loc[sub_idx]) s = m.decision_function(X_te) print(f"rbf g={gamma} C={C} AUC {roc_auc_score(y_te, s):.2f}") # still ~0.55 - on this data, bending the street buys nothing
The conclusion card - say it exactly like this: on Lumen's tabular, mostly-linear data the straight street wins: LinearSVC ~0.69 ties logistic regression, because both draw the same shape of boundary. The default RBF manages ~0.55-0.56, and a couple of gamma/C pokes stay ~0.55. Kernels shine when the true boundary curves - the moons in the explorer - and Lumen's does not. Kernels are not magic; they are a hypothesis about shape, and here the hypothesis is wrong.
If your RBF number differs slightly: the 6,000-row subsample makes round two noisier than usual - a point or two of AUC jitter is expected. The ranking (linear beats default RBF by a mile here) should be rock solid.
Homework

Before session 9 ◐ 25 min

Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · What is a support vector?

The margin is pinned entirely by the curb-touching points. Everything deep inside its own class is irrelevant to the boundary - which is also why a fitted SVM only needs to keep those few points.

2 · What does a large gamma do to an RBF SVM?

Gamma sets how far one point's influence travels. Large gamma = short reach = every point its own island = a boundary that wraps training points and memorizes noise. The same disease as k=1 and depth=None, wearing a new coat.

3 · Why did the demo subsample 6,000 rows for the RBF but give LinearSVC all 30,000?

It is purely a compute cost decision, and we said it out loud in the demo. The quadratic-to-cubic scaling of the kernel solver is the SVM's biggest production limitation; the linear solver does not share it.

Source material

What this session covers

Session 8 covers the SVM chapters of the anchor sources: StatQuest's three-part SVM arc for the intuition, and the scikit-learn SVM module (1.4) for the API truth and practical-use tips. Videos, labs, and certificates stay with the official providers.

✓StatQuest SVM Part 1 - main ideas: margin, support vectors, soft marginPart 1
✓StatQuest SVM Part 2 - the polynomial kernelPart 2
✓StatQuest SVM Part 3 - the RBF kernelPart 2 + live tool
✓sklearn 1.4 - SVC / LinearSVC / SVR roles, C intuition, gamma intuition, kernel functionsParts 1-2
✓sklearn 1.4 practical tips - scaling mandatory, decrease C for noise, exponential grid, O(n²)-O(n³) complexity, probability=True warningParts 1-3 + demo
◐class_weight for imbalancePart 3 card + homework
◐NuSVC / custom kernels / one-class SVMnot covered by design - niche parameterizations

Session 8 cheat sheet · pin this

The widest streetOf all separating lines, pick the one with the widest margin. Soft margin: allow priced violations, because real data overlaps.
Support vectorsThe curb-touching points - they alone define the boundary. The fitted model keeps only them; everything else is deletable.
The C dialLow C = wide tolerant street, smoother, more regularized. High C = narrow strict street, overfit risk. Noisy data: decrease C.
The gamma dialHow far one point's influence reaches. Large gamma = short reach = wiggly point-wrapping boundary = overfitting. Tune C and gamma together, exponential grid.
The kernel trickLift data to a space where a line works - but compute only the lifted dot products via the kernel, never the lifted features themselves.
Scale first, alwaysSVMs are not scale invariant. make_pipeline(StandardScaler(), SVC()) - no exceptions.
The honest endingLinearSVC ~0.69 ties logistic on Lumen; default RBF (6k subsample) ~0.55-0.56 and stays ~0.55 after pokes. Kernels are a hypothesis about shape, not magic.
Ranking without probabilitiesUse decision_function + roc_auc_score. probability=True is expensive, can disagree with the boundary, and is deprecated in sklearn 1.9 (gone in 1.11).