聪明的人脸识别1——Keras 搭建自己的Facenet人脸识别平台

学习前言

最近又学了我最喜欢的retinaface,以前也了解过facenet,但是没有训练过,可以去学习一下!
在这里插入图片描述

什么是Facenet

谷歌人脸识别算法,发表于 CVPR 2015,利用相同人脸在不同角度等姿态的照片下有高内聚性,不同人脸有低耦合性,提出使用 cnn + triplet mining 方法,在 LFW 数据集上准确度达到 99.63%。

通过 CNN 将人脸映射到欧式空间的特征向量上,实质上:不同图片人脸特征的距离较大;通过相同个体的人脸的距离,总是小于不同个体的人脸这一先验知识训练网络。

测试时只需要计算人脸特征EMBEDDING,然后计算距离使用阈值即可判定两张人脸照片是否属于相同的个体。
在这里插入图片描述
简单来讲,在使用阶段,facenet即是:
1、输入一张人脸图片
2、通过深度卷积网络提取特征
3、L2标准化
4、得到一个长度为128特征向量。

源码下载

https://github.com/bubbliiiing/facenet-keras

Facenet的实现思路

一、预测部分

1、主干网络介绍

在这里插入图片描述
facenet的主干网络起到提取特征的作用,原版的facenet以Inception-ResNetV1为主干特征提取网络。

本文一共提供了两个网络作为主干特征提取网络,分别是mobilenetv1和Inception-ResNetV1,二者都起到特征提取的作用,为了方便理解,本博文中会使用mobilenetv1作为主干特征提取网络。

MobilenetV1模型是Google针对手机等嵌入式设备提出的一种轻量级的深层神经网络,其使用的核心思想便是depthwise separable convolution(深度可分离卷积块)。

深度可分离卷积块由两个部分组成,分别是深度可分离卷积和1x1普通卷积,深度可分离卷积的卷积核大小一般是3x3的,便于理解的话我们可以把它当作是特征提取,1x1的普通卷积可以完成通道数的调整。

下图为深度可分离卷积块的结构示意图:
在这里插入图片描述
深度可分离卷积块的目的是使用更少的参数来代替普通的3x3卷积。

我们可以进行一下普通卷积和深度可分离卷积块的对比:

对于普通卷积而言,假设有一个3×3大小的卷积层,其输入通道为16、输出通道为32。具体为,32个3×3大小的卷积核会遍历16个通道中的每个数据,最后可得到所需的32个输出通道,所需参数为16×32×3×3=4608个。

对于深度可分离卷积结构块而言,假设有一个深度可分离卷积结构块,其输入通道为16、输出通道为32,其会用16个3×3大小的卷积核分别遍历16通道的数据,得到了16个特征图谱。在融合操作之前,接着用32个1×1大小的卷积核遍历这16个特征图谱,所需参数为16×3×3+16×32×1×1=656个。

可以看出来深度可分离卷积结构块可以减少模型的参数。

如下就是MobileNet的结构,其中Conv dw就是分层卷积,在其之后都会接一个1x1的卷积进行通道处理,
在这里插入图片描述

在这里插入图片描述

import math
import numpy as np
import tensorflow as tf

from keras import backend
from keras import backend as K
from keras.preprocessing import image
from keras.models import Model
from keras.layers.normalization import BatchNormalization
from keras.layers import Conv2D, Add, ZeroPadding2D, GlobalAveragePooling2D, Dropout, Dense, Lambda
from keras.layers import MaxPooling2D,Activation,DepthwiseConv2D,Input,GlobalMaxPooling2D
from keras.applications import imagenet_utils
from keras.applications.imagenet_utils import decode_predictions
from keras.utils.data_utils import get_file

def _conv_block(inputs, filters, kernel=(3, 3), strides=(1, 1)):
    x = Conv2D(filters, kernel,
               padding='same',
               use_bias=False,
               strides=strides,
               name='conv1')(inputs)
    x = BatchNormalization(name='conv1_bn')(x)
    return Activation(relu6, name='conv1_relu')(x)


