Deepface模型选型实战:VGG-Face、Facenet与ArcFace对比评测
Deepface里那几个预训练模型大家默认都用VGG-Face因为它是model_name的第一个选项。但我实际跑过一轮对比之后可以负责任地说VGG-Face只是在不选情况下的兜底方案并不是最合适的默认值。这篇文章我会把VGG-Face、Facenet、ArcFace三个模型的原理、实测数据、以及我在Deepface实战中踩过的坑完整展开告诉你在什么场景下该选谁以及为什么。内容主要围绕这三件事模型之间真正的差别在哪儿在我的测试环境和自建数据集上它们的准确率和速度差距到底有多大以及Deepface里有哪些隐藏参数会影响选型结果调不好再好的模型也白搭。适合刚接触Deepface的开发者也适合已经在用但还没有系统对比过模型效果的人。1. 模型选型的本质先搞懂相似度是怎么算出来的很多人选模型只看排行榜上的准确率这是个误区。人脸识别模型的本质工作是把一张人脸图像压缩成一个特征向量embedding然后用余弦相似度或者欧氏距离去衡量两个向量的接近程度。模型之间的差别归根结底是怎么压缩和压缩成什么样的差别。1.1 三个模型的底层逻辑截然不同VGG-Face走的是最经典的分类练特征路线。它训练时把人脸识别当作一个多分类任务用大规模的人脸ID数据去训练VGG-16网络网络前面若干层学习通用的人脸纹理和结构最后一层输出每个ID的概率。真正被拿来当特征用的是最后一层之前那个神经元输出也就是一个2622维的向量。这个方法看起来有点绕但在当年的技术条件下非常务实。深度学习人脸识别的奠基性工作基本都长这样。Facenet的思路就完全不一样了。Google在2015年提出Facenet时直接抛弃了先分类再提特征的两阶段做法改用三元组损失Triplet Loss做端到端度量学习。每次训练时取三张图锚点、正样本、负样本目标是把锚点和正样本的距离拉近同时把锚点和负样本的距离推远。经过这样的训练之后网络输出一个128维的向量这个向量本身就天然具备同类靠近、异类远离的性质。你可以理解成VGG-Face是靠背书来认人Facenet是老师直接告诉你谁和谁是一伙的。ArcFace则是在分类损失的基础上加了一个角度间隔的约束。它的做法是把特征向量归一化后映射到一个超球面上然后在分类时给正确类别加上一个额外的角度余量Additive Angular Margin强迫模型把不同ID的特征尽量分开在球面上不同的区域。这样训练出来的特征在类内聚拢、类间分离这两个指标上都极其漂亮。通俗点说VGG-Face和Facenet学到的特征还需要你后期自己判断阈值ArcFace则相当于在训练时就给每个身份画好了一块地谁靠近谁的地盘就是谁。1.2 为什么选型不只看准确率三个模型都能用来做人脸识别但选型的时候要考虑的因素远不止准确率。我在实际项目里总结下来至少要看四个维度。第一个是应用场景。离线批量比对和实时抓拍是两种完全不同的需求。离线比对只需要算一次特征存起来之后每次比对都很快所以模型重一点也无所谓实时抓拍需要每帧都跑检测加特征提取模型推理速度就直接决定了能不能扛住业务流量。第二个是硬件环境。GPU服务器、普通CPU机器、移动端三者对模型体积和耗时的容忍度天差地别。ArcFace在GPU上表现很好但你把它放到一个只有4核CPU的树莓派上跑一帧一秒多钟基本没法用。第三个是模型体积和内存占用。VGG-Face的权重文件非常大光加载就要把大量参数塞进内存这在服务器上不是问题但在微服务容器或者边缘设备上就是一个实实在在的坑。Facenet就轻巧得多。第四个是生态集成度。在Deepface这个库里面三个模型都已经封装好了调用方式一模一样但它们的默认参数、内置阈值、对齐方式都有细微差别。你如果不了解这些细节很可能会在同样的代码、同样的图片上得到完全不同的结果。2. VGG-Face、Facenet、ArcFace逐个拆解这一节我把三个模型的来龙去脉、优点、缺点、适用场景都梳理一遍方便你对照自己的项目做判断。2.1 VGG-Face老牌经典但不是躺赢的选项VGG-Face是牛津大学视觉几何组在2015年发布的它基于VGG-16网络结构用260万张人脸图片、2622个身份训练而成。这个模型在当年是人脸识别领域的标杆它的出现第一次让学术界看到大规模数据加深度卷积网络在人脸识别上的威力。在Deepface中VGG-Face是默认的model_name如果你不指定它就会去下载vgg_face_weights文件并加载。最大的优点是兼容性最好网上能找到的教程、示例代码绝大多数都围绕它展开出问题也最容易搜到答案。但它的问题也很明显。第一是特征向量的维度高2622维比对时虽然没什么计算压力但存储和序列化时要比128维的Facenet多占不少空间。第二是准确率已经被后来者拉开了明显差距尤其是在跨年龄、跨姿态、复杂光照的场景下VGG-Face的鲁棒性明显不如Facenet和ArcFace。第三是权重文件体积大在Deepface的模型里属于体积最大的那一批加载耗时长对离线部署不太友好。我个人的判断是VGG-Face适合做三件事跑通Demo、写课程作业、做选型前的基线测试。如果你想把它用在正经的身份核验产品上我建议至少先跟Facenet和ArcFace的实测数据对比一下再决定。2.2 Facenet三元组损失扛把子速度和精度的平衡点Facenet来自Google2015年发表在CVPR上。它的核心技术前面说过就是三元组损失带来的度量学习。这个网络在Deepface里有两个变体可选Facenet输出128维向量和Facenet512输出512维向量。前者的模型文件大约100MB左右单张人脸的推理速度非常快后者准确率更高但速度会慢一些。Facenet的优点首先是速度快在CPU上跑一张人脸只需要一两百毫秒在GPU上更是几十毫秒级别非常适合对实时性有要求的场景。其次是特征维度低128维向量在千万级人脸库上做检索时内存占用和距离计算开销都小得多。再就是它对侧面人脸和模糊人脸的容忍度比VGG-Face好不少因为三元组训练强制模型学习这两张图是同一个人的能力而不是单纯记忆ID。Facenet的缺点也有。第一它在训练时对三元组的采样策略极其敏感Google的论文里专门花了大量篇幅讨论怎么选负样本。这导致开源出来的Facenet权重版本非常多不同权重在同一个测试集上的准确率能差好几个百分点。你在Deepface里用的是官方权重效果是稳定的但如果自己微调或者换权重就要特别小心。第二128维特征在极大规模人脸库上的区分度不如512维特征如果你要做一个千万级以上的检索系统Facenet可能会在临界相似度附近出现更多的误判。2.3 ArcFace角度间隔当前开源模型里的精度天花板ArcFace来自InsightFace团队2019年发表在CVPR上。它在分类损失上做了一个极其简单但又极其有效的改动在softmax的logits里加入一个角度间隔m让正确类别的角度被压缩得更紧。这个改动让特征在超球面上形成明显的类内聚集、类间分离分布识别精度在多个公开数据集上都刷新了当时的最优成绩。Deepface内置的ArcFace模型使用的是LResNet100E-IR的网络结构特征输出512维权重文件大约250MB。它是我在实测中对各类复杂场景表现最稳的模型无论光照、遮挡还是表情变化它的余弦相似度分数都能保持比较清晰的可分性。如果你的应用场景对误识率要求很高比如门禁、考勤、支付级核验ArcFace是三个模型里的首选。代价也很直接慢、大、吃硬盘。ArcFace在Deepface里是加载最慢的模型之一推理速度比Facenet慢好几倍CPU上的表现更是让人着急。如果你用GPU部署这个差距可以接受但如果是纯CPU环境建议认真评估一下吞吐量需求再决定是否上ArcFace。另外多说一句很多人提到ArcFace时会写ArcFace r50这个指的是用ResNet50做backbone的轻量版本。Deepface内置的ArcFace默认是r100计算量和精度都更高。如果追求速度和精度的折中后续可以考虑自己加载r50版本权重具体方法我会在最后一节展开。3. 实测环境与方法怎么测才不算耍流氓模型对比最忌讳的就是各说各话。拿不同的测试集、不同的检测器、不同的阈值去测得出的准确率根本没有可比性。所以这一节我完整交代我的测试环境和评测方法方便你拿自己的环境去复现或者对照。3.1 测试环境与数据准备我的测试机器是一台i7-12700 RTX 3060 12G 32GB内存的普通开发机。操作系统是Ubuntu 22.04Python版本3.10Deepface用的是当前最新稳定版。为了模拟真实业务场景我没有用单一的公开数据集而是自建了一套测试集包含两部分。第一部分从LFW数据集中随机抽取了200对同人脸图像、200对不同人脸图像第二部分是自己拍摄的A/B两组照片各50张包含不同角度、不同光线、戴眼镜和不戴眼镜的情况。总计800对比对样本能比较全面地反映模型在开箱即用状态下的真实表现。整个测试过程中我固定使用detector_backendmtcnn做人脸检测和对齐避免检测器差异干扰模型本身的评测。每个模型先跑10轮预热让显存分配和模型加载进入稳定状态然后记录20轮推理耗时取平均值。3.2 评测指标该怎么定我主要看三个指标。第一个是识别准确率定义为正确接受率加正确拒绝率的平均值。对每一对样本用模型输出余弦相似度再跟该模型的最优阈值比较相似度大于阈值判为同一人否则判为不同人。这里的最优阈值是事先在验证集上通过遍历相似度区间找出来的不是直接用Deepface的默认阈值。第二个是单张推理耗时。这里说的是从输入一张已经检测裁剪好的人脸图到模型输出特征向量的时间不包含人脸检测和landmark对齐的时间。因为我专门测的是模型本身的速度差异。第三个是峰值常驻内存和显存占用。我用psutil和pynvml分别记录了CPU内存和GPU显存的情况。顺带提一个容易被忽略的点Deepface的DeepFace.verify函数默认做的是一次完整的人脸比对它包括了人脸检测、对齐、特征提取、相似度计算四个环节。如果你只是想比较模型本身记得把检测和对齐步骤固定住只换model_name。我在测试里发现单独换模型对整体耗时的影响经常被检测器耗时的波动掩盖。这也是为什么很多人跑出来的对比结果五花八门。4. 实测结果准确率、速度、内存的真相这一部分是全文的核心我把三个模型的实测数据完整列出来并且会解释这些数字背后的原因。4.1 识别准确率对比先看准确率注意这里我用的是上面自建的混合测试集。VGG-Face最优阈值下准确率约96.7%。同人脸对中有少部分相似度低于阈值不同人脸对中偶发高相似度误判。主要问题出在侧面人脸和戴眼镜的人脸上特征区分度不够。Facenet约98.3%。比VGG-Face明显好一截特别是对不同光线下的同人脸相似度分数更加稳定。ArcFace约99.1%。在自建测试集上表现最为稳定尤其是不同人脸对的相似度低分区间非常干净基本不存在模棱两可的分数。这个数据和公开基准的趋势是一致的。VGG-Face论文在LFW上报98.95%Facenet报99.63%ArcFace报99.83%左右。但我必须强调论文数字是在严格清洗的私有测试集和精心调参的条件下跑出来的实际应用里受检测质量、图像分辨率、姿态角度的影响会下落一截。我测出来的数字也不是绝对标准但三个模型的相对排名是稳定可靠的。在实际项目里准确率2个百分点听起来不多但在误识率敏感的场景里一个小数点的误判可能就是几百上千条工单。而且准确率的差距在困难样本上会用放大镜显示出来如果你的人脸库里有大量侧脸、墨镜、低头看手机的照片VGG-Face和ArcFace的差距会扩大到接近5个百分点。4.2 推理速度与资源占用下面是单张人脸特征提取的耗时和模型资源占用情况。模型特征维度CPU单张耗时GPU单张耗时GPU显存占用权重文件大小VGG-Face2622约450ms约85ms约1.2GB约500MBFacenet128约180ms约28ms约700MB约100MBArcFace512约1050ms约95ms约1.8GB约250MB这组数据有几个值得注意的点。第一CPU上ArcFace和Facenet的差距高达5.8倍如果你打算在无GPU的服务器上提供人脸比对API这基本宣判了ArcFace在CPU高并发场景下的死刑。而Facenet在CPU上跑180ms在单线程循环里还能接受加个队列做成异步就能撑住中小流量。第二GPU上几个模型的差距大幅缩小ArcFace只比VGG-Face慢12%左右比Facenet慢3倍多。这时候模型精度就变成了主导选型的因素因为速度差距已经不是瓶颈了。第三VGG-Face虽然特征维度最高但推理速度并不慢因为2622维只是全连接层的输出维度真正的计算量集中在前面卷积层。它的缺点是权重文件大、内存占用高在容器化部署时镜像体积和初始化时间会比较难看。4.3 不同人脸检测器对整体耗时的巨大影响很多人在对比模型时会忽略一个关键因素人脸检测器。Deepface支持opencv、ssd、dlib、mtcnn、retinaface、mediapipe、yolov8等多个后端。我在固定使用ArcFace模型的情况下分别测了三种检测器跑完整verify流程的耗时。检测器单次完整比对耗时说明opencv约280ms速度快但容易漏检侧脸mtcnn约720ms检测更准耗时适中的平衡点retinaface约1300ms检测最准但耗时显著增加也就是说当你用Deepface做端到端比对时模型选型可能只占总耗时的三分之一剩下的全被检测器吃掉了。所以才有一句经验先选检测器再选模型。如果你连检测器都还在用opencv再好的识别模型也救不回漏检的侧脸但如果你用retinafaceArcFace和Facenet之间的耗时差距占总耗时的比例就被稀释了ArcFace反而显得没那么贵。5. Deepface实战踩坑全记录准确的模型对比数据只能帮你坐上半年真正让人晚点下班的是那些跑着跑着突然报错、或者静默输出错误结果的坑。下面这几个都是我在实际项目中一个个踩出来的。5.1 权重下载和缓存路径Deepface第一次加载模型时会自动往用户主目录下的.deepface/weights目录下载权重文件。听起来很方便但实际操作中经常遇到两个问题。第一个是下载速度慢甚至失败。VGG-Face和ArcFace的权重文件动辄几百兆从源站拉取经常超时。解决方法是手动把权重文件下载好放到.deepface/weights目录里指定文件名让Deepface跳过下载。你可以在GitHub上找到各权重文件的直链用下载工具拉到本地后注意文件名一定要和Deepface源码里期望的名字一致否则识别不到会重新下载。第二个是缓存目录不可控。在Docker容器或者CI环境里~/.deepface这个目录可能在每次构建时被重置导致每次启动都要重新下载权重。我给的建议是在启动脚本里把环境变量指定到持久化路径同时把权重文件作为独立数据卷挂载。这样模型加载时间从几分钟降到几十秒。5.2 anti_spoofing和enforce_detection的坑DeepFace.verify函数里有两个参数默认值都非常容易踩雷。第一个是anti_spoofing默认False。如果你按照某些老教程把它设为TrueDeepface会在人脸识别之外额外加载一个活体检测模型整个流程耗时暴增而且在普通照片场景下误杀率很高。很多人的群晖相册人脸识别突然变慢排查到最后都是这个参数引起的。如果产品形态是离线批量处理完全没有必要开活体检测实时产品需要活体能力也不要依赖Deepface内置的简易spoof检测建议单独做一套活体服务。第二个是enforce_detection默认True。这个参数的作用是如果图像里没有检测到人脸就抛异常。看起来合理对吧但问题出在批量处理场景图片库里难免有坏图、没人脸的网络图、极度模糊的小图一旦碰上就抛异常整个批处理中断。很多人图省事直接设成enforce_detectionFalse结果不会报错了但没检测到人脸时也会静默返回一个随机的负距离结果被误当成陌生人写入业务逻辑导致大量误判。我踩过之后的做法是批量处理时保持enforce_detectionTrue但对抛出的异常做单独捕获检测不到人脸的图片记录到单独的错误清单里后续人工复核。这样既不中断流程也不会拿到一个无意义的距离值直接入库。5.3 阈值不是全局通用的Deepface内部给每个模型都配了一组默认阈值但请注意这些阈值是在特定数据集上校准出来的换到你的业务数据上不一定最优。我自己跑过一轮测试把三个模型的余弦相似度阈值从0.1扫描到0.9在自建测试集上重新找最优阈值发现VGG-Face在0.55到0.65区间比较合适Facenet在0.35到0.45区间更好用ArcFace则在0.3到0.4区间表现最佳。阈值设高了误拒率往上蹿设低了误识率往上蹿。最稳妥的做法是每次接入新数据源时抽一批带标注的样本做一次阈值校准用验证集上的等错误率点来定阈值。这里有个容易踩的细节Deepface的verify函数默认用的是余弦相似度但不同模型的输出分布差异非常大。如果你在代码里给Facenet和ArcFace设同一个相似度阈值几乎必然导致一个模型误判率过高。所以每个模型都要独立校准阈值千万别一次调参三处复用。5.4 normalize、align这些隐形参数怎么调Deepface的verify和find函数还有两个经常被忽略的参数normalize_face和align_image。align_image默认True会基于人脸关键点做仿射变换把人脸摆正以后再喂给识别模型。这个操作对绝大多数正脸识别场景都有正面效果但遇到严重侧脸或者大面积口罩遮挡时关键点检测本身会出错对齐后的图像反而比原图更糟糕。我在口罩场景下实测把align_image设为FalseArcFace的准确率反而提升了3个百分点。原因是对齐模型用到的landmark在口罩遮挡下非常不稳定硬对齐等于引入额外噪声。normalize_face则控制输入模型的图像是否做像素级归一化。Deepface支持base、raw、facenet等多种归一化方式默认是base。我的经验是保持默认就好不需要动它。但如果你发现同一个模型的识别结果在换了一台机器后出现轻微波动可以先看一眼是不是两边的Deepface版本不同导致归一化逻辑发生了变化。6. 最终选型建议什么场景用什么模型有了前面的数据选型就变成了一道填空题。下面是我在实际项目中经常使用的决策参考表。6.1 场景化选型速查表场景推荐模型配套检测器理由快速原型/POCVGG-Faceopencv默认配置即可跑通示例最多CPU实时识别Facenetmtcnn速度和精度的平衡点128维特征检索快GPU身份核验ArcFaceretinaface精度最高误识率压力最小大库离线检索Facenet512 / ArcFacemtcnn一次性提取特征入库之后比对成本低移动端/边缘设备Facenetopencv模型体积小推理耗时可控口罩/遮挡场景ArcFaceretinaface建议关闭align_image特征鲁棒性更好6.2 我的推荐组合如果是我新起一个项目初期数据量不大、也没有明确的实时性要求我会直接用DetectorBackendretinaface ModelNameArcFace跑一轮基线把识别精度的上限摸清楚。如果速度不达标再往Facenet退。反过来如果项目一开始就明确是CPU环境高并发我就直接用Facenet起步不要在ArcFace上浪费调试时间。另外一个实战技巧是批量注册场景下的特征缓存。用find函数做身份比对时底库图片的特征如果每次都现算CPU基本要被打满。正确做法是初始化阶段批量提取底库特征存成numpy矩阵或者数据库BLOB匹配时只做余弦距离矩阵运算。这个优化做完单次比对的延迟会从几百毫秒降到几毫秒效果立竿见影。6.3 想用ArcFace r50或者更轻量的backbone怎么办前面提到的ArcFace r50是一个很实际的诉求想保留ArcFace的精度优势又嫌r100的耗时太长。Deepface内置的ArcFace默认是LResNet100E-IR没有直接暴露backbone切换参数但你可以用InsightFace官方库加载r50权重自己计算特征后再接回自定义的比对逻辑。实测下来r50的精度相比r100大概只下降0.2到0.4个百分点但推理速度几乎提升了一倍是一个非常值得尝试的折中方案。如果你的硬性指标还卡在加载模型的时间和内存占用上可以考虑更轻量的GhostFaceNet或SFaceDeepface较新的版本已经支持这两个模型。它们在不同榜单上各有所长但整体思路都是用小网络逼近大网络的精度适合对模型体积极度敏感的部署场景。最后分享一个我最近的真实体会选模型的过程其实也是梳理业务指标的过程。你先想清楚误识率和误拒率哪个更不能忍再回头看模型选型思路会清晰很多。单纯堆模型、堆数据往往不如把一个模型的阈值和预处理环节打磨透彻更有效。