标准回答
Softmax 把 logits 转成概率分布,交叉熵衡量预测分布与真实 one-hot 的差距。数值稳定的核心是计算 exp 前减去每行最大值,结果不变但避免大数溢出。交叉熵取真实类别概率的负对数。最优雅的结论是:Softmax 与交叉熵复合后,对 logits 的梯度直接化简为 p−y,这让反向传播起步异常简洁。实现如下:
python
import numpy as np
def softmax(z):
"""数值稳定 Softmax:减去每行最大值防止 exp 溢出。z: (N, C)。"""
z = z - z.max(axis=1, keepdims=True) # 平移不改变结果
ez = np.exp(z)
return ez / ez.sum(axis=1, keepdims=True)
def cross_entropy(logits, labels):
"""logits: (N, C);labels: (N,) 整数类别。返回平均损失与梯度 (p - y)。"""
N = logits.shape[0]
p = softmax(logits)
eps = 1e-12
# 取每个样本真实类别的概率,做负对数
loss = -np.mean(np.log(p[np.arange(N), labels] + eps))
# 反向:dL/dz = p - y,其中 y 为 one-hot
grad = p.copy()
grad[np.arange(N), labels] -= 1.0
grad /= N
return loss, grad
if __name__ == '__main__':
rng = np.random.default_rng(0)
logits = rng.normal(0, 5, (4, 3)) # 故意用大值检验稳定性
labels = np.array([0, 2, 1, 2])
loss, grad = cross_entropy(logits, labels)
print('softmax row sums =', softmax(logits).sum(1)) # 全为 1
print('loss =', round(float(loss), 4))
print('grad shape =', grad.shape)常见误区
⚠️ 常见踩坑
不减最大值直接 exp 在 logits 较大时溢出为 inf,softmax 退化成 NaN;归约时忘记 keepdims=True 会让广播维度错位算错;以及先 softmax 再单独 log 比直接用 log-softmax(log-sum-exp)精度更差,类别多时尤其明显。
追问
追问 1:为什么 Softmax+交叉熵的梯度是 p−y?
把交叉熵 L=−Σ y_i log p_i 与 p_i=softmax(z)_i 复合,对 z_k 求偏导,利用 Softmax 雅可比 ∂p_i/∂z_k=p_i(δ_ik−p_k) 代入并求和,y 是 one-hot 求和为 1,化简后恰好得到 p_k−y_k,干净利落,正是工程上不分开实现两者的原因。
追问 2:Softmax 的平移与尺度不变性?
Softmax 平移不变:所有 logits 同加常数 c,分子分母同乘 e^c 抵消,结果不变(这正是减 max 的依据);但不是尺度不变:整体乘以 1/T(温度)会改变分布尖锐度,T 越小越接近 argmax,这是采样温度调控的原理。
延伸学习
与本题相关的知识库文章、术语、工具与行业资讯。