目录
一、开箱即用
二、性能基线
三、训练
3.1 训练公开数据
3.2 训练自己的数据
四、视频教程
五、论文解读
引言
Loss
数据集
端上部署
人脸识别是目前深度学习领域应用最为广泛的领域之一,各大框架都有不错的开源项目,本文提供insightface论文的分析,对应代码为insightface,其在在三个影响人脸识别模型精度的主要因素:数据、模型和Loss上都做了相关工作,并且开源了可复现的数据、代码等全流程实现,是当前事实上的工业基准。
一、开箱即用
如果只是直接使用而不是训练的话,可以参考基于insightface实现的人脸识别和人脸注册
import os
import cv2
import numpy as np
from insightface.app import FaceAnalysis
colors = [[0,255,0],[255,0,0],[0,0,255],[255,0,255],[255,255,0],[0,255,255]]
app = FaceAnalysis(providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
app.prepare(ctx_id=0, det_size=(640, 640))
facebank_db = "outputs/facebank.npy"
def facebank(dir = "images"):
persons = os.listdir(dir)
embeddings = {}
for file in persons:
imgpath = dir + "/"+file
img = cv2.imread(imgpath)
faces = app.get(img)
if len(faces) > 0:
embeddings[file[:-4]]=faces[0].normed_embedding
np.save(facebank_db, embeddings)
def demo(video_path = "images/TheBigBangTheory.mp4"):
facebank = np.load(facebank_db, allow_pickle=True).item()
cap = cv2.VideoCapture(video_path)
index = 0
width = int(cap.get(3))
height = int(cap.get(4))
out = cv2.VideoWriter('outputs/demo.avi',cv2.VideoWriter_fourcc(*'MJPG'), 20.0, (width, height), isColor=True)
while True:
ret, img = cap.read()
if not ret:
break
print(index)
faces = app.get(img)
scores = []
for face in faces:
feature = face.normed_embedding
max_score = 0
max_idx = -1
max_name = "Uknown"
for i, id in enumerate(facebank):
score = np.dot(feature, facebank[id].T)
scores.append(score)
box = face.bbox.astype(np.int)
if score > max_score:
max_score = score
max_idx = i
max_name = id
if max_score > 0.2:
color = colors[max_idx]
cv2.putText(img, f"{max_name}", (box[0], box[1]), 1,1, color)
x = box[2]
y = box[3]
y1 = int(y - max_score*(box[3]-box[1]))
cv2.rectangle(img,(x, y1), (x+10, y), color, -1)
else:
color = (0,0,0)
cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), color, 2)
#cv2.imwrite(f"outputs/demo/{index}.png", img)
index += 1
out.write(img)
if __name__=="__main__":
if not os.path.exists(facebank_db):
facebank()
demo()
二、性能基线
数据集规模
轻量级模型
MobileFaceNet with 60 epochs: 训练数据越多 精度越高
编码维度的影响: 编码维度越高 精度越高,但512后基本就不再增长
三、训练
3.1 训练公开数据
先从insightface datasets下载数据,里面提供了对齐(112x112)好的人脸文件,并打包成rec格式加速读取,如果需要原始文件图片,可参考load_images_from_bin.py自行保存,也可使用image_iter_rec.py里的FaceDataset直接加载数据
- MS1MV2 (87k IDs, 5.8M images)
- MS1MV3 (93k IDs, 5.2M images)
- Glint360K (360k IDs, 17.1M images)
- WebFace42M (2M IDs, 42.5M images)
- Your Dataset, Click Here!
人脸识别数据集
# 挂载内存盘,可以显著加速训练
sudo mount -t tmpfs -o size=40G tmpfs /train_tmp
把前面下载的数据拷到/train_tmp里面
git clone https://github.com/deepinsight/insightface
cd recognition/arcface_torch
# 单卡训练
python train_v2.py configs/ms1mv3_r50_onegpu
# 单机8卡训练
torchrun --nproc_per_node=8 train_v2.py configs/ms1mv3_r50
# 多机分布式训练
Node 0:
torchrun --nproc_per_node=8 --nnodes=2 --node_rank=0 --master_addr="ip1" --master_port=12581 train_v2.py configs/wf42m_pfc02_16gpus_r100
Node 1:
torchrun --nproc_per_node=8 --nnodes=2 --node_rank=1 --master_addr="ip1" --master_port=12581 train_v2.py configs/wf42m_pfc02_16gpus_r100
3.2 训练自己的数据
训练自己的数据要复杂很多,人半监督系统搭建(5)-流程总结首先通过爬虫等手段获取人脸图片,每个人放一个文件夹
/image_folder
├── 0_0_0000000
│ ├── 0_0.jpg
│ ├── 0_1.jpg
│ ├── 0_2.jpg
│ ├── 0_3.jpg
│ └── 0_4.jpg
├── 0_0_0000001
│ ├── 0_5.jpg
│ ├── 0_6.jpg
│ ├── 0_7.jpg
│ ├── 0_8.jpg
│ └── 0_9.jpg
使用如下代码检测里面的人脸并对齐,输出到data/aligned文件夹
import os
import cv2
import numpy as np
from tqdm import tqdm
from insightface.app import FaceAnalysis
from insightface.utils import face_align
root_dir=os.path.dirname(os.path.abspath(__file__))
output_dir = f"{root_dir}/data/aligned"
#'recognition'
app = FaceAnalysis(allowed_modules=['detection'], providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
app.prepare(ctx_id=0, det_size=(640, 640))
def align_faces(dir=f"{root_dir}/data/images"):
subs = os.listdir(dir)
for sub in tqdm(subs):
subdir = dir + "/"+ sub
if os.path.isdir(subdir):
files = os.listdir(subdir)
for file in files:
imgpath = subdir+"/"+file
if imgpath.endswith(".jpg") or imgpath.endswith(".png"):
img = cv2.imread(imgpath)
faces = app.get(img)
if len(faces) == 0:
continue
max_idx = 0
max_area = 0
for i, face in enumerate(faces):
bbox = face.bbox
area = abs(bbox[2]-bbox[0])*(bbox[3]-bbox[1])
if area > max_area:
max_area = area
max_idx = i
max_face = faces[max_idx]
aimg = face_align.norm_crop(img, landmark=max_face.kps)
dst_dir = f"{output_dir}/{sub}"
os.makedirs(dst_dir, exist_ok=True)
cv2.imwrite(f"{dst_dir}/{file}", aimg)
if __name__=="__main__":
align_faces()
原始的人脸图片里可能有很多错误,包括非本人等,因此要有一个数据清洗的步骤. 以样本特征向量为无向图中的节点, 并在相似度大于阈值的节点间构建边, 首轮迭代时每个节点在相邻节点中选择相似度最高的节点作为自己的分类, 在接下来的每轮迭代中根据所有邻居的分类情况计算权重和重新决定自己的分类, 直到达到指定迭代次数或分类趋于稳定. 这一算法dlib有相关实现可以直接调用, 不需要我们编写代码.具体参见使用DLib快速实现人脸聚类
import os
import cv2
import dlib
from tqdm import tqdm
from insightface.app import FaceAnalysis
root_dir=os.path.dirname(os.path.abspath(__file__))
output_dir = f"{root_dir}/data/aligned"
#'recognition'
app = FaceAnalysis(providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
app.prepare(ctx_id=0, det_size=(640, 640))
def cluster_faces(dir="data/images"):
subs = os.listdir(dir)
embeddings = []
img_paths = []
for sub in tqdm(subs):
subdir = dir + "/"+ sub
if os.path.isdir(subdir):
files = os.listdir(subdir)
for file in files:
imgpath = subdir+"/"+file
if imgpath.endswith(".jpg") or imgpath.endswith(".png"):
img = cv2.imread(imgpath)
faces = app.get(img)
if len(faces) == 0:
continue
max_idx = 0
max_area = 0
for i, face in enumerate(faces):
bbox = face.bbox
area = abs(bbox[2]-bbox[0])*(bbox[3]-bbox[1])
if area > max_area:
max_area = area
max_idx = i
max_face = faces[max_idx]
embeddings.append(dlib.vector(max_face.normed_embedding))
img_paths.append(imgpath)
labels = dlib.chinese_whispers_clustering(embeddings, 0.5)
print(labels)
if __name__=="__main__":
cluster_faces()
将清洗的人脸打包成rec格式用于训练,剩余训练步骤和前面相同
# 1) create train.lst using follow command
python -m mxnet.tools.im2rec --list --recursive train image_folder
# 2) create train.rec and train.idx using train.lst using following command
python -m mxnet.tools.im2rec --num-thread 16 --quality 100 train image_folder
四、视频教程
视频教程: 一亿ID的人脸识别训练和万亿人脸对(Trillion Pairs)的人脸识别评测文字版人脸识别最新进展以及工业级大规模人脸识别实践探讨
在开始前先问自己几个问题,如果能想清楚背后的原理可以直接进到代码阶段
- 为什么要做weight norm?
- 为什么要做feature norm?
- 为什么同时做了两个norm后就收敛不了了呢?
- 为什么要加margin?
- 乘性margin为什么难收敛?
- 加性margin有什么好处?
weight norm解决样本数不均衡问题,feature norm解决图像质量问题,两个norm加在一起可以将判别的欧式距离变为角度,加margin是为了增加不同类之间的间距,缩小类内间距.
weight normalization 本质上就做了一件事,在网络中引入一个先验,即告诉网络,无论类别本身的 sample 数量是多还是少,所有类别的地位都应该是平等的,因此它们的 weight 的 norm 也是相似的。
2.从数据角度看人脸识别中Feature Normalization的作用
5. 第一点,乘性 margin 把 cos 函数的单调区间压小了,导致优化困难,第二点,乘性 margin 所造成的 margin 实际上是不均匀的. 乘性 margin 对易于混淆的 class 不具有可分性
人脸识别中Softmax-based Loss的演化史, 推荐使用的库为MobileFaceNet_Tutorial_Pytorch
无论是早期的 weight normalization,feature normalization,还是后期这些形式的一些变体以及一些 trick,它们归根结底,都是为了以下三点
- 防止网络在长尾问题上“顾此失彼”。
- 防止网络一旦把样本分对就“浅尝辄止”。
- 防止网络在难样本问题上“掩耳盗铃”
五、论文解读
引言
由于在学习区分特征上高的上限,卷积神经网络近年来大幅提升了人脸识别的性能。为了提高softmax的判别能力,乘性间隔和余弦间隔等被融入到损失函数中来。本文提出了一个全新的加性角度间隔,称之为ArcFace,其拥有更好的几何解释。特别的,提出的cos(theta+m)直接最大化L2归一化后的角度空间上的间隔,和乘性间隔(cost(mtheta)以及cos(theta)-m相比,可以获得更具判别力的特征。我们还探索了网络设置、数据清洗等在人脸识别中的重要性。在LFW、CFP和AgeDB等测试集上做了大量的实验,表明本文方法的有效性。特别的,我们在MegaFace上获得了最好的成绩,并且完全可以复现,我们还把数据、模型和代码都给开源了,让你们没饭可吃....
通过深度卷积网络提取特征被认为是人脸验证、人脸聚类以及人脸识别最有效的手段。深度卷积网络负责在姿态对齐后把人脸图像映射到特征空间,并且最小化类内距离,最大化类间距离。
不同人脸识别方法的主要区别在以下三个方面:
首先就是数据,公开的训练集比如VGG-Face、VGG2-Face、CASIA-WebFace、UMDFaces、MS-Celeb-1M和MegaFace人的数量在几千到上百万不等。尽管MS-Celeb-1M和MegaFace数量很大,它们有很多标注噪声,并且分布是长尾的(少数人图片很多,大多数人只有几张图片),相比之下,google的私有数据甚至有几百万个人,在FRVT(人脸识别测试竞赛"中获得冠军的依图,一个中国的创业公司,训练是基于18亿张图的。由于学术界和工业界训练资源的巨大差距,一些人脸识别的结果是无法复现的。
其次是网络结构和设置。大容量的网络模型比如ResNet和Inception-ResNet,可以获得比VGG网络和Google InceptionV1更好的性能. 人脸识别不同的应用需要在速度和精度间取得平衡。对于移动设备上的人脸验证来说,实时的性能和模型体积对于流畅的用户体验至关重要。对于亿级别的安全系统,则需要更高的精度。
还有就是损失函数
1.基于欧式距离的损失 在42和31中,一个Softmax分类层被在已知身份的数据上训练,然后从网络的中间层提取特征,以便处理未见过身份的识别。Centre Loss、Range Loss和Marginal loss添加了额外的约束以便类内紧凑、扩增类间距离,但是它们仍然使用Softmax损失来训练模型。然后,基于分类的方法饱受类别增加到百万级别后显存占用的困扰以及每个身份都得有充足的数据。
对比损失和Triplet损失改进了训练策略。 对比损失正例和负例组成,损失函数的梯度把正例拉在起,把负例推开。Triplet损失最小化正例-锚例间相比于负例和锚例的距离。然而由于样本选择上的问题它们的训练不是很稳定,需要很多trick,一般人训练不出来。
2.角度和余弦间距损失
L-Softmax通过为每个id增加乘性约束来提升特征判别能力,spheraface把L-Softmax用在了权重归一化之后。由于cos函数的高度非凸性,分片化的代理函数被用来保证单调性,在训练中还使用了softmax损失来保证收敛。为了克服spheraface优化的困难,余弦加性损失cost(theta)-m把间距用在了余弦空间。加性余弦损失非常容易复现并且在MegaFace上取得了最好的成绩(TencentAILab FaceCNN v1)。和欧式空间上加间隔相比,角度和余弦上的加性间隔在球形空间上进行了显示的约束,这更符合人脸在流形空间上的先验。
总所周知,数据、网络和损失在性能上的影响由大到小,本文从所有的这些方面都进行了改进。
数据 我们清洗了最大公开的数据集,MS-Celeb-1M,通过自动和手工结合的方式。我们使用resnet27检查了清洗后的数据。我们发现MegaFace Probe集和Gallery集上有很多重叠,这严重影响了评估结果。我们清晰了Gallery集,清洗后训练集和测试集将会公开。
网络 使用VGG2数据作为训练数据,我们做了不同网络的大量实验,并且汇报LFW、CFP和AgeDB上的精度。提出的网络在不同姿态和年龄变化上都有很好的性能。我们还基于最新的网络探索了速度-精度间的平衡。
损失 我们提出了一个新的损失函数,加性角度间隔,来获取高判别性的特征。如图1所示,提出的cos(theta+m)损失直接优化角度。ArcFace不仅有更好的几何解释,而且击败了其他的基线模型。我们还从半自动难例挖掘的角度解释了为什么ArcFace比SphereFace和CosineFace更好。
性能 提出的ArcFace在当前公开最大的基准MegaFace上获得了最好的性能。我们把数据和复现结果的代码一并开源了出来,卷起来。
Loss
softmax loss 做分类问题的流程。输入一个训练样本,倒数第二层的 feature extraction layer 输出 feature x,和最后一层的 classification layer 的类别权重矩阵 W相乘,得到各类别的分数,再经过 softmax function 得到 normalize 后的类别概率,再得到 cross-entropy loss。
类别 weight Wk 可看作是一个类别所有样本的代表。 f是样本 feature 和类别 weight 的点积,可以认为是样本和类别的相似度或者分数。通常这个分数被称为 logit.Softmax 能够放大微小的类别间的 logit 差异,这使得它对这些微小的变化非常敏感,这往往对优化过程非常有利。
softmax 中的指数操作,可以迅速放大原始的 logit 之间的差异,使得“正确类别概率接近于 1”的目标变得简单很多。这种效应可以称为“强者通吃”
分类任务中最常用的softmax损失函数为
Softmax 交叉熵损失的三个性质:
- Softmax 交叉熵损失对分数的梯度之和为0
- Softmax 交叉熵损失对非目标分数的梯度之和等于对目标分数梯度的绝对值
- Softmax 交叉熵损失对分数的梯度绝对值之和为2 倍的对目标分数之和
然而,Softmax损失并没有显式的直接优化正例间的相似度,降低负例的相似度,这和测试时存在一个gap。换而言之,它只把样本按类别分开,当稍微高于边缘后就不再继续往类中心推进,也就是只有分类能力而没有判别能力,后面的魔改都是针对此展开。
Softmax Loss 因为其易于优化,收敛快等特性被广泛应用于图像分类领域。然而,直接使用 softmax loss 训练得到的 feature 拿到 retrieval,verification 等“需要设阈值”的任务时,往往并不够好。原因在于Softmax loss 在形式上是 softmax 函数加上交叉熵损失,它的目的是让所有的类别在概率空间具有最大的对数似然,也就是保证所有的类别都能分类正确,而 retievel 和 verification 任务所需要的是一个泛化性能更好的度量空间(metric space)。保证分类正确和保证一个泛化性优良的 metric space 这两者之间虽然相关性很强,但并不直接等价。
Visualizing feature vectors/embeddings using t-SNE and PCA
FR-Loss-on-Mnist: Face Recognition Loss
权重归一化 简单起见,取b=0,然后把目标logit变换成如下形式
和23,43,34一样,我们通过L2归一化把W变成1,这使得预测仅依赖于特征向量和权重间的角度
在SphereFace中的实验表明权重归一化仅能提升少许性能。
加性角度间隔 在sphereface提出乘性间隔,也就是对theta乘以一个数m,
由于cosine不是单调的,所以用了个策略给转成单调的
训练的时候加入了softmax帮助收敛,引入了一个参数lamda,可是这个参数的引入让训练变的复杂微妙起来,结果很难复现,很难收敛,需要精细调参。
特征归一化
特征归一化在人脸验证中广泛使用,L2-归一化的欧式距离和余弦距离。Parde等人发现L2-Norm和人脸图像质量相关。高质量的正前人脸Norm更大,而模糊的脸和大姿态有更小的L2-Norm。Ranjan等人在特征描述上添加了L2约束来把特征限制在固定半径的球上。特征的L2归一化在现有的框架上可以很容易的实现并且显著的提升性能。王峰等人指出低质量的人脸做归一化后梯度会很大,这可能会增加梯度爆炸的风险。从前述工作不难看出,权重和特征归一化在流行度量学习中非常重要,背后的逻辑在于去除半径的变化而只专注于把特征打散到球形空间上。
有好事者发现训练时使用欧氏距离,测试时用余弦相似度能提升准确率,但是直接训练余弦损失却无法收敛的问题,原因在于最后一层输出(-1,1)时造成的梯度太大,需要乘以s才可缓解.
NormFace等则对cos theta 减去m,构成余弦加性间隔
而ArcFace直接对角度施加约束
不同损失对比如下
目标logit分析
为了调研Sphereface、CosineFace和ArcFace为什么能提升性能的原因,我们分析了训练过程中目标logit和theta的分布。在这里我们使用LResNet-34E-IR模型在清洗后的MS1M数据上进行。
图4a画出了它们,对于SphereFace来说,最好的设置是m=4,lambda=5,这个m=1.5,lambda=0是类似的,然而,由于sphereface实现中m必须得是个整数,当我们尝试使用最小的间隔m=2,lambda=0时,训练无法收敛。因此,适当的降低目标logit能够增大训练难度提升性能,但是如果降低太多可能会导致训练发散。CosineFace和ArcFace都遵循这点,正如我们在图4a看到的那样,CosineFace把目标logit向下移动而ArcFace是向左移动的。现在总算能够理解从Softmax到CosineFace和ArcFace为啥能提升性能了。
对于m=0.5的ArcFace来说,目标logit并不是单调的,事实上在theta大于151.35度时目标logit还会增加,然而如图4c所示,从随机初始化的网络训练开始就最大的角度就很少超过105度。那段范围根本就没抵达过,因此不必过多的处理。
总体上来说,在60度到90度间添加太多的间隔可能会导致训练不收敛。在30度到60度间添加间隔能够提升性能,因为这部分大多是难例。在theta小于30度时添加间隔没什么卵用,因为这部分对应的是最简单的样本。
实验 本文的目标在以完全可以复现的方式在人脸认证和识别最大的基准MegaFace上获得最好的性能。我们使用LFW、CFP和AgeDB作为验证集并且在所有的上面都获得了最好的成绩。
训练数据 我们使用VGG2和MS-Celeb-1M作为训练数据。VGG2包含8631个的314890张图作为训练集,还有500个人的169396张图作为测试机。VGG2包含大的姿态、年龄、光照、种族和职业变化,由于VGG2是一个质量很高的数据集,我们直接使用而没有进行清洗。
原始的MS-Celeb-1M包含了10万个人的1千万张图,为了降低噪声获得高质量的训练数据,我们每个人相对于其中心进行排序,把那些明显远离中心的样本通过算法自动清洗掉。然后手工检查那些在边界附近的样本。最后我们获得了8万5千个人的380万张图。为了促进本领域的发展,我们把清洗后的数据开源了,但是使用数据时记得遵守原始的License并引用相关的论文。在这里我们的贡献仅是清洗数据而不是发布数据。
验证集 我们使用LFW、CFP和AgeDb作为验证集
LFW包含5749个人13233张图,在姿态、表情和光照上有很大的变化,我们使用6000对样本的协议进行测试。
CFP 包含500个人的10张正面和4张侧面照片。评估协议包含350个同一人和350个不同人的10折交叉验证,在这里我们使用最具挑战性的CFP-FP来汇报性能。
AgeDB 是一个包含姿态、表情、光照和年龄变化的数据,包含440个人的12240张图,演员、科学家和警察等,最小的年龄是3岁,最大的有101岁,每个人平均的年龄跨度达到了49岁。有差5岁、10岁、20岁和30岁 共4个组,这里我们选择难度最大的30岁差的组。
测试数据 MegaFace集是用于人脸识别算法最大的公开数据集,有百万量级。它包含Gallery集和probe集。gallery集包含不同个体的69万张图。probe集由两部分组成:FaceScrub和FGNet。FaceScrub包含530个人的10万张图,其中55742张图是男性,52076张图是女性。FGNet是一个年龄相关的数据,包含82个人的1002张图,每个人有在1-69岁不同年龄的多张图。
收集MegaFace是很耗时间的,不可避免的有很多噪声。对于FaceScrub来说,每个人下理应只有他的照片,对于混淆集来说,不应该含有FaceScrub的图。但是我们发现不仅FaceScrub有噪声,混淆集也含有噪声,这大大影响了性能。
图5给出了FaceScrub上的噪声例子,如图8c所示,我们把所有脸和其中心距离进行排序。实际上221号和136号并不是Aaron Eckhart,我们手工的清洗FaceScrub并且找到了605张噪声图,在测试时我们发现移除后可以提升1%的性能。图6b给出了混淆集的噪声,所有的4张图都是Alec Baldwin,我们手工清洗混淆集最终发现707张噪声图,在测试集我们添加了一个维度来区别其是否噪声后发现移除噪声可以提升15%的性能。
尽管请7个熟悉明星的标注员再三检查,也不能保证里面就没有错误了。我们相信群众的眼睛是雪亮的,我们把这些噪声列表开源了,方便大家更新。
数据集
需要注意的是MegaFace和MS-Celeb-1M中含有很多的噪声
如果需要将采集的图片裁剪成训练所需的大112x112,可使用Take_ID.py进行裁剪和对齐.
(1)拆分分类权重:分类权重W∈Rn×dW∈Rn×d拆分为K份,每个gpu存储其中一份分类权重
(2)求logit值:每个gpu上的特征feature,及其label,经过allgather操作汇聚并广播到每个GPU上,然后和当前gpu上的局部分类权重相乘,获得在这块GPU对应类别上的logit值
(3)求prob值:每块logit 经过allreduce操作可以得到所有gpu上的最大logit值,进而采用allreduce操作,求得exp(logit)和,从而求得概率值prob
(4)求loss值:由于全体样本的标签可能分散在各个GPU上,而在具体一块GPU中,仅有部分样本的标签落在当前GPU上,因此只能计算这些样本产生的loss值。
这块需要对softmax求导有一定的理解
softmax直白来说就是将原来输出是3,1,-3通过softmax函数一作用,就映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们就可以将它理解成概率,在最后选取输出结点的时候,我们就可以选取概率最大(也就是值对应最大的)结点,作为我们的预测目标!
class Arcface(Module):
# implementation of additive margin softmax loss in https://arxiv.org/abs/1801.05599
def __init__(self, embedding_size=512, classnum=51332, s=64., m=0.5):
super(Arcface, self).__init__()
self.classnum = classnum
self.kernel = Parameter(torch.Tensor(embedding_size,classnum))
nn.init.xavier_uniform_(self.kernel)
# initial kernel
self.kernel.data.uniform_(-1, 1).renorm_(2,1,1e-5).mul_(1e5)
self.m = m # the margin value, default is 0.5
self.s = s # scalar value default is 64, see normface https://arxiv.org/abs/1704.06369
self.cos_m = math.cos(m)
self.sin_m = math.sin(m)
self.mm = self.sin_m * m # issue 1
self.threshold = math.cos(math.pi - m)
def forward(self, embbedings, label):
# weights norm
nB = len(embbedings)
kernel_norm = l2_norm(self.kernel,axis=0) # normalize for each column
# cos(theta+m)
cos_theta = torch.mm(embbedings,kernel_norm)
cos_theta = cos_theta.clamp(-1,1) # for numerical stability
cos_theta_2 = torch.pow(cos_theta, 2)
sin_theta_2 = 1 - cos_theta_2
sin_theta = torch.sqrt(sin_theta_2)
cos_theta_m = (cos_theta * self.cos_m - sin_theta * self.sin_m)
cond_v = cos_theta - self.threshold
cond_mask = cond_v <= 0
keep_val = (cos_theta - self.mm) # when theta not in [0,pi], use cosface instead
cos_theta_m[cond_mask] = keep_val[cond_mask]
output = cos_theta * 1.0 # a little bit hacky way to prevent in_place operation on cos_theta
idx_ = torch.arange(0, nB, dtype=torch.long)
output[idx_, label] = cos_theta_m[idx_, label]
output *= self.s # scale up in order to make softmax work, first introduced in normface
return output
insightface: 如何一键刷分LFW 99.80% 从理论到实践, 用insightface构建人证识别系统
- 人脸识别算法演化史 模型评估 人脸识别的LOSS Loss-Functions
- 人脸识别系列之特征比对优化记录
- 从0开始一起玩人脸识别 人脸识别系列
- 如何走近深度学习人脸识别: Additive-Margin-Softmax
- NormFace:99.21% caffe-face:~99% centerloss,ECCV2016
- mobilefacenet-V2:99.66% facenet:lfw 99.65%