池化层
在卷积网络中, 通常会在卷积层之间增加池化(Pooling) 层, 以降低特征图的参数量, 提升计算速度, 增加感受野, 是一种降采样操作。池化是一种较强的先验, 可以使模型更关注全局特征而非局部出现的位置, 这种降维的过程可以保留一些重要的特征信息, 提升容错能力, 并且还能在一定程度上起到防止过拟合的作用 。
在物体检测中, 常用的池化有最大值池化(Max Pooling) 与平均值池化(Average Pooling) 。 池化层有两个主要的输入参数, 即核尺寸kernel_size与步长stride。一个核尺寸与步长都为2的最大值池化过程, 9、 20、 15与26进行最大值池化, 保留26
下面是PyTorch对于池化层的实现。
import torch
from torch import nn
max_pooling = nn.MaxPool2d(2, stride=2)
aver_pooling = nn.AvgPool2d(2, stride=2)
input = torch.randn(1, 1, 4, 4)
print(input)
>> tensor([[[[ 1.2237, -0.8173, -0.2594, 0.1698],
[-0.1023, 0.6973, -0.6429, 0.8561],
[-0.3660, 0.1269, 0.2488, 0.0576],
[ 0.0859, 0.1622, -0.0725, -0.0237]]]])
# 池化主要需要两个参数, 第一个参数代表池化区域大小, 第二个参数表示步长
out_max = max_pooling(input)
print(out_max)
>> tensor([[[[1.2237, 0.8561],
[0.1622, 0.2488]]]])
# 调用最大值池化与平均值池化, 可以看到size从[1, 1, 4, 4]变为了[1, 1, 2, 2]
out_aver = aver_pooling(input)
print(out_aver)
>> tensor([[[[0.2503, 0.0309],
[0.0023, 0.0525]]]])
Dropout层
在深度学习中, 当参数过多而训练样本又比较少时, 模型容易产生过拟合现象。 过拟合是很多深度学习乃至机器学算法的通病, 具体表现为在训练集上预测准确率高, 而在测试集上准确率大幅下降。 2012年, Hinton等人提出了Dropout算法, 可以比较有效地缓解过拟合现象的发生, 起到一定正则化的效果。Dropout的基本思想如图3.8所示, 在训练时, 每个神经元以概率p保留, 即以1-p的概率停止工作, 每次前向传播保留下来的神经元都不同, 这样可以使得模型不太依赖于某些局部特征, 泛化性能更强。 在测试时, 为了保证相同的输出期望值, 每个参数还要乘以p。 当然还有另外一种计算方式称为Inverted Dropout, 即在训练时将保留下的神经元乘以1/p, 这样测试时就不需要再改变权重。
至于Dropout为什么可以防止过拟合, 可以从以下3个方面解释。
·多模型的平均: 不同的固定神经网络会有不同的过拟合, 多个取平均则有可能让一些相反的拟合抵消掉, 而Dropout每次都是不同的神经元失活, 可以看做是多个模型的平均, 类似于多数投票取胜的策略。
·减少神经元间的依赖: 由于两个神经元不一定同时有效, 因此减少了特征之间的依赖, 迫使网络学习有更为鲁棒的特征, 因为神经网络不应该对特定的特征敏感, 而应该从众多特征中学习更为共同的规律,这也起到了正则化的效果。
·生物进化: Dropout类似于性别在生物进化中的角色, 物种为了适应环境变化, 在繁衍时取雄性和雌性的各一半基因进行组合, 这样可以适应更复杂的新环境, 避免了单一基因的过拟合, 当环境发生变化时也不至于灭绝。
在PyTorch中使用Dropout非常简单, 示例如下:
import torch
from torch import nn
# PyTorch将元素置0来实现Dropout层, 第一个参数为置0概率, 第二个为是否原地操作
dropout = nn.Dropout(0.5, inplace=False)
input = torch.randn(2, 64, 7, 7)
output = dropout(input)
print(output.shape)
Dropout被广泛应用到全连接层中, 一般保留概率设置为0.5, 而在较为稀疏的卷积网络中则一般使用下一节将要介绍的BN层来正则化模型, 使得训练更稳定。
BN层
为了追求更高的性能, 卷积网络被设计得越来越深, 然而网络却变得难以训练收敛与调参。 原因在于, 浅层参数的微弱变化经过多层线性变换与激活函数后会被放大, 改变了每一层的输入分布, 造成深层的网络需要不断调整以适应这些分布变化, 最终导致模型难以训练收敛。
由于网络中参数变化导致的内部节点数据分布发生变化的现象被称做ICS(Internal Covariate Shift) 。 ICS现象容易使训练过程陷入饱和区, 减慢网络的收敛。 前面提到的ReLU从激活函数的角度出发, 在一定程度上解决了梯度饱和的现象, 而2015年提出的BN层, 则从改变数据分布的角度避免了参数陷入饱
和区。 由于BN层优越的性能, 其已经是当前卷积网络中的“标配”。
BN层首先对每一个batch的输入特征进行白化操作, 即去均值方差过程。 假设一个batch的输入数据为x:B={ x1,…,xm} , 首先求该batch数据的均值与方差, 如式(3-5) 和式(3-6) 所示
以上公式中, m代表batch的大小, μB为批处理数据的均值, σ2B为批处理数据的方差。 在求得均值方差后, 利用式(3-7) 进行去均值方差操作:
白化操作可以使输入的特征分布具有相同的均值与方差, 固定了每一层的输入分布, 从而加速网络的收敛。 然而, 白化操作虽然从一定程度上避免了梯度饱和, 但也限制了网络中数据的表达能力, 浅层学到的参数信息会被白化操作屏蔽掉, 因此, BN层在白化操作后又增加了一个线性变换操作, 让数据尽可能地恢复本身的表达能力, 如公式(3-7) 和公式(3-8) 所示。
公式(3-8) 中, γ与β为新引进的可学习参数, 最终的输出为yi。
BN层可以看做是增加了线性变换的白化操作, 在实际工程中被证明了能够缓解神经网络难以训练的问题。 BN层的优点主要有以下3点:
·缓解梯度消失, 加速网络收敛。 BN层可以让激活函数的输入数据落在非饱和区, 缓解了梯度消失问题。 此外, 由于每一层数据的均值与方差都在一定范围内, 深层网络不必去不断适应浅层网络输入的变化,实现了层间解耦, 允许每一层独立学习, 也加快了网络的收敛。
·简化调参, 网络更稳定。 在调参时, 学习率调得过大容易出现震荡与不收敛, BN层则抑制了参数微小变化随网络加深而被放大的问题, 因此对于参数变化的适应能力更强, 更容易调参。
·防止过拟合。 BN层将每一个batch的均值与方差引入到网络中, 由于每个batch的这两个值都不相同, 可看做为训练过程增加了随机噪音, 可以起到一定的正则效果, 防止过拟合。
在测试时, 由于是对单个样本进行测试, 没有batch的均值与方差,通常做法是在训练时将每一个batch的均值与方差都保留下来, 在测试时使用所有训练样本均值与方差的平均值。
PyTorch中使用BN层很简单, 示例如下:
import torch
from torch import nn
# 使用BN层需要传入一个参数为num_features, 即特征的通道数
bn = nn.BatchNorm2d(64)
print(bn)
>> BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
# eps为公式中的є, momentum为均值方差的动量, affine为添加可学习参数
input = torch.randn(4, 64, 224, 224)
output = bn(input)
# BN层不改变输入、 输出的特征大小
print(output.shape)
>> torch.Size([4, 64, 224, 224])
尽管BN层取得了巨大的成功, 但仍有一定的弊端, 主要体现在以下两点:
由于是在batch的维度进行归一化, BN层要求较大的batch才能有效地工作, 而物体检测等任务由于占用内存较高, 限制了batch的大小, 这会限制BN层有效地发挥归一化功能。
数据的batch大小在训练与测试时往往不一样。 在训练时一般采用滑动来计算平均值与方差, 在测试时直接拿训练集的平均值与方差来使用。 这种方式会导致测试集依赖于训练集, 然而有时训练集与测试集的数据分布并不一致。
因此, 我们能不能避开batch来进行归一化呢? 答案是可以的, 最新的工作GN(Group Normalization) 从通道方向计算均值与方差, 使用更 为灵活有效, 避开了batch大小对归一化的影响。
具体来讲, GN先将特征图的通道分为很多个组, 对每一个组内的参数做归一化, 而不是batch。 GN之所以能够工作的原因, 笔者认为是在特征图中, 不同的通道代表了不同的意义, 例如形状、 边缘和纹理等, 这些不同的通道并不是完全独立地分布, 而是可以放到一起进行归一化分析。
全连接层
全连接层(Fully Connected Layers) 一般连接到卷积网络输出的特征图后边, 特点是每一个节点都与上下层的所有节点相连, 输入与输出都被延展成一维向量, 因此从参数量来看全连接层的参数量是最多的,如图3.9所示。
在物体检测算法中, 卷积网络的主要作用是从局部到整体地提取图像的特征, 而全连接层则用来将卷积抽象出的特征图进一步映射到特定维度的标签空间, 以求取损失或者输出预测结果。
在第2章中的感知机例子即是使用了三层的全连接网络进行分类,PyTorch使用全连接层需要指定输入的与输出的维度。 示例如下:
import torch
from torch import nn
# 第一维表示一共有4个样本
input = torch.randn(4, 1024)
linear = nn.Linear(1024, 4096)
output = linear(input)
print(input.shape)
>> torch.Size([4, 1024])
print(output.shape)
>> torch.Size([4, 4096])
然而, 随着深度学习算法的发展, 全连接层的缺点也逐渐暴露了出来, 最致命的问题在于其参数量的庞大。 在此以VGGNet为例说明, 其第一个全连接层的输入特征为7×7×512=25088个节点, 输出特征是大小为4096的一维向量, 由于输出层的每一个点都来自于上一层所有点的权重相加, 因此这一层的参数量为25088×4096≈108。 相比之下, VGGNet最后一个卷积层的卷积核大小为3×3×512×512≈2.4×106, 全连接层的参数量是这一个卷积层的40多倍。
大量的参数会导致网络模型应用部署困难, 并且其中存在着大量的参数冗余, 也容易发生过拟合的现象。 在很多场景中, 我们可以使用全局平均池化层(Global Average Pooling, GAP) 来取代全连接层, 这种思想最早见于NIN(Network in Network) 网络中, 总体上, 使用GAP有如下3点好处:
·利用池化实现了降维, 极大地减少了网络的参数量。
·将特征提取与分类合二为一, 一定程度上可以防止过拟合。
·由于去除了全连接层, 可以实现任意图像尺度的输入。