SSD网络中的SmoothL1LossLayer层借鉴于Fast R-CNN,用于计算smooth L1损失,其中的光滑L1函数如下:
其导函数为:
之所以称为光滑L1函数,是因为此函数处处可导,而原L1函数在x=0处是不可导的。
smooth L1损失为:
其中
为标签向量;
为预测向量。
在补充完上述了理论知识后,我们开始解析源码。
该层没有单独在caffe.proto中指定参数,其传入的一些参数,如loss_weight均来自LayerParameter,大家自行参阅一下。
(1)smooth_L1_loss_layer.hpp头文件
// ------------------------------------------------------------------
// Fast R-CNN
// copyright (c) 2015 Microsoft
// Licensed under The MIT License [see fast-rcnn/LICENSE for details]
// Written by Ross Girshick
// Modified by Wei Liu
// ------------------------------------------------------------------
#ifndef CAFFE_SMOOTH_L1_LOSS_LAYER_HPP_
#define CAFFE_SMOOTH_L1_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 SmoothL1 loss as introduced in:@f$
* Fast R-CNN, Ross Girshick, ICCV 2015.
*/
template <typename Dtype>
class SmoothL1LossLayer : public LossLayer<Dtype> { //继承于LossLayer
public:
explicit SmoothL1LossLayer(const LayerParameter& param)
: LossLayer<Dtype>(param), diff_() {}
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top); //层建立函数
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual inline const char* type() const { return "SmoothL1Loss"; }
virtual inline int MinBottomBlobs() const { return 2; } //输入blob最少为2个
virtual inline int MaxBottomBlobs() const { return 3; } //输入blob最多为3个
/**
* Unlike most loss layers, in the SmoothL1LossLayer we can backpropagate
* to both inputs -- override to return true and always allow force_backward.
*/
//这里和caffe中的EuclideanLoss层一样(两个输入blob都能进行反向传播)
//可参见https://blog.csdn.net/qq_21368481/article/details/81950538
virtual inline bool AllowForceBackward(const int bottom_index) const {
return true;
}
protected:
/// @copydoc SmoothL1LossLayer
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);
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> errors_;
bool has_weights_;
};
} // namespace caffe
#endif // CAFFE_SMOOTH_L1_LOSS_LAYER_HPP_
(2)CPU实现:smooth_L1_loss_layer.cpp
// ------------------------------------------------------------------
// Fast R-CNN
// copyright (c) 2015 Microsoft
// Licensed under The MIT License [see fast-rcnn/LICENSE for details]
// Written by Ross Girshick
// Modified by Wei Liu
// ------------------------------------------------------------------
#include <vector>
#include "caffe/layers/smooth_L1_loss_layer.hpp"
#include "caffe/util/math_functions.hpp"
namespace caffe {
template <typename Dtype>
void SmoothL1LossLayer<Dtype>::LayerSetUp(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
LossLayer<Dtype>::LayerSetUp(bottom, top);
has_weights_ = (bottom.size() == 3); //是否有权重,其中bottom[2]存储的是bottom[0]中每一元素在smoothL1损失函数中所占的权重
}
template <typename Dtype>
void SmoothL1LossLayer<Dtype>::Reshape(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
LossLayer<Dtype>::Reshape(bottom, top);
//检查bottom[0]和bottom[1]维度是不是一致
CHECK_EQ(bottom[0]->channels(), bottom[1]->channels());
CHECK_EQ(bottom[0]->height(), bottom[1]->height());
CHECK_EQ(bottom[0]->width(), bottom[1]->width());
if (has_weights_) { //检查bottom[0]和bottom[2]维度是不是一致
CHECK_EQ(bottom[0]->channels(), bottom[2]->channels());
CHECK_EQ(bottom[0]->height(), bottom[2]->height());
CHECK_EQ(bottom[0]->width(), bottom[2]->width());
}
//diff_,errors_的大小和bottom[0]大小一致
diff_.Reshape(bottom[0]->num(), bottom[0]->channels(),
bottom[0]->height(), bottom[0]->width());
errors_.Reshape(bottom[0]->num(), bottom[0]->channels(),
bottom[0]->height(), bottom[0]->width());
}
template <typename Dtype>
void SmoothL1LossLayer<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(),
diff_.mutable_cpu_data()); //caffe_sub()实现逐元素相减
if (has_weights_) {
caffe_mul(
count,
bottom[2]->cpu_data(),
diff_.cpu_data(),
diff_.mutable_cpu_data()); // d := w * (b0 - b1) caffe_mul()实现逐元素相乘
}
const Dtype* diff_data = diff_.cpu_data();
Dtype* error_data = errors_.mutable_cpu_data();
for (int i = 0; i < count; ++i) {
Dtype val = diff_data[i];
Dtype abs_val = fabs(val);
//分段实现smoothL1损失的计算
if (abs_val < 1.) {
error_data[i] = 0.5 * val * val;
} else {
error_data[i] = abs_val - 0.5;
}
}
top[0]->mutable_cpu_data()[0] =
caffe_cpu_asum(count, errors_.cpu_data()) / bottom[0]->num(); //caffe_cpu_asum()计算所有元素的绝对值之和
}
template <typename Dtype>
void SmoothL1LossLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
int count = diff_.count();
Dtype* diff_data = diff_.mutable_cpu_data();
for (int i = 0; i < count; ++i) {
Dtype val = diff_data[i];
// f'(x) = x if |x| < 1
// = sign(x) otherwise
if (fabs(val) < 1.) {
diff_data[i] = val;
} else {
diff_data[i] = (Dtype(0) < val) - (val < Dtype(0)); //实现sign(x),即x>0时,sign(x)=1,x<0时,sign(x)=-1
}
}
for (int i = 0; i < 2; ++i) {
if (propagate_down[i]) {
const Dtype sign = (i == 0) ? 1 : -1; //对应于两个输入blob,取不同的数值
const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();
//caffe_cpu_axpby实现b=alpha*a+beta*b(a和b均为向量)
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(SmoothL1LossLayer);
#endif
INSTANTIATE_CLASS(SmoothL1LossLayer);
REGISTER_LAYER_CLASS(SmoothL1Loss);
} // namespace caffe
其中的bottom[2]是权重,举个例子如下:
这里的
组成的向量即为bottom[2],而
。
从代码中也可以看出,损失函数的导数为:
当propagate_down[0] = true时,
当propagate_down[1] = true时,
另外需要注意的是,这里的两个输入blob都可以反向传播的意思其实是该层可以允许预测值和标签放反,即允许
为预测向量,
为标签向量,但需要设置propagate_down[1] = true或设置force_backward。
(3)GPU实现:smooth_L1_loss_layer.cu
// ------------------------------------------------------------------
// Fast R-CNN
// copyright (c) 2015 Microsoft
// Licensed under The MIT License [see fast-rcnn/LICENSE for details]
// Written by Ross Girshick
// Modified by Wei Liu
// ------------------------------------------------------------------
#include <vector>
#include "caffe/layers/smooth_L1_loss_layer.hpp"
#include "caffe/util/math_functions.hpp"
namespace caffe {
//计算smooth L1值
template <typename Dtype>
__global__ void SmoothL1Forward(const int n, const Dtype* in, Dtype* out) {
// f(x) = 0.5 * x^2 if |x| < 1
// |x| - 0.5 otherwise
CUDA_KERNEL_LOOP(index, n) { //相当于for循环,只是是多线程的for循环
Dtype val = in[index];
Dtype abs_val = abs(val);
if (abs_val < 1) {
out[index] = 0.5 * val * val;
} else {
out[index] = abs_val - 0.5;
}
}
}
template <typename Dtype>
void SmoothL1LossLayer<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(),
diff_.mutable_gpu_data()); // d := b0 - b1
if (has_weights_) {
caffe_gpu_mul(
count,
bottom[2]->gpu_data(),
diff_.gpu_data(),
diff_.mutable_gpu_data()); // d := w * (b0 - b1)
}
// NOLINT_NEXT_LINE(whitespace/operators)
SmoothL1Forward<Dtype><<<CAFFE_GET_BLOCKS(count), CAFFE_CUDA_NUM_THREADS>>>(
count, diff_.gpu_data(), errors_.mutable_gpu_data()); //计算每一元素的smooth L1值
CUDA_POST_KERNEL_CHECK;
Dtype loss;
caffe_gpu_asum(count, errors_.gpu_data(), &loss); //和caffe_cpu_asum实现一样的功能
top[0]->mutable_cpu_data()[0] = loss / bottom[0]->num(); //注意输出blob调用的是cpu_data/cpu_diff(cpu和gpu的数据是同步的)
}
//计算smooth L1的导函数
template <typename Dtype>
__global__ void SmoothL1Backward(const int n, const Dtype* in, Dtype* out) {
// f'(x) = x if |x| < 1
// = sign(x) otherwise
CUDA_KERNEL_LOOP(index, n) {
Dtype val = in[index];
Dtype abs_val = abs(val);
if (abs_val < 1) {
out[index] = val;
} else {
out[index] = (Dtype(0) < val) - (val < Dtype(0));
}
}
}
template <typename Dtype>
void SmoothL1LossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
int count = diff_.count();
// NOLINT_NEXT_LINE(whitespace/operators)
SmoothL1Backward<Dtype><<<CAFFE_GET_BLOCKS(count), CAFFE_CUDA_NUM_THREADS>>>(
count, diff_.gpu_data(), diff_.mutable_gpu_data());
CUDA_POST_KERNEL_CHECK;
//计算损失函数的导数(梯度)
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实现一样的功能
caffe_gpu_axpby(
bottom[i]->count(), // count
alpha, // alpha
diff_.gpu_data(), // x
Dtype(0), // beta
bottom[i]->mutable_gpu_diff()); // y
}
}
}
INSTANTIATE_LAYER_GPU_FUNCS(SmoothL1LossLayer);
} // namespace caffe