EVERYTHING AIAI engineering, made visual
0/18 complete
LESSON 03 · MACHINE LEARNING × AI · BUILD

One straight score,
squashed into yes or no.

p = σ(w·x + b) turns any number into a probability. A threshold turns that probability into a decision, and a loss turns every mistake into a direction to move.

75 MIN · 8 CHAPTERSPREREQ · PHASE 2 · LESSONS 01–02
FIG. 03 / SIGMOID ON THE LOGIT AXIS
LOG-ODDS z = 0.00 · P = 0.5000 · ODDS = 1.00 σ(z) threshold
LESSON 03TYPE · BUILD~75 MINPREREQ · PHASE 2 · LESSONS 01–02ORIGINAL LESSON ↗
THE 60-SECOND VERSIONThen show me why ↓
01 / A LINE PREDICTS NUMBERS

A class is a choice, not a quantity.

Fit a line to 0/1 labels and you get outputs like −0.05 or 1.25. Neither is a probability, a single far-away point drags the whole line, and a threshold placed on the line moves whenever the slope changes. Classification needs an output bounded to [0, 1].

y = 0.1455·hours − 0.2 → 1.2545 at 10 h
02 / SQUASH THE SCORE

The sigmoid bends any number into a probability.

Compute the same linear score z = w·x + b, then pass it through σ(z) = 1/(1+e^(−z)). Large positive z approaches 1, large negative z approaches 0, and z = 0 lands exactly at 0.5 — the decision boundary.

σ(0) = 0.5 · σ(2) = 0.8808 · σ(−2) = 0.1192
03 / PUNISH CONFIDENT MISTAKES

Log loss cares how wrong, not just whether.

Binary cross-entropy charges −ln(p) for a true 1 and −ln(1−p) for a true 0. A confidently wrong p = 0.1 costs 2.3026 — over three times the 0.6931 of a coin flip, and twenty-two times the 0.1054 of being sure and right. Its gradient simplifies to p − y, so learning never stalls when the model is confidently wrong.

L = −[y·ln p + (1−y)·ln(1−p)]
MENTAL MODEL IN ONE SENTENCE

Logistic regression is a linear score plus a sigmoid plus a threshold: z = w·x + b says how strongly the evidence points to class 1, p = σ(z) converts that score into a probability, and training nudges w and b until the labels you observed become likely.

By the end you will be able to compute a probability and its log-odds by hand, explain why MSE stalls and log loss does not, write the gradient update from memory, and choose a threshold instead of accepting 0.5 by default.

THE STEP PROBLEM

A straight line
cannot say yes or no.

Labels are 0 or 1 — a step, not a ramp. Linear regression is built for ramps: it minimizes squared error and can output any number at all.

Take ten students and record hours studied against pass (1) or fail (0). The labels jump from 0 to 1 between hour 4 and hour 5. Least squares fits the best straight line through those points: y = 0.1455·hours − 0.2. At hour 1 it predicts −0.0545; at hour 10 it predicts 1.2545. Negative probability and more-than-certain probability are both nonsense, and thresholding the line at 0.5 does not fix the deeper problem: the 0.5 crossing sits at hour 4.81 only because of the slope, so any change in the data slides the boundary.

Classification needs three things linear regression does not provide: bounded output in (0, 1), a sharp transition from one class to the other, and insensitivity to far-away points. The next two chapters build exactly that, starting from a quantity you already use without naming: odds.

10−0.051.25p = 0.5 at 4.81 hten students: line y = 0.1455x − 0.2100.51 at 1 h+ one student at 50 h → slope collapses50 hstudy hours (1 → 10, outlier off-scale)
Squared error makes a line fit a step the only way it can: by sloping through it. The predictions leave [0, 1] on both ends, and a single far-away student flattens the line for everyone — the probability of passing after one hour of study jumps from −0.05 to 0.51.
Worked fit: the least-squares numbers behind the line

Least squares picks the slope and intercept that minimize the sum of squared vertical errors. With n = 10 students:

hours x: 1 2 3 4 5 6 7 8 9 10 labels y: 0 0 0 0 1 1 1 1 1 1 x̄ = 55/10 = 5.5 ȳ = 6/10 = 0.6 numerator Σ (xᵢ − x̄)(yᵢ − ȳ) = 12 denominator Σ (xᵢ − x̄)² = 82.5 w = 12 / 82.5 = 0.14545 b = ȳ − w·x̄ = 0.6 − 0.14545·5.5 = −0.2

