2

前言

前面我们了解了,给我们一张图片,想要计算机识别它,需要先处理图片,比如灰度化,二值化,裁剪,去噪等等,那么我们处理完成之后,就要开始思考计算机是如何识别这张图片的。简言之,一张手写数字图片,计算机到底怎么知道它是数字几?相信你也好奇,我们一起来看一下吧。

大脑识别数字的方式

image.png
这是一张手写图片5,我们看一眼可以不加思考的说出来是5,但是仔细想一下,大脑做了三件事:你的眼睛捕捉到光线->大脑分析形状->调出记忆'这是5'。尽管这个过程快到感觉不出来,但是它确实发生了。

实际上,我们大脑是这样工作的:

  1. 第一层:看到小零件

    • 这里有一条横线
    • 这里有一条竖线
    • 这里有一个拐角
  2. 第二层:把小零件拼成部件

    • 横线 + 竖线 = 一个直角
    • 弧线 + 弧线 = 一个圆圈
  3. 第三层: 把部件组合成数字

    • 顶部一个弧 + 中间一个弧 + 底部一个弧 = 这是8
    • 顶部横线 + 中间横线 + 底部横线 + 连接它们的右边竖线和左边竖线 = 这是5

最后得出结论,这是数字5。

计算机识别数字的方式

计算机和人不同,他没有眼睛和脑子,前面我们讲过,计算机看到的数字是一个像素矩阵image.png
在这里,我们1代表有笔迹,0代表空白,电脑看到的就是这样的像素矩阵,他也不知道什么是横线,什么是竖线,什么是拐角。那么计算机怎么从这样的一个像素矩阵得出来这个数字是5呢?

这里就需要用到卷积神经网络,也就是CNN,用它来模拟人脑的分层机制。

CNN在这里可以简单的理解为3层(实际上可以更多,这里以三层为例)
第一层:识别小零件
第二层:把小零件组合成部件
第三层:把部件组合成完整的数字

生动的例子

想象在一个小房间,墙上贴着一张数字5的图片(28x28),房间里站着32个人,每个人手里拿着一个小零件探测器(3x3),1号探测横线,2号探测竖线,3号探测左斜线等等,每个人都不同,这些人开始工作了。


1号(横线探测器) 拿着自己的探测器,在图片上从上到下、从左到右扫。每照到一个位置,探测器就会显示一个数字:

  • 照到“5”的顶部横线 → 显示 0.9(很亮,因为很匹配)
  • 照到“5”的中间横线 → 显示 0.8(也很亮)
  • 照到“5”的底部横线 → 显示 0.9(很亮)
  • 照到空白区域 → 显示 0.0(不亮)
  • 照到竖线区域 → 显示 0.1(几乎不亮,因为不匹配)

1号的任务结束后,他交出了一张“横线热度地图”:整张图片上,哪里横线强、哪里横线弱,标得清清楚楚。2号(竖线探测器) 也干同样的事。他照到“5”的左边竖线 → 显示0.7;照到右边竖线 → 显示0.6;照到横线区域 → 几乎不亮。最后,2号交出一张“竖线热度地图”。

3号、4号、5号……一直到32号,每个人交出一张自己的“热度地图”。


一个小时后,墙上不再是一张图片,而是32张热度地图,每张都标注了"某一小零件在图片各个位置的强度"。第一批32个人干完活后,共有32张热度地图,每个都是26x26的,这时候来了一个偷懒的助手,“这些地图太大了,格子数太多了,我懒得看,需要精简一下”

如何精简呢?

以2*2小块为例子,我原本是image.png,但是现在助手看一眼说,最亮的是9,那现在我就要这个9,其他的都舍弃,那么原先26x26的图片,就会变成13x13,格子数直接变成原来的1/4。32张地图都这么处理。


第一批人下班了,第二批人来了,共64个,他们看的不是原始图片,而是第一批人画的32张热度地图,而他们的探测器更复杂一下,不再是简单的横线探测器,而是一个组合,比如1号是左边有竖线 + 底部有横线的└, 2号就是右边有竖线 + 顶部有横线的 ┐......

