丢弃法
动机
一个好的模型需要对输入数据的扰动鲁棒
- 使用有噪音的数据等价于Tikhonov正则
- 丢弃法:在层之间加入噪音
正则都可以理解为它在控制模型不要过拟合,不要太大
丢弃法不在数据中增加噪音,转而在层中增加噪音,所以丢弃法其实也是一种正则
无偏差的加入噪音
意味着,我以p的概率丢弃x,在剩下的概率里面我把x变大(因为p是一个0~1之间的数,所以1-p一定小于1)。
为什么要除一个1-p呢?,因为我们希望加入噪声之后我们的数据期望不变,而期望需要乘一个概率,当我们给x除了一个1-p之后可以看出,数据的期望公式并没有发生改变
训练中的丢弃法
通常将丢弃法作用在隐藏全连接层的输出上
推理中的丢弃法
dropout是一个正则项,而正则项只在训练中使用(包括L2正则等待):他们影响模型参数的更新
- 因为正则项只会对权重造成影响
其实最早dropout被提出的时候,作者没把他当成一个正则项,他的想法是:模型每次dropout可以看作是激活了不同的很小的神经子网络,最终训练出n个很小的子神经网络去平均。这样训练一个模型可以用很多小神经网络去平均,效果肯定会好。
但是后面被大家研究研究..发现它在实践中其实就是一个正则项
在推理过程中,丢弃法直接返回输出,即输出的是他本身:h = dropout(h)
- 这样也能保证确定性的输出
总结
- 丢弃法将一些输出项随机置0来控制模型复杂度
- 常作用在多层感知机的隐藏层输出上(很少用在cnn啊那些模型上面)
- 丢弃概率是控制模型复杂度的超参数(在1不丢~0全丢之间,一般最常见的丢弃概率是0.5、0.9、0.1)
代码实现
从0开始实现
我们实现dropout_layer函数,该函数以dropout的概率丢弃张量输入X中的元素
import torchdef dropout_layer(X, droupout):assert 0 <= dropout <= 1 # 一个断言,确定dropout率保持在正常区间if dropout == 1:return torch.zero_like(X) # 直接返回全 0if dropout == 0:return X # 不用丢,直接返回X# 随机生成一个0~1的向量,根据是否大于dropout生成一个布尔值(大于赋 1,小于赋 0)mask = (torch.randn(X.shape) > dropout).float()return mask * X / (1.0 - dropout)
为什么代码中挑出mask之后不直接 X[mask] = 0,来将值全部设成0?
因为无论对于GPU还是CPU来说,做乘法远远比去选一个元素来的快
定义具有两个隐藏层的多层感知机,每个隐藏层包含256个单元
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
dropout1, dropout2 = 0.2, 0.5class Net(nn.Module):def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2, is_training = True):super(Net, self).__init__()self.num_inputs = num_inputsself.training = is_trainingself.lin1 = nn.Linear(num_inputs, num_hiddens1)self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)self.lin3 = nn.Linear(num_hiddens2, num_outputs)self.relu = nn.ReLU()def forward(self, X):H1 = self.relu(self,lin1(X.reshape((-1, self.num_inputs))))if self.training == True:H1 = dropout_layer(H1, dropout1) # dropout一般作用在全连接隐藏层的输出上H2 = self.relu(self.lin2(H1))if self.training == True:H2 = dropout_layer(H2, dropout2)out = self.lin3(H2) # 注意!!输出层是不作用dropout的!return outnet = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
简洁实现
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(),nn.Dropout(dropout1), nn.Linear(256, 256), nn.ReLU(),nn.Dropout(dropout2), nn.Linear(256, 10)
)def init_weights(m):if type(m) == nn.Linear:nn.init.normal_(m.weight, std = 0.01)net.apply(init_weights)
QA
- dropout随机置 0 对求梯度和反向传播的影响是什么?
dropout置 0 的地方梯度就是 0,但是未置 0 的地方对应乘了一个数放大了,所以dropout对于梯度是一个对称的函数,而且置 0 的那些对应的权重这一轮就不会更新。
- dropout如何保证结果的正确性和可重复性?
所谓正确性,机器学习没有正确性hhh只有效果好不好。所以机器学习,特别是神经网络,你哪怕逻辑出了很大的bug,甚至可能看不出来,最终对acc的影响也就一个点不到。
对于dropout来说,你下次丢弃的东西可能就不是这些了,不过是dropout,对于整个神经网络来说可重复性都是一个很难的问题。或者你可以固定一个随机种子random seed,那你的drop就是可重复的,但是你整个网络的随机性还是挺重的,比如初始权值也是随机的,甚至你的cudnn每次算的都是不太一样的,加的顺序不一样出来的数就会不一样...几乎不能重复,其实没啥必要可重复,在一个范围内就行了。
机器学习的六字真言:越随机越稳定!!
- BN和dropout的相关性和区别
可以理解为BN是给CNN卷积层用的,而dropout是给全连接层用的
- 在同样lr下,dropout的介入会不会造成参数收敛更慢?
是有可能的!,但是并不太需要因为dropout的存在而调大lr的需要。因为dropout不改变期望,lr是对期望和方差敏感一点点的。但是确实有可能会导致收敛变慢
数值稳定性
因为在神经网络中,我们做了太多的矩阵乘法,所以很容易导致参数数值的不稳定
数值稳定性的常见两个问题:
- 梯度爆炸
- 梯度消失
梯度爆炸的问题
值超过值域(infinity)
对于16位浮点数尤为严重(数值区间6e-5~6e4)
我们经常采用16位浮点数,因为16位浮点数可以比32位浮点数块两倍
对学习率敏感
- 如果学习率太大 -> 大参数值 -> 更大的梯度
- 如果学习率太小 -> 训练无进展
- 我们可能需要在训练过程不断调整学习率
梯度消失的问题
梯度值变成 0
对16位浮点数尤为严重
训练没有进展
无论如何选择学习率
对于底层尤为严重
仅仅顶部层训练的较好
无法让神经网络更深
因为神经网络是从顶部回传的,到底部会变得特别特别小
总结
- 当数值过大或者过小时会导致数值问题
- 常发生在深度模型中,因为其会对n个数累乘