Numeric check: at 5 hours the model says 0.14545·5 − 0.2 = 0.5273, and it crosses 0.5 at x = (0.5 + 0.2)/0.14545 = 4.8125 hours. Nothing in the fit prevents the line from leaving [0, 1].

Enrichment: one outlier, refit by hand

Add a single student who studied 50 hours and passed. The means move — and so does every prediction, even for students nowhere near hour 50.

n = 11 x̄ = 105/11 = 9.5455 ȳ = 7/11 = 0.6364 refit: w = 0.0150 b = 0.4935 (slope fell 90%: 0.14545 → 0.0150) predictions: 1 h → 0.5085 (was −0.0545) 5 h → 0.5683 (was 0.5273) 10 h → 0.6432 (was 1.2545, now barely above 0.5) 0.5 crossing: x = (0.5 − 0.4935)/0.0150 = 0.4355 h (it moved from hour 4.81 to half an hour!)

One observation changed the meaning of the model for every other student. Logistic regression will not be immune to outliers in the features, but a sigmoid keeps the output bounded, so a far point cannot make the predicted probability 1.25 or −0.05.

Quick check

A linear model trained on pass/fail labels predicts 1.7 for a new student. What is the right interpretation?

ODDS, THE MULTIPLIER

Probability is a share.
Odds is a ratio.

Before the S-curve, there is a smaller idea: the same belief can be written as a probability, as odds, or as log-odds — and the model works in the last one.

If a probability is p = 0.8, the odds are p/(1−p) = 0.8/0.2 = 4, which bookmakers write as 4:1. Flip it around: odds of 4:1 mean 4 parts success to 1 part failure, so p = 4/5 = 0.8. The log-odds (also called the logit) take the natural log of the odds: ln(4) = 1.3863.

Log-odds look like a strange choice until you see their shape: a probability squeezes a lot of meaning into the last hundredth near 0 and near 1, while log-odds stretch that whole interval across every real number. Odds of 1 (p = 0.5) give log-odds 0; confidence for class 1 grows positive, confidence for class 0 grows negative, and the two directions are mirror images.

011:91:41:2.331:12.33:14:19:1odds = p/(1−p)−2.20−1.39−0.8500.851.392.20log-odds = ln(p/(1−p)): stretched to (−∞, ∞)p = 0.8 · odds 4 · log-odds 1.3863
The same number described three ways. Probability is the share of successes; odds is the ratio of success to failure; log-odds is the dial that spreads (0, 1) across the entire number line, symmetric around 0.5.
Probability pOdds p/(1−p)Log-odds ln(p/(1−p))
0.100.1111−2.1972
0.200.2500−1.3863
0.501.00000.0000
0.804.00001.3863
0.909.00002.1972
0.9519.00002.9444
Derivation: the sigmoid is the logit, back to front

The sigmoid was not pulled from thin air. It is exactly the inverse of the log-odds function, which is why the model’s raw output z can be read as a log-odds.

start: z = ln( p / (1 − p) ) exponentiate: e^z = p / (1 − p) multiply by (1 − p): e^z · (1 − p) = p expand: e^z − p·e^z = p collect p on the right: e^z = p·(1 + e^z) solve for p: p = e^z / (1 + e^z) multiply top and bottom by e^(−z): p = 1 / (1 + e^(−z)) = σ(z) ✓

Numeric check, exactly. If p = 0.8 then odds = 4 and z = ln 4 = 1.3863. Feed that back: e^(−1.3863) = 1/4, so σ(1.3863) = 1/(1 + 1/4) = 4/5 = 0.8 ✓. Try p = 0.95: odds = 19, z = ln 19 = 2.9444, and 1/(1 + 1/19) = 19/20 = 0.95 ✓.

Quick check

A model outputs a log-odds of 2.1972 for an email. What probability is that?

THE S-CURVE

Squash the score
into a probability.

The sigmoid takes the linear score z = w·x + b — any number on the logit dial — and bends it into a probability between 0 and 1.

The definition is one line: σ(z) = 1/(1 + e^(−z)). Three values anchor the whole curve: σ(0) = 0.5 (the boundary), σ(2) = 0.8808 (fairly confident class 1), and σ(−2) = 0.1192 (fairly confident class 0). As z grows the output approaches 1 but never reaches it; as z shrinks it approaches 0. The curve is smooth everywhere, which is what makes it trainable.

