CNN in Deep Learning: From Pixels to Feature Maps


Preface by the author

最近在复习 ML 的时候又翻到了很久以前读过的一篇 CNN 入门文章。这类文章的好处是不会一上来就把人扔进大量公式里,而是先用图像、filter、feature map 这些直观概念把问题讲清楚。所以这篇笔记还是沿用原来那种比较散的学习笔记风格:先从我个人理解的角度解释 CNN 是什么,再补上一些必要的公式和 DL 里的训练逻辑。

我之前在 CUHK 选过 Prof. Hongsheng Li 的 _ML for SP Applications_,那门课对我后来的 research thesis 也很有帮助。不过我的背景更偏 IoT 和 embedded systems,数学功底不算特别扎实,所以这篇文章的目标不是写成教科书,而是把 CNN 里最容易卡住新人的地方尽量讲顺。

Disclaimer

这篇文章主要是个人学习总结,行文风格会比较松散。为了保证可读性,我不会把每个推导都写到最严格的形式,但会尽量保证核心概念不出错。如果你发现理解有偏差,欢迎通过 GitHub issue 交流。

Abstract

Convolutional Neural Network 听起来像一个很重的术语,但它的核心思想其实很朴素:图像里的有用信息通常不是孤立的单个像素,而是局部区域里反复出现的结构,比如边缘、角点、纹理、部件,最后再组合成物体类别。

CNN 的思想确实受到视觉神经科学的启发,例如 Hubel 和 Wiesel 关于感受野的研究;工程上可以追溯到 Fukushima 的 Neocognitron、LeCun 等人的 LeNet。真正让 CNN 在大众视野里爆发的是 AlexNet 在 2012 年 ImageNet 比赛中的表现。之后,VGG、Inception、ResNet 等结构不断改进网络深度、连接方式、非线性层和训练策略,使 CNN 成为深度学习中最经典的一类模型。

如果只想抓住 CNN 的主线,可以先记住三件事:

  1. CNN 把图像看成张量,而不是一张“图片文件”。
  2. 卷积层通过可学习的 filters 在局部窗口里抽取 feature maps。
  3. 多层卷积会把低级视觉特征逐步组合成高级语义表示,最后交给分类头输出预测结果。

1. From Image to Tensor

人类看图像的时候会直接说“这是一只猫”或者“这是一辆车”,但模型看到的是数字矩阵。对于一张 RGB 图片,假设分辨率是 $H \times W$,那么它通常会被表示成:

$$ \mathbf{x} \in \mathbb{R}^{H \times W \times C}, $$

其中 $C=3$ 对应 red、green、blue 三个通道。如果一次训练送入 $B$ 张图片,那么输入 batch 可以写成:

$$ \mathbf{X} \in \mathbb{R}^{B \times H \times W \times C}. $$

当然,不同框架对 channel 的位置有不同习惯。例如 TensorFlow 常见的是 NHWC,PyTorch 常见的是 NCHW。但本质上都是在说:图像是一组有空间结构的多通道数值。

这也是 CNN 和普通 fully connected network 的关键差别。假如直接把 $256 \times 256 \times 3$ 的图像 flatten 成一个长向量,模型会丢掉很多空间邻近关系;而 CNN 会保留“附近像素更可能共同构成局部结构”这个 inductive bias。

2. What Makes CNN Work

我觉得理解 CNN 时,最重要的是先抓住三个词:locality, parameter sharing, translation equivariance

Locality 指的是卷积层只看局部窗口。比如一个 $3 \times 3$ filter 每次只处理当前位置附近的小区域。图像里的边缘、纹理、角点往往就是局部结构,所以这很自然。

Parameter sharing 指的是同一个 filter 会在整张图上滑动。也就是说,如果某个 filter 学会检测“竖直边缘”,它不需要在左上角、右下角分别学习一套参数。这样既减少了参数量,也让模型更容易泛化。

Translation equivariance 的意思是:如果输入里的某个局部模式移动了位置,feature map 里对应的响应也会跟着移动。CNN 不会因为一个边缘从左边移动到右边就完全认不出来。

用一句话概括,CNN 的核心假设是:视觉特征具有局部性,并且同一种局部模式可以出现在图像的不同位置。

3. Convolution Layer

经过预处理的 data 传入 CNN 时,最先遇到的通常是 convolutional layer。卷积层不是一个固定的图像处理算子,而是包含一组可学习的 filters/kernels。这些 kernel 的数值会在训练中通过反向传播学出来,而不是由人手工指定。

如果输入是 $\mathbf{x}$,某个卷积核是 $\mathbf{W}$,输出 feature map 是 $\mathbf{y}$,那么一个简化的二维卷积可以写成:

$$ y_{i,j,k} = b_k + \sum_{u=0}^{F_h-1} \sum_{v=0}^{F_w-1} \sum_{c=1}^{C} W_{u,v,c,k}\, x_{i+u,j+v,c}. $$