def _depthwise_conv_block(inputs, pointwise_conv_filters,
                          depth_multiplier=1, strides=(1, 1), block_id=1):

    x = DepthwiseConv2D((3, 3),
                        padding='same',
                        depth_multiplier=depth_multiplier,
                        strides=strides,
                        use_bias=False,
                        name='conv_dw_%d' % block_id)(inputs)

    x = BatchNormalization(name='conv_dw_%d_bn' % block_id)(x)
    x = Activation(relu6, name='conv_dw_%d_relu' % block_id)(x)

    x = Conv2D(pointwise_conv_filters, (1, 1),
               padding='same',
               use_bias=False,
               strides=(1, 1),
               name='conv_pw_%d' % block_id)(x)
    x = BatchNormalization(name='conv_pw_%d_bn' % block_id)(x)
    return Activation(relu6, name='conv_pw_%d_relu' % block_id)(x)

def relu6(x):
    return K.relu(x, max_value=6)

def MobileNet(inputs, embedding_size=128, dropout_keep_prob=0.8, alpha=1.0, depth_multiplier=1):
    x = _conv_block(inputs, 32, strides=(2, 2))
    x = _depthwise_conv_block(x, 64, depth_multiplier, block_id=1)

    x = _depthwise_conv_block(x, 128, depth_multiplier, strides=(2, 2), block_id=2)
    x = _depthwise_conv_block(x, 128, depth_multiplier, block_id=3)

    x = _depthwise_conv_block(x, 256, depth_multiplier, strides=(2, 2), block_id=4)
    x = _depthwise_conv_block(x, 256, depth_multiplier, block_id=5)

    x = _depthwise_conv_block(x, 512, depth_multiplier, strides=(2, 2), block_id=6)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=7)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=8)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=9)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=10)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=11)

    x = _depthwise_conv_block(x, 1024, depth_multiplier, strides=(2, 2), block_id=12)
    x = _depthwise_conv_block(x, 1024, depth_multiplier, block_id=13)

2、根据初步特征获得长度为128的特征向量

在这里插入图片描述
利用主干特征提取网络我们可以获得一个特征层,它的shape为(batch_size, h, w, channels),我们可以将其取全局平均池化,方便后续的处理(batch_size, channels)。

我们可以将平铺后的特征层进行一个神经元个数为128的全连接此时我们相当于利用了一个长度为128的特征向量代替输入进来的图片。这个长度为128的特征向量就是输入图片的特征浓缩。

x = GlobalAveragePooling2D()(x)
x = Dropout(1.0 - dropout_keep_prob, name='Dropout')(x)
x = Dense(classes, use_bias=False, name='Bottleneck')(x)
x = BatchNormalization(momentum=0.995, epsilon=0.001, scale=False,
                      name='BatchNorm_Bottleneck')(x)

3、l2标准化

在这里插入图片描述
在获得一个长度为128的特征向量后,我们还需要进行l2标准化的处理。

这个L2标准化是为了使得不同人脸的特征向量可以属于同一数量级,方便比较。

在进行l2标准化前需要首先计算2-范数:
∣ ∣ x ∣ ∣ 2 = ∑ i = 1 N x i 2 ||\textbf{x}||_2 =\sqrt{\sum_{i=1}^Nx_i^2} x2=i=1Nxi2 ,也就是欧几里得范数,即向量元素绝对值的平方和再开方。

L2标准化就是每个元素/L2范数;

在keras代码中,只需要一行就可以实现l2标准化的层。

x= Lambda(lambda  x: K.l2_normalize(x, axis=-1))(x)
# 创建模型
model = Model(inputs, x, name='inception_resnet_v1')

到这里,我们输入进来的图片,已经变成了一个经过l2标准化的长度为128的特征向量了!

4、构建分类器(用于辅助Triplet Loss的收敛)

当我们完成第三步后,我们已经可以利用这个预测结果进行训练和预测了。

但是由于仅仅只是用Triplet Loss会使得整个网络难以收敛,本文结合Cross-Entropy Loss和Triplet Loss作为总体loss。

Triplet Loss用于进行不同人的人脸特征向量欧几里得距离的扩张,同一个人的不同状态的人脸特征向量欧几里得距离的缩小。
Cross-Entropy Loss用于人脸分类,具体作用是辅助Triplet Loss收敛。