The sigmoid is also the missing piece from the previous chapter: its output is bounded, so no outlier can produce a probability of 1.25. What an outlier can still do is push z far into the flat tail, where the curve barely responds — a saturation problem we will meet again when gradients vanish.

Sigmoid explorer

Weight w sets the steepness, bias b slides the curve left or right, and x₀ is the input you are scoring. The dashed vertical line is the decision boundary, where z = 0 and p = 0.5.

z₀ = w·x₀ + b = 1.5·1.2 + -0.5 = 1.3000 p = σ(z₀) = 0.7858 odds = p/(1−p) = 3.6693 log-odds = ln(odds) = 1.3000 derivative with respect to z: σ′(z₀) = p(1−p) = 0.1683 tangent in this picture: dp/dx = w·σ′(z₀) = 0.2524 decision boundary: x = 0.3333 (z = 0, p = 0.5)

Push w negative and the curve flips: the probability now falls as x grows. Bias only moves the curve — it never changes its shape.

Derivation: the sigmoid derivative is p(1 − p)

Training needs to know how fast the probability changes when the score moves. Differentiate the quotient, then simplify.

σ(z) = (1 + e^(−z))^(−1) dσ/dz = −(1 + e^(−z))^(−2) · (−e^(−z)) = e^(−z) / (1 + e^(−z))² notice: 1 − σ(z) = e^(−z) / (1 + e^(−z)) so: σ(z)·(1 − σ(z)) = e^(−z) / (1 + e^(−z))² ✓ σ′(z) = σ(z)(1 − σ(z)) = p(1 − p)

Numeric checks. At z = 0: p = 0.5, so σ′(0) = 0.5·0.5 = 0.25 — the steepest the curve ever gets. At z = 1: p = 0.7311, so σ′(1) = 0.7311·0.2689 = 0.1966. At z = 2: 0.8808·0.1192 = 0.1050. A numerical derivative at z = 1, (σ(1.000001) − σ(0.999999))/0.000002, gives 0.19661 — matching p(1−p) to four decimals.

The shortcut to remember: the slope of the sigmoid at a point is p(1 − p), where p is the height at that point. It peaks at the decision boundary and fades toward zero in both tails.

THE COST OF CONFIDENCE

Punish confident mistakes,
not just mistakes.

A probability needs a grade. Binary cross-entropy scores the probability the model gave to the truth — and it grows without bound as that probability shrinks.

The loss for one example is −(y·ln p + (1−y)·ln(1−p)). If the true label is 1 the second term vanishes, leaving −ln p: sure and right costs 0.1054 at p = 0.9, a coin flip costs 0.6931 at p = 0.5, and confidently wrong costs 2.3026 at p = 0.1. If the true label is 0 the formula mirrors: −ln(1−p). The average over every example is the training loss, usually just called log loss or binary cross-entropy.

Log loss vs MSE vs 0–1 loss

Drag the confidence slider. The true label is fixed; watch what each loss charges for a wrong answer at probability p.

y = 1, p = 0.10 BCE = −[y·ln p + (1−y)·ln(1−p)] = 2.3026 MSE = (p − y)² = 0.8100 0–1 = 1 gradient through the sigmoid (with respect to z): cross-entropy: p − y = -0.9000 MSE: 2(p − y)·p(1 − p) = -0.1620 ratio: 5.6×

Near p = 0.5 all three losses agree in spirit. Push p away from the true label and cross-entropy grows without bound, while MSE levels off — and its gradient shrinks exactly when the model is most wrong.

Derivation: why the loss is exactly negative log-likelihood

There is a reason this formula and not some other. It is the loss that comes from asking the model to make the observed labels as likely as possible.

for one example, with p = σ(z): if y = 1, likelihood = p if y = 0, likelihood = 1 − p combine: likelihood = p^y · (1 − p)^(1−y) make every example likely at once: multiply them take the log (turns products into sums): Σ [ y·ln p + (1 − y)·ln(1 − p) ] convention is to minimize, and to average over n: BCE = −(1/n) Σ [ y·ln p + (1 − y)·ln(1 − p) ] ✓