这批人干完活,有交出64张热度地图,每个都是11x11的,每张对应一种零件组合。

这个偷懒助手还是会再次偷懒,再次去除,最后得到5x5的。


第二批人下班了,再来第三批人,这时候有128个人,他们看的是第二批的64张热度地图。他们的探测器更高级,可以探索复杂的局部特征,这些特征已经非常接近完整的数字部件,比如1号是“0”的大圆圈特征, 2号是“5”的左上角特征,3号是“4”的旗子形特征,4号是“5”的左上角特征 ......

每人拿着自己的复杂特征探测器,在5×5的64张热度地图上滑动。每照到一个位置,探测器计算“这个位置有没有我要找的复杂模式”,输出一个数字(越亮越符合)。
注意:5×5的地图已经很小了,滑动几次就覆盖完了。最终每个探测器会输出一张更小的热度地图,再经过最后一次池化,变成1个数字。也就是说,第三批的128个人,每人最终只产出1个数字,代表“整张图片里,我要找的那种复杂特征的强度”。

1号是0.1 ,2号是0.9,3号是0.1,4号是0.6......


最后再进来一批人,10个投票者,每人负责一个数字(0-9),他们不看图片,不看热度地图,而是看第三批的128的数字。5号投票者拿到这128个数字,发现2号权重是0.9,4号是0.6 ...... 4号投票者也拿到这128个数字,发现1号是0.1.....然后计算出得分,发现5的最高,那么得出结论这是5。

卷积层

在第一层,CNN准备了32个"探测器",每个探测器专门负责找一种小零件:1号 -> 找横线, 2号 -> 找竖线, 3号 -> 找左斜线, ......

怎么找?

简单来说,可以将探测器想象成3*3的小方块,里面填充了一组数字,这个数字决定了这个探测器是什么样子的。

比如横线探测器长这样:image.png把它盖在图片上,如果上面一排是1会加分,如果下面一排是1会减分,而如果是中间一排则不管,就是我期待第一排是横线,第三排是空白的,第二排不管。


卷积计算

从左上角开始,盖住行1-3,列1-3,这部分全是0,则计算后的结果是0。我们直接滑到有数字的位置image.png,进行计算 1 x 1 + 1 x 1 + 0 + 1 x (-1) = 1,滑动到image.png进行计算: 1 x 1 + 1 x 1 + 1 x 1 + 0 + 1 x (-1) + 0 = 2, 如果滑动到这里image.png,此时计算结果是-3, 但是我们不取-3,ReLU会把负数变成0,所以最后输出=0。

ReLU = Rectified Linear Unit(线性修正单元),把负数清零,正数保留。让模型只关注"有特征"的地方,忽略"反特征"的地方。ReLU将负数变为0,正数保持不变。

我们可以看出,经过滑动之后,原本10x10的像素矩阵变成了8x8的特征图:image.png

这张图说明了:数字大的地方 = 清晰的边缘,数字小的地方 = 微弱的边缘, 0的地方 = 没有这种边缘。

一共32个不同的卷积核进行滑动,得到不同的特征图。

池化

32张热度图,每张都挺大的。为了减少计算量,CNN做了一个操作:池化。

池化很简单。以2×2的池化为例:把热度图分成2×2的小块,每个小块只保留最大的那个数字,其他三个扔掉。

原来8×8的图(64个数字)会变成4×4的图(32个数字),数据量减少75%。

为什么要池化?两个原因:1.计算量变小,跑得快; 2.让模型对“小偏移”不敏感 —— 数字写歪了一点点,不影响判断。

池化之后,32张热度图每张都缩小了一半(比如从8×8变成4×4)。image.png

第二层卷积层

第一层找到的是小零件(横线、竖线)。第二层的任务是把这些小零件组合成更大的部件。

这一层有64个探测器。注意:这层的探测器不再看原始图片,而是看第一层输出的32张热度图。也就是说,第二层的探测器能看到“哪里横线强、哪里竖线弱”这种组合信息。

