ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从VeriCam验证基线看开放集识别:先验证再分类的工程实践

2026/9/4 13:10:14 拓冰建站 浏览量
从VeriCam验证基线看开放集识别:先验证再分类的工程实践 VeriCam 这个题目看着像是一个模型名字实际上更值得关注的是它提出的一种思路面对未知数据分类系统不能只想着“归类”而要先完成验证。尤其在做视觉分类、工业质检、开放环境识别甚至文本关系分类时模型几乎都会遇到训练时没见过的样本。传统做法是把所有样本强制塞进已知类别里结果只要现场出现一种新瑕疵、新物体或者新关系系统就会用很高的置信度给出一个错误答案。VeriCam 的价值在于把“验证”作为一个基线步骤提出来先判断这份数据到底属不属于当前已知空间再决定继续分类还是交给人工处理。下面按我在实测这类验证基线时习惯的顺序把这个思路完整拆一遍。1. 先想清楚未知数据的“未知”到底卡在哪一环1.1 闭集分类和开放分类是两套问题大多数模型训练时使用的是闭集假设训练集有多少个标签测试时就只从这些标签里出样本。模型只需要学一个边界把已知类别分开就行。但在真实场景里几乎没有这么理想。生产线会出现从未标注过的新缺陷安防摄像头会拍到新角度、新物体内容审核会遇到新类型文本关系抽取系统也会碰到没有预定义关系的句子。这些数据的共同点是标签空间里根本没有它们的位置。传统分类器遇到这类样本时会怎么做softmax 最后一层会把概率归一化模型必须把所有概率分配给已知类别。即使样本完全不属于任何一个类它也会挑一个相对最高的类别输出。这时候模型“分得很自信”但结果没有任何工程意义。真实业务要的不是这种伪自信而是让模型能说一句“这个我看不懂”。1.2 验证和分类是两个不同阶段很多人把“检测未知数据”理解成再加一个类别比如把所有未知类统一标成“其他”。这个做法表面能跑实际很容易出问题。因为未知数据的形态并不统一有的是已知类别里的异常形态有的是完全陌生的新类别有的是噪声有的是不同来源的域偏移。把它们硬压成一个“其他”类会让模型学到一堆互相冲突的边界已知类别的精度也会被拖垮。VeriCam 这类验证基线更稳妥的做法是把流程拆成两段。第一段只做验证判断当前样本是否落在已知类别分布范围内。验证通过再走第二步用分类器给出具体标签。验证不通过直接拒绝分类输出 unknown或者交给人工复核。分类和验证分离之后每一步的目标都更清楚也更容易排查问题。1.3 这种基线到底适合谁用如果你正在做以下几类事情这个思路会特别有参考价值。第一种是工业视觉检测。正常的时候只需要分良品和不良品但现场随时可能出现未知缺陷类型。第二种是自动驾驶或巡检场景模型需要区分已知交通目标和从未见过的障碍物。第三种是内容理解系统需要把低置信度和不认识的内容单独捞出来而不是硬分进已有分类里。第四种是做科研和算法对比需要用一个足够简单、足够稳定的验证基线作为参照再去评估其他开放集识别方法是否真的有效。我自己的判断标准是如果你的业务允许“拒绝回答”并且拒绝之后有人工复核通道那验证基线的价值就非常大。如果业务要求每个样本都必须给出一个标签那基线能提供的帮助会小很多需要再叠加一层强制映射逻辑。2. 正式跑之前先把环境、数据和指标口径统一好2.1 运行环境按“能提取特征”来准备原始材料没有给出 VeriCam 的具体实现参数所以我这里说的是通用前置条件。这类验证基线的最底层能力来自特征提取器通常是训练好的卷积网络或视觉 Transformer。跑通单条推理任务的门槛并不高有 CPU 也能跑但速度和批量处理能力会有明显差异。可以按这个条件准备开发环境系统Linux 最省事Windows 和 macOS 也能跑注意文件路径风格不同。Python 版本建议 3.8 以上具体以实际依赖为准。深度学习框架PyTorch 或 TensorFlow 都可以看你现成模型的格式。图像处理库OpenCV、Pillow 至少要有一个用于读取和预处理图片。科学计算库NumPy、scikit-learn 常用在距离计算和指标评估上。显存属于“影响速度但不决定成败”的资源。如果只有 CPU可以把批量数调小一次处理 8 张、16 张图片。如果手头有 4G 显存的 GPU大部分预训练特征提取器都能正常跑。真正吃显存的是后续微调或者同时管理大量类别原型这会另说。2.2 数据切分不能只分训练集和测试集要验证“未知数据”能不能被识别评估集必须有技巧地构造。最核心的原则是参与分类器训练的类别在验证阶段不能全部出现必须有一部分类别从头到尾只作为 unknown 数据使用模型在训练时绝对没见过它们。我建议至少分成三块已知类别支持集。用于训练特征提取器或者计算类别原型。验证校准集。里面既包含已知类别样本也包含一部分 unknown 样本用来调阈值。测试集。包含已知类别和另一批 unknown 样本用来做最终效果评估。很多项目失败不是因为模型不行而是测试时把同源的 unknown 和已知样本混在一起做交叉验证导致评估结果虚高。unknown 数据一定要和已知类别在来源上有区分度不能只是单纯地挖掉几个已知类别的标签否则模型很可能只是靠低频视觉特征来识别而不是靠真正的类别语义边界。2.3 用哪些指标判断基线效果在做验证基线时不能只看分类准确率。因为一个能拒绝所有样本的系统在传统“全部分类正确”指标里会非常差但在真实业务里反而可能是安全的。适合判断验证基线的常用指标如下。指标计算口径关注点AUROC把已知/未知二分后计算 ROC 曲线下面积综合衡量已知和未知的可分性FPR95%TPR已知样本召回率达到 95% 时未知样本的误收率业务最关心的安全边界已知类别精确率验证通过且被分类的样本中分类正确的比例保证分类质量不因验证而下降未知拒绝率未知样本被验证阶段拒绝的比例验证环节的直接能力覆盖率测试集中被验证通过的样本比例避免过度拒绝导致业务不可用第一次跑建议先看 AUROC 和 FPR95%TPR。AUROC 太低说明特征空间里已知和未知基本重叠修阈值意义不大要回头处理特征提取器和数据问题。AUROC 还行但阈值选不准说明问题出在标定环节优先处理阈值搜索方法。3. 从零搭一个可复现的验证基线流程3.1 整体流程先看一遍这类基线在设计上并不复杂关键在于每一步都要有明确产出。我建议按照下面五步来做加载或训练特征提取器把原始图片变成向量。用已知类别的训练样本计算每个类别的原型也就是类别中心。在验证集上计算分数分布并搜索验证阈值。对待测样本先做距离判断确定它属于已知类别还是 unknown。对验证通过的样本执行类别映射输出最终标签和置信度。先跑通这五步再考虑并发、批量、接口这些工程化问题。3.2 特征提取器怎么选验证基线好不好用很大程度取决于特征空间有没有区分度。直接使用模型最后 softmax 之前的 logits 也是一种思路但它在开放集场景下容易过于自信。稳定做法是截断分类头把分类头之前的特征向量拿出来作为样本表示。下面这段代码只用于演示结构不是 VeriCam 原项目的源码。实际使用时要根据你自己的模型调整层名和输出维度。import torchvision.models as models # 示例使用预训练 ResNet 作为特征提取器 encoder models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) # 去掉分类头输出特征向量 encoder.fc torch.nn.Identity() # 推理时得到的是特征向量而非类别概率 with torch.no_grad(): feature encoder(image_tensor)如果用预训练模型不需要从头训练就能得到不错的基线。如果业务数据高度特殊比如工业零件背景和 ImageNet 差异很大建议在自己的已知类别上再做一轮微调。微调时不要只优化分类头最好保留特征空间的度量结构否则特征向量可能发生“坍缩”——所有已知类都挤在一起未知类反而很难被拒掉。3.3 用类别原型代替类别中心硬边界得到每个样本的特征后可以对每个已知类别算一个原型向量。最简单的方式是取该类别所有训练样本特征的平均值然后再做一次归一化。之后新样本和每个类别原型的距离就是验证分数的基础。我在实践里更倾向于使用余弦相似度而不是欧氏距离因为它对特征向量的长度不敏感对图像尺度、对比度的变化更稳定。余弦相似度越接近 1说明样本和某个已知类别越像整体相似度都很低说明样本处于已知分布之外。一个类别原型类可以这样组织class PrototypeModel: def __init__(self): self.class_centers {} # {class_name: tensor} def add_class(self, name, features): # features 是该类别多个样本的特征集合 center features.mean(dim0) center center / center.norm() self.class_centers[name] center def score(self, feature): # 返回样本和所有类别的相似度 max_score -1.0 best_label None for name, center in self.class_centers.items(): s (feature * center).sum().item() if s max_score: max_score s best_label name return max_score, best_label如果你的标注噪声较大可以考虑更稳健的原型计算方式比如先对某一类的多个样本做聚类去掉离群样本后再取均值。这样能避免个别坏标注把类别中心带偏。3.4 阈值要在验证集上搜索不要拍脑袋验证环节最核心的一个参数就是阈值。阈值太高系统会大量拒绝已知类别样本覆盖率很低阈值太低unknown 样本会被放进已知类别安全边界失效。阈值不能只看单个分类任务的验证集精度来决定而要通过已知和未知两类样本的分数分布来搜索。大致流程是提取验证集所有样本的特征。计算每个样本和它所属类别的相似度得到 known 分数分布。对 unknown 样本计算相似度得到 unknown 分数分布。遍历候选阈值记录每个阈值下的已知样本召回率和 unknown 误收率。根据业务安全等级选择最终阈值。下面是一个阈值搜索的示例import numpy as np # known_scores: 已知样本相似度 # unknown_scores: 未知样本相似度 for threshold in np.linspace(0.5, 0.99, 50): known_recall (known_scores threshold).mean() unknown_reject (unknown_scores threshold).mean() print(threshold, round(known_recall, 3), round(unknown_reject, 3))业务对风险敏感就选一个高 unknown 拒绝率对应的阈值哪怕牺牲一部分已知覆盖业务更看重可用性就优先保证已知召回率高于 95%再接受一定的未知误收率。这个权衡必须做一个显式记录否则下次换数据集或者换特征提取器时阈值会变成最难解释的玄学。3.5 第一次跑通按最小方式验证我在做这类项目时不会一口气把全量数据扔进去。先取 30 到 50 个已知样本和 20 到 30 个 unknown 样本作为最小验证集先跑单张图片确认特征提取、归一化、相似度计算都能输出结果。再跑一整个小批量文件夹确认日志和输出文件能正常生成。检查每个 unknown 样本的相似度分数是否显著低于已知样本。如果看不到差异先别调阈值回头检查特征提取器是否真的去掉了分类头以及图像预处理是否和数据加载保持一致。如果前面流程都正常再上全量数据。这样能把“算法问题”和“工程问题”隔离开。很多看似模型不收敛的情况实际是图片读成空张、路径错误、归一化参数不统一导致的。4. 关键参数、资源占用和结果判断4.1 参数不是越复杂越好验证基线的参数比端到端深度模型要少但每个参数的影响都很直接。参数项作用调整边界特征维度决定原型和距离计算开销维度太低语义表达不够太高会放大噪声常用 128 到 1024距离/相似度方式余弦或欧氏判断样本是否靠近原型特征做过归一化时优先用余弦验证阈值决定已知覆盖和未知拒绝的平衡点必须用验证集标定不能随机设置批量大小影响特征提取速度CPU 上先设 8 或 16GPU 可逐步增大图像分辨率影响特征质量与预训练模型输入不一致时效果会下降类别原型数量决定每次预测比对的次数类别很多时要做向量化批量计算避免 for 循环耗时这里面最容易忽略的是图像分辨率。很多预训练模型默认输入是 224×224如果你的业务图是 1024×1024 的大图直接缩到 224 会丢失局部细节unknown 样本和小目标缺陷更难被识别。稳妥做法是先做一次小样本对照观察不同分辨率下的 known/unknown 分值和耗时变化。4.2 正常流程的输出应该长什么样一次完整的验证加分类建议输出结构化结果方便后续接日志、告警或者人工平台。字段至少包括图片路径或样本 ID验证结果known 还是 unknown最大相似度分数如果验证通过输出预测类别如果验证拒绝输出第二、第三候选类别和对应分数这样做的好处是能从日志里复盘。即使某个 unknown 样本被误收进已知类别你也可以看到它的分数很接近阈值边缘而不是因为一个莫名其妙的分类结果才出错。向其他系统输出时用 JSON 格式最直接。例如{ sample_id: img_1024, is_known: true, score: 0.912, label: surface_scratch, secondary_label: surface_stain, secondary_score: 0.871 }如果写入数据库就把 score 和 is_known 作为单独字段不要混在文本描述里。这样后续做阈值调整、模型迭代时可以直接用 SQL 或 pandas 做分布分析。4.3 资源占用和批量速度怎么判断最低配置下也能跑但要对预期有数。纯 CPU 跑一个 ResNet18 级别的特征提取器单张图片可能在几十到几百毫秒之间GPU 环境会快很多但瓶颈往往不在模型本身而在图像解码和批量加载。启动批量任务时我建议把流程拆成两段先离线缓存已知类别原型待测样本再逐批提取特征。不要在每次预测时重算一遍已知类别特征。如果你手头数据量到了几十万甚至上百万张除了关注显存还要关注磁盘 I/O。图片解码是容易被忽略的瓶颈。可以先做一个小实验在同一台机器上分别测 1000 张和 10000 张图片的端到端耗时观察耗时是否线性增长。如果增幅明显高于线性就要检查是不是磁盘随机读取太慢或者输出日志重复写盘。5. 最常见的问题和通用排错顺序5.1 几个高频失败现象现象常见原因处理方向所有 unknown 都被判成已知阈值过低特征空间不具备区分能力unknown 来源和训练数据太相似先看分数分布再决定调阈值还是换特征已知样本大量被拒绝阈值过高已知类别特征方差太大图像预处理不一致降低阈值增加类别原型或改用高斯建模输出结果不稳定数据加载顺序不同图像增强在推测阶段仍被开启批量归一化在 eval 模式有问题固定随机种子确认 model.eval()保持推理预处理一致分数分布几乎重叠特征坍缩模型没有微调分类头未被正确截断检查特征向量维度换更有效的编码器单条跑通但全量任务卡住显存或内存不足输出目录写满并发没有资源限制降低批量数清理磁盘加任务重试如果你遇到“明明代码能跑但输出质量差”的情况第一步不是换更复杂的模型而是先可视化分数分布。直接把 known 和 unknown 的相似度分数画在同一张图上两条线叠在一起说明是特征空间的分离度问题两条线分开但阈值选得不对说明是阈值标定问题。这两类问题的修法完全不同。5.2 通用排查顺序我在排查这类系统时会严格遵守“现象、输入、环境、参数、工具”的顺序不会一上来就改模型结构。先看现象是报错、卡住、无输出还是输出质量不对再看输入文件路径是否正确图片能否正常解码类别标签和原始目录是否对应。再看环境依赖版本是否和训练时一致CUDA 是否可用磁盘空间是否充足。再看参数阈值、批量数、图像尺寸、归一化方式是否被意外改动。最后才看工具本身特征提取器有没有加载错权重分类头是否真的被截断。许多看起来像模型能力不足的问题最后都出在输入或环境上。比如我用过一个项目某类图片无法读取模型直接将该类平均特征计算成空向量导致这个类别所有样本都被判成 unknown。这类问题如果只盯着验证阈值调永远也调不对。5.3 如何验证修复是否有效修复后不是跑一次准确率就结束。建议做一个小型回归测试从已知样本和 unknown 样本中各抽一部分记录修复前后的分数分布、AUROC、FPR95%TPR以及典型误判样本列表。对比后才能确认修复方向是否正确。如果是数据问题要做一次数据完整性和标签一致性检查防止脏数据反复影响模型。如果是阈值问题要把阈值标定脚本固化成可重复执行的步骤并记录标定使用的校验集文件列表方便后续复现。6. 从验证基线到生产落地还要补哪些东西6.1 基线不是最终方案但它是重要的参照物VeriCam 这类名字里带 “Baseline” 的工作本意通常是给后续更复杂的算法提供比较基准。它的优势不在“最准”而在“最容易复现、最容易解释、最容易失败修复”。生产环境里一个稳定到能解释的基线往往比一个黑盒高精度模型更好落地。所以不要指望验证基线在所有数据集上都超过最新的开放集识别方法。它的价值是给你一个明确的下限如果连一个基于原型距离验证的简单方案都跑不好那就不要急着上更复杂的模型如果能跑过验证基线说明提升来自更先进的特征或分类策略而不是来自调参运气。6.2 同一个思路可以扩展到文本关系分类等任务热词里有一个方向是 relation classification via convolutional deep neural network也就是利用卷积神经网络做关系分类。这个问题和视觉分类非常像一个句子中的实体对可能属于某种预定义关系也可能根本没有关系。如果只用 softmax 强行分类模型会对无关系句子输出一个概率最大的关系标签错误率非常高。参考 VeriCam 的思路可以这样改造用卷积网络对实体上下文做编码得到关系表示向量对每种预定义关系维护一个关系原型待测句子先计算与所有关系原型的相似度。最高相似度低于阈值时就输出 no_relation。这套逻辑和图像验证基线是同一个套路只是数据编码层从图片变成了文本。迁移成本很小效果很容易评估。这也说明验证基线的价值并不仅限于某一个项目。只要业务中存在“不知道输入属于哪一类”的场景都可以先引入一个简单的验证步骤让系统能拒绝回答未知问题。6.3 落地建议经过几次实测后我的建议比较明确。第一优先把验证流程跑稳再优化分类精度。能正确拒绝 unknown 数据比把已知数据分得更精确更重要。第二未知样本不要直接丢弃。把被拒绝的样本保存下来定期让业务人员做少量标注等数量足够时再扩展成新类别。这能让系统持续成长。第三把阈值、校验集、特征提取器版本记录成配置。生产环境一旦换模型要重新标定阈值不要沿用旧数值。最后总结一个实际经验这个方案真正落地时最该盯住的不是功能列表而是你看不见的边界条件。原始材料里没有给出 VeriCam 的官方实现细节所以我上面所有流程都按通用验证基线的方式去还原。你可以先用 30 张图把五步流程跑通再看分数分布最后决定要不要引入更复杂的距离度量、异常检测模型或增量学习方法。从这个角度说验证基线不只是一个算法更是一套让未知数据从“模型乱猜”变成“系统主动拒绝”的工程方法。