← All tutorials

On this page

1 Why a linear score is not a probability 2 The sigmoid function 3 The cross-entropy loss 4 A worked example on one flower 5 The decision boundary 6 Regularization and class weights 7 Logistic regression in practice 8 Failure modes of logistic regression 9 XOR needs a nonlinear boundary 10 References

Logistic Regression

On the regression page a line predicts a number. Here the answer is yes or no: is this iris flower of the species virginica? (The Iris data set holds 150 flowers of three species, four measurements each.) Logistic regression keeps the line and maps its score to a probability with the sigmoid.

Petal length x1, width x2two measurements of one flowerScore zz = w1·x1 + w2·x2 + bSigmoid pthe sigmoid turns z into a probabilityCost and one stepcross-entropy, then one gradient stepBoundary at p = 0.5z = 0 is a straight boundaryThree classesone yes-or-no model per classIn practicethe scikit-learn pipeline, cost and gotchas1123557
Figure 1. Each box's marker is the number of the section that teaches it.

Gradient descent (Section 3) repeats one step on all the samples (the notebook runs 5,000 steps at learning rate 1); each step costs about twice a prediction, the scores and sigmoids and then the gradient.

1 Why a linear score is not a probability

Fit a line to 0-or-1 labels and it predicts values like 2.4 or −0.7, which are not probabilities, and one far point drags the whole line sideways.

So we keep the line as a raw score and pass it through the sigmoid, a function that maps any number into the range 0 to 1. That number reads as a probability, and 0.5 becomes the decision boundary.

2 The sigmoid function

The line's raw score can be anything from minus infinity to plus infinity. The sigmoid maps it into the range 0 to 1:

The sigmoid turns any score into a probability.
σ(z) = 11+e−z
z is the raw score and σ (sigma) here is the sigmoid function, not a standard deviation; e is Euler's number, about 2.718. Big positive scores come out near 1, big negative scores near 0.
σ(0) = 0.5
A score of 0 lands at exactly 50/50. The curve is steepest here, where the decision lives, and flattens at the extremes, where the probability is already near 0 or 1.
σ(−z) = 1 − σ(z)
The curve is symmetric around zero, so a score of −z gives no the same probability that z gives yes.
010.510straight line runs past 1straight line runs below 0sigmoid staysbetween 0 and 1featureno (0)yes (1)
Figure 2. The straight line leaves the 0 to 1 range; the sigmoid stays inside it and crosses 0.5 at 1.

3 The cross-entropy loss

Squared error asks "how far off was the number?" For probabilities the better question is "how surprised were you?" If the truth is yes and you said 90% yes, no surprise. If you said 10% yes, huge surprise. That is the binary cross-entropy cost (the cross-entropy loss with two classes): each sample's cost depends only on the probability it gave its true label.

Binary cross-entropy is the cost of one sample.
L = −log p  (y = 1)
y is the true label, 1 for yes and 0 for no; p = σ(z) is the predicted probability of yes; log is the natural log. For a yes (y = 1) the cost is −log p: about 0.1 at 90% and about 2.3 at 10%: a confident right answer costs almost nothing, a confident wrong one costs a lot.
L = −log(1 − p)  (y = 0)
The cost of a no (y = 0), the same rule mirrored: 1 − p is the probability given to no. The NumPy training step packs both cases into one line, where y switches one term off.

Cross-entropy is the negative log likelihood of a coin-flip model; the Likelihood and EM page shows why.

The gradient, the arrow that points uphill on the cost so the learning step can go the other way and lower it, has a shape you already know:

Same gradient as linear regression, with p in place of ŷ.
∂L∂wj = (p − y)·xj
How much the cost changes when weight wj alone changes (∂ reads "partial"): the error p − y times feature xj. The error is p − 1 for a yes and p for a no, so a sure, right answer barely moves the weights.
∂∂wj 12(ŷ − y)2 = (ŷ − y)·xj
The squared-error gradient from linear regression, where ŷ is the raw line's prediction. Put p in place of ŷ in this result and you get the first line: cross-entropy's log cancels the sigmoid's slope, which squared error would keep as an extra p(1 − p).

One full-batch training step in NumPy, with labels 0 or 1.