Numeric check. For a two-example batch, (y=1, p=0.9) and (y=0, p=0.8): the per-example costs are −ln(0.9) = 0.1054 and −ln(0.8) = 0.2231, so the average is (0.1054 + 0.2231)/2 = 0.1643. Notice the loss never uses ln(0.9) for the second example — it uses the probability the model assigned to the label that actually happened, 0.8.

Derivation: why not MSE? Follow the gradient

The instinct is to reuse MSE = (p − y)². Put a sigmoid in the middle and the derivative picks up an extra factor of σ′(z) = p(1−p):

d(MSE)/dz = 2(p − y) · p(1 − p) d(BCE)/dz = p − y (derived in the next chapter) at z = −4 (p = 0.0180) with y = 1: BCE push = 0.0180 − 1 = −0.9820 MSE push = 2(−0.9820)(0.0177) = −0.0347 BCE is about 28× larger — exactly when the model is most wrong convexity: d²(BCE)/dz² = p(1 − p) ≥ 0 for every z d²(MSE)/dz² turns negative in the tail: at z = −2 it is about −0.119

Two failures in one: MSE’s gradient vanishes when the model is confidently wrong (the tail flattens), and its surface is not convex, so there can be local minima and plateaus. BCE is convex in w and b and keeps a large honest gradient where it matters. Teaching simplification: this is the single-example picture; for a full model the same argument applies term by term, and convexity holds because z is linear in the weights.

Quick check

The true label is 0 and the model predicts p = 0.9. What is the log loss for this example?

LEARN FROM ERRORS

The gradient is just
the error again.

Push the log-loss through the sigmoid with the chain rule and the p(1−p) factors cancel. What is left is (p − y), the prediction minus the label.

Training repeats one loop: compute p for every example, average the errors, and step every weight against its gradient. The surprise is how clean the gradients are — dw = (1/n)·Σ(p−y)·x and db = (1/n)·Σ(p−y). These are the same formulas linear regression uses. The only change is that p now comes out of a sigmoid instead of a straight line.

Derivation: the chain rule makes p(1−p) cancel

Write the per-example loss as a function of the score z, and differentiate in two steps.

loss(z) = −[ y·ln σ(z) + (1 − y)·ln(1 − σ(z)) ] step 1, derivative with respect to p: d loss/dp = −y/p + (1 − y)/(1 − p) = (p − y) / (p(1 − p)) step 2, derivative of p with respect to z: dp/dz = σ′(z) = p(1 − p) multiply (chain rule): d loss/dz = (p − y)/(p(1 − p)) · p(1 − p) = p − y ✓ average over n examples, then apply dw = dloss/dz · x: dL/dw = (1/n) Σ (pᵢ − yᵢ)·xᵢ dL/db = (1/n) Σ (pᵢ − yᵢ)

Numeric checks. If p = 0.7311 and y = 1, the push on z is 0.7311 − 1 = −0.2689: lower z a little. If p = 0.1192 and y = 0, the push is 0.1192 − 0 = +0.1192: raise z. Sign and size both make sense — the farther the probability is from the label, the bigger the correction.

Training console

Four examples: (x=1, y=0), (x=2, y=0), (x=3, y=1), (x=4, y=1). Step the gradient descent loop and watch every number move. Defaults match the worked table in the chapter.

#xyz = w·x + bp = σ(z)lossp − y
1100.00000.50000.69310.5000
2200.00000.50000.69310.5000
3310.00000.50000.6931-0.5000
4410.00000.50000.6931-0.5000

Loss history, left to right. Each bar is the average binary cross-entropy after one full-batch update.

step 0 · L = 0.6931 w = 0.0000 b = 0.0000 gradients now: dw = (1/n)·Σ(p−y)·x = -0.5000 db = (1/n)·Σ(p−y) = 0.0000 one update with lr = 0.5: w ← 0.0000 − 0.5·( -0.5000 ) = 0.2500 b ← 0.0000 − 0.5·( 0.0000 ) = 0.0000

At step 0 every p = 0.5, so the errors are large and the first step is the biggest. As p approaches the labels, errors shrink and updates get smaller — the same “error × input” rule linear regression uses, with p coming from the sigmoid.

Worked example: two steps on four points

Data: (x, y) = (1, 0), (2, 0), (3, 1), (4, 1). Start at w = 0, b = 0 with learning rate 0.5. Everything below is exact arithmetic — the same numbers the console shows.

