【NVCaffe源碼分析】SigmoidCrossEntropyLossLayer

    交叉熵 loss 用於度量兩個概率分佈之間的相似性(度量兩個概率分佈之間相似性的方法有很多,交叉熵是其中之一),放在我現在所做的深度學習具體項目中來說就是要度量模型預測出來的概率分佈與gt分佈之間的相似性。

其中\hat{p_{n}}爲預測概率分佈,是通過sigmoid函數將輸入x_{n}映射輸出爲1時的概率:

NVCaffe在實現SigmoidCrossEntropyLossLayer的時候理所當然地包含了Sigmoid和CrossEntropyLoss兩部分功能。其中Sigmoid部分直接調用了SigmoidLayer的代碼。

template <typename Ftype, typename Btype>
void SigmoidCrossEntropyLossLayer<Ftype, Btype>::LayerSetUp(
    const vector<Blob*>& bottom, const vector<Blob*>& top) {
  LossLayer<Ftype, Btype>::LayerSetUp(bottom, top);
  sigmoid_bottom_vec_.clear();
  sigmoid_bottom_vec_.push_back(bottom[0]);
  sigmoid_top_vec_.clear();
  sigmoid_top_vec_.push_back(sigmoid_output_.get()); //get what??
  sigmoid_layer_->SetUp(sigmoid_bottom_vec_, sigmoid_top_vec_);
}

template <typename Ftype, typename Btype>
void SigmoidCrossEntropyLossLayer<Ftype, Btype>::Reshape(
    const vector<Blob*>& bottom, const vector<Blob*>& top) {
  LossLayer<Ftype, Btype>::Reshape(bottom, top);
  CHECK_EQ(bottom[0]->count(), bottom[1]->count()) <<
      "SIGMOID_CROSS_ENTROPY_LOSS layer inputs must have the same count.";
  sigmoid_layer_->Reshape(sigmoid_bottom_vec_, sigmoid_top_vec_);
}

前向傳播

template <typename Ftype, typename Btype>
void SigmoidCrossEntropyLossLayer<Ftype, Btype>::Forward_cpu(
    const vector<Blob*>& bottom, const vector<Blob*>& top) {
  // The forward pass computes the sigmoid outputs.
  sigmoid_bottom_vec_[0] = bottom[0];
  sigmoid_layer_->Forward(sigmoid_bottom_vec_, sigmoid_top_vec_); //yep,在這裏計算了sigmoid
  // Compute the loss (negative log likelihood)
  const int count = bottom[0]->count();
  const int num = bottom[0]->num();
  // Stable version of loss computation from input data
  const Ftype* input_data = bottom[0]->cpu_data<Ftype>();
  const Ftype* target = bottom[1]->cpu_data<Ftype>();
  float loss = 0.F;
  for (int i = 0; i < count; ++i) {
    //注意,代碼裏面所用到的公式和標準公式不一樣
    loss -= input_data[i] * (target[i] - (input_data[i] >= 0.)) -
        log(1. + exp(input_data[i] - 2. * input_data[i] * (input_data[i] >= 0.)));
  }
  top[0]->mutable_cpu_data<Ftype>()[0] = loss / num;
}

    這裏bottom[0]->num()獲取的相當於是(NxCxHxW)中的N,即批量樣本的大小(Batch Size),而bottom[0]->count()則相當於N * C * H * W的結果。for循環裏面計算的是一個batch size的總的loss,所以最終要做一個平均。初看計算loss的代碼你可能會一臉懵逼,怎麼跟標準的sigmoid cross entropy計算公式不一樣。嗯,確實不一樣,但也只是轉換了一下形式。

e^{-x_{n}}很大時,有可能溢出,導致\frac{e^{-x_{n}}}{1+e^{-x_{n}}}的計算結果不準確。可以轉化成以下形式,這樣e的指數就總是負數:

所以,最終的交叉熵loss就變成了下面這個樣子:

這個跟代碼就基本上是一樣的了。

反向傳播

    在閱讀反向傳播的代碼之前先回顧一下交叉熵損失函數的導數的計算。對於Sigmoid函數而言,其輸入爲x_{n},輸出爲\hat{p_{n}}

根據鏈式法則的定義,交叉熵損失函數對於輸入x_{n}的導數爲:

對應反向傳播的代碼爲:

template <typename Ftype, typename Btype>
void SigmoidCrossEntropyLossLayer<Ftype, Btype>::Backward_cpu(
    const vector<Blob*>& top, const vector<bool>& propagate_down,
    const vector<Blob*>& bottom) {
  if (propagate_down[1]) {
    LOG(FATAL) << this->type()
               << " Layer cannot backpropagate to label inputs.";
  }
  if (propagate_down[0]) {
    // First, compute the diff
    const int count = bottom[0]->count();
    const int num = bottom[0]->num();
    const Btype* sigmoid_output_data = sigmoid_output_->cpu_data(); 
    const Btype* target = bottom[1]->cpu_data<Btype>();
    Btype* bottom_diff = bottom[0]->mutable_cpu_diff<Btype>();
    caffe_sub(count, sigmoid_output_data, target, bottom_diff); //y[i] = a[i] - b[i]
    // Scale down gradient
    const Btype loss_weight = top[0]->cpu_diff<Btype>()[0]; //1
    caffe_scal(count, Btype(loss_weight / num), bottom_diff);  //1/n
  }
}

其中loss_weight爲權重,注意它來自top[0],但是SigmoidCrossEntropyLoss已經Loss層,後面已經沒有別的層了。這裏只是取了一個初始化爲1.0的權重係數。最後的那個caffe_scale用於將bottom_diff中的值乘以1/N。

template <typename Dtype>
void caffe_scal(const int N, const Dtype alpha, Dtype *X);

template <>
void caffe_scal<float16>(const int N, const float16 alpha, float16 *X) {
  // cblas_hscal(N, alpha, X, 1); ?
  for (int i = 0; i < N; ++i) {
    X[i] = alpha * X[i];
  }
}

 

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