聪明的人脸识别3——Pytorch 搭建自己的Facenet人脸识别平台

article/2025/8/28 0:29:56

聪明的人脸识别3——Pytorch 搭建自己的Facenet人脸识别平台

  • 学习前言
  • 什么是Facenet
  • 源码下载
  • Facenet的实现思路
    • 一、预测部分
      • 1、主干网络介绍
      • 2、根据初步特征获得长度为128的特征向量
      • 3、l2标准化
      • 4、构建分类器(用于辅助Triplet Loss的收敛)
    • 二、训练部分
      • 1、数据集介绍
      • 2、LOSS组成
  • 训练自己的Facenet人脸识别算法

学习前言

好的还有Pytorch版。
在这里插入图片描述

什么是Facenet

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

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

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

源码下载

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

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 torch.nn as nndef conv_bn(inp, oup, stride = 1):return nn.Sequential(nn.Conv2d(inp, oup, 3, stride, 1, bias=False),nn.BatchNorm2d(oup),nn.ReLU6())def conv_dw(inp, oup, stride = 1):return nn.Sequential(nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),nn.BatchNorm2d(inp),nn.ReLU6(),nn.Conv2d(inp, oup, 1, 1, 0, bias=False),nn.BatchNorm2d(oup),nn.ReLU6(),)class MobileNetV1(nn.Module):def __init__(self):super(MobileNetV1, self).__init__()self.stage1 = nn.Sequential(# 160,160,3 -> 80,80,32conv_bn(3, 32, 2), # 80,80,32 -> 80,80,64conv_dw(32, 64, 1), # 80,80,64 -> 40,40,128conv_dw(64, 128, 2),conv_dw(128, 128, 1),# 40,40,128 -> 20,20,256conv_dw(128, 256, 2),conv_dw(256, 256, 1),)self.stage2 = nn.Sequential(# 20,20,256 -> 10,10,512conv_dw(256, 512, 2),conv_dw(512, 512, 1),conv_dw(512, 512, 1),conv_dw(512, 512, 1),conv_dw(512, 512, 1),conv_dw(512, 512, 1),)self.stage3 = nn.Sequential(# 10,10,512 -> 5,5,1024conv_dw(512, 1024, 2),conv_dw(1024, 1024, 1),)self.avg = nn.AdaptiveAvgPool2d((1,1))self.fc = nn.Linear(1024, 1000)def forward(self, x):x = self.stage1(x)x = self.stage2(x)x = self.stage3(x)x = self.avg(x)# x = self.model(x)x = x.view(-1, 1024)x = self.fc(x)return x

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

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

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

class Facenet(nn.Module):def __init__(self, backbone="mobilenet", dropout_keep_prob=0.5, embedding_size=128, num_classes=None, mode="train"):super(Facenet, self).__init__()if backbone == "mobilenet":self.backbone = mobilenet()flat_shape = 1024elif backbone == "inception_resnetv1":self.backbone = inception_resnet()flat_shape = 1792else:raise ValueError('Unsupported backbone - `{}`, Use mobilenet, inception_resnetv1.'.format(backbone))self.avg = nn.AdaptiveAvgPool2d((1,1))self.Dropout = nn.Dropout(1 - dropout_keep_prob)self.Bottleneck = nn.Linear(flat_shape, embedding_size,bias=False)self.last_bn = nn.BatchNorm1d(embedding_size, eps=0.001, momentum=0.1, affine=True)if mode == "train":self.classifier = nn.Linear(embedding_size, num_classes)def forward(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)x = self.last_bn(x)x = F.normalize(x, p=2, dim=1)return xdef forward_feature(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)before_normalize = self.last_bn(x)x = F.normalize(before_normalize, p=2, dim=1)return before_normalize, xdef forward_classifier(self, x):x = self.classifier(x)return 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范数;

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

class Facenet(nn.Module):def __init__(self, backbone="mobilenet", dropout_keep_prob=0.5, embedding_size=128, num_classes=None, mode="train"):super(Facenet, self).__init__()if backbone == "mobilenet":self.backbone = mobilenet()flat_shape = 1024elif backbone == "inception_resnetv1":self.backbone = inception_resnet()flat_shape = 1792else:raise ValueError('Unsupported backbone - `{}`, Use mobilenet, inception_resnetv1.'.format(backbone))self.avg = nn.AdaptiveAvgPool2d((1,1))self.Dropout = nn.Dropout(1 - dropout_keep_prob)self.Bottleneck = nn.Linear(flat_shape, embedding_size,bias=False)self.last_bn = nn.BatchNorm1d(embedding_size, eps=0.001, momentum=0.1, affine=True)if mode == "train":self.classifier = nn.Linear(embedding_size, num_classes)def forward(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)x = self.last_bn(x)x = F.normalize(x, p=2, dim=1)return xdef forward_feature(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)before_normalize = self.last_bn(x)x = F.normalize(before_normalize, p=2, dim=1)return before_normalize, xdef forward_classifier(self, x):x = self.classifier(x)return x

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

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

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

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

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

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

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

class Facenet(nn.Module):def __init__(self, backbone="mobilenet", dropout_keep_prob=0.5, embedding_size=128, num_classes=None, mode="train"):super(Facenet, self).__init__()if backbone == "mobilenet":self.backbone = mobilenet()flat_shape = 1024elif backbone == "inception_resnetv1":self.backbone = inception_resnet()flat_shape = 1792else:raise ValueError('Unsupported backbone - `{}`, Use mobilenet, inception_resnetv1.'.format(backbone))self.avg = nn.AdaptiveAvgPool2d((1,1))self.Dropout = nn.Dropout(1 - dropout_keep_prob)self.Bottleneck = nn.Linear(flat_shape, embedding_size,bias=False)self.last_bn = nn.BatchNorm1d(embedding_size, eps=0.001, momentum=0.1, affine=True)if mode == "train":self.classifier = nn.Linear(embedding_size, num_classes)def forward(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)x = self.last_bn(x)x = F.normalize(x, p=2, dim=1)return xdef forward_feature(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)before_normalize = self.last_bn(x)x = F.normalize(before_normalize, p=2, dim=1)return before_normalize, xdef forward_classifier(self, x):x = self.classifier(x)return x

二、训练部分

1、数据集介绍

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

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

2、LOSS组成

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

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

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

我们所使用的公式为。
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.pth或者facenet_mobilenet.pth放在model_data文件夹内。
在这里插入图片描述
在train.py中指定合适的模型预训练权重路径。facenet_inception_resnetv1.pth是我已经训练过的基于inception_resnetv1的facenet网络;
facenet_mobilenet.pth是我已经训练过的基于mobilenet的facenet网络。
在这里插入图片描述
运行train.py开始训练。


http://chatgpt.dhexx.cn/article/qNgWfSiq.shtml

相关文章

syscall()

1、syscall的定义 #include<unistd.h> #include<sys/syscall.h> / For SYS_xxx definitions /long syscall(long number, ...);syscall执行间接系统调用&#xff0c;使用该函数会执行一个系统调用&#xff0c;根据指定的参数 number 和所有系统调用的汇编语言接口…

linux systemctl命令详解

笔者在前文中概要的介绍了 systemd 的基本概念和主要特点。由于 systemd 相关的绝大多数任务都是通过 systemctl 命令管理的&#xff0c;所以本文将集中的介绍 systemctl 命令的用法。注意&#xff0c;本文以 ubuntu 16.04 进行介绍&#xff0c;文中所有的 demo 都在 ubuntu 16…

Linux常用命令——sysctl命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) sysctl 时动态地修改内核的运行参数 补充说明 sysctl命令被用于在内核运行时动态地修改内核的运行参数&#xff0c;可用的内核参数在目录/proc/sys中。它包含一些TCP/ip堆栈和虚拟内存系统的高级选项&#xff…

