Skip to content

Logistic Regression ​

Binary classification - sigmoid, cross-entropy, decision boundaries


One-Sentence Summary ​

Logistic regression models the probability of a binary outcome using a linear combination of features passed through a sigmoid function.


Core Formulation ​

Model ​

P(y=1|x)=σ(w⋅x+b)=11+e−(w⋅x+b)

The sigmoid function maps any real number to (0,1):

σ(z)=11+e−z

Sigmoid Function

Why Sigmoid? ​

PropertyBenefit
Maps R→(0,1)Interpretable as probability
S-shaped curveCaptures threshold behavior
Nice derivative: σ′(z)=σ(z)(1−σ(z))Efficient gradient computation

Decision Boundary ​

  • Predict y=1 if P(y=1|x)>0.5, i.e., w⋅x+b>0
  • The decision boundary is the hyperplane w⋅x+b=0

Decision Boundary


Loss Function ​

Why Not MSE? ​

MSE loss with sigmoid creates a non-convex optimization problem with many local minima.

Cross-Entropy Loss (Log Loss) ​

L(w)=−1n∑i=1n[yilog⁡(y^i)+(1−yi)log⁡(1−y^i)]

where y^i=σ(w⋅xi+b)

Cross-Entropy Loss

Intuition:

ScenarioLoss
y=1, y^→1→0 (correct, confident)
y=1, y^→0→∞ (wrong, confident)
y=0, y^→0→0 (correct, confident)
y=0, y^→1→∞ (wrong, confident)

Maximum Likelihood Interpretation ​

Cross-entropy equals negative log-likelihood under Bernoulli distribution:

P(y|x)=y^y(1−y^)1−ylog⁡L=ylog⁡(y^)+(1−y)log⁡(1−y^)

Minimizing cross-entropy = Maximizing likelihood


Optimization ​

Gradient Descent ​

∂L∂w=1n∑i=1n(y^i−yi)xi

Remarkably similar to linear regression gradient!

python
def logistic_regression_gd(X, y, lr=0.01, n_iters=1000):
    n, d = X.shape
    w = np.zeros(d)
    b = 0

    for _ in range(n_iters):
        z = X @ w + b
        y_pred = 1 / (1 + np.exp(-z))

        dw = (1/n) * X.T @ (y_pred - y)
        db = (1/n) * np.sum(y_pred - y)

        w -= lr * dw
        b -= lr * db

    return w, b

Newton's Method ​

For faster convergence, use second-order optimization:

wnew=w−H−1∇L
  • Pros: Converges in fewer iterations
  • Cons: Each iteration is O(d3) due to Hessian inversion

Regularization ​

TypeFormulaEffect
L2 (Ridge)L+λ|w|22Prevents large weights
L1 (Lasso)L+λ|w|1Produces sparse weights (feature selection)

sklearn's C Parameter ​

sklearn uses C=1/λ:

  • Large C: Less regularization, more complex model
  • Small C: More regularization, simpler model

Multiclass Extension ​

One-vs-Rest (OvR) ​

Train k binary classifiers:

  • Classifier 1: Class 1 vs rest
  • Classifier 2: Class 2 vs rest
  • ...

Prediction: Class with highest probability

Softmax Regression (Multinomial) ​

Generalizes sigmoid to k classes:

P(y=j|x)=ewj⋅x∑k=1Kewk⋅x

Loss: Categorical cross-entropy

L=−∑i=1n∑j=1kyijlog⁡P(y=j|xi)

Key Properties ​

Assumptions ​

AssumptionDescription
Linearity in log-oddslog⁡p1−p=w⋅x is linear
IndependenceObservations are independent
No multicollinearityFeatures not perfectly correlated
Large sample sizeMLE needs sufficient data

Strengths vs Weaknesses ​

StrengthsWeaknesses
Probabilistic output (confidence scores)Linear decision boundary only
Interpretable coefficients (odds ratios)Requires feature engineering for non-linearity
Efficient: O(nd) per iterationSensitive to outliers
Built-in regularizationClass imbalance issues
No distributional assumptions on X

Coefficient Interpretation ​

Log-Odds ​

log⁡p1−p=w0+w1x1+w2x2+⋯

wj = change in log-odds for one-unit increase in xj

Odds Ratio ​

Odds Ratio for xj=ewj
Odds RatioInterpretation
>1Feature increases odds of positive class
<1Feature decreases odds
=1No effect

