Skip to content

第 5 章:损失函数

神经网络需要一个"裁判"来评判自己预测得有多准。损失函数就是这个裁判。

5.1 损失函数的作用

前向传播之后,我们有:

  • 预测值 y:网络认为图片是各数字的概率,shape (n, 10)
  • 真实标签 t:图片实际是哪个数字,shape (n,)(整数 0-9)

现在需要一个数字来描述"预测有多差"——这就是损失(Loss)

要求:

  1. 预测越准,损失越小
  2. 预测越差,损失越大
  3. 函数要可微(能求导),这样才能用梯度下降优化

5.2 直觉:为什么用 log?

假设我们只考虑正确类别的预测概率

真实标签是数字"7",网络输出概率:
情况A:y[7] = 0.90  (预测很准)
情况B:y[7] = 0.50  (预测一般)
情况C:y[7] = 0.10  (预测很差)

我们希望损失函数满足:损失(A) < 损失(B) < 损失(C)

一个直觉想法:损失 = 1 - y[正确类别]

情况A:损失 = 1 - 0.90 = 0.10
情况B:损失 = 1 - 0.50 = 0.50
情况C:损失 = 1 - 0.10 = 0.90

但实际上用的是 log,而不是 1。为什么?

情况A:损失 = -log(0.90) ≈ 0.105
情况B:损失 = -log(0.50) ≈ 0.693
情况C:损失 = -log(0.10) ≈ 2.303

比较两种方案:

      情况A    情况B    情况C
1-y:  0.10     0.50     0.90    (等间距)
-log: 0.105    0.693    2.303   (差距不等)

log 方案的优势:

  • 预测接近 1(极好)时,损失极小(接近 0)
  • 预测接近 0(极差)时,损失极大(趋向无穷大)
  • 对"差得很远"的预测,给出更大的惩罚,优化动力更强

5.3 交叉熵损失(Cross-Entropy Loss)

5.3.1 公式

对于 n 个样本的批量,交叉熵损失是:

L=1ni=1nlog(y[i,t[i]])

其中:

  • y[i,t[i]]:第 i 个样本在正确类别 t[i] 上的预测概率
  • log:自然对数(以 e 为底)
  • 除以 n:对所有样本求平均,使损失值不随 batch size 变化

核心思想:只看正确类别的预测概率,要它尽量大。

5.3.2 直观图示

-log(p)

∞ |*
  | *
  |  *
  |   *
2 |     *
  |       *
1 |           *
  |                 *
0 +------+------+------→ p
  0     0.5          1.0
  ↑                  ↑
 极差               完美
预测→损失趋∞         预测→损失=0

5.4 从信息论理解交叉熵(选读)

如果你学过信息论,这里是更深层的理解。

信息熵(自信息): 一个事件的概率为 p 时,其信息量为 log(p)。概率越小(越意外),信息量越大。

交叉熵的含义: 在真实分布(one-hot 标签)下,用模型预测的分布来编码信息,平均需要多少信息量。当模型预测完全正确时,交叉熵 = 真实熵(最小值)。

你不理解这段也没关系,直接记住公式就行。


5.5 代码实现分析

python
def cross_entropy_error(y, t):
    if y.ndim == 1:           # 单样本情况:变成(1, n)的矩阵
        t = t.reshape(1, -1)
        y = y.reshape(1, -1)
    if t.size == y.size:      # 如果 t 是 one-hot 编码,转成整数标签
        t = np.argmax(t, axis=1)
    n = y.shape[0]
    return -np.sum(np.log(y[np.arange(n), t] + 1e-7)) / n

关键行解析:

y[np.arange(n), t] — 花式索引

这是 numpy 的高级索引技巧,一次取出所有样本在正确类别上的概率:

python
n = 3
y = [[0.1, 0.2, 0.7],   # 第0个样本
     [0.8, 0.1, 0.1],   # 第1个样本
     [0.3, 0.6, 0.1]]   # 第2个样本

t = [2, 0, 1]            # 正确标签:0→类别2,1→类别0,2→类别1

np.arange(3) = [0, 1, 2]

y[np.arange(3), t]
= y[[0, 1, 2], [2, 0, 1]]
= [y[0,2], y[1,0], y[2,1]]
= [0.7,    0.8,    0.6]      ← 各样本在正确类别上的概率

+ 1e-7 — 防止 log(0)

如果某个概率预测值恰好是 0(极端情况),log(0) = ,会导致数值问题。加上一个极小值 107 保证不会取到 0。


5.6 一个完整例子

设定:

批量大小 n=3,3个类别(0, 1, 2)

真实标签 t = [2, 0, 1]

预测概率 y:
  样本0: [0.10, 0.20, 0.70]  → 预测类别2(概率70%),正确!
  样本1: [0.80, 0.15, 0.05]  → 预测类别0(概率80%),正确!
  样本2: [0.30, 0.60, 0.10]  → 预测类别1(概率60%),正确!

计算损失:

正确类别的概率:
  样本0:y[0, t[0]] = y[0, 2] = 0.70
  样本1:y[1, t[1]] = y[1, 0] = 0.80
  样本2:y[2, t[2]] = y[2, 1] = 0.60

各损失:
  -log(0.70) ≈ 0.357
  -log(0.80) ≈ 0.223
  -log(0.60) ≈ 0.511

平均损失:
  L = (0.357 + 0.223 + 0.511) / 3 ≈ 0.364

如果预测很差:

预测概率 y:
  样本0: [0.40, 0.40, 0.20]  → 预测类别0或1,但实际是类别2
  样本1: [0.20, 0.60, 0.20]  → 预测类别1,但实际是类别0
  样本2: [0.50, 0.30, 0.20]  → 预测类别0,但实际是类别1

正确类别的概率:
  样本0:y[0, 2] = 0.20
  样本1:y[1, 0] = 0.20
  样本2:y[2, 1] = 0.30

各损失:
  -log(0.20) ≈ 1.609
  -log(0.20) ≈ 1.609
  -log(0.30) ≈ 1.204

平均损失:
  L = (1.609 + 1.609 + 1.204) / 3 ≈ 1.474

预测差时损失 1.474,预测好时损失 0.364。差距明显,符合预期。


5.7 为什么 Softmax + 交叉熵是绝配?

这个组合不仅直觉上合理,数学上也有一个极其优雅的性质:

它们的联合梯度极其简洁!

具体推导在下一章(反向传播),这里先透露结论:

La2[i,j]=y[i,j]1[j=t[i]]

用人话说: 梯度 = 预测概率 - 真实标签(one-hot)。

  • 如果 j 是正确类别:梯度 = y1(预测越接近1,梯度越小)
  • 如果 j 不是正确类别:梯度 = y(预测越接近0,梯度越小)

网络"知道"自己错了多少,自动调整——这就是为什么这个组合在实践中表现优异。


5.8 小结

概念记忆要点
损失函数衡量预测与真实的差距,越小越好
交叉熵L=1nlog(y[正确类别])
为什么用 log对差距大的预测惩罚更重,优化动力更强
+1e-7防止 log(0) 导致数值溢出
为什么配 softmax联合梯度极其简洁(见下一章)

下一章:反向传播——如何利用损失函数的梯度,告诉每个参数该往哪个方向调整。


← 第 4 章 | 返回目录 | 第 6 章:反向传播 →

基于 Kaggle MNIST 数据集,使用纯 numpy 从零实现