Softmax Regression
Objectives
Section titled “Objectives”After this lesson, you will be able to:
- Understand why combining multiple binary classifiers (one-vs-rest) fails to produce well-formed probabilities, and how softmax regression resolves this natively.
- Compute the softmax function by hand, explain its reliance on the exponential function, and implement a numerically stable version using the max-subtraction trick.
- Derive the cross-entropy loss function with one-hot labels, obtain the gradient, and train the model via gradient descent.
- Recognize that the decision boundaries of softmax regression are piecewise linear, and know when softmax should not be applied.
Prerequisites
Section titled “Prerequisites”- Logistic Regression, in Machine Learning (Learned)
Intuition
Section titled “Intuition”You are examining an iris flower with petal length measuring 4.9 cm and petal width measuring 1.5 cm. Which of the three species does it belong to: setosa, versicolor, or virginica? This is the famous iris dataset, published by statistician Ronald Fisher in 1936: 150 flowers, 50 per species. This lesson focuses on the two petal measurements to enable 2D visualization. The first flower of each species in the dataset appears as follows:
| Species | Petal Length (cm) | Petal Width (cm) |
|---|---|---|
| setosa | 1.4 | 0.2 |
| versicolor | 4.7 | 1.4 |
| virginica | 6.0 | 2.5 |
Your flower lies on the boundary between versicolor and virginica. An informative answer is not merely a single species name, but three probabilities summing to 1, such as “64% versicolor, 35% virginica”.
Method 1: Combining Three Binary Classifiers
Section titled “Method 1: Combining Three Binary Classifiers”You already know binary logistic regression. The simplest approach is training three separate models, each answering a yes/no question: “Is it setosa?”, “Is it versicolor?”, “Is it virginica?”. This strategy is known as one-vs-rest (OvR). Training these three models with LogisticRegression on 105 flowers (70% of data, standardized) and querying them on your flower yields:
| Question | Probability “Yes” |
|---|---|
| Is it setosa? | 0.008 |
| Is it versicolor? | 0.425 |
| Is it virginica? | 0.350 |
| Sum | 0.783 |
Two obvious problems emerge:
- The probabilities do not sum to 1. Each model trains in isolation with no awareness of the others. Across the full 150-sample dataset, this sum fluctuates between 0.405 and 1.539.
- All three models predict “no”, because none exceeds 0.5. Picking the highest probability still suggests versicolor, but the “is it versicolor?” model faces genuine geometric difficulty: versicolor is sandwiched between the other two species, making it impossible for a single linear boundary to separate it from “the rest”.
Method 2: Scoring Each Class
Section titled “Method 2: Scoring Each Class”Instead of three isolated questions, let a single model assign a continuous score to every class, then compare these scores simultaneously. Suppose the model scores your flower with three values, called logits: setosa , versicolor , virginica . A higher score indicates a better fit.
How do we convert these scores into valid probabilities? Dividing each score by their linear sum fails: negative scores exist, and the sum equals exactly 0. Softmax exponentiates each score with first (ensuring strictly positive numbers), then normalizes by the sum:
| Species | Logit | Probability | |
|---|---|---|---|
| setosa | −3.0 | 0.0498 | 0.0053 |
| versicolor | 1.8 | 6.0496 | 0.6422 |
| virginica | 1.2 | 3.3201 | 0.3525 |
| Total | 9.4195 | 1.0000 |
For instance, . The three probabilities sum to 1, preserve the original ranking of the scores, and declare versicolor the winner.
The goal of this lesson is: assign each class its own weight vector to generate scores, convert those scores into probabilities summing to 1, and learn parameters so that the true class of each sample receives maximum probability.
Theory
Section titled “Theory”Problem Formulation
Section titled “Problem Formulation”We have classes, indexed (for iris, ). Each training instance has features and belongs to exactly one of the classes. This setting is multiclass classification: classes are mutually exclusive.
Softmax regression equips each class with its own weight vector (having components) and bias term . The score for class is its logit:
Each logit is a linear model identical to Linear Regression, except models run in parallel on the same input . For iris with 2 features, the model has weights and 3 bias terms. Grouping all logits forms vector .
The Softmax Function
Section titled “The Softmax Function”The softmax function maps logit vector into probability vector :
The numerator is the exponential of the class logit; the denominator sums exponentials across all classes and is identical for every class. We interpret as the “probability the model assigns to class ”. The predicted class is .
Why use the exponential function? Because it guarantees three essential properties:
- Strict positivity. for all real (even negative ones), ensuring all probabilities are strictly positive and sum to 1.
- Order preservation. is strictly monotonically increasing: higher logits yield higher probabilities. The class with the largest logit always receives the largest probability.
- Difference amplification. Dividing two probabilities cancels their denominators, yielding :
A logit difference of 1 translates to an odds ratio of ; a difference of 2 yields . In our iris example, versicolor leads virginica by 0.6 points, so .
The name “softmax” reflects that it is a smooth, differentiable approximation of the argmax operator: the highest-scoring class captures the bulk of the probability mass, but remaining classes retain non-zero shares that vary smoothly with logits, enabling gradient-based optimization.
Invariance to Constant Shifts and the Max-Subtraction Trick
Section titled “Invariance to Constant Shifts and the Max-Subtraction Trick”The ratio formula reveals that only the differences between logits dictate probabilities. Adding an arbitrary scalar to every logit leaves probabilities unaffected:
The second step factors out of the denominator sum and cancels it against the numerator.
This algebraic identity prevents numerical overflow. Standard 64-bit floating point numbers top out around , meaning overflows whenever . If logits are , calculating returns infinity, and produces nan. The solution: subtract the maximum logit from all logits before exponentiating. Logits shift to , whose maximum exponential is —immune to overflow while preserving exact mathematical probabilities. Every numerical library implements softmax with this shift. The first code example tests both forms.
For Two Classes, Softmax Reduces to Sigmoid
Section titled “For Two Classes, Softmax Reduces to Sigmoid”Setting , divide numerator and denominator of by :
Thus, two-class softmax regression is mathematically identical to binary logistic regression with logit . Hence, logistic regression is simply a special case of softmax regression, which is formally termed multinomial logistic regression.
The subtraction also demonstrates parameter redundancy: multiple parameter sets yield identical models. Adding an identical vector to all leaves all pairwise differences unchanged. Regularization resolves this indeterminacy.
One-Hot Labels and Cross-Entropy
Section titled “One-Hot Labels and Cross-Entropy”To compare predicted probabilities with true labels, encode the target label as a -dimensional binary vector where only the true class index is 1:
| Species | One-Hot Vector |
|---|---|
| setosa | |
| versicolor | |
| virginica |
Integer labels 0, 1, 2 are avoided during loss computation to prevent implying artificial ordinal rankings or metric distances between categories.
The loss for an individual sample is the cross-entropy:
Because is one-hot, every term with vanishes, isolating the true class :
The loss is simply the negative logarithm of the probability assigned to the correct class. For our versicolor iris: . Had it been virginica, ; had it been setosa, . Confident mistakes are penalized heavily. When , this formula collapses exactly to binary log loss.
Across training samples, the overall loss is the mean:
Minimizing cross-entropy maximizes the joint likelihood of observing the training data under the model. When all parameters start at zero, softmax assigns uniform probability to every class, resulting in baseline loss . For , baseline loss is . Effective training must drive loss below this baseline.
We require the gradient of with respect to each logit .
Step 1: Express directly in terms of . Substituting the softmax definition into , and using :
Step 2: Differentiate with respect to . The first term has derivative if , and 0 otherwise—which is . The second term differentiates via where :
Step 3: Combine terms.
The gradient with respect to logits is simply predicted probability minus one-hot label. For our iris flower (true class versicolor): . Gradient descent moves opposite to the gradient, increasing the true class logit and decreasing incorrect logits proportionally to their overconfidence. The gradient components always sum to zero since probabilities and one-hot vectors each sum to 1.
Step 4: Propagate to parameters. Logit has derivative with respect to , and 1 with respect to . By the chain rule:
This reproduces the structure from logistic regression across every individual class.
Training with Gradient Descent
Section titled “Training with Gradient Descent”Aggregating across all training samples using matrix notation where is , is , and is :
- is the logit matrix.
- contains row-wise softmax probabilities, and contains one-hot ground truth labels.
Averaging sample gradients yields:
The update rules are and .
Three important training dynamics to note:
- No closed-form solution exists, requiring iterative optimization. Fortunately, is globally convex in , devoid of local minima.
- Solutions are non-unique due to shift invariance. Adding a constant offset to all class weights preserves identical predictions. Regularization (as in Ridge, Lasso, and Elastic Net) selects the minimum-norm parameter configuration; scikit-learn’s
LogisticRegressionenables L2 regularization by default (C=1.0). - Linearly separable classes cause weights to diverge. Setosa is linearly separable from the other two species. Without regularization, the optimizer pushes setosa probabilities ever closer to 1 by inflating weights indefinitely, causing loss to drift downward without converging. The second code cell demonstrates this behavior.
Decision Boundary
Section titled “Decision Boundary”The model predicts the class with the largest probability, which coincides with the class possessing the highest logit. The boundary separating class and class occurs where their logits equate:
This defines a linear hyperplane in feature space. The decision region for class is the intersection of half-spaces where its logit dominates, forming a convex polyhedron. Combining regions produces piecewise linear decision boundaries meeting at points of threefold logit equality. The third code cell visualizes these boundaries on the iris dataset.
Softmax in Neural Networks and LLMs
Section titled “Softmax in Neural Networks and LLMs”The final layer of nearly all deep classification networks is mathematically identical to softmax regression: preceding hidden layers learn high-level representations, while the output layer computes logits, applies softmax, and trains via cross-entropy with the gradient. Large language models operate on the same principle, where spans the vocabulary size (tens of thousands of tokens). The temperature parameter adjusted in text generation APIs scales logits by before softmax, as introduced in Using the Text API. The Visualization section illustrates the impact of scaling logits.
Limitations
Section titled “Limitations”- Boundaries are strictly piecewise linear. Non-linear class boundaries require feature transformations or non-linear architectures (e.g., decision trees, neural networks).
- Enforces mutual exclusivity. Multi-label problems where instances belong to multiple categories simultaneously should not use softmax.
- Probabilities may be miscalibrated. Models with identical accuracy can output markedly different probability distributions depending on regularization and optimization duration.
Visualization
Section titled “Visualization”The table below takes the logits of our iris flower, , scales them by factor (equivalent to temperature ), and computes softmax probabilities:
| Logits Evaluated | setosa | versicolor | virginica |
|---|---|---|---|
| 0.3333 | 0.3333 | 0.3333 | |
| 0.0495 | 0.5460 | 0.4045 | |
| (original) | 0.0053 | 0.6422 | 0.3525 |
| 0.0053 | 0.6422 | 0.3525 | |
| 0.0001 | 0.7685 | 0.2315 | |
| 0.0000 | 0.9975 | 0.0025 |
Observations from top to bottom:
- Equal logits yield uniform distributions. The top row reflects the untrained state: per class, with cross-entropy equal to .
- Constant shifts leave outputs identical. The row is identical to the baseline, enabling the max-subtraction trick.
- Large scaling sharpens the distribution. While class rankings never change, scaling by 10 concentrates nearly all probability onto versicolor: softmax approaches a hard
argmax. Conversely, scaling toward zero flattens the distribution toward uniform uncertainty.
This clarifies why weight inflation increases model overconfidence: doubling weights doubles logits, sharpening probabilities toward 0 and 1 without shifting boundaries.
Code Examples
Section titled “Code Examples”The three cells below run independently. The first relies solely on NumPy. The second and third load scikit-learn (and Matplotlib for the third).
The first cell compares naive versus numerically stable softmax on the iris logits, tests large positive shifts (+1000), and validates the reduction to sigmoid.
Numerically stable softmax with NumPy
import numpy as np
def softmax_naive(z): # Naive version: exponentiate each logit directly. e = np.exp(z) return e / e.sum()
def softmax(z): # Subtract the largest logit first: same result, largest exp is e^0 = 1. e = np.exp(z - z.max()) return e / e.sum()
# Three logits of one flower: setosa, versicolor, virginica.z = np.array([-3.0, 1.8, 1.2])p = softmax(z)print("p =", p.round(4), f" sum = {p.sum():.4f}")
# Add the same constant to every logit: probabilities stay the same.print("z + 5 =", softmax(z + 5).round(4))
# Large logits: e^1001.8 exceeds the largest float (about 1.8e308).big = z + 1000with np.errstate(over="ignore", invalid="ignore"): print("naive =", softmax_naive(big))print("stable =", softmax(big).round(4))
# K = 2: softmax of (z1, z2) equals the sigmoid of z1 - z2.z2 = np.array([1.8, 1.2])sigmoid = 1 / (1 + np.exp(-(z2[0] - z2[1])))print("K=2 =", softmax(z2).round(4), f" sigmoid = {sigmoid:.4f}")The first run downloads Python, about 10 MB.
- Logits produce probabilities summing to 1, exactly matching the Intuition section. Adding 5 to all logits yields identical output.
- Adding 1000 causes naive exponentiation to produce
[nan, nan, nan]due to floating-point overflow (). The max-subtraction approach computes exact probabilities without issue. - The two-class test verifies that matches .
The second cell trains softmax regression using batch gradient descent on iris petal dimensions. Data is split into 105 training samples and 45 test samples (stratify=y), standardized with StandardScaler, and optimized from with across 1000 epochs, before benchmarking against scikit-learn’s LogisticRegression.
Softmax regression via gradient descent
import numpy as npfrom sklearn.datasets import load_irisfrom sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import StandardScaler
X, y = load_iris(return_X_y=True)X = X[:, 2:] # petal length and petal width (cm)X_tr, X_te, y_tr, y_te = train_test_split( X, y, test_size=0.3, random_state=0, stratify=y)scaler = StandardScaler().fit(X_tr) # learn from the training set onlyA_tr, A_te = scaler.transform(X_tr), scaler.transform(X_te)m, K = len(y_tr), 3Y = np.eye(K)[y_tr] # one-hot labels: m rows, K columns
def softmax(Z): E = np.exp(Z - Z.max(axis=1, keepdims=True)) # subtract each row's max return E / E.sum(axis=1, keepdims=True)
# Each column of W is the weight vector w_k of one class.W, b, eta = np.zeros((2, K)), np.zeros(K), 0.5for epoch in range(1001): P = softmax(A_tr @ W + b) if epoch in (0, 1, 10, 100, 1000): loss = -np.mean(np.log(P[np.arange(m), y_tr])) print(f"epoch {epoch:4d}: loss = {loss:.4f}") G = P - Y # gradient w.r.t. the logits: y_hat - y W -= eta * A_tr.T @ G / m b -= eta * G.mean(axis=0)
acc = np.mean(np.argmax(A_te @ W + b, axis=1) == y_te)print(f"GD test acc = {acc:.4f}")# With 3 or more classes, LogisticRegression uses softmax by default.clf = LogisticRegression().fit(A_tr, y_tr)print(f"sklearn test acc = {clf.score(A_te, y_te):.4f}")flower = scaler.transform([[4.9, 1.5]]) # petal 4.9 cm x 1.5 cmprint("GD p =", softmax(flower @ W + b)[0].round(3))print("sklearn p =", clf.predict_proba(flower)[0].round(3))The first run downloads Python, about 10 MB.
- At epoch 0, loss evaluates to 1.0986 (), reflecting uniform initial probabilities. Loss falls to 0.4657 at epoch 10, 0.1836 at epoch 100, and 0.0976 at epoch 1000.
- Loss continues creeping downward even after 1000 epochs: because setosa is linearly separable, unregularized weights inflate indefinitely to drive setosa probabilities arbitrarily close to 1.
- Both models achieve identical test accuracy: 0.9778 (44 out of 45 test flowers).
- For the flower measuring 4.9 cm 1.5 cm, scikit-learn outputs , closely mirroring our intuition example. The unregularized GD implementation outputs 0.771 for versicolor, showing higher confidence due to larger unpenalized weight norms.
The third cell uses a scikit-learn pipeline (StandardScaler + LogisticRegression) to plot decision boundaries across the 2D feature space and evaluate test performance via confusion matrix and classification_report.
Decision boundaries and test set evaluation
import matplotlib.pyplot as pltimport numpy as npfrom sklearn.datasets import load_irisfrom sklearn.linear_model import LogisticRegressionfrom sklearn.metrics import classification_report, confusion_matrixfrom sklearn.model_selection import train_test_splitfrom sklearn.pipeline import make_pipelinefrom sklearn.preprocessing import StandardScaler
iris = load_iris()X, y = iris.data[:, 2:], iris.target # the two petal features (cm)X_tr, X_te, y_tr, y_te = train_test_split( X, y, test_size=0.3, random_state=0, stratify=y)model = make_pipeline(StandardScaler(), LogisticRegression())model.fit(X_tr, y_tr)
# Color each grid point by the class with the highest probability.g1, g2 = np.meshgrid(np.linspace(0.5, 7.5, 300), np.linspace(0, 2.8, 300))grid = np.c_[g1.ravel(), g2.ravel()]region = model.predict(grid).reshape(g1.shape)fig, ax = plt.subplots(figsize=(6, 4.5))ax.contourf(g1, g2, region, levels=[-0.5, 0.5, 1.5, 2.5], colors=["#e8d5b7", "#c9d9c3", "#c7cfe0"])for k, marker in enumerate(["o", "s", "^"]): pts = X_tr[y_tr == k] ax.scatter(pts[:, 0], pts[:, 1], marker=marker, edgecolor="k", facecolor="white", label=iris.target_names[k])ax.set_xlabel("petal length (cm)")ax.set_ylabel("petal width (cm)")ax.legend(loc="upper left")plt.show()
# Evaluate on the test set: rows are true classes, columns predictions.y_pred = model.predict(X_te)print(confusion_matrix(y_te, y_pred))print(classification_report(y_te, y_pred, digits=3, target_names=iris.target_names))The first run downloads Python, about 10 MB.
The visualization highlights piecewise linear decision boundaries: straight segments partition the space into three convex regions. Setosa sits isolated behind a wide boundary margin, whereas the versicolor–virginica boundary threads directly through their overlapping interface.
The test confusion matrix confirms a single misclassification: one virginica flower with slender dimensions (5.6 cm 1.4 cm) misclassified as versicolor (assigned 0.593 versicolor vs. 0.405 virginica). The classification report details per-class precision, recall, and F1 alongside macro and weighted averages (matching at 0.978 due to balanced test splits), as covered in Model Evaluation.
Common Pitfalls
Section titled “Common Pitfalls”Quick Check
Section titled “Quick Check”Summary and Further Reading
Section titled “Summary and Further Reading”Key Takeaways:
- Softmax regression assigns each class a logit , producing probabilities . Unlike separate binary models, probabilities strictly sum to 1 while preserving logit ordering.
- Softmax depends exclusively on logit differences: subtracting the maximum logit prevents overflow without altering probabilities. For , softmax reduces to , proving logistic regression is a special case.
- Under one-hot encoding, cross-entropy simplifies to , initialized at for untrained models. The logit gradient is , yielding parameter gradients for gradient descent.
- Decision boundaries are piecewise linear. Softmax forms the standard classification output layer for deep neural networks and large language models, strictly suited for mutually exclusive single-label tasks.
Further Reading:
- Vũ Hữu Tiệp (2017). Machine Learning cơ bản, Bài 13: Softmax Regression. Foundational Vietnamese guide from one-vs-rest to softmax, numerical stability, and NumPy implementation.
- Zhang, A., Lipton, Z. C., Li, M., Smola, A. J. (2023). Dive into Deep Learning, Section 4.1: Softmax Regression. Comprehensive treatment from maximum likelihood and information-theoretic perspectives.
- scikit-learn developers (2007–2026). scikit-learn 1.8 User Guide, Section 1.1.11.2: Multinomial Case. Technical formulation of multinomial logistic regression in scikit-learn.
- Fisher, R. A. (1936). The Use of Multiple Measurements in Taxonomic Problems. Annals of Eugenics 7(2), 179–188. Original paper introducing the iris dataset.
- Bridle, J. S. (1990). Probabilistic Interpretation of Feedforward Classification Network Outputs, with Relationships to Statistical Pattern Recognition. In Neurocomputing, Springer, 227–236. Landmark paper naming the softmax layer in neural networks.
Your browser is blocking storage. Your progress will be lost when you reload the page.
Content licensed under CC BY-NC-SA 4.0. Code under the MIT license.About