这里 $k$ 表示第 $k$ 个 filter,也就是第 $k$ 个输出通道;$F_h$ 和 $F_w$ 是 kernel 的高度和宽度;$b_k$ 是 bias。

如果先忽略 dilation,卷积输出的空间尺寸大致由 kernel size、padding 和 stride 决定:

$$ H_{\text{out}} = \left\lfloor \frac{H + 2P_h - F_h}{S_h} \right\rfloor + 1, \quad W_{\text{out}} = \left\lfloor \frac{W + 2P_w - F_w}{S_w} \right\rfloor + 1. $$

其中 $P$ 是 padding,$S$ 是 stride。Padding 可以让边缘像素也被充分利用,也可以控制输出尺寸;stride 越大,下采样越明显。

一个经典的 CNN 结构

在下面这个例子里,input 是一个 $5 \times 5$ 的二维矩阵,我们使用一个 $3 \times 3$ filter 在上面滑动。每次滑动时,窗口内的数值和 filter 对应元素相乘再求和,得到 feature map 中的一个值。

Conv layer 的计算示例

我一开始理解 CNN 时,只是把卷积层看成线性代数里的矩阵运算。这个角度没有错,但还不够直观。更直观的理解是:一个 filter 就像一个小型 pattern detector。如果某个局部图像块和这个 filter 想检测的 pattern 很像,输出就大;如果不像,输出就小。

比如下面这个简化示例中,一个 $7 \times 7$ filter 可以被看成一条曲线。当它扫过一张图时,越像这条曲线的局部区域,响应越强。

可视化一个简单的 kernel

这就是 feature extraction 的基本逻辑。早期层的 filters 可能学到边缘、颜色对比和简单纹理;更深层的 filters 会在前面 feature maps 的基础上检测更复杂的形状、部件甚至语义模式。

4. Nonlinearity: Why ReLU Is Needed

如果 CNN 只有卷积层,那么每一层本质上仍然是线性变换。很多线性层堆在一起,最后仍然等价于一个线性变换,表达能力会很有限。所以在卷积之后,我们通常会接一个 nonlinear activation。

最常见的是 ReLU:

$$ \operatorname{ReLU}(z)=\max(0,z). $$

它的作用很直接:小于 0 的响应被压成 0,大于 0 的响应保留。直观上,这像是在说“这个 feature 是否被激活”。ReLU 计算便宜,也能缓解 sigmoid/tanh 在深层网络中容易出现的梯度消失问题。

所以一个常见的局部结构是:

$$ \text{Conv} \rightarrow \text{ReLU} \rightarrow \text{Pooling}. $$

当然,现代 CNN 还会加入 BatchNorm、Dropout、Residual connection 等结构,但对入门理解来说,上面这条链已经足够重要。

5. Pooling and Downsampling

Pooling layer 的作用通常是降低 feature map 的空间尺寸,让模型对小范围位移更不敏感,同时减少后续计算量。最常见的是 max pooling 和 average pooling。

Max pooling 可以写成:

$$ y_{i,j,c} = \max_{(u,v)\in \Omega_{i,j}} x_{u,v,c}, $$

其中 $\Omega_{i,j}$ 是 pooling window 覆盖的局部区域。它保留局部区域里最强的响应,比较像“只要这个 feature 在附近出现过,就把它记下来”。

Average pooling 则是求平均,更像是在做局部平滑:

$$ y_{i,j,c} = \frac{1}{|\Omega_{i,j}|} \sum_{(u,v)\in \Omega_{i,j}} x_{u,v,c}. $$

在很多现代网络里,最后也会用 global average pooling,把每个 channel 的整张 feature map 平均成一个数,从而避免巨大的 flatten 向量。

6. Receptive Field

CNN 里还有一个很重要但容易被忽略的概念:receptive field。它指的是某一层某个神经元在原始输入图像中能“看到”的区域。

第一层的 $3 \times 3$ conv 只能看到 $3 \times 3$ 的像素块;第二层的一个神经元虽然仍然可能是 $3 \times 3$ conv,但它看到的是第一层 feature map 上的 $3 \times 3$ 区域,而第一层每个点又对应原图的一小片区域。所以网络越深,后面的神经元通常拥有越大的 effective receptive field。

这也解释了为什么 CNN 能从 low-level feature 逐步走向 high-level feature:

  1. 浅层:边缘、角点、颜色对比。
  2. 中层:纹理、简单形状、局部部件。
  3. 深层:物体部件、语义组合、类别相关特征。

7. Fully Connected Layer and Softmax

卷积层和 pooling 层主要负责 feature extraction。到网络末尾,我们需要把这些 feature 映射成具体任务的输出。对于图像分类任务,这通常意味着输出一个长度为 $K$ 的 logits 向量:

$$ \mathbf{o} = [o_1,o_2,\dots,o_K]. $$

