DeepLearning(基於caffe)優化策略(2)--防擬合篇:Dropout

        DeepLearning,甚至機器學習,都或多或少的會出現過擬合的現象。防止過擬合,就成爲了一個大家普遍關注的話題。

        過擬合的原因,大致分爲這幾種:數據量過小、數據有噪聲、學習的網絡模型更復雜(例如:本來是二次的方程,如果過於擬合成三次甚至更高,導致訓練的loss特別小,測試時則不然,會比較大)。

一、什麼是dropout?

        Dropout是深度學習中一種防擬合的方法,是指在模型訓練時隨機讓網絡中某個隱含層結點的權重不工作(暫時將這些隱藏層的結點權值保留但不工作不更新權值,下次工作的時候再工作更新權值)

      二、公式推導

        

              公式如下:

        

         通過公式,可以看到,r就是隨機讓某些權值不起作用。

      三、分析爲什麼會收到這樣的效果

        詳細請見原文章:https://arxiv.org/pdf/1207.0580.pdf

        由於每次“丟棄”的部分節點是隨機出現的,因此這時候的權值更新不再依賴固定的樣式,(這時候讓我想到了隨機森林的算法思想),阻止某些特徵僅僅在特殊的特徵下起作用的情況。

      四、Caffe中的dropout_layer

// TODO (sergeyk): effect should not be dependent on phase. wasted memcpy.
#include <vector>
#include "caffe/layers/dropout_layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {

/*設置dropout層對象,先調用NeuronLayer類完成基本設置*/
template <typename Dtype>
void DropoutLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  NeuronLayer<Dtype>::LayerSetUp(bottom, top);
  /*protobuf文件中傳入的dropout的概率,也就是當前去除掉threshold_概率個數據不用*/
  /*因爲是有放回的隨機去除掉threshold_概率個數據,那麼每個數據被去除的概率爲threshold_*/
  threshold_ = this->layer_param_.dropout_param().dropout_ratio();
  DCHECK(threshold_ > 0.);
  DCHECK(threshold_ < 1.);
  scale_ = 1. / (1. - threshold_);
  uint_thres_ = static_cast<unsigned int>(UINT_MAX * threshold_);
}

/*形狀reshape和內存分配,同理先調用NeuronLayer類的Reshape函數完成基本的top與bottom數據的reshape*/
template <typename Dtype>
void DropoutLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  NeuronLayer<Dtype>::Reshape(bottom, top);
  //這個類要單獨分配一段內存用來存儲滿足伯努利分佈的隨機數
  rand_vec_.Reshape(bottom[0]->shape());
}

template <typename Dtype>
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();/*輸入數據blob個數*/
  if (this->phase_ == TRAIN) {/*當前處在測試階段*/
    // Create random numbers
    caffe_rng_bernoulli(count, 1. - threshold_, mask); /*產生伯努利隨機數*/
    for (int i = 0; i < count; ++i) {
      top_data[i] = bottom_data[i] * mask[i] * scale_;  /*遍歷每個數據在滿足伯努利分佈的下的輸出值*/
    }
  } else {
    caffe_copy(bottom[0]->count(), bottom_data, top_data); /*測試階段每個數據都要輸出*/
  }
}

template <typename Dtype>
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();/*輸入數據blob個數*/
      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);/*如果不是訓練就直接拷貝數據*/
    }
  }
}

#ifdef CPU_ONLY
STUB_GPU(DropoutLayer);
#endif

INSTANTIATE_CLASS(DropoutLayer);
REGISTER_LAYER_CLASS(Dropout);
}  // namespace caffe

      五、caffe中的應用

        在網絡配置中增加以下層:

layer {
  name: "drop1"
  type: "dropout"
  bottom: "ip1"
  top: "ip1"
  dropout_param {
    dropout_ratio: 0.5     //參數調整
  }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章