import numpy as np
# X: (n, 2), y: (n,) of 0/1
EPS = 1e-12  # keeps log finite
z = X @ w + b
p = 1 / (1 + np.exp(-z))
p = np.clip(p, EPS, 1 - EPS)
yes = y * np.log(p)
no = (1 - y) * np.log(1 - p)
loss = -np.mean(yes + no)
n = len(y)
grad_w = X.T @ (p - y) / n
grad_b = np.mean(p - y)
w = w - lr * grad_w
b = b - lr * grad_b
  1. 1Score z and sigmoid p (Section 2).
  2. 2Cross-entropy averaged over the batch (Section 3).
  3. 3The gradient is (p − y) times each feature.
  4. 4One step downhill with step size lr.

4 A worked example on one flower

Figure 3. One virginica flower is scored, called at 0.5 and used for one learning step.

One flower goes from measurements to a probability to a call, then nudges the weights: add up the weighted measurements, turn the score into a probability, call it at 0.5, and take one learning step.
z = w1·x1 + w2·x2 + b
The score multiplies each measurement by its weight, adds them up and shifts the sum by the bias. A larger weight makes its measurement count for more; the bias moves every flower's score by the same amount.
p = σ(z) = 11+e−z
The sigmoid turns the score into a probability by mapping any number into the range between 0 and 1. Read the fraction as 1 divided by 1 plus e raised to the power −z.
decide p ≥ 0.5
The call is yes when the probability is at least one half, and no otherwise.
wj ← wj − η·(p − y)·xj
In one learning step on this flower, each weight moves against its gradient (p − y)·xj, scaled by the learning rate η (eta, lr in the code). Here y = 1, so p − y is negative and each weight grows along its feature; the bias takes the same step with x set to 1.
w1, x1 the first weight and the petal measurement it multipliesw2, x2 the second weight and the petal measurement it multipliesb the bias, which shifts the boundaryp the flower's probabilityη the learning rate, which sets how far one step goesz the weighted scoreσ the sigmoid, which maps any score to a probability0.5 the call line· timese Euler's number, about 2.718; e−z the power e is raised to≥ at least← becomes; p − y the error

5 The decision boundary

5.1 A threshold draws a straight boundary

The sigmoid hits one half exactly when the raw score is zero.

Why the 0.5 boundary is a straight line.
σ(z) ≥ 0.5 ⇔ z ≥ 0
The sigmoid crosses one half exactly at zero (⇔ reads "exactly when"), so "probability at least one half" is "raw score at least zero".
w1·x1 + w2·x2 + b = 0
The boundary is the points where the score w1·x1 + w2·x2 + b is zero: a straight line, the same line from regression, now working as the boundary between yes and no.

Any other threshold t also gives a straight boundary, just shifted:

The boundary for any threshold, not just one half.
logit(t) = log t1 − t
logit(t) is the log of the threshold t divided by 1 − t, the share that is left. It names the raw score whose sigmoid is exactly t, so the boundary for a general threshold sits where the raw score equals logit(t), and at t = 0.5 this gives the score-zero boundary of the 0.5 card.

Every point whose probability clears t lands on the yes side. Raising t calls fewer flowers yes (fewer false alarms, more misses); lowering it calls more. For virginica, t = 0.1 also calls 15 of the 50 versicolor yes, and t = 0.9 keeps only 41 of the 50 virginica (counts from the 800-step fit in Figure 4).

5.2 More than two classes

For more than two classes, one-vs-rest runs one yes-or-no model per class (three for Iris). The softmax, the multi-class form of the sigmoid, scores all classes at once:

Softmax gives one probability per class, all summing to 1.
pc = ezcΣm ezm
zc is class c's score and m runs over every class. Exponentiate every class score and divide by their sum, so the probabilities pc sum to 1. Exponentiating makes every score positive and widens the gaps, so the top class takes most of the probability; adding the same number to every score changes nothing. With two classes it is the sigmoid again.

The Logistic Regression notebook in the Machine-Learning-Concepts repo runs this on the 150 Iris flowers: two petal measurements, centered on the median and scaled to unit spread, and one yes-or-no classifier per class.

One-vs-rest in scikit-learn; StandardScaler centers on the mean rather than the notebook's median.

from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.multiclass import OneVsRestClassifier
from sklearn.preprocessing import StandardScaler
X, y = load_iris(return_X_y=True)
petals = X[:, 2:4]  # petal size
sc = StandardScaler()
X = sc.fit_transform(petals)
base = LogisticRegression()
ovr = OneVsRestClassifier(base)
ovr.fit(X, y)
virg = ovr.estimators_[2]
p = virg.predict_proba(X)[:, 1]
t = 0.5  # threshold
called_yes = p >= t
  1. 1Keeps the two petal measurements and scales them.
  2. 2Fits one yes-or-no model per class.
  3. 3Virginica probability, called yes at threshold t (Section 5).