Linux之systemctl命令基本使用

文章目录 1. systemctl 管理指令2. systemctl 设置服务的自启动状态3. 应用案例&#xff1a;4. 细节讨论&#xff1a; 1. systemctl 管理指令 基本语法&#xff1a; systemctl [start | stop | restart | status] 服务名systemctl 指令管理的服务在 /usr/lib/systemd/system 查…

systemctl命令解析

原文链接如果有效&#xff0c;请点击原文链接查看。 原文&#xff1a;http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html 一、由来 历史上&#xff0c;Linux 的启动一直采用init进程。 下面的命令用来启动服务。 $ sudo /etc/init.d/apache2 start # …

Linux常用命令——systemctl命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) systemctl 系统服务管理器指令 补充说明 systemctl命令是系统服务管理器指令&#xff0c;它实际上将 service 和 chkconfig 这两个命令组合到一起。 任务旧指令新指令使某服务自动启动chkconfig --level 3 ht…

【Linux】之systemd与systemctl

文章目录 一、systemd1. systemd 守护进程管理 Linux 的启动2. systemd 提供的功能:3. systemd 使用单元来管理不同类型的对象。4. 服务单元信息 二、systemctl1. systemctl输出中的服务状态2. 列出servera上安装的所以服务单元3. 列出servera上所有活动和不活动的套接字单元4.…

【linux systemctl】Linux命令之systemctl命令

一、systemctl命令简介 CentOS 5使用SysV init&#xff1b;CentOS 6使用Upstart&#xff0c;CentOS 7使用Systemd管理守护进程。centos7采用 systemd管理&#xff0c;服务独立的运行在内存中&#xff0c;服务响应速度快&#xff0c;但占用更多内存。独立服务的服务启动脚本都在…