Stepwbp for x = 1, 2, 3, 4Lossdwdb
00.00000.00000.5, 0.5, 0.5, 0.50.6931−0.50000.0000
10.25000.00000.5622, 0.6225, 0.6792, 0.73110.6250−0.05780.1487
20.2789−0.07440.5510, 0.6186, 0.6819, 0.73910.6124−0.05250.1476

Read step 0 carefully. With w = b = 0 every prediction is 0.5, so the errors are (0.5, 0.5, −0.5, −0.5). The biases cancel: db = 0. The weights do not: the first two examples push w down, the last two push it up, but the label-1 examples sit farther out, so dw = −0.5 and w becomes 0.25. Loss falls from 0.6931 to 0.6250; by step 2 it is 0.6124, and the errors keep shrinking. That is the whole algorithm.

From scratch — Pythonpython
import math

def sigmoid(z):
    z = max(-500, min(500, z))
    return 1.0 / (1.0 + math.exp(-z))

class LogisticRegression:
    def __init__(self, n_features, learning_rate=0.1):
        self.weights = [0.0] * n_features
        self.bias = 0.0
        self.lr = learning_rate

    def predict_proba(self, x):
        z = sum(w * xi for w, xi in zip(self.weights, x)) + self.bias
        return sigmoid(z)

    def compute_loss(self, X, y):
        total = 0.0
        for xi, yi in zip(X, y):
            p = max(1e-15, min(1 - 1e-15, self.predict_proba(xi)))
            total += yi * math.log(p) + (1 - yi) * math.log(1 - p)
        return -total / len(y)

    def fit(self, X, y, epochs=1000):
        n = len(y)
        for _ in range(epochs):
            dw = [0.0] * len(self.weights)
            db = 0.0
            for xi, yi in zip(X, y):
                error = self.predict_proba(xi) - yi
                for j in range(len(dw)):
                    dw[j] += error * xi[j]
                db += error
            for j in range(len(dw)):
                self.weights[j] -= self.lr * (dw[j] / n)
            self.bias -= self.lr * (db / n)
        return self
Forty lines: sigmoid, a clipped log-loss, and full-batch gradient descent. NumPy and scikit-learn are the same loop vectorized and tuned.
WHERE THE ANSWER FLIPS

The boundary is a line.
The threshold is a choice.

The model always flips class where z = 0 and p = 0.5. Moving the flip point to 0.8 does not retrain the model — it changes which mistake you would rather make.

With two features the decision boundary is the set of points where w₁x₁ + w₂x₂ + b = 0 — for logistic regression, always a straight line. One side has positive z (predict 1), the other negative z (predict 0), and the probability is exactly 0.5 on the line itself. The weight vector (w₁, w₂) is perpendicular to the line and points toward class 1; its length sets how fast confidence grows as you move away.

Decision boundary, trained live

Drag any point (or use the controls) to move it. Press train and watch full-batch gradient descent rotate the line until the red error rings disappear.

accuracy = 3/6 = 0.5000 w = [0.4000, 0.9000] b = -1.2000 boundary: 0.4000·x₁ + 0.9000·x₂ + -1.2000 = 0 x₂ = -0.4444·x₁ + 1.3333

Start from a bad line: the model is right about class 1 and wrong about class 0. Each step nudges w toward the points it misses, and the boundary swings until both clusters sit on the right side.

Worked boundary: w = (1, 1), b = −6
boundary: 1·x₁ + 1·x₂ − 6 = 0 → x₂ = 6 − x₁ point (2, 2): z = 2 + 2 − 6 = −2 p = σ(−2) = 0.1192 → class 0 point (5, 5): z = 5 + 5 − 6 = +4 p = σ(4) = 0.9820 → class 1 point (3, 3): z = 0, p = 0.5 — exactly on the fence distance from the line: z / ‖w‖ = z / √2 (5, 5) is 4/1.414 = 2.83 units away, confidently class 1

Because z is linear, the probability depends only on the signed distance from the line, scaled by ‖w‖. A bigger ‖w‖ makes the same geometry more confident — which is exactly what regularization will push back on.

Thresholds and the confusion matrix

The 0.5 flip point is convention, not law. For a spam filter you might accept spam only above 0.8 to keep false alarms low; for cancer screening you might flag anything above 0.2 so nothing is missed. The four outcomes of every binary decision are the confusion matrix — TP (flagged and true), FP (flagged but false), FN (missed), TN (correctly ignored) — and they give three numbers worth knowing:

precision = TP / (TP + FP) "of all flags, how many were real?" recall = TP / (TP + FN) "of all real cases, how many did we catch?" F1 = 2·P·R / (P + R) "one number balancing both"

Threshold and confusion matrix

Twelve test emails with their spam probabilities. Lower the threshold to catch more spam (recall up, precision down); raise it to flag only the sure things (precision up, recall down).

0.95
y=1
0.90
y=1
0.85
y=0
0.80
y=1
0.70
y=0
0.65
y=1
0.55
y=1
0.45
y=0
0.40
y=0
0.30
y=0
0.20
y=0
0.10
y=0

Sorted left to right by probability. The accent line is the threshold: everything to its left is predicted spam (1), everything to the right is not (0). Correct calls are tinted; mistakes are red.

TP · predicted 1, actual 1
5
FP · predicted 1, actual 0
2
FN · predicted 0, actual 1
0
TN · predicted 0, actual 0
5
threshold t = 0.50 → predicted spam: 7/12 accuracy = (TP + TN)/12 = (5 + 5)/12 = 0.8333 precision = TP/(TP + FP) = 5/(5 + 2) = 0.7143 recall = TP/(TP + FN) = 5/(5 + 0) = 1.0000 F1 = 2·P·R/(P + R) = 0.8333 TP = 5 FP = 2 FN = 0 TN = 5

Watch accuracy stay flat while precision and recall trade places. That is why one number is never enough for an imbalanced problem: the threshold decides which mistake you would rather make.

Worked threshold table: twelve test emails

Probabilities sorted high to low: 0.95, 0.90, 0.85, 0.80, 0.70, 0.65, 0.55, 0.45, 0.40, 0.30, 0.20, 0.10; the true spam labels are 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 — five spam, seven not. Predict 1 when p ≥ t:

tTPFPFNTNPrecisionRecallF1Accuracy
0.3055020.50001.00000.66670.5833
0.5052050.71431.00000.83330.8333
0.8031260.75000.60000.66670.7500
0.9020371.00000.40000.57140.7500

Watch what each move buys. Raising t from 0.5 to 0.8 removes the false alarm at 0.85·(y=0) and the one at 0.70·(y=0), improving precision from 0.7143 to 0.75, but it also drops the true spam at 0.65 and 0.55, cutting recall from 1.0 to 0.6. Accuracy alone would have called t = 0.9 (0.75) worse than t = 0.5 (0.8333) while its precision is perfect — accuracy hides which errors you are making.

Quick check

You raise the threshold from 0.5 to 0.8. What usually happens?

TAMING THE WEIGHTS

If the data separates,
the weights never stop growing.

A perfect split has no best answer: every weight increase makes the probabilities more extreme and the loss a little smaller. A penalty on weight size gives the optimizer somewhere to stop.

Logistic regression never says “confident enough”. If the classes can be separated exactly, doubling every weight pushes every p closer to 0 or 1 and lowers the log loss — the minimum is at infinity. Real data is rarely perfectly separable, but near-separable data still produces huge weights and wildly overconfident probabilities. The cure is to add a penalty to the loss:

L2: J(w, b) = BCE(w, b) + λ · Σ wⱼ² L1: J(w, b) = BCE(w, b) + λ · Σ |wⱼ| λ = 0 is no penalty; larger λ pulls the weights harder toward 0. The bias b is usually left out of the penalty.

Weight shrinkage, live

Both models train on the same six points with gradient descent. The dashed boundary is unregularized; the solid one adds an L2 penalty and gives up a little training confidence to keep the weights small.

λ = 02.646
λ = 0.101.036
λ = 0.10 · 500 steps · lr = 0.3 no penalty : w = [1.117, 2.399] b = -6.707 this run : w = [0.728, 0.737] b = -2.989 ‖w‖ shrinks 60.9% penalty λ(w₁² + w₂²) = 0.1073 penalty gradient 2λw = [0.1457, 0.1473]

λ = 0 lets the weights grow as long as they keep lowering the loss. Turn λ up and the boundary pivots back toward the middle: training accuracy may dip, but the model stops chasing the last hard point.

