我的知识记录

digSelf

深度解析卷积神经网络(CNN):从像素到模型的完整指南

2025-06-15
深度解析卷积神经网络(CNN):从像素到模型的完整指南

本文是一篇关于卷积神经网络(CNN)的综合性技术教程。文章从传统神经网络处理图像的局限性出发,系统性地阐述了CNN的设计哲学——​归纳偏置​。通过将卷积操作类比为​滑动内积​,深入剖析了其模式匹配的数学本质。文章逐层递进,详细介绍了从一维到多维、从单通道到多通道的卷积过程,以及填充、步长、池化、激活函数等核心组件的作用。最后,文章揭示了CNN的学习引擎——​自动微分与反向传播​,并解释了为何反向传播是训练深度模型的标准选择。本文旨在为读者提供一个从零开始、全面且深刻的CNN知识体系。

图像的数字表示

本节介绍图像在计算机中是如何存储的,并将其拓展到在深度学习中图像的一般表示方式。

像素:图像的基本构成

在计算机中,图像是由一组像素(Pixel)所组成的,像素是组成数字图像的最小单位。每张图像都有以下三种属性:

  • 大小:图像的大小由其高度和宽度所描述,单位可以为厘米、英寸或者像素等。
  • 色彩空间:可以采用不同的色彩空间来描述颜色。常用的有RGB色彩空间,通过其基色的线性组合可生成各种颜色。
  • 通道:这是色彩空间的一项属性,可以理解为颜色线性空间中的基底,基底的个数即为通道数。例如RGB色彩空间具有三种基本颜色(即基底)RGB,即红色、绿色和蓝色。通过这三种颜色的线性组合,可生成各种各样的颜色,下图即为RGB色彩空间的示意图:

RGB

为了能够显示各种颜色,就需要像素能够存储彩色信息。对于RGB色彩空间,常见的是各通道均采用一个字节(即8bit)来存储像素的颜色值,共24bit来存储像素的所有RGB色彩值。由于每个通道(即颜色基)只用8bit来存储,共可存储2^8 = 256个状态。例如,对于红色(R通道),0表示在给定像素中无红色,255表示在给定像素中强度最高(或可理解为红色占比为100%),对于其他通道同理。

上述内容是对图像在计算机中的表示的基本介绍,更加详细的请查阅参考资料:What is a Pixel? - GeeksforGeeks

张量:深度学习中的图像语言

在深度学习中,图像一般表示为一个NumPy数组,维度一般是3维,包括图片大小和通道数量等属性。此外,如果是批量处理的话,会引入一个图像数量的维度。这时的NumPy数组一般称为张量(Tensors)。例如:

# [sample_number, height, width, channel_number]
[3, 28, 28, 3]

表示的就是3张图片所构成的列表,每张图片的宽和高均为28像素,采用的是3通道的色彩空间。该通道数量排在最后的表示方式称为Channel-last约定。需要注意的是:不是所有的表示方式都会采用Channel-Last约定。因此,在调用别人的数据集时,务必要确定其数据编码格式后再进行下一步操作。

卷积神经网络(CNN)的核心思想

现给定图像中物体识别和分类的任务,即从图像识别出目标物体,例如从图像中识别出宠物狗。如何构建一个神经网络来完成上述任务?传统的前馈神经网络(Feed-forward Neural Networks,FNNs)虽然可以完成上述任务,但是会存在有以下问题:

  • FNNs是全连接神经网络(Fully-connected Neural Networks, FCNNs),若采用FNN完成图像物体识别任务,其对图像中目标物体位置敏感。为正确识别目标物体,需要各个位置均有目标物体的图像作为数据集,进而对于数据量的需求较高。若数据量较小,很容易过拟合(Overfitting)
  • 对于图像规模较大时的FNN参数量巨大,学习时所需要的算法时间复杂度和空间复杂度会变的不可承受。

基于此,需要设计一个新的网络架构,来高效的完成图像物体识别和分类任务,这一个新的神经网络被称为卷积神经网络(Convolutional Neural Networks, CNNs)。

归纳偏置:CNN的“设计哲学”

