人脸识别合集 | 人脸识别损失函数(上)基于欧几里得距离

基于欧几里得距离的损失函数:Contrastive loss、Triplet loss、Center Loss+Softmax loss

Softmax loss适用于多分类,只具有可分离性(separable),即将类间分离,而没有判别性(discriminative),即不能类内聚合。而人脸识别需要有可分离性+判别性,可以泛化到对未知人脸的同类与不同类的分类,所以有了以下的损失函数。
在有监督的机器学习领域,通常有固定的类别,这时就可以使用基于softmax的交叉熵损失函数进行训练。但有时,类别是一个变量,此时使用triplet loss就能解决问题,triplet loss的优势在于细节区分,即当两个输入相似时,triplet loss能够更好地对细节进行建模,相当于加入了两个输入差异性差异的度量,学习到输入的更好表示。
siamese network, triplet network的输入都是成对或者triplet的,怎么对一个样本进行分类啊?神经网络的优势在于表示学习,自动的特征提取。所以,成对或者triplet的输入能让神经网络学到输入的更好的表示,后面再接svm, logtistic regression就可以分类了。

1 对比损失(Contrastive Loss)
Contrastive loss 最初源于Yann LeCun于2006年发表的 Dimensionality Reduction by Learning an Invariant Mapping,该损失函数原本主要是用于降维中,即本来相似的样本,在经过降维(特征提取)后,两个样本仍旧相似;而原本不相似的样本,在经过降维后,两个样本仍旧不相似。同样,该损失函数也可以很好的表达成对样本的匹配程度。
对比损失的定义

  • 对比损失,需要用成对样本X1和X2来训练,还需要标注Y标签是否相似。公式如下:
    • 有一点点像是Modified Huber Loss(结合了MSE、Hinge Loss)


其中Dw(X1,X2)代表两个成对样本特征X1和X2的欧氏距离(二范数),P 表示样本的特征维数,Y为两个样本是否匹配的标签,m为阈值。

  • 它可以很好的表达成对样本的匹配程度
    • Y=0,两个样本相似或匹配,红线,距离越大损失越大,横轴为欧式距离
    • Y=1,样本不相似,蓝线,距离越小损失越大
      • 当距离超过阈值margin的,则把其loss看做为0,即不相似的特征离的很远


对比损失的梯度
使用随机梯度下降来更新w,以得到较小loss,更好表达成对样本的匹配程度。计算loss梯度的公式如下:

  • Y=0,两个样本相似时,梯度为:
  • Y=1,两个样本不相似时,梯度为:


用弹簧模型类比
弹簧模型公式为:F=−K*X ,(F表示两点间弹簧的作用力,K是弹簧的劲度系数,X为弹簧拉伸或收缩的长度,弹簧静止状态时X=0)

  • (a) 相似是吸引力,蓝点与只吸引(attract-only) 弹簧连接到相似的点。
  • (b) 与相似对关联的损失函数及梯度
  • (c) 不相似是弹射力,蓝点仅与半径为m的圆内的非相似点用m-repulse-only弹簧连接
  • (d) 与非相似对关联的损失函数及梯度
  • (e) 一个点被不同方向的其他点拉动,形成平衡的情况


TensorFlow实现contrastive loss
输入图像对及它们的标签(相似或不相似),例如输入28x28x1的图像:

left = tf.placeholder(tf.float32, [None, 28, 28, 1])
right = tf.placeholder(tf.float32, [None, 28, 28, 1])
label = tf.placeholder(tf.int32, [None, 1]). # 0 if same, 1 if different
margin = 0.2

left_output = model(left)  # shape [None, 128]
right_output = model(right)  # shape [None, 128]

d = tf.reduce_sum(tf.square(left_output - right_output), 1)
d_sqrt = tf.sqrt(d)

loss = label * tf.square(tf.maximum(0., margin - d_sqrt)) + (1 - label) * d

loss = 0.5 * tf.reduce_mean(loss)


参考:参考1 TF实现contrastive loss和triplet loss