想要利用Cross-Entropy Loss进行训练需要构建分类器,因此对第三步获得的结果再次进行一个全连接用于分类。

构建代码如下,当我们在进行网络的训练的时候,可使用分类器辅助训练,在预测的时候,分类器是不需要的:

def facenet(input_shape, num_classes=None, backbone="mobilenet", mode="train"):
    inputs = Input(shape=input_shape)
    if backbone=="mobilenet":
        model = MobileNet(inputs)
    elif backbone=="inception_resnetv1":
        model = InceptionResNetV1(inputs)
    else:
        raise ValueError('Unsupported backbone - `{}`, Use mobilenet, inception_resnetv1.'.format(backbone))

    if mode == "train":
        x = Dense(num_classes)(model.output)
        x = Activation("softmax", name = "Softmax")(x)
        combine_model = Model(inputs,[x, model.output])
        return combine_model
    elif mode == "predict":
        return model
    else:
        raise ValueError('Unsupported mode - `{}`, Use train, predict.'.format(mode))

二、训练部分

1、数据集介绍

我们使用的数据集是CASIA-WebFace数据集,我已经对其进行了预处理,将其属于同一个人的图片放到同一个文件夹里面,并且进行了人脸的提取和人脸的矫正。
在这里插入图片描述
数据集里面有很多的文件夹,每一个文件夹里面存放同一个人的不同情况下的人脸。不同文件夹存放不同人的脸。

这是\0000045文件夹里面的人脸,属于同一个人。
在这里插入图片描述
这是\0000099文件夹里面的人脸,属于同一个人。
在这里插入图片描述

2、LOSS组成

facenet使用Triplet Loss作为loss。
Triplet Loss的输入是一个三元组

  • a:anchor,基准图片获得的128维人脸特征向量
  • p:positive,与基准图片属于同一张人脸的图片获得的128维人脸特征向量
  • n:negative,与基准图片不属于同一张人脸的图片获得的128维人脸特征向量

我们可以将anchor和positive求欧几里得距离,并使其尽量小。
我们可以将negative和positive求欧几里得距离,并使其尽量大。

我们所使用的公式为。
L = m a x ( d ( a , p ) − d ( a , n ) + m a r g i n , 0 ) L=max(d(a,p)−d(a,n)+margin,0) L=max(d(a,p)d(a,n)+margin,0)
d(a,p)就是anchor和positive的欧几里得距离。
d(a,n)就是negative和positive的欧几里得距离。
margin是一个常数。

d(a,p)前面为正符号,所以我们期望其越来越小。
d(a,n)前面为负符号,所以我们期望其越来越大。

即我们希望,同一个人的不同状态的人脸特征向量欧几里得距离小。
不同人的人脸特征向量欧几里得距离大。

但是由于仅仅只是用Triplet Loss会使得整个网络难以收敛,本文结合Cross-Entropy Loss和Triplet Loss作为总体loss。

Triplet Loss用于进行不同人的人脸特征向量欧几里得距离的扩张,同一个人的不同状态的人脸特征向量欧几里得距离的缩小。
Cross-Entropy Loss用于人脸分类,具体作用是辅助Triplet Loss收敛。

训练自己的Facenet人脸识别算法

博客中所使用的例子为CASIA-WebFace数据集。
在这里插入图片描述
下载数据集,放在根目录下的dataset文件夹下。在这里插入图片描述
运行根目录下的txt_annotation.py,生成训练所需的cls_train.txt。
在这里插入图片描述
cls_train.txt中每一行都存放了一张图片和它对应的类别(需要类别是因为训练时会用交叉熵损失辅助收敛。)
在这里插入图片描述

下载facenet_inception_resnetv1.h5或者facenet_mobilenet.h5放在model_data文件夹内。
在这里插入图片描述
在train.py中指定合适的模型预训练权重路径。facenet_inception_resnetv1.h5是我已经训练过的基于inception_resnetv1的facenet网络;
facenet_mobilenet.h5是我已经训练过的基于mobilenet的facenet网络。
在这里插入图片描述
运行train.py开始训练。

  • 22
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 98
    评论
