【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];
  }
}

 

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