Cross-Entropy Loss: Multi-Class Classification
Cross-Entropy Loss: Multi-Class Classification
Cross-Entropy Loss is the standard loss function for multi-class classification tasks. It fuses the Softmax probability distribution with Negative Log-Likelihood (NLL). In netcl, the calculation is numerically stabilized via the Log-Sum-Exp trick to prevent floating-point overflow and underflow on GPU hardware.
[!TIP] Theory & Curriculum Link: For the first-principles derivation of loss functions, optimization, and analytical gradients, see Curriculum: What is Machine Learning? and Curriculum: Calculus & Gradients.
1. Quick Example: 10-Class Classification
import numpy as np
import netcl.autograd as ag
import netcl.nn as nn
from netcl.core.device import manager
from netcl.core.tensor import Tensor
q = manager.default("auto").queue
# 1. Multi-class classification network
model = nn.Sequential(
nn.Linear(q, 64, 32),
nn.ReLU(),
nn.Linear(q, 32, 10),
)
# 2. Batch: 4 samples, 10 classes, target class indices (0 to 9)
x = Tensor.from_host(q, np.random.randn(4, 64).astype(np.float32))
y_indices = Tensor.from_host(q, np.array([2, 0, 9, 1], dtype=np.int32))
# 3. Differentiable forward pass and loss evaluation
with ag.Tape() as tape:
logits = model(ag.tensor(x))
loss = ag.cross_entropy(logits, ag.tensor(y_indices))
tape.backward(loss)
print(f"Cross-Entropy Loss scalar: {loss.value.to_host()[0]:.4f}")
2. Mathematical Formulation
For a batch of $N$ samples and $C$ classes, let $z_{b, c}$ denote the unnormalized logit score produced by the model for sample $b$ and class $c$. The true target label is $y_b \in \{0, \dots, C-1\}$.
Softmax Probability Distribution
The predicted probability for class $c$ is given by the Softmax function:
$$P(y = c \mid \mathbf{z}_b) = \frac{\exp(z_{b, c})}{\sum_{c'=1}^C \exp(z_{b, c'})}$$
Negative Log-Likelihood and the Log-Sum-Exp Trick
A naive implementation of $-\log P(y = y_b \mid \mathbf{z}_b)$ computes $\exp(z)$, which overflows float32 when logits exceed $+88$.
To guarantee numerical stability, netcl shifts logits by the row maximum:
$$m_b = \max_{c=1, \dots, C} z_{b, c}$$
Using the identity:
$$\log \sum_{c'=1}^C \exp(z_{b, c'}) = m_b + \log \sum_{c'=1}^C \exp(z_{b, c'} - m_b)$$
The mean Cross-Entropy loss across the batch is:
$$\mathcal{L} = \frac{1}{N} \sum_{b=1}^N \left[ m_b + \log \left( \sum_{c=1}^C \exp(z_{b, c} - m_b) \right) - z_{b, y_b} \right]$$
Analytical Gradient
The derivative of Cross-Entropy with respect to logits has an exceptionally clean analytical form:
$$\frac{\partial \mathcal{L}}{\partial z_{b, c}} = \frac{1}{N} \left( P(y = c \mid \mathbf{z}_b) - \mathbb{I}_{[c = y_b]} \right)$$
Where $\mathbb{I}_{[c = y_b]} = 1$ if $c$ is the correct class and $0$ otherwise. The backward pass simply subtracts $1.0$ from the predicted probability of the true class!
3. Practical Usage: Training Loop & Padding Masking
import numpy as np
import netcl.autograd as ag
import netcl.nn as nn
import netcl.optim as opt
from netcl.core.device import manager
from netcl.core.tensor import Tensor
q = manager.default("auto").queue
model = nn.Linear(q, 16, 4)
optimizer = opt.AdamW(model.parameters(), lr=0.01)
x_batch = Tensor.from_host(q, np.random.randn(8, 16).astype(np.float32))
y_batch = Tensor.from_host(q, np.random.randint(0, 4, size=(8,)).astype(np.int32))
for step in range(5):
with ag.Tape() as tape:
logits = model(ag.tensor(x_batch))
loss = ag.cross_entropy(logits, ag.tensor(y_batch))
tape.backward(loss)
optimizer.step()
optimizer.zero_grad()
loss_scalar = float(loss.value.to_host()[0])
print(f"Step {step+1:02d}: Loss = {loss_scalar:.4f}")
Masking Padding in Language Models (ignore_index)
When training autoregressive language models, padding tokens are ignored using ignore_index = -100:
from netcl.autograd.shape_ops import cross_entropy_indices
# logits: (Batch * Seq_Len, Vocab_Size)
# targets: (Batch * Seq_Len,) with -100 at padding positions
with ag.Tape() as tape:
loss = cross_entropy_indices(logits_node, targets_array, ignore_index=-100)
tape.backward(loss)
4. Critical Engineering Rules
- Do Not Apply Softmax Inside Your Model: The final layer of your neural network must output raw, unnormalized logits. Never add an explicit
nn.Softmax()layer at the end of your network;ag.cross_entropyhandles Softmax internally with the Log-Sum-Exp trick. - Data Types: Logits must be
float32(orfloat16under AMP). Target indices must be integers (int32).
Related Documentation
- NN API Reference: Layer definitions and functional operations.
- Concepts: MSE Loss: Mean Squared Error loss for continuous regression.
- Concepts: Metric Losses: Triplet, Contrastive, and InfoNCE losses.