Caffe 源码阅读实践 WeightedEuclideanLossLayer 的实现

需求

需要这个层的原因是在复现 DeepPose 的时候,paper中有这么一句

It should be noted, that the above objective can be used even if for some images not all joints are labeled. In this case, the corresponding terms in the sum would be omitted.

所以这个omitted 就把他当做 weighted 吧

参考

动手前先好好学习了下caffe的源码结构和数据类型

Caffe解读1 — Pinned Memory Vs. Non-Pinned Memory

Caffe解读3 — SyncedMemory

Caffe解读4 — Blob

Caffe解读5 — Layer

然后参考了

caffe中带权重的softmaxloss实现(一)

并且以下代码都是在EuclideanLossLayer的基础上修改的

分析

Forward

  • Computes the WeightedEuclidean (L2) loss

    $ E = \frac{1}{2N} \sum\limits_{n=1}^N ((\hat{y}_n – y_n) *(\hat{y}_n – y_n)) \cdot w_n $

    where $ * $ means element-wise multiply and $ \cdot $ means inner-product, for real-valued regression tasks.

  • Bottom input Blob vector (length 3)

    $ (N \times C \times H \times W) $ the predictions $ \hat{y} \in [-\infty, +\infty]$

    $ (N \times C \times H \times W) $ the targets $ y \in [-\infty, +\infty]$

    $ (N \times C \times H \times W) $ the weights $ w \in [-\infty, +\infty]$

  • Top output Blob vector (length 1) $ (1 \times 1 \times 1 \times 1) $

    the computed Euclidean loss:

    $ E = \frac{1}{2N} \sum\limits_{n=1}^N ((\hat{y}_n – y_n) * (\hat{y}_n – y_n)) \cdot w_n $

    where $ * $ means element-wise multiply and $ \cdot $ means inner-product

Backward

  • $ (N \times C \times H \times W) $ the predictions $\hat{y}$

    Backward fills their diff with gradients $ \frac{\partial E}{\partial \hat{y}} = \frac{1}{n} \sum\limits_{n=1}^N w_n * (\hat{y}_n – y_n)$ if propagate_down[0]

  • $ (N \times C \times H \times W) $ the targets $y$

    Backward fills their diff with gradients $ \frac{\partial E}{\partial y} = \frac{1}{n} \sum\limits_{n=1}^N w_n * (y_n – \hat{y}_n)$ if propagate_down[1]

源码

include/caffe/layers 中新建 weighted_euclidean_loss_layer 并写入

#ifndef CAFFE_WEIGHTED_EUCLIDEAN_LOSS_LAYER_HPP_
#define CAFFE_WEIGHTED_EUCLIDEAN_LOSS_LAYER_HPP_

#include <vector>

#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"

#include "caffe/layers/loss_layer.hpp"

namespace caffe {

/**
 * @brief Computes the WeightedEuclidean (L2) loss @f$
 *          E = \frac{1}{2N} \sum\limits_{n=1}^N ((\hat{y}_n - y_n) *
 *        (\hat{y}_n - y_n)) \cdot w_n @f$ where @f$ * @f$ means element-wise multiply
 *        and @f$ \cdot @f$ means inner-product, for real-valued regression tasks.
 *
 * @param bottom input Blob vector (length 3)
 *   -# @f$ (N \times C \times H \times W) @f$
 *      the predictions @f$ \hat{y} \in [-\infty, +\infty]@f$
 *   -# @f$ (N \times C \times H \times W) @f$
 *      the targets @f$ y \in [-\infty, +\infty]@f$
 *   -# @f$ (N \times C \times H \times W) @f$
 *      the weights @f$ w \in [-\infty, +\infty]@f$
 * @param top output Blob vector (length 1)
 *   -# @f$ (1 \times 1 \times 1 \times 1) @f$
 *      the computed Euclidean loss: @f$ E =
 *        \frac{1}{2N} \sum\limits_{n=1}^N ((\hat{y}_n - y_n) *
 *        (\hat{y}_n - y_n)) \cdot w_n @f @f$
 *      where @f$ * @f$ means element-wise multiply
 *        and @f$ \cdot @f$ means inner-product
 *
 * See include/caffe/layers/euclidean_loss_layer.hpp for more information
 */
template <typename Dtype>
class WeightedEuclideanLossLayer : public LossLayer<Dtype> {
 public:
  explicit WeightedEuclideanLossLayer(const LayerParameter& param)
      : LossLayer<Dtype>(param), diff_(), data_() {}
  virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual inline int ExactNumBottomBlobs() const { return 3; }

  virtual inline const char* type() const { return "WeightedEuclideanLoss"; }
  /**
   * Unlike most loss layers, in the WeightedEuclideanLossLayer we can backpropagate
   * to both inputs -- override to return true and always allow force_backward.
   */
  virtual inline bool AllowForceBackward(const int bottom_index) const {
    return true;
  }

 protected:
  /// @copydoc WeightedEuclideanLossLayer
  virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);

