caffe源碼閱讀《二》softmax層

前傳代碼

template <typename Dtype>
void SoftmaxLayer<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();
  Dtype* scale_data = scale_.mutable_cpu_data();
  int channels = bottom[0]->shape(softmax_axis_);
  int dim = bottom[0]->count() / outer_num_;
  caffe_copy(bottom[0]->count(), bottom_data, top_data);
  // We need to subtract the max to avoid numerical issues, compute the exp,
  // and then normalize.
  /*************這裏遍歷找出上一層的最大值*****************/
  for (int i = 0; i < outer_num_; ++i) {
    // initialize scale_data to the first plane
    caffe_copy(inner_num_, bottom_data + i * dim, scale_data);
    for (int j = 0; j < channels; j++) {
      for (int k = 0; k < inner_num_; k++) {
        scale_data[k] = std::max(scale_data[k],
            bottom_data[i * dim + j * inner_num_ + k]);
      }
    }
    /***找到最大值放在scale_data這個數組裏,一般一張圖片只有一個分類,這個數組其實只有一個數***/
    // subtraction
    caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels, inner_num_,
        1, -1., sum_multiplier_.cpu_data(), scale_data, 1., top_data);
    // exponentiation
    caffe_exp<Dtype>(dim, top_data, top_data);
    // sum after exp
    caffe_cpu_gemv<Dtype>(CblasTrans, channels, inner_num_, 1.,
        top_data, sum_multiplier_.cpu_data(), 0., scale_data);
    // division
    for (int j = 0; j < channels; j++) {
      caffe_div(inner_num_, top_data, scale_data, top_data);
      top_data += inner_num_;
    }
  }
}

由於softmax的前向算法是bottom層所有的元素作爲以e爲底的指數進行求和,例如有100個分類,那麼上面一個全連接層的輸出爲x1,x2,...x100。

那麼softmax輸出的是每個分類的概率,求法就是y_{i}=\frac{e^{xi}}{\sum_{j}^{n}e^{xj}},也就是要先求出每個輸入的e爲底的值,然後求和,每個的概率就是剛求的值/和。這樣保證了所有概率加起來肯定是1.

這裏由於要求指數函數,值可能會很大,所以caffe先找出最大的輸入,然後每個值減去它之後再求指數,因爲e爲底的指數函數是單調遞增的,並且輸入爲0的時候就是1了,所以我們減去最大的,也就能保證算出來的都是小於1的數,也就不用擔心溢出的問題了。

caffe_cpu_gemm 這個函數是調用的cblas裏算矩陣乘法的函數,caffe使用這個來做減法,也就是減去最大值。

然後caffe_exp求冪,也就是exp(xi)這一步

caffe_cpu_gemv來求和

然後caffe_div求每一項的概率,存入top_data中。

其中inner_num是指的每一張圖片對應的分類數,outer_num是batch_size,scale層原本是存儲過渡數據,也就是從上到下傳遞不改變的數據的,這裏scale_data則用來作爲臨時變量存儲中間結果。

 

**********************************************************分割線*****************************************************************************

反向代碼

計算loss

template <typename Dtype>
void SoftmaxWithLossLayer<Dtype>::Forward_cpu(
    const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
  // The forward pass computes the softmax prob values.
  softmax_layer_->Forward(softmax_bottom_vec_, softmax_top_vec_);
  const Dtype* prob_data = prob_.cpu_data();
  const Dtype* label = bottom[1]->cpu_data();
  int dim = prob_.count() / outer_num_;
  int count = 0;
  Dtype loss = 0;
  for (int i = 0; i < outer_num_; ++i) {
    for (int j = 0; j < inner_num_; j++) {
      const int label_value = static_cast<int>(label[i * inner_num_ + j]);
      if (has_ignore_label_ && label_value == ignore_label_) {
        continue;
      }
      DCHECK_GE(label_value, 0);
      DCHECK_LT(label_value, prob_.shape(softmax_axis_));
      loss -= log(std::max(prob_data[i * dim + label_value * inner_num_ + j],
                           Dtype(FLT_MIN)));
      ++count;
    }
  }
  top[0]->mutable_cpu_data()[0] = loss / get_normalizer(normalization_, count);
  if (top.size() == 2) {
    top[1]->ShareData(prob_);
  }
}

首先softmax的loss計算方法是

L=-\sum y_{i} * logs_{i}

其中si就是之前求得那個概率,yi其實只有兩個值,要麼是0要麼是1,也就是對應類別的概率。

那麼這個公式實際上就可以簡化成L = -logsi

也就是說正確的那個你預測的概率越大,也就是預測的越準確,損失越小。

這點在代碼裏很容易找到,其中有一點,caffe爲了防止預測概率爲0,設置了一個概率最小值,也就是FLT_MIN

1.17549435E-38F

這樣算出的loss是87.3365,所以我們訓練的時候經常會碰到這個。

這個原因其實是概率爲0了,之所以爲0不是因爲真的是0,而是之前的特徵太大了,exp()這個特徵就直接爆炸了,導致float成了一個很小的數。所以輸入數據一定要歸一化,學習率也不宜過大,不然也容易亂跳跳到一個很大的權值。

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