
去年接了一个园区闸机改造项目注册库里几千号员工平均每个人只有两张照片一张入职时拍的证件照一张现场采集的半身照。当时拿现成的人脸识别模型微调了一下结果闸机识别率很不稳定白天好一点傍晚逆光时经常把人拦在外面。后来沉下心把真实场景下的大规模双样本学习这条路摸了一遍踩了不少坑也拿到了实打实的效果提升。这篇文章就把我们踩过的坑、试过的方法、以及最后沉淀下来的完整方案整理出来。做门禁考勤、安防检索、人脸库建设的工程同学应该能直接从里面抄到不少东西。1. 真实场景把人脸识别逼到了什么份上1.1 一人两张图是常态不是边缘情况以前看论文总觉得研究人脸识别就得有海量数据动辄百万级身份、每个身份几十上百张照片。但真跑到项目现场就会发现现实完全是另一回事。园区打卡系统拿到的人脸数据经常是一个身份只有注册时留下的1到3张照片工地实名制通道也差不多工人在闸机前拍一张照就算建档了学校的迎新系统更典型新生录取照片加上入学现场照几乎就是全部。为什么会这样一方面是采集成本部署方不愿意花人力组织大规模集中采像另一方面是合规约束能收集的人脸数据是有限的不能像互联网平台那样无限搜集。所以一个身份只有两张样本不是一个极端case而是绝大部分ToB业务里绕不开的硬约束。我把这类问题叫大规模双样本学习身份类别数量可能是十万级甚至百万级但每个身份可供训练的样本只有两张。训练时数据分布长尾到极致推理时注册底库又要把成千上万个人区分开难度比学术界常规的小样本学习还要棘手。1.2 双样本不是小样本人脸的简单叠加刚开始团队成员有个误解觉得小样本学习研究了这么多年拿现成的方法套人脸不就行了真做下去才发现问题没那么简单。通用小样本学习比如在CIFAR或者ImageNet子集上做5-shot分类通常只有几十到几百个类别类别少、每类样本也少模型拟合压力主要在怎么从少量样本中泛化。而大规模双样本人脸识别是每类样本少到2个的同时类别数量还被拉到几十万既要横向区分海量身份又要在单个身份内部建立稳定的特征表达。为了看清这里的差异我们列了一张对比表对比维度传统大规模人脸识别通用小样本学习大规模双样本学习类别规模十万百万级几十几百级十万百万级每类样本数几十几百张15张13张类内变化来源年龄、角度、光照、遮挡语义类别差异采集设备、登记照/抓拍照差异主要难点特征判别性快速泛化类别太多样本太少同时出现关键损失ArcFace等边际损失Prototype/MAML双样本约束代理分类头从表里能看出来大规模双样本学习卡在两头难模型需要把海量身份映射到特征空间的不同区域但每个身份的监督信号又极度稀疏。这决定了它既不能直接套通用小样本方法也不能沿用传统Softmax训练的老路。1.3 为什么传统Softmax那套在大规模双样本面前失灵很多人第一反应是我直接用ArcFace训练不就行了。我在内部数据上做过对照实验同样用ResNet-50作为骨架当每个身份提供10张照片时验证集TARFAR1e-4大概是92%上下把每个身份裁到只剩2张照片后同样训练轮数TAR掉到了75%左右内部数据集仅供参考。十几个点的差距足以让项目从能用变成没法用。这里面的核心原因有三个。第一分类头参数量随类别数线性膨胀。50万类对应一个50万×512的全连接权重矩阵光参数就有1GB以上显存和通信开销都受不了。第二每类只有两个样本Softmax损失计算出来的类中心极度不稳定batch之间波动很大训练过程像踩在棉花上。第三Softmax系损失本质上只做类别间挤压并不强制同一身份的不同照片在特征空间里靠得足够近样本少的时候这个弱点会被放大。所以要想在真实场景下把双样本用起来必须在训练范式上做调整而不是把希望寄托在换一个更好看的backbone上。2. 双样本学习的核心思路把两张照片的价值榨干2.1 一张图当身份锚一张图当约束样本面对只有两张照片的现实我们设计训练策略时换了个思路不再把两张图都当成普通的分类样本而是给它们分配不同角色。一张图作为身份锚点负责建立这个人在整体身份空间中的位置另一张图作为约束样本专门用来和锚点图构成配对迫使模型学习到同一个人的两张不同照片应该映射到相近特征这个关键信息。训练损失因此可以拆成两部分分类损失对锚点图做身份分类让特征向量落在对应的代理向量附近配对约束损失让锚点图和约束样本的特征距离尽可能小同时拉远和其他身份的距离。这样做的好处是单张图负责提供身份语义配对则弥补类内方差的缺失两者互为补充。实际实现时我们在每个batch里对同一个身份抽2张真实样本同时额外做一次随机数据增强再抽2张四张图一起参与训练。增强后的配对相当于给模型施加了一个身份不变性的先验让它在光照、裁剪、模糊等扰动下都能保持特征稳定。2.2 用代理向量代替全连接分类头传统Softmax在人脸识别里最常见的形态是ArcFace它会在特征和权重向量之间加一个角度边际。但类别数到几十万后全连接权重矩阵本身就成了包袱。我们的做法是引入代理向量Prototype机制每个身份在特征空间中维护一个类别代理初始化时直接取该身份所有样本特征的平均值双样本场景下就是两张图的特征均值然后随着训练过程同步更新。代理向量相比全连接权重有一个本质优势它天然带有这块区域属于谁的语义。特征和代理向量的余弦相似度被映射成概率目标身份除了要求余弦相似度大于其他身份还要减去一个角度边际项。公式可以写成特征z与代理w的余弦相似度记为cos(θ)目标类别的分数为cos(θy) - m其中m是角度边际损失要求该分数高于所有非目标类别的cos(θj)对特征做L2归一化缩放因子设为固定值。边际m的取值可以按身份样本数动态调整样本越少的身份m略微调小一点避免因为监督稀疏导致过度挤压特征空间。我们在50万类规模的训练中把全连接分类头换成了代理向量机制后单卡显存占用下降了接近40%训练稳定性也有明显提升。2.3 双分支度量网络同一对照片产生多个监督信号训练框架上我们采用了一个双分支结构两条分支共享同一个backbone分别输入锚点图和约束样本图输出各自的特征向量。两条分支的权重完全相同不是两个独立网络。PyTorch风格的模型结构大概是这样的import torch.nn as nn class DualBranchNet(nn.Module): def __init__(self, base_model, embed_dim512): super().__init__() self.base base_model self.head nn.Linear(base_model.out_dim, embed_dim) def forward(self, anchor, ref): za self.head(self.base(anchor)) zr self.head(self.base(ref)) return za, zr训练时除了分类损失和配对约束我们还加了一项全局正则让特征向量的分布尽量均匀地落在单位超球面上避免大量身份挤在一起。三项损失加起来锚点图负责这个人是第几类约束图负责这两张图是同一个人的不同状态全局正则负责别让整个特征空间一团乱麻。这里有一个工程上很关键的点双分支只出现在训练阶段推理时只需要跑其中一个分支从部署视角看模型计算量和普通人脸识别模型完全一样不需要为双分支结构额外买单。3. 大规模训练里那些文档不会写的工程细节3.1 五十万类别时分类层是第一个瓶颈在50万类甚至更多类别的设定下第一道坎就是分类层。全连接权重矩阵不仅显存放不下反向传播更新它的耗时也会拖慢整个训练。我们用的方案是混合式训练每个step随机采样一个类别子集比如2万类只在这2万类的范围内做分类代理更新同时把batch内所有其他身份作为负样本用对比损失补足全局判别性。这个设计的想法是单个step里让模型面对全部50万个类既不现实也没必要随机采样的2万类已经足够提供身份分类的梯度信号而batch内负样本对比损失则保证模型在做区分时不只依赖采样到的类别还能和其他随机身份做实时对抗。配套的还有类别均衡采样器每个身份每轮最多贡献固定数量的样本防止一些照片特别多的身份霸占训练节奏。实际使用下来类别均衡采样比普通随机采样带来的TAR提升有2-3个点内部数据这个收益比调整损失函数来得还直接。3.2 采样策略往往比调损失函数更影响结果前面提到了采样器的重要性这里展开说一下。双样本场景下最难处理的不是那种角度夸张、光线诡异的样本而是相似身份互相混淆——比如两个员工戴同款安全帽、脸型轮廓接近闸机就会拿不定主意。这种难样本不是靠简单的随机增广就能覆盖的。我们的做法是构造批内全负样本策略一个batch里同时出现多个ID每个ID抽2张以上样本训练时每个锚点图把batch内其他所有ID都视为负样本。这样的负样本规模等于batch_size * (每个ID平均样本数 - 1)负样本数量上去了模型被迫近距离区分相似身份收敛后特征的可分性明显更好。实践下来还有两个小经验第一个是batch size不能太小我们最少用512条件允许直接拉到1024甚至2048大batch在这里带来的负样本红利非常明显第二个是必须使用全局shuffle多机多卡时如果每张卡自己乱采样类别分布会失衡影响代理向量的全局一致性。3.3 调参记录温度、边际、代理更新率调参是双样本训练里最磨人的环节。我把我们试过的几组不稳定现象和对应的处理方式整理了一下不稳定现象常见原因我们采用的对策特征全部坍缩到一个点附近对比损失权重过大或学习率过大降低配对损失权重增加warmup代理向量几乎不更新每步采样的类别子集太固定扩大采样类别范围随机化采样损失一直降不下去温度太小、边际偏大温度从0.07调到0.1边际降到0.3左右收敛后指标忽高忽低评估协议存在数据泄漏严格执行按身份划分集合修复测试集我们最终采用的损失权重配比是分类损失权重1.0配对约束权重0.5全局正则0.1。温度系数设为0.07角度边际设为0.35。这个组合不是调出来的最优解而是综合考虑稳定性和训练耗时后的性价比之选。如果你在自己的数据上跑我建议先固定一个组合跑通再围绕温度和配对损失权重做小范围搜索大范围搜索的性价比很低。4. 评估体系里最容易自欺欺人的地方4.1 离线验证协议必须注册2张、测试其余很多团队在评估人脸识别模型时习惯沿用公开数据集的协议按类别划分出train和test然后算TARFAR。但这个协议在双样本场景下会失真。你评估的模型明明训练时每个身份只用2张图验证时却又允许它用每个身份几十张注册照片这属于评估和线上实际使用场景脱节。正确的离线评估协议应该刻意模拟真实注册场景训练结束后每个身份只保留2张注册图剩余照片作为测试图注册图应尽量是质量较好、角度较正的样本模拟在线注册测试图应包含自拍、闸机抓拍、监控视角等低质量样本模拟日常使用。我们在TARFAR的档位上线下评估统一用FAR1e-4作为主指标因为门禁场景里每10000次比对允许1次误识这个量级比较贴合实际如果做特别严格的高安全场景再额外关注1e-5和1e-6。4.2 数据泄漏一个看起来性能提升3个点的实验评估协议里最坑的是数据泄漏我们在这里交过学费。有一批从公开渠道收集的面部数据同一个身份的不同照片跨度可能有好几年有人拿旧照片进了训练集新照片留进了测试集。由于训练阶段见过这个人的旧照片测试时遇到他的新照片模型无形中已经认识这个人了。结果离线TAR虚高了3到5个点模型上线后立刻被真实场景打回原形。排查方法说穿了并不复杂严格保证训练集、验证集、测试集的身份集合完全不相交同一个人的任何一张照片都只能出现在一个集合里。还要注意采集批次和来源照片数据如果是从同一个时间点的同一批采集任务里出来的也要按身份拆分块避免批次信息造成隐性泄漏。4.3 阶段式上线测试比离线更有说服力离线评估再怎么严谨也不可能完全复现真实闸机的使用环境。所以我们在离线指标基本达标后一定要做一个阶段式灰度上线先在线下实验室模拟闸机场景再找一到两个真实的门禁出入口跑两周统计真实抓拍成功率、误识次数、单次识别耗时。灰度期间我们重点关注三件事不同时段的自然光变化对大角度抓拍的影响、注册当天识别和注册一周后识别的效果差异、以及逆光、口罩、低头等遮挡情况下的失败模式分布。这些信息用来看离线协议还有哪些盲区比直接闷头调模型有用得多。5. 从实验室到门禁机落地时踩过的坑5.1 注册照和抓拍之间的域鸿沟双样本模型训练得再好落地时也逃不过注册照片是高清正面照抓拍照片是低光照广角图的域鸿沟。闸机摄像头安装位置低、逆光强度大抓到的脸经常半边是黑的这跟注册照完全是两个分布。我们在落地侧做了三件事来弥合这个鸿沟。第一在注册阶段同样做图像质量归一化让注册照片经过和抓拍一致的矫正、缩放、光照调整保证进到特征提取器之前两者的预处理管线完全相同。第二对抓拍视频流做多帧特征聚合不是把几帧的特征直接平均而是先对每一帧做质量评估取质量最高的那帧特征或者对Top-3帧做加权聚合。第三注册照本身也要过一个质量门限分太低的照片不能直接进底库提示用户重新采集宁缺毋滥。5.2 新增员工只有一张照片推理侧怎么办训练模型是离线一次性的但生产环境每天都有新员工入职他可能只提供了一张证件照。总不能为了一个人重新训练一遍模型所以推理侧的注册逻辑必须能承受单张样本。我们的做法是在特征库层面做增强注册时先用质量门限判断这张照片是否合格不达标就让对方重新拍如果确实只有一张合格照片则对该照片特征加一个服从小方差分布的随机噪声生成多个虚拟特征再取它们的均值作为最终入库特征。相当于在特征域做了数据增强比直接拿单张特征入库更稳。要特别注意噪声幅度不能太大否则会把特征推离原本的身份区域反而引入误识。我们内部测试的噪声标准差控制在特征向量L2范数的0.05以内效果比较稳妥。5.3 样本少不代表可以不守数据合规底线双样本场景下总有人试图用各种渠道多凑样本这事儿必须划红线。人脸数据采集要遵循知情同意和最小必要两大原则训练数据要尽量做去标识化处理个人可识别信息与画像特征分开存储。部署侧尽量采用本地化推理不在云端长期保存原始人脸图像。这些不是走过场而是决定项目能不能长期稳定运行的根基。数据合规一旦出错后面算法做得再好都是空中楼阁。6. 私货时间我自己的几条建议最后聊几条纯个人经验不算什么标准答案但都是实战里证过的路子。第一花了最多时间做双样本训练之后我的最大体会是先把评估协议修对再谈算法优化。评估口径不对后面的一切调参都等于在错误的地图上找宝藏。第二优化顺序最划算的是采样器其次是损失权重和损失设计最后才是换backbone。我们试过一上来就换更大的网络效果远没有换一个合适的采样器来得明显。第三把照片来源作为辅助标签喂给模型很有效比如区分证件照、现场照、抓拍图让网络知道输入图像属于哪种域双样本模型在跨域场景下的稳定性会有肉眼可见的提升。如果你手头正在做类似的项目起步阶段建议把batch尽量往上拉配合EMA和warmup先把训练稳定住再用注册2张、测试若干的协议去验证。真实场景的双样本学习没有那么玄乎它本质上是把样本稀缺这个约束拆开揉碎一点点想办法从数据、损失、评估三个方向同时补救。能把这件事做扎实比单纯堆模型复杂度管用得多。