项目标题是“人员检测与跟踪:人脸识别_(15).人脸识别系统的性能评估与优化”,核心是“性能评估”和“优化”。我要写的是一篇从业者视角的实操博文,围绕评估口径、指标计算、评估集构造、优化路径(数据/阈值、模型/推理)、踩坑排查展开。正文、关键词、摘要都为空,我基于标题做合理补全,不编造真实项目背景。
写作上要避免模板化章节名、AI套话、emoji、mermaid、真实机构名,用虚构代称。字数要够、段落要拆细、每段不少于150字,穿插表格和计算示例。开头用具体场景切入,不用“随着……发展”。
下面直接输出博文。把一套人脸识别服务从实验室推到真实业务里,最容易被忽略的一步不是训练,而是“评估与优化”这个闭环。我见过太多团队,离线测试集上识别准确率刷到 95% 以上,接进人员和目标跟踪的流水线之后,误识别率直接翻了十倍,前端值班的人一天要处理几十条告警。问题往往不在模型本身,而在于评估时所用的数据分布与线上完全不同:摄像头角度、光照条件、人群构成、底库规模,全都对不上。这篇内容就聊这件事——在人员检测与跟踪的场景下,人脸识别系统到底该怎么评估、指标怎么算、优化从哪里下手。适合正在做识别服务落地、或者已经上线但效果不达预期的同学看,无论你是写算法的还是搭工程的,都能找到可以直接抄的部分。
1. 先对齐评估规范,否则“95%准确率”根本没有意义
1.1 识别任务其实分三种,指标完全不同
很多人张嘴就说“人脸识别准确率”,但在工程上这个词是没法直接用的,因为底下的任务是三种完全不同的东西。
第一种是1:1 验证,也就是“这张脸是不是他本人”。典型场景是门禁刷卡加刷脸、手机解锁。它的输出是一个二分类判断,评估看的是误接受率(FAR)和误拒绝率(FRR),以及在不同阈值下的曲线。
第二种是1:N 检索,从底库里找出最像的若干个人。人员和目标跟踪系统里最常见的就是这个:检测框出来一个人脸,去底库里搜,看是不是名单里的人。它输出的 Top-K 候选,评估看 Rank-1、Rank-5、mAP。
第三种是1:N 聚类或档案归并,更偏向后端分析,把同一个人的多条轨迹合并成一个档案。它本质上是把检索和跟踪结合起来,评估得看聚类纯度或者跨摄像头 ID 一致性。
这三种任务的阈值选择逻辑、误判代价、评估集构造方式全都不一样。我之前参与一个项目,算法同学报上来一个“99.2% 的准确率”,我一看评估脚本:正样本对是从同一次采集会话里随机抽的,负样本对是随便配的,还用了验证任务的最优阈值。把这个模型放到 1:N 检索场景,底库一万人的情况下 Top-1 命中只有 70% 出头。这不是模型不行,是评估方式错了。
所以第一件事:在写任何评估代码之前,先明确当前系统服务于哪种任务,用哪种指标,误判的业务代价是什么。这三件事定不下来,后面所有优化的方向都可能是错的。
1.2 把业务语言翻译成指标语言的一张对照表
业务方说的话和工程侧的指标之间,需要一张翻译表。下面是按我自己的项目经验整理出来的常用映射,可以直接拿去用:
| 业务方的说法 | 对应的工程指标 | 说明 |
|---|---|---|
| “别把陌生人放进来” | FAR(误接受率) | 安全优先,阈值往高调 |
| “自己人别被挡在外面” | FRR(误拒绝率)、TAR@FAR | 体验优先,阈值往低调 |
| “能在人群里找到目标” | Rank-1、Rank-5、mAP | 检索任务,看候选列表 |
| “一个人别被识别成好几个” | ID Switch、IDF1 | 跟踪链路的身份保持度 |
| “要能实时跑” | P50 和 P99 时延、FPS | 平均值参考价值有限 |
| “跑一天别崩” | 24 小时长稳、显存增长曲线 | 工程稳定性指标 |
这张表的价值在于,它把“效果好一点”这种模糊需求,逼成了可以量化的验收条件。业务方说“识别要准”,你可以追问一句:是宁愿漏掉几个也不想认错人,还是宁愿多报几个也不想漏?这两句话对应的阈值可能差 0.1 以上,误报量能差好几倍。
1.3 阈值不是模型的一部分,而是业务的一部分
这一点我想单独强调。很多团队把阈值当成模型文件里的一个固定参数,训练完定一个 0.6 或者 0.75,全场景通用。这个做法在小规模单一场景里勉强能用,一旦摄像头数量上来、人群构成复杂,必然出问题。
阈值本质上是误接受和误拒绝之间的一个交换点,而这两者的代价是由业务决定的。同一个模型,用在考勤打卡场景,误接受代价高(代打卡),阈值应该定得高;用在客流分析场景,误拒绝代价高(少统计一个人无所谓,但漏掉整体趋势就麻烦),阈值可以定得低。
所以在系统设计上,阈值必须是可以按场景、按摄像头、甚至按时段配置的。我在一个项目里做过统计,白天大堂光线好,0.62 的阈值下误报率是千分之三;晚上侧门灯光弱,同样的阈值误报率飙到了百分之二。后来做了分场景标定,把夜间阈值单独提到 0.7,误报才压下去,代价是夜间漏检多了几个,业务方认可这个交换。
2. 把人脸识别指标拆到能动手算的程度
2.1 FAR、FRR 与 TAR@FAR 的实操算法和常见错误
这三个指标的定义不复杂,但算对的人不多。
构造两组分数:正样本对(同一个人的两张不同照片)的相似度分数,负样本对(不同人的两张照片)的相似度分数。给定一个阈值 t,把正样本对里分数低于 t 的比例叫 FRR,负样本对里分数高于 t 的比例叫 FAR。扫一遍所有可能的 t,就得到 ROC 曲线。
问题在于样本量。FAR 要达到 1e-4(万分之一)这种量级,你至少得有几万到几十万量级的负样本对,否则统计出来的数字没有意义。举个具体计算:如果 FAR 的真实值是 1e-4,你用了 1e5 个负样本对,误接受的期望次数是 10 次,按二项分布估算,次数的相对标准差大约是 1/√10 ≈ 32%。也就是说你测出来的 FAR 可能在 0.7e-4 到 1.3e-4 之间晃。如果只用了 1e4 个负样本对,期望只有 1 次,那这个数基本就是噪声。
TAR@FAR 则是固定一个 FAR 水平,去读对应的 TAR(也叫 TPR,真接受率)。行业里常用的报告口径是 TAR@FAR=1e-3、1e-4、1e-5 三档,对应不同的安全等级。写报告的时候务必标明用的是哪一档,否则两组数字根本没法比较。
还有一个特别常见的错误:评估脚本里的相似度计算和线上推理代码不是同一套。离线评估时用了归一化后的余弦相似度,线上推理漏了 L2 归一化,或者图片预处理时的对齐关键点和训练时不一致,分数分布的尺度就变了。我遇到过一次,离线评估 TAR@FAR=1e-4 是 0.96,线上实测只有 0.88,查了两天才发现是评估脚本对图像做了 BGR 到 RGB 的转换,而线上服务的读取模块忘了转。这种问题在指标层面看不出任何异常,只能靠对齐两边的输入输出逐帧比对。
提示:评估脚本和线上推理代码必须是同一份预处理和人脸对齐逻辑,建议直接把预处理封装成一个共享模块,两边共同引用。宁可多一层依赖,也不要各写一份。
2.2 检索任务里 Rank-1 会骗人,底库规模是隐藏变量
Rank-1 是最容易被误读的指标。同样一个模型,底库有 100 人,Rank-1 可能 99%;底库有 10 万人,Rank-1 可能掉到 85%。原因很简单:候选多了,撞脸的概率就上去了。这不是模型变差,是任务变难。
所以报告 Rank-1 时必须带上底库规模,最好同时报 Rank-5 和 mAP。有时候 Top-1 错了一点,但 Top-5 里几乎都能命中,这在实际业务里是可以接受的,因为后端还有人工复核或者多帧投票。
更严谨的做法是用 1:N 的开放集评估。也就是说,查询集里有一部分人根本不在底库里,系统需要能够判定“查无此人”。这时候评估就要同时看两个数:底库内的人有没有被正确检索出来(命中率),底库外的人有没有被正确拒绝(拒识率)。这两个数会互相拉扯,阈值调高命中率降,调低误认涨。
底库规模对误认率的影响可以粗略估一下。假设单次比对的 FAR 是 1e-5,底库有一万人,那么一次查询的期望误接受次数是 10000 × 1e-5 = 0.1 次,也就是说大约每十次查询会有一次产生一个误匹配候选。如果底库扩到十万人,这个数就变成 1 次,几乎每次查询都会冒出一个假阳性。这就是为什么很多系统在试点时表现很好,一扩底库就崩。
2.3 跟踪链路上的 IDF1 和 ID Switch,别只盯着检测框
在人员和目标跟踪的场景里,人脸识别不是孤立的,它挂在一整条流水线上:检测 → 跟踪 → 质量筛选 → 识别 → 身份融合。
很多团队只评估最后的识别准确率,忽略了跟踪质量对最终效果的影响。实际上,如果跟踪器频繁把一个人的轨迹切断,重新起了一条新 ID,那这个人就会被反复送去做识别,既增加计算量,又增加误判机会。
这里要引入两个跟踪侧的指标。ID Switch统计的是在连续轨迹中,同一个目标被赋予了不同 ID 的次数,越低越好。IDF1则综合了身份正确性和轨迹完整性,计算方式大致是:
IDF1 = 2 × IDTP / (2 × IDTP + IDFP + IDFN)其中 IDTP 是身份正确匹配的帧数,IDFP 是身份错误匹配的帧数,IDFN 是应匹配未匹配的帧数。IDF1 比单纯的 MOTA 更能反映“身份”这件事,而人员跟踪系统关心的恰恰是身份一致性。
我一般会把整条链路的评估拆成三层:
| 层级 | 核心指标 | 出问题时的典型表现 |
|---|---|---|
| 检测 | 召回率、误检率 | 戴口罩的人群漏检严重 |
| 跟踪 | ID Switch、IDF1、轨迹碎片数 | 一人多条轨迹,反复触发识别 |
| 识别 | TAR@FAR、Rank-1、拒识率 | 分不清,乱认人 |
分层的好处是出问题能快速定位到是哪一层。我遇到过一次线上效果突然变差,识别指标没变化,检测召回也没变化,最后发现是跟踪器的匹配阈值被改过,ID Switch 翻了三倍,导致同一个人被重复识别,每次识别用的都是一张质量较差的侧面帧。
2.4 端到端时延:平均值会掩盖真相,P99 才是用户感受
性能评估里最容易被糊弄过去的数字就是平均时延。一个系统平均 40ms,听起来很漂亮,但如果 P99 是 800ms,那用户每隔一会儿就会明显感觉到卡顿。
在一条检测+跟踪+识别的流水线里,尾延迟的来源通常有几处:
- 批处理等待:为了提升吞吐,把多帧凑成一个 batch,如果凑不满就一直等,短请求就被拖长。
- 显存分配抖动:动态显存分配在负载波动时会产生毫秒级的抖动。
- 队列积压:某个环节成为瓶颈,请求在队列里排队。
- 单帧大脸特别多:某一帧画面里突然出现十几张需要识别的人脸,识别环节瞬时压力上来。
评估时我建议同时记录 P50、P90、P99 和最大值,并且按帧内人脸数量分桶统计。因为 1 张脸和 15 张脸的场景,时延特性完全是两回事。只报一个全局平均,等于什么都没说。
3. 评估集怎么造,才能避免自己骗自己
3.1 正负样本对的构造方式,比模型更影响结论
评估集的构造方式直接决定了结论的可靠性。我见过最典型的错误是:正样本对从同一个人相邻几帧的视频里抽,负样本对随机配对。这样构造出来的正样本对几乎是同一个姿态、同一个光照、相隔几十毫秒,难度极低;负样本对又是随机配的,大概率差异巨大。这样测出来的系统当然漂亮,但和真实场景完全脱节。
我推荐的构造方式是分层抽。以人员跟踪的场景为例:
- 正样本对:同一个人,跨时段(相隔至少半小时)、跨摄像头(如果有多个视角)、跨姿态(正面 + 侧脸),每组时间跨度覆盖白天和夜晚。
- 负样本对:随机配对之外,再额外构造一批难负样本。所谓难负样本,是指那些模型容易混淆的组合:年龄接近、性别相同、脸型相似的人,或者戴同样帽子、同样口罩的人。难负样本的比例建议不低于总负样本的 20%,否则测出来的 FAR 会偏乐观。
数量上,正样本对几千对就够看趋势,负样本对的量要根据你关心的 FAR 量级来定,前面算过,1e-4 需要十万量级。
还有一点:样本的形变要贴近线上。线上的人脸来自摄像头,有运动模糊、有压缩块效应、有广角畸变、分辨率可能只有 60×60。如果评估集用的是清晰的证件照,那评估结果就是自说自话。我通常会直接从线上真实抓拍的帧里挑样本,哪怕数量少一点,也比用公开数据集干净图强。
3.2 按姿态、遮挡、光照、分辨率做切片,才叫“评估”
只报一个全局指标,等于把系统的优势和劣势全糊在一起。真正有用的做法是把评估集切成若干片,逐片看指标。
常见的切片维度:
| 维度 | 分档建议 |
|---|---|
| 人脸像素宽度 | <48、48-80、80-128、>128 |
| 俯仰/偏航角 | 正面、轻微侧(<30°)、大侧(30-60°) |
| 遮挡 | 无、口罩、帽子、手挡、眼镜反光 |
| 光照 | 顺光、逆光、暗光、强光过曝 |
| 场景 | 室内大厅、走廊、电梯、户外 |
切片之后你会看到非常具体的问题。比如整体 TAR 是 0.94,拆开看,人脸宽度大于 128 像素的档位是 0.995,而小于 48 像素的档位只有 0.71。这时候优化方向就很清楚了:要么在检测侧提高小脸的检出和裁剪放大,要么对小人脸直接放弃识别、只做跟踪。
我最常见到的现象是大侧脸和小人脸这两片的指标塌方,而它们又恰恰贡献了相当比例的样本。如果评估时不分片,你永远不知道自己的系统在哪种情况下不可靠。
3.3 数据泄漏的几种隐蔽形态
评估集和训练集之间的数据泄漏,比大家想象的普遍。除了最明显的“同一个人同时出现在训练集和测试集”,还有几种很隐蔽的形态:
第一种,同一次采集会话切帧。同一段视频切出的前后两帧被分别放进训练和测试,画面内容几乎一样,模型当然能认出来。这种泄漏在视频数据集上极其常见。
第二种,图像增广后跨集。一张图做了水平翻转放进训练集,原图放进测试集。翻转人脸对模型来说几乎等价,等于泄漏。
第三种,底库和查询同源。检索任务的评估里,查询图片和底库里的注册图片来自同一张原图的裁剪,只是裁剪框不同。这会让 Rank-1 虚高。
第四种,数据清洗时按人随机划分失败。很多标注数据里一个人有多条记录,划分时按记录随机分,没按身份分,同一个人就跨了集。
排查方式其实不复杂:做一次“身份集合的交集检查”,确认训练集和测试集的身份集合没有任何交集;同时对测试样本做一次近邻检索,把相似度超过 0.95 的跨集样本对列出来人工看一眼。这一步花不了多长时间,但能避免后面几个月的方向性错误。
3.4 黄金集和回归校验:让每次迭代都有底线
评估不能只在模型上线前做一次,它是一个持续的流程。我的做法是维护一个固定版本的黄金集:300 到 500 个身份,覆盖上面的各个切片维度,样本版本打上日期戳,冻结不再改动。
每次模型或者参数有变更,先在这个集上跑一遍,和上一版的指标逐项比对。设定一个门禁规则,比如:
- 全局 TAR@FAR=1e-3 下降超过 0.5 个百分点,打回;
- 任一切片的指标下降超过 2 个百分点,打回;
- P99 时延增加超过 20%,打回。
有了这个门禁,至少能挡住大部分“为了修一个场景、搞坏十个场景”的改动。黄金集规模不用太大,关键是覆盖面和稳定性,而不是数量。
注意:黄金集的样本必须控制权限,不能随便被拿去训练。我见过有团队为了冲指标,把测试集混进了训练数据,最后一轮回归所有指标全线飘红,因为模型已经完全过拟合了。
4. 优化路径之一:从数据和阈值下手,改动最小收益最大
4.1 质量分门控:先筛人,再识人
很多团队在识别精度上死磕模型,却忽略了一个更便宜的办法:不要把垃圾图片送进识别模型。
人脸识别模型对输入质量非常敏感。一张严重模糊、侧脸 60 度、只有 30 像素宽的脸,送进去不仅大概率认错,还会污染后续的身份融合逻辑。质量分门控的思路是,在识别之前先算一个质量分,低于阈值的直接丢弃或者延后处理,等这个人转过身来、走近一点、拍到清晰帧再识别。
质量分通常是几个因子的加权组合:
- 人脸尺寸:检测框宽度,低于下限直接排除;
- 清晰度:拉普拉斯方差,阈值根据场景实测定,一般低于 100 视为模糊;
- 姿态角:由关键点估算偏航和俯仰,超过 45 度降权;
- 遮挡比例:用关键点置信度或者分割结果估算;
- 亮度与对比度:避免过曝和欠曝。
这套东西落地之后收益非常直接。我在一个项目里加了质量门控,识别调用次数下降了约 40%,但最终的身份确认准确率反而上升了 3 个多点,因为送进模型的全是有效帧。计算资源省下来了,准确率还涨了,这是性价比最高的一类优化。
需要注意的一点:门控阈值不能定死。户外场景和室内走廊的光照差异巨大,用同一套清晰度阈值会导致户外几乎全部被过滤掉。我一般按摄像头分别统计质量分的分布,取各自的分位数来定阈值。
4.2 分场景阈值标定与分数归一化
前面说过阈值要按场景配置,具体怎么标定?
做法是:对每个摄像头(或者每组相似摄像头),采集一批标注过的样本对,构造本地正负样本集,然后按目标 FAR 反查阈值。比如业务要求误报率控制在千分之一,就在本地负样本分数上取 99.9 分位数作为阈值。样本量至少要几千对,否则分位数不稳。
更省事的办法是做分数归一化。不同摄像头拍出来的人脸,模型给出的原始相似度分布本身就有偏移,有的摄像头整体偏高,有的整体偏低。如果能把原始分数转换成“在本地负样本分布中的分位数”,那不同场景就可以用同一个分位数阈值。这个方法我在几个多摄像头项目里用过,效果比逐摄像头标定省事很多,前提是每个摄像头都有一定量的负样本统计。
顺带说一个实操细节:模型升级之后,分数分布一定会变,之前标定的阈值全部作废,必须重新标定。这一点如果不提前在流程里约定好,升级上线的时候一定会出事故。
4.3 多帧融合和跟踪 ID 投票,把时间维度用起来
单帧识别的信息量是有限的。同一个人在不同帧里有不同的角度、表情、光照,把这些信息聚合起来,准确率能显著提升。这就是多帧融合的价值。
常见的聚合方式有三种:
- 分数平均:同一 track 内所有有效帧的相似度取均值,简单稳定,但会被个别垃圾帧拉低;
- Top-K 均值:取分数最高的 K 帧求均值,K 一般取 3 到 5,对垃圾帧更鲁棒;
- 投票法:每帧给出一个候选身份,按票数决定最终结果,票数相同再看平均分。
我更倾向 Top-K 均值,因为它在“用足好帧”和“抵抗噪声”之间平衡得比较好。K 的取值要结合 track 的长度来定,如果一个人只出现两秒、有效帧不到十帧,K 取 5 就偏大了。
这里有一个和跟踪强耦合的坑:如果跟踪器把一个人的轨迹切成了两条,投票就会被分散。比如一个人走了十秒,第 5 秒时被遮挡导致 ID 切换,两条轨迹各自只有 5 秒的帧,投票基数减半,反而更容易出错。所以在做多帧融合之前,最好先确保跟踪的 ID 稳定性,或者做一个轨迹合并的后处理。
4.4 底库治理:没人愿意干,但收益巨大
检索类系统的效果,有很大一部分取决于底库质量,而这部分工作往往没人愿意做,因为它不出现在任何论文里。
底库常见的问题有这么几个:
一人多模板无序增长。每次注册都新增一条记录,同一个人攒了十几条,质量参差不齐,还占内存。正确做法是维护每个身份的模板集合,设定上限(比如 5 条),新模板进来时按质量分替换掉最差的那条。
低质量模板长期存在。注册时没有质量门控,模糊的、侧脸的、戴墨镜的全进了库。建议定期跑一次底库体检,把质量分低于阈值的模板标记出来,通知业务方重新采集,或者直接降权。
重复身份。同一个人用不同证件、不同姓名注册了两次。这种要靠聚类或者人工核查来处理,技术上可以做一次全库两两比对,把相似度极高的对子拉出来人工确认。
特征版本不一致。模型升级之后,底库里的特征还是老模型提取的,新旧特征混在一起比对,分数完全不可比。所以模型升级必须配套底库全量重算,这一步要写进上线清单里。
底库治理的效果是可以量化的:把低质量模板清掉 15%,检索的 Top-1 往往能提升 1 到 3 个点,比调模型划算得多。
5. 优化路径之二:模型与推理侧的提速减重
5.1 骨干和输入分辨率的权衡,要先看小脸占比
模型侧的优化,第一件事是确定输入分辨率。人脸识别模型常见的输入是 112×112,也有用 160×160 或 128×128 的。分辨率提升带来的精度增益,在不同场景下差别极大。
我的判断依据是评估集里小人脸的占比。如果切片统计显示,人脸宽度小于 48 像素的样本占比超过 20%,那提升输入分辨率或者改成“检测侧放大裁剪”是有意义的;如果小脸占比很低,提升分辨率带来的收益可能只有零点几个点,却要付出翻倍的计算量,不值。
这里有个更划算的做法:在检测侧做文章,而不是在识别侧。因为小脸在原始画面里像素就那么多,识别模型输入再大也是插值放大,信息量并没有增加。真正的解法是让检测模型在更高分辨率的输入上工作,把小人脸检测出来并裁出更大的区域,再送进识别模型。
骨干网络的替换也是同理。大骨干换小骨干能省算力,但精度损失要先在黄金集上量出来。我的经验是,在切片评估里看,大骨干和小骨干的差距主要集中在小脸和大侧脸这两片上,全局指标可能只差 0.5 个点,但那两片可能差 5 个点。如果你的业务恰好在这两片上敏感,就不能省这个算力。
5.2 量化、蒸馏、算子融合的落地顺序
模型压缩的手段很多,但顺序不能乱,乱了一步步都是坑。我推荐的顺序是:
第一步,先做推理引擎层面的图优化和算子融合。这一步不改变数值精度,纯粹是把能合并的算子合并、能省的中间张量省掉。收益通常在 20% 到 40%,风险几乎为零。
第二步,上 FP16。半精度在绝大多数人脸识别模型上精度损失可以忽略,速度提升明显,显存占用减半。评估时要特别注意,FP16 之后分数分布会有细微变化,阈值需要重新标定一次。
第三步,考虑 INT8 量化。这一步必须谨慎,因为它对精度的影响不是均匀的。一定要用真实场景的图片做校准集,不能用随机噪声或者公开数据集凑数。校准集要覆盖各个切片,特别是暗光和小脸。量化之后必须重跑黄金集,逐片比对,重点看小脸那一片有没有塌方。
第四步,蒸馏。如果业务对精度要求高、又必须用小模型,可以用大模型做教师,让小模型去拟合大模型的特征分布。蒸馏的收益在小模型上比较明显,但训练周期长,适合作为中长期优化。
有一个特别容易被忽略的点:量化之后的速度提升,不一定和理论算力成正比。实际吞吐还受内存带宽、访存模式、批大小影响。我见过 INT8 理论算力翻四倍,实测端到端只快了 30% 的情况,因为瓶颈根本不在计算单元上,在数据搬运上。所以优化必须做端到端实测,不能只看单算子 benchmark。
5.3 流水线并行和批处理的收益边界
在一条检测+跟踪+识别的流水线里,各环节的耗时特性完全不同。检测是重计算、每帧必跑;识别是轻一些、但只对触发帧跑。如果串行执行,整体时延就是各环节之和。
常见的优化是把流水线拆成几个阶段,用队列连接,各自独立并行。检测线程持续产出,识别线程从队列里取任务。这样在识别环节繁忙时,检测不会停下来等。
批处理则要设置一个最大等待时间。做法是识别线程每次从队列里取任务,如果能凑到目标批大小就立刻执行;如果凑不满,等够 N 毫秒(比如 10ms)也执行。这个超时机制是控制尾延迟的关键,没有它,低负载下反而会因为等待凑批导致时延上升。
还有一个边界要清楚:批处理对吞吐的提升是有上限的。批大小从 1 增到 8,吞吐可能涨 3 倍;从 8 增到 32,可能只再涨 20%,因为已经打满了。盲目加大批大小只会增加时延和显存占用。我一般会在压测里扫一遍批大小,画出吞吐和 P99 时延的曲线,选那个拐点。
5.4 线上该盯的几个数,比离线指标更重要
优化不能只看离线指标,线上要有实时监控。我会盯这几类数:
第一类是系统层:QPS、GPU 利用率、显存占用、队列深度、各环节耗时分布。显存占用要做时序曲线,缓慢爬升说明有泄漏。
第二类是业务层:识别调用量、有效识别率(有多少帧通过质量门控)、Top-1 相似度的分布。这个分布很关键,如果它的均值或方差发生漂移,通常意味着人群构成或者摄像头状态变了。
第三类是结果层:空结果率、候选数分布、人工复核的驳回率。人工复核的驳回率是最真实的精度信号,虽然滞后,但最可信。
这几类数要做到能按摄像头、按时段下钻。我遇到过一次全场识别效果下滑,最后定位到是某个区域的摄像头镜头被灰尘糊住了,画面整体对比度下降,质量门控把大部分帧都过滤掉了。如果只看全局指标,只会看到一个缓慢下降的曲线,根本定位不到具体设备。
6. 几个反复踩到的坑,以及我的排查习惯
6.1 指标突然变好,第一反应应该是怀疑评估脚本
这是我这么多年形成的一个条件反射:指标大幅提升的时候,先别急着高兴,去查评估流程。
常见的原因有几种。评估脚本里的样本对构造变了,比如正样本对的时间跨度缩小了,难度自然下降;测试集被重新清洗过,把难样本剔掉了;阈值搜索的范围变了,找到了一个更优但过拟合测试集的点;最糟糕的是测试数据泄漏进了训练。
我的排查顺序是:先 diff 评估脚本,再 diff 测试集版本号和样本哈希,最后做一次训练集测试集的身份交集检查。这三步走完,八成的问题能定位。
反过来,指标大幅下降的时候也是同样的逻辑,先确认评估流程没变,再去查模型。我见过一次线上报警说识别率掉了 20 个点,最后发现是评估任务用的底库特征没更新,还是旧模型的。
6.2 换个摄像头就掉点,本质是域偏移
实验室用的摄像头和现场装的摄像头,参数、安装高度、俯仰角、焦距、白平衡全都不一样,拍出来的人脸分布天然不同。这是典型的域偏移问题。
缓解手段有几个层次。最直接的是把现场数据加进训练,哪怕只有几百张,只要覆盖了现场的成像特点,提升就很明显。其次是在评估集里加入现场样本,避免用实验室数据自嗨。再往上是做跨域的数据增广,模拟不同的色温、对比度、噪声、压缩程度。
有个成本很低的技巧:在现场摄像头装好之后,先采集一批无标注的抓拍图,人工挑出质量尚可的几百张,做一遍伪标注或者半监督训练。这个过程一天能搞定,效果比在实验室里调一个月的模型参数都好。
6.3 显存泄漏和长稳测试,必须在压测阶段解决
推理服务的显存问题,短时间跑是看不出来的。有些泄漏来自缓冲区没释放,有些来自动态内存池的碎片化,跑上几个小时才会显形。上线之后半夜崩一次,排查成本极高。
我的习惯是在压测阶段跑一次24 小时长稳测试:固定负载、固定输入流,每 10 分钟记录一次显存和内存占用。正常情况应该是持平或者只有小幅波动,如果曲线呈锯齿状缓慢上行,说明有泄漏。
同时要测负载突变的场景:从空闲状态突然涌入大量请求,看服务能不能撑住,队列会不会无限增长,有没有熔断。这类场景在真实业务里很常见,比如某个时间段人流突然集中。
长稳测试还有个附带价值:能暴露一些偶发的数值问题。比如某些极端输入导致输出 NaN,短时间测不出来,跑一天总会遇到。发现了就加输入校验,把这类样本挡在外面。
提示:长稳测试的输入流最好不要用循环播放同一段视频,那样缓存命中率会异常高,测出来的性能偏乐观。用一段足够长的、内容有变化的录像,或者用真实抓拍流回放。
最后分享一个我个人觉得很有用的小习惯:每次做优化改动,不管多小,都在黄金集上跑一遍,并把结果、日期、改动说明记在一张表里。这张表积累半年之后,你会对自己的系统有非常清晰的认知——知道哪些改动有效果、哪些是噪声、哪些改动之间有冲突。这比任何一次性的调优都值钱。