FaceNet原理和TensorFlow实现

写在前面:

人脸识别、验证、关键点检测是计算机视觉领域元老级的课题,前人提出了大量优秀的算法来实现在不同场景不同光照强度不同分辨率等situation下的人脸识别、验证或关键点检测。据博主所知,基于OpenCV的dlib是一个很优秀的开源库,当然还有很多优秀的算法和算法库,不过这不是本文的关注点,感兴趣的可以Google一下。(此处建议科学上网)

 

FaceNet简介:

FaceNet是Google提出的用于人脸识别(recognition,k-NN),验证(verification, two persons),聚类(clustering, find common people among these faces)。与用于分类的神经网络(MobileNet、GoogleNet、VGG16/19、ResNet等)不同的是FaceNet选择了一种更直接的端到端的学习和分类方式,同时生成更少的参数量。

先上一张FaceNet框架图,走一篇流程。

输入 Batch,有一个提取特征的深度神经网络(论文中用的是ZF-Net和Inceptio),然后对网络提取到的特征做 L2-normalization,之后通过embedding编码生成128d的向量,优化三元组损失函数得到最优模型。

embedding 的数学解释:f(x)\epsilon R^{d}, d = 128, x 是输入图像,\left \| f(x) \right \|_{2} = 1

Little-Tips:

基于softmax的分类网络需要固定尺寸的特征输入,由于卷积网络(如VGG16)的卷积核和Pooling Layer的尺寸都是固定的,所以这也就变相的限制了输入图像必须是固定的SIZE,如YOLO训练时用的是224*224,部署时扩大2*2,SSD基于VOC2007数据集是300*300,即对于任何一个已经训练好的基于soft Max分类的网络输入图像的尺寸都是唯一固定的。至于为什么可以减少参数量,由于soft Max接收的特征来自于全连接层(fc-layer),而fc-layer的参数比例是比较大的,通过在最后一层卷积输出加上全局平均池化(GAP)可以极大的减少参数量并且在测试阶段的准确率不会有大的浮动。如下图:

对于卷机层的输出不再进行flatten、fc1、fc2等直接对w*h的feature mapzuo做平均池化得到一个平均值,生成1*1*d的向量输入到soft Max分类层。这样做的优点是参数较少,训练迅速方便在移动端部署,缺点是特征丢失较多并且没有对特征进行非线性组合,容易关注于局部而不是全局。(扯远了。。。。)

FaceNet是通过学习一个固定维度的embedding 空间向量(128-D)each image 都有一个自己的向量表示,类似于哈希编码,然后通过计算向量间的距离来实现一系列的功能,用triplets-loss代替了soft Max,直接把图像映射到128d的embedding空间然后计算欧式距离,更直接和高效。在下面会有详细介绍。

然鹅:凡事皆有意外。

依然保留soft Max分类层,加入ROI Pooling、SPP-Net(The Spatial Pyramid Pooling Layer)中文可以理解成图像金字塔,有别于FPN(feature pyramid networks)特征金字塔,就可以输入不同尺寸的图像。会在以后的文章中探讨这些问题。

 

Triplet cost function

这个东西值得单独拿出来说,虽然三元组函数不是一个新概念但是谷歌创造性的把它用在了人脸这一领域,简单粗暴却又高效直接,可以说它是整个FaceNet的灵魂。

最大的优点就是一步到位,直接通过量化两张人脸通过网络映射成的128D向量之间的距离作出诸如验证、识别等任务。同时省却了其他人脸识别算法中人脸对齐操作。比如主干网络是经典的如VGG、GoogleNet等分类网络,在做人脸验证时要抽取fc_layer或者某一卷积层的featuremap作为人脸的特征然后做比对证明“你” 是“你”,非常的不直接不高效(特征较多),而facenet就没有这个问题。

先看个示意图:

 

从训练数据中抽取 A 的40张人脸图,然后选取其中一张作为 anchor,在选取另一张作为 positive,
从不是A的脸的图中选取一张作为 negative。
显然,我们希望通过网络训练得到最终实现

||anchor - positive|| < ||anchor - negative||

用数学表示:

对于所有的三元组,都要满足 anchor 与 positive 的距离加上\alpha(where α is a margin that is enforced between positive and negative pairs,即正样本和负样本之间必须存在的一个距离下限)小于 anchor 与 negative 的距离。

所以,FaceNet的 loss function 就可以写成如下的形式:

 

注意后面有一个 “+” ,等同于 max(f(x), 0), 结合论文理解,
如果 f(x) < 0 说明当前三元组是正确无需调整的 (no loss),
如果 f(x) > 0,则说明不满足限制条件也即有了 error ,
符合 loss function 的定义。