比如有一个探测器专门找“直角”:它要求:左边有竖线(强度高) + 底部有横线(强度高),两个条件同时满足 → 这个探测器亮起来。

第二层输出64张新的热度图,每张代表一种部件的分布。

再池化一次,图片继续缩小。

此时热度图变成了1x1的,不需要复杂特征探测器了。

第三层卷积层

但是如果是28x28的图片,再经过第二层处理与池化后图片变为变为5x5的图片,可以再次进行第三层处理。

第二层找到的是部件组合(直角、门框形)。第三层的任务是把这些部件组合成更复杂的特征,这些特征已经非常接近完整的数字了。

这一层有128个探测器。注意:这层的探测器不再看原始图片,也不再看第一层的热度图,而是看第二层输出的64张热度图。也就是说,第三层的探测器能看到“哪里直角强、哪里门框形强”这种更高级的组合信息。

比如:

1号探测器:专门找“0”的大圆圈特征 → 它要求:上下左右都有弧线,围成一个封闭的圈
2号探测器:专门找“5”的左上角特征 → 它要求:顶部横线的左端 + 左边竖线的上端,同时出现
......

还有一类探测器专门负责不同写法:

50号探测器:找偏左写的5
51号探测器:找偏右写的5
......

第三层的每个探测器最终只产出1个数字,代表“整张图片里,我要找的那种复杂特征的强度”。

结果示例(对于一张数字5的图片):

  • 1号探测器(0的大圆圈)→ 0.1(5没有大圆圈)
  • 2号探测器(5的左上角)→ 0.8
  • 3号探测器(4的旗子形)→ 0.1
  • 4号探测器(8的两个圆圈)→ 0.0
  • 5号探测器(7的横线+斜线)→ 0.2
  • ......
  • 50号探测器(偏左写的5)→ 0.2
  • 51号探测器(偏右写的5)→ 0.1
  • 52号探测器(笔画粗的5)→ 0.3
  • 53号探测器(笔画细的5)→ 0.2
  • ......

第三层输出:128个数字。每个数字在0-1之间,代表某种复杂特征的存在强度。

全连接层(Dense)与输出层

第三层结束后,我们有128个特征值。这128个特征值就是这张图片的“特征向量”。

最后一步,我们需要根据这128个特征值判断是数字几。

这一层有10个神经元,每个神经元对应一个数字(0-9)。它们不看原始图片,不看特征图,只看第三层的128个特征值。每个神经元都有自己的权重,对128个特征值进行加权求和,然后通过Softmax函数转换为概率分布,概率最高的即为识别结果。

CNN如何学会这些?

你可能会问:探测器里的数字(比如横线探测器里那组[1,1,1,0,0,0,-1,-1,-1])是谁设计的?第二层、第三层的探测器又是谁设计的?为什么偏偏是这些数字?

答案是:没有人设计。这些数字是训练出来的。

CNN一开始什么都不会。它需要经过一个“学习”过程,才能变成我们前面描述的那个样子。

随机初始化

在训练开始之前,所有探测器(卷积核)里的数字都是随机的。也就是说,第一层的32个探测器并不知道自己要找横线还是竖线,第二层的64个探测器也不知道自己要找什么部件组合。它们只是32个装满随机数的3×3小方块。

可想而知,这个时候CNN的识别结果完全是乱猜的。给它一张5,它可能说这是3;给它一张8,它可能说这是0。

从错误中学习

接下来进入训练环节。我们给CNN看大量图片,每张图片都附带一个“正确答案”标签。

比如给CNN看一张5的图片,同时告诉它:“这张图正确的答案是5。”

CNN先用自己的随机探测器做一次完整的计算(卷积→ReLU→池化→全连接→Softmax),得到一个预测结果,比如“5的概率10%,3的概率80%,其他数字各占一点”。它会猜错。

然后我们计算一个“损失值”——这个值代表预测结果和正确答案之间的差距。猜得越离谱,损失值越大。

有了损失值之后,CNN从最后一层开始,一层一层往前倒推,计算每个探测器里的每个数字对这次错误“贡献”了多少。贡献大的数字需要大调,贡献小的数字需要小调。这个过程叫反向传播。

