输入:
bottom[0]: NxKx1x1维,N为样本个数,K为类别数。是预测值。
bottom[1]: Nx1x1x1维, N为样本个数,类别为K时,每个元素的取值范围为[0,1,2,…,K-1]。是groundTruth。
输出:
top[0]: 1x1x1x1维, 求得是hingeLoss。
关于HingeLoss:
p: 范数,默认是L1范数,可以在配置中设置为L1或者L2范数。
:指示函数,如果第n个样本的真实label为k,则为1,否则为-1。
tnk: bottom[0]中第n个样本,第k维的预测值。
前向传播代码分析:
template
void HingeLossLayer::Forward_cpu(const vector*>& bottom,
const vector*>& top) {
const Dtype* bottom_data = bottom[0]->cpu_data(); //得到num个样本的dim个预测值
Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();//这是一个指针,我们会把数据放到对应的位置。
const Dtype* label = bottom[1]->cpu_data(); //得到num个样本的groundTruth
int num = bottom[0]->num();// num即batchsize N
int count = bottom[0]->count();// count就是占用的空间
int dim = count / num;//dim就是种类数k
caffe_copy(count, bottom_data, bottom_diff);
for (int i = 0; i < num; ++i) {
//label[i]中存储了第i个样本的真实class,取值范围[0,1,2,...,K-1]
//此处将第i个样本的K维预测值的label[i]处乘以-1相当于计算德尔塔(ln=k),因为只把label对应的位置乘以-1
bottom_diff[i * dim + static_cast(label[i])] *= -1;
}
for (int i = 0; i < num; ++i) {
for (int j = 0; j < dim; ++j) {
//计算hinge,即不管范数的部分的其他步骤以及做完,存入 bottom_diff,即bottom[0]->mutable_cpu_diff()中
bottom_diff[i * dim + j] = std::max( Dtype(0), 1 + bottom_diff[i * dim + j]);
}
}
Dtype* loss = top[0]->mutable_cpu_data();//存入top中,作为loss值输出
switch (this->layer_param_.hinge_loss_param().norm()) {
case HingeLossParameter_Norm_L1: //L1范数
loss[0] = caffe_cpu_asum(count, bottom_diff) / num;//算完l1 loss
break;
case HingeLossParameter_Norm_L2: //L2范数
loss[0] = caffe_cpu_dot(count, bottom_diff, bottom_diff) / num; //相乘,算完 l2 loss
break;
default:
LOG(FATAL) << "Unknown Norm";
}
}
正向还是比较好懂的,看看反向。
反向传播原理:
由于bottom[1]是groundtruth,不需要反传,只需要对bottom[0]进行反传,反传是损失E对t的偏导。
以L2范数为例,求偏导为:
反向传播源码分析:
template
void HingeLossLayer::Backward_cpu(const vector*>& top,
const vector& propagate_down, const vector*>& bottom) {
if (propagate_down[1]) {
LOG(FATAL) << this->type()
<< " Layer cannot backpropagate to label inputs.";
}
if (propagate_down[0]) {
Dtype* bottom_diff = bottom[0]->mutable_cpu_diff(); //说明中提到的hinge,在前向传播中已经计算出来了
const Dtype* label = bottom[1]->cpu_data();//groundtruth
int num = bottom[0]->num();//跟前面一样
int count = bottom[0]->count();
int dim = count / num;
for (int i = 0; i < num; ++i) {
//相当于求hinge*偏hinge/偏tnk部分
bottom_diff[i * dim + static_cast(label[i])] *= -1;
//那些预测正确部分,loss为0,乘以-1,则为0,预测错误,那么乘以-1.
//要知道,这里并没有特意提取预测值做比较,而是通过在正向传播中caffe_copy(count, bottom_data, bottom_diff);这条语句隐性地输入了这个数。
}
const Dtype loss_weight = top[0]->cpu_diff()[0];//这个是prototxt里面的用户自己给定的lossweight,表示一个scale,用于多loss function
switch (this->layer_param_.hinge_loss_param().norm()) {
case HingeLossParameter_Norm_L1: //L1部分反传
caffe_cpu_sign(count, bottom_diff, bottom_diff); //L1求导的结果: 正返回1 负返回-1 0返回0
caffe_scal(count, loss_weight / num, bottom_diff); //scale一下,后两者相乘赋给bottom_diff
break;
case HingeLossParameter_Norm_L2: //L2部分反传,就是scale一下
caffe_scal(count, loss_weight * 2 / num, bottom_diff);
break;
default:
LOG(FATAL) << "Unknown Norm";
}
}
}