2 三元组损失(Triplet Loss)
Triplet loss源于google在2015发表的 FaceNet: A Unified Embedding for Face Recognition and Clustering,伴随FaceNet。
三元组损失的定义

  • 三元组损失:最小化锚点和具有相同的身份的正例之间的距离,并最大化锚点和不同身份的负例之间的距离。
  • 目标:
    • 相同标签的两个示例使其嵌入在嵌入空间中靠近在一起,不同标签的两个示例的嵌入距离要很远
    • 但不希望推动每个标签的训练嵌入到非常小的簇中。 唯一的要求是给出同一类的两个正例和一个负例,负例应该比正例的距离至少远margin。 这与SVM中使用的margin非常相似,这里希望每个类的簇由margin分隔。
  • 期望的三元组约束:
  • 三元组损失函数:
  • 梯度,是对3个样本向量的:
  • 需要将3个样本在嵌入空间表示成3个向量,图像经过特征提取再降维就是在嵌入空间上。
  • 在嵌入上定义三元组损失步骤:
    • 锚点(anchor)、与锚类别相同的正例(positive)、类别不同的负例(nagative)
    • 对嵌入空间的距离d,一个三元组(a,p,n)上的损失为:
      • 最小化该损失就是让锚点与正例的距离d(a,p)趋近0,并使锚点与负例的距离大于d(a,p)+间隔,即d(a,n)>d(a,p)+margin
  • 缺点:Triplet loss难于实现,所有的triplet组合太多了,都要训练效率低,所以要挑一些比较好的triplet进行训练

Triplet的选取

  • 如何选择triplet,如何用正负例构建triplet,对模型训练的效率有很大影响。easy negative example比较容易识别,没必要训练,否则会严重降低训练效率。若都采用hard negative example,又可能会影响训练效果。
  • Facenet论文中采用了随机的semi-hard negative构建triplet进行训练。
  • 基于negative example与anchor和positive距离,分为三类三元组:
    • 容易三元组(easy triplets):损失为0的三元组,因为d(a,n)>d(a,p)+margin
    • 困难三元组(hard triplets) :其中负例比正例更靠近锚点,即d(a,n)<d(a,p)
    • 半困难三元组(semi-hard triplets):其中负例不比正例更接近锚点,但仍有大于0的损失,d(a,p)<d(a,n)<d(a,p)+margin


Batch Hard Sampling
2017的《In Defense of the Triplet Loss for Person Re-Identification》提出batch hard的表现最好。

  • Offline triplet mining:离线triplet mining将所有的训练数据喂给神经网络,得到每一个训练样本的编码,根据编码计算得到negative example与anchor和positive example之间的距离,根据这个距离判断semi-hard triplets,hard triplets还是easy triplets。
    • 它仅选择了hard 或 semi-hard triplets,但不够高效,因为最初要把所有的训练数据喂给神经网络,而且每过1个或几个epoch,可能还要重新对negative examples进行分类。
  • Online triplet mining:Google提出了在线triplet mining的方法,即将B张图片(一个batch)喂给神经网络,得到B张图片的embedding。则triplet的组合一共最多B^3个triplets,其中包含很多没用的triplet(如三个negative examples和三个positive examples)。
    • valid triplets:一个triplet(B_i,B_j,B_k),样本i和j有相同的label且不是同一个样本,而样本k具有不同的label。就是一个正例两个负例或者一个负例两个正例。
    • 还需要用batch all或batch hard方法挑选出有效的triplet,假设一个batch的数据包含P*K张人脸,P个人,每人K张图片,如下:
    • batch all:选择所有的valid triplet,并对hard 和 semi-hard triplets上的loss进行平均。
      • 产生PK*(K-1)*(PK-K)个triplet,即PK个anchor,对于每个anchor有k-1个可能的positive example,PK-K个可能的negative examples
      • 但不用easy triplets,因为easy triplets的损失为0,平均会把整体损失缩小
    • batch hard:对于每一个anchor,选择hardest positive(距离anchor最远的positive example) 和 hardest negative(距离anchor最近的negative example)
      • 产生PK个triplet,这些triplet是最难分的

Triplet network
《Deep metric learning using Triplet network》指出Siamese network是双胞胎连体,Triplet network是三胞胎连体,,输入是三个,一个负例+两个正例,训练的目标是找到满足相同类别间的距离尽可能的小,让不同类别间的距离尽可能的大的向量。Triplet在cifar, mnist的数据集上效果都是很不错的。


