最近在重新过 MIT 6.S191 Lecture 3(卷积神经网络)。2017 年我刚接触 CV 那会儿,CNN 算是吃饭的家伙,每天都在调。后来精力逐渐转到 ML Infra,成天跟 GPU 显存、通信拓扑和算子优化打交道,卷积网络的很多具体细节就慢慢生疏了——写个 nn.Conv2d 时 weight 的四维形状到底怎么排、Kaiming 初始化的方差怎么推、感受野怎么算,冷不丁被问到,脑子里还得反应一下。
这次顺着课件从头过了一遍,我最大的体会是:那些具体的输出尺寸公式、经典模型的层数、固定的 Conv → ReLU → Pool 模板,确实不用死记,需要时翻一眼手册或者 print 一下 shape 就行。反而是以前天天写、觉得理所当然以至于忽略掉的几个底层设计,这次回看时体会更深:
- 为什么处理图像必须从局部看起?
- 为什么一套参数可以在整张图上满场复用?
- 为什么卷积天然是平移等变而不是平移不变?
- 单层卷积只看小窗口,网络深层到底是怎么看到全局的?
- 空间上的变换和通道上的变换,为什么可以解耦开?
说白了,就是当初卷积神经网络为了吃下视觉数据,到底在模型结构里硬编码了哪些先验假设。
拉平成一维接全连接,到底不行在哪?#
拿一张常见尺寸的 RGB 图像来说:
1224 × 224 × 3最直观的暴力做法,就是把它展开成一个 150,528 维的向量,然后接全连接层(Fully Connected Layer)。
以前我跟同行聊到这,第一反应通常是"参数量爆炸":如果隐层开 1024 维,光第一层权重就有一亿五千万个参数,显存和算力根本顶不住。
但我后来想通的是,这只是工程层面的代价,还有一个更根本的拓扑问题——展开成一维向量之后,网络结构彻底丢失了输入的二维空间邻近关系。
在原始图像里,像素点 (100, 100) 和 (101, 100) 紧挨在一起。但拉平之后,它们无非是向量里两个普通的维度,跟 (0, 0) 与 (223, 223) 的距离在全连接层看来毫无区别。全连接层对每个输入维度都有独立的权重,把所有位置一视同仁,完全不知道输入数据本来带有二维网格拓扑。
如果图像左上角出现了一条边缘,右下角也出现了一条边缘,在物理世界里它们是同一种局部几何结构;但全连接层由于缺乏空间先验,必须给每个位置分配不同的权重重新硬学一遍。
卷积神经网络(CNN)的核心选择恰好相反:显式把图像的空间几何结构做进网络架构里。一个神经元一开始只关注局部的一小块区域,而不是一次性看完整张图。
这种在模型结构中预先注入的数据偏好,就是归纳偏置(inductive bias)。选择一种网络架构,就是在明确告诉模型:空间上相邻位置的关联,远比远距离位置更值得优先处理。
为什么图像必须先看局部?#
自然图像里的信息不是均匀散落的。边缘、纹理、角点、色块平滑过渡,这些最基础的视觉基元,主要都依赖相邻像素之间的差异与配合。离得八丈远的两个像素点,在低层次特征上几乎没有任何直接因果关联。
CNN 通过局部连接(local connectivity)把这个先验固化在网络中:每个输出单元只和输入的一个局部小窗口相连。
平时写网络时习惯了 3×3 卷积核,但这里容易有一个误区:3×3 本身不是什么精妙的数学魔法,真正起作用的是限制在局部邻域内建模关联。无论换成 5×5 还是 7×7,局部连接的核心假设没有变。之所以选小尺寸,主要是为了在获得同样感受野的前提下,用更少的参数堆出更深的非线性层。
左上角学会的特征,右下角为什么不用重学?#
假设网络已经学会了识别一种局部模式,比如一条垂直边缘。这条边缘出现在图像左上角,或者出现在右下角,在局部几何结构上没有任何区别。
如果图像上的每个空间位置都分配一套独立的卷积核权重,就意味着同样的竖线特征,在左上角学了一遍,挪到右下角又得从零再学一遍。
CNN 的解法是权重共享(weight sharing):同一个卷积核(kernel)在整张图的所有空间位置上滑动复用。
很多人复习时容易把权重共享单纯记成"为了减少参数量"。减少参数确实是直接收益,但我理解更深层的设计假设是:视觉特征在统计上具有平移同质性,同一种特征变换在不同空间位置上完全可以复用。 这并非碰巧节省了参数,而是设计者明确认定局部特征提取规则不随绝对坐标的改变而改变。
卷积核到底长什么样:[out_channels, in_channels, k, k]#
概念上经常把 kernel 和 filter 混着叫,落到具体实现上,它到底是什么张量?
对于标准的 2D 卷积,输入通常是四维张量(忽略 batch 维度就是 \(H \times W \times C_{in}\))。一个 filter 在空间上只看一个很小的窗口 \(K_h \times K_w\),但在通道维度上,它必须一次性吃下输入的全部通道 \(C_{in}\)。
在 PyTorch 里写一行:
1nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3)这一层的权重形状是:
1[64, 3, 3, 3]也就是 [out_channels, in_channels, kernel_h, kernel_w]:
- 共有 64 个 filter;
- 每个 filter 本身是一个三维体积块(
3 × 3 × 3),在当前滑窗位置将所有输入通道的数据做内积求和,产出单个标量; - 一个 filter 扫完整张图,产出一张二维特征图(feature map);
- 64 个 filter 各自独立滑动,就堆叠出了 64 个输出通道。
这个参数形状以前天天用,但时间一长容易忽视一个基本事实:卷积核里面的权重不是硬编码的固定算子,而是一组纯粹的可学习参数。 它们和全连接层的权重一样,参与端到端的梯度反向传播,由优化器根据任务 loss 自动更新。
初学时容易拿边缘检测算子(如 Sobel)来辅助建立直觉,但卷积核绝不是固定模板。CNN 的突破,正是把特征提取规则的设计权交给了反向传播。
初始权重为什么不能随便给?#
既然卷积核是一堆可学习参数,训练从随机初始化开始,那这个"随机"能不能随便从标准正态分布里采几个数?
这也是我重温课件时觉得有意思的细节。初始化要同时解决两个矛盾:既要打破对称性,又要维持前向与反向传播的信号尺度。
如果所有卷积核权重都初始化成 0(或者完全相同的值),它们在前向传播时对同样的输入产出完全一致的激活值;反向传播时每个 filter 拿到的梯度也完全一致;更新后所有 filter 依然丝毫不差,多个通道永远分化不开。
光打破对称性还不够。方差偏大,信号逐层指数放大溢出;方差偏小,信号经过几十层之后衰减归零。
何恺明提出的 Kaiming 初始化(He initialization)根据每一层的输入连接数(fan-in)来动态缩放权重方差:
$$ W \sim \mathcal{N}\!\left(0,\;\frac{2}{fan_{in}}\right) $$对于卷积层,一个输出点实际看到的输入数量不仅包含通道数,还要乘上卷积核的空间面积:
$$ fan_{in} = C_{in} \times K_h \times K_w $$以 in_channels = 64、kernel_size = 3 为例,fan_in = 64 × 3 × 3 = 576。分子的 2 则来自于 ReLU 激活函数将大约一半的负信号截断为 0,为了维持方差守恒必须做 2 倍补偿。
现在的深度学习框架都封装好了默认初始化,日常写模型极少需要手算。但我一直保留的心智模型是:初始化不是随意随机,而是在打破对称性的同时,利用连接数精确控制方差,确保深层网络的信号既不爆炸也不消失。
平移等变(Equivariance)还是平移不变(Invariance)?#
以前很多教程和面试题里经常粗暴地总结:“CNN 具有平移不变性。”
重新看推导会发现这个表述不够严谨。卷积操作本身具有的是平移等变性(Translation Equivariance),而不是平移不变性(Translation Invariance)。
等变性的数学形式是:先平移再卷积,等价于先卷积再平移。
$$ \text{Conv}(T(x)) = T(\text{Conv}(x)) $$如果输入图像里的目标从左侧移到了右侧,卷积计算出的响应特征图(feature map)上,代表该目标的高响应区域也会同步向右平移。卷积本身保留了目标的位置变动,而不是抹平位置。
真正的不变性(Invariance)是指:不管目标在画面左边还是右边,整个系统最终给出的分类概率都稳定输出该类别。这种不变性主要不是单层卷积提供的,而是通过后续的池化(Pooling)、全局平均池化(Global Average Pooling)逐步汇总降维,把空间分布压缩掉之后才形成的。
3x3 卷积是怎么看到整张图的?#
如果每一层卷积都只老老实实看 3×3 的微小区域,深层网络凭什么能理解整张大图上的全局语义?
答案就在于感受野(Receptive Field)随网络深度的逐层累加。
以 stride=1、padding=0 的 3×3 卷积为例:
- 第 1 层输出的一个点,覆盖输入的
3×3像素; - 第 2 层输出的一个点,覆盖第 1 层的
3×3区域,对应到原始输入就是5×5; - 第 3 层输出的一个点,对应到原始输入就是
7×7。
每叠加一层 3×3 卷积,理论感受野就向外扩展 2。
这也是 VGG 最经典的洞见:用两个堆叠的 3×3 卷积替代一个 5×5 卷积,或者用三个 3×3 替代一个 7×7。两者覆盖的有效视野完全一样,但堆叠小卷积能引入更多次非线性激活(ReLU),而且参数量显著更少(三个 3x3 是 \(3 \times 3^2 = 27\),单个 7x7 是 \(7^2 = 49\))。
只要网络足够深,局部的滑动计算就能自然长成覆盖全图的全局视野。
3x3 卷积 vs 1x1 卷积:空间混合与通道混合#
CNN 中间层的输出通常是一个形状为 \(H \times W \times C\) 的三维特征张量。从表征学习的角度看,更自然的心智模型是:在空间二维网格的每一个坐标 \((i, j)\) 上,都存在一个 \(C\) 维的稠密特征向量 \(h_{ij} \in \mathbb{R}^C\)。
基于这个视角,卷积操作就可以清晰地拆解成两个正交维度的计算:
- 空间混合(Spatial Mixing):信息在不同的空间坐标之间流动传递;
- 通道混合(Channel Mixing):信息在同一个位置的不同通道之间做线性投影与重组。
标准 3×3 卷积是一次性把空间邻近信息与通道特征深度搅合在一起的运算。
而很多人刚接触 1×1 卷积时都会纳闷:窗口大小只有 1,根本不看周围像素,它到底在"卷"什么?
从向量视角看就非常直白了:1×1 卷积在空间上寸步不动,它只是在每个位置 \((i, j)\) 上,把当前对应的 \(C_{in}\) 维向量通过一个矩阵相乘,线性投影到 \(C_{out}\) 维:
并且整张图的所有空间位置,共享这同一个线性变换矩阵。
1x1 卷积完全不做空间混合,它是纯粹的通道混合(Channel Mixing)。 它的作用就是无缝升维、降维(如 ResNet bottleneck)以及跨通道特征重组。
把空间混合和通道混合解耦开的思路,后来直接孕育了 MobileNet 的深度可分离卷积(Depthwise Separable Convolution):先用单通道卷积各卷各的空间,再用 1×1 卷积整合跨通道信息,把计算量大幅砍掉一个数量级。
剥掉具体实现后,真正留下的是什么?#
回看当初学 CNN 时整理的满篇笔记,大部分内容都在死磕卷积核尺寸、步长、填充规则、池化层参数,以及早期那些经典的 AlexNet 或 VGG、ResNet 各自堆了多少层。
时隔多年再看,这些具体数字和固定模板早就随着模型迭代而褪色了。真正穿透时间、至今依然在指导视觉架构设计的,是当初为了处理图像而量身打磨的几条核心归纳偏置(inductive bias):
- 局部连接(Locality):尊重图像数据的空间相关性,特征提取优先在局部邻域展开;
- 权重共享(Weight Sharing):基于视觉规律的平移同质性,一套特征提取规则全图复用;
- 平移等变(Translation Equivariance):目标位置变动,特征响应同步平移,保留空间拓扑;
- 层级组合(Hierarchical Composition):用深度和感受野的逐层累加,把低阶线条拼成高阶语义;
- 解耦变换(Decoupled Mixing):空间混合负责拓扑聚合,通道混合负责表征投影。
任何一种成功的深度学习网络结构,都不是凭空堆砌矩阵乘法,而是把物理数据的客观规律,以数学约束的形式硬编码进架构中。
离开 CV 几年、转做 ML Infra 后,日常大部分时间都在跟显存碎片、通信拓扑、算子融合与并行策略较劲。偶尔抽空跳出算子性能的微观视角,回过头重温经典模型最质朴的结构设计,依然会觉得这种数据物理规律与网络先验之间的严整映射非常迷人。
参考资料#
- MIT 6.S191 Lecture 3: Convolutional Neural Networks https://introtodeeplearning.com
- He, K. et al., “Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification,” ICCV 2015
- Glorot, X. and Bengio, Y., “Understanding the difficulty of training deep feedforward neural networks,” AISTATS 2010