动机#
卷积神经网络源于对生物视觉系统的模拟,即不同的视细胞能够看到的视野不一样,通过叠加视野来形成最终的视觉图像。相较于 MLP 处理图像的高参数量、空间信息损失来说,卷积神经网络在保留空间架构的同时,实现了效率和准确度的协同提升。
CNN 中归纳偏置主要体现在以下方面:
- 平移不变形 - 物体特征(局部区域)的识别不会因为位置的改变而发生变化;
- 空间上的权重共享 - 不同位置使用的是同一个卷积核,平等对待每一块局部区域;
- 局部连接 - 局部区域已经足够进行物体识别,因此后一层神经元仅于前一层的特定区域的神经元相连。
卷积层#
卷积的数学本质是空间位置滑动的内积。
2D 卷积 / 互相关#
- 表示从原始图像上提取的局部区域;
- 表示卷积核,维度与输入图像一致;
- 表示 输出特征维度 / 卷积核个数。
输入特征维度为 、卷积核维度为 。则输出特征维度为 。参数量为 。
通过公式可以看出 卷积神经网络在空间维度上是局部连接的,但是在通道维度上是全连接的。
感受野#
感受野(Receptive field) 指的是输出特征上的一个元素在输入特征上映射的区域大小。随着层数的叠加,感受野也会逐渐变大,直到看完整个输入图像(深层特征中的单个元素会对应原始图像中更大的区域)。
填充和步长#
- 填充(Padding) - 对输入特征采用 0 填充、控制输出特征空间尺寸;
- 步长(Stride) - 调节特征的下采样速度 / 特征提取精度。
如果加上步长和卷积,此时输出特征空间维度为 。
FLOPs 计算过程#
-
标准卷积层计算公式:
-
全连接层计算公式:
和 表示输入特征维度和输出特征维度, 和 表示输出特征的空间维度。最后将每层的结果相加就得到最终的 FLOPs。
CAM 可视化技术#
Class Activation Mapping 通过生成热力图,来表明输入图像中与预测类别最相关的区域。最后一层卷积层 + 全局平均池化层 + MLP 层保证了 每个通道的特征映射对最终分类的结果都有直接贡献。
具体步骤为:
- 对于输入的一张图像,经过卷积层和全局平均池化层之后,得到每个通道的特征向量;
- 全连接层的权重 表示每个通道对各个类别的贡献程度。通过将这些权重与对应的特征向量加权求和,可以得到一个与输入图像尺寸相同的二维激活图;
- 将这个激活图进行上采样和归一化,叠加到原始图像上,形成热力图,显示出模型关注的区域。
还有一种不依赖于特定网络结构、基于梯度信息的 Grad-CAM 改进方案。
池化层#
最大 / 平均池化#
池化(Pooling) 操作也是基于窗口扫描,与卷积操作不同的地方在于它是取窗口内的最大值 / 平均值。
- 池化层一般会减小特征的空间维度,但不会减少通道维度;
- 池化层的使用能够减少参数量,避免过拟合的风险。但会丢失空间信息;
- 由于池化层对空间信息的过渡压缩,后续基于 CNN 的模型都很少使用该技术了。
空间金字塔池化#
空间金字塔池化(Spatial Pyramid Pooling) 指对输入特征在不同尺度进行池化操作,然后将这些不同尺度的特征 拼接 在一起,以捕获多尺度的信息。避免 单一尺度导致的信息损失。
这里需要注意一点的是经过池化操作得到的特征向量是在 通道维度 进行拼接。
全局平均池化#
全局平均池化(Global Average Pooling) 将每个特征的 空间维度 压缩为单一的数值 / 平均化,然后将这些数值连接起来。这样就将 高维的特征转换成了低维的特征向量。维度变化为 。
全局平均池化有如下优点 / 缺点:
- 大幅减少参数数量、在不依赖位置空间的前提下捕捉全局特征;
- 通过平均压缩了空间信息,不适用于利用空间位置的分割、检测等任务。
分层表示学习#
在 CNN 中,我们通过堆叠多个卷积层和池化层来逐渐提取图像的高级特征,实现 层次化的特征学习 。浅层提取到的是图像的低级特征,如边缘、纹理等,而深层提取到的是图像的高级特征,如物体的形状、结构等。
卷积种类#
1x1 卷积#
1x1 卷积指卷积核大小为 1。与传统卷积不同的是,它仅在通道维度上进行操作,不改变特征的空间尺寸。且能显著降低参数量,并且通过跨通道融合信息、引入更多的非线形性;
瓶颈层(Bottleneck Layer) - 设计,即降维 -> 提取特征 -> 恢复维度 / 升维。
组卷积#
组卷积(Group Convolution) 最先是由 AlexNet 提出的,主要思想是将输入特征沿 通道维度 分成多个组,然后对每个组执行标准卷积操作,最后将结果合并。通过组卷积,网络可以处理具有更多通道数的特征,这为小型网络提供更多特征提取的机会。
转置卷积 / 反卷积#
转置卷积 / 反卷积(Transpose Convolution) 主要用于 上采样(Upsampling) 操作,即将特征的空间尺寸扩大。对于需要恢复图像细节或扩大特征的尺寸的任务来说非常重要 / U-Net。
它的操作步骤可以表示为:
- 插值步骤 - 在输入特征的元素之间通过网络自主学习如何填充 0,扩大特征尺寸;
- 卷积步骤 - 在插值的结果上运用标准卷积操作。
空洞卷积#
空洞卷积(Dilated Convolution) 通过引入 Dilation Rate 使得 同样大小的卷积核能够获得更大的感受野,避免下采样带来的空间信息损失。当扩张率为 1 时,退化为标准卷积;当扩张率为 4 时,表示 在卷积核中间 插入三个 0。
深度可分离卷积#
深度可分离卷积(Depthwise Separable Convolution) 是一种高效的卷积操作,他是分组卷积的一种特殊形式(分组数等于通道数 / 每个组只有一个特征)。它通过 分解标准卷积 操作大幅减少参数量,由两个部分组成:
- 深度卷积(Depthwise convolution) - 使用多组不同的卷积核对输入特征进行标准卷积操作;
- 逐点卷积(Pointwise convolution) - 使用 卷积对深度卷积的输出特征的每个位置进行卷积。
标准卷积计算量:
深度可分离卷积计算量:
如何理解这个公式呢?深度卷积的通道数为 1,所以上方公式左侧 ;逐点卷积卷积核大小为 1,所以上方公式右侧 。
可变形卷积#
可变形卷积(Deformable Convolution) 是指在卷积核所处理的每一个输入位置上增加了一个方向向量,等价于卷积核变为任意形状,从而在训练过程中扩大感受野,适应输入图像的未知变化。
形式化的表示为在位置信息上加入了可学习偏移项:
而输入特征 则是通过双线形插值得到。
实用训练技巧#
批归一化 / 组归一化#
批归一化通过减去均值除以方差引入先验 / 逐通道,假设 各个通道表示的特征一样重要。又因为过度归一化而引入两个可学习参数,但会导致数据分布发生细微偏移。形式化表示为:
其中 表示第 层的净输入、 表示缩放参数、 表示偏移参数。
训练阶段
- 使用当前小批次 的均值和方差进行归一化,通道的每一维都有独立的 和 ;
- 计算并更新每一层的 移动平均(EMA) 均值和方差,以便在推理的时候使用。
推理阶段
- 使用在训练阶段得到的移动平均进行归一化。
移动平均
我们直接看 动手学深度学习 / 批量规范化 ↗ 的代码:
import torchfrom torch import nnfrom d2l import torch as d2l
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum): if not torch.is_grad_enabled(): # 预测模式下,直接使用传入的移动平均所得的均值和方差 X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps) else: mean = X.mean(dim=(0, 2, 3), keepdim=True) var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True) # 训练模式下,用当前的均值和方差做标准化 X_hat = (X - mean) / torch.sqrt(var + eps) # 更新移动平均的均值和方差 moving_mean = momentum * moving_mean + (1.0 - momentum) * mean moving_var = momentum * moving_var + (1.0 - momentum) * var Y = gamma * X_hat + beta # 缩放和移位 return Y, moving_mean.data, moving_var.data
虽然主流卷积神经网络依赖这一项技术,但是如果将使用了 BN 的预训练模型适配到新的数据集上的时候,BN 会因为数据分布的不同而导致训练不稳定。解决办法有:
- 动态更新批归一化层的均值和方差;
- 使用 层归一化 或 实例归一化 等不依赖统计量的技术。
组归一化 解决了批归一化因批量太小而导致估计不准确的问题,它通过将通道维度分离成 G 个组,每组独立进行归一化操作。
数据增强#
复杂数据增强库:https://github.com/albumentations-team/albumentations ↗
经典的数据增强技术包括:
- Color / PAC / Scale Jittering
- Random Scale / Crop
- Horizontal / Vertical Flip
- Shift / Rotation / Reflection
- Label Shuffle
- Noise
高级的数据增强技术包括:
- Mixup - 对两条训练样本进行线形插值来生成新的样本
- CutMix - 将一张图片的切割部分混合到另外一张图片上
- AutoAugment - 搜索最佳的数据增强策略组合
经典卷积架构#
Recipe is all you need:
VGG#
VGG 采用了连续的 小尺寸卷积核,通过层叠多个类似卷积层来提升网络的深度。它的特点包括:
- 小尺寸卷积核 - 更少的参数量达到与大尺寸卷积核相同的有效感受野;
- 层叠效应 - 两个 的卷积层的有效感受野相当于 的卷积层,三个相当于 的卷积层。
Inception Series#
Incption 系列采用了一种贪婪的做法,通过不同大小的卷积核提取不同空间信息(必要的时候进行填充),最后将输出在通道维度上拼接起来。
该网络主要由 Inception 模块 组成,后续的版本迭代也是基于基础模块进行设计改良:采用小卷积核、批归一化等
ResNet / ResNeXt#
ResNet 通过引入 残差学习 解决了深度网络训练难的问题,成功训练了超过 100 层的网络。指的是在网络中引入 “残差连接” 使得输入与通过若干层的输出相加,这样做的好处是:
- 缓解了梯度消失问题,梯度能够根据连接传递到浅层网络;
- 能够很好的学习输入的恒等映射、保留原始特征。
该网络主要由 残差模块 组成,其中每个模块瓶颈层。包含 5 个阶段:
ResNext 在残差模块的基础上添加了多条并行路线 / 扩大宽度,类似 Inception 模块,相较于 ResNet 有更好的 FLOPs / Accuracy 权衡。
DenseNet#
DenseNet 在 ResNet 的基础上进一步提高了特征的重用性,相较于 “残差连接” 的相加,该网络使用 “密集连结” 操作来对输入进行拓展,使得每一层都与前面所有层相连接。这样做的好处是:
- 使得每一层的特征都能够被充分利用;
- 促进了梯度的流动,缓解了梯度消失问题。
该网络主要由 Dense 模块 和 Transition 层 组成,其中前者通过增长率定义输出与输入之间通道数的关系,后者通过卷积和池化控制输出特征的通道数量和尺寸。
ConvNeXt#
[强推] 论文地址:A ConvNet for the 2020s ↗
ConvNeXt 从 ResNet-50/200 出发,逐步 “现代化” 它的设计,使得它的性能能够接近基于 Transofmer 的视觉模型。
上述架构为 ConvNext-B。作者还设计了其他参数量的模型 ConvNext-S、ConvNext-L 等。
模型的压缩和量化#
量化(Quantization) - 权重量化和激活量化。目的都是将浮点数转换为整数,只是量化的对象不同。
剪枝(Pruning) - 结构化剪枝和非结构化剪枝,区别在于前者剪的是权重连接,后者剪的是整个结构单元。
知识蒸馏(Knowledge Distillation) - 教师-学生模型,学生模型教师的输出(软标签)来获得类似的表现。
The Lottery Ticket Hypothesis#
动机 - 网络剪枝虽可以大幅减少参数量,但剪枝后得到的子网络很难从头开始训练。
彩票假设提出,在一个随机初始化的密集网络中存在一个稀疏子网络(“中奖彩票”),可以通过适当的剪枝来达到与原始网络相似甚至更好的性能。关键在于网络的初始化。具体的实现步骤为:
- 训练原始网络至收敛;
- 移除低权重幅值的连接(剪枝);
- 重置剩余权重至初始值,重新训练。
通过迭代式的剪枝和重新训练,直至网络收敛。
轻量级卷积架构#
在同等精度下,轻量级网络具有以下优势:
- 更有效的分布式训练 - 参数量越小,服务器之间的通信开销越少,分布式训练的可扩展性越好;
- 将新模型导出到客户端的开销更少 - 一些公司会使用 架空更新(over-the-air update),即定期将新模型更新到产品中。网络更新需要大量的数据传输;
- 可以部署到 FPGA 和嵌入式设备上 - FPGA 的内存通常少于 10MB,小模型不会受到带宽限制。
SqueezeNet#
SqueezeNet 在参数量减少了 50 倍,模型尺寸减少了 510 倍的情况下,达到了与 AlexNet 的同等精度。
该网络主要由 Fire 模块 组成,分成两个部分:
- Squeeze - 使用 的卷积核进行特征压缩,减少通道数;
- Expand - 使用 和 的卷积核分别进行特征扩展,恢复部分特征信息。
SENet#
SENet 通过 通道注意力机制 对特征通道间的相关性进行建模,包括 Squeeze 过程 和 Excitation 过程:
- Squeeze 过程对输入特征沿着通道维度进行全局平均池化,用于提取特征的一阶统计量;
- Excitation 过程通过两层 MLP 计算通道之间的相关性分数,并且引入了 倍降采样。
最后沿着输入特征的通道维度乘以相关性分数得到最终的输出特征。该模块可以 即插即用到任意卷积神经网络 中。
通道注意力机制的 Pytorch 实现代码如下:
import torch.nn as nn
class SELayer(nn.Module): def __init__(self, channel, reduction=16): super(SELayer, self).__init__() self.avgpool = nn.AdaptiveAvgPool2d(1) self.fc = nn.Sequential( nn.Linear(channel, channel//reduction,bias=False), nn.ReLU(inplace=True), nn.Linear(channel//reduction,channel, bias=False), nn.Sigmoid() )
def forward(self, x): batch_size, channels, height, width = x.size() y = self.avgpool(x).view(batch_size, channels) y = self.fc(y).view(batch_size, channels, 1, 1) return x * y.expand_as(x)
ShuffleNet#
ShuffleNet 通过组卷积和 通道打乱机制 建立不同组之间的信息沟通,解决了每个组的输出只与组内的特征有关导致的表征问题。
Channel Shuffle 的 Pytorch 实现代码:
class ChannelShuffle(nn.Module): def __init__(self, groups=3): super(ChannelShuffle, self).__init__() self.groups = groups
def forward(self, x): batch_size, channels, height, width = x.size() assert channels % self.groups == 0 group_channels = channels // self.groups x = x.view(batch_size, self.groups, group_channels, height, width) x = x.permute(0, 2, 1, 3, 4).contiguous() x = x.view(batch_size, channels, height, width)
return x
MobileNet Series#
MobileNet v1
该网络使用深度可分离卷积代替普通卷积,在准确率轻微下降的情况下极大地减少了模型的参数量和计算量,从而使得模型可以部署在移动端。并且模型的大部分参数来源于逐点卷积。
MobileNet v2
作者认为深度卷积部分的卷积核训练后容易变得稀疏(大部分值为 0)是由于映射到低维空间的特征经过 ReLU 后会损失较多信息。而每一个模块的输入都是上一个模块应用 ReLU 之后的结果,因此深度可分离模块会导致信息损失。
一个很自然的想法就是取消模块最后的激活函数并且通过增加输入特征的通道数来增加信息量。
- Linear bottleneck - 去除模块最后的逐点卷积处的 ReLU 激活函数,相当于应用线性激活;
- Inverted residual block - 在深度卷积之前添加逐点卷积增加输入特征的通道数。
残差模块和倒置残差模块的区别在于前者先降维后升维度,后者先升维后降维。
轻量化卷积算子汇总#
上图描述了各类卷积算子对输入特征在空间维度和通道维度的影响,以及对应的复杂度分析。
高级卷积架构#
Spatial Transformer Network#
Spatial Transformer Networks(STN) 是一种可以动态学习空间变换的模块,能够自动对输入数据进行几何变换从而提高模型在处理空间维度不变性的任务的表现。
主要由三个部分组成:
- 定位网络(Localization Network) - 接受输入图像并输出变换参数;
- 网格生成器(Grid Generator) - 接受变换参数输出网格坐标,表示输出图像相较于输入的位置;
- 采样器(Sampler) - 使用网格从输入图像上提取像素值。
Non-local Neural Network#
传统卷积神经网络虽然能够有效地提取局部区域的特征,但它在捕捉远距离像素或不同区域之间的关系时显得力不从心。该网络通过引入全局的相似性度量来捕捉长距离依赖关系,它的基本思想是对所有特征的每个位置进行加权聚合。但很明显这种对每一帧的像素之间建模以及对若干帧不同像素之间的建模的复杂度是非常高的,后续基于 Transofmer 的工作都是利用 patch 计算相似度。