TensorFlow实现triplet loss
输入三张图片 anchor, positive, negative,但不需要标签:
(1)offline triplets实现

anchor_output = ...  # shape [None, 128]
positive_output = ...  # shape [None, 128]
negative_output = ...  # shape [None, 128]

d_pos = tf.reduce_sum(tf.square(anchor_output - positive_output), 1)
d_neg = tf.reduce_sum(tf.square(anchor_output - negative_output), 1)

loss = tf.maximum(0., margin + d_pos - d_neg)
loss = tf.reduce_mean(loss)

(2)batch hard的实现方式

def batch_hard_triplet_loss(labels, embeddings, margin, squared=False):

"""Build the triplet loss over a batch of embeddings.
For each anchor, we get the hardest positive and hardest negative to form a triplet.
Args:
   labels: labels of the batch, of size (batch_size,)
   embeddings: tensor of shape (batch_size, embed_dim)
   margin: margin for triplet loss
   squared: Boolean. If true, output is the pairwise squared euclidean distance matrix. If false, output is the pairwise euclidean distance matrix.
Returns:
   triplet_loss: scalar tensor containing the triplet loss
"""

# Get the pairwise distance matrix
pairwise_dist = _pairwise_distances(embeddings, squared=squared)


# For each anchor, get the hardest positive
# First, we need to get a mask for every valid positive (they should have same label)
mask_anchor_positive = _get_anchor_positive_triplet_mask(labels)
mask_anchor_positive = tf.to_float(mask_anchor_positive)

# We put to 0 any element where (a, p) is not valid (valid if a != p and label(a) == label(p))
anchor_positive_dist = tf.multiply(mask_anchor_positive, pairwise_dist)

# shape (batch_size, 1)
hardest_positive_dist = tf.reduce_max(anchor_positive_dist, axis=1, keepdims=True)

# For each anchor, get the hardest negative
# First, we need to get a mask for every valid negative (they should have different labels)
mask_anchor_negative = _get_anchor_negative_triplet_mask(labels)
mask_anchor_negative = tf.to_float(mask_anchor_negative)

# We add the maximum value in each row to the invalid negatives (label(a) == label(n))
max_anchor_negative_dist = tf.reduce_max(pairwise_dist, axis=1, keepdims=True)
anchor_negative_dist = pairwise_dist + max_anchor_negative_dist * (1.0 - mask_anchor_negative)

# shape (batch_size,)
hardest_negative_dist = tf.reduce_min(anchor_negative_dist, axis=1, keepdims=True)

# Combine biggest d(a, p) and smallest d(a, n) into final triplet loss
triplet_loss = tf.maximum(hardest_positive_dist - hardest_negative_dist + margin, 0.0)

# Get final mean triplet loss
triplet_loss = tf.reduce_mean(triplet_loss)

return triplet_loss


参考:
参考1 TF代码 Olivier Moindrot caffe中triplet loss layer的实现 MNIST示例


3 中心损失(Center Loss)
Center Loss源于深圳先研院乔宇、Yandong Wen等在ECCV 2016上发表的 A Discriminative Feature Learning Approach for Deep Face Recognition
判别性

  • 深度学习的特征需要具有discriminative(判别性)和泛化能力,以便在没有标签预测的情况下识别新的未见类别,如一个人脸即便没有训练过也能判断类别。判别性同时表征了紧凑的类内差异和可分离的类间差异。
  • 判别性特征可以通过最近邻(NN)或k近邻(k-NN)算法进行良好分类,其不一定取决于标签预测。
  • 而softmax损失仅鼓励特征的可分离性,所得到的特征对于人脸识别不是足够有效的。
  • 对比损失和三元组损失分别构成图像对和三元组的损失函数,然而与原样本相比,训练对或三元组的数量急剧增加,导致了网络收敛缓慢。

