InterviewPrepKit

Home / Coding / Machine Learning Coding / Linear Models / Logistic Regression (Gradient Descent)

Logistic Regression (Gradient Descent)

hard 00:00
Solving tips
  • Prepend a column of ones to X so the bias is just another weight; then a single dot product handles weights and intercept together.
  • The gradient of the mean binary cross-entropy has a famously clean form: (1/n) X^T (sigmoid(Xw) - y). No log terms survive, so you never need to evaluate the loss to take a step.
  • Use a sign-branched sigmoid (via np.abs) so exp() never sees a positive argument; a naive 1/(1+exp(-z)) overflows and poisons the gradient.

Implement binary logistic regression trained with batch gradient descent, from scratch with NumPy. Logistic regression is the canonical linear classifier, and interviewers use it to check that you can assemble the full loop: add a bias, push features through a stable sigmoid, form the gradient of the cross-entropy loss, and update the weights. The math is short but the two classic traps (forgetting the bias, and an overflowing sigmoid) are exactly what they are watching for.

Model

Stack a column of ones onto X so the intercept is folded into the weight vector. With that augmented design matrix, the model predicts

p = sigmoid(X w)          where sigmoid(z) = 1 / (1 + exp(-z))

Training minimizes the mean binary cross-entropy loss. Its gradient with respect to w collapses to a single clean expression:

grad = (1 / n) * X^T (sigmoid(X w) - y)

and each gradient-descent step is w <- w - lr * grad. Note the gradient depends only on the residual p - y, so you never have to evaluate the loss itself to train.

Task

Implement two functions with the signatures shown in the starter.

  • train_logistic_regression(X, y, lr, n_iters) prepends a bias column of ones to X, initializes the weights to zeros, and runs n_iters full-batch gradient-descent steps using the gradient above. It returns the length n_features + 1 weight vector, where w[0] is the bias.
  • predict_proba(X, w) prepends the same bias column and returns sigmoid(X w) as probabilities in (0, 1).

Use a numerically stable sigmoid so large-magnitude scores do not overflow. Do not call sklearn, scipy, or any built-in logistic/expit helper.

Example

import numpy as np

# Two clearly separable clusters in 1D.
X = np.array([[0.0], [1.0], [2.0], [8.0], [9.0], [10.0]])
y = np.array([0, 0, 0, 1, 1, 1])

w = train_logistic_regression(X, y, lr=0.5, n_iters=5000)
p = predict_proba(X, w)

np.round(p, 2)                # -> array([0., 0., 0., 1., 1., 1.])
(p >= 0.5).astype(int)        # -> array([0, 0, 0, 1, 1, 1])  (all training points correct)

The learned weights split the two clusters: rows near 0 get probabilities close to 0, rows near 10 get probabilities close to 1.

Constraints

  • 1 <= n_samples <= 10^5, 1 <= n_features <= 10^3; use vectorized NumPy, not Python loops over samples.
  • y contains only 0 and 1.
  • Scores X w may reach large magnitudes; the sigmoid must not raise an overflow warning or return nan/inf.
  • Values fit in float64.

Write your solution, then hit Run tests to check it — or get a mock grade from the AI coach.

The coach remembers this session — revise your code and ask again, and it grades your progress. It gives hints, not the answer.
Report a bug