卷积运算
用小尺寸卷积核在图像上滑动并提取特定特征。
从"只看局部"出发
上一节我们得到的结论是:对图像有用的层结构应当满足两个性质—— 只关注局部 ,以及对 同一种特征在不同位置出现 保持 平移不变 (translation invariance)。 前者由"只看一个小窗口"这件事本身自然给出;后者意味着: 无论一只猫的耳朵出现在画面左上角还是右下角,识别它所用的局部计算 应当完全一样 。
要在工程上实现"平移不变",最直接的做法是让 同一组参数在所有位置反复使用 , 也就是 跨位置的参数共享 ——这只是手段,目的仍然是平移不变。 把"只看局部 + 同一组参数走遍每个位置"合在一起,便得到一种很自然的运算: 用一个小尺寸的"局部探测器",在大图上一格一格挪过去 , 每挪到一处就看看"局部范围里有没有我感兴趣的特征"。
这种"小窗口 + 滑动 + 局部加权"的运算,在图像处理领域有个名字—— 卷积 (Convolution)。 在引入神经网络之前,卷积已经被用了几十年来做边缘检测、模糊、锐化等图像处理。 本节我们先把它 当成一个固定的运算 看清楚,下一节再让它"长出"可训练参数。
卷积运算:小窗口滑动 + 对位相乘求和
卷积运算的核心是 两个矩阵 :
- 输入图像 :一张较大的矩阵,尺寸 。
- 卷积核 (kernel,也称 filter) :一个尺寸较小的矩阵, 常见尺寸如 、 。
计算过程是:把卷积核 从图像左上角开始 ,每次按一定步长滑动; 每停一处,就把 卷积核与图像当前局部窗口对位相乘 , 再把所有乘积加起来——得到的就是输出图像在该位置的 一个像素值 。
公式里 4 个下标各有分工:
- 是 输出位置 ——指的是要算 的第 行、第 列那一格。
- 是 卷积核内部的下标 ——分别从 0 到 、0 到 遍历核里每一个元素。
- 所以 表示:把卷积核左上角对齐到输入图像的 这一格、再往右下方覆盖一个 窗口里的某个像素。
其中 是卷积核尺寸, 是卷积运算的输出,也叫 特征图 (feature map)。
控制滑动方式:步长(stride)与填充(padding)
上面的描述里隐含了两个默认假设:卷积核每次 右移 1 格、下移 1 格 ,并且滑动严格停留在图像内部。 实际使用中,这两点都会被显式地参数化——
步长指卷积核每次滑动跳过几格。严格来说它有 两个方向 : 横向步长 (每次向 右 移几格)和 纵向步长 (一行扫完后向 下 移几格)。
实践中绝大多数情况会让两者取同样的值,因此不做特别说明时,所说的"步长 "就是简写 ;本节后续公式也沿用这个简写。 默认 ,也就是逐格滑动。
在输入图像四周补一圈 0 ,让卷积核能"伸出"原图边界。
表示在 上下各补 1 行、左右各补 1 列 。 常用做法是设 (核为奇数时),可以让 输出与输入尺寸保持一致 。
把 一起代入,输出特征图的高与宽分别是:
当 时,公式就退化为最初那一版 ——输出会比输入小一圈; 要想"输出尺寸等于输入",最常见的做法就是让 padding 把这一圈再补回来。
如果一时没看明白上面的公式, 建议跟着下面的演示一步步算一遍 —— 卷积说到底就是"在窗口里乘一乘加一加",没有更复杂的成分。 你可以改改输入 / 卷积核的大小、步长、填充, 也可以直接点击输入或卷积核里的任意一格修改数值,立刻看到当前窗口算式与输出怎么变。
卷积核里的数字决定了它能"检测"什么
关键观察来了: 卷积运算本身是固定的,但卷积核里的数值不同,输出图像就完全不同 。 下面给出几个图像处理中的经典卷积核,目的是让大家 直观感受到 「换一组数字就能换一种功能」—— 重点是 建立感觉 ,不需要记住具体数值。 每个核下面的简短说明也只是帮助理解它"为什么有这种效果", 在阅读时只需关注"输出图像和原图的差别"即可。
把这些固定卷积核作用在同一张图上,得到的输出图各不相同—— 每一种卷积核都对应一种"我能识别什么"的 特定特征 。 下面这个演示把这件事变得可触摸:左边是输入的灰度图、右边是卷积后的输出图, 底部的 kernel 下拉框可以切换不同的预设核——选「自定义」还能自己改这 9 个权重, 输出图随之实时变化。 把鼠标移到输入或输出图上,输入图里红色虚线框标出的当前 感受野、 中间一整条乘加展开式、以及输出图上对应的落点会 同步移动 , 让每一个输出像素是怎么算出来的都看得清清楚楚;你也可以用 "上传图片" 换成自己的图来观察效果。
多切换几个核你会发现: 有意思的特征都对应着特定形状的卷积核 。 边缘检测类的核(边缘检测、Sobel)在人脸轮廓、五官边界这些 明暗突变处 给出强响应, 把一张完整的脸"提炼"成一组线条;浮雕、锐化各自强调不同的方向与细节;而单位核则原样保留输入。 这些核往往是"专家手工设计的"——比如 Sobel 算子是为了边缘检测专门设计的, 高斯模糊的核是为了平滑而设计的。
手工设计的局限
看到这里,问题就来了:
- 识别 边缘 要用 Sobel 核,识别 角点 要换一组核,识别 纹理 还得另设计……
- 同一种特征在不同尺寸下又需要不同尺寸的核;
- 真正复杂的任务(识别猫、识别人脸)所需要的特征, 人类根本说不清 该用什么核。
手工设计卷积核可以解决"特征已知 + 任务简单"的场景, 但很难扩展到现代视觉任务。 既然神经网络擅长 从数据中学习权重 , 一个自然的想法就是——
附录:卷积的数学定义与"互相关"术语
本附录仅作术语溯源,不影响后续学习,可跳过。
严格意义上的 卷积 ,连续形式为:
离散形式:
二维离散版本里, 等价于 先把核 翻转、再做对位相乘 。 我们这一节用的运算 没有这一步翻转,因此严格说叫 互相关 而非卷积。但由于卷积核在下一节会变为可学习参数,是否翻转不影响最终能否学到所需特征 (翻转后的核同样可被参数化学到),因此 PyTorch、TensorFlow 等深度学习框架都直接称之为"卷积", 约定俗成沿用至今。