先上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可能看不到對方的狀態。