最后,根据反向传播的结果,微调每个探测器里的数字。调完之后,再用下一张图片重复这个过程。

训练与测试

通常我们会把数据分成两份:

训练集(比如60000张):用来训练模型。CNN通过这些图片学习,每次学完都调整自己的参数。

测试集(比如10000张):模型从未见过的图片。训练完成后,用这些图片来验证模型的真实效果。如果模型在测试集上也能达到很高的准确率,说明它是真的学会了,而不是仅仅背下了训练集。

训练的结果

经过几万次、甚至几十万次这样的调整(术语叫“迭代”),探测器里的数字逐渐从随机变成了有意义的形状。

你会发现:

第一层的某个探测器变成了横线检测器(数字[1,1,1,0,0,0,-1,-1,-1])

第一层的另一个探测器变成了竖线检测器

第二层的某个探测器变成了直角检测器(看到左边有竖线、底部有横线就亮)

第三层的某个探测器变成了“5的整体骨架”检测器

有些探测器变成了人类能理解的有意义形状,有些则变成了人类看不懂的奇怪形状——但CNN觉得好用。这就是它的厉害之处:不需要人教它“长什么样”,它自己从数据里学。

问题

将可能存在的疑惑总结在下面。

问题1:为什么用3×3的卷积核,不用2×2或者5×5?

3×3是目前最常用的选择。2×2太小,看不到足够的上下文信息(比如看不到横线两端的延伸);5×5太大,参数量多(25个参数 vs 9个参数),计算更慢。而且两个3×3卷积层堆叠,感受野相当于一个5×5,但参数量更少(9+9=18 < 25)。所以3×3是效率最高的选择。

问题2:32、64、128这些数字是怎么来的?为什么不能是10个或者1000个?

这些数字是经验值,不是数学推导出来的。32、64、128是经典的倍增模式,因为越往后特征越复杂,需要更多探测器。用10个太少,学不到足够的特征;用1000个太多,计算慢而且容易过拟合。32/64/128是在效果和效率之间的平衡。你可以理解为:32个找基础特征(横竖斜),64个组合成部件,128个识别复杂模式。

问题3:池化一定要用2×2吗?能不能用别的?

可以用别的。3×3池化、4×4池化都可以,但2×2最常用。原因很简单:2×2不丢失太多信息,又能减少75%的数据量。如果池化窗口太大(比如4×4),信息丢失太严重,可能会把重要的特征也扔掉了。

问题4:ReLU把负数变成0,会不会丢掉重要信息?

不会。负数代表“和要找的特征相反”。在手写数字识别中,我们只关心“有没有这个特征”,不关心“有没有相反的特征”。比如横线探测器,我们只想知道“这里有没有横线”,不需要知道“这里有没有反过来的横线”(那种情况在正常图片里不存在)。所以把负数变成0是安全的,反而让下一层的计算更干净。

结语

一开始,计算机看到的只是一堆0和1组成的数字矩阵。它不知道什么是横线,什么是竖线,更不知道什么是数字。

然后,通过卷积层,它学会了用小方块去扫描图片,寻找横线、竖线、斜线这些小零件。通过ReLU,它只保留有用的信息,扔掉负数。通过池化,它把图片缩小,让计算更快,也让模型对数字的偏移不那么敏感。

接着,通过第二层卷积,它把小零件组合成部件——直角、圆圈、门框形。通过第三层卷积,它把部件组合成更复杂的特征——数字的整体骨架、不同写法的模式。

最后,通过全连接层和Softmax,它把所有这些特征汇总起来,投出每一票,选出得分最高的数字。

计算机从头到尾不知道什么叫“5”。它只知道:当一张图片经过这些计算后,5号投票者的分数最高。

这就是CNN的核心思想:层层抽象,逐级组合。从像素到边缘,从边缘到部件,从部件到完整数字。每一层都建立在上一层的基础上,每一层都比上一层看得更“大”、更“抽象”。

以上是我关于CNN最基础知识的理解,如果存在问题,感谢指出!


李子轩xuan
66 声望11 粉丝