题记: 最近Swin Transformer在计算机视觉上大放异彩,成为许多视觉榜单上的霸主,然而传统的Conv卷积如Resnet就真的不行了吗? 一些学者就传统的卷积网络进行了深入的研究,并通过细致的实验,精心设计的结构和一些"trick",实验表明,Convnet的能力并不亚于Transformer。最近笔者就 Revisiting ResNets:Improved Training and Scaling Strategies 这篇文章中提到的一些trick进行了相关调研。本篇笔记主要调研的是论文中所使用的随机深度的正则化方法,其他很多方法的实现已经在timm库中进行了实现,笔者也是参考其中的代码进行复现的。
timm 库Git地址:https://github.com/feng-lab/pytorch-image-models/tree/feng/timm
Deep Network with Stochastic Depth
背景
基于一个公认的共识,即通过增加卷积网络的深度能显著降低预测误差,提高网络的表达能力,然而随着网络层的加深,也会带来一些负面影响,比如梯度消失,前向传播耗时增加、训练缓慢、模型过拟合训练数据等等。为了解决这些问题,我们提出了随机深度的方法,一个看似矛盾的设置,在训练过程降低网络的深度,在测试阶段保持网络的深度。通过该方法,大大减少了训练时间,并在评估的一些数据集上显著改善了测试误差。通过使用随机深度的正则方法,我们在1200层的残差网络上中依然能够保证其有效性。
方法介绍
随机深度的实现,基于一个非常朴素的做法:在一个非常深的网络中,对于每个批次的数据,随机删除一些层级子集,并通过恒等函数绕过这些层级。我们以ResNets作为主干卷积网络,残差网络中跳远连接方式,是为了解决多层卷积后信号传播消失的问题。形式上有如下表达,其中
H
l
H_l
Hl表示第l层第输出,
f
(
.
)
f(.)
f(.)表示典型的卷积变换映射函数。
H
l
=
R
e
L
U
(
f
l
(
H
l
−
1
)
+
i
d
(
H
l
−
1
)
)
(
1
)
H_l=ReLU(f_l(H_{l-1})+id(H_{l-1})) \qquad \quad(1)
Hl=ReLU(fl(Hl−1)+id(Hl−1))(1)
其中
i
d
(
.
)
id(.)
id(.)表示恒等变化,这里假设最后的输出通过一个ReLU的激活函数进行非线性变化。
f
l
f_l
fl可以是多个卷积和
B
N
BN
BN的组合。一个残差连接块的结构如下图表示:
Drop方法
Drop是在深度神经网络中常用的一种正则方法,通过随机丢弃一些神经元,从而减少神经元之间的依赖关系,让隐藏节点保持独立,使得网络不过度依赖某些神经元,从而达到缓解过拟合的目的。一些成熟的drop技术应用如DropCnnect,Maxout或者DropIn都被证明了对缓解测试误差有一定的帮助。
相似的,我们的随机深度可以理解为不同深度网络的组合,相比固定深度的网络能获得更高的多样性,与Dropout不同的是我们作用的是网络的深度,而不是减少网络的宽度(神经元个数)。值得注意的是,作者在文中提到,Dropout在与Batch Normalization结合时,往往会失去作用(BN也有正则的作用),如在110层具有BN的resnet中,Dropout并不能有效降低网络的测试误差。
Stochastic Depth 随机深度方法
随机深度的方法基于一个简单的直觉,为了在训练过程中应该减少网络的有效深度,我们随机跳过一些层级链接来实现这一效果。在一个批次的数据中,随机选择部分子集去掉对应的转换函数,只保留恒等映射。随机深度的目的是在迅雷过程缩小网络的深度,而在测试过程保持其不变。通过在uxnl过程随机删除ResBlocks,并通过跳跃链接来绕过卷积函数实现。如下公式(2)所示,
b
l
b_l
bl表示第l层的ResBlocks是否活跃(b=1)或不活跃(b=0),进一步 可以通过伯努利分布来随机控制连接的有效性。
H
l
=
R
e
L
U
(
b
l
f
l
(
H
l
−
1
)
+
i
d
(
H
l
−
1
)
)
(
2
)
H_l=ReLU(b_lf_l(H_{l-1})+id(H_{l-1}))\qquad \quad(2)
Hl=ReLU(blfl(Hl−1)+id(Hl−1))(2)
如果b_l=1,等式(2)就相当于一个原本的残存连接,如公式(1)所示,如果b=1就是一个恒等映射:
H
l
=
i
d
(
H
l
−
1
)
(
3
)
H_l=id(H_{l-1}) \qquad \quad(3)
Hl=id(Hl−1)(3)
对于这样的连接Drop是基于输入层
H
l
−
1
H_{l-1}
Hl−1是非负的,通过RuLU激活后,总是一个大于0的数,所以随机深度的
l
>
=
2
l>=2
l>=2,即从第一层卷积之后开始。
注:在实际应用中,并没有严格的限制,因为很多的卷积网络比如IResnet等,每一个Block的输出并不一定会使用ReLU输出(ReLU使用过多会有造成负面影响,如IResnet中,每个Blocak只在中间的卷积后使用一次)。
随机深度方法只有一个超参数
p
l
p_l
pl, 即邻域链接的概率,我们可以固定为一个常数。另外我们提出了一个简单的线性衰减规则,对于第一层的连接概率p_0=1,随着层级的增加逐渐衰减:
p
ℓ
=
1
−
l
L
(
1
−
p
L
)
(
4
)
p_{\ell}=1-\frac{l}{L}(1-p_L) \qquad \quad(4)
pℓ=1−Ll(1−pL)(4)
线性衰减的保留策略是基于一个直觉,即浅层网络提取的特征更为基础,对后面的深层网络使用更加重要。作者在后续也对这个超参做了大量实验,证明了线性衰减策略的有效性和可靠性。
让我们看下线性衰减下,网络深度的期望。在前向反向传播的过程中,
f
l
f_l
fl以
(
1
−
p
ℓ
)
(1-p_{\ell})
(1−pℓ)的概率被抛弃,从而一定程度上减少网络的深度,我们记最终的网络深度为
L
~
\tilde{L}
L~的随机变量,则很自然的深度的期望为:
E
(
L
~
)
=
∑
ℓ
=
1
L
p
ℓ
E(\tilde{L})=\sum_{\ell=1}^{L}p_{\ell}
E(L~)=∑ℓ=1Lpℓ,带入公式(4),在
p
L
p_L
pL=0.5的衰减规则下,训练的深度将减少到
E
(
L
~
)
=
(
3
L
−
1
)
/
4
E(\tilde{L})=(3L-1)/4
E(L~)=(3L−1)/4,或当L非常大的时候,近似等于
E
(
L
~
)
≈
3
L
/
4
E(\tilde{L})\approx3L/4
E(L~)≈3L/4,如当L=54时,通过随机深度衰减,在0.5的衰减率下,深度近似为40层.
实验结果
接下来我们看下作者的一系列实验结果,首先是随机深度和固定深度的对比试验,我们分别在CIFAR-10和CIFAR-100使用110-layer ResNet进行测试,测试结果如下图所示:
从图中可以观察到:
- 添加随机深度正则后,网络的训练loss会明显高于固定深度的loss
- 在训练的早期,固定深度的测试误差和随机深度的测试误差差不多,但是随着训练周期的增加,随机深度的测试误差要低于固定深度的误差
- 在更大的分类训练数据上,随机深度的优越性会更加明显
我们在看看更深层数下随机深度的表现能力,在SVHN数据集和CIFAR-10数据集下的实验。
从上图中,我们能看出:
- 随着层数的增加,随机深度方法的优越性要更加明显(测试误差相对更低)
- 在SVHN数据集上,固定深度的方法在训练后期loss有上升的趋势,而随机深度的训练要更加鲁棒。
SVHN是一个真实世界的图像数据集,里面包含了各种各样的数字牌,其中73272张训练图片,26032张测试图片,相对于CIFAR-10训练集,数据量要更大一些。
另外作者还进行了耗时的对比试验,可以看到随机深度的策略在一定成都上能够减小训练的时间。
最后对于超参数的调节,作者在CIFAR-10的数据集上进行了一系列的对比试验:
左图是110-layer ResNet下,固定p_L随机深度 ,线性衰减随机深度和固定深度策略下的测试错误率对比,可以看到线性衰减策略始终有较好的性能,在p_L=0.5时,有最优的测试错误率。
右图则是不同p_L下,不同网络深度的测试错误率热力图,可以看到,随着层数的增加,p_L也应该要增加,即drop的概率要增加。随机深度策略在更深的网络优势越为明显。
代码实现
随机深度的代码实现非常简单,对于一个批次的数据,通过伯努利分布来随机挑选一部分的数据跳过连接。为了让函数可倒,我们将连接层前向的输出矩阵乘上一个因子来控制连接的概率。具体代码参考如下:详细实现代码可参考文章开头的git地址.
def drop_path(x, drop_prob: float = 0., training: bool = False):
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
This is the same as the DropConnect impl I created for EfficientNet, etc networks, however,
the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper...
See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted for
changing the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use
'survival rate' as the argument.
"""
if drop_prob == 0. or not training:
return x
keep_prob = 1 - drop_prob
shape = (x.shape[0],) + (1,) * (x.ndim - 1) # work with diff dim tensors, not just 2D ConvNets
random_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device)
random_tensor.floor_() # binarize
output = x.div(keep_prob) * random_tensor
return output
class DropPath(nn.Module):
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
"""
def __init__(self, drop_prob=None):
super(DropPath, self).__init__()
self.drop_prob = drop_prob
def forward(self, x):
return drop_path(x, self.drop_prob, self.training)
# 在block的前向传播中,在最后的链接处使用
def forward(self, x):
shortcut = x
if self.conv_exp is not None:
x = self.conv_exp(x)
x = self.conv_dw(x)
if self.se is not None:
x = self.se(x)
x = self.act_dw(x)
x = self.conv_pwl(x)
if self.drop_path is not None: # 是否使用随机深度
x = self.drop_path(x)
if self.use_shortcut:
x[:, 0:self.in_channels] += shortcut
return x
总结
文章思路和方法都是比较简单清晰的,和dropout其实是有异曲同工的作用,但是实际的作用,在不同数据集和不同的网络中表现应该也是不同的,就像dropout很多时候并不一定能提高网络的性能,具体还得在特定场景进行实验,从作者的实验结果中来看,对于层数越多的网络,随机深度性能越好,同时drop率也要相应增加。
同时笔者也有一些疑问:
1、在测试过程中,不开启drop path的,如果在训练过程中,P_L设置的比较大,那么是不是会有些层的训练不够,而导致推断能力不足。
2、作者没有详细分析或说明为什么BN会使Dropout失效,这对Drop Path是否也会有一样的影响。