本次介绍的人脸识别方法,其核心贡献就是如何加快相似度的学习速度,这里所谓的相似度和一般意义上的Triplet Loss很像,即:相同身份的人脸距离较近,不同身份的人脸距离较远。方法来源于:
《arxiv:Learning a Metric Embedding for Face Recognition using the Multibatch Method》
Introduction
很多人脸识别模型都是一种Metric或者Embeding模型,即相同身份的人脸距离较近,不同身份的人脸距离较远。这样有一个好处就是,模型除了用来做recognition之外还可以很方便的拿来做face verification或者face clustering.
基于深度神经网络的Embeding方法有很多,其不同之处可以归到3个方面:
(1)Loss Function
有的是直接比较两个目标相同还是不相同, 即 \(L[x_i, x_j]\);
有的使用的是最近很流行的Triplet Loss, 即\(L[x_{pos},x_{pos},x_{neg}]\)
(2)Network Architecture
网络的构建一直都很灵活,有得网络还需要对图片进行对齐等预处理。
(3)Classification Layer
有得直接训练一个端到端的Embedding网络;
有得是先按照普通的分类问题训练,然后再提取中间层特征再去train一个Embedding网络;
以著名的Google的FaceNet为例,在只挖掘hard三元组的情况下,多机训练了超过1个月。
因此,本文的目标就是降低训练时间
Learn a Metric
整个网络学习一个从输入\(x\)到输出\(f_w(x)\in\mathbb{R}^d\)的一个映射,学习规则如下:
\(y=y’ \Longrightarrow |f_w(x)-f_w(x’)|^2 < \theta-1\)
\(y\neq y’ \Longrightarrow |f_w(x)-f_w(x’)|^2 > \theta+1\)
现在,我们先据此定义one pair的Loss,其中训练集定义为:\({(x_i,y_i)_{i=1}^m},y_i为标签\)
\(l(w,\theta;x_i,x_j,y_{ij})=(1-y_{ij}(\theta-|f_w(x)-f_w(x’)|^2))_+\)
其中,\(y_{ij}\in{\pm1}\),+1表示\(x_i和x_j\)属于同一身份,另外\((u)_+:=max(u,0)\)。
则整体的Loss为:
\(L(w,\theta)=\frac{1}{m^2-m} \sum_{i\neq j\in [m]}l(w,\theta;x_i,x_j,y_{ij})\)
另一方面,文章从理论和实验上证明了上面的Loss比hinge-loss或者softmax-loss等多分类Loss要更难收敛。
所以,Google的Facenet中有很大一部分工作就是在如何选择和设计Triplet三元组,因此本文后面就参考该思想设计了新的训练方法。
The Multi-Batch Estimator
这一部分在原文中占比挺多,可惜感觉完全在水,总之实现的方法就是:
假如batch_size=K,那么两两配对的话总共会有\(K\times(K-1)\)种可能。(不过实际程序实现的时候,应该只有\(\frac{K\times(K-1)}{2}\),因为\((x_i,x_j)\)或者\((x_j,x_i)\)在BP时是一样的)。
于是,我们实际每一个batch中都进行类似遍历,最后将所有pair的loss加和即为一个batch的Loss。
原文中作者的实验参数配置如下:batch_size=256,共16个人每人16张图片; 训练图片2.6M; 模型大小为1.3M; 输入图像为112x112的RGB图像,编码长度128Bit; 学习率固定0.01,最后一个epoch降为0.001。
备注:
(1):训练图片来自互联网收集,其中有一定错误,采集办法来自于《Deep Face Recognition》,也是一篇很有启发性的方法。
(2):文中给出了详细的网络结构,该结构参考了NIN结构;
(3):输入图片需要先进行对齐,作者对齐时也采用了深度网络。
caffe 实现
我在caffe中实现了该层,命名为PairLoss.
#include <vector>
#include "caffe/layers/pair_loss_layer.hpp"
#include "caffe/util/math_functions.hpp"
namespace caffe {
template <typename Dtype>
void PairLossLayer<Dtype>::Reshape(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
LossLayer<Dtype>::Reshape(bottom, top);
CHECK_EQ(bottom[0]->num(), bottom[1]->count())
<<