Softmax 会把 logits 转换成概率分布:

$$ p_k = \frac{\exp(o_k)} {\sum_{j=1}^{K}\exp(o_j)}. $$

如果标签是 one-hot vector $\mathbf{y}$,分类任务常用 cross-entropy loss:

$$ \mathcal{L} = - \sum_{k=1}^{K} y_k \log p_k. $$

如果真实类别是 $t$,那么上式就变成:

$$ \mathcal{L}=-\log p_t. $$

也就是说,模型给正确类别的概率越高,loss 越小。训练时,反向传播会根据 loss 对所有参数求梯度,包括卷积核、bias、BatchNorm 参数和 classifier head 中的权重。

举个例子,假设 CIFAR-10 有 10 个类别,softmax 输出为:

$$ [0.00, 0.10, 0.10, 0.75, 0.00, 0.00, 0.00, 0.00, 0.00, 0.05]. $$

这表示模型认为第 4 个类别的概率最高。注意这里的“第 4 个类别”不是某一个神经元机械地检测“dog face”或者“wheel”,而是前面很多 distributed features 共同作用后的分类结果。

8. How CNN Learns

从训练角度看,CNN 做的事情可以概括为:

  1. Forward pass:输入图像经过 conv、activation、pooling、classifier,得到预测概率。
  2. Loss calculation:用 cross-entropy 等 loss 衡量预测和真实标签的差距。
  3. Backpropagation:计算 loss 对每个参数的梯度。
  4. Optimizer step:用 SGD、Adam 等优化器更新参数。

如果用最简单的 SGD 表示,参数更新可以写成:

$$ \theta_{t+1} = \theta_t - \eta \nabla_{\theta} \mathcal{L}(\theta_t), $$

其中 $\eta$ 是 learning rate。对 CNN 来说,$\theta$ 包括所有可学习的 kernel weights、biases 和后续分类层参数。

所以 CNN 不是“人为设计好一堆边缘检测器”,而是在数据和 loss 的驱动下自己学出合适的 filters。人类提供的是结构上的先验:局部连接、权重共享、层级组合。

9. A Tiny CNN Pipeline

为了把上面的内容串起来,可以想象一个最简单的 image classifier:

  1. Input image
  2. Conv(3x3, 32 filters)
  3. ReLU
  4. MaxPool(2x2)
  5. Conv(3x3, 64 filters)
  6. ReLU
  7. MaxPool(2x2)
  8. Global Average Pooling
  9. Linear layer
  10. Softmax
  11. Class probabilities

这个结构虽然很小,但已经包含了 CNN 的主要思想:

  • Conv 负责提取局部模式。
  • ReLU 提供非线性表达能力。
  • Pooling 降低空间尺寸并扩大后续感受野。
  • Global average pooling 或 flatten 把空间 feature 变成向量。
  • Linear + softmax 输出每个类别的概率。

10. Common Misunderstandings

误解 1:Convolution layer 一定会压缩图像。
不一定。是否压缩取决于 stride、padding、kernel size 和是否接 pooling。使用 same padding 且 stride = 1 时,输出空间尺寸可以和输入一样。

误解 2:Filter 越多,feature map 一定越小。
Filter 数量决定输出 channel 数,不直接决定 height 和 width。空间尺寸主要由 stride、padding、pooling 等控制。

误解 3:Fully connected layer 是 CNN 最重要的部分。
对于图像任务来说,真正体现 CNN 特点的是前面的卷积特征提取。分类头很重要,但它主要负责把 learned representation 映射到 label space。

误解 4:CNN 已经过时。
Transformer 在视觉任务中非常强,但 CNN 的归纳偏置仍然有价值。很多工业场景、嵌入式视觉任务、轻量模型和 hybrid architecture 里,CNN 仍然是非常实用的选择。

Conclusion

CNN 的强大之处,不是因为它神秘,而是因为它把图像任务中的几个合理假设变成了可训练结构:局部区域有意义,同一类 pattern 可以出现在不同位置,低级特征可以逐步组合成高级语义。

从公式上看,卷积层只是局部加权求和;从系统角度看,CNN 是一种把 image tensor 转换成 semantic representation 的层级特征学习机器。理解这一点后,再去看 LeNet、AlexNet、VGG、ResNet 或轻量 CNN,就会更容易抓住它们到底是在改进什么。


Author: Mingjing Xu
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Mingjing Xu !
 Previous
Personal thoughts of the academic Personal thoughts of the academic
Personal academic notes on reading papers efficiently, understanding paper structure, building research taste, using Zotero, writing with LaTeX, and preparing arXiv submissions.
2022-11-08
Next 
Git & GitHub Git & GitHub
A practical Git and GitHub command notebook covering clone, init, add, commit, log, checkout, reset, branch, merge, push, pull, and common remote-push troubleshooting.
2022-09-29
  TOC