  /**
   * @brief Computes the Euclidean error gradient w.r.t. the inputs.
   *
   * Unlike other children of LossLayer, WeightedEuclideanLossLayer \b can compute
   * gradients with respect to the label inputs bottom[1] (but still only will
   * if propagate_down[1] is set, due to being produced by learnable parameters
   * or if force_backward is set). In fact, this layer is "commutative" -- the
   * result is the same regardless of the order of the two bottoms.
   *
   * @param top output Blob vector (length 1), providing the error gradient with
   *      respect to the outputs
   *   -# @f$ (1 \times 1 \times 1 \times 1) @f$
   *      This Blob's diff will simply contain the loss_weight* @f$ \lambda @f$,
   *      as @f$ \lambda @f$ is the coefficient of this layer's output
   *      @f$\ell_i@f$ in the overall Net loss
   *      @f$ E = \lambda_i \ell_i + \mbox{other loss terms}@f$; hence
   *      @f$ \frac{\partial E}{\partial \ell_i} = \lambda_i @f$.
   *      (*Assuming that this top Blob is not used as a bottom (input) by any
   *      other layer of the Net.)
   * @param propagate_down see Layer::Backward.
   * @param bottom input Blob vector (length 3)
   *   -# @f$ (N \times C \times H \times W) @f$
   *      the predictions @f$\hat{y}@f$; Backward fills their diff with
   *      gradients @f$
   *        \frac{\partial E}{\partial \hat{y}} =
   *            \frac{1}{n} \sum\limits_{n=1}^N w_n * (\hat{y}_n - y_n)
   *      @f$ if propagate_down[0]
   *   -# @f$ (N \times C \times H \times W) @f$
   *      the targets @f$y@f$; Backward fills their diff with gradients
   *      @f$ \frac{\partial E}{\partial y} =
   *          \frac{1}{n} \sum\limits_{n=1}^N w_n * (y_n - \hat{y}_n)
   *      @f$ if propagate_down[1]
   */
  virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
  virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

  Blob<Dtype> diff_;
  Blob<Dtype> data_;
};

}  // namespace caffe

#endif  // CAFFE_EUCLIDEAN_LOSS_LAYER_HPP_

src/caffe/layers 新建 weighted_euclidean_loss_layer.cpp 并写入

#include <vector>

#include "caffe/layers/weighted_euclidean_loss_layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {

template <typename Dtype>
void WeightedEuclideanLossLayer<Dtype>::Reshape(
  const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
  LossLayer<Dtype>::Reshape(bottom, top);
  CHECK_EQ(bottom[0]->count(1), bottom[1]->count(1))
      << "Inputs must have the same dimension.";
  CHECK_EQ(bottom[1]->count(1), bottom[2]->count(1))
      << "Inputs must have the same dimension.";
  diff_.ReshapeLike(*bottom[0]);
  data_.ReshapeLike(*bottom[0]);
}

template <typename Dtype>
void WeightedEuclideanLossLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
    const vector<Blob<Dtype>*>& top) {
  int count = bottom[0]->count();
  caffe_sub(
      count,
      bottom[0]->cpu_data(),
      bottom[1]->cpu_data(),
      data_.mutable_cpu_data());
  caffe_mul(
        count,
        data_.cpu_data(),
        bottom[2]->cpu_data(),
        diff_.mutable_cpu_data());
  Dtype dot = caffe_cpu_dot(count, data_.cpu_data(), diff_.cpu_data());
  Dtype loss = dot / bottom[0]->num() / Dtype(2);
  top[0]->mutable_cpu_data()[0] = loss;
}

template <typename Dtype>
void WeightedEuclideanLossLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
  for (int i = 0; i < 2; ++i) {
    if (propagate_down[i]) {
      const Dtype sign = (i == 0) ? 1 : -1;
      const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();
      caffe_cpu_axpby(
          bottom[i]->count(),              // count
          alpha,                              // alpha
          diff_.cpu_data(),                   // a
          Dtype(0),                           // beta
          bottom[i]->mutable_cpu_diff());  // b
    }
  }
}

#ifdef CPU_ONLY
STUB_GPU(WeightedEuclideanLossLayer);
#endif

INSTANTIATE_CLASS(WeightedEuclideanLossLayer);
REGISTER_LAYER_CLASS(WeightedEuclideanLoss);

}  // namespace caffe

新建 weighted_euclidean_loss_layer.cu 并写入

#include <vector>

#include "caffe/layers/weighted_euclidean_loss_layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {

template <typename Dtype>
void WeightedEuclideanLossLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
    const vector<Blob<Dtype>*>& top) {
  int count = bottom[0]->count();
  caffe_gpu_sub(
      count,
      bottom[0]->gpu_data(),
      bottom[1]->gpu_data(),
      data_.mutable_gpu_data());
  caffe_gpu_mul(
        count,
        data_.gpu_data(),
        bottom[2]->gpu_data(),
        diff_.mutable_gpu_data());
  Dtype dot;
  caffe_gpu_dot(count, data_.gpu_data(), diff_.gpu_data(), &dot);
  Dtype loss = dot / bottom[0]->num() / Dtype(2);
  top[0]->mutable_cpu_data()[0] = loss;
}

template <typename Dtype>
void WeightedEuclideanLossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
  for (int i = 0; i < 2; ++i) {
    if (propagate_down[i]) {
      const Dtype sign = (i == 0) ? 1 : -1;
      const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();
      caffe_gpu_axpby(
          bottom[i]->count(),              // count
          alpha,                              // alpha
          diff_.gpu_data(),                   // a
          Dtype(0),                           // beta
          bottom[i]->mutable_gpu_diff());  // b
    }
  }
}

INSTANTIATE_LAYER_GPU_FUNCS(WeightedEuclideanLossLayer);

}  // namespace caffe

然后 make 编译下就好了

使用

如下即可

layer {
  name: "loss"
  type: "WeightedEuclideanLoss"
  bottom: "predict"
  bottom: "label"
  bottom: "weight"
  top: "loss"
}

发表评论