手算交叉熵损失
构造从「瞎猜」到「非常自信」的预测序列,观察交叉熵数值如何随之下降。
学完本案例你将理解:为什么分类任务用交叉熵而不用 MSE——它比 MSE 更「严厉」地惩罚自信但错误的预测。
生活引入
考试评分——答错比不答更糟
两个学生在单选题(答案是 A)上的表现:小红预测 A 的概率 0.5(不确定但方向对),小明预测 C 的概率 0.95(非常自信地错了)。小红虽然不确定但方向对了,小明不仅错了还非常自信——这种错误更不可原谅。
交叉熵损失就像这个评分标准:预测正确时奖励自信,预测错误时惩罚自信。
直观理解
真实标签是「猫」(one-hot = [1, 0, 0])。模拟模型从「瞎猜」到「训练得越来越准」的输出:
- 完全瞎猜 [0.33, 0.33, 0.34] → 损失 1.10
- 非常自信且正确 [0.98, 0.01, 0.01] → 损失 0.02
- 非常自信但错误 [0.02, 0.9, 0.08] → 损失 3.91(比瞎猜高近 4 倍!)
最后一个数值值得关注:因为 -log(0.02) ≈ 3.91,而 -log(0.33) ≈ 1.10。
数学定义
\[ H(p, q) = -\sum_{i=1}^{C} p_i \cdot \log(q_i) \]\(p\) 是真实分布(one-hot),\(q\) 是预测分布。对于「自信但错误」的预测,交叉熵 = -log(0.02) ≈ 3.91,MSE = (1-0.02)^2 ≈ 0.96。交叉熵的惩罚是 MSE 的 12 倍。
Python 动手实践
实例
import numpy as np
def cross_entropy(p_true, q_pred, eps=1e-12):
q_pred = np.clip(q_pred, eps, 1 - eps)
return -np.sum(p_true * np.log(q_pred))
# 真实标签:猫 (第 0 类)
p_true = np.array([1, 0, 0]) # [猫, 狗, 鸟]
predictions = {
"完全瞎猜 (均匀)": np.array([0.33, 0.33, 0.34]),
"有点感觉": np.array([0.5, 0.3, 0.2]),
"逐渐靠谱": np.array([0.7, 0.2, 0.1]),
"训练较好": np.array([0.9, 0.07, 0.03]),
"非常自信且正确": np.array([0.98, 0.01, 0.01]),
"非常自信但错误!": np.array([0.02, 0.9, 0.08]),
}
print("RUNOOB 交叉熵损失对比 (真实: 猫=[1,0,0])\n")
print(f"{'预测描述':<20} {'损失':<10} {'分析'}")
print("-" * 52)
for name, q in predictions.items():
loss = cross_entropy(p_true, q)
if loss > 2: analysis = "严重错误!"
elif loss > 0.5: analysis = "方向对但不确定"
else: analysis = "很好"
print(f"{name:<20} {loss:<10.4f} {analysis}")
# MSE vs 交叉熵对比
q_bad = predictions["非常自信但错误!"]
mse_bad = np.mean((p_true - q_bad) ** 2)
ce_bad = cross_entropy(p_true, q_bad)
print(f"\nRUNOOB 自信但错误时: MSE={mse_bad:.4f}, 交叉熵={ce_bad:.4f} (严厉 {ce_bad/mse_bad:.0f}x)")
def cross_entropy(p_true, q_pred, eps=1e-12):
q_pred = np.clip(q_pred, eps, 1 - eps)
return -np.sum(p_true * np.log(q_pred))
# 真实标签:猫 (第 0 类)
p_true = np.array([1, 0, 0]) # [猫, 狗, 鸟]
predictions = {
"完全瞎猜 (均匀)": np.array([0.33, 0.33, 0.34]),
"有点感觉": np.array([0.5, 0.3, 0.2]),
"逐渐靠谱": np.array([0.7, 0.2, 0.1]),
"训练较好": np.array([0.9, 0.07, 0.03]),
"非常自信且正确": np.array([0.98, 0.01, 0.01]),
"非常自信但错误!": np.array([0.02, 0.9, 0.08]),
}
print("RUNOOB 交叉熵损失对比 (真实: 猫=[1,0,0])\n")
print(f"{'预测描述':<20} {'损失':<10} {'分析'}")
print("-" * 52)
for name, q in predictions.items():
loss = cross_entropy(p_true, q)
if loss > 2: analysis = "严重错误!"
elif loss > 0.5: analysis = "方向对但不确定"
else: analysis = "很好"
print(f"{name:<20} {loss:<10.4f} {analysis}")
# MSE vs 交叉熵对比
q_bad = predictions["非常自信但错误!"]
mse_bad = np.mean((p_true - q_bad) ** 2)
ce_bad = cross_entropy(p_true, q_bad)
print(f"\nRUNOOB 自信但错误时: MSE={mse_bad:.4f}, 交叉熵={ce_bad:.4f} (严厉 {ce_bad/mse_bad:.0f}x)")
RUNOOB 交叉熵损失对比 (真实: 猫=[1,0,0]) 预测描述 损失 分析 ---------------------------------------------------- 完全瞎猜 (均匀) 1.0986 方向对但不确定 有点感觉 0.6931 方向对但不确定 逐渐靠谱 0.3567 很好 训练较好 0.1054 很好 非常自信且正确 0.0202 很好 非常自信但错误! 3.9120 严重错误! RUNOOB 自信但错误时: MSE=0.3260, 交叉熵=3.9120 (严厉 12x)
AI 中的应用场景
| 场景 | 使用的损失函数 |
|---|---|
| 图像分类 | 多分类交叉熵(CategoricalCrossEntropy) |
| 语言模型 | 每个 token 位置的交叉熵之和——GPT 训练的目标函数 |
| 知识蒸馏 | 用教师模型的软标签做交叉熵 |
