Memoria 开发记录 18:相似人脸不等于同一个人——身份聚类、向量索引与错误合并
前言
人物相册的目标不是找出“画面里有人”,而是把同一个人在不同时间、光照、表情和角度下的照片聚合起来。这个任务比普通图片分类更敏感:把同一个人拆成多个簇会显得混乱,把两个不同的人合并到一起则会直接破坏用户信任。
Memoria 为人脸与照片向量建立 ObjectBox 索引,并在后续提交中收紧身份聚类、删除旧向量回退。这里的关键经验是:向量存在不代表身份可靠,聚类必须围绕错误代价设计。
通用图片向量不能替代人脸向量
MobileCLIP 擅长理解整张图片语义,但整图向量会同时编码背景、服装、场景和构图。如果用它判断人物身份,同一个人在海边和室内的照片可能差异很大,穿相似衣服的不同人反而可能靠得很近。
身份聚类应使用专门的人脸裁剪和 face embedding:
原图 -> 人脸检测 -> 对齐裁剪 -> FaceNet/ArcFace 向量 -> 身份聚类
主题页面可以用整图向量判断“人物场景”,但不应该把它宣称为身份识别。
向量索引解决什么问题
照片和人脸数量增长后,逐条读取实体中的向量并线性比较会越来越慢。向量索引将 embedding 与业务实体分离,并支持近似最近邻查询。
索引实体通常需要记录:
- 业务实体 ID;
- 模型版本;
- 归一化向量;
- 媒体或人脸类型;
- 可选的生成元数据。
模型版本非常关键。不同模型产生的向量不能直接混在同一个空间比较。即使维度相同,语义分布也可能完全不同。
为什么要收紧身份聚类
提交 bd80e0d 调整人脸聚类、embedding 和 pipeline,并大幅扩充测试。身份聚类常见失败来自“链式合并”:
A 与 B 相似
B 与 C 相似
于是算法把 A、B、C 合为一组
但 A 与 C 实际不是同一个人
当阈值稍宽,DBSCAN 或并查集式聚类很容易沿着中间样本不断扩张。更保守的策略需要检查簇内一致性,而不是只看局部邻居。
对于人物相册,错误合并的代价通常高于错误拆分。因此可以:
- 提高合并阈值;
- 要求候选与簇中心和多个成员都相似;
- 对低质量、侧脸和遮挡样本降低权重;
- 小簇先独立保留,不急于强行归并;
- 为用户提供手动合并和拆分能力。
删除旧向量回退的重要性
提交 949064e 删除旧向量 fallback。回退逻辑看起来提高兼容性,实际可能让系统在新索引缺失时悄悄使用旧模型或实体字段中的过期向量。
这种静默回退会导致最难排查的问题:同一个页面中,一部分结果来自新模型,一部分来自旧模型,排序和聚类不可复现。
更可靠的策略是:
当前模型向量存在 -> 使用
当前模型向量缺失 -> 明确重新生成或标记不可用
绝不跨模型空间静默比较
测试为什么比调阈值更重要
人脸聚类无法只靠几个演示样本判断。测试应覆盖:
- 同一人不同角度应合并;
- 双胞胎或相似人物不应轻易合并;
- 单张低质量脸不能连接两个稳定簇;
- 空向量、维度错误和旧模型版本必须拒绝;
- 聚类结果在输入顺序变化后保持稳定。
提交中增加数百行聚类测试,价值就在于把“感觉这个阈值不错”转化为可持续验证的行为约束。
总结
人物身份聚类是一项需要保守设计的能力。正确方向不是尽可能把每张脸归入某个组,而是保证每次自动合并都足够可信。
最终经验包括:
- 身份聚类必须使用专用人脸向量;
- 向量索引要记录模型版本,禁止跨空间比较;
- 聚类需要防止链式误合并;
- 错误合并的代价高于错误拆分;
- 不应通过旧向量静默 fallback 掩盖缺失;
- 阈值和聚类规则必须由稳定测试集约束。
对应提交:bd80e0d、949064e、00e4e4a、6bf61a2。