Worked penalty: gradients and shrinkage
L2 penalty derivative: d(λ·w²)/dw = 2λ·w combined gradient: dJ/dw = (1/n) Σ (p − y)·x + 2λ·w numeric check: w = [3, 4], λ = 0.1 penalty value = 0.1 · (3² + 4²) = 0.1 · 25 = 2.5 penalty gradient = 2 · 0.1 · [3, 4] = [0.6, 0.8] with lr = 0.1 and no data gradient: w ← [3, 4] − 0.1·[0.6, 0.8] = [2.94, 3.92] equivalently: multiply by (1 − 2·λ·lr) = 1 − 0.02 = 0.98 → 2% smaller every step, forever the lab, after 500 steps at lr = 0.3 on six points: λ = 0 → w = [1.117, 2.399] ‖w‖ = 2.646 λ = 0.10 → w = [0.728, 0.737] ‖w‖ = 1.036 λ = 1.00 → w = [0.145, 0.148] ‖w‖ = 0.207

L1 behaves differently because the absolute value has a constant slope: at w = 3 the L1 gradient is 0.1 while the L2 gradient is 0.6; at w = 0.1 the L1 gradient is still 0.1 while L2’s has faded to 0.02. L1 keeps pushing small weights all the way to exactly zero(sparse models), while L2 shrinks everything smoothly and never eliminates a feature on its own.

PenaltyAdds to the lossGradientWhat you get
L2 (ridge)λ·Σ wⱼ²2λ·wⱼ, proportional to sizeSmall, smooth weights; every feature kept; the default
L1 (lasso)λ·Σ |wⱼ|λ·sign(wⱼ), constant sizeMany weights become exactly 0; automatic feature selection

The other imbalance: class counts

There is a second way a classifier goes quietly wrong. Take 100 examples with 95 negatives and 5 positives. A model that always predicts “negative” scores 95% accuracy while catching zero positives — recall 0, precision undefined, F1 0. Accuracy alone is blind to imbalance. Two standard responses: change the threshold (predict positive more eagerly), or change the cost by weighting the rare class. Giving each positive 19× the weight of a negative is like duplicating the 5 positives until the classes balance, and the optimizer stops ignoring them.

CHECK YOURSELF

Five questions.
Then the terms worth keeping.

Answer before you look. The loss question and the threshold question are the two that show up in real model reviews.

0 / 5 answered · 0 correct

01What is the range of the sigmoid function's output?

02Why is logistic regression called “regression” even though it classifies?

03Why is binary cross-entropy used instead of MSE for logistic regression?

04A spam filter has precision = 0.95 and recall = 0.60. What does that mean?

05In softmax regression for 4 classes, what is true about the output probabilities?

Key terms, demystified

Click a card to swap the lazy description for what it actually means.

Exercises from the lesson

