↓ Skip to main content
  1. Posts/

9 年后重温 CNN:剥掉算子细节后,真正留下了什么

·4695 words·10 mins
Note: This article is available in Chinese only. 本文暂无英文版本。 View original

最近在重新过 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]
Conv2d Filter 的 Tensor Shape

也就是 [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\)。

基于这个视角,卷积操作就可以清晰地拆解成两个正交维度的计算:

  1. 空间混合(Spatial Mixing):信息在不同的空间坐标之间流动传递;
  2. 通道混合(Channel Mixing):信息在同一个位置的不同通道之间做线性投影与重组。

标准 3×3 卷积是一次性把空间邻近信息与通道特征深度搅合在一起的运算。

而很多人刚接触 1×1 卷积时都会纳闷:窗口大小只有 1,根本不看周围像素,它到底在"卷"什么?

从向量视角看就非常直白了:1×1 卷积在空间上寸步不动,它只是在每个位置 \((i, j)\) 上,把当前对应的 \(C_{in}\) 维向量通过一个矩阵相乘,线性投影到 \(C_{out}\) 维:

$$ \mathbb{R}^{C_{in}} \to \mathbb{R}^{C_{out}} $$

并且整张图的所有空间位置,共享这同一个线性变换矩阵。

1x1 卷积:纯通道混合运算

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

Related

一组 QKV 就够了吗:Multi-Head Attention 到底在拆什么

上一篇结尾我留了一个问题:一组 \(W_Q, W_K, W_V\) 只定义了一个 matching / routing space,如果不同类型的信息需求需要不同的 routing 策略,一个 space 够吗? 这个问题让我停了下来。因为到 single-head self-attention 为止,每个 token 已经可以访问整个 sequence 了——任意两个位置之间的通路都是通的。如果表达能力不够,加大 \(d_k\) 不就行了?为什么还要搞出 Multi-Head?

从动态读取到 QKV:Self-Attention 的公式是怎么长出来的

上一篇结尾留了三个问题:谁和我相关?相关程度是多少?我该从对方那里读什么? Self-attention 允许 sequence 内的位置之间直接建立通路,不再需要沿着 recurrent state 一步步搬运。但"允许直接交互"和"知道怎么交互"是两件事——architecture 把路打通了,到底是谁在决定走哪条路、读多少、读什么?