Ordinary logistic regression draws one straight line and calls it a day. But real boundaries curve. What if, instead of fitting one classifier for all of space, we re-fit a fresh one around every query point — trusting nearby examples and ignoring the rest? Turn one knob, τ, and watch a straight line melt into a boundary that hugs the data.
Picture a medical screening clinic. On the wall is a chart: horizontal axis is a patient's resting heart rate, vertical axis is their blood-oxygen level. Each past patient is a dot — orange if they later turned out healthy, blue if they needed follow-up. A new patient walks in. Where do their numbers land, and which color is the region around them?
If the healthy and follow-up patients were cleanly split by a single sloping line, ordinary logistic regression — the classifier you already know — would nail it. It finds the one best straight cut through the plane and says "everything on this side is orange, everything on that side is blue." One line. Forever. Everywhere.
But biology doesn't cleanly split with a ruler. The healthy region might be a curved island in the middle — normal heart rate AND normal oxygen — surrounded on all sides by trouble. A straight line literally cannot carve out an island. No matter how you tilt it, a line leaves an infinite half-plane on each side. The shape we need is curved, and a line has no curve in it.
Here is the simulation that makes the failure visceral. Below, the two classes interleave along a curve. The dashed line is the best a single straight logistic boundary can do. Drag the slider to bend the true boundary more and more, and watch the straight line fall hopelessly behind — it gets a chunk of points wrong no matter where you put it.
Orange and blue dots are two classes. The dashed gray line is the best single straight logistic-regression boundary. Drag curvature up: the true split bends, and the straight line can't follow — the misclassified count climbs and never reaches zero.
So what do we do? Here is the central idea of this entire lesson, and it is almost suspiciously simple:
This is locally-weighted logistic regression (LWLR). The word "locally" means: near the query, examples count fully; far away, they fade out. The word "weighted" means: each training point gets a weight — a number between 0 and 1 saying how much it matters for this particular query. A nearby patient's record is worth 1.0; a patient on the far side of the chart might be worth 0.001.
Notice what just changed about when the work happens. Ordinary logistic regression trains once, stores one θ, and then every prediction is a fast dot product. LWLR does no training up front at all. It stores the raw data and does all the work at query time: for each new point, weigh the data, run an optimizer, get an answer, throw it away. This is called a lazy or non-parametric method — we'll unpack exactly what that costs in the final chapter.
Everything else in this lesson is filling in three blanks: (1) what exactly is the weight formula, (2) how do we actually fit the weighted classifier fast, and (3) what does the one tuning knob — the bandwidth τ — control? Get those three and you can build the whole thing from scratch, which you literally will in the Code Lab at the bottom.
Before we add the "locally-weighted" twist, let's rebuild ordinary logistic regression from scratch in one chapter, so the new pieces have somewhere to attach. If this is fresh, the full Logistic Regression Gleam goes slower; here we move briskly but skip nothing essential.
We have training data: pairs (x(i), y(i)) for i = 1, …, m. Each x(i) is a feature vector (in our clinic, [heart rate, oxygen]), and each label y(i) ∈ {0, 1}. We want a function that takes a new x and outputs a probability that y = 1.
A raw linear score θTx can be any real number — from −∞ to +∞. A probability must live in (0, 1). The bridge between them is the sigmoid (logistic) function, which squashes any real number into (0, 1):
Here g(z) = 1/(1 + e−z). At z = 0 it gives 0.5 (a coin flip). As z → +∞ it saturates to 1; as z → −∞ it saturates to 0. By the bias trick we set x0 = 1, so θTx = θ0 + θ1x1 + θ2x2 — the θ0 term is the intercept that lets the boundary float off the origin.
We need a number that says "how well does this θ explain the data?" so we can search for the θ that scores best. That number is the likelihood: the probability the model assigns to the labels we actually observed.
For one example with label y, the model's predicted probability of that specific label is a clever one-liner: h(x)y(1−h(x))1−y. Check it: if y = 1 the second factor is anything0 = 1, leaving h(x). If y = 0 the first factor is anything0 = 1, leaving 1−h(x). One formula, both cases.
Assuming examples are independent, the likelihood of the whole dataset is the product over all i. Products of many small numbers underflow and are painful to differentiate, so we take the logarithm — turning the product into a sum (log of a product is a sum of logs). The log-likelihood is:
We maximize ℓ(θ) because a bigger ℓ means the model assigned higher probability to the labels we truly saw — it "explains the data better." (If you've met cross-entropy loss, it's exactly −ℓ: maximizing log-likelihood and minimizing cross-entropy are the same act, sign-flipped.)
Let's make this concrete with one worked number. Suppose for some example y = 1 and the model currently predicts h(x) = 0.8. Its contribution to ℓ is 1·log(0.8) + 0·log(0.2) = log(0.8) = −0.223. Now suppose the model had predicted only h(x) = 0.3 for that same y = 1 example: contribution = log(0.3) = −1.204. The first θ gives a less-negative (better) log-likelihood, exactly because it put more probability on the correct label. Maximizing ℓ pushes h(x) up where y = 1 and down where y = 0.
The S-curve is g(z). Move the score z slider: the marker rides the curve. Pick the true label (0 or 1) and read off this example's contribution to the log-likelihood — it shoots toward 0 (perfect) when the prediction agrees with the label, and plunges very negative when it confidently disagrees.
Once θ is fixed, we predict class 1 when h(x) ≥ 0.5. Since the sigmoid hits 0.5 exactly at z = 0, the decision boundary is the set of points where θTx = 0 — a straight line (in 2D) or hyperplane (in higher D). That straightness is the limitation Chapter 0 ran into. LWLR's whole job is to let that boundary curve, without abandoning this machinery.
Here is where LWLR earns its name. We will attach to each training example a weight wi ∈ (0, 1] that says, "for this query, how much should example i count?" Nearby examples get weights near 1; far examples get weights near 0. The weight formula is a single, beautiful exponential bump:
Let's name every piece, because each one carries the whole intuition:
Read the formula as a story. The query shines a soft circular spotlight on the data. Right at the query (distance 0), the exponent is 0, so w = e0 = 1: full brightness. As an example sits farther away, the squared distance grows, the exponent goes more negative, and the weight fades smoothly toward 0: that example slips into the dark. There is no hard cutoff — the light dims gradually. That smoothness is what makes the resulting boundary smooth too.
Let's actually compute some weights, every step. Put the query at x = (0, 0), and use bandwidth τ = 1, so 2τ2 = 2·12 = 2. Here are four training points and their weights:
Trace the meaning: the point sitting exactly on the query counts fully (1.000). One unit away, it's already down to 0.607 — still a strong vote. Two units away (diagonally), 0.368. And the point three units out is essentially switched off at 0.011 — it contributes about a hundredth of a normal example. That far point will barely budge the local fit, which is the whole point: the classifier we build at the origin should be shaped by the origin's neighborhood, not by distant data.
Now watch what τ does to those same four points. Redo x(4) with a bigger bandwidth τ = 3, so 2τ2 = 18: w4 = e−9/18 = e−0.5 = 0.607. The far point that was nearly dead (0.011) is now a full participant (0.607)! A larger τ widens the spotlight so even distant points count — pushing LWLR back toward treating all data equally, i.e., toward ordinary logistic regression. A smaller τ shrinks the spotlight to a pinprick. Hold that lever; Chapter 4 pulls it hard.
The curve is the weight w = exp(−d²/2τ²) as a function of distance d from the query. Drag τ: a small τ makes a narrow, steep spotlight (only the closest points survive); a large τ makes a wide, gentle one (far points still count). The dot marks the weight at the probe distance — drag that too and read the exact weight, just like the hand calculation above.
Two design choices in this kernel are worth pausing on, because they reveal why the formula has this exact shape (Concept + Realization — we don't accept a formula we can't justify):
Why squared distance, and why the exponential? Squaring makes the bump perfectly round and smooth at the center (no kink at d = 0) and, paired with the exp, gives the Gaussian "bell" shape — the same shape as a normal distribution. That smoothness matters: a kinked or hard-cutoff weighting would produce a kinked decision boundary. The exponential guarantees weights are always positive (you can't have a "negative amount of trust") and decay gracefully rather than dropping to zero abruptly.
Why the 2τ2 in the denominator and not just τ? It's a convention borrowed from the Gaussian, where 2σ2 appears. The "2" and the "square" make τ behave like a standard deviation — a natural "one-sigma radius" where the weight has dropped to e−1/2 ≈ 0.607. So "τ = 1" literally means "points one unit out still count 61%." That interpretability is the payoff.
We now have weights. Let's bolt them onto the log-likelihood and then actually solve for the best local θ. The weighted log-likelihood simply drops a wi in front of each example's term — "count this example wi times":
Two new symbols. The ∑ term is the familiar log-likelihood, now weighted. The leading −(λ/2)θTθ is a ridge (L2) penalty with a tiny λ = 10−4. Why is it there? Because in a local fit, the spotlight sometimes lands on a region where one class is entirely absent — the data is perfectly separable locally, and the unpenalized optimizer would drive θ to infinity chasing ever-higher confidence. The minuscule ridge gently pulls θ back toward 0, keeping the matrices invertible and the arithmetic finite. It's a numerical seatbelt, not a modeling choice; λ is small enough to barely affect the answer where data is plentiful.
To maximize ℓ we need to find where its slope is zero. Gradient ascent would take many small uphill steps. Newton's method does something smarter: it uses the curvature (the second derivative) to jump, in essentially one or a few steps, straight to the peak of a bowl-shaped function. Because the logistic log-likelihood is concave (one smooth hill, no false summits), Newton converges in a handful of iterations — here, six is plenty. For LWLR this speed is precious: remember, we re-solve from scratch at every query point, so a slow optimizer would be agony. Newton's curvature-awareness is what makes per-query refitting practical.
Newton's update for maximizing a function is θ ← θ − H−1∇, where ∇ is the gradient (vector of first derivatives) and H is the Hessian (matrix of second derivatives). Let's derive both for our weighted objective — every term justified.
Take the derivative of ℓ with respect to θ. The key fact we lean on is the sigmoid's clean derivative: ∂h/∂(θTx) = h(1−h). Working through the log terms (using that derivative and the chain rule), each example's weighted contribution to the gradient collapses to a strikingly simple form:
Define zi = wi(y(i) − h(x(i))) — the weighted residual for example i, "how wrong are we here, scaled by how much this point counts." Stacking all the x(i) as rows of a matrix X and all zi into a vector z, the sum ∑i zi x(i) is exactly the matrix-vector product XTz. So:
The −λθ is the derivative of the ridge term −(λ/2)θTθ. Read the whole thing: the gradient pushes θ in the direction of weighted, signed errors. Where we under-predict (y > h) on a heavily-weighted point, zi is positive and large, and θ moves to fix it.
Differentiate the gradient once more to get the Hessian (the matrix of how the gradient changes). Each example contributes a term proportional to its weight times h(1−h) — the slope of the sigmoid, biggest where the model is most uncertain (h near 0.5). Collecting these into a diagonal matrix D:
and the full Hessian is:
The −λI comes from differentiating −λθ once more (the derivative of −λθ w.r.t. θ is −λI, the identity scaled). Every Dii is negative (weights, h, and 1−h are all positive, with a leading minus sign), which makes H negative-definite — the mathematical signature of a concave hill with a single peak. That's the guarantee Newton needs to march uphill reliably.
Let's do a single Newton update on a tiny one-dimensional example so every number is visible. We'll drop the bias to keep θ a single scalar (so X, z, H are all 1×1 — just numbers). Setup: two training points, both with weight w = 1 for simplicity, ridge λ = 0, starting from θ = 0.
One step moved θ from 0 to +1.0. Sanity-check the direction: x(1) = 2 has label 1, x(2) = −2 has label 0, so a positive θ correctly says "bigger x → class 1." With θ = 1, the prediction at x = 2 is g(2) = 0.88 (was 0.5) and at x = −2 is g(−2) = 0.12 — both moved decisively toward their true labels in a single step. That one-jump efficiency is the gift of using curvature.
The curve is the weighted log-likelihood ℓ(θ) for the two-point setup above. The dot is the current θ. Press Newton step: it leaps using curvature, landing near the peak almost immediately. Press Gradient step to compare — gradient ascent inches along, taking many steps to get where Newton went in one. Reset to start over.
This is the heart of the lesson. We have one knob, τ, and turning it sweeps LWLR across the entire spectrum from "memorize every wiggle" to "draw one straight line." Understanding τ is understanding the bias/variance trade-off in the most tactile way you'll ever see it.
Recall the spotlight from Chapter 2. τ is its radius. Now think about what radius does to the decision boundary, which is what we actually care about:
With a very small τ, the spotlight is a pinprick. At each query, only the two or three nearest training points have non-negligible weight; everything else is dark. The local fit therefore bends to satisfy just those few neighbors. Move the query a little and a different tiny set of neighbors takes over, so the boundary can swerve sharply. The result is a boundary that hugs the data — it threads between individual points, even chasing single noisy outliers.
This is high variance: the boundary is exquisitely sensitive to the exact training sample. Resample the data slightly and the wiggly boundary changes shape completely. It memorizes noise. In the τ → 0 limit, LWLR essentially becomes a nearest-neighbor classifier — each query just copies its closest point's label.
Now crank τ up. The spotlight floods the whole dataset; every point's weight approaches the same value (they're all "near" relative to the enormous radius). When all weights are roughly equal, the weighted log-likelihood is just a scalar multiple of the unweighted one — and maximizing it gives the same θ as ordinary, global logistic regression. The location of the query stops mattering. Every query produces the same straight-line boundary.
This is high bias: the model is too rigid to follow a curved truth, so it's systematically wrong wherever the real boundary bends — exactly the failure from Chapter 0. As τ → ∞, LWLR becomes ordinary logistic regression. That's a beautiful unification: ordinary logistic regression is just LWLR with the spotlight turned up to infinity.
Let's anchor this with a number. Suppose the true boundary is a gentle arc and we have m = 200 points with a bit of label noise. With τ = 0.05, a query's effective sample size — the sum of weights, roughly "how many points actually vote" — might be just 2 or 3. Fitting a classifier on 3 noisy points is a recipe for wild swings. With τ = 5, the effective sample size is nearly all 200, so the fit is stable but blind to local curvature. With τ = 0.4, maybe 25–40 points vote — enough to average out noise, few enough to stay local. That "effective number of voters = sum of weights" is the cleanest way to feel what τ trades off.
Here is the payoff — the simulation this whole lesson was building toward. Two classes interleave along a curve (a curved truth no straight line can capture). The shaded region is LWLR's decision boundary, recomputed at every pixel by running the full Newton solve from Chapter 3. Drag τ and watch the boundary breathe: small τ makes it wiggle tightly around individual points; large τ straightens it toward a single line. Find the τ where it cleanly traces the curve.
Orange = class 1, blue = class 0. The colored wash is the LWLR boundary (warm = predicts class 1, blue = predicts class 0), painted by solving a fresh weighted logistic regression at every grid cell. Drag τ: watch it morph from a wiggly local boundary (small τ) to a nearly straight global one (large τ). Toggle the data noise to see small-τ overfitting chase the noise. Resample draws fresh data.
Play with it until the lesson clicks: at τ ≈ 0.12 the boundary is a jagged, paranoid thing that loops around stray points (variance). At τ ≈ 2.5 it's nearly a straight diagonal that slices through the middle, getting the curve's ends wrong (bias). Somewhere around τ ≈ 0.4–0.6 it smoothly follows the true arc. That region — not too tight, not too loose — is the goal of every bias/variance trade-off you'll ever tune.
You don't eyeball it in practice — you use cross-validation. Hold out part of the data, fit LWLR with several candidate τ values, and measure validation accuracy (or held-out log-likelihood) for each. The τ with the best validation score wins. The curve of "validation error vs. τ" is famously U-shaped: high on the left (tiny τ, overfit), high on the right (huge τ, underfit), with a minimum in the middle. Picking τ = the bottom of that U is the disciplined version of the eyeballing you just did.
We've built the whole thing. Now the honest accounting: what does this power cost, where does LWLR sit in the family of methods, and what should you carry away?
Ordinary logistic regression is parametric and eager: it does expensive work once (training), boils the entire dataset down to a fixed vector θ, then throws the data away. Prediction is a single dot product — lightning fast, and the model is tiny regardless of how much data trained it.
LWLR is the opposite: non-parametric and lazy. It does no training. It keeps the entire training set forever, because every prediction needs all of it. For each query, it computes m weights (one per training point), then runs ~6 Newton iterations, each of which builds and inverts a (d×d) Hessian where d is the feature dimension. So per query the cost is roughly O(m·d + d3) — and there's no amortization, because nothing is reused between queries.
Concretely: classify 10,000 new points against 50,000 training examples, and you run 10,000 independent Newton solves, each touching all 50,000 points. That's why LWLR shines on small-to-medium datasets with low feature dimension and curved boundaries, and becomes impractical at large scale. The Code Lab below paints its boundary by solving at every grid cell — you'll feel the cost as the few seconds it takes to run.
| Method | Boundary | Training | Relation to LWLR |
|---|---|---|---|
| Ordinary logistic regression | One straight line | Once, eager | LWLR with τ → ∞ |
| k-Nearest Neighbors | Jagged, local | None, lazy | LWLR with τ → 0 (hard spotlight) |
| LWLR | Smoothly curved | None, lazy | The dial between the two above |
| Kernel methods (SVM) | Curved, global | Once, eager | Also use a kernel, but learn one global model |
| Locally-weighted linear regression | Curved regression | None, lazy | Same trick for regression instead of classification |
The deep connection: LWLR sits between ordinary logistic regression (its τ→∞ limit, a global straight line) and k-Nearest Neighbors (its τ→0 limit, a purely local vote). The Gaussian weight is a kernel — the same idea that powers kernel SVMs and kernel density estimation — but here it's used to re-weight examples for a local fit rather than to lift data into a high-dimensional space. And the locally-weighted trick isn't specific to classification: swap the logistic log-likelihood for squared error and you get locally-weighted linear regression (the original CS229 LWR), which curves a regression line the same way we just curved a classifier.
· Logistic Regression & the Perceptron — the global classifier LWLR localizes; LWLR is this with τ→∞.
· Linear Regression & LMS — the regression cousin; apply the same weighting trick to get locally-weighted linear regression.
· Contrastive Learning / similarity — the Euclidean-distance kernel is a similarity measure, the same currency as embeddings & kNN.
Now scroll down. The Workbook drills the hand calculations — weights, a Newton step, the limits, the sign bug. Then the Code Lab hands you the real thing: edit τ, press Run, and watch the boundary you've been reasoning about paint itself.
Work these by hand before peeking. Click a card to reveal the worked solution. Constants you'll reuse: e−0.5≈0.607, e−1≈0.368, e−2≈0.135, e−4.5≈0.011, g(0)=0.5, g(1)≈0.731, g(2)≈0.881, g(−2)≈0.119.
Two rungs. First you build the weighting kernel and watch the soft spotlight light up the plane — the field the chapter described, but as a real 2D image driven by your exponential. Then you build the Newton update itself and paint the curved decision boundary at every cell of a dense grid. Each lab ships runnable but wrong — run it, see the failure, fix the one line that is the concept. Press Run (Python boots once; the grid solve takes a couple seconds).