1. 开放集识别到底在解决什么问题第一次接触开放集识别Open Set Recognition简称 OSR这个概念是在做一个图像分类项目的时候。当时训练集里只有十类目标测试的时候模型遇到了一张完全不属于这十类的图片结果它仍然以很高的置信度把这张图分到了某个已知类别里。这个问题在学术上叫“闭集假设失效”——传统分类器默认测试时出现的类别一定在训练集中出现过但现实世界显然不是这样运转的。开放集识别要解决的核心问题就是模型在测试阶段遇到训练时从未见过的类别时既能正确分类已知类别又能把未知类别识别出来并拒绝分类。这跟异常检测、分布外检测有交集但侧重点不同。异常检测更多关注“数据是否异常”而开放集识别关注的是“这个样本属于已知类还是未知类”同时还要保持对已知类的分类精度。这个方向从 2013 年前后被系统性地提出以来经历了从传统方法到深度学习方法的演进。早期的工作主要基于支持向量机SVM的变体比如 1-vs-Set Machine、W-SVM 等核心思路是在决策边界上留出空间给未知类。后来随着深度学习的兴起基于深度神经网络的开集识别方法逐渐成为主流比如 OpenMax、G-OpenMax、CROSR、ARPL 等。这些方法试图在特征空间或者 logit 空间上做文章让已知类的特征更紧凑同时给未知类留出低密度区域。为什么这个问题值得关注因为几乎所有真实世界的应用场景都是开放集的。你部署一个人脸识别系统不可能保证数据库里包含了全世界所有人你做一个医学影像诊断模型不可能穷尽所有疾病类型你搞一个垃圾邮件过滤器新的垃圾邮件变种每天都在出现。所以开放集识别不是一个纯学术问题它有很强的实际落地价值。适合读这篇内容的人包括正在做分类模型落地、遇到未知类别误判问题的工程师研究异常检测、分布外检测方向的学生和研究者以及任何对模型鲁棒性和可靠性感兴趣的技术人员。不管你是刚入门还是已经有一些经验下面这些内容应该都能帮你理清这个领域的脉络和实操要点。2. 开放集识别的核心思路与方案选型2.1 从闭集到开集问题定义的关键转变闭集分类的数学表达很简洁给定训练集 $D_{train} {(x_i, y_i)}_{i1}^N$其中 $y_i \in {1, 2, ..., K}$模型学习一个映射 $f: X \rightarrow Y$测试时假设所有测试样本的标签都在 ${1, ..., K}$ 中。但开放集识别把这个假设打破了测试集中可能存在标签不在 ${1, ..., K}$ 中的样本模型需要输出一个额外的“未知”类别。形式化地开放集识别要求模型学习一个函数 $f: X \rightarrow Y \cup {unknown}$同时满足两个条件对已知类样本的分类准确率尽可能高对未知类样本的拒绝率尽可能高。这两个目标之间存在天然的张力——你如果把决策边界收得太紧已知类的样本可能被误拒放得太松未知类样本又会混进来。这里有一个很重要的概念叫“开放空间风险”Open Space Risk。它衡量的是决策边界在特征空间中覆盖的“未知区域”的大小。开放集识别的目标就是在保证经验风险最小的同时最小化开放空间风险。这个理念最早由 Scheirer 等人在 2013 年的论文中系统提出后来成为这个领域的理论基础。2.2 主流技术路线对比生成式 vs 判别式 vs 重构式目前开放集识别的方法大致可以分成三条技术路线每条路线背后的直觉不同适用场景也有差异。生成式方法的核心思想是建模已知类的数据分布然后根据测试样本是否落在已知类分布的高密度区域来判断它是否属于已知类。OpenMax 是这类方法的代表它不直接修改分类器的输出而是在 softmax 之前对 logit 向量做处理用极值理论Extreme Value TheoryEVT对每个已知类的 logit 分布尾部建模然后根据测试样本的 logit 向量与各个已知类分布的偏离程度来计算“未知”概率。G-OpenMax 在此基础上引入了生成对抗网络来生成伪未知样本让模型在训练阶段就见过一些“类似未知”的样本从而学得更好的决策边界。判别式方法直接训练一个能够区分已知类和未知类的判别器。这类方法通常会在损失函数上做文章比如 ARPLAdversarial Reciprocal Points Learning通过引入“互反点”reciprocal points的概念让已知类特征围绕互反点分布而未知类样本则远离这些互反点。CROSR 则是利用重构误差作为辅助信号结合分类器的中间层特征来判断未知类。重构式方法假设已知类数据可以被模型很好地重构而未知类数据的重构误差会显著更大。基于自编码器的方法通常属于这一类。这类方法的优势是直观但缺点是重构误差并不总是能可靠地区分已知和未知——有时候未知类样本也可能被重构得很好。方法类型代表方法核心机制优势局限生成式OpenMax, G-OpenMax极值理论建模 logit 分布不需要修改网络结构对 logit 分布假设敏感判别式ARPL, CROSR学习区分性特征空间端到端训练效果好需要精心设计损失函数重构式基于自编码器的方法重构误差作为判别信号直观易理解重构误差区分度不稳定选哪条路线取决于你的具体场景。如果你已经有一个训练好的分类器不想重新训练OpenMax 这种后处理方法是最省事的。如果你可以从头训练模型并且有足够的计算资源ARPL 这类端到端方法通常能取得更好的效果。重构式方法更适合数据维度不高、重构任务本身比较有意义的场景。2.3 评估指标为什么不能只看准确率开放集识别的评估比闭集分类复杂得多因为你需要同时衡量已知类分类和未知类拒绝两个维度。常用的指标包括AUROC以真阳性率已知类正确分类为纵轴假阳性率未知类被误分为已知类为横轴曲线下面积越大越好。这个指标不依赖于具体的阈值选择适合比较不同方法的整体性能。F1-Score在某个阈值下综合精确率和召回率。但开放集识别中“正类”的定义需要明确——通常是把已知类作为正类未知类作为负类。Open Set Classification Rate (OSCR)绘制正确分类率随拒绝阈值变化的曲线曲线下的面积反映了模型在已知类和未知类之间的权衡能力。闭集准确率在已知类样本上的分类准确率用来确保模型没有因为过度关注未知类拒绝而牺牲已知类性能。注意很多论文只报告 AUROC但 AUROC 高不代表实际部署效果好。因为实际应用中你需要选一个具体的阈值而这个阈值的选择会直接影响已知类和未知类的误判率。建议在评估时同时看 AUROC 和某个固定阈值下的 F1 或 OSCR。3. 核心方法拆解与实操要点3.1 OpenMax 的实现细节与参数选择OpenMax 是我最早动手实现的开集识别方法之一它的思路很巧妙不改变分类器的训练过程只在推理阶段对 softmax 的输入做修正。具体步骤是这样的首先用训练集在分类器倒数第二层通常是全连接层之前提取每个已知类的特征向量计算每个类的均值向量MAVMean Activation Vector。然后对每个类计算训练样本的 logit 向量与 MAV 的距离取距离最大的那一部分比如前 20%来拟合一个威布尔分布Weibull distribution。威布尔分布是极值理论中常用的分布用来建模尾部行为。推理时对于测试样本的 logit 向量计算它与每个已知类 MAV 的距离然后用对应的威布尔分布计算一个“属于该类的概率”。这个概率会被用来修正原始的 softmax 输出距离越远修正后的概率越低从而给“未知”类留出空间。实操中有几个关键参数需要调尾部分位数tail size用来拟合威布尔分布的样本比例。太小会导致分布拟合不稳定太大会让模型对未知类不够敏感。经验值在 20% 到 30% 之间具体取决于每个类的样本数量。未知类阈值修正后的概率中“未知”类的概率超过多少时判定为未知。这个阈值需要在验证集上调通常用已知类和伪未知类比如从其他数据集采样的样本来模拟开放集场景。距离度量欧氏距离是最常用的但在高维空间中余弦距离有时更稳定。import numpy as np from scipy.stats import weibull_min class OpenMax: def __init__(self, tail_size0.2, alpha1.0): self.tail_size tail_size self.alpha alpha self.means {} self.weibull_params {} def fit(self, features, labels): # features: 倒数第二层特征, labels: 类别标签 for cls in np.unique(labels): cls_features features[labels cls] mean_vec np.mean(cls_features, axis0) self.means[cls] mean_vec # 计算距离并取尾部 distances np.linalg.norm(cls_features - mean_vec, axis1) tail_count max(1, int(len(distances) * self.tail_size)) tail_distances np.sort(distances)[-tail_count:] # 拟合威布尔分布 params weibull_min.fit(tail_distances, floc0) self.weibull_params[cls] params def predict_proba(self, logits, features): # 修正 logits adjusted_logits logits.copy() for cls in self.means: dist np.linalg.norm(features - self.means[cls]) params self.weibull_params[cls] # 威布尔分布的生存函数值作为修正权重 w weibull_min.sf(dist, *params) adjusted_logits[cls] * w # 计算未知类概率 unknown_score np.sum(logits - adjusted_logits) adjusted_logits np.append(adjusted_logits, unknown_score) # softmax exp_logits np.exp(adjusted_logits - np.max(adjusted_logits)) return exp_logits / np.sum(exp_logits)这段代码是一个简化版的 OpenMax 实现实际使用时还需要考虑批量处理、数值稳定性等问题。但核心逻辑就是上面这些用威布尔分布建模每个已知类的 logit 尾部然后在推理时根据测试样本与已知类分布的距离来修正概率。3.2 ARPL 的损失函数设计与训练技巧ARPLAdversarial Reciprocal Points Learning是近年来比较有代表性的端到端开集识别方法。它的核心思想是为每个已知类引入一个“互反点”reciprocal point这个点代表的是“不属于该类的区域”。训练时让已知类样本靠近自己的类中心同时远离其他类的互反点对于未知类样本训练时用伪未知样本模拟则让它们靠近互反点。损失函数由两部分组成分类损失和互反点损失。分类损失就是标准的交叉熵确保已知类样本被正确分类。互反点损失则是一个对抗性的项它鼓励已知类样本与互反点保持距离同时鼓励未知类样本靠近互反点。训练时的关键技巧伪未知样本的生成ARPL 通常用两种方式生成伪未知样本一是从其他数据集采样外部伪未知二是在特征空间中对已知类样本做扰动内部伪未知。外部伪未知的效果通常更好但需要额外的数据。互反点的初始化互反点通常初始化为对应类中心的负方向或者随机初始化后让模型自己学。实践中发现用类中心的负方向初始化收敛更快。温度系数在计算互反点损失时通常会引入一个温度系数来控制分布的平滑程度。温度太高会导致区分度不够太低会导致训练不稳定。经验值在 0.1 到 1.0 之间。import torch import torch.nn as nn import torch.nn.functional as F class ARPLoss(nn.Module): def __init__(self, num_classes, feat_dim, temperature0.5): super().__init__() self.num_classes num_classes self.temperature temperature # 互反点参数 self.reciprocal_points nn.Parameter( torch.randn(num_classes, feat_dim) * 0.01 ) def forward(self, features, labels, is_unknownNone): # 归一化特征和互反点 features F.normalize(features, dim1) rp F.normalize(self.reciprocal_points, dim1) # 计算特征与互反点的距离 dist torch.matmul(features, rp.t()) # [batch, num_classes] if is_unknown is None: # 已知类样本最大化与对应互反点的距离 # 即最小化与互反点的相似度 loss F.cross_entropy(dist / self.temperature, labels) else: # 已知类样本和未知类样本分别处理 known_mask ~is_unknown unknown_mask is_unknown loss_known F.cross_entropy( dist[known_mask] / self.temperature, labels[known_mask] ) # 未知类样本最小化与所有互反点的距离 loss_unknown -torch.log( torch.sigmoid(dist[unknown_mask].max(dim1)[0]) ).mean() loss loss_known loss_unknown return loss这个损失函数的设计逻辑是对于已知类样本模型需要学会把它们映射到远离互反点的区域对于未知类样本模型需要学会把它们映射到靠近互反点的区域。这样在推理时如果一个样本的特征靠近某个互反点就说明它很可能是未知类。3.3 特征空间可视化与决策边界分析不管用什么方法最终都需要理解模型在特征空间上是怎么划分已知类和未知类的。t-SNE 或 UMAP 可视化是最直观的工具。把测试集的已知类和未知类样本一起投影到二维空间用不同颜色标注就能看到模型的决策边界是否合理。一个常见的现象是如果未知类样本在特征空间上跟某个已知类混在一起那模型很难把它们分开。这时候需要考虑几个方向一是增加特征维度让已知类和未知类有更多的区分空间二是引入更强的特征解耦损失比如中心损失、对比损失等让已知类特征更紧凑三是用多个特征层的信息融合而不是只用最后一层。实操心得在做特征可视化时建议同时画出已知类和未知类的分布并且用不同的标记区分“正确拒绝的未知类”和“误分为已知类的未知类”。这样能更清楚地看到模型在哪些区域容易出错。4. 完整实操流程从数据准备到模型评估4.1 数据集划分与开放集场景构造开放集识别的数据集划分跟闭集分类很不一样。你需要把类别分成“已知类”和“未知类”两部分。常见的做法是从总类别中选一部分作为已知类比如 60%剩下的作为未知类。训练时只用已知类数据测试时已知类和未知类数据都出现。但这里有一个细节未知类数据在训练时是完全不可见的所以你不能用未知类数据来调参。为了解决这个问题通常会从已知类中再留出一部分作为“验证未知类”用来模拟开放集场景。比如总共有 10 个类选 6 个作为已知类2 个作为验证未知类2 个作为测试未知类。训练时用 6 个已知类的训练集验证时用 6 个已知类的验证集加上 2 个验证未知类的数据测试时用 6 个已知类的测试集加上 2 个测试未知类的数据。这种划分方式的好处是你可以在验证阶段就调整好阈值和超参数而不需要等到测试阶段。但缺点是验证未知类和测试未知类的分布可能不同导致调出来的参数在测试集上不一定最优。数据集划分已知类验证未知类测试未知类训练集全部无无验证集部分部分无测试集剩余无全部另一个需要注意的点是类别不平衡。已知类和未知类的样本数量可能差异很大这会影响阈值的选择。如果未知类样本很少模型可能会倾向于把所有样本都判为已知类。这时候可以考虑对未知类样本做过采样或者在损失函数中给未知类更高的权重。4.2 模型训练与超参数调优训练开放集识别模型时有几个超参数对最终效果影响很大学习率开放集识别通常比闭集分类更难训练因为模型需要同时学习分类和拒绝。学习率太大会导致训练不稳定太小会导致收敛太慢。建议用余弦退火或者阶梯式衰减初始学习率在 0.001 到 0.01 之间。批大小批大小会影响伪未知样本的生成质量。批太小伪未知样本的多样性不够批太大训练速度慢且可能过拟合。经验值在 64 到 256 之间。伪未知样本比例在 ARPL 这类方法中伪未知样本的比例会影响模型的拒绝能力。比例太高模型可能过度拒绝已知类比例太低模型对未知类不够敏感。通常设置在 0.1 到 0.3 之间。正则化系数权重衰减和 dropout 对防止过拟合很重要。开放集识别中模型容易在已知类上过拟合导致对未知类的泛化能力下降。建议权重衰减在 1e-4 到 1e-3 之间dropout 在 0.3 到 0.5 之间。训练过程中需要监控的指标包括训练损失、验证集上的闭集准确率、验证集上的 AUROC。如果闭集准确率很高但 AUROC 很低说明模型过度拟合了已知类需要增加正则化或调整伪未知样本的比例。如果两者都很低说明模型欠拟合需要增加模型容量或训练时间。4.3 阈值选择与推理部署训练完模型后最后一步是选一个合适的阈值来区分已知类和未知类。这个阈值的选择直接决定了实际部署时的误判率。常用的阈值选择方法有两种一是基于验证集上的 AUROC 曲线选使 F1 最大的阈值二是基于已知类样本的得分分布选一个分位数比如 95%作为阈值。第一种方法更直接但需要验证集上有足够的未知类样本。第二种方法不需要未知类样本但可能不够准确。推理部署时还需要考虑计算开销。OpenMax 这类后处理方法只需要在推理时多做一次距离计算和威布尔分布评估开销很小。ARPL 这类端到端方法需要模型输出特征向量然后计算与互反点的距离开销也不大。但如果你的模型很大推理延迟可能会成为瓶颈这时候可以考虑模型蒸馏或量化。注意在实际部署中建议保留一个“人工审核”的环节。对于模型判定为未知类的样本不要直接丢弃而是交给人工审核。这样既能保证系统的安全性又能收集新的未知类样本为后续的模型迭代提供数据。5. 常见问题与排查技巧实录5.1 已知类准确率下降怎么办这是开放集识别中最常见的问题为了让模型能拒绝未知类结果已知类的分类准确率也下降了。原因通常是模型在训练时过度关注了“拒绝”这个目标导致特征空间被压缩得太紧已知类之间的区分度下降。解决方法有几个方向。一是调整损失函数的权重降低未知类拒绝损失的权重让模型更关注分类。二是增加已知类样本的多样性比如用数据增强或者 Mixup 来扩充训练集。三是用两阶段训练先训练一个闭集分类器然后冻结特征提取层只训练拒绝模块。这样能保证已知类的分类性能不受影响。5.2 未知类拒绝率低怎么调如果模型对未知类的拒绝率很低说明决策边界太松了。可能的原因包括伪未知样本的生成不够多样导致模型没有见过足够多的“类似未知”的样本或者阈值设得太高导致很多未知类样本被误判为已知类。排查步骤先看验证集上的 AUROC如果 AUROC 本身就很低说明模型的特征空间没有很好地区分已知和未知需要改进模型结构或损失函数。如果 AUROC 还行但固定阈值下的拒绝率低说明阈值选得不对需要重新调阈值。如果阈值调了还是不行可能是验证未知类和测试未知类的分布差异太大需要考虑用更鲁棒的阈值选择方法。5.3 训练不收敛或震荡严重开放集识别的训练比闭集分类更容易出现不收敛或震荡的情况因为损失函数中包含了对抗性的项。常见的原因和解决方法问题现象可能原因解决方法损失震荡严重学习率太大降低学习率加 warmup损失不下降互反点初始化不好用类中心负方向初始化验证 AUROC 波动大伪未知样本比例不稳定固定伪未知样本的生成种子训练后期过拟合正则化不足增加权重衰减和 dropout实操心得我在训练 ARPL 的时候遇到过一个坑——互反点的学习率如果跟主干网络一样会导致互反点更新太快训练不稳定。后来把互反点的学习率设为主干网络的 0.1 倍训练就稳定多了。这个细节在论文里通常不会写但实际做的时候很关键。5.4 跨数据集泛化能力差在一个数据集上训练的开放集识别模型换到另一个数据集上效果可能会大幅下降。这是因为不同数据集的已知类分布和未知类分布差异很大模型学到的决策边界不一定能迁移。提升跨数据集泛化能力的方法包括用多个数据集联合训练让模型见过更多样的已知类分布用领域自适应技术对齐不同数据集的特征分布或者在推理时用测试集的无标签数据做自适应调整决策边界。但这些方法都会增加实现复杂度需要根据实际需求权衡。6. 开放集识别的落地场景与影响范围开放集识别的应用场景其实比很多人想象的更广。除了前面提到的人脸识别、医学影像诊断、垃圾邮件过滤还有很多其他领域也在用。在工业质检中产品缺陷的种类往往无法穷举训练时只能覆盖已知的几种缺陷类型。开放集识别可以帮助系统在遇到新的缺陷类型时发出警报而不是强行分到已知类别里。在自动驾驶中路面上的物体种类几乎是无限的开放集识别可以帮助系统识别出训练时没见过的障碍物从而做出更安全的决策。在金融风控中欺诈手段不断翻新开放集识别可以帮助系统发现新的欺诈模式。从影响范围来看开放集识别正在从学术研究走向工程落地。越来越多的公司在做模型部署时会考虑开放集场景而不是假设测试集和训练集同分布。这个趋势背后是大家对模型可靠性和安全性的重视程度在提升。不过也要客观地说开放集识别目前还没有到“开箱即用”的程度。大部分方法在标准数据集上能取得不错的效果但换到实际场景中由于数据分布更复杂、未知类定义更模糊效果往往会打折扣。所以如果你打算在实际项目中用开放集识别建议先在小规模数据上验证可行性再逐步扩大规模。我个人在实际操作中的体会是开放集识别的效果很大程度上取决于特征空间的质量。如果特征提取器本身就能学到很好的区分性特征那后面的拒绝模块会容易很多。所以与其在拒绝策略上花太多时间不如先把特征提取器训好。另外阈值的选择不要追求在验证集上的最优而是要留一定的安全边际因为实际部署时的数据分布跟验证集总会有差异。