### 回答1: FaceNet是一个用于人脸识别的深度学习模型,旨在实现高性能的人脸识别算法。在Keras实现FaceNet模型需要遵循以下步骤: 1. 数据准备:数据集应该包含大量的人脸图像,每个图像应标记对应的人名或ID。数据集可以通过互联网或手动收集。 2. 数据预处理:数据需要进行预处理,包括图像重缩放、灰度或彩色转换、预处理图像增强等。 3. 构建模型:构建FaceNet模型可以适当使用预训练模型,包括VGG、ResNet等。该模型主要包括卷积层、Pooling层、全连接层等。其中,卷积层用于提取特征,全连接层用于将提取的特征向量映射到特定的实空间。 4. 训练模型:在数据集上对模型进行训练,并执行交叉验证以避免过拟合。 5. 评估性能:评估FaceNet模型的性能可以使用准确性、召回率、精确度等指标。 在keras中的实现,需要先导入相关的库,如kerastensorflow等,然后在构建模型时逐层添加网络层。具体的实现,包括前向和后向传播,可以根据具体的模型来进行编程实现。 总之,FaceNet是一个优秀的人脸识别模型Keras提供了方便的接口来实现模型以及进行相关优化和改进。通过对FaceNet模型的建模和训练,可以实现高效的人脸识别,为安全管理、身份认证等领域奠定基础。 ### 回答2: Facenet是一种流行的人脸识别模型,它是通过深度神经网络实现的。最近,人们在Keras框架下实现Facenet模型,使其更便于使用和调试。 在Keras实现中,Facenet模型主要由三个部分组成:人脸检测、人脸对齐和人脸嵌入。 人脸检测部分使用一个卷积神经网络来检测图像中的人脸并确定它们的位置。这个网络训练的目的是检测一张图像中的每一个人脸,返回一个框定每个人脸位置和大小的边框。 人脸对齐是将检测到的人脸进行转换,使其风格和大小匹配,以便在嵌入步骤中获得更好的结果。这个部分的实现使用了一系列的仿射变换和三维对准技术,以确保每个人脸在变换后能够对齐。 人脸嵌入是将归一化后的检测图像映射为人脸向量,也是Facenet的核心部分。这个部分使用了一个具有三个等距卷积层的深度神经网络,并将每个人脸转换为一个128维的向量。这个向量的距离可以表示两张人脸之间的差异,从而实现识别功能。 值得注意的是,在Keras实现中,Facenet模型使用了Triplet Loss作为损失函数,它可以帮助提高嵌入向量的质量,并使得具有相同特征的人脸之间的距离尽可能小,同时使具有不同特征的人脸之间的距离最大化。 总之,通过使用Keras框架实现Facenet模型,可以方便地进行人脸识别和验证任务,为深度学习人脸识别应用提供了有效的实现方式。 ### 回答3: FaceNet是一种人脸识别模型,它可以将人脸图像转换为具有独特特征的向量。这个模型可以在Keras实现Keras是一种用于构建神经网络的高级Python库,它提供了一种简单易用的API,使得用户可以集中精力于模型的构建和调优。在Keras实现FaceNet采用深度学习的方法,结合了卷积神经网络和三元组损失函数。 具体来说,FaceNet模型通过卷积神经网络提取人脸图像的特征。在图像经过卷积层、池化层和全连接层之后,可以将其转换为一个固定长度的向量,该向量可以表示出不同人脸之间的差异。通过三元组损失函数,模型可以对这些向量进行训练。三元组损失函数是一种用于训练嵌入向量的度量学习函数,让嵌入向量之间的距离有明确的含义,可以识别出不同人脸之间的差异。 在Keras实现FaceNet模型,需要使用以下库:tensorflow, numpy, matplotlib,和sklearn。这些库可以让用户在建模,训练和评估模型时使用特定函数和工具。 总之,采用Keras实现FaceNet模型能够实现准确、快速地对人脸进行识别,这可以开辟人脸识别应用程序的新前景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 98
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Bubbliiiing

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

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

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

打赏作者

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

抵扣说明:

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

余额充值