Logistic regression for classification

Turn a linear score into a probability with the sigmoid and train it by gradient descent on log loss.

Python
import math

xs = [1, 2, 3, 4, 5, 6]
labels = [0, 0, 0, 1, 1, 1]


def sigmoid(z):
    return 1 / (1 + math.exp(-z))


w = b = 0.0
for _ in range(3000):
    grad_w = sum((sigmoid(w * x + b) - y) * x for x, y in zip(xs, labels)) / len(xs)
    grad_b = sum((sigmoid(w * x + b) - y) for x, y in zip(xs, labels)) / len(xs)
    w -= 0.5 * grad_w
    b -= 0.5 * grad_b

predictions = [1 if sigmoid(w * x + b) >= 0.5 else 0 for x in xs]
print(predictions == labels)
print(round(sigmoid(w * 2 + b), 3), round(sigmoid(w * 3.5 + b), 3), round(sigmoid(w * 5 + b), 3))
print(round(-b / w, 2))
Output
True
0.001 0.537 0.999
3.47

Explanation

Despite its name, logistic regression is a classification method. It computes a linear score z = w·x + b and passes it through the sigmoid function σ(z) = 1 / (1 + e⁻ᶻ), which maps any number to a value between 0 and 1 that is read as the probability of the positive class. A prediction is positive when that probability is at least 0.5, which happens exactly when z >= 0.

The model is trained by minimizing the log loss (binary cross-entropy), which punishes confident wrong predictions heavily. Its gradient has the same pleasant shape as in linear regression: ∂L/∂w = average of (σ(z) − y)·x. The decision boundary, where the probability is 0.5, is at x = −b / w for one feature.

The data below are separable: every example with x <= 3 is class 0 and every example with x >= 4 is class 1. The trained model classifies all six correctly and places the boundary near 3.47, in the gap between 3 and 4. The exact position within the gap depends on how long training runs. For separable data, the unregularized weights would grow forever as training continues, so practical implementations add a regularization penalty or stop early.

Explore the dry run
Guided walkthroughReady
1import math
2
3xs = [1, 2, 3, 4, 5, 6]
4labels = [0, 0, 0, 1, 1, 1]
5
6
7def sigmoid(z):
8 return 1 / (1 + math.exp(-z))
9
10
11w = b = 0.0
12for _ in range(3000):
13 grad_w = sum((sigmoid(w * x + b) - y) * x for x, y in zip(xs, labels)) / len(xs)
14 grad_b = sum((sigmoid(w * x + b) - y) for x, y in zip(xs, labels)) / len(xs)
15 w -= 0.5 * grad_w
16 b -= 0.5 * grad_b
17
18predictions = [1 if sigmoid(w * x + b) >= 0.5 else 0 for x in xs]
19print(predictions == labels)
20print(round(sigmoid(w * 2 + b), 3), round(sigmoid(w * 3.5 + b), 3), round(sigmoid(w * 5 + b), 3))
21print(round(-b / w, 2))

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
The sigmoid squashes any score into (0, 1), so the output can be read as a probability.—
—
The log-loss gradient: prediction minus label, weighted by the input.—
—
After training, all six examples are classified correctly.—
True
Probability of class 1: near 0 at x = 2, about one half in the gap at 3.5, near 1 at x = 5.—
True
0.001 0.537 0.999
The decision boundary is where w·x + b = 0, at x ≈ 3.47.—
True
0.001 0.537 0.999
3.47

Cost and common mistakes

Each training step costs O(n) for n examples (O(n·d) with d features), the same as linear regression.

  • Logistic regression produces a probability, not a certainty. Use a threshold appropriate to the costs of errors, not always 0.5.
  • On perfectly separable data the weights keep growing, making the probabilities extreme. Use regularization.
  • The model is linear in its inputs, so a single logistic regression cannot separate classes that need a curved boundary without feature engineering.

Try it yourself

Roughly what probability does the model give for x = 3.47?

Reveal answer and reasoning

About 0.5, because that is the decision boundary where w·x + b = 0 and σ(0) = 0.5.

Further reading