Dropout
Hinton最早提出了dropout的概念,用于作为nn的强力的正则化手段之一,按照Hinton的原话,他的灵感来自于:
“我去银行办理业务。柜员不停的换人,于是我问其中给的一个人这是为什么。他说他不知道,但他们经常换来换去的。我猜想,银行工作人员想要成功欺诈银行,他们之间要互相合作才行,这让我意识到,在每个样本中随机删除不同的部分神经元,可以阻止他们的阴谋,因此可以降低过拟合”
简单来说,dropout通过将部分的节点直接置0,强制迫使随机选择出来的神经元共同工作,削弱了神经元节点的联合适应性,增强泛化能力。
实际上dropout的作用在于增加了数据的扰动,我们知道方差的定义是由于数据发生变化导致的模型泛化性能的变动,而dropout直接通过部分节点置0的方式来主动产生干扰的数据,例如某一层的原始输出是[0.5,0.4,0.3,0.2,0.1]通过使用dropout之后变成了[0.5,0.4,0,0,0](此时0所对应的连接权重是不会发生更新的(具体原因可见之前写的为啥DNN的缺失值有时候可以置0的推导)而仅有存在值的输入所对应的连接权进行了更新),这样在一定程度上削弱了数据扰动的影响,因为我们常说的方差产生的原因就是原始数据的特征分布发生变化导致了模型输出发生了变化,输出变化越显著则方差越大,
现在我们假设一个极端的情况,假设所有原始数据都是:[0.5,0.4,0.3,0.2,0.1]经过训练之后模型整体的结构完全拟合了
[0.5,0.4,0.3,0.2,0.1]这个样本,那么当其中的部分数据发生变化,比如变成[1.5,1.4,0.3,0.2,0.1]则模型的输出很可能会发生巨大的变化,但是如果我们实现使用了dropout,
并且这里我们假设另一个极端,dropout丢弃的永远是0.5,0.4所对应的节点,那么整个网络相当于完全只通过[0.3,0.2,0.1]就完成了训练,此时,无论[0.5,0.4]这个位置上对应的数据怎么变化,神经网络的输出都不会发生显著变化,相对于[0.5,0.4]这两个特征而言,模型的方差趋于0。
当然现实情况不太可能如此极端,样本是不断变化的,dropout选择的节点也是不断变化的,不过理解意思就行了。
补充:我还真是没看出来dropout和bagging有什么关系,正常的bagging是对原始数据的抽样训练不同数据集下的基学习器然后进行简单平均,而dropout前后网络始终是同一个网络,输出也始终是同一个网络输出,并没有涉及到多个基学习器取平均的问题。
注意下文所说的层输出均为经过激活函数之后的输出。
机器之心:Dropout的前世与今生zhuanlan.zhihu.com![daf62764323e9bd691ee881350432714.png](https://i-blog.csdnimg.cn/blog_migrate/496c4c3cd84205b9da0f9db90f9f09f3.jpeg)
关于dropout的起源与发展可见上:
![9fcf22e98369f1f9e15091bec20584d2.png](https://i-blog.csdnimg.cn/blog_migrate/628253f7ae5a1e77b96be21e431a8522.jpeg)
emmm。。。。我觉得掌握常见的就好了。。。。
最早的dropout叫Vanilla dropout,举个例子好理解,
假设当前迭代过程中,dropout的drop rate为0.8,当前层的神经元有100个,在训练阶段因为每次只有占比20%的神经元,也就是20个神经元参与了训练,假设训练完毕之后,平均每个神经元的输出为10,则训练阶段每一层的期望输出为20*10=200。,但是在测试(预测)阶段,我们是不使用dropout方法的,而使用所有的神经元共同预测,此时该层的总期望输出为100*10=1000,显然模型的总期望输出发生改变了。。。这玩个屁啊,所以为了保证预测(测试)阶段的输出的期望不变,我们用1000*(1-dropout_rate)=200。
Vanilla dropout的问题在于根据每一层的dropout rate的不同,后续预测的时候还要对不同层的输出做调整很麻烦,所以后来诞生了inverted dropout,也是keras、tf等中实现的,最常用的dropout 方法了。
inverted dropout的思路很简单,以上面的例子为例,dropout的drop rate为0.8,当前层的神经元有100个,在训练阶段因为每次只有占比20%的神经元,也就是20个神经元参与了训练,假设训练完毕之后,平均每个神经元的输出为10,为了避免我们在测试(预测)的时候再引入对层输出的修改,我们把这一步移到训练阶段,方法就是我们在训练的时候对dropout之后的输出除以(1-dropout_rate),则200/(1-0.8)=1000,此时dropout层的原始输出还是不变的是200,但是我们在这里实际上等于在训练的时候加了一个rescale层使得其期望输出与测试(预测)阶段的期望输出保持一致,注意测试阶段是没有这个rescale层的。。。
1、Inverted-dropout把保持期望一致的关键步骤转移到了训练阶段,节省了测试(预测)阶段的步骤,提升了速度,这对于nn的产品化的用户体验比较重要。
2、dropout方法里的 keep_prob 是一个可能需要调节的超参数,用Inverted-dropout的情况下,当你要改变 keep_prob 的时候,只需要修改训练阶段的代码,而测试阶段的推断代码没有用到 keep_prob ,就不需要修改了,降低了写错代码的概率。
总之,一个是降测试阶段输出值使得训练和测试的期望输出一致,一个是提高训练阶段输出值使得训练和测试的期望输出一致。
dropout和伯努利分布的关系:
![ea3ee207ebcbba29a773b95ae246a13e.png](https://i-blog.csdnimg.cn/blog_migrate/5bba598c96eee53376f936dd9a7e3982.png)
dropout相当于给每一个神经元加入一个开关以伯努利分布的概率公式决定开或关。
![6619286c2ba7499e5f2e7cc16b13bd09.png](https://i-blog.csdnimg.cn/blog_migrate/c425b83b16be636f4fdaa6a9f206deb4.png)
dropconnect
Deep learning:四十六(DropConnect简单理解)www.cnblogs.com![44bb4eeeeb5e166c6e86712f83604557.png](https://i-blog.csdnimg.cn/blog_migrate/a72d0a0dbd15edb7781a8a0f7f1da273.jpeg)
完整来源于:
和dropout的思路不同,dropconnect,顾名思义,每一次迭代随机drop掉层之前的连接权,也就是将部分连接权直接置0,迫使网络在每个训练步骤中适应不同的连接
![475c4a13c710dffdca1c314a09b05c57.png](https://i-blog.csdnimg.cn/blog_migrate/c7f74f12a5aa6fec854f5d82d02f90cf.jpeg)
对比可以看到,dropout就是对某个隐藏层的输出进行随机置0,而dropconnect则是对于某个隐藏层的输入权重进行随机置0,一个是对输出置0一个是对输入置0,(这里需要注意的是,dropconnect对输入权重进行置0不是真的把权重w变成0,只是在计算上让它等于0而已。。。后续权重还是保持和上一轮一样的。。)
![500bd06b1ffe714ef5448bc04c5a4e6f.png](https://i-blog.csdnimg.cn/blog_migrate/11a853cd24184874b49d4b9709fb7779.jpeg)
注意:因为DropConnect只能用于全连接的网络层(和dropout一样),如果网络中用到了卷积,则用patch卷积时的隐层节点是不使用DropConnect的,因此上面的流程里有一个Extract feature步骤,该步骤就是网络前面那些非全连接层的传播过程,比如卷积+pooling.(看了半天没明白这里extract features是什么,原来是用来表示非全连接层的输出而已。。。)
上图表示训练阶段dropconnect的运行流程,很好理解,就是按照伯努利分布随机采样一个Mask矩阵,这个Mask矩阵中有0有1,然后Mask矩阵乘上原始的权重矩阵W,这样就起到了部分W置0的效果。然后进入激活函数,输出,反向传播。。。balabala,这里需要注意的地方是:
![c8ad51d9851eb3919106fedb5b47713f.png](https://i-blog.csdnimg.cn/blog_migrate/26af74c32be04c29bc82f11f24952bdc.png)
即使连接权置0还是会导致在计算的时候被置0的连接权的权重得到更新(这一点可以结合之前DNN的权重的梯度更新公式来看,即使W等于0它的值还是会得到更新具体可见:
马东什么:为什么DNN有时候可以把缺失值用0来代替?zhuanlan.zhihu.com按照这里的例子去推导一遍就知道了)
而dropconnect做的比较绝,既然训练阶段计算的时候这些被选中的W都是0,那么在梯度更新的时候也不允许有更新,因此这里把梯度更新矩阵与Mask矩阵相乘,使得那些被置0的连接全得到0的梯度更新,也就是不更新。。。
而在测试(预测)的时候,dropconnect会更加麻烦一些,暂时没看明白,回去看看《图解深度学习》好了,这里写的不是特别清晰。
Deep learning:四十六(DropConnect简单理解)www.cnblogs.com![44bb4eeeeb5e166c6e86712f83604557.png](https://i-blog.csdnimg.cn/blog_migrate/a72d0a0dbd15edb7781a8a0f7f1da273.jpeg)
![e1403c7122ca5140e5eeef93016521e7.png](https://i-blog.csdnimg.cn/blog_migrate/00967fdb577b8d77440090cdcdd8c003.jpeg)
这里假设对第l层隐层进行dropconnect,那么在预测(测试)的阶段:
我们以第l层的第k个神经元为例来说明:
假设第l层的第k个神经元与第l-1层之间一共有m个连接权,我们先计算这m个连接权的未激活输出u得到了一个未激活输出矩阵M,然后计算它的均值和方差。
然后,我们针对于m个连接权中的每一个权重w,进行高斯分布采样,并将采样的结果作为这一条连接权的输出替换原来正常计算得到的输出,然后进入激活函数再传递给第l层的第k个神经元。
这种方式的计算量很大,在github上找到了keras版的dropconnect的实现,没有使用到这么复杂的高斯采样的方法:
from keras.layers import Dense, Wrapper
import keras.backend as K
class DropConnect(Wrapper):
def __init__(self, layer, prob=1., **kwargs):
self.prob = prob
self.layer = layer
super(DropConnect, self).__init__(layer, **kwargs)
if 0. < self.prob < 1.:
self.uses_learning_phase = True
def build(self, input_shape):
if not self.layer.built:
self.layer.build(input_shape)
self.layer.built = True
super(DropConnect, self).build()
def compute_output_shape(self, input_shape):
return self.layer.compute_output_shape(input_shape)
def call(self, x):
if 0. < self.prob < 1.:
self.layer.kernel = K.in_train_phase(K.dropout(self.layer.kernel, self.prob) * (1-self.prob), self.layer.kernel)
self.layer.bias = K.in_train_phase(K.dropout(self.layer.bias, self.prob) * (1-self.prob), self.layer.bias)
return self.layer.call(x)
我觉得这种更合理更高效一些,按照论文里的设计的方法,每一个神经元的M个连接权还要去做高斯采样,计算复杂度太高了,很不方便。
这里实际上就是随机对权重w和偏执进行dropout操作而已,rescale之类的不需要做。
dropconnect由于drop的是输入而不是输出,所以不需要像dropout那样做一些额外的麻烦测试集的预测的后处理之类的。
target drop涉及到nn的模型压缩和修剪的问题,暂时不是很感兴趣。有空再研究吧