全网首发,Swin Transformer+FaceNet实现人脸识别

article/2025/8/28 0:21:07

目录

一、 简介

二、Swin Transformer作为Backbone

1.Swin Transformer整体结构 

2.PatchEmbed = Patch Partition + Linear Embedding

3.Swin Transformer Block

(1)Window Partition

(2)Shifted Window based Self-Attention

三、Triplet Loss


一、 简介

        与其他的深度学习方法在人脸上的应用不同,FaceNet并没有用传统的softmax的方式去进行分类学习,然后抽取其中某一层作为特征,而是直接进行端对端学习一个从图像到欧式空间的编码方法,然后基于这个编码再做人脸识别、人脸验证和人脸聚类等。

FaceNet主要有两个重点:Backbone和Triplet loss。我们也将主要从这两个方面介绍。

代码:oaifaye/facenet-swim-transformer

二、Swin Transformer作为Backbone

        FaceNet论文使用了两个Backbone:Zeiler&Fergus架构和Google的Inception v1。随之时代的发展,已经不会有人再使用这两个模型了,基本所有优秀的模型都可以作为FaceNet的Backbone,想大Inception+ResNet足够了,想小MobileNet很优秀。这些模型都很棒,于是我们今天使用Swin Transformer。

        目前Transformer应用到图像领域主要有两大挑战:
        1.目标尺寸多变。不像NLP任务中token大小基本相同,目标检测中的目标尺寸不一,用单层级的模型很难有好的效果。
        2.图片的高分辨率。高分辨率会使得计算复杂度呈现输入图片大小的二次方增长,这显然是不能接受的。

顾名思义,Hierarchical(多层级)解决第一个问题;Shifted Windows(滑窗)解决第二个问题。

1.Swin Transformer整体结构 

        先看模型整体结构,论文中的结构图跟代码有削微的差别,我以代码为准重画了结构图。因为原版FaceNet的输入是160x160,输出是128x128,所以我将输入改为了160x160,输出层加了全连接层将768映射成128.

图1

         整个模型采取层次化的设计,一共包含4个Stage,每个stage都会缩小输入特征图的分辨率,像CNN一样逐层扩大感受野。

2.PatchEmbed = Patch Partition + Linear Embedding

        PatchEmbed包含了两个功能:Patch Partition 和 Linear Embedding,Patch Partition负责将图片切成Patch; Linear Embedding负责对Patch做Embedding。结构图如下:

        上面的96代表Embedding的维度,我们使用默认值96,代码及中文注释如下:

class PatchEmbed(nn.Module):r""" 将图片分割成不重叠的小patch,并做Embedding,尺寸下采样尺寸为patch_size的大小。Args:img_size (int): 输入图像大小.  默认: 224.我们使用160patch_size (int): Patch token 的大小,默认为 4*4. in_chans (int): 输入图像的通道数,默认为 3.embed_dim (int): 线性 projection 输出的通道数,默认为 96.norm_layer (nn.Module, optional): 归一化层, 默认为N None."""def __init__(self, img_size=224, patch_size=4, in_chans=3, embed_dim=96, norm_layer=None):super().__init__()img_size = to_2tuple(img_size)patch_size = to_2tuple(patch_size)patches_resolution = [img_size[0] // patch_size[0], img_size[1] // patch_size[1]]self.img_size = img_sizeself.patch_size = patch_sizeself.patches_resolution = patches_resolutionself.num_patches = patches_resolution[0] * patches_resolution[1]self.in_chans = in_chansself.embed_dim = embed_dimself.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)if norm_layer is not None:self.norm = norm_layer(embed_dim)else:self.norm = Nonedef forward(self, x):B, C, H, W = x.shape# FIXME look at relaxing size constraintsassert H == self.img_size[0] and W == self.img_size[1], \f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."x = self.proj(x)x = x.flatten(2)x = x.transpose(1, 2)  # 结构为 [B, num_patches=patche_h*patche_w, C]if self.norm is not None:x = self.norm(x)return xdef flops(self):Ho, Wo = self.patches_resolutionflops = Ho * Wo * self.embed_dim * self.in_chans * (self.patch_size[0] * self.patch_size[1])if self.norm is not None:flops += Ho * Wo * self.embed_dimreturn flops

3.Swin Transformer Block

        重点来了,先看Swin Transformer Block结构图:

图2

        图1中可以看到Stage1、2、4都有两个Swin Transformer Block,Stage3有6个Swin Transformer Block。我们以Stage1为例,输入和输出是一样的,都是1,1600,96。这块的重点两个个部分:

(1)Window Partition

        根据window_size分窗,这里window_size=5,那们40x40的一个featuremap就能分成8个window,有点类似于YOLYv5中的focus结构,示意图如下:

        相关代码

def window_partition(x, window_size):"""将输入分割为多个不重叠窗口Args:x: (B, H, W, C)window_size (int): window sizeReturns:windows: (num_windows*B, window_size, window_size, C)"""B, H, W, C = x.shapex = x.view(B, H // window_size, window_size, W // window_size, window_size, C)windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C)return windows

(2)Shifted Window based Self-Attention

        图2中的Attention模块是在每个窗口下计算注意力的,大大减少了计算量,但是也带来了一个问题:每个窗口建的联系是薄弱的,  为了更好的促进window间进行信息交互,Swin Transformer引入了shifted window操作。偶数轮次执行Shifted Window,如下图:

         左边是没有重叠的Window Attention,而右边则是将窗口进行移位的Shift Window Attention。可以看到移位后的窗口包含了原本相邻窗口的元素。但这也引入了一个新问题,即window的个数翻倍了,由原本四个窗口变成了9个窗口。

        为了在保持非重叠窗口的高效计算的同时引入跨窗口连接,我们提出了一种移动窗口分区方法,在实际代码里,我们是通过对特征图移位,并给Attention设置mask来间接实现的。能在保持原有的window个数下,最后的计算结果等价。示意图如下:

         具体实现是通过torch.roll来实现的,torch.roll的用法大概是下面这样:              在Attention中通过设置mask,让Shifted Window Attention在与Window Attention相同的窗口个数下,达到等价的计算结果。首先我们对Shift Window后的每个窗口都给上index,并且做一个roll操作(window_size=2, shift_size=1)

        我们希望在计算Attention的时候,让具有相同index QK进行计算,而忽略不同index QK计算结果。最后正确的结果如下图所示:

        

        而要想在原始四个窗口下得到正确的结果,我们就必须给Attention的结果加入一个mask(如上图最右边所示) 

        相关代码:

class WindowAttention(nn.Module):r""" 基于有相对位置偏差的多头自注意力窗口,支持移位的(shifted)或者不移位的(non-shifted)窗口.输入:dim (int): 输入特征的维度.window_size (tuple[int]): 窗口的大小.num_heads (int): 注意力头的个数.qkv_bias (bool, optional): 给 query, key, value 添加可学习的偏置,默认为 True.qk_scale (float | None, optional): 重写默认的缩放因子 scale.attn_drop (float, optional): 注意力权重的丢弃率,默认为 0.0.proj_drop (float, optional): 输出的丢弃率,默认为 0.0."""def __init__(self, dim, window_size, num_heads, qkv_bias=True, qk_scale=None, attn_drop=0., proj_drop=0.):super().__init__()self.dim = dim # 输入特征的维度self.window_size = window_size  # 窗口的高 Wh,宽 Wwself.num_heads = num_heads  # 注意力头的个数head_dim = dim // num_heads  # 注意力头的维度self.scale = qk_scale or head_dim ** -0.5  # 缩放因子 scale# 定义相对位置偏移的参数表,结构为 [2*Wh-1 * 2*Ww-1, num_heads]self.relative_position_bias_table = nn.Parameter(torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads))# 获取窗口内每个 token 的成对的相对位置索引coords_h = torch.arange(self.window_size[0])  # 高维度上的坐标 (0, 7)coords_w = torch.arange(self.window_size[1])  # 宽维度上的坐标 (0, 7)coords = torch.stack(torch.meshgrid([coords_h, coords_w]))  # 坐标,结构为 [2, Wh, Ww]coords_flatten = torch.flatten(coords, 1)  # 重构张量结构为 [2, Wh*Ww]relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :]  # 相对坐标,结构为 [2, Wh*Ww, Wh*Ww]relative_coords = relative_coords.permute(1, 2, 0).contiguous()  # 交换维度,结构为 [Wh*Ww, Wh*Ww, 2]relative_coords[:, :, 0] += self.window_size[0] - 1  # 第1个维度移位relative_coords[:, :, 1] += self.window_size[1] - 1  # 第1个维度移位relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1  # 第1个维度的值乘以 2倍的 Ww,再减 1relative_position_index = relative_coords.sum(-1)  # 相对位置索引,结构为 [Wh*Ww, Wh*Ww]self.register_buffer("relative_position_index", relative_position_index)  # 保存数据,不再更新self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)  # 线性层,特征维度变为原来的 3倍self.attn_drop = nn.Dropout(attn_drop)  # 随机丢弃神经元,丢弃率默认为 0.0self.proj = nn.Linear(dim, dim)  # 线性层,特征维度不变self.proj_drop = nn.Dropout(proj_drop)  # 随机丢弃神经元,丢弃率默认为 0.0trunc_normal_(self.relative_position_bias_table, std=.02)  # 截断正态分布,限制标准差为 0.02self.softmax = nn.Softmax(dim=-1)  # 激活函数 softmax# 定义前向传播def forward(self, x, mask=None):"""输入:x: 输入特征图,结构为 [num_windows*B, N, C]mask: (0/-inf) mask, 结构为 [num_windows, Wh*Ww, Wh*Ww] 或者没有 mask"""B_, N, C = x.shape  # 输入特征图的结构# 将特征图的通道维度按照注意力头的个数重新划分,并再做交换维度操作qkv = self.qkv(x).reshape(B_, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)q, k, v = qkv[0], qkv[1], qkv[2]  # 方便后续写代码,重新赋值# q 乘以缩放因子q = q * self.scale# @ 代表常规意义上的矩阵相乘attn = (q @ k.transpose(-2, -1))  # q 和 k 相乘后并交换最后两个维度# 相对位置偏移,结构为 [Wh*Ww, Wh*Ww, num_heads]relative_position_bias = self.relative_position_bias_table[self.relative_position_index.view(-1)].view(self.window_size[0] * self.window_size[1], self.window_size[0] * self.window_size[1], -1)# 相对位置偏移交换维度,结构为 [num_heads, Wh*Ww, Wh*Ww]relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous()attn = attn + relative_position_bias.unsqueeze(0)  # 带相对位置偏移的注意力图if mask is not None:  # 判断是否有 masknW = mask.shape[0]  # mask 的宽# 注意力图与 mask 相加attn = attn.view(B_ // nW, nW, self.num_heads, N, N) + mask.unsqueeze(1).unsqueeze(0)attn = attn.view(-1, self.num_heads, N, N)  # 恢复注意力图原来的结构attn = self.softmax(attn)  # 激活注意力图 [0, 1] 之间else:attn = self.softmax(attn)attn = self.attn_drop(attn)  # 随机设置注意力图中的部分值为 0# 注意力图与 v 相乘得到新的注意力图x = (attn @ v).transpose(1, 2).reshape(B_, N, C)x = self.proj(x)  # 通过线性层x = self.proj_drop(x)  # 随机设置新注意力图中的部分值为 0return x

三、Triplet Loss

损失函数公式:

        输入是一个三元组,包括锚(Anchor)示例、正(Positive)示例、负(Negative)示例,通过优化锚示例与正示例的距离小于锚示例与负示例的距离,实现样本之间的相似性计算。

        a:anchor,锚示例;

        p:positive,与a是同一类别的样本;

        n:negative,与a是不同类别的样本;

        margin是一个大于0的常数。最终的优化目标是拉近a和p的距离,拉远a和n的距离。设定一个margin常量,可以迫使模型努力学习,能让锚点a和负例n的distance值更大,同时让锚点a和正例p的distance值更小。

        由于margin的存在,使得triplets loss多了一个参数,margin的大小需要调参。如果margin太大,则模型的损失会很大,而且学习到最后,loss也很难趋近于0,甚至导致网络不收敛,但是可以较有把握的区分较为相似的样本,即a和p更好区分;如果margin太小,loss很容易趋近于0,模型很好训练,但是较难区分a和p。

        Triplet Loss代码实现:

def triplet_loss(alpha = 0.2):def _triplet_loss(y_pred,Batch_size):anchor, positive, negative = y_pred[:int(Batch_size)], y_pred[int(Batch_size):int(2*Batch_size)], y_pred[int(2*Batch_size):]pos_dist = torch.sqrt(torch.sum(torch.pow(anchor - positive,2), axis=-1))neg_dist = torch.sqrt(torch.sum(torch.pow(anchor - negative,2), axis=-1))keep_all = (neg_dist - pos_dist < alpha).cpu().numpy().flatten()hard_triplets = np.where(keep_all == 1)pos_dist = pos_dist[hard_triplets].cuda()neg_dist = neg_dist[hard_triplets].cuda()basic_loss = pos_dist - neg_dist + alphaloss = torch.sum(basic_loss)/torch.max(torch.tensor(1), torch.tensor(len(hard_triplets[0])))return lossreturn _triplet_loss


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

相关文章

深度学习之facenet人脸识别网络介绍

1.前言 照例先来一段废话&#xff0c;不要跟我说什么物质决定意识&#xff0c;也不要告诉我意识超越物质。在我眼中&#xff0c;这个世界本就是一个战场。软弱的意志自然无法战胜物质&#xff0c;但是足够强大的意识也是能够做到的。在战争没有进行完之前&#xff0c;谁也不知道…

聪明的人脸识别4——Pytorch 利用Retinaface+Facenet搭建人脸识别平台

睿智的目标检测51——Pytorch 利用RetinafaceFacenet搭建人脸识别平台 学习前言什么是Retinface和Facenet1、Retinface2、Facenet 整体实现代码实现流程一、数据库的初始化二、检测图片的处理1、人脸的截取与对齐2、利用Facenet对矫正后的人脸进行编码3、将实时图片中的人脸特征…

Facenet 原理介绍

引子[编辑 | 编辑源代码] 这篇wiki主要介绍facenet人脸相似比较的基本原理&#xff0c;另外两篇wiki主要介绍基于tensorflow实现facenet的准确率测试及源码解读。经过在网上的一番搜索&#xff0c;找到了facenet实现人脸聚类的论文和论文解读&#xff0c;以及github上根据facen…

【 facenet-retinaface】快速复现 实现 facenet-retinaface-pytorch 人脸识别 windows上 使用cpu实现

目录 0 前言1 搭建环境与项目2 人脸预测与结果展示 0 前言 这一次要复现的是人脸识别中的 facenet-retinaface-pytorch 是在上一次博客的内容上更进一步 快速复现 实现 facenet-pytorch 人脸识别 windows上 使用cpu实现 人脸对比 参考了&#xff1a; Pytorch 利用Facenet和Re…

FaceNet

摘要&#xff1a; 尽管人脸识别领域最近取得了重大进展[10,14,15,17]&#xff0c;但大规模有效地实施人脸验证和识别对当前方法提出了严峻挑战。在本文中&#xff0c;我们提出了一个称为 FaceNet 的系统&#xff0c;它直接学习从人脸图像到紧凑欧几里得空间的映射&#xff0c;其…

【facenet】快速复现 实现 facenet-pytorch 人脸识别 windows上 使用cpu实现 人脸对比

目录 0 前言1 搭建环境与项目2 人脸预测与结果展示 0 前言 这一次要复现的是人脸识别中的 facenet-pytorch 参考了&#xff1a; Pytorch 搭建自己的Facenet人脸识别网络&#xff08;Bubbliiiing 深度学习 教程&#xff09; https://gitee.com/xiaozhao123666/facenet-pytorch …

利用MTCNN和facenet实现人脸检测和人脸识别

利用MTCNN和facenet实现人脸检测和人脸识别 人脸检测和人脸识别技术算是目前人工智能方面应用最成熟的技术了。本博客将利用mtcnn和faceNet搭建一个实现人脸检测和人脸识别的系统。基本思路也很简单&#xff0c;先利用mtcnn的进行人脸检测&#xff0c;当然也可以使用其他的人脸…

facenet 总结一

Facenet是谷歌研发的人脸识别系统&#xff0c;该系统是基于百万级人脸数据训练的深度卷积神经网络&#xff0c;可以将人脸图像embedding&#xff08;映射&#xff09;成128维度的特征向量。以该向量为特征&#xff0c;采用knn或者svm等机器学习方法实现人脸识别。 CASIA-WebFac…

【人脸识别】FaceNet详解

论文题目&#xff1a;《FaceNet: A Unified Embedding for Face Recognition and Clustering》 论文地址&#xff1a;FaceNet 1、概述 FaceNet&#xff08;A Unified Embedding for Face Recognition and Clustering&#xff09;直接把输入图像变成欧式空间中的特征向量&#…

人脸识别系统FaceNet原理

1. 概述 近年来&#xff0c;随着深度学习在CV领域的广泛应用&#xff0c;人脸识别领域也得到了巨大的发展。在深度学习中&#xff0c;通过多层网络的连接&#xff0c;能够学习到图像的特征表示&#xff0c;那么两张人脸的图像&#xff0c;是不是可以通过深度学习判别其是否是相…

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

聪明的人脸识别3——Pytorch 搭建自己的Facenet人脸识别平台 学习前言什么是Facenet源码下载Facenet的实现思路一、预测部分1、主干网络介绍2、根据初步特征获得长度为128的特征向量3、l2标准化4、构建分类器&#xff08;用于辅助Triplet Loss的收敛&#xff09; 二、训练部分1…

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;但占用更多内存。独立服务的服务启动脚本都在…