Center Loss 是 2016 年 ECCV 的一篇人脸识别文章中加入的新损失函数。原作者是使用 Caffe 实现的,有很多人实现了各种版本的 Center Loss。但是我发现 github 上唯一的 Pytorch 版本并没有完全按照作者的方法来实现,我就打算修改一下。以下的思考都是在修改代码的过程中进行的
一、Center Loss 的原理
要实现 Center Loss,必须知道 Center Loss 的原理。
Center Loss 一般是和 Softmax Loss 配合使用的,Softmax Loss 可以使类间距离变大,而 Center Loss 可以使类内距离更小,下面的图片能很形象地表现出 Center Loss 的作用。
Center Loss 的流程大致如下:
-
保存一个参数,这个参数存储的是 feature 的中心值,我们定义成 centers_param
-
前传过程中计算输入的特征值 features 与存储的参数之间的均方误差(MSE)
-
反向传播时,feature 的梯度公式如下: 中心值的梯度是由作者定义的,公式如下:
这样就会导致,Center Loss 层输入 feature 的梯度很容易求,直接自动求导即可,但是,中心值的参数就需要手动更新了。
注:作者定义的是变化而不是梯度,所以不需要乘学习率,但是需要乘以作者指定的一个系数。为了方便说明可以简化看作梯度)
二、Pytorch 中的 backward
那么到底该怎样手动更新呢?要搞清楚这点首先要了解 Pytorch 中的backward
方法
我们看一下官方文档中backward
的基本用法
从文档可以看出:
- 当
variables
是标量时,不用指定grad_variables
(事实上,此时grad_variables
为 1),这种情况就是一般的loss.backward()
这种用法。 - 当 variables 为非标量且
require_grad
为True
时,需要指定grad_variables
,文档中对grad_variables
的解释为“gradient of the differentiated function w.r.t. corresponding variables”
,其实意思就是损失函数相对与variables
的梯度
因此backward
可以实现两种情况,一种是傻瓜式的,给一个loss
,backward
可以把所有前层require_grad=True
的梯度算出来;而另一种是从中间层开始往前算,这种就需要知道grad_variables
了。原理和链式法则一模一样。
三、实现 Center Loss
到现在,解决方案已经呼之欲出了:进行两次backward
即可。如图:
Center Loss 层有两个变量,进行loss.backward()
,两个变量都会求出对应的梯度。而centers_param.backward(man_set_centers_grad)
,则可以直接把man_set_centers_grad
赋值给variable
的梯度,也就是存储中心值的梯度。那么我们连续进行以上两个backward
,就可以实现想要的手动更新。不过需要注意的是,连续两次backward
,会把两次梯度累加。所以在第一次backward
后使用zero_grad()
方法,把梯度置零即可。