先上caffe dropout_layer.cpp源码,如下:

// LayerSetUp 
DCHECK(threshold_ > 0.); 
DCHECK(threshold_ < 1.);
scale_ = 1. / (1. - threshold_); 
// forward 
void DropoutLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, 
        const vector<Blob<Dtype>*>& top) { 
    const Dtype* bottom_data = bottom[0]->cpu_data();
    Dtype* top_data = top[0]->mutable_cpu_data(); 
    unsigned int* mask = rand_vec_.mutable_cpu_data();
    const int count = bottom[0]->count();
    if (this->phase_ == TRAIN) {
        // 产生01掩码,伯努利随机数
      // Create random numbers
    caffe_rng_bernoulli(count, 1. - threshold_, mask);
      for (int i = 0; i < count; ++i) {
        // 丢弃部分置0,保留部分按inverted dropout需要放大scale_倍
        top_data[i] = bottom_data[i] * mask[i] * scale_;
      }
    } else {    // 测试阶段原样输出
    caffe_copy(bottom[0]->count(), bottom_data, top_data);
  }    
} 
//backward 
void DropoutLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down,
    const vector<Blob<Dtype>*>& bottom) {
  if (propagate_down[0]) {
    const Dtype* top_diff = top[0]->cpu_diff();
    Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();
    if (this->phase_ == TRAIN) {
      const unsigned int* mask = rand_vec_.cpu_data();
      const int count = bottom[0]->count();
      for (int i = 0; i < count; ++i) {
        bottom_diff[i] = top_diff[i] * mask[i] * scale_;
      }
    } else {
      caffe_copy(top[0]->count(), top_diff, bottom_diff);
    }
  }
}

## 原始的dropout的原理:
在训练时,每个神经单元以概率p被保留(dropout丢弃率为1-p);在测试阶段,每个神经单元都是存在的,权重参数w要乘以p,成为:pw。测试时需要乘上p的原因:考虑第一隐藏层的一个神经元在dropout之前的输出是x,那么dropout之后的期望值是$E=px+(1−p)0$ ,在测试时该神经元总是激活,为了保持同样的输出期望值并使下一层也得到同样的结果,需要调整$x→px$. 其中p是Bernoulli分布(0-1分布)中值为1的概率。

## Caffe中的dropout(inverted dropout)原理:
原始的dropout需要在测试阶段调整$x\rightarrow px$,这样会增加测试阶段的计算量,因此将缩放的过程转移到训练阶段来做,测试阶段与不使用dropout时相同,也就是***Inverted dropout***,训练时前向阶段保留下来的神经元的权重乘以1/p,因此在测试阶段本来需要调整的$x\rightarrow px$就变成了$p* \frac {1}{p}\rightarrow x$,也就是说测试阶段不需要变了。因此在代码里面添加inverted dropout只会影响到训练过程,不会影响测试过程。

可以看出在训练阶段,前向的过程是先产生一个伯努利随机数,做一个mask数组,用mask随机对输入数据做了一个“掩膜”,再做scale倍的缩放,而scale_ = 1. / (1. - threshold_); threshold默认0.5,这里scale就是2,给输出的data每个元素的值扩大2倍。

整理一下,在训练阶段的前向传递时,输入是一个向量,做一个同样维度的mask向量,用伯努利分布给mask赋值以后,mask中就是0和1的元素。此时用data[i]\*mask[i]就相当于让一部分神经元失活,但是这样会造成输出值范围变化,因此设置scale = 1/(1-ratio),有data[i]\*mask[i]\*scale。

## AlphaDropout
Alpha Dropout是一种保持输入均值和方差不变的Dropout,该层的作用是通过缩放和平移使得在dropout时也保持数据的自规范性。Alpha Dropout与SELU激活函数配合较好。更多细节参考论文 Self-Normalizing Neural Networks.

## 意义与理解
//来自知乎
dropout掉不同的隐藏神经元就类似在训练不同的网络,随机删掉一半隐藏神经元导致网络结构已经不同,整个dropout过程就相当于对很多个不同的神经网络取平均。而不同的网络产生不同的过拟合,一些互为“反向”的拟合相互抵消就可以达到整体上减少过拟合。
减少神经元之间复杂的共适应关系: 因为dropout程序导致两个神经元不一定每次都在一个dropout网络中出现。(这样权值的更新不再依赖于有固定关系的隐含节点的共同作用,阻止了某些特征仅仅在其它特定特征下才有效果的情况)。 迫使网络去学习更加鲁棒的特征 (这些特征在其它的神经元的随机子集中也存在)。换句话说假如我们的神经网络是在做出某种预测,它不应该对一些特定的线索片段太过敏感,即使丢失特定的线索,它也应该可以从众多其它线索中学习一些共同的模式(鲁棒性)。(这个角度看 dropout就有点像L1,L2正则,减少权重使得网络对丢失特定神经元连接的鲁棒性提高)

(还有一个比较有意思的解释是,Dropout类似于性别在生物进化中的角色:物种为了生存往往会倾向于适应这种环境,环境突变则会导致物种难以做出及时反应,性别的出现可以繁衍出适应新环境的变种,有效的阻止过拟合,即避免环境改变时物种可能面临的灭绝。 当地球都是海洋时,人类是不是也进化出了再海里生活的能力呢?)
//以上来自知乎
取平均和集成学习中的bagging有点类似。学习多个网络,最后投票决定,只是这种方式更加高效,简洁。
减少神经元之间复杂的共适应关系,这一点则是在dropout过程中,会避免co-adapted,就是说这种机制能够避免unit_a unit_b两个的共同自适应,换句话说有可能b的状态会参考a的现在的状态,而dropout会使得a和b可能看不到对方的状态。