1. 从一万个类别预测说起:DeepID 到底在解决什么问题
人脸识别这件事,在深度学习大规模普及之前,长期卡在一个很尴尬的位置上。实验室环境下拍几张正脸照,识别率看着还行,一旦换到真实场景——光照变化、姿态偏转、表情差异、部分遮挡——准确率就断崖式下跌。当年做人脸识别的人都有一个共识:真正难的不是分类器本身,而是"用什么特征去描述一张脸"。
传统方案走的是人工设计特征的路子,比如 LBP、Gabor、SIFT 这些算子,配合 PCA、LDA 做降维,再喂给 SVM 做分类。这套流程能跑通,但天花板很明显:人工特征能表达的判别信息有限,遇到跨姿态、跨光照的样本就力不从心。而 DeepID 这篇工作的核心思路,是把"特征提取"这件事交给卷积神经网络去做,并且用一个非常巧妙的任务来驱动网络学习——预测一万个类别的人脸身份。
这个标题里的 "Predicting 10,000 Classes" 不是随口写的数字,它是整个方法的关键设计。网络不是直接做"是不是同一个人"的二分类,而是被训练去回答"这张脸属于一万个身份中的哪一个"。这个任务难度极高,正因为难,网络被迫学到极其细粒度的、具有强判别力的特征表示。学完之后,把最后的分类层砍掉,前面输出的那个向量,就是我们要的人脸特征——也就是标题里的 DeepID(Deep hidden IDentity feature)。
我第一次读到这个思路的时候,感觉它和后来 NLP 领域用大规模词表训练语言模型、再拿中间层表示做下游任务的逻辑如出一辙。用一个大而难的任务做预训练,把学到的表示迁移到小任务上,这个范式在视觉领域,DeepID 算是早期非常有代表性的实践。
这篇文章我打算把 DeepID 这套方法从头到尾拆一遍:它为什么这么设计、网络结构长什么样、一万类分类任务怎么落地、特征怎么切块组合、验证阶段用什么度量、训练时有哪些坑。适合正在做人脸识别、度量学习、或者对"用分类任务学特征表示"这个范式感兴趣的读者。哪怕你只是想把 DeepID 复现出来跑通,文中的参数选择和实操细节也能直接抄。
2. 核心设计思路拆解:为什么是"一万类分类"而不是"直接比对"
2.1 人脸验证和身份分类,本质是两件事
很多人一开始会混淆:人脸识别不就是判断两张脸是不是同一个人吗?那直接训练一个二分类网络不就行了?问题在于,二分类任务给出的监督信号太弱了。你告诉网络"这两张是同一人""这两张不是同一人",网络学到的是一个粗糙的边界,它不需要理解人脸的细粒度结构,只要抓住一些表面差异就能把训练集拟合好。
而一万类分类任务完全不同。网络要在一万个身份之间做区分,意味着它必须捕捉到那些能把张三和李四分开、同时又能让张三的不同照片聚在一起的特征。这种压力会逼着卷积层去学习眼睛、鼻子、嘴巴、脸型、肤色纹理等具有身份判别力的局部模式。换句话说,分类任务只是一个"手段",真正的目的是让网络在完成这个难任务的过程中,顺带学出一套好的人脸表示。
这里有个关键认知:DeepID 最终用于人脸验证时,用的不是分类结果,而是分类网络中间层的输出向量。分类任务只是训练时的"脚手架",用完就拆。
2.2 一万类这个量级是怎么定的
类别数不是拍脑袋定的。类别太少,比如几百类,网络很容易过拟合,学到的特征判别力不足;类别太多,比如几十万类,训练成本飙升,而且很多类别样本极少,反而拖累收敛。一万类这个量级,在当年的数据和算力条件下是一个平衡点——既能提供足够强的监督信号,又不至于让训练完全不可行。
从信息论的角度看,一万类分类意味着每次前向传播,网络要在一个 log(10000)≈9.2 维的决策空间里做选择。这个任务的信息量远大于二分类的 1 bit,网络被迫把输入图像压缩成一个高度浓缩、判别性极强的表示。这也是为什么 DeepID 学出来的特征,即使只用简单的距离度量,也能取得很好的验证效果。
2.3 特征切块与多区域组合的设计哲学
DeepID 还有一个容易被忽略但极其重要的设计:它不是对整张脸提取一个特征,而是把脸切成多个区域,每个区域单独提特征,最后拼接。这个思路背后有很强的先验——人脸的不同部位携带的身份信息是互补的。眼睛区域对身份判别贡献大,鼻子、嘴巴、脸颊各有各的信息,全局脸型也有用。
如果只用一个全局特征,网络可能会偏向某些显著区域而忽略其他。切块之后,每个 patch 强制网络关注局部细节,最后把多个 patch 的特征拼起来,形成一个维度更高、信息更全的表示。实测下来,这种多区域组合相比单区域,验证准确率有明显提升。代价是特征维度变高、计算量变大,但在精度优先的人脸验证场景里,这个代价是值得的。
3. 网络结构与关键实现细节
3.1 卷积网络的具体层配置
DeepID 用的卷积网络结构,放在今天看算是"轻量级"的,但在当年是经过精心设计的。整体是若干个卷积层加池化层堆叠,最后接全连接层,输出一个 160 维的 DeepID 特征向量,再送到一个 softmax 层做一万类分类。
具体来说,卷积层负责提取局部纹理和结构模式,池化层负责降采样和一定程度的平移不变性。这里有个细节值得说:DeepID 特征是从倒数第二层全连接层取出的,而不是最后一层。最后一层是分类层,它的输出维度是一万,直接和类别绑定,泛化性差;倒数第二层是 160 维,它是网络对身份的"压缩表示",既保留了判别信息,又不至于过拟合到具体类别上。
| 层级类型 | 作用 | 关键参数 |
|---|---|---|
| 卷积层 | 提取局部特征 | 小卷积核,逐层加深通道数 |
| 池化层 | 降采样、增强鲁棒性 | 最大池化为主 |
| 全连接层 | 特征整合 | 输出 160 维 DeepID 特征 |
| Softmax 层 | 一万类分类 | 仅训练时使用,验证时丢弃 |
3.2 输入 patch 的尺寸与切块策略
输入不是整张对齐后的人脸,而是从人脸关键点周围裁剪出的 patch。常见的做法是取多个尺度、多个位置的 patch,比如以眼睛、鼻子、嘴巴为中心各取若干块,再加上一些全局块。每个 patch 单独送进网络,得到一个 160 维特征,最后把所有 patch 的特征拼接起来。
这里有个实操经验:patch 的尺寸和数量需要根据你的人脸对齐质量来调。如果对齐做得好,关键点稳定,可以取更多、更小的 patch,捕捉更细的局部信息;如果对齐质量一般,patch 取大一点、数量少一点,避免噪声干扰。我见过有人直接照搬论文的 patch 配置,结果因为自己的人脸对齐流程不同,效果差了一大截。
3.3 一万类 softmax 的训练技巧
一万类 softmax 在当年是个不小的计算挑战。类别多,输出层参数矩阵就是 160×10000,参数量一百六十万,加上梯度计算,显存和算力压力都不小。几个实用的处理方式:
- 用 GPU 加速:这个不用多说,CPU 上训一万类基本没法玩。
- 合理设置 batch size:batch 太小梯度噪声大,太大显存吃紧,需要根据硬件找平衡点。
- 学习率 warmup:一万类任务初期梯度方向不稳定,先用小学习率预热几百步,再升到正常值,收敛更稳。
- 类别采样均衡:如果某些身份样本特别多、某些特别少,训练会偏向多数类,需要对采样做均衡处理。
注意:一万类分类任务里,很多类别可能只有几张甚至一张图。这种"少样本类别"会让 softmax 训练变得不稳定。一个常见做法是保证每个类别至少有若干张图,不足的类别做数据增强补齐,或者干脆从训练集中剔除。
4. 从训练到验证:完整实操流程
4.1 数据准备与预处理
整个流程的第一步是数据。你需要一个带身份标注的人脸数据集,每个身份有多张不同条件下的照片。数据量越大、身份越多,学出来的特征泛化性越好。预处理包括人脸检测、关键点定位、对齐、裁剪。
对齐这一步特别关键。DeepID 依赖关键点来切 patch,如果关键点漂移,patch 就切歪了,特征质量直接崩。我的建议是:对齐后的人脸,双眼应该在同一水平线上,人脸中心大致在图像中央。可以做一个可视化检查,随机抽几十张对齐结果看一眼,确认没有明显歪斜。
4.2 训练流程与参数设置
训练阶段的核心是让网络在一万类分类任务上收敛。流程大致如下:
- 初始化网络权重,卷积层用合适的初始化方法,避免梯度消失或爆炸。
- 前向传播,计算一万类 softmax 损失。
- 反向传播,更新所有层参数。
- 在验证集上监控分类准确率和特征质量。
- 训练到收敛后,保存模型,取出倒数第二层的 160 维特征提取能力。
参数方面,学习率、动量、权重衰减这些都需要调。我个人的经验是,先用一个小规模子集快速跑通流程,确认没有 bug,再上全量数据。直接上全量数据,一旦中间某步出错,浪费的时间成本很高。
4.3 特征提取与拼接
训练完成后,把每张人脸的所有 patch 分别送进网络,取出每个 patch 的 160 维特征,然后拼接。假设你用了 10 个 patch,最终特征维度就是 1600 维。这个高维特征就是后续验证的基础。
拼接方式也有讲究。简单拼接是最直接的,但不同 patch 的贡献其实不一样。有些工作会对不同 patch 的特征做加权,或者做 PCA 降维后再拼接。如果计算资源允许,我建议先做简单拼接,跑通 baseline,再考虑加权优化。
4.4 验证阶段的度量方法
验证阶段,给定两张人脸,分别提取 DeepID 特征,然后计算相似度。常用的度量有两种:
- 余弦相似度:看两个特征向量的夹角,对向量长度不敏感,适合特征已经归一化的情况。
- 联合贝叶斯(Joint Bayesian):把人脸特征建模成"身份分量 + 类内变化分量",通过概率模型判断两张脸是否同一人。这个方法在 DeepID 的论文里被重点使用,效果比直接算距离好。
联合贝叶斯的核心思想是:两张脸的差异,一部分来自身份不同,一部分来自同一个人的不同照片(姿态、光照、表情)。把这两部分分开建模,就能更准确地判断。实现上需要在一批标注数据上估计类内和类间的协方差矩阵,然后做推断。
| 度量方法 | 优点 | 适用场景 |
|---|---|---|
| 余弦相似度 | 简单、快、无需额外训练 | 快速 baseline、特征已归一化 |
| 联合贝叶斯 | 精度高、能建模类内变化 | 对精度要求高的验证任务 |
| 欧氏距离 | 直观、易实现 | 特征分布较均匀时 |
5. 常见问题与排查技巧实录
5.1 训练不收敛或损失震荡
这是最常见的问题。原因可能有很多:学习率太大、batch size 太小、数据没归一化、类别极度不均衡。排查顺序建议是:先看数据,确认输入图像和标签对应正确、像素值归一化到位;再看学习率,试着降一个数量级;最后看类别分布,做采样均衡。
我踩过的一个坑是:图像读取时通道顺序搞反了(RGB 当成 BGR),网络也能训,但收敛慢、精度低,排查了很久才发现。所以数据预处理完,一定要可视化几张图确认。
5.2 特征区分度不够
如果训练收敛了,但验证时特征区分度差,可能的原因包括:patch 切得不好、网络容量不够、训练数据身份数太少。可以尝试增加 patch 数量、加深网络、扩充训练身份。另外,检查一下取特征的那一层是不是取错了,一定要取全连接层而不是 softmax 层。
5.3 验证阶段阈值怎么定
人脸验证最终要输出"是/否同一人",需要一个相似度阈值。阈值定高了,误拒多;定低了,误接受多。标准做法是在一个独立的验证集上,遍历不同阈值,画出 ROC 曲线,根据业务需求选一个平衡点。千万不要在训练集上定阈值,那样会严重高估性能。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 损失不下降 | 学习率过大、数据标签错 | 降学习率、检查数据 |
| 损失震荡 | batch 太小、类别不均 | 增大 batch、均衡采样 |
| 验证精度低 | 特征层取错、patch 质量差 | 确认取层、检查对齐 |
| 过拟合 | 数据太少、模型太大 | 数据增强、正则化 |
| 推理慢 | 特征维度高、patch 多 | 降维、减少 patch |
6. 这套方法的影响范围与后续演进
DeepID 的价值不只是一篇论文,它确立了一个范式:用大规模身份分类任务来驱动人脸特征学习。这个思路后来被广泛继承和发展。后续的工作在这个基础上做了很多改进,比如更深的网络、更大的类别数、更好的损失函数(如中心损失、角度间隔损失)、更强的数据增强。
从更宏观的视角看,DeepID 代表的"分类预训练 + 特征迁移"思路,已经超出了人脸识别的范畴。在很多需要学通用表示的领域,用一个大而难的任务做预训练,再把中间层表示迁移到下游任务,都成了标准操作。理解了 DeepID,其实就理解了这类方法的内核。
我在实际复现和使用这套方法时最大的体会是:特征的质量,七分靠数据和对齐,三分靠网络结构。很多人一上来就纠结网络怎么改,其实把数据清洗好、对齐做准、patch 切对,效果提升比换网络明显得多。另外,一万类分类这个任务本身对算力有要求,如果资源有限,可以先用几千类跑通,验证流程没问题再扩到一万类。这个内容后续还可以往度量学习的方向扩展,比如把 softmax 换成带间隔的损失,进一步拉开类间距离、压缩类内距离,验证精度还能再上一个台阶。