文章目录
- 理论
- dropout
- DropPath
- 代码
- 问题:dropout中为什么要除以 keep_prob?
在vit的代码中看到了DropPath,想知道DropPath与nn.Dropout()有什么区别,于是查阅相关资料记录一下。
理论
dropout
dropout是最早的用于解决过拟合的方法,是所有drop类方法的大前辈。dropout在12年被Hinton提出,并且在《ImageNet Classification with Deep Convolutional Neural Network》工作AlexNet中使用到了dropout。
原理 :在前向传播的时候,让某个神经元激活以概率1-keep_prob(0<p<1)停止工作。
功能 : 这样可以让模型泛化能力更强,因为其不会过于依赖某些局部的节点。训练阶段以keep_prob的概率保留,以1-keep_prob的概率关闭;测试阶段所有的神经元都不关闭,但是对训练阶段应用了dropout的神经元,输出值需要乘以keep_prob。
注意:dropout现在一般用于全连接层。卷积层一般不使用Dropout,而是使用BN来防止过拟合,而且卷积核还会有relu等非线性函数,降低特征直接的关联性。
DropPath
DropPath于论文《FractalNet: Ultra-Deep Neural Networks without Residuals(ICLR2017)》中与FractalNet一起提出。
原理 :字如其名,DropPath将深度学习网络中的多分支结构随机删除。
功能 :一般可以作为正则化手段加入网络,但是会增加网络训练的难度。尤其是在NAS问题中,如果设置的drop_prob过高,模型甚至有可能不收敛。
代码
import torch
import torch.nn as nndef drop_path(x, drop_prob: float = 0., training: bool = False):if drop_prob == 0. or not training: # drop_prob废弃率=0,或者不是训练的时候,就保持原来不变return xkeep_prob = 1 - drop_prob # 保持率shape = (x.shape[0],) + (1,) * (x.ndim - 1) # (b, 1, 1, 1) 元组 ndim 表示几维,图像为4维random_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device) # 0-1之间的均匀分布[2,1,1,1]random_tensor.floor_() # 下取整从而确定保存哪些样本 总共有batch个数output = x.div(keep_prob) * random_tensor # 除以 keep_prob 是为了让训练和测试时的期望保持一致# 如果keep,则特征值除以 keep_prob;如果drop,则特征值为0return output # 与x的shape保持不变class DropPath(nn.Module):def __init__(self, drop_prob=None):super(DropPath, self).__init__()self.drop_prob = drop_probdef forward(self, x):return drop_path(x, self.drop_prob, self.training)if __name__ == '__main__':input = torch.randn(3, 2, 2, 3)drop1 = DropPath(drop_prob=0.4) # 实例化drop2 = nn.Dropout(p=0.4)out1 = drop1(input)out2 = drop2(input)print(input)print(out1)print(out2)print(out1.shape, out2.shape)
结果如下:
tensor([[[[-0.4603, -0.2193, 0.7828], # 这是input[-0.4790, -0.3336, 1.3353]],[[-0.4309, -0.6019, -0.4993],[ 0.2313, 0.7210, -0.2553]]],[[[ 0.0653, -0.4787, 0.6238],[ 1.4323, 1.0883, -0.6952]],[[ 0.0912, 0.8802, -0.6991],[ 0.7248, -0.9305, 0.2832]]],[[[ 0.0923, 0.4770, 0.5671],[ 1.2669, 0.4013, 0.3464]],[[ 0.8646, -0.3866, -0.8333],[-1.1507, 1.4823, 0.1255]]]])
tensor([[[[-0.7672, -0.3655, 1.3047], # 这是DropPath[-0.7984, -0.5560, 2.2255]],[[-0.7181, -1.0032, -0.8322],[ 0.3855, 1.2016, -0.4255]]],[[[ 0.0000, -0.0000, 0.0000],[ 0.0000, 0.0000, -0.0000]],[[ 0.0000, 0.0000, -0.0000],[ 0.0000, -0.0000, 0.0000]]],[[[ 0.1539, 0.7949, 0.9452],[ 2.1115, 0.6688, 0.5773]],[[ 1.4411, -0.6444, -1.3888],[-1.9179, 2.4706, 0.2092]]]])
tensor([[[[-0.7672, -0.0000, 0.0000], # 这是nn.Dropout[-0.7984, -0.5560, 2.2255]],[[-0.0000, -1.0032, -0.8322],[ 0.0000, 1.2016, -0.4255]]],[[[ 0.0000, -0.7979, 1.0397],[ 2.3872, 0.0000, -0.0000]],[[ 0.0000, 0.0000, -1.1652],[ 0.0000, -1.5509, 0.4720]]],[[[ 0.1539, 0.0000, 0.9452],[ 2.1115, 0.0000, 0.5773]],[[ 1.4411, -0.6444, -1.3888],[-1.9179, 0.0000, 0.0000]]]])
torch.Size([3, 2, 2, 3]) torch.Size([3, 2, 2, 3])
我们看input
第一个数是-0.4603,keep_prob是1-drop_prob=0.6,-0.4603/0.6=-0.7672,而两个out
中的第一个数均为-0.7672,说明两种方法均有把保留的数值除以keep_prob。
此外,可以看到,DropPath的输出中,是随机将一个batch中所有的神经元均设置为0。而在dropout中,是每个batch中随机选择概率为p的神经元设置为0。下面展示在两个输出中的一个batch的结果,看一下区别:
补充一下DropPath在vit中的调用:
self.drop_path = DropPath(drop_path_ratio) if drop_path_ratio > 0. else nn.Identity()x = x + self.drop_path(self.attn(self.norm1(x)))
x = x + self.drop_path(self.mlp(self.norm2(x)))
问题:dropout中为什么要除以 keep_prob?
为了保证使用dropout的神经元输出激活值的期望值与不使用时一致,结合概率论的知识来具体看一下:假设一个神经元的输出激活值为 a a a。在不使用drop_path的情况下,其输出期望值为 a a a。如果使用了drop_path,神经元就可能有保留和关闭两种状态,把它看作一个离散型随机变量,它就符合概率论中的0-1分布,其输出激活值的期望变为 p ∗ a + ( 1 − p ) ∗ 0 = p a p*a+(1-p)*0=pa p∗a+(1−p)∗0=pa,此时若要保持期望与不使用drop_path时一致,就需要除以 p p p。
参考资料:
https://www.cnblogs.com/dan-baishucaizi/p/14703263.html#bottom
https://www.cnblogs.com/pprp/p/14815168.html
https://blog.csdn.net/weixin_54338498/article/details/125670154
https://blog.csdn.net/wuli_xin/article/details/127266407