Four short problems, from feature engineering to a hand-computed gradient step. Try first; a worked answer is one click away.

  1. Generate a dataset that is not linearly separable — points on two concentric circles — and train logistic regression. Record the accuracy, add polynomial features (x1², x2², x1x2), and train again. Explain the jump.
    Show one worked answer

    With class 1 inside radius 1.5 and class 0 between radii 3 and 4.5, every line through the plane must cut both rings, so the best raw-feature accuracy is around 50% plus noise. Adding x1², x2², x1·x2 gives the model z = w1x1² + w2x2² + w3x1x2 + w4x1 + w5x2 + b; the boundary is still a line, but in the 5-dimensional feature space it can be the circle x1² + x2² = c for any c in the empty gap between the rings, say c = 6. One weight on (x1² + x2²) plus a bias is enough, so accuracy jumps toward 100%. Moral: logistic regression is linear in the features you hand it — feature engineering changes what is linearly separable.

  2. Build a 3-class confusion matrix for the softmax model and compute per-class precision and recall. Which class is hardest to classify, and why?
    Show one worked answer

    Treat each class k as one-vs-rest: TPk is the diagonal cell, FNk is the row sum minus the diagonal, FPk is the column sum minus the diagonal. Suppose a richer test set has 20 examples per class and counts [[16, 4, 0], [3, 15, 2], [0, 1, 19]] (rows = actual, columns = predicted). Class 0: precision 16/(16+3) = 0.8421, recall 16/20 = 0.80. Class 1: precision 15/(15+4+1) = 0.75, recall 15/20 = 0.75. Class 2: precision 19/(19+2) = 0.9048, recall 19/20 = 0.95. Class 1 is hardest: its cluster at (5, 1) sits closest to class 0 at (1, 1) along the x-axis, and class 2 at (3, 5) pulls a few borderline points up. Per-class numbers reveal this; overall accuracy (50/60 = 0.8333) hides it.

  3. Build an ROC curve from scratch for the four scored examples (0.9, y=1), (0.7, y=0), (0.6, y=1), (0.3, y=0). List the (FPR, TPR) points and compute the AUC with the trapezoidal rule.
    Show one worked answer

    Sort by score descending: 0.9(+), 0.7(−), 0.6(+), 0.3(−). Sweep thresholds from 1.0 down to 0.0. At ≥1.0: TP=0, FP=0 → (0, 0). At ≥0.9: TP=1, FP=0, and there are 2 positives and 2 negatives → TPR = 1/2 = 0.5, FPR = 0 → (0, 0.5). At ≥0.7: TP=1, FP=1 → (0.5, 0.5). At ≥0.6: TP=2, FP=1 → (0.5, 1.0). At ≥0.3: TP=2, FP=2 → (1, 1). Trapezoids: (0→0.5) width 0.5 × average height 0.5 = 0.25; (0.5→1) width 0.5 × average height 1.0 = 0.5; AUC = 0.75. A perfect ranking (0.95+, 0.85+, 0.2−, 0.1−) gives AUC = 1.0; a coin flip gives 0.5.

  4. By hand, take three points (x=0, y=0), (x=1, y=1), (x=2, y=1). Start with w = 0, b = 0, learning rate 1.0, and compute one gradient step of logistic regression. Then check the loss before and after.
    Show one worked answer

    Step 0: z = 0 everywhere, so p = 0.5 for all three points and L = −ln(0.5) = 0.6931. Errors p − y are 0.5, −0.5, −0.5. dw = (0.5·0 + (−0.5)·1 + (−0.5)·2)/3 = −1.5/3 = −0.5000; db = (0.5 − 0.5 − 0.5)/3 = −0.1667. Update: w = 0 − 1·(−0.5) = 0.5, b = 0 − 1·(−0.1667) = 0.1667. Step 1: z values are 0.1667, 0.6667, 1.1667, so p = 0.5416, 0.6608, 0.7625 and L = −[ln(0.4584) + ln(0.6608) + ln(0.7625)]/3 = (0.7801 + 0.4143 + 0.2712)/3 = 0.4885. One step cut the loss from 0.6931 to 0.4885.

Terms this lesson borrows from later lessons (or outside)

You do not need to master these here. Each one gets a proper treatment in its own lesson; the one-line meaning is enough to keep reading. Orange dotted underlines in the prose point back to this list.

  • featureOne input measurement per example, the x in z = w·x + b. (Phase 2, Lesson 01)
  • gradient descentRepeatedly step each weight opposite its gradient to reduce the loss. This lesson uses the same loop as linear regression. (Phase 2, Lesson 02)
  • learning rateThe step size in gradient descent. Too small crawls, too large overshoots; sigmoid saturation makes large steps tempting and dangerous. (Phase 2, Lesson 02)
  • mean squared errorThe average squared prediction error, the natural loss for linear regression — and the wrong tool once a sigmoid is in the middle. (Phase 2, Lesson 02)
  • overfittingFitting noise in the training set so the model generalizes worse. Regularization is the standard cure. (Phase 2, Lesson 10)
  • regularizationAdding a penalty on weight size to the loss: L2 shrinks weights smoothly, L1 pushes some to exactly zero. (Phase 1, Lesson 18; Phase 2, Lesson 12)
  • ROC curve & AUCA curve of true-positive rate against false-positive rate as the threshold moves; AUC summarizes it in one number. (Phase 2, Lesson 09)
  • class weightingMaking errors on a rare class cost more, so an imbalanced model stops ignoring it. (Phase 2, Lesson 17)
KEEP GOING

A picture is a start.
Practice is the rest.

This lesson is a port of an open course. Everything here traces back to it — and the next step is running the code yourself.

Lesson text adapted from AI Engineering from Scratch (Phase 02, Lesson 03) and the Math Foundations Notebook reference build. The sigmoid explorer, decision-boundary trainer, loss comparison, threshold sweep, training console and regularization lab, plus the numeric checks and worked exercise answers, are original to this page. Every lab runs in your browser.