Contents
, where , , and is 1 if and 0 otherwise
Deep Breakdown
What it tells me (Situation Awareness): The Categorical distribution models one single draw that lands in exactly one of classes, with class having probability . It is the multi-class generalisation of the Bernoulli () and the one-trial special case of the Multinomial (). The product form looks odd but simply "switches on" the probability of the observed class, exactly like does for Bernoulli. Its negative log-likelihood for one observation is , which is precisely the cross-entropy loss. In DA/DS: every softmax output of a multiclass classifier is a Categorical distribution over labels; LLM next-token prediction is a Categorical over the vocabulary (sampling with temperature, top-k and top-p all reshape before drawing); multinomial logistic regression, Naive Bayes class priors, customer segment assignment, ticket routing, and the "which plan will this user pick" question are all Categorical models.
Flowchart
Real-world use
Where it is used
Multiclass classification (softmax layer, multinomial logistic regression), language-model token sampling, recommendation of one item among , routing a support ticket to one of teams, modelling which product category a shopper buys, and topic or cluster assignment in mixture models.
Problem it solves
Gives a valid probability for each of mutually exclusive outcomes of one event, so a model can express uncertainty across classes instead of just outputting a single label, and so that model can be trained by maximum likelihood.
How it solves the problem
The model produces non-negative numbers that sum to 1 (softmax guarantees this). The observed class receives likelihood ; maximising over the training set is the same as minimising average cross-entropy. At prediction time I either take (classification) or sample from (generation).
Example in a business
A support-ticket router outputs for (Billing, Tech, Sales). If the ticket really is Billing, the loss is ; if it is really Sales, the loss is , more than six times larger. That asymmetry is what pushes the model to put mass on the right class, and calibrated let the operations team auto-route only tickets with and send the rest to a human.
The formula, piece by piece
- : the outcome of one trial, taking a value in .
- : number of possible classes or categories.
- : probability of class ; the parameter vector lives on the probability simplex.
- : exactly one class happens.
- : indicator (one-hot encoding) that the outcome is class .
- : every factor is except the observed class, so the product equals .
- and : each one-hot component is a Bernoulli.
- : the negative log-likelihood, i.e. the cross-entropy between the one-hot label and .
The formula in words
"One trial, possible outcomes: the probability of seeing class is just , and the probabilities over all classes add to one." Tiny example: a shopper buys from Electronics, Fashion, or Home with probabilities . .
Critical Concepts
- Bernoulli and Multinomial relatives: gives the Bernoulli; independent Categorical draws, counted per class, give the Multinomial.
- Softmax output is a Categorical: . Compute it with the log-sum-exp trick so large logits do not overflow.
- Cross-entropy = Categorical NLL: training any softmax classifier with cross-entropy is maximum-likelihood estimation of a Categorical model.
- MLE from data: with no features, , the class frequencies (class priors).
- Argmax vs sampling: classification uses ; LLMs sample, and temperature rescales logits as to make sharper ( less than 1) or flatter ( greater than 1).
- Calibration: a model can rank classes well yet output over-confident ; check reliability diagrams before using as real probabilities in business rules.
Step-by-step calculation (tiny example)
Step 1: Model output Router probabilities for (Billing, Tech, Sales). Check: .
Step 2: Probability of the true label True label is Billing, so the one-hot is and .
Step 3: Loss for this ticket .
Step 4: Compare with a wrong-but-possible label If the true label were Tech: . If Sales: .
Step 5: Interpretation The less probability the model gave to the truth, the larger the loss; averaging these over all tickets gives the training cross-entropy.
Code examples
Beginner example (Python)
import numpy as np
labels = ["Billing", "Tech", "Sales"]
p = np.array([0.7, 0.2, 0.1])
print(f"Sum of p = {p.sum():.2f}") # 1.00
for name, pk in zip(labels, p):
print(f"P({name}) = {pk:.1f} | loss if true = {-np.log(pk):.4f}")
# Billing 0.3567, Tech 1.6094, Sales 2.3026
rng = np.random.default_rng(42)
draws = rng.choice(len(p), size=100_000, p=p)
print("Empirical frequencies:", np.round(np.bincount(draws) / draws.size, 3)) # about [0.7 0.2 0.1]Beginner example (R)
p <- c(Billing = 0.7, Tech = 0.2, Sales = 0.1)
print(-log(p)) # 0.3567 1.6094 2.3026
set.seed(42)
draws <- sample(names(p), size = 100000, replace = TRUE, prob = p)
print(round(table(draws) / length(draws), 3))Professional example (Python)
import numpy as np
from scipy.special import logsumexp
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import log_loss
from sklearn.model_selection import train_test_split
def categorical_nll(logits: np.ndarray, y: np.ndarray) -> float:
"""Mean negative log-likelihood of a Categorical(softmax(logits)) model.
log p_y = z_y - logsumexp(z), computed stably.
"""
log_probs = logits - logsumexp(logits, axis=1, keepdims=True)
return float(-log_probs[np.arange(len(y)), y].mean())
X, y = make_classification(
n_samples=3000, n_features=8, n_informative=5, n_classes=3, random_state=42
)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42)
clf = LogisticRegression(max_iter=1000).fit(X_tr, y_tr)
logits = clf.decision_function(X_te) # z_1..z_K per ticket
probs = clf.predict_proba(X_te) # softmax(z) = Categorical parameters
print(f"Rows sum to 1: {np.allclose(probs.sum(axis=1), 1.0)}")
print(f"Manual Categorical NLL: {categorical_nll(logits, y_te):.4f}")
print(f"sklearn log_loss: {log_loss(y_te, probs):.4f}") # identical
# Business rule: auto-route only confident predictions
confident = probs.max(axis=1) >= 0.8
acc_conf = (probs.argmax(axis=1)[confident] == y_te[confident]).mean()
print(f"Auto-routed share: {confident.mean():.1%} | accuracy there: {acc_conf:.1%}")Source: domain knowledge