dropout 有两种实现方式,Vanilla Dropout 和inverted Dropout。前者是 原始论文 中的朴素版后者在 Andrew Ng 的 cs231 课程中(https://cs231n.github.io/neural-networks-2/#init)有介绍。其实就两个要点:
- rescale。输入序列为 X = [ x 0 , x 1 , x 2 , x 3 ] X=[x_0,x_1,x_2,x_3] X=[x0,x1,x2,x3] ,正常的梯度序列为 D = [ d 0 , d 1 , d 2 , d 3 ] D=[d_0,d_1,d_2,d_3] D=[d0,d1,d2,d3] ,假设以丢弃概率 p=0.2 进行伯努利采样Q得到的 mask 序列M =1011 。如果是 vanilla 版本,在 forward 时直接对输入进行遮罩 X ′ = [ x 0 , 0 , x 2 , x 3 ] X^{'}=[x_0,0,x_2,x_3] X′=[x0,0,x2,x3] ,在 backward 时梯度和输入保持一致 D ′ = [ d 0 , 0 , d 2 , d 3 ] D^{'}=[d_0,0,d_2,d_3] D′=[d0,0,d2,d3];在infer 时进行 1-p 倍的缩小I = 0.8X 。你看,训练时候因为随机扔掉了一些节点Q,总期望变小,那么预测时候就全体缩小一点来保持一致。这样处理的一个最大问题,就是预测过程需要跟着 dropout 策略做调整,哪些层取消了、加重了或者减轻了,都需要改。一不小心就会出错,inverted 版本顺势而生,把所有的修改都放在训练阶段,保持预测阶段不变。在 forward 时先遮罩再进行 1/(1-p) 的放大 X ′ = 1.25 ∗ [ x 0 , 0 , x 2 , x 3 ] X^{'}=1.25*[x_0,0,x_2,x_3] X′=1.25∗[x0,0,x2,x3],在 backward 时需要同步修改梯度 D ′ = [ d 0 , 0 , d 2 , d 3 ] D^{'}=[d_0,0,d_2,d_3] D′=[d0,0,d2,d3],在 infer 时不做额外的处理I = X。训练时虽然也随机扔掉了一些节点,但是做了 rescale 之后总期望被拉回了原来的水平,训练过程和预测过程仍然是一致的。
- forward和backward的时候扩大1-p倍,Infer的时候啥也不用干了
Pytorch对dropout的实现早就被放到cpp和cuda里去了,在古早版本里还有python版本的dropout,在https://github.com/colesbury/pytorch-old/blob/master/torch/nn/functions/dropout.py 这里可以看到源代码,input.new()就是随机了一个矩阵,下面摘录一些要点:
import torch
import torch.nn as nn
import torch.autograd.functional as Function
class MyDropoutFunc(Function):
def __init__(self, p, train):
self.p = p
self.train = train
def forward(self, input):
if self.p > 0 and self.train:
self.noise = self.rand(input.size()).bernoulli_(1 - self.p).div_(1 - self.p)
input.mul_(self.noise)
return input
return input
def backward(self, grad_output):
if self.p > 0 and self.train:
return grad_output.mul(self.noise)
else:
return grad_output
补充一个写成nn.Module的版本:
import torch
import torch.nn as nn
class MyDropout(nn.Module):
def __init__(self,p):
super(MyDropout, self).__init__()
self.p = p
def forward(self, input):
self.noise = torch.rand(input.size()).bernoulli_(1-self.p).div_(1-self.p)
if self.training and self.p > 0:
input.mul_(self.noise)
return input
return input
def backward(self, grad_output):
if self.train and self.p > 0:
grad_output.mul_(self.noise)
return grad_output
if __name__ == '__main__':
input = torch.randn(4,3)
input2 = input.clone()
my_dropout = MyDropout(0.2)
my_dropout.train()
print(my_dropout(input))
dropout = nn.Dropout(0.2)
print(dropout(input2))
'''
tensor([[-0.5323, 0.6883, -1.2510],
[ 0.0000, 0.3083, 0.0000],
[ 0.9472, 0.0000, 0.8548],
[ 0.4423, -0.0776, 1.0469]])
tensor([[-0.5323, 0.6883, -0.0000],
[ 0.4311, 0.3083, 0.0000],
[ 0.9472, 0.3472, 0.8548],
[ 0.4423, -0.0776, 1.0469]])
'''
部分转述自: https://www.zhihu.com/question/60874090/answer/181672076?from=groupmessage