为高效识别目标物体和降低学习和存储成本,可利用先验知识来指导设计网络结构,这些假设被称为归纳偏置(Inductive Bias)。归纳偏置指的是存在一组假设,以指导神经网络结构的设计,使其学习倾向于符合假设的解决方案。归纳偏置可类比为优化问题中的约束条件,以保证优化问题的解决方案从可行域中选择。

对图像中的目标物体进行观察与统计,可归纳出以下两点:

  • 目标物体可以出现在图像中的任意位置;
  • 由若干局部像素点聚集在一起形成目标物体图像,即邻近的像素点是相关的(称为局部性

从上述两个先验结论中,可归纳出:

  1. 设计的网络结构对于输入(即图像)数据应位置不敏感,即无论输入如何平移变化,均不影响目标物体的识别结果;
  2. 可在任意位置,对重复出现的局部像素点模式进行提取,且提取出的特征应该保持不变,即输入的数据平移,则对应的特征也进行同样的平移操作。

归纳出的这两个要点分别被称为平移不变性(Translation Invariance)平移等变性(Translation Equivalence),其中平移不变性为希望最终设计的神经网络涌现出来的特性。

为了实现平移不变性,做出以下两点基本假设:

  • 局部性
  • 平移等变性

在有了上述两点归纳偏置后,需在设计网络架构时,尽可能保证学习到的模型满足归纳偏置。

卷积:CNN的基石

从一维卷积理解核心原理

为了简化问题却又不失一般性,现假设输入信号/图像为一维有限数组,该数组可采用向量的形式表示,记作\pmb x = [x_1, x_2, \cdots, x_N]^\top。现需要“特征提取器”,从输入向量中提取特定像素聚集的模式,记特征提取器为\pmb u,其长度在定义网络结构时预先指定,需满足不等式1 \le \dim(\pmb u) \le N

由于目标物体可能出现在图像中的任意位置,则特定模式也可能出现在图像中的任意位置。因此,为保证不重不漏,特征提取器需“扫描”输入信号的所有位置。\pmb u扫描输入向量\pmb x的过程可被视为模式匹配的过程,其每一次匹配的计算结果的全体所构成的数组(或矩阵)称为特征图

在当前的问题设定中(即输入信号和特征提取器均为1维的情况),模式匹配其实就是在做相似度检测。根据线性代数中的知识可知,可采用内积运算(Inner Product)检测两个向量的相似程度。根据Cauchy-Schwartz不等式:

|\pmb u^\top \pmb x| \le \|\pmb x\|_2 \cdot \|\pmb u\|_2

可知,当输入信号\pmb x中的片段与\pmb u的相似程度越高,其匹配的计算结果(即响应程度)越大。由于模式匹配过程中需要扫过整个输入信号\pmb x,因此在一维的情况下,其做的是滑动内积操作。将特征图记作G,令M \doteq \dim(\pmb u),则滑动内积操作可被描述为下式:

G[t] = \sum_{i = 1}^M \pmb u_i \pmb x_{t + i - 1}

该式又被称为互相关/互协方差(Cross-Correlation)。由于是对每一个位置均进行模式匹配,因此当输入信号中发生了平移,提取到的特征图相应的也会发生同样的平移,这保证了平移等变性

二维卷积:在图像上滑动

现将问题拓展为输入信号是二维单通道情况,假设输入图片的宽和高均相等,则一张二维图片可被表示为矩阵F \in \mathbb{R}^{n\times n}。与此同时,特征提取器也由一维向量扩展为二维向量H \in \mathbb{R}^{m \times m},其维度需满足不等式1 \le m \le n

在物体识别任务中,图像被表示为了矩阵的形式,因此该矩阵也被称为数据矩阵。 为检测输入信号中是否包含特定模式,需要将一维的向量内积拓展为矩阵内积的形式,使其能够具有向量内积的性质,而这个矩阵内积操作就是Frobenius矩阵内积。对于两个同形状的矩阵A, B,其Frobenius矩阵内积定义为下式:

\left<A, B\right>_F = \operatorname{tr}(A^\top B) = \sum_{i}\sum_j A_{ij}B_{ij}

Frobenius内积也满足Cauchy-Schwartz不等式,即:

|\left<A, B\right>_F| \le \|A\|_F \cdot \|B\|_F

因此,当输入信号与特征提取器的模式匹配程度越高时,该区域的响应值(即Frobenius内积)的值则越大。同理,特征提取器需“扫描”输入信号的所有位置,因此计算的是滑动Frobenius矩阵内积。假设位置[i, j]表示的是矩阵第i行第j列的索引,则滑动Frobenius矩阵内积可表示为下式:

G[i, j] = \sum_{u}\sum_v F[i + u][j + v] \cdot H[u][v]

即互相关在二维情况下的计算公式。此外,为增强模型的表达能力,会增加一个偏置项b,该偏置项的作用是给特征图提供一个整体的偏移。需注意的是对于一个输出特征图,所有的位置共享同一个偏置项 b。此时,互相关特征图的计算公式可拓展为下式:

G[i, j] = \sum_{u}\sum_v F[i + u][j + v] \cdot H[u][v] + b

需注意的是,互相关运算不满足交换律,即不对称。为了满足对称性,引入卷积运算_convolution1。对于离散变量,卷积运算的定义如下:

\begin{align*} G[i][j] &= (F * H)[i][j] \\ &= \sum_u \sum_v F[i - u][j - v] \cdot H[u][v] \\ &= \sum_u \sum_v F[u][v] H[i - u][j - v] \end{align*}

其中特征模式矩阵H被称为卷积核(Convolutional Kernel)。同理,可以增加偏置项b增加模型的表达能力,进而卷积运算可以拓展为下式:

\begin{align*} G[i][j] &= (F * H)[i][j] \\ &= \sum_u \sum_v F[i - u][j - v] \cdot H[u][v] + b \\ &= \sum_u \sum_v F[u][v] H[i - u][j - v] + b \end{align*}

卷积与互相关的计算过程唯一区别是,卷积运算先将卷积核H进行翻转翻180度后(即二维的卷积先将卷积核进行对角的翻转),再滑动顺序计算。此外,由于卷积运算的索引计算采用的是减法的形式,因此可能会存在负数索引的情况,其并不是代表访问索引为负值的物理内存,而是代表的相对位置。假设卷积核的中心元素所在位置被定义为原点(即[0, 0]),则[-1, 0]位于该元素的正上方,[-1, -1]位于该元素的左上角。

出现负数索引的情况的原因是当卷积核H的原点位于数据矩阵F的左上角位置。此时,就会出现负数索引。这种情况反应的是下述问题:当卷积核的中心对准输入数据的边缘或角落时,那些超出输入边界的卷积核部分应该和什么值进行计算?

为了解决该问题,引入了填充(Padding)的概念,例如使用0元素填充的方式,将输入数据矩阵F的边缘填充为0,这样可保证卷积核超出输入数据矩阵F的边界部分不影响计算结果。此外,填充技术还可以调整特征图的大小。假设每次滑动一个元素,为防止卷积核超出输入边界需在输入数据矩阵F的四周分别填充了p行和p0元素,则获得的特征图的大小为:

(n + 2p - m + 1) \times (n + 2p - m + 1)

如果计算结果不是整数,通常会进行向下取整。此外,在实践中,人们通常会用两个术语来概括填充策略:

  • VALID 填充: 指的是不进行任何填充 (p=0)。在这种模式下,卷积核只能在输入图像的“有效”区域内移动,输出的特征图尺寸会小于输入尺寸。
  • SAME 填充: 指的是通过计算,自动在输入周围填充适量的0,使得当步长 s=1 时,输出特征图的尺寸与输入特征图的尺寸保持相同。这在设计深层网络时非常方便,可以避免特征图尺寸在传递过程中迅速缩小。

与此同时,当卷积核的大小m \ll n时,特征图的数量可能多大,因此引入滑动步长(Strides),以减少特征图的数量。令步长为s\ge 1(s \in \mathbb{Z}),则此时每一个特征图的大小计算公式为下式:

(\left\lfloor \frac{(n + 2p - m)}{s} \right\rfloor + 1) \times (\left\lfloor \frac{(n + 2p - m)}{s} \right\rfloor + 1)

需要特别注意的是:在深度学习框架中,将多维度的互相关称为卷积运算。原因是深度学习中的卷积核中的参数的具体取值是通过学习得到的,由于梯度下降法和误差反向传播的作用,即使采用标准卷积运算,先180度反转卷积核再滑动计算的结果,与不反转直接采用互相关公式计算的最终结果是一致的。在这种情况下,在保证最终结果一致的前提下,省略了180度翻转卷积核的操作,计算更加简便。因此在深度学习框架中,采用互相关实现卷积运算。

多通道卷积:处理彩色图像

最后,对于输入信号为二维多通道(例如 C_{in} 个通道)的情况,其卷积过程更为复杂。此时,卷积核也必须具有相同的通道数 C_{in}。运算时,卷积核的每个通道与输入数据对应的通道分别进行二维互相关运算,然后将得到的 C_{in} 个结果逐元素相加,最终合并成一个单通道的输出特征图

池化层:降维与增强不变性

为进一步降低存储空间,以及进一步削弱位置敏感性,采用池化(Pooling),即下采样(Down Sampling)操作将特征图的维度降低,常用的池化有最大值池化和平均值池化等。池化操作的目的是忽略相对不重要的信号/信息,保留最重要的信号/信息。

具体而言,将特征图均分成若干子块,若采用的是最大池化,则对每一个子块取最大值填充到对应位置的池化后的特征图中;若采用的是平均值池化,则对每一个子块取均值后填充到池化后的特征图中。当池化前,平移后的最大信号所在位置未超过其原先位置所在的池化子块时,其并不会影响池化后的结果,在一定程度上去除了位置敏感。

需要注意的是,下采样出的值不是学习出来的

从零到一:构建完整的CNN架构

目前为止,通过卷积运算共享参数的特性,在解决了多通道模式匹配问题,降低了神经网络的要学习的参数数量。此外,通过池化操作,在降低了位置敏感性的同时进一步降低了参数的数量。但是最终的目标是识别物体任务,因此需要将其建模为多分类任务。为此,需将提取好的高维特征图展开(Flatten)成一个向量,并通过FNN获得最终的类别线性倾向性(即logits),通过softmax归一化后,得到最终的类别概率分布,具体的神经网络架构如下图所示_of_CNN2

CNN框架

需要注意的是:在池化层之前,需将卷积后的特征图通过激活函数,得到激活特征图。为缓解梯度消失(Gradient Vanishing)的问题,将激活函数从sigmoid函数替换为了ReLU(Rectified Linear Unit,线性整流单元)。

由于卷积运算本质上是一种线性运算,如果仅仅是线性操作的堆叠,那么整个网络无论多深,最终也只能表达线性关系。虽然池化操作在一定程度上引入了非线性,由于线性操作占了绝大多数,有限的池化操作所引入的非线性影响较小。为了让网络能够学习和表示更加复杂的非线性特征(这也是现实世界中绝大多数模式的本质),必须在卷积层之后引入非线性的激活函数(如ReLU)。

有很多优秀的参考资料直观的展示了CNN的工作过程和原理,例如:3D Visualization of a Convolutional Neural Network以及 CNN Explainer,读者若想直观的观察CNN的整体工作流程,可以查阅上述两个参考资料。

引擎揭秘:自动微分与反向传播

计算CNN的误差传播递推公式较为复杂,若对手动推导感兴趣可参考资料CNN BackPropagation - CMU。由于很多情况下,人工神经网络手动推导递推公式是十分复杂的,为解决这个问题,引入了自动微分(autograd)框架来自动计算节点的梯度。

自动微分的基础是求导的链式法则,应用链式法则可以将复杂的求导拆开为若干个容易子问题的解,通过减而治之的方式解决复杂的原始问题。假设\pmb x, \pmb w \in \mathbb{R}^n, y \in \mathbb R,求函数z = (\left<\pmb x, \pmb w\right> - y)^2计算\frac{\partial z}{\partial \pmb w}的导数。首先,可通过换元法,令:

\begin{align*} a &= \left< \pmb x, \pmb w\right> \\ b &= a - y \\ z &= b^2 \end{align*}

将上述求导问题分解为三个子部分,利用求导的链式法则,可得:

\frac{\partial z}{\partial \pmb w} = \frac{\partial z}{\partial b} \cdot \frac{\partial b}{\partial a} \cdot \frac{\partial a}{\partial \pmb w}

进而有:

\begin{align*} \frac{\partial z}{\partial \pmb w} &= 2b \cdot 1 \cdot \pmb x \\ &= 2(a - y) \pmb x \\ &= 2(\left< \pmb x, \pmb w \right> - y) \pmb x \end{align*}

上述过程可以将其表示为一个计算图,如下所示:

graph TD subgraph "输入 (Inputs)" x[<font size=5>x</font>] w[<font size=5>w</font>] y[<font size=5>y</font>] end subgraph "计算过程 (Operations)" A["<font size=5>a = &lt;x, w&gt;</font><br><font color=gray>内积 (Inner Product)</font>"]; B["<font size=5>b = a - y</font><br><font color=gray>相减 (Subtraction)</font>"]; Z["<font size=5>z = b²</font><br><font color=gray>平方 (Square)</font>"]; end %% 定义连接关系 x -- " " --> A; w -- " " --> A; A -- "a" --> B; y -- " " --> B; B -- "b" --> Z; %% 样式定义 style x fill:#E3F2FD,stroke:#333,stroke-width:2px style w fill:#E3F2FD,stroke:#333,stroke-width:2px style y fill:#E3F2FD,stroke:#333,stroke-width:2px style A fill:#FFF9C4,stroke:#333,stroke-width:2px style B fill:#FFF9C4,stroke:#333,stroke-width:2px style Z fill:#C8E6C9,stroke:#333,stroke-width:2px

这一个计算图本质上其实就是一个有向无环图(Directed Acyclic Graph,DAG),该图由节点和边构成,每个节点代表了一次计算,每条有向边代表了一个输入。构建计算图的流程是首先将数学公式先展开,分解为若干个操作子,即图中的a, b, z等;然后再将这些操作子按照计算顺序将计算表示成一个DAG。

有了上述计算图之后,即可自动计算导数。利用链式法则计算导数有两种方式,分别是正向累计反向传播。由于乘法的结合律不改变计算乘法的计算结果,因此两种方式得到的导数其实是一致的。该描述可以被描述为下式:

\begin{align*} \frac{\partial z}{\partial \pmb w} = \frac{\partial z}{\partial b} \cdot (\frac{\partial b}{\partial a} \cdot \frac{\partial a}{\partial \pmb w}), &&\text{正向累计} \\ \frac{\partial z}{\partial \pmb w} = (\frac{\partial z}{\partial b} \cdot \frac{\partial b}{\partial a}) \cdot \frac{\partial a}{\partial \pmb w}, &&\text{反向传播} \end{align*}

即:正向累计先计算最内层的导数,反向传播先计算最外层的导数。

自动微分的反向传播需要用到前向传播执行图的计算结果。因此,在计算反向传播之前,需先进行前向传播,存储中间计算结果;然后,从相反方向执行图,利用链式法则逐步计算对应计算节点的导数,此过程中可以忽略无需计算导数的分支(即剪枝操作)。自动微分的计算时间复杂度为O(N),空间复杂度也为O(N),其中N为操作子个数。

为什么深度学习几乎无一例外地使用反向传播,而不是正向累计?正向模式和反向模式的计算量本身是相似的,但它们在处理不同类型的计算任务时,效率天差地别。这取决于输入的数量输出的数量,具体对比如下表所示:

正向累计(Forward Mode) 反向传播(Reverse Mode)
核心 计算的是“一个输入变化”对“所有输出”的影响 计算的是“所有输入变化”对“一个输出”的影响
效率 一次正向传播,可以计算出最终输出对某一个输入参数的偏导数。如果要计算损失函数 L 对 N 个参数(w_1, w_2, ..., w_N)的全部偏导数,就需要执行 N 次正向传播。 一次前向传播 + 一次反向传播,就可以计算出某一个输出(通常是最终的标量损失 L)对所有输入参数(w_1, w_2, ..., w_N)的偏导数
适用场景 输入少、输出多时高效 输入多、输出少时极其高效

一个典型的神经网络,其参数(输入)数量极其庞大(可达数百万甚至数十亿),而最终的损失函数(输出)通常只有一个标量值。在这种“多输入,单输出”的场景下,反向传播的优势是压倒性的。它只需要一次完整的往返(forward + backward pass)就能得到损失对所有参数的梯度,而正向模式则需要进行数百万次传播。这正是反向传播成为训练神经网络事实标准的核心原因。

  • 0