(2)式是网络的loss-function,也就是我们需要求解的最优化项,但是在此之前有一个很棘手的问题,如何在大量的正样本和负样本中选择 positive 和 negative 。当然你可以选择除 anchor 之外的所有 positive 以及 negative 列出所有可能的三元组,这样做计算量过大并且模型的收敛速度极慢,注意到 loss-function,如果三元组之间的关系满足公式(1),那么值为0也就是其对损失函数没有贡献,换言之真正起到优化损失函数的三元组是不满足公式(1)的。因此对于同一类样本也即来自于同一个人的人脸图像中给定一个 anchor ,计算其与剩下的所有 positive 的欧式距离然后选择最大值,同时计算 anchor 与 negative 欧式距离的最小值,只需要这个最大值小于最小值那么对所有的 positive 均可以与 negative 区分开也即正确分类。这个 positive 也被称为 hard-positive,这个 negative 叫 hard-negative,这样的三元组是 hard-triplets,也只有这样的三元组可以提升模型的学习能力。

在整个训练集上计算上述的最大值和最小值可能会导致模型欠拟和,因为 hard-positive 和 hard-negative 中会包含大量错误标签和人脸信息极少的图像,这会导致训练得到的模型不可用。论文中提出了两种解决算法:

(1)训练网络之前把样本分成 N 份,分别计算每一份样本的最大值和最小值(anchor 与 positive 欧式距离最大值,与negative 欧式距离最小值,选出 hard-positive/negative,下文的最大值、最小值都是这个意思),然后依次用这些数据训练网络,比如从第一份数据开始,训练完成后保持模型的参数,然后这些参数作为第二份数据训练模型时的初始化参数,依次类推。

(2)在每一个 mini-batch 中选 hard-positive/negative 。需要设置合适的 batch size(1800)

方法(2)更高效,论文中也是用的该方法,在每个 mini-batch 中为每个 ID 保证有 40张 positive,同时加入随机 negative faces。有一点不同的是论文中不只是用了 hard-positive 还用到了所有可能的 anchor-positive 组合,这样做模型更稳定,并且在训练时更易收敛。类似的如果只用 hardest-negatives 导致模型过早的收敛到不理想的局部最优点并且极有可能得到一个不可用的模型:f(x) = 0,为了解决这个问题论文采取了下面这种方式来挑选 negative。

这种方式选出的 negative 是 semi-hard,相比较 positive 它们离 anchor 足够远,但是它们与 anchor 的欧式距离非常接近 anchor-positive 的欧式距离,这些 negatives 分布在先验 positive 与 negative 距离 \alpha 之中的(Those negatives lie inside the margin α.),好的模型中它们一定是大于 \alpha 的。

算法的流程走完了,等等写实践的部分。

 

 

 

 

 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是使用 TensorFlow 实现 FaceNet 人脸识别的代码: ```python import tensorflow as tf import numpy as np import cv2 class FaceNet: def __init__(self, model_path): self.graph = tf.Graph() with self.graph.as_default(): sess_config = tf.ConfigProto() sess_config.gpu_options.allow_growth = True self.sess = tf.Session(config=sess_config) self.sess.run(tf.global_variables_initializer()) saver = tf.train.import_meta_graph(model_path + '.meta') saver.restore(self.sess, model_path) self.images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0") self.embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0") self.phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0") self.embedding_size = self.embeddings.get_shape()[1] def prewhiten(self, x): mean = np.mean(x) std = np.std(x) std_adj = np.maximum(std, 1.0 / np.sqrt(x.size)) y = np.multiply(np.subtract(x, mean), 1 / std_adj) return y def l2_normalize(self, x, axis=-1, epsilon=1e-10): output = x / np.sqrt(np.maximum(np.sum(np.square(x), axis=axis, keepdims=True), epsilon)) return output def calc_embeddings(self, images): prewhiten_images = [] for image in images: prewhiten_images.append(self.prewhiten(image)) feed_dict = {self.images_placeholder: prewhiten_images, self.phase_train_placeholder: False} embeddings = self.sess.run(self.embeddings, feed_dict=feed_dict) embeddings = self.l2_normalize(embeddings) return embeddings def calc_distance(self, feature1, feature2): return np.sum(np.square(feature1 - feature2)) def compare(self, image1, image2): feature1 = self.calc_embeddings([image1])[0] feature2 = self.calc_embeddings([image2])[0] distance = self.calc_distance(feature1, feature2) return distance if __name__ == '__main__': model_path = 'model/20180402-114759/model-20180402-114759.ckpt-275' facenet = FaceNet(model_path) img1 = cv2.imread('img1.jpg') img2 = cv2.imread('img2.jpg') distance = facenet.compare(img1, img2) print('Distance between img1 and img2:', distance) ``` 在运行代码之前,需要下载 FaceNet 模型。可以从 [这里](https://github.com/davidsandberg/facenet/tree/master/src/models) 下载预训练模型。将下载的模型文件夹放到代码中 `model_path` 的位置即可。 代码中定义了一个 `FaceNet` 类,通过 `calc_embeddings` 方法可以计算图像的 embedding 特征向量。然后通过 `calc_distance` 方法计算两幅图像的距离,最后得到的值越小说明两幅图像越相似。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nobrody

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值