CO5 Topic 29 of 35
Logistic regression with gradient descent
Bend the straight line through a sigmoid to get a probability, then train it by gradient descent. This is one neuron, and the entry point to neural networks.
You will be able to
- Explain why a sigmoid gives a probability
- Write the logistic loss and its gradient
- Implement batch gradient descent with NumPy
- Compare your model with scikit-learn's
- Draw the decision boundary
Read
- Textbook 1, §6.5 Maximum likelihood hypotheses for predicting probabilities
- Textbook 2, Ch. on logistic regression and neurons
- Topic 9 of this course
Intuition
Linear regression can predict 1.7 or −0.4, which makes no sense as a probability. Pass the same score through the sigmoid, and every output lies between 0 and 1. The boundary is still a straight line, but the far-away points that spoiled topic 28 no longer tilt it.
How it works
p = σ(w₀ + w₁x₁ + … + wₙxₙ)
Loss = −(1/n) Σ [ y ln p + (1 − y) ln(1 − p) ]
∂Loss/∂wⱼ = −(1/n) Σ (y − p) xⱼ
wⱼ ← wⱼ + η (1/n) Σ (y − p) xⱼ
This is the gradient rule from topic 9, averaged over the data. The boundary is where p = 0.5, which is where the weighted sum equals 0.
Reading the weights. A weight is the change in the log odds per unit of that input. Bigger weights mean a steeper curve, not necessarily a more important feature, unless the features are on the same scale. So standardise first.
A worked prediction. With w₀ = −4 and w₁ = 2 at x = 2.5: the sum is 1, and p = σ(1) = 0.731.
The optimiser and the model are separate things. Logistic regression is the model. Gradient descent is one way to fit it, and scikit-learn uses a faster solver by default.
Run it: logistic regression from scratch
This uses a real medical dataset, with two features so that we can draw the boundary.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
data = load_breast_cancer()
names = ["mean radius", "mean texture"]
X = data.data[:, [list(data.feature_names).index(n) for n in names]]
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)
scale = StandardScaler().fit(X_train)
X_train, X_test = scale.transform(X_train), scale.transform(X_test)
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def add_bias(X):
return np.hstack([np.ones((len(X), 1)), X])
A, w = add_bias(X_train), np.zeros(3)
eta, losses = 0.1, []
for step in range(2001):
p = sigmoid(A @ w)
loss = -np.mean(y_train * np.log(p) + (1 - y_train) * np.log(1 - p))
losses.append(loss)
w += eta * A.T @ (y_train - p) / len(y_train)
if step in (0, 10, 100, 500, 2000):
print(f"step {step:4}: loss = {loss:.4f}")
accuracy = ((sigmoid(add_bias(X_test) @ w) > 0.5) == y_test).mean()
print(f"\nOur model: test accuracy {accuracy:.3f}, weights {np.round(w, 3)}")
sk = LogisticRegression(C=1e6).fit(X_train, y_train)
print(f"scikit-learn: test accuracy {sk.score(X_test, y_test):.3f}, "
f"weights {np.round(np.r_[sk.intercept_, sk.coef_[0]], 3)}")
fig, ax = plt.subplots(1, 2, figsize=(10, 3.4))
ax[0].plot(losses, color="#0f766e")
ax[0].set(title="Loss during training", xlabel="step", ylabel="loss")
ax[1].scatter(*X_test[y_test == 0].T, label="malignant", s=14)
ax[1].scatter(*X_test[y_test == 1].T, label="benign", s=14)
xs = np.array([-2.5, 3])
ax[1].plot(xs, -(w[0] + w[1] * xs) / w[2], "k--", label="boundary")
ax[1].set(title="Test data and decision boundary", xlabel=names[0] + " (scaled)", ylabel=names[1] + " (scaled)")
ax[1].set_ylim(-3, 4); ax[1].legend()
plt.tight_layout()
plt.show()
Try this: set eta to 5.0 and then 0.001. Look at the loss curve for each one.
Common mistakes
- Skipping scaling before gradient descent. Training becomes very slow, or the loss swings about.
- Using accuracy alone on imbalanced data. See precision and recall in topic 30.
- Thinking the sigmoid makes the boundary curved. The boundary is still straight, unless you add non-linear features.
Problems
- With w₀ = −4 and w₁ = 2, find the x where p = 0.5.
- Calculate the loss for one example with y = 1 and p = 0.9.
- Change the program to use all 30 features. What happens to accuracy?
- Add stochastic updates, using one example at a time, and compare the loss curves.
- Explain what
C=1e6is doing inLogisticRegression.
Flashcards
Say the meaning to yourself, then select the card to check.
- Sigmoid
- σ(z) = 1 / (1 + e^(−z)).
- Logistic loss
- −[y ln p + (1 − y) ln(1 − p)].
- Logistic boundary
- Where p = 0.5, which is a straight line or hyperplane.
- Why scale first?
- Gradient descent descends faster with similar feature scales.