中心损失的定义

  • 中心损失:为每一个类别提供一个类别中心,最小化min-batch中每个样本与该类别中心的距离,即缩小类内距离。
  • 有效地表征了深度特征的类内距离,提升深度特征的判别能力,在保持不同类别的特征可分离的同时最小化类内距离是关键。公式如下,c_yi就是第yi个类别的特征中心,xi表示全连接层之前的特征,m表示mini-batch的大小
  • LC相对于xi的梯度和c_yi的更新方程为:
    • 如果条件满足则δ(条件)=1,否则δ(条件)= 0,即只有当yi(表示yi类别)和cj的类别j一样的时候cj才需要更新。
    • center可使用"xavier"进行初始化,然后在每个mini-batch迭代后在当前类别中更新一次。;每次迭代过程中,只对对应类别的特征取平均计算,相当于让cj是向x的平均移动。
    • 为了避免少样本类别造成的较大干扰,采用一个因子α来控制类别中心的学习率。
  • 用softmax损失+中心损失的联合监督来训练CNN进行判别性特征学习,可以获得用于鲁棒的人脸识别的高判别性特征。标量λ用于平衡两个损失函数,则总的损失函数为:
    • softmax可以让不同类的深度特征分开;center loss可以将同一类的特征吸引到类中心。两者结合不仅可以扩大不同类的特征区别,还可以减小同一类的特征的区别。
  • 判别性特征的学习算法步骤:
  • λ对于softmax loss与center loss联合监督下深度特征分布的影响:
    • λ越大center loss比重越大,类内越聚合,判别能力越大。不同颜色的点表示来自不同类别的深度特征,白色的圆点(c0,c1,...,c9) 分别表示 10 类深度特征的类别中心.
  • 优点:
    • 容易实现:梯度方程和更新方程易于推导,得到的CNN模型是可训练的。
    • 容易训练:Centers的更新基于可调学习率的mini-batch
    • 容易输入:中心损失与softmax loss具有相同的训练数据格式,不需要复杂的样本挖掘和重组,直接在特征输出层中引入它即可。
      • 这在对比损失和三元组损失中是不可避免的,由于预先收集所有可能的训练测试标识是不切实际的,因此CNNs中的标签预测并不总是适用。需要对深度学习的特性进行足够的一般化,以便在没有标签预测的情况下识别新的未知类。
    • 容易收敛:在联合监督下,DeepIDNet由0.7M人脸图像训练,在14小时内迭代28k收敛。
  • 缺点:另一个角度上说,center loss采取的是在训练过程中用空间换取时间的策略

基于Center Loss + Softmax Loss的人脸识别网络
三个local convolution层的权值分别在4×4、2×2和1×1区域进行局部共享。将第4个池化层和第3个local convolution层连接起来作为第1个全连接层的输入。全连通层的输出尺寸为512。


本论文在Wild (LFW)和Youtube Face (YTF)上的测试,我们用λ=0.003和α=0.5的模型C,有更少的0.7M的训练数据和更简单的网络架构。模型A仅受softmax损失的监督,模型B受softmax loss + contrastive loss相结合进行监督,模型C的结果准确率更高,这说明在设计的神经网络中,中心损耗比对比损耗更有优势。但仍不如三元组准确率高。


Tensorflow实现Center loss

def center_loss(features, label, alfa, nrof_classes):
    nrof_features = features.get_shape()[1]
   # 训练过程中,需要保存当前所有类中心的全连接预测特征centers, 每个batch的计算都要先读取已经保存的centers
    centers = tf.get_variable('centers', [nrof_classes, nrof_features], dtype=tf.float32, initializer=tf.constant_initializer(0), trainable=False)
    label = tf.reshape(label, [-1])
    centers_batch = tf.gather(centers, label) # 获取当前batch对应的类中心特征

    diff = (1 - alfa) * (centers_batch - features) # 计算当前的类中心与特征的差异,用于Cj的的梯度更新,这里做了一个 1-alfa操作,比较奇怪,和原论文不同
    centers = tf.scatter_sub(centers, label, diff) # 更新梯度Cj,对于上图中步骤6,tensorflow会将该变量centers保留下来,用于计算下一个batch的centerloss

    loss = tf.reduce_mean(tf.square(features - centers_batch)) # 计算当前的centerloss 对应于Lc
    return loss, centers


参考:
作者源码: github.com/ydwen/caffe-
作者课件 参考1 caffe实现

转载请注明:zhuanlan.zhihu.com/Face
还可在我的知乎中查看其他系列专栏《CNN模型合集》《人脸识别合集》《目标检测合集》《CS231n深度视觉笔记》《OpenCV图像处理教程

编辑于 2019-08-03 17:08