在深度学习之前,认图靠的是流水线:预处理、提特征、再分类。 四个零件,每个都得人来挑。 这一节把「挑特征」这件事量成一张表。 结果跟直觉相反:在干净的小图上,原始像素反而最强,越「高级」的特征越差。 可噪声一大,胜负就翻过来了。
这一节是 10.2 的落地版。上一节用主成分降维,这一节换几种更土的手工特征。 它也是 9.4 的反面: 9.4 说「卷积核让结构自带平移不变性」,这一节说「人也设计得出这种结构,而且设计得还不错」。 差别在于,人设计的那个只管一个任务,学出来的可以管很多任务。
一、一条图像流水线有四个零件
五类 8 乘 8 的点阵数字,每类 40 张训练、40 张测试。 每张图先按概率翻掉一批像素,再随机平移最多一格。 对机器来说,这不是图,就是一串 64 个数。流水线要做的第一件事, 是决定「把这 64 个数换成什么」再交给分类器。 四个零件分别是:预处理(去噪、二值化、裁剪)、特征提取(换一种记账方式)、 降维(挑出几个真有用的数)、分类器(最近邻、SVM、随机森林)。 这一节主要动第二个零件。因为在整个流水线里,它决定了后面所有环节的上限。 特征里没有的信息,再好的分类器也变不出来。
二、五种特征,噪声 10% 的时候谁赢
先看噪声 10% 这一档,只换特征,分类器一律用最近邻:
| 特征 | 维数 | 怎么算 | 测试准确率 |
|---|---|---|---|
| 原始像素 | 64 | 直接摊平 | 0.9650 |
| 行列投影 | 16 | 8 个行和 + 8 个列和 | 0.9050 |
| 梯度直方图 1 块 | 8 | 整图梯度方向加权计数 | 0.5700 |
| 梯度直方图 4 块 | 32 | 切 2 乘 2 四块各算一遍 | 0.7700 |
| 梯度直方图 16 块 | 128 | 切 4 乘 4 十六块 | 0.8800 |
| 原始像素赢了,16 维的行列投影紧跟在后头,梯度直方图垫底。 | |||
| 这跟「高级特征一定更强」的直觉相反,得说清楚为什么。 | |||
| 这几类数字的区别,恰恰就在「笔画落在哪几格」——位置本身就是身份。 | |||
| 而梯度方向直方图把位置信息扔掉了。 | |||
| 1 块版本只剩 8 个数,整张图只记「有横有竖有斜」,谁也认不出是 0 还是 3。 | |||
| 把块数从 1 加到 4 再加到 16,准确率从 0.5700 一路爬到 0.8800——它扔的全是干货,扔得越少越好。 | |||
| 这背后有一条更一般的规律:特征的设计,本质是在决定「记住什么、忘掉什么」。 | |||
| 忘掉的正好是没用的,就赚;忘掉的正好是有用的,就赔。 |
三、噪声一大,胜负就翻
同一套数据,把翻转概率一路加上去:
| 特征 | 5% | 20% | 30% | 40% |
|---|---|---|---|---|
| 原始像素 | 0.9750 | 0.7800 | 0.4500 | 0.2150 |
| 行列投影 | 0.9800 | 0.6500 | 0.3750 | 0.2600 |
| 梯度直方图 1 块 | 0.8100 | 0.3450 | 0.2150 | 0.2050 |
| 梯度直方图 4 块 | 0.9150 | 0.4650 | 0.3200 | 0.2350 |
| 梯度直方图 16 块 | 0.9550 | 0.5850 | 0.3800 | 0.1800 |
| 转折点在这里:翻到 40%,原始像素掉到 0.2150,跟瞎猜的 0.2000 只隔一张纸; | ||||
| 反而是 16 维的行列投影还有 0.2600,成了第一名。 | ||||
| 规律不复杂:特征越粗,单个像素翻错对它的影响越小,噪声一大就显出抗噪; | ||||
| 代价是它一开始就丢了一截判别力,噪声小的时候白丢。 | ||||
| 所以「用哪个特征」的正确答案永远跟着噪声水平走,没有通吃的。 | ||||
| 这一点在工程上很要命:你在实验室里用干净数据挑出来的特征,到了现场(光照、雨雪、 | ||||
| 压缩噪声)未必还是最好的。 |
四、抗噪和抗平移,是两件事
前面每张图既加噪声又平移,混在一起看不清是谁在拖后腿。 拆开量:同一张图,只加噪声,或者只平移,看特征变了多少。用余弦相似度,1.0000 表示一点没变:
| 扰动 | 原始像素 | 行列投影 | 梯度直方图 1 块 |
|---|---|---|---|
| 只翻 20% 像素 | 0.7095 | 0.9066 | 0.9421 |
| 只平移 1 格 | 0.2488 | 0.6729 | 0.8837 |
| 原始像素两头都最脆。平移一格,相似度就掉到 0.2488——因为笔画整体挪了位,64 个数全错位了。 | |||
| 行列投影对平移掉得慢得多(0.6729):它只记「每行有几个黑点」,不记在哪一行。 | |||
| 梯度直方图对两样都不太敏感(0.9421 和 0.8837),因为它压根不记像素值,只记方向。 | |||
| 但请把这张表和上一张表对起来看:梯度直方图最稳,准确率却最低。 | |||
| 稳定只是必要条件,不是充分条件。一个恒等于常数的特征最稳定—— | |||
| 它对噪声的不变性和对类别的无差别,是同一件事。 | |||
| 这就是「特征工程」最难受的地方:你没法单独优化稳定性,也没法单独优化判别力, | |||
| 两者在同一个设计选择上互相拉扯。 |
五、预处理,和它的边界
最后一环是预处理。噪声 30% 时,加一步最土的 3 乘 3 均值滤波:
| 流水线 | 不用滤波 | 先滤波 |
|---|---|---|
| 原始像素 | 0.4200 | 0.4500 |
| 行列投影 | 0.3700 | 0.4000 |
| 涨 3 个点,不多,但方向是稳的。 | ||
| 注意这一步在真实系统里是全自动的——没人手工去滤测试图,所以这么比是公平的。 | ||
| 预处理的边界在哪儿?它只能对付「已知类型的噪声」。 | ||
| 均值滤波对高斯白噪声有效,对椒盐噪声一般,对运动模糊基本无效。 | ||
| 所以工业界的预处理往往是好几步串起来,每一步都针对一种已知的退化。 | ||
| 把这一节收一下。 | ||
| 这条流水线在 2012 年之前是绝对主流:人工设计特征(SIFT、HOG、Haar)加一个分类器(SVM、 | ||
| 随机森林)。 | ||
| 它的死结就在这一节的四张表里: | ||
| 特征得靠人挑,挑法跟噪声水平、跟任务、跟数据量全挂钩,没有通吃的答案。 | ||
| 而 9.4 讲的卷积核,就是把这个「挑」的活儿交给数据自己去学—— | ||
| 同一个道理,只是决定「记住什么」的那个人,从工程师换成了梯度下降。 |
六、动手 Demo:一条图像识别流水线,特征换一个就全变了
五段:数据长什么样、五种特征在 10% 噪声下谁赢、噪声扫到 40%、抗噪和抗平移拆开量、 预处理值多少。 第 2 段和第 3 段是一对,一个说「原始像素最强」,一个说「噪声一大就翻盘」。 它们合起来构成这一节最有用的一句话:特征没有绝对的好坏,只有匹不匹配。
def f_proj(a): # 16 维:8 个行和加 8 个列和
return np.concatenate([a.sum(1), a.sum(0)]) / 8.0
def f_hog(a, nb=8): # 8 维:梯度方向的加权直方图
gy, gx = np.gradient(a) # 水平和垂直方向的差分
mag = np.hypot(gx, gy) # 这个点的梯度有多强
ang = np.arctan2(gy, gx) % (2.0 * np.pi)
b = np.minimum(nb - 1, (ang / (2.0 * np.pi / nb)).astype(int))
h = np.bincount(b.ravel(), weights=mag.ravel(), minlength=nb)
return h / (h.sum() + 1e-9) # 归一化,去掉整体明暗的影响
def blur(a): # 3 乘 3 均值滤波,最土的去噪
p = np.pad(a, 1, mode=u"constant")
out = np.zeros_like(a)
for i in range(a.shape[0]):
for j in range(a.shape[1]):
out[i, j] = p[i:i + 3, j:j + 3].mean()
return out
实跑输出:
--------------------------------------------------------------------
一条图像识别流水线,特征换一个就全变了
--------------------------------------------------------------------
==============================================================
[1] 先看清楚要认的东西
五类 8 乘 8 的点阵数字,每类造 40 张训练、40 张测试。
每张图先按概率翻掉一批像素,再随机平移最多一格。原图长这样:
0 ..####.. .#....#. #......# #......#
#......# #......# .#....#. ..####..
1 ...##... ..###... ...##... ...##...
...##... ...##... ...##... .######.
2 ..####.. .#....#. ......#. .....#..
....#... ...#.... ..#..... .######.
3 ..####.. .#....#. ......#. ...####.
......#. ......#. .#....#. ..####..
4 ....##.. ...#.#.. ..#..#.. .#...#..
#....#.. ######.. .....#.. .....#..
噪声 20%、平移一格之后,其中一张「3」变成什么样:
噪声 0%
..####..
.#....#.
......#.
...####.
......#.
......#.
.#....#.
..####..
噪声 20%
...###..
.......#
..#...#.
.#.#####
.##...#.
......##
....#.##
..###..#
对机器来说,这从来不是图,就是一串 64 个数。
流水线要决定的,是「把这 64 个数换成什么」再交给分类器。
[2] 五种特征,噪声 10% 的时候谁赢
同 200 张训练、200 张测试,只换特征,最近邻分类:
特征 维数 测试准确率
原始像素 64 0.9650
行列投影 16 0.9050
梯度直方图 1 块 8 0.5700
梯度直方图 4 块 32 0.7700
梯度直方图 16 块 128 0.8800
**原始像素赢了,16 维投影紧跟在后头,梯度直方图垫底。**
**这跟「高级特征一定更强」的直觉相反,得说清楚为什么:**
**这几类数字的区别就在「笔画落在哪几格」,位置本身就是身份。**
**梯度直方图恰恰把位置信息扔掉了——1 块版本只剩 8 个数,谁也认不出。**
**把块数从 1 加到 4 再加到 16,准确率一路往上爬,正说明它扔的全是干货。**
[3] 但噪声一大,胜负就翻了
同一套数据,把翻转概率一路加上去:
特征 5% 20% 30% 40%
原始像素 0.9750 0.7800 0.4500 0.2150
行列投影 0.9800 0.6500 0.3750 0.2600
梯度直方图 1 块 0.8100 0.3450 0.2150 0.2050
梯度直方图 4 块 0.9150 0.4650 0.3200 0.2350
梯度直方图 16 块 0.9550 0.5850 0.3800 0.1800
**转折点在这里:翻到 40%,原始像素掉到 0.2150,跟瞎猜的 0.2000 隔着一张纸;**
**反而是 16 维的行列投影还有 0.2600。**
**规律不复杂:**
**特征越粗,单个像素翻错对它的影响越小,噪声一大就显出抗噪;**
**代价是它一开始就丢了一截判别力,噪声小的时候白丢。**
**所以「用哪个特征」的正确答案永远跟着噪声水平走,没有通吃的。**
[4] 抗噪和抗平移,是两件事
前面每张图既加噪声又平移,混在一起看不清是谁在拖后腿。
拆开量:同一张「3」,只加噪声,或者只平移,看特征变了多少。
用余弦相似度,1.0000 表示一点没变:
扰动 原始像素 行列投影 梯度直方图 1 块
只翻 20% 像素 0.7095 0.9066 0.9421
只平移 1 格 0.2488 0.6729 0.8837
**看得出来了:原始像素两头都最脆。**
**平移一格就能让它相似度掉到 0.2488——因为笔画整体挪了位,64 个数全错位了。**
**行列投影对平移掉得慢得多(0.6729):它只记「每行有几个黑点」,不记在哪一行。**
**梯度直方图抗噪最强(0.9421),因为它压根不记像素值,只记方向。**
**但请把这张表和上一张表对起来看:梯度直方图最稳,准确率却最低。**
**稳定只是必要条件,不是充分条件——一个恒等于常数的特征最稳定,**
**它对噪声的不变性和对类别的无差别,是同一件事。**
最后补一枪:预处理。噪声 30% 时,加一步最土的 3 乘 3 均值滤波:
流水线 不用滤波 先滤波
原始像素 0.4200 0.4500
行列投影 0.3700 0.4000
**涨得不多,但方向对了。这一步在流水线里是全自动的,**
**所以「预处理」在工程上从来不算可选项。**
[5] 这条流水线在深度学习之前一直用
五段连起来:**噪声小的时候像素和投影都在 0.9 以上,别急着上花活**;
**噪声一上来,粗特征反而反超**;**抗噪和抗平移是两码事**;
**而最稳的那个往往判别力最差**;**预处理是免费的几个点**。
2012 年之前图像识别的标准做法就是这套:
人工设计特征(SIFT、HOG、Haar)加一个分类器(SVM、随机森林)。
它的死结也在这一节里摆着——特征得靠人挑,而挑法跟噪声水平、
跟任务、跟数据量全挂钩,没有通吃的答案。
9.4 那节讲的卷积核,就是把这个「挑」的活儿交给数据自己去学。
下一节把同一套思路搬到声音上:像素换成波形采样点,
梯度方向直方图换成频谱包络。
第 2 段那张表的排序值得记下来:64 维的原始像素赢过 128 维的梯度直方图。 这不是「深度学习没用」,是「在这个任务上位置就是身份」。 换个任务(比如从街景里找行人),排序就会反过来。 第 3 段那个 0.2150 对 0.2600 是转折点。 请注意 0.2000 是瞎猜的线——原始像素在 40% 噪声下已经贴着它走了。 第 4 段拆开量的做法本身值得学。 前面所有实验都是「噪声 + 平移」两个扰动一起上,得出的结论说不清是谁造成的。 把扰动控制成单因子,问题一下就清楚了。 第 4 段最后那几句是本节的骨头:稳定和有用不是一回事。 梯度直方图相似度 0.9421(最稳),准确率 0.5700(最差),这两个数放在一起才完整。
30 秒实验
- 把第 3 段的噪声加到 50%,重跑。 五种特征会全部塌到 0.2 附近——噪声超过信号的时候,什么特征都救不了,该做的是换传感器。
- 把第 2 段的梯度直方图块数从 16 加到 64(每块 1 个像素),重跑。 准确率会往原始像素靠——块小到极限,梯度直方图就退化成像素本身了。
- 把第 5 段的滤波换成 5 乘 5 均值,重跑。 30% 噪声下会再涨一点,但 10% 噪声下反而掉——滤波窗口太大,把笔画也抹平了。
常见误区
- 以为「特征越高级越好」。Demo 第 2 段:8 维梯度直方图只有 0.5700, 64 维原始像素 0.9650。位置是身份的时候,忘掉位置就是自杀。
- 以为「稳定的特征一定好用」。Demo 第 4 段:梯度直方图相似度 0.9421 最稳, 准确率 0.5700 最差。常数特征最稳,也最没用。
- 以为「噪声和位移可以混着算」。Demo 第 4 段拆开之后才看清: 原始像素掉分的主因是平移(0.2488),不是噪声(0.7095)。 混在一起量,你会把账算错。
- 以为「预处理总能加分」。它能对付已知类型的退化。 均值滤波对高斯噪声有效,对椒盐噪声一般,对运动模糊基本无效。 预处理不是万能的,它是针对性的。
自测三问(点开对答案)
1. 为什么 8 维的梯度直方图在这个任务上那么差? —— 因为**这几类数字的身份就写在「笔画落在哪几格」上,而梯度直方图把位置扔了**。 Demo 第 2 段:1 块版本只有 0.5700, **切到 4 块 0.7700,切到 16 块 0.8800,越细越好——正说明它扔掉的都是干货。** 2. 为什么噪声一大,粗特征反而反超? —— 因为**粗特征对单点扰动不敏感**。 Demo 第 3 段:40% 噪声下,**原始像素 0.2150(瞎猜是 0.2000),行列投影还有 0.2600。** **代价是噪声小的时候它白丢判别力——5% 噪声下像素 0.9750,投影 0.9800,其实差不多,** **但 10% 时像素 0.9650 对投影 0.9050,差距就出来了。** 3. 「稳定」和「好用」为什么不是一回事? —— 因为**稳定只要求「输入变了输出别变」,不要求「不同类别输出不同」**。 Demo 第 4 段:梯度直方图对噪声的相似度 0.9421,对平移 0.8837,两项都最稳; **可准确率 0.5700 最低。** **一个恒等于常数的特征,稳定到极点,判别力为零。**下一节把同一套思路搬到声音上。 像素换成波形采样点,梯度方向直方图换成频谱包络。 会多出一个图像没有的麻烦:声音有长短,同一个词说快说慢,采样点数就不一样。 这个麻烦在图像里不存在,在语音里是头号问题。
- ☐ (本节完,下一节 10.4 语音识别)