Linux命令之systemctl命令

一、systemctl命令简介 CentOS 5使用SysV init&#xff1b;CentOS 6使用Upstart&#xff0c;CentOS 7使用Systemd管理守护进程。centos7采用 systemd管理&#xff0c;服务独立的运行在内存中&#xff0c;服务响应速度快&#xff0c;但占用更多内存。独立服务的服务启动脚本都在…

idea 如何使用tomcat启动项目

1、首先对项目进行打包&#xff08;使用maven&#xff09; 2、打包成功后会有target 3、选择右侧的长条框&#xff08;如果没有tomcat&#xff0c;框是灰色的&#xff0c;这时你要自己手动加一个tomcat&#xff09;&#xff0c;选择Edit 4、如图所示、点击“”号&#xff0c;继…

Tomcat启动项目慢

原因是多方面&#xff0c;我遇到的情况有三种可能导致tomcat启动项目变慢 情况一&#xff1a;tomcat在启动过程中会检查jar&#xff0c;当有大量的jar被检测的时候&#xff0c;启动需要很长时间 解决办法&#xff1a;将catalina.properties文件中的这一行 tomcat.util.scan.S…

Eclipse配置Tomcat以及使用Tomcat启动项目

1、打开Peferences弹窗 Windeows--->Peferences : 2、输入Server&#xff0c;点击Runtime Enviroument&#xff0c;点击Add&#xff1a; 3、选择tomcat版本&#xff1a; 4、找到自己的tomcat的下载路径&#xff0c;点击finish&#xff1a; 5、顶级apply and close&#xf…

IDEA配置本地tomcat启动项目

1.确认tomcat本地安装是否成功 tomcat安装可以参考我之前写的内容,Windows下tomcat安装教程 2.idea启动tomcat设置 1.新建项目 2.点击Java Enterprise选项&#xff0c;并进行设置 ​ 服务器选择对应的Tomcat版本、JDK版本号要与Tomcat的版本号对应&#xff0c;不然可能出现…

Java项目部署到tomcat启动

Java项目部署到tomcat启动 1.选中项目右击进行打包2.把后缀war文件放入tomcat-->webapps下3.修改tomcat-->conf--server.xml文件4.修改tomcat-->bin-->startup.bat和 shutdown.bat文件5.启动startup.bat 1.选中项目右击进行打包 2.把后缀war文件放入tomcat–>we…

Tomcat启动闪退问题处理

一、问题描述 启动tomcat一闪就退出。 二、解决办法 1、确定JAVA_HOME配置正确 必须有JAVA_HOME变量&#xff0c;且配到了path中 2、新增TOMCAT_HOME变量 3、新增CATALINA_BASE和CATALINA_HOME变量 4、修改Path变量 把%CATALINA_HOME%\lib和%CATALINA_HOME%\bin放到Path中 三…

Tomcat启动后出现乱码

今天启动时出现了如下乱码&#xff1a; 解决方案&#xff1a; 1.找到Tomcat目录下conf文件夹中的logging.properties文件&#xff0c;用记事本打开 2.打开logging.properties文件&#xff0c;找到文件中的java.util.logging.ConsoleHandler.encoding UTF-8&#xff0c; 3.将其…

IDEA创建Tomcat启动配置

很多时候我们都是使用spring自带的Tomcat容器就可以启动应用&#xff0c;这里介绍下使用外置tomcat启动应用。 1.点击IDEA菜单&#xff1a;【Run】–>【Edit Configurations】&#xff0c;进入如下界面&#xff1a; 2.点击左上方【】&#xff0c;选择【Tomcat Server】->…

IDEA整合Tomcat启动javaweb项目

1.前言 突然兴起想要写这么一篇博客,不是因为别的,纯属是因为小编还是一个小白的时候,听某马和某硅谷的javaweb网课时,遇到这个章节一直启动不起来,虽然小编依旧是个菜鸡,但是今时不同往日了,小编已经打破桎梏成为了一个有素养的菜鸡,蓦然回首,还有这么多苦读的学子卡在这个章…

Tomcat启动时,报404错误

在网页输入 https://localhost:8080时出现Not Found 404错误 在Tomcat正常启动后出现的这个错误一般就是该8080端口被占用了&#xff0c;所以我们可以修改Tomcat中的配置文件&#xff0c;打开conf目录后&#xff0c;打开server.properties文件 修改端口号8080为8081 之后再次在…

设置Tomcat启动窗口名称

设置Tomcat启动窗口名称 问题&#xff1a;因为需要启动多个Tomcat&#xff0c;但是每个窗口名称都是tomcat 解决&#xff1a; 1、在Tomcat的bin目录下找到catalina.bat文件,打开编辑 2、在文件中找到if "%TITLE%" "" set TITLETomcat,将Tomcat修改为想要…