CenterLoss的caffe实现与深入理解
目的:
打算将centerloss与SSD的multibox_loss融合,或者与faster-rcnn的softmax回归融合。
项目代码将会发布到:https://github.com/BOBrown
思路概述:
paper title:A Discriminative Feature Learning Approach for Deep Face Recognition
Centerloss发表于ECCV2016,研究内容是关于如何学习到更具判别力的特征,如类内聚较小的特征。
作者github : https://github.com/ydwen/caffe-face
文章: 如果arXiv下载慢可以去我的网盘下载, https://pan.baidu.com/s/1up_PWpR85HqVe10yhFzHoQ
这里引用文章中的截图来简要介绍文章思路。
上述图片显示了一个维度是2的特征向量在特征空间上的表现,第一张图是没有加入center loss的原始神经网络训练得到的特征向量的可视化结果。(之所以选用2维的向量是因为可视化方便)
加入center loss之后随着 增大,学习到的同一个类别的特征向量在空间距离就越靠近,因此类间聚就会增大。
数学方法与caffe实现:
1.forward
上述公式 是指sample个数,例如batch size=128那么 ,文章中说mini-batch的学习centerloss的方式更容易使centerloss收敛。 是第i个样本的center。
这里center的含义是:有多少个输出类别就会有多少个center向量,其中每一个center向量与 的维度相同。
当然,公式(2)也可以由向量内积的形式进一步写成为公式(3),其中
表示第i个样本的第j个维度的数值,
表示第i个样本的center是
类别的center,这个center的第j个维度:
那么实现中也是按照公式(3)来完成forward的计算:
template <typename Dtype>
void CenterLossLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
const Dtype* bottom_data = bottom[0]->cpu_data();
const Dtype* label = bottom[1]->cpu_data();
const Dtype* center = this->blobs_[0]->cpu_data();
Dtype* distance_data = distance_.mutable_cpu_data();//distance_这个blob保存x^i_j - c^{y_i}_j
// the i-th distance_data
for (int i = 0; i < M_; i++) {
const int label_value = static_cast<int>(label[i]);
// D(i,:) = X(i,:) - C(y(i),:)
caffe_sub(K_, bottom_data + i * K_, center + label_value * K_, distance_data + i * K_);//相减赋值给distance_data
}
Dtype dot = caffe_cpu_dot(M_ * K_, distance_.cpu_data(), distance_.cpu_data());//在M_ * K_维度进行内积,也就是平方和
Dtype loss = dot / M_ / Dtype(2); 归一化loss
top[0]->mutable_cpu_data()[0] = loss;
}
2.Backward
Backward负责计算bottom[0]->mutable_cpu_diff()和this->blob[0]_->mutable_cpu_diff(),后面这个也叫weights_diff,是带参网络层所特有的一个blob。
在center_loss_layer.cpp和.cu代码中,this->blobs_[0]就是指代center,因为center是通过样本学习到的参数。在Backward中需要考虑center这个参数的梯度以及对bottom_data的梯度。
(1)求对bottom data的梯度:
这个比较简单,按照公式(3),对
的偏导如下:
代码,需要注意top[0]->cpu_diff()[0]是惩罚项系数:
if (propagate_down[0]) {
caffe_copy(M_ * K_, distance_.cpu_data(), bottom[0]->mutable_cpu_diff());
caffe_scal(M_ * K_, top[0]->cpu_diff()[0] / M_, bottom[0]->mutable_cpu_diff());//此处top diff是loss weights,也就是惩罚项
}
(2) 求对weight diff的梯度:
公式:
解释上述公式:
表示第j个center向量,center向量的个数与类别数相关,有N个类别,就有N个center向量。
在梯度更新时,并不是更新全部的center向量,而是更新这个mini-batch抓取到的类别index对应的center向量,因此存在
的判断。分母是归一化梯度处理,本来是除以
就可以了。但是为了防止有的
向量没有出现在这次的mini-batch中,这样会导致分母为0,因此采用
的归一形式。
代码部分:
caffe_set(N_ * K_, (Dtype)0., variation_sum_.mutable_cpu_data());//variation_sum_是临时变量
for (int n = 0; n < N_; n++) {//N表示类别数,每一个c_j向量都需要进行梯度求解
int count = 0;
for (int m = 0; m < M_; m++) {
const int label_value = static_cast<int>(label[m]);
if (label_value == n) {
count++;
caffe_sub(K_, variation_sum_data + n * K_, distance_data + m * K_, variation_sum_data + n * K_);
}
}
caffe_axpy(K_, (Dtype)1./(count + (Dtype)1.), variation_sum_data + n * K_, center_diff + n * K_);
}
}
Centerloss的应用
centerloss在人脸识别的效果显著,但是我在训练的时候发现,先训练一个不带centerloss的人脸模型,之后基于这个pretrain model加入centerloss进行微调效果更佳好一点。
centerloss对于output number比较高的任务有较高的表现力。例如一般来说人脸识别的分类的output number=10000+,因此提高类内距是有必要的。但是在output number较低的任务中,例如cifar10/cifar100,表现力可能会下降。
参考文献:
A Discriminative Feature Learning Approach for Deep Face Recognition. ECCV2016