Binary Classification Setup#
In binary classification, labels are . The model outputs a real-valued score (logit), which is squashed through the sigmoid function to produce a probability:
The sigmoid maps , with , as , and as . Its derivative is clean:
The logistic loss (also called binary cross-entropy) for a single example is:
When , the loss is — heavily penalises . When , the loss is — heavily penalises .
Logistic Regression Gradient#
For one example, the gradient with respect to is:
The derivation uses the chain rule and the sigmoid derivative:
The result is remarkably simple: the gradient is the feature vector weighted by the prediction error. This is the classification analogue of the least squares gradient .
For the full dataset, the ERM objective and its gradient are:
Worked numeric example:
Take , , . Then , . The gradient is .
The loss is . If the model had been perfectly confident (), the loss would be near 0.
Softmax and Multi-Class#
For classes, the model outputs logits (typically where ). The softmax function converts these to a probability distribution:
Softmax outputs sum to 1 and are all positive. The cross-entropy loss for true class is:
The gradient with respect to the logits has a clean form:
where is 1 if is the true class, 0 otherwise. So the gradient vector is where is the one-hot encoding of the true label. By the chain rule, .
Again, the gradient is the probability-error-weighted feature vector — exactly the same structural form as the binary case.
Convexity of Logistic Loss#
For a single example, the logistic loss is convex in . The Hessian is:
The term (the sigmoid output is always strictly between 0 and 1), and is PSD. Therefore for all — the function is convex.
For the full dataset, the Hessian is:
Since has positive entries on the diagonal, , making PSD. The logistic regression objective is convex.
However, logistic regression is not strongly convex unless has full column rank and the data is non-separable. When the data is linearly separable, can drive the loss arbitrarily close to 0 (no finite minimiser — this motivates regularisation).
Loss and Likelihood#
Minimising cross-entropy (logistic loss) is equivalent to maximum likelihood estimation (MLEMaximum Likelihood Estimation) under a Bernoulli model. The probability of label given features and parameters is:
The log-likelihood of the full dataset is . Maximising the log-likelihood = minimising the cross-entropy loss. This connection — loss functions as negative log-likelihoods — is fundamental to modern ML and will be explored fully in the Probability & Statistics course.
Compute the sigmoid derivative at $z = 0$: $\\sigma'(0) = ___$.
The gradient of the logistic loss for one example (x, y) with y ∈ {0,1} is:
Browser lab#
Plot the logistic loss surface in 2D (weight space) for a small synthetic dataset, and run GD to find the decision boundary.
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
n = 100
X_pos = rng.normal(loc=[2, 2], scale=1.0, size=(n//2, 2))
X_neg = rng.normal(loc=[-2, -2], scale=1.0, size=(n//2, 2))
X = np.vstack([X_pos, X_neg])
y = np.hstack([np.ones(n//2), np.zeros(n//2)])
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def loss_and_grad(w, X, y):
logits = X @ w
preds = sigmoid(logits)
eps = 1e-15
loss = -np.mean(y * np.log(preds + eps) + (1 - y) * np.log(1 - preds + eps))
grad = X.T @ (preds - y) / len(y)
return loss, grad
w = np.zeros(2)
eta = 1.0
losses = []
for _ in range(100):
loss, grad = loss_and_grad(w, X, y)
losses.append(loss)
w -= eta * grad
print(f"Final w: {w}")
print(f"Final loss: {loss:.6f}")
print(f"Decision boundary: x2 = {-w[0]/w[1]:.3f} x1")
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.5))
ax1.scatter(X_pos[:, 0], X_pos[:, 1], c="tab:blue", alpha=0.6, label="y=1")
ax1.scatter(X_neg[:, 0], X_neg[:, 1], c="tab:red", alpha=0.6, label="y=0")
xx = np.linspace(-5, 5, 100)
ax1.plot(xx, -w[0]/w[1] * xx, "k--", label="decision boundary")
ax1.set_xlim(-5, 5); ax1.set_ylim(-5, 5)
ax1.set_xlabel("x1"); ax1.set_ylabel("x2")
ax1.set_title("Data and Learned Decision Boundary")
ax1.legend()
ax2.plot(losses)
ax2.set_xlabel("Iteration"); ax2.set_ylabel("Logistic Loss")
ax2.set_title("Loss vs Iteration")
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()