这一节把五种手段一项项加到同一个模型上,看训练与测试那道沟怎么变。 起点是 100 张图、原始像素、tanh:训练 1.0000、测试 0.8562,沟 0.1438。 加数据增强后测试跳到 0.9475,沟缩到 0.0225——这是整章最划算的一步。 再标准化反而略降到 0.9337,换 ReLU 升到 0.9700。 最后加 Dropout 却掉到 0.8850。 这就是这一章最该记住的一句:手段不是叠得越多越好。
前面五节各自讲一种手段,各自验证各自的效果。 这一节做一件前面没做的事:把它们放进同一个模型,一项项累加。 单独有效的两件事放在一起,可能互相抵消——这是所有「技巧清单」类文章最不愿意提的一点。
一、一项项加上去
起点刻意选最裸的一版:100 张图、原始 0/1 像素、tanh、不做任何正则化。 然后按「改数据、改预处理、改激活函数、改正则」的顺序一项项加。
| 累加到哪一步 | 训练准确率 | 测试准确率 | 沟 |
|---|---|---|---|
| 裸跑 | 1.0000 | 0.8562 | 0.1438 |
| 加数据增强 8 倍 | 0.9700 | 0.9475 | 0.0225 |
| 再加标准化 | 0.9700 | 0.9337 | 0.0363 |
| 再换 ReLU | 0.9600 | 0.9700 | -0.0100 |
| 再加 Dropout 0.7 | 0.9200 | 0.8850 | 0.0350 |
| 第一行沟有十四个百分点,这是过拟合的原始状态。 | |||
| 加上数据增强,沟一下缩到两个多点——这一步贡献了整章七成以上的改善。 | |||
| 标准化那一步反而略降。 | |||
| 原因在第 2 节量过:这份数据的条件数只有 357.7,本来就不歪,除标准差白忙。 | |||
| 「有一步没加分」这件事本身就值得记:不是每个技巧在每份数据上都有效。 |
二、换 ReLU 那条要单独说
换 ReLU 那一步测试到了 0.9700,是五行里最高的,沟甚至是负的。 但这一步有个隐藏成本:学习率必须从 0.1 压到 0.02。 如果照抄 0.1,训练准确率会塌到 0.09——整个网络一轮就炸,输出退化成常数。 这一点第 3 节已经量化过:ReLU 的学习率到 0.3 就整锅崩。 所以「换激活函数几乎免费」这句话有前提:你得顺手把学习率一起调。 清单类文章通常只写「换 ReLU 效果更好」,不写「顺便把学习率降五倍」。
三、最后一步为什么是负收益
最后加 Dropout,测试从 0.9700 掉到 0.8850,掉了八点五。 这不是 Dropout 坏了,是它在这个位置不合适。 数据增强已经把 100 张堆到了 800 张,过拟合基本被压住了。 这时候再加一层正则化,等于在已经不缺约束的地方继续加约束——有效容量被压得不够用, 直接从「刚好」滑到「欠拟合」。 回看第 4 节那张表就明白了:训练张数 400 时,用与不用 Dropout 都是 1.0000。 数据够了,它就没活干了。
四、五项办法的账
把收益和代价放在一张表里:
| 办法 | 主要作用 | 代价 |
|---|---|---|
| 加训练样本 | 直接缩小沟 | 标注最贵,要人一条条标 |
| 数据增强 | 把已知规律写成数据 | 几乎免费,但要懂任务 |
| 清洗标签 | 去噪、去矛盾 | 要人复核,量大时很磨 |
| 预处理 | 让学习率不敏感 | 算一次,白化要存矩阵 |
| 换激活函数 | 梯度传得下去 | 改一行,但要重调学习率 |
| Dropout | 压过拟合 | 训练变慢,容量变小 |
| DropConnect | 同上,粒度更细 | 另存一份同形 mask,更贵 |
| 性价比最高的两项是「换激活函数」和「数据增强」,几乎不花钱。 | ||
| 最贵的两项是「加样本」和「清标签」,可它们的收益最扎实。 | ||
| 中间三项看情况——数据歪就做预处理,过拟合重就上 Dropout。 |
五、三条不能忘的前提
一、这些手段都在压过拟合,压不出更高的上限。 数据本身信息不够,再加正则化也只是让模型少背错题,不会变聪明。 二、过拟合不严重时,硬加正则化会拖后腿。 上面最后一行就是活例子:数据已经够了,Dropout 反而扣掉八点五。 三、先看沟,再动手。 训练和测试差得远,才轮到这一章的办法。 两边都低,那是欠拟合——该加容量、加步数、换模型,加 Dropout 只会更糟。
六、动手 Demo:五种手段逐项累加,看沟怎么变
数据 400 张训练、800 张测试,训练只取 100 张。 每一行都重新训练一遍,2000 步,同一个随机种子。 训练准确率一律在最初那 100 张上算。 这一点很重要:增强之后训练集有 800 张,如果拿 800 张算训练准确率,各行之间就不可比了。
def augment(X, y, mult=8, jit=1, seed=7):
u"""把每张图平移一格,重复 mult 份。"""
rg = np.random.default_rng(seed)
XX, yy = [], []
for _ in range(mult):
for xa, ya in zip(X, y):
img = xa.reshape(8, 8).copy()
dr, dc = rg.integers(-jit, jit + 1, 2)
XX.append(np.roll(np.roll(img, dr, 0), dc, 1).ravel().copy())
yy.append(ya)
return np.array(XX), np.array(yy)
def run(tag, X, y, xev, xts, act="tanh", keep=1.0, lr=0.1):
m = fit(X, y, act=act, keep=keep, steps=2000, lr=lr, seed=11)
# 训练准确率一律在最初那 100 张上算,这样每一步都可比
print(tag, acc(xev, ys, m, act), acc(xts, yte, m, act))
Xa, ya = augment(Xs, ys, 8, 1)
run(u"裸跑", Xs, ys, Xs, Xte)
run(u"+ 增强", Xa, ya, Xs, Xte)
run(u"+ 标准化", (Xa - mu) / sd, ya, Xs_n, Xte_n)
run(u"+ 换 ReLU", (Xa - mu) / sd, ya, Xs_n, Xte_n, act="ReLU", lr=0.02)
run(u"+ Dropout 0.7", (Xa - mu) / sd, ya, Xs_n, Xte_n,
act="ReLU", keep=0.7, lr=0.02)
实跑输出:
--------------------------------------------------------------------
小结:五种手段一项项加上去,看沟怎么变窄
--------------------------------------------------------------------
[1] 这一章讲了五种办法,都是冲同一件事去的:训练集上太顺,新数据上就露怯
下面把五种办法一项一项加上去,看「训练与测试的沟」怎么变
起点是最裸的一版:100 张图、原始 0/1 像素、tanh、不做任何正则化
训练准确率一律在最初那 100 张上算,这样每一步都可比
累加到哪一步 训练准确率 测试准确率 沟
裸跑 1.0000 0.8562 0.1438
+ 数据增强 8 倍 0.9700 0.9475 0.0225
+ 数据增强 8 倍、标准化 0.9700 0.9337 0.0363
+ 上面两项、换 ReLU 0.9600 0.9700 -0.0100
+ 上面三项、Dropout 0.7 0.9200 0.8850 0.0350
第一行训练 1.0000、测试八成六,沟有十四个百分点
加上数据增强,沟一下缩到两个多点——这一步是整章最划算的
有趣的是标准化那一步反而略降:这份数据本来就不歪,除标准差白忙
换 ReLU 时学习率必须从 0.1 压到 0.02,否则一轮就炸(训练只剩 0.09)
「换激活函数几乎免费」这句话有前提:你得顺手把学习率一起调
到这一行为止,测试 0.9700,比裸跑高出十一个百分点,沟基本没了
可最后加 Dropout 反而掉了八点——增强已经把样本堆到 800 张,
再叠一层正则化,有效容量就不够用了
这就是这一章最该记住的一句:手段不是叠得越多越好
[2] 五项办法的收益与代价
办法 主要作用 代价
加训练样本 直接缩小沟 标注最贵,要人一条条标
数据增强 把已知规律写成数据 几乎免费,但要懂任务
清洗标签 去噪,去矛盾 要人复核,量大时很磨
预处理 让学习率不敏感 算一次,白化要存矩阵
换激活函数 梯度传得下去 换一行代码,最便宜
Dropout 压过拟合 训练变慢,容量变小
DropConnect 同上,粒度更细 另存一份同形 mask,更贵
性价比最高的两项是「换激活函数」和「数据增强」,几乎不花钱
最贵的两项是「加样本」和「清标签」,可它们的收益最扎实
[3] 三条不能忘的前提
一、这些手段都在压过拟合,压不出更高的上限
数据本身信息不够,再加正则化也只是让模型少背错题,不会变聪明
二、过拟合不严重时,硬加正则化会拖后腿
上面最后一行就是活例子:数据已经够了,Dropout 反而扣掉八点
第 4 节那个实验也一样,训练集给到 400 张,它就不划算了
三、先看沟,再动手
训练和测试差得远,才轮到这一章的办法;两边都低,那是欠拟合,
该加容量、加步数、换模型,而不是加 Dropout
[4] 一句话收这一章
泛化能力不是「训出来的」,是「用数据和对数据的理解约束出来的」
下一章换工具:这一章所有实验都是手写 numpy,第七章讲现成的框架
run 里多传一个 xev 参数,就是为了这件事。
X 和 y 是真正拿去训练的数据(可能已经增强过),xev 是用来算训练准确率的那 100 张。
两者分开,前面几行才有可比性。
最后一行 keep=0.7 配上 lr=0.02,两个超参数是绑在一起的。
如果沿用 0.1,这一行会因为 ReLU 崩塌而变成 0.09,看起来像「Dropout 把模型搞坏了」,
其实是学习率的问题。
第四行那个「沟是负的」不要当成异常。
训练准确率是在 100 张原始图上算的,测试是 800 张;测试集更大、更平滑,偶尔高过训练集很正常。
30 秒实验
- 把累加顺序倒过来:先加 Dropout,再加增强。 最后一行仍然会掉,但掉的位置变了——说明「第几步加」也有影响。
- 把最后一行 Dropout 换成 DropConnect 0.7。 结果差不多,也在 0.88 附近,同样不如不加。
- 把起点从 100 张改成 400 张再逐项累加。 第一行的沟会小很多,后面几步的收益也会跟着缩水——因为一开始就没什么过拟合可压。
常见误区
- 「技巧都叠上,效果一定最好。」 实测最后一行 0.8850,比不加 Dropout 的 0.9700 低八点五。 每一项都有它的适用前提,前提不成立时,加上去就是负收益。
- 「标准化是标配,必须做。」 这一节里它让测试从 0.9475 掉到 0.9337。 标准化的作用是让学习率不敏感,数据本来不歪的时候,它换不来分数。
- 「训练准确率掉了,说明正则化起作用了。」 第 4 节那张表里,保留率从 1.0 扫到 0.3,训练准确率一直是 1.0000。 容量远大于任务难度时,丢一半单元也照样拟合。 判断正则化有没有用,要看测试准确率,不是看训练准确率掉了没有。
自测三问(点开对答案)
1. **为什么数据增强这一步的收益最大?** —— **因为它在增加「约束」而不损失「容量」。** **它把「数字挪一格还是同一个数字」这条你已经知道的规律,变成了 700 张新样本。** **其他手段要么在减容量(Dropout),要么在改尺度(预处理),都不增加信息。** 2. **训练和测试都低的时候,该做什么?** —— **那是欠拟合,不该用这一章的任何手段。** **该做的是加容量、加训练步数、换更强的结构或激活函数。** **加 Dropout 只会让它更糟——你在给一个本来就学不动的模型继续上枷锁。** 3. **为什么「沟」比「测试准确率」更适合当诊断指标?** —— **因为测试准确率混了两件事:模型好不好、任务难不难。** **沟只反映一件事:模型有没有在训练集上占便宜。** **沟大就是过拟合,两边都低就是欠拟合,对症才能下药。**这一章所有实验都是手写 numpy:自己写前向、自己推反传、自己实现 dropout。 下一章换工具——Theano、Pylearn2、Caffe、Chainer、TensorFlow,这些框架把这些活全包了。
- ☐ (本章完,下一节 7.1 深度学习开发环境)