setosa versicolor virginica
class

Figure 4. The solid line is the chosen class's boundary at t; filled flowers are called yes.

0.50

Setosa splits off cleanly and virginica nearly so. Versicolor sits between the other two, so its one straight boundary calls only 12 of its 50 flowers yes at t = 0.5 and takes in 16 virginica too.

6 Regularization and class weights

When a straight line can almost split the classes, training inflates the weights to push probabilities to 1 and 0. Regularization adds a penalty that grows with the weights.

The loss plus a penalty on big weights.
Ltotal = L + λ·Σj wj2
L here is the cross-entropy added up over all samples; λ (lambda) is the penalty strength; Σj adds up over every weight j. Each weight is squared, so a weight twice as large costs four times as much. Raise λ and the weights shrink, the probabilities soften toward 0.5; at λ = 0 the weights on separable data grow without bound. scikit-learn writes the same penalty as C = 1/(2λ), so a small C is a strong penalty; its default is C = 1.0.

When yes is rare, class weights multiply each yes sample's loss: a larger weight calls more flowers yes, a smaller one fewer, and the default weights every class 1.

Each line builds one model; "balanced" weights each class by the inverse of its share of the samples.

from sklearn.linear_model import LogisticRegression
LR = LogisticRegression
strong = LR(C=0.1)  # C = 1/(2λ)
yes_x5 = LR(class_weight={1: 5})
bal = LR(class_weight="balanced")

The boundary is still straight. The SVM page trains the same kind of boundary with the hinge loss, a cost that is zero once a point is past the boundary by a margin.

7 Logistic regression in practice

The usual pipeline; predict_proba returns one row per flower and one column per class.

from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_iris(return_X_y=True)
split = train_test_split
parts = split(X, y, stratify=y)
Xtr, Xte, ytr, yte = parts
LR = LogisticRegression
clf = LR(C=1.0, max_iter=1000)
sc = StandardScaler()
model = make_pipeline(sc, clf)
model.fit(Xtr, ytr)
proba = model.predict_proba(Xte)
acc = model.score(Xte, yte)
  1. 1Loads iris and holds out a stratified test split.
  2. 2Scaling, then logistic regression with C = 1 and up to 1,000 solver iterations (max_iter); too few stops early with a warning.
  3. 3Fits, then test probabilities and accuracy.
SettingWhere it entersRaise itLower itDefault
C = 1/(2λ)Inverse of the penalty in the regularization cardWeaker penalty; on separable data the weights grow without boundStronger penalty; near 0 every probability drifts to 0.51.0
max_iterSolver iterationsMore time to convergeA ConvergenceWarning, weights not yet settled100
Threshold tCalls yes when p ≥ tFewer yes calls, fewer false alarmsMore yes calls, fewer misses0.5 (predict)

LogisticRegression uses the lbfgs solver with an L2 penalty; each iteration is about one pass over the data. The penalty is on by default, so scale first, or unscaled features are penalized unevenly. With three or more classes it fits one softmax model, not the notebook's one-vs-rest set, so its probabilities can differ slightly. For streaming data, SGDClassifier with the log loss takes the per-batch steps taught above.

8 Failure modes of logistic regression

CauseSymptomFix
Unscaled featuresSlow, uneven steps, with one weight racing while the other crawlsStandardize the features first
Perfectly separable dataWeights grow without bound and probabilities hit 0 and 1L2 penalty or early stopping
Rare yes classAlmost everything is called noMove the threshold or use class weights
Class in the middleOne straight boundary cannot surround it (versicolor in the threshold tool)A model that bends (MLP)

9 XOR needs a nonlinear boundary

Versicolor showed one class that a straight line cannot cut off. XOR, two classes on the two diagonals of a square, is the simplest data set with that problem. Stacking lines with bends between them fixes it, on the next page, Multilayer Perceptrons (MLP).

any straight linekeeps both colors on each side11223
Figure 5. In XOR blue 1 and red 2 sit on crossing diagonals, so no straight line 3 splits them.

10 References

  1. Fisher, R. A. (1936). The use of multiple measurements in taxonomic problems. Annals of Eugenics. (the Iris dataset)
  2. Hastie, T., et al. (2009). The Elements of Statistical Learning, Chapter 4 (Logistic Regression).