Facenet细节剖析

paper facenet & mobilefacenet 实现细节剖析

人脸识别项目 细节剖析

1. 图片输入的大小是多少?最后的 embedding 是多少? 为什么?

图片输入大小设置为 160x160。why? 随着图片分辨率的增大,验证集的准确率会上升,但是对应的运算量也会显著上升。160 是一个相对合适的分辨率大小。

(facenet 原论文尝试了 40x40, 80x80, 112x112, 160x160, 256x256)

embedding 的大小为 128, 实验测得,当超过 128 时候,验证集的准确率已经没有上升,反而有所下降。(64 -> 128 -> 256 -> 512)。why? 128D向量拥有足够的容量hold 住大规模的人脸数据集(百万人脸级别), 并且相对紧凑。

2. 基本原理

选取三元组(anchor, pos, neg),其中,x和p是同一类,x和n是不同类。那么学习的过程就是学到一种表示,对于尽可能多的三元组,使得anchor和pos的距离,小于anchor和neg的距离

3. 主干网络:

原始论文中使用的是 googlenet、github上的facenet使用的 inception resnet v1、mobilefacenet 使用的是 mobilenet v2

(1) GoogleNet

原论文中使用的是 GoogleNet, Googlenet 的主要涉及在于以下几点:

  • Inception Module中包含3种不同尺寸的卷积和1个最大池化,增加了网络对不同尺度的适应性。

第一个分支对输入进行 1x1卷积,1x1卷积可以跨通道组织信息,提高网络的表达能力,同时可以对输出通道升维和降维

第二个分支先使用了 1x1 卷积,然后连接 3x3 卷积,相当于进行两次特征变换。

第三个分支和第二个分支类似,先是使用了1x1 的卷积,然后连接 5x5 的卷积。

最后一个分支则是3x3 最大池化后直接使用1x1卷积。

Inception Module 的4个分支在最后通过一个聚合操作合并。

  • 去除了最后的全连接层,用全局平均池化层来取代它。
(2) Mobilenet v2

另一篇论文则使用了 mobilenetv2 (输入变成112) 结构作为主要网络:主要修改点如下所示:

  • Inception 结构替换为 mobilenet v2 结构:

    mobilenet 的结构要点:(1)skip-connection 跳层连接 (2) 将传统卷积分解为逐通道卷积(先进行逐通道卷积但是通道之间不相加,然后使用1x1 卷积对通道进行整合), 并在前面加一个1x1 的卷积进行升维。(3) 将最后的 ReLU 替换为 Linear(非线性在高维有溢出,但是在低维不如线性好)

  • 用全局可分离卷积替代原有的全局池化层。why? 特征图上的中心点的感受野和边角的感受野是不同的,中心点的感受野包括了完整的图片,边角点的感知域却只有部分的图片, 不应该视为同等重要。这样会导致性能的下降。

  • 使用 arcface 替换掉 triplet 损失函数。

  • 小细节:通道扩张倍数变小(facenet 是 1024, mobilenets 则是 512);使用prelu代替relu;使用batch Normalization。

4. 预处理工作:

(1) 数据集的整理和清洗(DDM 智能猫眼[大约10W张,6千人的样子-> 猫眼300台,每台20人]的真实数据 和 CASIA 人脸数据)

  • 类间过滤: 清洗掉距离与类中心小于0.5 的负样本(对一个类别,先计算类中心,然后用所有样本,与其进行比较)
  • 图片的过滤:将经过人脸检测(MTCNN/FaceBoxes) 返回的人脸置信度 小于 0.75 的图片直接过滤掉

  • 按图像数量阈值进行数据划分(人脸低于10张的过滤掉)

  • 类间距离问题:有些可能是名字重合的,找出来合并在一起

(2) 人脸识别前的处理:

  • 人脸对齐?可以有也可以没有,影响不是很大 how?( 通过仿射变换将原本的五个landmark缩放旋转到固定的位置)

    from skimage import transform as trans
    # src
    src = np.array([
      [30.2946, 51.6963],
      [65.5318, 51.5014],
      [48.0252, 71.7366],
      [33.5493, 92.3655],
      [62.7299, 92.2041] ], dtype=np.float32 )
    if image_size[1]==112:
      src[:,0] += 8.0
      
    # dst
    dst = landmark.astype(np.float32)
    tform = trans.SimilarityTransform()
    tform.estimate(dst, src)
    M = tform.params[0:2,:]
    # warpAffine
    warped = cv2.warpAffine(img, M, (image_size[1],image_size[0]), borderValue = 0.0)
  • 模糊过滤?(将检测出来的人脸使用拉普拉斯做卷积运算,然后计算方差, 将方差小于 400 过滤掉)

  • 人脸置信度,这里设置置信度0.8,人脸置信度太小可能不是人脸,直接过滤掉。
  • 对图片进行标准化处理:(x-均值)/标准差
5. 后处理(如何进行分类?)

facenet 使用欧式距离来度量是否是一个人。使用十折交叉验证,我们设定阈值为 1.24。

mobilenet 使用相似性余弦度量:阈值设定为 0.7。

3

6. 参数调整:(没有怎么调整参数, 使用默认参数,替换掉了数据集而已)
  • 优化器:RMSProp

    tf.train.RMSPropOptimizer(learning_rate, decay=0.9, momentum=0.9, epsilon=1.0)
  • 初始学习率:0.1

  • 学习率下降方式:exponential_decay

    # Learning rate schedule: Maps an epoch number to a learning rate
    0:  0.1  # 初始学习率
    300: 0.01
    400: 0.001
    1000: 0.0001
  • margin α=0.2

7. why facenet ?
  • softmax不直接,(三元组直接优化距离),因⽽而性能也不不好。 softmax产⽣生的特征表示向量量都很⼤大,⼀一般超过1000维。

  • faceNet并没有像DeepFace和DeepID那样需要对⻬。

  • faceNet得到最终表示后不不⽤用像DeepID那样需要再训练模型进⾏分类,直接计算距离就好了了,简单⽽而有效。

8. 模型的大小:

线上模型: model_size: (Inception resnet v2: 186M) Param:7.5M

线下模型:(手机端) model_size: 4.1M param: 0.99M

8. 训练技巧
  • 三元组的选择
         在一个minibatch中,我们根据当时的 embedding,选择一次三元组,在这些三元组上计算triplet-loss,  再对embedding进行更新,不断重复,直到收敛或训练到指定迭代次数。
    

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!