人脸识别相关的模型训练,我前后做了挺长时间。坦白说,刚开始接手第一版人脸识别模型的时候,我以为这东西跟普通分类任务没多大区别——给一堆人脸图片,训一个分类器,最后把特征抽出来算相似度。真正跑起来才发现,这套看似顺理成章的流程里,坑一个接一个:数据清洗的工作量远超预期、训练收敛慢得让人怀疑人生、Loss曲线看着挺正常但拿到测试集上效果直接拉胯。如果你正在做人脸识别模型训练,或者正打算把手里的分类模型往人脸识别方向靠,这篇文章或许能帮你少踩一些我踩过的坑。我尽量把能落地的经验都写出来,包括数据、结构、损失函数、训练参数、评估、部署以及各种玄学问题排查,都是实际跑过的记录。
1. 项目概述:人脸识别模型训练到底难在哪
1.1 任务定义与人脸识别的特殊性
人脸识别的本质是学习一个“映射”:把一张人脸图像映射成一个固定长度的特征向量,让同一个人的不同照片在向量空间里距离足够近,不同人的照片距离足够远。听起来像是一个很标准的度量学习问题,但真正难点在于数据的特殊性。
首先是类内差异大。同一个人的脸会随年龄、姿态、光照、表情、妆容、遮挡发生剧烈变化,甚至同一个人的两张照片可能比两个不同人的照片差异还大。其次是类间差异小。两个长的很像的人,或者某些人脸的局部特征高度相似,模型很容易把不同人强行拉近。这两个特性叠加在一起,导致人脸识别模型对特征判别性的要求比普通图像分类高得多。
还有一个容易忽略的点:人脸识别本质上是一个开放集问题。训练时你只有有限的身份数,但部署时遇到的人脸可能完全不在训练集里。所以模型必须学到真正语义层面的身份特征,而不是单纯记住训练集里的人脸。这也是为什么人脸识别训练要比普通分类多做很多“防死记硬背”的工作。
1.2 技术选型的核心取舍
我在做技术选型时重点考虑了三件事:训练框架、骨干网络、损失函数。
框架我最终选了 PyTorch,原因很实际:调试方便,分布式训练生态成熟,社区里能参考的开源实现最多。虽然 TensorFlow 在部署侧也有优势,但对我们这种需要频繁改模型结构、调损失函数的场景来说,PyTorch 的灵活性和调试体验明显更好。
骨干网络方面,第一版我用了 ResNet50,后面为了上线速度换过 MobileFaceNet 这类轻量网络,也试过 ResNet100 这种大模型刷精度。ResNet50 属于性价比最高的选择,训练速度适中,精度足够支撑大多数场景;ResNet100 能把精度往上顶一档,但训练和推理代价都明显增加;轻量网络则适合端侧或实时需求,精度会有一定损失。我的建议是:如果刚开始做,先用 ResNet50 把整条链路跑通,后续再根据场景换网络,不要一上来就堆大模型,否则出了问题排查起来非常痛苦。
损失函数是影响最终效果最关键的一环。我最终确实选择了基于加性角度间隔的 ArcFace 这类损失函数,后面在第三节专门展开讲原因。这里简单说结论:在主流方案里,基于 margin 的 softmax 变体在人脸识别效果上明显优于原生 softmax 和三元组损失,是被大量实验验证过的“标准答案”。
2. 数据才是真正的护城河
2.1 数据来源、规模与清洗流程
人脸识别模型的效果上限很大程度由数据决定。我第一版模型用的是公开数据集加一部分自采数据,初始规模大概有数十万张图、几万个身份。后来逐步扩充到百万级数据,效果才有了质的提升。
但这个过程中最痛苦的环节不是收集数据,而是数据清洗。公开数据集虽然量大,但里面混着大量脏数据:错误标签、重复图片、模糊人脸、非人脸图像、同一身份下混入其他人脸等。如果不做清洗,模型学到的东西就是一团糟,训练 Loss 看似下降,实际上特征空间完全是乱的。
我的清洗流程分四步走:
- 初筛:用现成的分类模型对每张图做人脸检测和质量打分,把检测置信度低、模糊度过高、人脸占比过小的图片直接过滤掉。
- 聚类去噪:对每个身份下的图片提取特征做聚类,把偏离主簇的图片挑出来人工确认,一般是错标或混入的其他身份。
- 去重:用特征相似度做全局去重,把完全重复或极度相似的图片保留一张,避免模型在重复数据上过拟合。
- 人工抽检:每个身份随机抽几张图看一遍,重点检查标签是否一致、姿态是否单一、是否存在性别或年龄明显错乱的情况。
这里要特别提醒,自采数据涉及个人肖像信息,采集和使用前必须取得明确授权,整个处理流程要做好脱敏和权限管控,这不是走形式,而是底线问题。
2.2 人脸检测与对齐的标准做法
人脸检测和对齐是训练前端的两个必备环节。检测不好说,后面全白搭;对齐不对,模型学到的特征就会混入姿态干扰。
检测我用的主要是 MTCNN 和 RetinaFace 这类开源方案。MTCNN 速度快、部署简单,适合批量离线处理;RetinaFace 在困难场景下的召回率更高,对遮挡、极端姿态更鲁棒。实际批量清洗数据时,我会优先用 RetinaFace,宁可慢一点也要把绝大多数人脸找出来。
对齐这一步非常关键,很多人容易忽略。人脸的旋转、缩放、偏移如果不做归一化,模型就得额外学习这些变化,等于浪费了大量表达能力。标准做法是:检测人脸后定位眼睛、鼻子、嘴角等关键点,然后通过相似变换把人脸标准到固定尺寸,通常是 112x112。这一步做完,训练样本的分布会规整很多,模型更容易收敛,最后得到的特征也更有判别力。
对齐坐标的选择也有讲究。5 点关键点(两眼、鼻尖、两个嘴角)是最常用的;如果要处理大姿态场景,可以考虑 68 点或更多关键点做更精细的对齐,但计算量会上升,通常 5 点就够用了。
2.3 数据增强怎么做才有效
数据增强是人脸识别训练中容易被低估的一项。很多人只在分类任务里用过翻转和裁剪,但人脸识别对姿态、光照、遮挡的敏感度更高,增强策略应该更有针对性。
我试下来最有效的一组增强包括:
- 随机水平翻转:简单但非常有用,能直接扩充训练样本,还不会破坏身份信息。
- 随机亮度、对比度、饱和度扰动:模拟不同光照环境,让模型对光照有更好的鲁棒性。
- 随机遮挡模拟:在图片上随机放置黑色矩形块,或者模拟口罩遮挡下半张脸,能显著提升模型在遮挡场景下的表现。
- 随机擦除(Cutout 类方法):效果与遮挡模拟类似,但对细节纹理的鲁棒性也有帮助。
- 轻微旋转和缩放:配合对齐的归一化思路,模拟检测框的轻微误差。
有两点需要注意:一是增强强度要克制,过强的亮度和对比度扰动会让图像失真,反而损害训练效果;二是增强要基于对齐后的人脸做,不要先增强再对齐,否则会产生大量无效样本。
3. 模型结构与损失函数怎么选
3.1 Backbone 选 ResNet 还是轻量网络
骨干网络的选择直接决定了精度和速度的平衡。我把 ResNet 系列和轻量网络都跑过一遍,结论很明确:如果算力允许,ResNet 是默认选择;如果要上手机或嵌入式设备,再考虑 MobileFaceNet 这类轻量骨干。
ResNet50 和 ResNet100 我都用于训练过完整的人脸识别模型。ResNet50 参数量适中,单卡 batch size 可以开到 128 甚至更大,训练速度合适,精度也足够覆盖大多数场景。ResNet100 比 ResNet50 在公开测试集上能有明显提升,但训练时间差不多翻倍,显存占用也大了很多。如果追求极致精度,比如刷榜单或做疑难场景,ResNet100 值得一试;如果做产品落地,ResNet50 往往是更理性的选择。
轻量网络方面,MobileFaceNet 是我用的相对多的一个。它速度快、参数量小,很适合移动端,但精度相比 ResNet 有明显下降。这里有个经验:同样的数据、同样的损失函数,轻量网络需要更大的 batch size 和更长的训练时间才能勉强接近 ResNet 的效果。所以在用轻量网络时,不要沿用之前的训练配置,得重新调参。
3.2 从 Softmax 到 ArcFace
人脸识别损失函数的演进过程,简单说就是一路从“分类”走向“度量”。原生 Softmax 损失让大家把不同身份分对,但分对了不代表特征有足够的区分度——很多特征在空间里挤成一团,根本拉不开。三元组损失试图通过样本对的距离约束来改善这个问题,但实际训练时三元组的选择非常困难,收敛很慢,效果不稳定,我自己做过几轮实验后基本放弃了。
后来用的核心方案是加性角度间隔损失,这类方法在业内被广泛验证。核心思路是在 Softmax 分类的 logit 上增加一个角度间隔 margin,迫使模型为了把不同身份分开,必须把同类特征进一步聚拢,不同类特征进一步推开。
ArcFace 的具体做法是这样的:把特征和分类权重做归一化,让 logit 只由特征向量与权重向量之间的夹角决定,然后在真实类别的夹角上加上一个固定 margin,再乘上一个缩放系数 s。这样一来,模型要正确分类,就必须学到夹角足够小的同类特征,效果上相当于给特征空间加了一个强约束。
这套方案的优点非常明显:训练稳定、不依赖样本对挖掘、收敛速度快、特征判别性强,而且实现起来也不复杂,基本就是一个修改过的交叉熵。目前产业界人脸识别训练的主流方案基本都被这类 margin 损失占据,ArcFace 又是其中最常用的一种,因为它同时兼顾了理论清晰度、实现简洁性和实际效果。
3.3 特征维度、归一化与训练参数
特征维度是一个值得细抠的点。最常用的 embedding 维度是 512,这个数字在多个公开评测中被验证过,效果和性能的平衡最好。我试过 128 和 1024 两种维度,128 维特征在某些场景下速度优势明显,但精度会下降;1024 维特征精度略有提升,但特征存储和比对耗时都增加了不少。一般业务场景直接用 512 就行,不用纠结。
特征归一化(L2 Norm)几乎是必须做的。特征向量归一化之后只保留方向信息,让模型专注于学习角度关系,这对 ArcFace 这类基于角度的损失函数尤其重要。训练完成后,相似度计算一般用余弦相似度即可。
还有一个常被忽略的参数是缩放系数 s。ArcFace 中 s 一般取 64,这个值不是随便定的,它决定了特征分布被压缩在一个多大的超球面上。s 太小会让类别间的角度边界变得模糊,s 太大则容易让模型对难样本过度自信,导致过拟合。64 是经过大量实验验证的常用值,除非有特殊理由,否则不建议轻易改动。
4. 训练流程实操与调优记录
4.1 完整的训练配置参考
以下是一份我实际跑过、效果稳定的训练配置,给想复现的人一个参考基线:
- 输入分辨率:112x112
- 骨干网络:ResNet50
- embedding 维度:512
- 损失函数:ArcFace,margin 0.5,缩放系数 s=64
- 优化器:SGD,momentum 0.9,weight decay 5e-4
- 初始学习率:0.1(配合 warmup 使用)
- batch size:256(单卡 128,双卡并行)
- 训练轮数:20~25 个 epoch,伴随余弦退火
- 预处理:RGB 归一化到 [-1, 1]
这份配置在多次实验中表现都比较稳。学习率从 0.1 开始貌似有点高,但配合前 5 个 epoch 的 warmup,模型能稳定进入收敛状态,整体收敛速度反而比我最初用 0.01 时要快。warmup 的过程是从一个很小的学习率逐步升到目标学习率,防止训练初期梯度爆炸或者特征分布剧烈震荡。
优化器这里有一个坑:很多人直接套用 Adam。Adam 在分类任务里确实省心,但在人脸识别的大规模训练里,SGD 配合 momentum 的收敛效果往往更好,特征空间的判别性也更优。我最早用 Adam 试过,训练 Loss 下降快,但测试集上的精度明显不如 SGD,后来就全部切回 SGD 了。
4.2 训练过程中的动态调整
训练过程中最常做的事情就是盯着学习率曲线。我的经验是:不要等 Loss 完全不动了才降学习率,而是要提前降,给后面的训练留出缓冲时间。
我用的调度方式是余弦退火,整体思路是让学习率从初始值平滑地衰减到接近 0,中间不人为打断。优点是曲线平滑,不需要频繁手动改参数。如果你更习惯手动分阶段降学习率,可以按“0.1 训练 8 epoch,降到 0.01 训练 6 epoch,再降到 0.001 收尾”这种节奏来,也能有不错的效果。
batch size 对训练效果的影响也很大。一般来说,batch size 越大,特征的判别性越好,因为单次迭代中能看到更多的负样本对。我用 256 时效果稳定,尝试过 512 还能进一步提升精度,但显存不够,只能用梯度累积模拟,速度会慢很多。如果你的显存允许,优先上大 batch size;不允许的话,梯度累积是一个可接受的折中方案。
训练到中后期,我建议每次降低学习率之后做一次完整的评估,确认测试集精度确实在涨。如果精度出现回退,可以回滚到上一个 checkpoint,再降低学习率重新训练几轮。这种手动干预虽然麻烦,但能避免一整夜的训练白跑。
4.3 特征分类头与性能监控
训练过程中如果能跑评估,千万别省,这是整个训练流程里最值得关注的监控环节。
我每训练完一个 epoch 会做两件事:一是记录训练 Loss 和验证集 Loss;二是在测试集上跑一遍特征提取和精度评估。测试集我用的是一组公开的人脸识别基准,里面包含了几千对同身份人脸对和异身份人脸对。评估指标以准确率为主,同时也会记录 FNMR@FMR 等指标,方便了解模型的错误倾向。
还有一点值得提醒:测试集和训练集一定要保证没有身份重叠,否则评估结果虚高,根本没有参考价值。我第一版模型就是因为训练集里的数据源跟测试集有重叠,导致评估精度一度达到 99% 以上,后来才发现是“作弊”了,这个坑踩得记忆深刻。
5. 评估方法、阈值选取与测试坑
5.1 通用测试集与验证协议
人脸识别模型的评估方法比较固定,一般是在公开基准数据集上做“人脸验证”任务:给定两张人脸图片,判断它们是不是同一个人。数据集里会提供若干同人正对和异人负对,模型负责提取特征并计算相似度,最终根据相似度阈值判定。
我在使用公开基准时并不会只用一组数据。通常的做法是同时看多组不同难度的基准:一个以日常状态为主,难度适中;一个包含跨年龄、大姿态和种族多样性的场景;还有一个偏向仿真场景,包含遮挡和模糊样本。单一基准的分数高并不能说明模型真的好,多基准综合看才有说服力。我在实际项目里就见过模型在 A 基准上刷到很高,但在 B 基准上直接崩掉的情况,多集评测能提早暴露这种问题。
5.2 阈值怎么定最科学
测试集精度计算依赖阈值,但这个阈值不能随便拍脑袋。
人脸验证任务的本质是二分问题:相似度高于阈值为同一个人,低于阈值为不同人。如果把所有可能的阈值都扫一遍,画出 ROC 曲线,可以得到每个阈值下的误识率和拒识率。然后需要结合业务场景选一个合理的点:金融级场景对误识率极其敏感,宁可多拒几次也不能认错;门禁考勤相对宽松,可以适当放宽阈值,换取更低的拒识率。
我的经验是先用验证集画出 ROC 曲线,选几个候选阈值,再在一个留出的独立测试集上做最终确认。阈值确定之后一般不会再频繁调整,除非遇到业务反馈的明显偏置,比如误识率过高或拒识率过高,再做针对性校准。
5.3 测试阶段常见的坑
测试阶段最容易踩的坑有三个,我全都踩过:
第一个是测试图片的预处理不一致。训练时做的是五点对齐到 112x112,测试时如果直接用原图缩放,或者用不同的关键点坐标,特征分布就会发生偏移,精度掉得非常快。必须保证测试流程和训练流程的图像预处理完全一致。
第二个是相似度计算方式不一致。有些人在训练时用余弦相似度,测试时却用欧式距离去算,或者在存放特征时忘了做归一化,导致结果完全乱了。最稳妥的方案是:训练、测试、部署阶段全部统一为余弦相似度,特征全部先做 L2 归一化再入库。
第三个是数据泄漏。训练集和测试集的身份必须严格隔离,这个前面也说过。我不止一次见过别人在评估模型时,测试集里的人脸其实跟训练集来自同一个人的不同照片,导致精度虚高到离谱,这种模型上线之后一定会被打回原形。
6. 常见问题与排查技巧实录
6.1 Loss 不降或者震荡
Loss 不降是最常见的劝退问题,原因可能有很多,我按排查顺序给一个参考思路:
先看学习率。学习率过大一般表现为 Loss 剧烈震荡、不下降甚至上升;过小表现为 Loss 下降极慢,像是被冻住了。可以先改成逐段衰减策略,把初始学习率减半看效果,做实验排除这一项再继续排查。
再看数据。如果训练集里有很多错标或者噪声样本,Loss 会在局部出现尖峰。我做过一次实验,故意往训练集里混入 5% 的错误标签,Loss 表现得很稳定,但测试精度明显下降。所以 Loss 没问题并不等于数据没问题,还要配合验证集精度来判断。
再看损失函数参数。ArcFace 的 margin 如果设置得过大,会让训练非常困难,Loss 迟迟不降。我在早期实验里把 margin 设到 0.8,训练初期 Loss 就一直停在很高的位置,后来改回 0.5 才恢复正常。这个参数对训练难度的影响非常大,新手建议先用默认值。
如果以上都没问题,还有最后一招:缩短训练长度重新跑一个小规模实验,比如只取训练集的 20% 训练 5 个 epoch,快速验证整体流程是否通畅。这样可以避免在完整训练集上白跑几天之后才发现问题。
6.2 显存不足与训练速度慢
显存不足是家常便饭,但有成熟的解决方案。
最直接的是降低 batch size,保证训练能跑起来,但 batch size 太小会影响特征质量。折中做法是梯度累积:每积累几个 batch 的梯度后统一更新一次参数,理论上等价于一个大 batch size 的训练效果。我实际用下来,累积 4 步的效果和真正的大 batch size 相比差距不大,但要注意调整学习率,因为等效 batch size 变了,学习率也得同步调整。
混合精度训练是另一个提速和降显存的重要手段。把模型参数保留 FP32,前向和反向过程用 FP16 计算,显存占用几乎减半,训练速度也有明显提升。PyTorch 里的 AMP 模块用来很方便,基本就是几行代码的事。唯一需要注意的是一些数值敏感的算子需要保持 FP32,否则可能出现 Loss 不稳定的情况,AMP 默认配置已经处理了大部分场景,直接开就好。
6.3 过拟合与跨场景失效
人脸识别模型过拟合的表现很隐蔽:训练集精度很高,公开测试集精度也不错,但一拿到实地场景就“翻车”。这类问题的本质往往是数据分布单一,模型的泛化面太窄。
解决办法首先是扩充数据多样性,特别是你实际部署场景里的姿态、光照、设备类型。如果目标场景是安防摄像头,训练数据里就要有大量低角度、低分辨率、远距离人脸;如果场景是手机解锁自拍,训练数据就要覆盖各种手持距离和室内外光线。拿通用人脸数据集直接部署到特定场景,效果大概率不会好。
其次是更激进的数据增强,特别是模拟你场景中的噪声、模糊、过曝等因素。如果场景遮挡多,就增加随机遮挡增强的比例;如果场景光线暗,就加入低光照模拟。这种做法比找真实数据便宜得多,往往能带来明显效果改善。
如果单一模型始终无法适配多类场景,最终的解决思路是做场景化微调:用目标场景的数据对模型做少量 step 的微调。我的经验是,微调时学习率要比正常训练低一个数量级,训练轮数控制在少量 epoch 内,否则模型会遗忘之前学到的通用特征。这类微调对轻量模型的效果尤其明显,算是一条性价比极高的路线。
7. 部署量化与落地经验
7.1 模型压缩与 INT8 量化
训练只是第一步,模型真正要上一个规模不小的产品场景,部署侧的功夫往往比训练更磨人。
我的经验是从 FP32 模型开始做 INT8 量化。量化能显著缩小模型体积、提升推理速度,但人脸识别模型对量化比较敏感,特征质量的下降往往和量化误差直接挂钩。我一般会优先尝试训练后量化,也就是 PTQ。好处是无需重新训练,流程简单;坏处是如果精度掉得厉害,就得上量化感知训练 QAT 了。
QAT 的核心思路是在训练过程中模拟量化误差,让模型自己适应低精度表达。实际操作中需要在网络中插入量化/反量化节点,然后正常训练几个 epoch。我试过一次,精度退化从 PTQ 的 1.5% 降到了 0.3% 以内,效果非常明显。代价是要重新训练和调参,时间成本不低,只有在 PTQ 不能满足精度要求时才值得做。
7.2 推理加速与工程细节
模型部署时的推理加速,主要关注三点:计算图优化、批处理策略、特征存储与比对。
计算图优化可以用推理引擎自带的优化器完成,相关工具对主流骨干网络优化得比较完善,一般能带来 1.5 到 3 倍左右的加速收益。如果你的模型里有一些自定义算子,可能会成为优化瓶颈,需要单独处理。
批处理策略针对的是同时处理多张图的场景。在实际业务中,如果一次来了一批待比对的人脸,把多张图拼成一个 batch 推理,整体吞吐会明显高于逐张推理。但这个优化会让单张延迟上升,实时性要求高的场景需要权衡。
特征存储与比对是另一个容易被忽视的瓶颈。当底库特征规模达到百万级时,朴素的全量比对会非常慢。我的经验是把特征分成几段分别存储并建立索引,只对候选集做精排,类似粗排加精排的组合思路。这样比对速度能提升好几个数量级,而精度几乎没有损失。
8. 写在最后的几点体会
人脸识别模型训练做到后面,我对“数据决定上限,训练决定下限”这句话的感受越来越深。模型结构、损失函数这些看得见摸得着的东西,大家都能搜到不少资料,但真正让模型好用起来的,往往是那些藏在数据清洗、预处理、评估细节里的功夫。
我印象最深刻的一个项目是某跨平台系统的多场景人脸识别模块,训练阶段精度看起来够用了,上线后却因为测试集和业务场景的分布不一致,被反馈误报率偏高。后来重新收集了一批目标场景数据做微调,加上更严格的数据清洗和阈值校准,问题才彻底解决。整个过程下来,我对“评估指标好”和“业务效果好”之间的差距有了更具体的认识,也重新调整了自己做模型的习惯,每一步都要想着真实落地场景。
如果你现在正准备开始做人脸识别,我的建议很简单:不要急着刷精度,先把数据流程稳下来,把训练、评估、部署的标准链路跑通,然后再去追求更高的分数。这条链路里的每一步都会在后续迭代中反复用到,早一点理顺,后面会省很多折腾的时间。