Example: If w1=0.5 for "years_experience":

  • Odds ratio =e0.5≈1.65
  • "Each additional year increases odds of success by 65%"

Interview Questions ​

Q1: Why logistic over linear regression for classification? ​

Key points:

  1. Bounded output - Linear regression can predict outside [0,1]
  2. Probabilistic interpretation - Calibrated probabilities for ranking/thresholding
  3. Proper loss function - Cross-entropy correctly penalizes confident mistakes

Q2: How to handle class imbalance? ​

ApproachDescription
Class weightsUpweight minority class (class_weight='balanced')
Threshold adjustmentDon't use 0.5; tune based on precision-recall
ResamplingSMOTE, undersampling (after train/test split!)
Different metricsUse precision, recall, F1, PR-AUC instead of accuracy

Q3: Non-linear relationships? ​

Options (in order of complexity):

  1. Feature engineering (polynomial, interaction terms)
  2. Generalized additive models (GAMs)
  3. Non-linear models (trees, neural networks)

Q4: Sigmoid vs Softmax? ​

SigmoidSoftmax
Single value →(0,1)Vector of k values →k probabilities summing to 1
Binary classificationMulticlass classification
σ(z)softmax([z1,…,zk])j=ezj∑kezk

For binary: softmax([z,0]) = [σ(z),1−σ(z)]


Code Reference ​

python
import numpy as np

class LogisticRegression:
    def __init__(self, lr=0.01, n_iters=1000, regularization='l2', lambda_=0.1):
        self.lr = lr
        self.n_iters = n_iters
        self.regularization = regularization
        self.lambda_ = lambda_
        self.weights = None
        self.bias = None

    def sigmoid(self, z):
        return 1 / (1 + np.exp(-np.clip(z, -500, 500)))

    def fit(self, X, y):
        n, d = X.shape
        self.weights = np.zeros(d)
        self.bias = 0

        for _ in range(self.n_iters):
            z = X @ self.weights + self.bias
            y_pred = self.sigmoid(z)

            # Gradients
            dw = (1/n) * X.T @ (y_pred - y)
            db = (1/n) * np.sum(y_pred - y)

            # Regularization
            if self.regularization == 'l2':
                dw += (self.lambda_ / n) * self.weights
            elif self.regularization == 'l1':
                dw += (self.lambda_ / n) * np.sign(self.weights)

            # Update
            self.weights -= self.lr * dw
            self.bias -= self.lr * db

    def predict_proba(self, X):
        return self.sigmoid(X @ self.weights + self.bias)

    def predict(self, X, threshold=0.5):
        return (self.predict_proba(X) >= threshold).astype(int)


class SoftmaxRegression:
    """Multiclass logistic regression."""
    def __init__(self, lr=0.01, n_iters=1000):
        self.lr = lr
        self.n_iters = n_iters
        self.weights = None

    def softmax(self, z):
        exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))
        return exp_z / np.sum(exp_z, axis=1, keepdims=True)

    def fit(self, X, y):
        n, d = X.shape
        k = len(np.unique(y))
        y_onehot = np.zeros((n, k))
        y_onehot[np.arange(n), y] = 1
        self.weights = np.zeros((d, k))

        for _ in range(self.n_iters):
            y_pred = self.softmax(X @ self.weights)
            self.weights -= self.lr * (1/n) * X.T @ (y_pred - y_onehot)

    def predict(self, X):
        return np.argmax(self.softmax(X @ self.weights), axis=1)

Quick Reference Card ​

LOGISTIC REGRESSION
-----------------------------------------------------
Model:    P(y=1|x) = sigma(w.x + b)
Sigmoid:  sigma(z) = 1/(1 + e^(-z))
Loss:     Cross-entropy = -[y log(y_hat) + (1-y) log(1-y_hat)]
Gradient: dL/dw = (1/n) sum (y_hat - y)x

DECISION BOUNDARY
-----------------------------------------------------
Linear: w.x + b = 0
Predict y=1 if w.x + b > 0

MULTICLASS
-----------------------------------------------------
OvR:     Train k binary classifiers
Softmax: P(y=j|x) = e^(w_j.x) / sum_k e^(w_k.x)

INTERPRETATION
-----------------------------------------------------
w_j = change in log-odds per unit increase in x_j
e^(w_j) = odds ratio for x_j

USE WHEN
-----------------------------------------------------
- Binary/multiclass classification
- Need probability estimates
- Want interpretable coefficients
- Linear decision boundary is sufficient