摘要:我们从人脸识别技术的技术细节讲起,带你初步了解人脸识别技术的发展过程。通过平台实例的操作,带你看看如何利用公有云的计算资源,快速训练一个可用的人脸识别模型。
Margin Based Classification
Margin based classification 不像在 feature 层直接计算损失的 metric learning 那样对 feature 加直观的强限制,是 依然把人脸识别当 classification 任务进行训练,通过对 softmax 公式的改造,间接实现了对 feature 层施加 margin 的限制,使网络最后得到的 feature 更 discriminative。
Center loss
参考论文:A Discriminative Feature Learning Approach for Deep Face Recognition
ECCV 2016 的这篇文章主要是提出了一个新的 Loss:Center Loss,用以辅助 Softmax Loss 进行人脸的训练,为了让同一个类别压缩在一起,最终获取更加 discriminative 的 features。center loss 意思即为:为每一个类别提供一个类别中心,最小化 min-batch 中每个样本与对应类别中心的距离,这样就可以达到缩小类内距离的目的。下图为最小化样本和类别中心距离的损失函数。
$$
为每个 batch 中每个样本对应的类别中心,和特征的维度一样,用欧式距离作为高维流形体距离表达。因此,在 softmax 的基础上,center loss 的损失函数为:
个人理解 Center loss 就如同在损失函数中加入了聚类的功能,随着训练的进行,样本自觉地聚类在每一个 batch 的中心,进一步实现类间差异最大化。但是我觉得,对于高维特征,欧氏距离并不能反映聚类的距离,因此这样简单的聚类并不能在高维上取得更好的效果。
L-Softmax
原始的 Softmax 的目的是使得,将向量相乘的方式变换为向量的模与角度的关系,即,在这个基础上,L-Softmax 希望可以通过增加一个正整数变量 m,可以看到:
使得产生的决策边界可以更加严格地约束上述不等式,让类内的间距更加的紧凑,让类间的间距更加有区分性。所以基于上式和 softmax 的公式,可以得到 L-softmax 的公式为:
由于 cos 是减函数,所以乘以 m 会使得内积变小,最终随着训练,类本身之间的距离会增大。通过控制 m 的大小,可以看到类内和类间距离的变化,二维图显示如下:
作者为了保障在反向传播和推理过程中能够满足类别向量之间的角度都能够满足 margin 的过程,并保证单调递减,因此构建了一种新的函数形式ψ(θ):
有人反馈 L-Softmax 调参难度较大,对 m 的调参需要反复进行,才能达到更好的效果。
Normface
参考论文:NormFace: L2 Hypersphere Embedding for Face Verification
这篇论文是一篇很有意思的文章,文章对于权重与特征归一化做了很多有意思的探讨。文章提出,sphereface 虽然好,但是它不优美。在测试阶段,sphereface 通过特征间的余弦值来衡量相似性,即以角度为相似性度量。但在训练过程中也有一个问题,权重没有归一化,loss function 在训练过程中减小的同时,会使得权重的模越来越大,所以 sphereface 损失函数的优化方向并不是很严谨,其实优化的方向还有一部分去增大特征的长度了。有博主做实验发现,随着 m 的增大,坐标的尺度也在不断增大,如下图所示。
因此作者在优化的过程中,对特征做了归一化处理。相应的损失函数也如下所示:
其中 W 和 f 都为归一化的特征,两个点积就是角度余弦值。参数 s 的引入是因为数学上的性质,保证了梯度大小的合理性,原文中有比较直观的解释,可以阅读原论文,并不是重点。s 既可以变成可学习的参数,也可以变成超参,论文作者给了很多推荐值,可以在论文中找到。其实,FaceNet 中归一化的欧氏距离,和余弦距离是统一的。
AM-softmax/CosFace
参考论文:Additive Margin Softmax for Face Verification
CosFace: Large Margin Cosine Loss for Deep Face Recognition
看上面的论文,会发现少了一个东西,那就是 margin,或者说是 margin 的意味少了一些,所以 AM-softmax 在归一化的基础上有引入了 margin。损失函数如下:
直观上来看,比更小,所以损失函数值比 Normface 里的更大,因此有了 margin 的感觉。m 是一个超参数,控制惩罚,当 m 越大,惩罚越强。该方法好的一点是容易复现,而且没有很多调参的 tricks,效果也很好。
ArcFace
与 AM-softmax 相比,区别在于 Arcface 引入 margin 的方式不同,损失函数:
乍一看是不是和 AM-softmax 一样?注意 m 是在余弦里面。文章指出基于上式优化得到的特征间的 boundary 更为优越,具有更强的几何解释。
然而这样引入 margin 是否会有问题?仔细想 cos(θ+m) 是否一定比 cos(θ) 小?
最后我们用文章中的图来解释这个问题,并且也由此做一个本章 Margin-based Classification 部分的总结。
这幅图出自于 Arcface,横坐标为 θ 为特征与类中心的角度,纵坐标为损失函数分子指数部分的值(不考虑 s),其值越小损失函数越大。
看了这么多基于分类的人脸识别论文,相信你也有种感觉,大家似乎都在损失函数上做文章,或者更具体一点,大家都是在讨论如何设计上图的 Target logit-θ 曲线。
这个曲线意味着你要如何优化偏离目标的样本,或者说,根据偏离目标的程度,要给予多大的惩罚。两点总结:
太强的约束不容易泛化。例如 Sphereface 的损失函数在 m=3 或 4 的时候能满足类内最大距离小于类间最小距离的要求。此时损失函数值很大,即 target logits 很小。但并不意味着能泛化到训练集以外的样本。施加太强的约束反而会降低模型性能,且训练不易收敛。
选择优化什么样的样本很重要。Arcface 文章中指出,给予 θ∈[60° , 90°] 的样本过多惩罚可能会导致训练不收敛。优化 θ ∈ [30° , 60°] 的样本可能会提高模型准确率,而过分优化 θ∈[0° , 30°] 的样本则不会带来明显提升。至于更大角度的样本,偏离目标太远,强行优化很有可能会降低模型性能。
这也回答了上一节留下的疑问,上图曲线 Arcface 后面是上升的,这无关紧要甚至还有好处。因为优化大角度的 hard sample 可能没有好处。这和 FaceNet 中对于样本选择的 semi-hard 策略是一个道理。
Margin based classification 延伸阅读
A discriminative feature learning approach for deep face recognition [14]
提出了 center loss,加权整合进原始的 softmax loss。通过维护一个欧式空间类中心,缩小类内距离,增强特征的 discriminative power。
Large-margin softmax loss for convolutional neural networks [10]
Sphereface 作者的前一篇文章,未归一化权重,在 softmax loss 中引入了 margin。里面也涉及到 Sphereface 的训练细节。
使用 ModelArts 训练人脸模型
人脸识别算法实现解释
本文我们部署的人脸识别算法模型主要包括两部分:
第一部分为人脸检测算法模型,该模型将图片中的人脸进行识别,返回人脸的位置信息;
第二部分为人脸特征表示算法模型,也称之为识别模型。这个部分将 crop 出的人脸图像 embedding 到一个固定维度大小的向量,然后利用该向量与底库进行比对,完成人脸识别的整体流程。
如下图 9 所示,整体算法实现的流程分为线下和线上两个部分,在每次对不同的人进行识别之前首先利用训练好的算法生成人脸标准底库,将底库数据保存在 modelarts 上。然后在每次推理的过程中,图片输入会经过人脸检测模型和人脸识别模型得到人脸特征,然后基于该特征在底库中搜索相似对最高的特征,完成人脸识别的过程。
图 9 ModelArts 中人脸识别算法实现流程
在实现过程中,我们采用了基于 Retinaface+resnet50+arcface 的算法完成人脸图像的特征提取,其中 Retinaface 作为检测模型,resnet50+arcface 作为特征提取模型。
在镜像中,运行训练的脚本有两个,分别对应人脸检测的训练和人脸识别的训练。
人脸检测的训练脚本为:run_face_detection_train.sh
该脚本的启动命令为 sh run_face_detection_train.sh data_path model_output_path
其中 model_output_path 为模型输出的路径,data_path 为人脸检测训练集的输入路径,输入的图片路径结构如下:
人脸识别的训练脚本为:run_face_recognition_train.sh
该脚本的启动命令为 sh run_face_recognition_train.sh data_path model_output_path
其中 model_output_path 为模型输出的路径,data_path 为人脸检测训练集的输入路径,输入的图片路径结构如下:
底库生成的脚本:run_generate_data_base.sh
该脚本的启动命令为:sh run_generate_data_base.sh data_path detect_model_path recognize_model_path db_output_path
其中 data_path 为底库输入路径,detect_model_path 为检测模型输入路径,recognize_model_path 为识别模型输入路径,db_output_path 为底库输出路径。
底库生成的脚本:run_face_recognition.sh
该脚本的启动命令为:sh run_generate_data_base.sh data_path db_path detect_model_path recognize_model_path
其中 data_path 为测试图片输入路径,db_path 为底库路径,detect_model_path 为检测模型的输入路径,recognize_model_path 为识别模型的输入路径
训练过程
华为云 ModelArts 有训练作业的功能,可以用来作模型训练以及对模型训练的参数和版本进行管理。这个功能对于多版本迭代开发的开发者有一定的帮助。训练作业中有预置的一些镜像和算法,当前对于常用的框架均有预置镜像(包括 Caffe, MXNet, Pytorch, TensorFlow )和华为自己的昇腾芯片的引擎镜像(Ascend-Powered-Engine)。
本文我们会基于 ModelArts 的自定义镜像特性,上传自己在本机调试完毕的完整镜像,利用华为云的 GPU 资源训练模型。
我们是想在华为云上的 ModelArts 基于网站上常见的明星的数据训练完成一个人脸识别模型。在这个过程中,由于人脸识别网络是工程师自己设计的网络结构,所以需要通过自定义镜像进行上传。所以整个人脸训练的过程分为以下九步:
构建本地 Docker 环境
从华为云下载基础镜像
根据自己需求构建自定义镜像环境
导入训练数据到自定义镜像
导入人脸识别底库到自定义镜像
导入预训练模型到自定义镜像
上传自定义镜像到 SWR
使用华为云训练作业进行训练
使用华为云进行推理工作
构建本地 Docker 环境
Docker 环境可以在本地计算机进行构建,也可以在华为云上购买一台弹性云服务器进行 Docker 环境构建。全过程参考 Docker 官方的文档进行:
https://docs.docker.com/engine/install/binaries/#install-static-binaries
从华为云下载基础镜像
官网说明网址:
我们训练需要使用到的是 MXNet 的环境,首先需要从华为云上下载相对应的自定义镜像的基础镜像。官网给出的下载命令如下:
在训练作业基础镜像的规范里,找到了这个命令的解释。
https://support.huaweicloud.com/engineers-modelarts/modelarts_23_0217.html
根据我们的脚本要求,我使用的是 cuda9 的镜像:
官方还给出了另一种方法,就是使用 docker file 的。基础镜像的 dockerfile 也是在训练作业基础镜像的规范里找到的。可以参考一下的 dockerfile:
https://github.com/huaweicloud/ModelArts-Lab/tree/master/docs/custom_image/custom_base
根据自己需求构建自定义镜像环境
由于比较懒,所以还是没有使用 Dockerfile 的方式自己构建镜像。我采用的是 另一种方式!
因为我们的需求就是 cuda 9 还有一些相关的 python 依赖包,假设官方的镜像提供的是 cuda 9 的,我们大可以在训练脚本中跟着这个教程加一个 requirement.txt。简单高效快捷就能解决需求!!!下面是教程~~~
https://support.huaweicloud.com/modelarts_faq/modelarts_05_0063.html
上传自定义镜像到 SWR
官网教程:
上传镜像的页面写着,文件解压后不得超过 2GB。但是官方提供的基础镜像就 3.11GB,我们加上需要的预训练的模型后镜像是 5+GB,所以不能使用页面进行上传的工作,必须使用客户端。上传镜像首先要创建组织,
如果觉得产品文档理解还是比较难,可以尝试一下 SWR 页面的 pull/push 镜像体验:
这里后面引导了客户如何将本地镜像推上云端,第一步是登陆仓库:
第二步拉取镜像,这个我们就用自己打的自定义镜像代替,
第三步修改组织,使用根据产品文档创建的组织名。在这一步需要将本地的一个镜像重命名为云上识别的镜像命。具体看下图解释:
第四步推送镜像,
当熟练掌握这四步技巧的时候,可以脱离这个教程,使用客户端进行上传。使用客户端登陆然后上传。客户端登陆可以使用生成临时 docker loging 指令。这个页面在”我的镜像“-> ”客户端上传“->”生成临时 docker login 指令“中:
在本地 docker 环境中,使用这个生成的临时 docker login 指令登陆后,使用下面的命令进行上传镜像:
使用华为云训练作业进行训练
华为云 ModelArts 提供训练作业给用户进行模型训练。在训练作业中有预置镜像和可以选择自定义镜像。预置的镜像包含市面上大部分框架,没有特殊要求的时候,使用这些框架的镜像进行训练也是很方便的。本次测试还是使用的自定义镜像。
自定义镜像中不仅需要在镜像中进行配置自己的环境,假如改变了训练作业启动的方式,还需要修改训练的启动脚本。从华为云 ModelArts 官网拉取下来的官方镜像的/home/work/路径下有一个启动脚本”run_train.sh”,自定义的启动脚本需要基于这个脚本进行修改。主要是要注意 “dls_get_app”,这个是从 OBS 下载相关的命令。其他的部分根据自己的训练脚本进行修改。
如果需要上传训练结果或者模型到 OBS,需要参考”dls_get_app”加”dls_upload_model”的命令。在我们这次训练中,上传的脚本如下:
训练作业进行调试的时候,当前可以使用免费提供的一小时 V100。ModelArts 的训练作业一个比较好的地方是方便了我们版本管理。版本中会记录所有通过运行参数传入到训练脚本里的所有参数,还可以使用版本对比进行参数对比。还有个比较方便的地方是可以基于某一个版本进行修改,减少了重新输入所有参数这一步骤,比较方便调试。
在训练作业中训练完成后,还可以在 ModelArts 中进行模型部署上线。
作者介绍
Leon Li:浙江大学光电系毕业生,在大华工作 2 年后,18 年 4 月入职华为,一直从事图像算法的优化以及相关产品落地的工作。
Hannah:英国 UCL(伦敦大学学院)数据科学毕业生, 2018 年 6 月应届毕业生身份入职华为,机器学习平台算法落地负责人。
麦克周:在华为,每一位员工都会有自己的导师,我是 Leon 和 Hannah 的领路人。
评论