学习就是不断调参数、让损失变小。 损失函数就是那个被打分的东西,它决定了模型最终会学成什么样。 平方误差 E = ½Σ(t-y)² 最直觉,但配 sigmoid 时饱和区会把梯度压没。 交叉熵 E = -Σt·log y 在答错时梯度更大,所以分类任务几乎都用它。 实测:正确答案是 1、网络只给 0.01 时,平方误差 0.4900、梯度 -0.99; 交叉熵 4.6052、梯度 -100.0。两者差一百倍。
4.4 节讲训练循环时,「算损失」那一步只是一笔带过。这一节把它摊开。 第 1 章 1.3 节提过批量学习与在线学习,这一节从「损失」这个角度再看同一件事。 下一节的反向传播是从损失往回推的,所以损失的导数长什么样,直接决定了梯度长什么样。
一、三种学习方式:一次看几个样本
批量学习把所有样本过一遍、把梯度加起来、然后更新一次。 方向准,但一轮只前进一步,而且整个数据集得装进内存。 在线学习来一个样本就更新一次。 快、能跟上数据分布的变化,但单个样本的梯度噪声大,损失曲线全是毛刺。 小批量学习(mini-batch)折中:一次看 32 或 128 个。 既利用了矩阵运算的并行,又保留了噪声带来的好处,现在是事实标准。 第 1 章的实测可以挪到这里看:同一份 XOR 数据,批量更新 54 次就收敛,在线要 266 次。 不是在线学习更笨,是它每次只看一个样本,走的路更弯。
二、平方误差:最直觉的那一个
E = ½ Σ (t - y)²。前面那个 ½ 没有别的作用,只是让求导时平方出来的 2 被约掉。 对 y 求导得 ∂E/∂y = y - t。 判对了是 0,判错了正比于差多少。这恰好是 4.3 节差量规则的推广——差量规则就是「一个样本、 一次更新」的平方误差。 多个输出时把各类加起来,就是「每个输出都往自己的目标值靠」。回归任务到现在还是默认用它。
三、交叉熵:给概率准备的
E = -Σ t·log y。 t 是 one-hot,只有正确那一类是 1,所以求和号后面其实只剩一项:E = -log y_正确。 它对「自信地答错」罚得极重:y_正确 = 0.01 时 -log 0.01 = 4.6052;y_正确 = 0.99 时只有 0.0101。 差距不是线性的,是对数的反向。 导数 ∂E/∂y = -t/y。y 越小分母越小,梯度越大——这是交叉熵最要紧的性质。
四、为什么分类任务几乎都用交叉熵
三条理由,按工程上的重要性排: 第一,梯度尺度对。答错越狠推得越用力。 平方误差的梯度是 p-1,p 落在 0 到 1 之间,所以它最大也就到 -1,答错到 0.01 时仍然是 -0.99。 第二,和 sigmoid、softmax 配起来能约掉饱和因子。 平方误差配 sigmoid 时,对 z 的梯度是 (y-t)·y(1-y); y 饱和到 0.99 时 y(1-y) 只剩 0.0099,两个小量相乘,梯度几乎传不回去。 第三,信息论解释:交叉熵衡量两个分布的差异,最小化它就是让预测分布逼近真实分布。 这条最「正确」,但真正让它在工程上胜出的是前两条。 有个反直觉的细节: p=0.99 时平方误差 0.0001,比交叉熵的 0.0101 还小。所以「交叉熵的损失值更小」是错的, 它胜在梯度形状,不在数值大小。
五、损失函数的选择表
| 任务 | 输出层 | 损失函数 | 备注 |
|---|---|---|---|
| 回归(预测一个连续数) | 不加激励 | 平方误差 | 输出范围不限 |
| 二分类 | sigmoid(1 个输出) | 交叉熵 | 也可以两个输出加 softmax |
| 多分类 | softmax(N 个输出) | 交叉熵 | 类别互斥时用 |
| 多标签 | sigmoid(N 个输出) | 各类交叉熵相加 | 类别不互斥时用 |
这张表是工程默认值,不是定理。 真正决定的是「你想让什么样的错误被罚得更重」——写损失函数,其实就是在写这句话。
六、动手 Demo:损失函数:区别在一个分母
造一个最简场景:正确答案是 1,网络给出概率 p。 p 从 0.99 一路降到 0.01,把两种损失的数值和梯度一起算出来。
import numpy as np
for p in (0.99, 0.90, 0.50, 0.10, 0.01):
sq = 0.5 * (p - 1.0) * (p - 1.0) # 平方误差
sq_g = p - 1.0 # 它对 p 的导数
ce = -np.log(p) # 交叉熵(正确答案是 1)
ce_g = -1.0 / p # 它对 p 的导数
print("p=%.2f 平方 %.4f 梯度 %+.4f 交叉 %.4f 梯度 %+.4f"
% (p, sq, sq_g, ce, ce_g))
实跑输出:
--------------------------------------------------------------------
损失函数:平方误差与交叉熵,区别在一个分母
--------------------------------------------------------------------
一个样本、正确答案是 1。看网络给出不同的概率 p 时,两种损失各是多少、梯度各有多大。
p 平方误差 其梯度 交叉熵 其梯度
---------------------------------------------
0.99 0.0001 -0.0100 0.0101 -1.0101
0.90 0.0050 -0.1000 0.1054 -1.1111
0.50 0.1250 -0.5000 0.6931 -2.0000
0.10 0.4050 -0.9000 2.3026 -10.0000
0.01 0.4900 -0.9900 4.6052 -100.0000
**p = 0.99(答得很对):平方误差 0.0001、交叉熵 0.0101,两个都很小。**
**p = 0.01(自信地答错):平方误差 0.4900、交叉熵 4.6052。**
看梯度那一列,差距更狠:平方误差给 -0.99,交叉熵给 -100.0——**差一百倍。**
为什么?平方误差对 p 的导数是 p - 1,最大也就是 1;
交叉熵的导数是 -1/p,p 越小它越爆炸。**这正是我们想要的:**
模型越是「自信地答错」,越该被狠狠推一把。
反过来,平方误差配 sigmoid 还有个隐患:**输出饱和时,两个小量相乘**,
梯度会小到几乎传不回去。交叉熵和 sigmoid 配在一起,那个分母恰好把饱和的因子约掉——
这就是分类任务几乎都用交叉熵的工程原因,不只是「理论上更合理」。
数值那一列,p 越小两列分得越开。 p=0.99 时两个都很小(0.0001 对 0.0101),p=0.01 时变成 0.4900 对 4.6052。 梯度那一列更狠:p=0.01 时平方误差给 -0.99,交叉熵给 -100.0。 平方误差的梯度被 1 卡住了上限,交叉熵没有上限。 上限从哪来?平方误差对 p 的导数是 p-1,p 落在 0 到 1 之间,所以它的绝对值最大就是 1。 交叉熵的导数是 -1/p,p 趋向 0 时它没有边界。 但也别把交叉熵当万能:p 恰好等于 0 时 log 0 会出问题, 实际代码里都要加一个极小量(Demo 里加的 1e-12)。这已经不是数学问题,是浮点问题。 还有一条更隐蔽的好处:交叉熵和 sigmoid 配在一起时,那个 1/p 恰好把 sigmoid 的导数 p(1-p) 约掉, 对 z 的梯度退化成一个干净的减法 y-p。这就是 4.7 节反向传播里 dp 那一行能写得那么短的原因。
30 秒实验
- 把 p=0.01 那行的交叉熵手算一遍:-log 0.01 等于多少?(提示:ln 10 约等于 2.302585)
- 把平方误差前面的 ½ 去掉,对 p 的导数会变成多少?梯度上限还是 1 吗?
- 如果标签不是 1 而是 0.7(这叫标签平滑),p=0.01 时交叉熵会变成多少?
常见误区
- 以为损失值小就代表模型好。损失是「用某把尺子量出来的差距」,换一把尺子数值全变。 只有同一把尺子下的数才能互相比较。换损失函数之后,旧的学习率、旧的收敛轮数全部作废。
- 以为交叉熵只能用于分类。它衡量两个分布的差异,只要输出能解释成概率分布就能用。 语言模型的损失就是交叉熵,只不过类别数是整个词表。
- 把那个 ½ 当成装饰。它只为求导时约掉 2。 去掉它,梯度整体乘 2,等价于学习率翻倍——不会报错,但会让调参记录对不上。
自测三问(点开对答案)
1. 「自信地答错」时,两种损失的梯度差多少? —— 实测 p=0.01:平方误差 -0.99,交叉熵 -100.0,差一百倍。 原因是平方误差的导数 p-1 有上限 1,交叉熵的导数 -1/p 没有上限。 2. 平方误差配 sigmoid 为什么会梯度消失? —— 对 z 的梯度是 (y-t)·y(1-y)。 输出一旦饱和到 0.99,y(1-y) 只剩 0.0099,两个小量相乘,梯度就快没了。 交叉熵的那个分母正好把它约掉。 3. 小批量学习为什么成了事实标准? —— 它同时拿到批量的稳定和在线学习的快:一次看几十上百个样本,梯度噪声被平掉大半, 同时又能用上矩阵运算的并行。损失函数定了,接下来两个问题:导数怎么算得出来、又怎么用它改参数。 下一节先看最简单的情形——只有一个参数时,怎么一步步挪到谷底。
- ☐ (本节完,下一节 4.6 梯度法)