考虑MTCNN的训练场景,要求负样本只参与分类训练,而part样本只参与回归训练,正样本则要同时参与分类和回归训练,我们在数据集将不需要参与训练的部分标注为-1,如下图所示:
但是,如果直接使用这样的数据集进行训练的话,这些label=-1的样本也会参与计算梯度,这样显然是不对的。
那么,如何配置caffe使其忽略掉这些label=-1的数据呢?换句话说,我们需要caffe在回传梯度时将这些label=-1的样本梯度设置为0,这样,这些样本就不会参与训练了。
事实上,caffe中的LossLayer层提供了ignore_label参数,当该参数被设置时,程序会忽略ignore_label对应项的loss。caffe中所有loss层都是LossLayer层的子类,但并不是所有loss层都实现了ignore_label功能,SoftmaxWithLoss层可以直接使用这一功能,但EuclideanLoss层就没有这一功能。
我们的所有修改都在prototxt文件中的Loss层部分,对于训练过程中的其它步骤,和以前中提到的多标签分类/回归训练过程完 全 一 致。
一、SoftmaxWithLoss层的ignore_label
调用方法如下:
layer {
name: "loss_cls"
type: "SoftmaxWithLoss"
bottom: "conv4-1"
bottom: "label_cls"
top: "loss_cls"
loss_weight: 1
loss_param{
ignore_label: -1
}
}
显然,只需要在loss_param中添加一个ignore_label项就可以解决问题。
二、EuclideanLoss层的ignore_label
接下来,我们需要修改caffe源码,来为EuclideanLoss层添加ignore_label,添加后的调用方法应该和SoftmaxWithLoss层的调用方法完全一致。
总的来说,我们需要修改以下几个文件:
- 在EuclideanLoss层的基础上添加新层:EuclideanSpIgnoreLossLayer,将include/caffe/layers/euclidean_loss_layer.hpp、src/caffe/layers/euclidean_loss_layer.cpp和src/caffe/layers/euclidean_loss_layer.cu分别拷贝一份,命名为include/caffe/layers/euclidean_sp_ignore_loss_layer.hpp、src/caffe/layers/euclidean_sp_ignore_loss_layer.cpp和src/caffe/layers/euclidean_sp_ignore_loss_layer.cu。
- 修改include/caffe/layers/euclidean_sp_ignore_loss_layer.hpp文件
- 修改src/caffe/layers/euclidean_sp_ignore_loss_layer.cpp文件
- 修改src/caffe/layers/euclidean_sp_ignore_loss_layer.cu文件
接下来就是这些文件的修改版,其中,//###表示修改的部分,主要改动就是添加根据label将diff设置为0的代码:
1. include/caffe/layers/euclidean_sp_ignore_loss_layer.hpp:
在类声明中添加如下部分:
//###
/// Whether to ignore instances with a certain label.
bool has_ignore_label_;
/// The label indicating that an instance should be ignored.
int ignore_label_;
int num_nonzero_;
2. src/caffe/layers/euclidean_sp_ignore_loss_layer.cpp:
可以对照下面的文件进行修改。
#include <vector>
#include "caffe/layers/euclidean_sp_ignore_loss_layer.hpp"
#include "caffe/util/math_functions.hpp"
namespace caffe {
template <typename Dtype>
void EuclideanSpIgnoreLossLayer<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.";
diff_.ReshapeLike(*bottom[0]);
//###
has_ignore_label_ =
this->layer_param_.loss_param().has_ignore_label();
if (has_ignore_label_) {
ignore_label_ = this->layer_param_.loss_param().ignore_label();
}
}
template <typename Dtype>
void EuclideanSpIgnoreLossLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
int count = bottom[0]->count(); // batch-size * channels
int num = bottom[0]->num(); // batch-size
int channels = bottom[0]->channels(); // channels (4 or 10)
//###
caffe_sub( // 按元素相减
count,
bottom[0]->cpu_data(),
bottom[1]->cpu_data(),
diff_.mutable_cpu_data());
num_nonzero_ = num; // 找到需要被忽略的项,将diff置0
if (has_ignore_label_) {
const Dtype* label_data = bottom[1]->cpu_data();
Dtype* diff_data = diff_.mutable_cpu_data();
for (int i = 0; i < num; ++i) {
bool ignore = true;
for (int j = 0; j < channels; ++j) {
const int label_value = static_cast<int>(label_data[i * channels + j]);
if (label_value != ignore_label_)
ignore = false;
}
if (ignore) {
for (int j = 0; j < channels; ++j)
diff_data[i * channels + j] = 0;
num_nonzero_ --;
}
}
}
Dtype dot = caffe_cpu_dot(count, diff_.cpu_data(), diff_.cpu_data()); // 按元素相乘再相加
// Dtype loss = dot / bottom[0]->num() / Dtype(2);
Dtype loss = dot / Dtype(2.0*num_nonzero_); // 除以2N(非0项)
top[0]->mutable_cpu_data()[0] = loss;
}
template <typename Dtype>
void EuclideanSpIgnoreLossLayer<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();
const Dtype alpha = sign * top[0]->cpu_diff()[0] / num_nonzero_;
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(EuclideanSpIgnoreLossLayer);
#endif
INSTANTIATE_CLASS(EuclideanSpIgnoreLossLayer);
REGISTER_LAYER_CLASS(EuclideanSpIgnoreLoss);
} // namespace caffe
2. src/caffe/layers/euclidean_sp_ignore_loss_layer.cu:
可以对照下面的文件进行修改。
#include <vector>
#include "caffe/layers/euclidean_sp_ignore_loss_layer.hpp"
#include "caffe/util/math_functions.hpp"
namespace caffe {
template <typename Dtype>
void EuclideanSpIgnoreLossLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
int count = bottom[0]->count(); // batch-size * channels
int num = bottom[0]->num(); // batch-size
int channels = bottom[0]->channels(); // channels (4 or 10)
//###
caffe_gpu_sub( // 按元素相减
count,
bottom[0]->gpu_data(),
bottom[1]->gpu_data(),
diff_.mutable_gpu_data());
num_nonzero_ = num; // 找到需要被忽略的项,将diff置0
if (has_ignore_label_) {
const Dtype* label_data = bottom[1]->gpu_data();
Dtype* diff_data = diff_.mutable_gpu_data();
for (int i = 0; i < num; ++i) {
bool ignore = true;
for (int j = 0; j < channels; ++j) {
const int label_value = static_cast<int>(label_data[i * channels + j]);
if (label_value != ignore_label_)
ignore = false;
}
if (ignore) {
for (int j = 0; j < channels; ++j)
diff_data[i * channels + j] = 0;
num_nonzero_ --;
}
}
}
Dtype dot;
caffe_gpu_dot(count, diff_.gpu_data(), diff_.gpu_data(), &dot); // 按元素相乘再相加
// Dtype loss = dot / bottom[0]->num() / Dtype(2);
Dtype loss = dot / Dtype(2.0*num_nonzero_); // 除以2N(非0项)
top[0]->mutable_cpu_data()[0] = loss;
}
template <typename Dtype>
void EuclideanSpIgnoreLossLayer<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();
const Dtype alpha = sign * top[0]->cpu_diff()[0] / num_nonzero_;
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(EuclideanSpIgnoreLossLayer);
} // namespace caffe
到这里为止,程序就修改完成了,编译caffe,不出问题的话,此时EuclideanSpIgnoreLoss层就可以支持ignore_label的添加了。
调用方法如下:
layer {
name: "loss_box"
type: "EuclideanSpIgnoreLoss"
bottom: "conv4-2"
bottom: "label_box"
top: "loss_box"
loss_weight: 0.5
loss_param{
ignore_label: -1
}
}
意为:如果该层的所有输入标签均为-1,则忽略这个样本的loss。