先上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可能看不到对方的状态。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:caffe dropout解读 - Python技术站