Deep Network with Stochastic Depth(阅读笔记)一种随机深度的正则化方法

题记: 最近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

目录

  • Deep Network with Stochastic Depth
  • 背景
  • 方法介绍
    • Drop方法
    • 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(Hl1)+id(Hl1))(1)
其中 i d ( . ) id(.) id(.)表示恒等变化,这里假设最后的输出通过一个ReLU的激活函数进行非线性变化。 f l f_l fl可以是多个卷积和 B N BN BN的组合。一个残差连接块的结构如下图表示:
Deep Network with Stochastic Depth(阅读笔记)一种随机深度的正则化方法_第1张图片

Fig1 残差块ResBlock结构

Drop方法

Drop是在深度神经网络中常用的一种正则方法,通过随机丢弃一些神经元,从而减少神经元之间的依赖关系,让隐藏节点保持独立,使得网络不过度依赖某些神经元,从而达到缓解过拟合的目的。一些成熟的drop技术应用如DropCnnect,Maxout或者DropIn都被证明了对缓解测试误差有一定的帮助。
相似的,我们的随机深度可以理解为不同深度网络的组合,相比固定深度的网络能获得更高的多样性,与Dropout不同的是我们作用的是网络的深度,而不是减少网络的宽度(神经元个数)。值得注意的是,作者在文中提到,Dropout在与Batch Normalization结合时,往往会失去作用(BN也有正则的作用),如在110层具有BN的resnet中,Dropout并不能有效降低网络的测试误差。

在这里插入图片描述

Fig2 作者在文中提到Dropout与BN结合后表现不佳

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(Hl1)+id(Hl1))(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(Hl1)(3)
对于这样的连接Drop是基于输入层 H l − 1 H_{l-1} Hl1是非负的,通过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=1Ll(1pL)(4)
线性衰减的保留策略是基于一个直觉,即浅层网络提取的特征更为基础,对后面的深层网络使用更加重要。作者在后续也对这个超参做了大量实验,证明了线性衰减策略的有效性和可靠性。
让我们看下线性衰减下,网络深度的期望。在前向反向传播的过程中, f l f_l fl ( 1 − p ℓ ) (1-p_{\ell}) 1p的概率被抛弃,从而一定程度上减少网络的深度,我们记最终的网络深度为 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~)=(3L1)/4,或当L非常大的时候,近似等于 E ( L ~ ) ≈ 3 L / 4 E(\tilde{L})\approx3L/4 E(L~)3L/4,如当L=54时,通过随机深度衰减,在0.5的衰减率下,深度近似为40层.
Deep Network with Stochastic Depth(阅读笔记)一种随机深度的正则化方法_第2张图片

Fig3 P_l为0.5时,线性衰减深度示意图

实验结果

接下来我们看下作者的一系列实验结果,首先是随机深度和固定深度的对比试验,我们分别在CIFAR-10和CIFAR-100使用110-layer ResNet进行测试,测试结果如下图所示:
Deep Network with Stochastic Depth(阅读笔记)一种随机深度的正则化方法_第3张图片

Fig4 固定深度和随机深度在CIFAR-10和100上的表现对比

从图中可以观察到:

  • 添加随机深度正则后,网络的训练loss会明显高于固定深度的loss
  • 在训练的早期,固定深度的测试误差和随机深度的测试误差差不多,但是随着训练周期的增加,随机深度的测试误差要低于固定深度的误差
  • 在更大的分类训练数据上,随机深度的优越性会更加明显

我们在看看更深层数下随机深度的表现能力,在SVHN数据集和CIFAR-10数据集下的实验。
Deep Network with Stochastic Depth(阅读笔记)一种随机深度的正则化方法_第4张图片

Fig5 固定深度和随机深度分别在SVHN和CIFAR-10数据集上152层和1202层的模型对比

从上图中,我们能看出:

  • 随着层数的增加,随机深度方法的优越性要更加明显(测试误差相对更低)
  • 在SVHN数据集上,固定深度的方法在训练后期loss有上升的趋势,而随机深度的训练要更加鲁棒。

SVHN是一个真实世界的图像数据集,里面包含了各种各样的数字牌,其中73272张训练图片,26032张测试图片,相对于CIFAR-10训练集,数据量要更大一些。
另外作者还进行了耗时的对比试验,可以看到随机深度的策略在一定成都上能够减小训练的时间。
在这里插入图片描述

最后对于超参数的调节,作者在CIFAR-10的数据集上进行了一系列的对比试验:
Deep Network with Stochastic Depth(阅读笔记)一种随机深度的正则化方法_第5张图片

Fig5 左图:在CIFAR-10上,固定pL和衰减pL,测试误差(%)对比。右:CIFAR-10上的测试误差(%)随pL和网络深度而变化的热力图。

左图是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是否也会有一样的影响

你可能感兴趣的:(人脸识别,torch,深度学习,计算机视觉,人工智能)