教材这一节列了三块战场:图像识别、语音识别、自然语言处理。 三块地方的路线是同一个形状——先是人挑特征,后来换成网络自己挑。 实测:同一批 8 乘 8 的数字图,喂原始像素测试 0.9025,喂人挑好的七段码只有 0.5613。 但把平移去掉,段码立刻涨到 0.9775,跟原始像素打成平手。 它输的不是信息量,是位置。
前七章一直在讲「怎么做」,这一章讲「已经做成了什么」。 它是全书唯一一章没有公式、也没有代码的章节。 但这一章恰恰最该配一个 Demo——因为「应用案例」这个词,很容易被读成一串成就清单。 而清单读起来爽,不解决问题。
一、图像识别:那届比赛改写了后面十年
教材给了一张 ILSVRC 的历年成绩折线图。 2012 年那一届,一个叫 AlexNet 的卷积网络把 top-5 错误率从两成六压到一成五, 第二名还在两成六。 一成五这个数字本身不稀奇,稀奇的是它和第二名差了十一个百分点。 这个比赛办了这么多年,之前每一届的冠军都比上一届好一两个点。 忽然有一届差了十一点,那不是「又进步了」,那是「换了个办法」。 换掉的东西,现在回头看很清楚:
| 环节 | 之前 | 之后 |
|---|---|---|
| 特征 | SIFT、HOG,人手设计 | 卷积核,网络自己学 |
| 分类器 | SVM、随机森林 | 几层全连接加 softmax |
| 流程 | 先提特征,再训分类器,两段 | 端到端,一段 |
| 注意第三行。前两行是「换了工具」,第三行是「换了分工」。 | ||
| 以前是人负责「看什么」,机器负责「怎么分」;现在是机器把两件事一起干了。 |
二、语音识别:先做过一遍的事
语音那边的故事比图像还早一点,只是没那么轰动。 在这之前,主流做法是 GMM-HMM:用高斯混合模型描述每一帧声音, 用隐马尔可夫模型描述音素怎么连成词。 换成深度学习,改动其实很小:把 GMM 换成几层神经网络,HMM 那套结构留着。 这叫 DNN-HMM 混合模型,2010 年前后开始取代 GMM-HMM。 值得留意的是「混合」这两个字。 它不是推倒重来,是把链条里最弱的一环换掉,剩下的照用。 工程上大部分进步都是这个形状——教材喜欢讲颠覆,现场多半是替换。
三、自然语言处理:把词变成向量
文本这块最难,因为输入根本不是数字。 在这之前,最常见的手段叫词袋:把一句话拆成词,数每个词出现几次,得到一个很长的稀疏向量。 词袋有两个硬伤,用一句话就能说清:
| 硬伤 | 后果 |
|---|---|
| 词序全丢了 | 「狗咬人」和「人咬狗」是同一个向量 |
| 词和词之间没有关系 | 「汽车」和「轿车」是两个毫不相干的维度 |
| 第一个硬伤靠循环网络补,第二个硬伤靠词向量补。 | |
| 词向量这件事的思路很漂亮:让每个词变成一个几十维的稠密向量, | |
| 让「意思相近」变成「向量离得近」。 | |
| 第 7 章那个 Chainer 的 Demo 里,变长序列的麻烦就是从这里来的。 | |
| 文本不像图像,每句话的长短都不一样,补零补到哪算完,是个真问题。 |
四、三块战场,同一个套路
把三个领域摆在一起,会发现它们的路线是复制的:
| 领域 | 人工特征时代 | 端到端时代 |
|---|---|---|
| 图像 | SIFT/HOG 加 SVM | 卷积网络吃像素 |
| 语音 | MFCC 加 GMM-HMM | 频谱进网络 |
| 文本 | 词袋加手工规则 | 词向量加循环网络 |
| 左列全是「人先想好该看什么」,右列全是「让网络自己找」。 | ||
| 这条路能走通,靠的不是算法变聪明,是数据和算力便宜到了某个点。 | ||
| 这一节最容易读出的一个误解是:人工特征不行。 | ||
| 其实不是。人工特征在它那个年代是最好的选择,只是它有个天花板——它只能看见设计者想到的东西。 | ||
| 数据一多,这个天花板就成了瓶颈;数据不够的时候,它反而是护栏。 |
五、Demo 要验的正是这件事
下面这个 Demo 就是那句「人工特征有天花板」的缩小版。 同一批 8 乘 8 的数字图,喂三种东西:原始 64 个像素、2 乘 2 池化后的 16 个数、 按七段数码管取的 7 个数。 第三种是人挑的特征,而且挑得极其内行——它直接照着「数字是怎么写的」来取。 按理说应该碾压原始像素。 实测结果反着来,而且反得很有道理。 这个 Demo 的结论不是「人工特征不行」,是「人工特征怕位置变化」:段码的掩码只有一列宽, 图往右挪一格,整段就落空了。
六、动手 Demo:三种表示,谁在什么条件下才占便宜
数据还是第 1 章那张 8 乘 8 的数字图,网络还是 64→32→16→10。 只换喂进去的东西,别的全不动。 这个 Demo 做了四层排除:先看谁赢,再排除「网络太小」,再排除「没训够」, 最后用一个对照实验把原因钉死。
# 同一批 8x8 数字图,三种喂法
def to_pool(X): # 64 -> 16:2x2 取最大
img = x.reshape(8, 8)
return np.maximum(np.maximum(img[0::2, 0::2], img[0::2, 1::2]),
np.maximum(img[1::2, 0::2], img[1::2, 1::2])).ravel()
MASK = {s: seg_mask(s) for s in 'abcdefg'} # 七段的像素位置
def to_seg(X): # 64 -> 7:每段亮不亮
img = x.reshape(8, 8)
return np.array([img[MASK[s]].mean() for s in 'abcdefg'])
# 数字 1 的段码是 [0, 1, 1, 0.25, 0, 0, 0.25]
# 把这张图向右滚一格,七个位置全变成 0.00
seg_b = [np.roll(one_digit(1), 1, 1)[MASK[s]].mean() for s in 'abcdefg']
实跑输出:
--------------------------------------------------------------------
应用案例:三种表示,谁在什么条件下才占便宜
--------------------------------------------------------------------
[1] 同一份数据,三种「看」法
第 1 章那张 8 乘 8 的数字图又回来了。同一批图,可以喂三种东西进去:
表示 维数 怎么来的
原始像素 64 每张图摊平,一个像素一个输入
池化后 16 2 乘 2 取最大,8 乘 8 压成 4 乘 4
段码 7 按七段数码管去问:a 到 g 七段各亮不亮
第三种是「人想出来的特征」。做这行的人有个朴素的期待:
七段码里 a 到 g 亮不亮,基本上已经把「这是几」写出来了,
网络拿到这么干净的七个数字,应该比啃 64 个像素轻松得多。
下面就来验一验这个期待。
[2] 每类给 20 张,三种表示各训一遍
网络 64→32→16→10 或者 7→32→16→10,都是两千轮、学习率 0.1、同一个种子。
训练集每类 20 张、带 ±1 像素平移;测试集每类 80 张、平移之外再翻一个像素。
表示 输入维 参数量 训练准确率 测试准确率
原始 64 像素 64 2778 1.0000 0.9025
4×4 池化 16 维 16 1242 0.7500 0.5563
7 维段码 7 954 0.7600 0.5613
结果和期待反着来:段码垫底,原始像素第一。
而且看「训练准确率」这一列——网络连训练集都背不下来,说明不是泛化出了问题。
[3] 先把「是不是网络太小」这条排除掉
7 维网络只有 954 个参数,64 维的有 2778 个。参数少也可能是它吃亏的原因。
把段码网络的隐层加宽,把参数量顶到跟 64 维差不多,再跑一遍:
7→45→45→10 2890 个参数 训练 0.7600 测试 0.5700
64→32→16→10 2778 个参数 训练 1.0000 测试 0.9025
参数从 954 加到 2890,准确率纹丝不动。不是容量的事。
[4] 那是没训够吗?把轮数加到十倍
两千轮提到两万轮,学习率也跟着调,看训练准确率能不能顶上去:
表示 两千轮 lr0.1 八千轮 lr0.5 两万轮 lr0.3
原始 64 像素 1.0000 1.0000 1.0000
7 维段码 0.7600 0.7650 0.7650
两万轮之后还是那个数。训练准确率顶到天花板下不来,说明这七个数字里
本来就没有足够的信息把十个类别分开——不是优化器偷懒,是输入混了。
[5] 为什么会混:拿数字 1 出来看
数字 1 在七段码里只亮 b、c 两段,而这两段在图上都是「第五列那一竖」。
段码掩码是按位置硬框定的:b 段取第 2 到 4 行第 5 列,c 段取第 5 到 7 行第 5 列。
现在把这张图往右平移一格:
段码 原图 右移一格后
a 0.00 0.00
b 1.00 0.00
c 1.00 0.00
d 0.25 0.00
e 0.00 0.00
f 0.00 0.00
g 0.25 0.00
原图七个数字是 [0.00 1.00 1.00 0.25 0.00 0.00 0.25]。
b 和 c 是整整 1.00,d 和 g 是 0.25——那点零头不是错,
是竖段的末端压在了横段的格子上。七段数码管本来就允许段与段在角落相交。
往右挪一格之后变成 [0.00 0.00 0.00 0.00 0.00 0.00 0.00]。
七个位置全归零。图上明明还站着两条竖线,可它们已经跑到掩码外面去了,
于是这个样本在段码表示里彻底消失,成了和「全黑的图」一模一样的东西。
单像素宽的竖段被卡死在一列上,容错量正好是一格。
[6] 验证:不让图平移,段码还吃亏吗
三个条件各跑三种表示。为了不让「不平移」退化成十张一模一样的图,
不平移那一组每个样本翻 2 个随机像素当扰动。
条件 原始 64 像素 池化 16 维 7 维段码
训练不平移、测试不平移 0.9775 0.8125 0.9775
训练平移、测试平移 0.9025 0.5563 0.5613
训练不平移、测试平移 0.2200 0.3625 0.2625
第一行里三种表示的差距一下子缩小了——段码从 0.5613 爬到 0.9775。
和原始像素打成平手,两列都是 0.9775。
只要图老实待在该待的位置,人想出来的七个数字确实够用——
它平移那组输掉的 0.4163,全是位置造成的。
第三行是把「训练没见过平移、测试全是平移」的情况摆出来,三种表示一起掉。
有意思的是掉得最狠的反而是原始像素,0.2200,比段码的 0.2625 还低。
原因不难猜:64 维网络把「第几行第几列亮着」当成了位置标签,
位置一挪,所有标签同时失效;段码本来信息就少,反而没那么多错的先验可依赖。
这也顺带说明一件事:分布偏移面前,输入维数高不是保护,是负担。
[7] 这件事跟这一章讲的应用案例有什么关系
教科书 8.1 节列了图像、语音、文本三块战场,每一块的路线都是同一个形状:
先有人工设计的特征,后来被端到端网络整个换掉。
领域 深度学习之前 深度学习之后
图像识别 SIFT/HOG 加 SVM 卷积网络直接吃像素
语音识别 MFCC 加 GMM-HMM 频谱直接进网络
自然语言 词袋加手工规则 词向量加循环网络
这一节测出来的东西,解释了「换掉」为什么发生得那么彻底。
手工特征从来不是不聪明,是太脆:它把「东西在哪」和「东西是什么」绑死了。
SIFT 之所以搞得那么复杂——关键点检测、尺度空间、方向直方图——
全是为了把这个绑定松开。而卷积网络从一开始就不绑:
同一个卷积核在整张图上滑一遍,第 3 章 3.7 测过平移一格后
卷积还有 0.5487,全连接只剩 0.3894。
所以 8.1 那三块战场被端到端吃掉,不只是因为数据变多了,
更因为「让网络自己找特征」这条路天生对位置不敏感。
数据管够的时候,它把手工特征唯一的那点便宜也追平了。
[8] 三条必须说清楚的限制
第一,这份数据是照着七段数码管造的,段码表示等于把答案抄进了输入,
它输不是因为笨,是因为平移。换成真实手写数字,它连抄都没得抄。
第二,十倍轮数只测了两个表示,中间那档池化的天花板没单独验,
只能说它那 0.5563 上限的原因和段码类似——池化把细线结构糊掉了。
第三,这里的结论只适用于「小样本加配准敏感」这一个角落。
第 3 章那张表是反着测的:手工特征加线性只有七成六,
原始像素加卷积是九成九。两边不矛盾——一边在说「什么时候人工特征划算」,
一边在说「数据够多时谁赢」。
第一层结论就跟预期相反:段码垫底(0.5613),原始像素第一(0.9025)。 而且看训练准确率那一列,段码只有 0.7600——网络连训练集都背不下来。 这说明问题不在泛化,在输入本身。 第二层排除:把段码网络的隐层从 32 加到 45,参数量从 954 顶到 2890, 跟原始像素那台的 2778 差不多。准确率纹丝不动(0.5700)。 不是容量的事。 第三层排除:两千轮加到两万轮,学习率从 0.1 试到 0.5,训练准确率卡在 0.7650 不动。 两万轮还上不去,那就不是优化器偷懒,是输入里没这个信息。 第四层,找到真凶:数字 1 的段码是 [0, 1, 1, 0.25, 0, 0, 0.25],向右滚一格之后, 七个位置全变成 0.00。 图上明明还站着两条竖线,可它们已经跑到掩码外面去了。 这个样本在段码表示里,变成了一张全黑的图。 最后的对照实验最干净:不让图平移,三种表示的训练测试同分布,段码立刻涨到 0.9775, 跟原始像素(0.9775)打成平手。 它输掉的那 0.4163,全是位置造成的。 还有一个反直觉的结果:训练没见过平移、测试全是平移的时候, 掉得最狠的反而是原始像素(0.2200),比段码(0.2625)还低。 64 维网络把「第几行第几列亮着」当成了位置标签,位置一挪全失效。 输入维数高不是保护,是负担。
30 秒实验
- 把段码的掩码从一列宽改成两列宽。 平移一格之后就不会全归零了,准确率应该能爬回来一截。 这就是 SIFT 里「尺度空间」要解决的那件事的迷你版。
- 把数字 1 单独拿出来,向右滚 0 到 3 格,打印每一步的段码向量。 你会看到它一格一格地「消失」——这是人工特征脆性的最直观演示。
- 把三种表示都加上数据增强(随机平移),再跑一次平移测试。 三种都会涨,但涨的幅度不一样——原始像素涨得最多,因为它有东西可学。
常见误区
- 「深度学习赢了,所以人工特征没用了。」 这一节的对照实验说明相反:不平移的那一组里,人工特征和原始像素打平。 人工特征不是没用,是它的有效期取决于「世界有多规整」。
- 「卷积网络比全连接强,因为它参数少。」 参数少只是省显存。 第 3 章 3.7 和这一节都指向同一件事:卷积真正强的地方是它不管位置,权重在整张图上滑。
- 「AlexNet 那一年是一次突变。」 算法层面没那么突变——卷积、池化、ReLU、Dropout 在它之前都有。 突变的是「两个 GPU 加一百二十万张标注图」这个组合,凑齐了才跑得动。
自测三问(点开对答案)
1. **为什么七段码这个人挑的特征,反而输给了原始像素?** —— **因为它的掩码按位置硬框定,一像素宽的竖段容错量正好是一格。** **图一平移,掩码里就空了。去掉平移之后它立刻追平原始像素。** **所以它输的不是信息量,是配准。** 2. **语音识别那次替换,为什么叫「混合」而不是「端到端」?** —— **因为它只把链条里的 GMM 换成神经网络,HMM 那套结构留着了。** **真正端到端的语音识别要更晚,要等序列模型成熟。** **工程上的进步多半是这个形状:换一环,不是推倒重来。** 3. **词袋的两个硬伤分别靠什么补?** —— **词序丢失靠循环网络补,词与词之间没有关系靠词向量补。** **这两个东西合起来,才有了后来的文本模型。**应用讲完了,接下来是「还能走多远」。 教材 8.2 节列了三个瓶颈:数据、算力、黑箱。 下一节把这三个都量一遍——能量出来的量,量不出来的说清楚为什么。
- ☐ (本节完,下一节 8.2 深度学习的未来)