
在真實的圖像分類落地場景中困擾我們的往往不是“已知類別識別得不夠準”而是模型在面對從未見過的輸入時依然會給出一個十分自信的錯誤答案。比如安防攝像頭采集到一種訓練階段完全沒有出現過的物品分類器沒有能力判斷“我不認識它”反而會把它強行歸到某個已知類別里。這個問題如果得不到控制直接上線就會帶來誤報、漏報和運營上的連鎖風險。本文圍繞 “VeriCam: A Verification Baseline for the Classification of Unknown Data” 展開。這里的關鍵詞并不單指某一套固定代碼而是代表一類任務如何在視覺分類系統中建立“未知數據分類驗證基線”并用量化指標評估模型對未知輸入的響應。適合正在做圖像分類、開放集識別、模型上線前評估的算法工程師和學生閱讀。讀完你會掌握一套最小可運行的驗證基線流程包括數據劃分、未知樣本構造、MSP 置信度基線、AUROC/FPRTPR95 指標計算以及容易踩坑的細節。1. 未知數據分類為什么需要驗證基線1.1 從閉集分類到開放世界分類傳統的圖像分類模型通常遵循閉集假設訓練集和測試集來自同一組類別模型只需要在有限類別里選擇概率最大的答案。這種假設在學術 benchmark 里非常常見但現實世界并不是一個封閉集合。攝像頭、傳感器、用戶上傳內容隨時可能帶來未見過的類別。我們可以把這些輸入統稱為 Unknown Data也就是模型訓練階段沒有見過的數據。此時模型面對的是開放世界既要能區分已知類別之間的差異也要能識別出“這個樣本不屬于任何已知類別”。“Unknown Data”并不是單一概念。它可以包括與已知類別完全不同的新類別與已知類別語義相近但分布不同的數據數據損壞、噪聲、對抗擾動帶來的異常輸入與任務完全無關的背景內容。如果模型不具備對未知輸入的判斷能力那么它輸出的置信度分數就沒有意義。所以我們需要一種“驗證基線”先把模型面對未知數據時的行為量化出來再決定是否需要改進算法。1.2 Verification Baseline 到底驗證什么Verification Baseline 可以理解為一套標準化的驗證流程。它回答的問題非常明確當模型遇到 Unknown Data 時它能不能保持“謹慎”具體來說驗證基線需要完成三件事。第一把已知類別和未知類別放在同一個評價框架里讓問題從“這張圖是哪個類”變成“這張圖是否來自已知類別空間”。第二為每個樣本計算一個可比較的置信度分數例如最大 Softmax 概率分數高代表模型認為樣本更接近已知類別。第三在固定的指標下匯報模型區分已知和未知的能力而不是只看分類準確率。用 VeriCam 這類名稱作為驗證基線本質上是為了強調視覺場景中的可復現評估。真實項目里算法團隊經常用不同的數據集、不同的預處理方式、不同的評價口徑各自驗證得到的結論無法橫向比較。有了統一基線后任何改進方法都可以先在同一個協議上跑一遍確認提升真實有效。1.3 與 OSR、OOD、異常檢測的關系這里有必要區分幾組經常混淆的概念因為不同場景里“未知數據”的定義差別很大。開放集識別也就是 Open Set Recognition簡稱 OSR關注的是模型能否在測試時正確拒絕“新類別”同時保留對已知類別的識別能力。分布外檢測也就是 Out-of-Distribution Detection簡稱 OOD Detection關注的是測試樣本是否來自訓練分布之外比如 CIFAR-10 模型遇到 SVHN 圖像。異常檢測也就是 Anomaly Detection通常更關注數據中的少量異常點例如工業質檢中的缺陷樣本。這三個方向在方法上經常互相借鑒但評價口徑并不完全一致。本文以視覺圖像分類為主線重點解決 OSR 和 OOD 方向上都需要的核心問題如何構造 Known/Unknown 數據并給出一個可靠的驗證基線。2. 基線實驗設計與評價指標2.1 問題形式化定義為了把問題講清楚我們可以做一個形式化定義。假設有已知類別集合K {c1, c2, ..., cM}模型使用 K 中的訓練數據完成分類因此模型見過所有已知類別。測試階段我們輸入兩類樣本已知類樣本標簽確實屬于 K未知類樣本標簽不屬于 K。驗證基線的目標不是判斷未知樣本具體屬于哪個新類別而是計算一個二分類判斷樣本是否為已知類別可以把來自已知類別的樣本視為正類把來自未知類別的樣本視為負類。這樣原本的“M 類分類問題”就被轉換成一個“驗證問題”。Verification Baseline 的名稱也因此而來我們驗證的其實是模型對已知類別空間的信任邊界。2.2 數據集應該怎么劃分建立驗證基線的第一步是準備數據。為了保證實驗可復現建議把數據劃分成四份數據集合數據來源是否參與訓練用途已知類訓練集已知類別中的訓練樣本是訓練分類器已知類驗證集已知類別中的驗證樣本否只用于選閾值確定判定邊界已知類測試集已知類別中的測試樣本否評估已知類識別能力未知類測試集未知類別樣本否評估對未知類的拒識能力一個常見的錯誤是只用訓練集上的表現來評估模型或者讓測試用的未知類別以任何形式出現在訓練階段。這樣得到的結果會偏樂觀無法反映真實線上場景。未知類數據在驗證階段應該保持完全不可見。另一個關鍵點是閾值不能直接在測試集上挑選否則會產生信息泄漏。更規范的做法是先用已知類驗證集確定一個能保證指定已知類召回率的閾值再把這個閾值固定下來去測試集上評估最終的混淆矩陣和指標。2.3 核心指標選擇分類任務里準確率并不能完整反映驗證基線的質量。因為如果已知類和未知類數量不平衡模型即使把所有樣本都判為“已知”也可能獲得很高的準確率。這里采用更合適的指標。AUROCArea Under the ROC Curve是最常用的指標之一。它的含義是隨機抽取一個已知類樣本和一個未知類樣本已知類樣本的分數高于未知類樣本的概率。AUROC 越高說明模型對已知和未知的區分能力越強。AUROC 接近 0.5 表示模型基本沒有區分能力接近 1 表示區分能力很強。FPRTPR95 是另一個工程中很重要的指標。TPR 表示已知類樣本被正確判定為已知類的比例FPR 表示未知類樣本被錯誤判定為已知類的比例。FPRTPR95 的含義是當模型能識別出 95% 的已知類樣本時有多少比例的未知類樣本會被誤判成已知類。這個指標直接對應業務中的“誤報率”在很多安全敏感場景中非常關鍵。除了上述指標還可以額外統計已知類準確率、未知類拒識率以及混淆矩陣方便從不同角度觀察結果。3. 環境準備與項目結構3.1 運行環境本文示例以 PyTorch 為基礎實現環境要求如下。版本需要根據你的項目實際情況調整這里重點演示配置和代碼思路。操作系統Windows / Linux / macOS 均可Python建議使用 3.8 或更高版本深度學習框架PyTorch建議使用較新的穩定版本數據集工具torchvision指標計算scikit-learn數值計算NumPy。在命令行中安裝核心依賴pip install torch torchvision sklearn numpy如果你的機器有 CUDA 顯卡可以安裝對應版本的 GPU PyTorch如果沒有CPU 版本也能完成整個演示只是訓練速度會慢一些。安裝時盡量統一依賴版本避免不同包之間的接口沖突。3.2 演示項目結構為了讓代碼模塊清晰建議按下面的目錄組織工程VeriCam-Demo/ ├── data_utils.py # 數據集劃分與 Known/Unknown 構造 ├── models.py # 小型視覺分類網絡 ├── metrics.py # 驗證基線評估指標 └── run_demo.py # 主流程訓練、閾值選擇、評估這份結構足夠做一次完整實驗。你在實際項目中可以繼續擴展出config.py管理超參數、inference.py對接線上推理但核心的驗證協議與這里的邏輯保持一致。4. 完整代碼實現4.1 數據劃分與 Unknown Data 構造我們需要從 CIFAR-10 數據集中切出已知類別與未知類別。例如選擇其中 5 個類作為已知類另外 5 個類作為未知類。這樣能保證數據集的加載非常簡單環境允許時會自動下載到本地。值得注意的是這種劃分方式會讓未知類與已知類來自同一個數據集語義上仍屬于同一分布因此驗證難度偏低。它適合用來打通實驗流程。在更嚴格的實驗中建議使用 CIFAR-100 或 Tiny ImageNet 等數據集裁剪出的圖像作為真正意義上的 Unknown Data。先把流程跑通再替換為更困難的數據是比較穩妥的思路。下面是data_utils.py的完整代碼# 文件路徑VeriCam-Demo/data_utils.py import torch from torch.utils.data import Subset, Dataset from torchvision import datasets, transforms class VerificationDataset(Dataset): 將原始 Subset 包裝成驗證數據集。 is_known1 表示該樣本屬于已知類別空間 is_known0 表示該樣本屬于未知類別空間。 def __init__(self, subset, is_known): self.subset subset self.is_known int(is_known) def __len__(self): return len(self.subset) def __getitem__(self, index): x, y self.subset[index] return x, y, self.is_known def filter_by_classes(dataset, allowed_classes): 只保留 dataset 中類別在 allowed_classes 里的樣本。 allowed_classes 是 list[int]。 allowed_set set(allowed_classes) indices [ i for i, (_, label) in enumerate(dataset) if int(label) in allowed_set ] return Subset(dataset, indices) def get_cifar10_datasets(data_root./data, known_classes(0, 1, 2, 3, 4)): 返回數據字典包含 - known_train: 訓練分類器使用的已知類訓練集 - known_val: 用來選擇閾值的已知類驗證集 - known_test: 已知類測試集 - unknown_test: 未知類測試集 train_transform transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding4), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)), ]) test_transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)), ]) # 下載并加載完整 CIFAR-10 full_train datasets.CIFAR10( rootdata_root, trainTrue, downloadTrue, transformtrain_transform ) full_test datasets.CIFAR10( rootdata_root, trainFalse, downloadTrue, transformtest_transform ) all_classes set(range(10)) known_set set(known_classes) unknown_classes sorted(all_classes - known_set) # 過濾出已知類訓練數據 known_train_full filter_by_classes(full_train, known_classes) # 再從已知類訓練數據中切出驗證集例如 5000 個樣本 n len(known_train_full) n_val 5000 indices list(range(n)) torch.manual_seed(0) val_indices set(torch.randperm(n)[:n_val].tolist()) train_indices [i for i in indices if i not in val_indices] known_train Subset(known_train_full, train_indices) known_val Subset(known_train_full, sorted(val_indices)) # 已知類測試集和未知類測試集 known_test filter_by_classes(full_test, known_classes) unknown_test filter_by_classes(full_test, unknown_classes) # 包裝成驗證數據集 return { known_train: VerificationDataset(known_train, is_known1), known_val: VerificationDataset(known_val, is_known1), known_test: VerificationDataset(known_test, is_known1), unknown_test: VerificationDataset(unknown_test, is_known0), }這段代碼需要注意三個地方。第一驗證集的索引來自已知類訓練數據內部不能和測試集有交集。第二VerificationDataset返回的第三個元素 1/0 用來告訴評估函數當前樣本是已知還是未知。第三隨機種子固定為 0方便復現在真實項目中可以把 seed 作為參數傳入方便做多次重復實驗。4.2 定義視覺分類網絡本文用一個小型 CNN 而不是大型預訓練網絡。原因是驗證基線討論的重點在協議和指標上使用小型網絡可以在普通 CPU 機器上完成訓練方便讀者快速復現。# 文件路徑VeriCam-Demo/models.py import torch.nn as nn class SmallCNN(nn.Module): 適用于 CIFAR-10 32x32 輸入的小型卷積網絡。 輸出維度為 num_classes不包含 Softmax。 def __init__(self, num_classes5): super(SmallCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(128, num_classes), ) def forward(self, x): return self.classifier(self.features(x))CIFAR-10 的圖像尺寸是 32x32。經過三次MaxPool2d(2)后特征圖會縮小到 4x4最后引入AdaptiveAvgPool2d((1, 1))把特征圖統一池化到 1x1保證網絡對輸入尺寸變化有更好的魯棒性。需要重點理解的是網絡最后一層輸出的是 logits不是概率。我們在后續計算最大 Softmax 概率時會先對 logits 做 Softmax再取最大值。不要把 Softmax 寫進模型里因為訓練時使用的交叉熵損失函數會自動結合 logits 和 Softmax單獨把 Softmax 放進模型反而會增加數值不穩定性。4.3 評估指標實現在驗證基線中我們要計算三個核心結果AUROC、FPRTPR95、混淆矩陣。下面把指標封裝成獨立模塊方便在訓練結束后直接調用。# 文件路徑VeriCam-Demo/metrics.py import numpy as np from sklearn.metrics import roc_auc_score, roc_curve def compute_metrics(known_scores, unknown_scores, threshold): 計算驗證指標。 參數 - known_scores: 已知類樣本的置信度分數 - unknown_scores: 未知類樣本的置信度分數 - threshold: 判定閾值分數 threshold 判定為已知類 返回 - dict 類型的指標結果 y_true np.concatenate([ np.ones(len(known_scores)), np.zeros(len(unknown_scores)), ]) y_score np.concatenate([known_scores, unknown_scores]) # 在固定閾值下計算混淆矩陣相關指標 pred (y_score threshold).astype(int) tp np.sum((pred 1) (y_true 1)) fn np.sum((pred 0) (y_true 1)) fp np.sum((pred 1) (y_true 0)) tn np.sum((pred 0) (y_true 0)) known_acc tp / max(tp fn, 1) unknown_acc tn / max(tn fp, 1) auc roc_auc_score(y_true, y_score) fprs, tprs, _ roc_curve(y_true, y_score) # 找到 TPR 0.95 時盡量低且首次滿足條件的 FPR valid_indices np.where(tprs 0.95)[0] fpr_at_tpr95 fprs[valid_indices[0]] if len(valid_indices) 0 else float(nan) return { auc: auc, fpr_at_tpr95: fpr_at_tpr95, known_acc: known_acc, unknown_acc: unknown_acc, confusion_matrix: { tp: int(tp), fn: int(fn), fp: int(fp), tn: int(tn), }, } def select_threshold_by_known_scores(known_scores, tpr_target0.95): 使用已知類驗證集的分數挑選閾值。 目標是讓已知類樣本保留 tpr_target 比例的召回率。 if len(known_scores) 0: raise ValueError(known_scores 不能為空) # 例如 tpr_target0.95則取已知樣本分數分布的 5% 分位點 # 約 95% 的已知樣本分數會大于等于該閾值 threshold np.percentile(known_scores, (1 - tpr_target) * 100) return float(threshold)這里的閾值選擇邏輯非常重要。我們是在“已知類驗證集”上選擇閾值而不是在測試集上選擇。測試集只負責最終評估這樣可以避免閾值過擬合。如果線上業務對誤報率更敏感可以把閾值定得更高如果希望盡量少漏掉已知類樣本可以調低閾值。驗證基線的意義恰恰就是提供一個可調節的評估框架而不是固定某一條規則。4.4 主流程訓練與驗證最后是主流程代碼。它會依次完成訓練、分數采集、閾值選擇和指標計算。# 文件路徑VeriCam-Demo/run_demo.py import numpy as np import torch import torch.nn as nn from torch.utils.data import DataLoader from data_utils import get_cifar10_datasets from models import SmallCNN from metrics import compute_metrics, select_threshold_by_known_scores def collect_confidence_scores(model, loader, device): 遍歷數據集返回最大 Softmax 概率分數和 is_known 標簽。 model.eval() scores_list [] known_list [] with torch.no_grad(): for x, _, is_known in loader: x x.to(device) logits model(x) probs torch.softmax(logits, dim1) max_probs probs.max(dim1).values scores_list.append(max_probs.cpu().numpy()) known_list.append(is_known.numpy()) scores np.concatenate(scores_list) is_known np.concatenate(known_list) return scores, is_known def main(): device torch.device(cuda if torch.cuda.is_available() else cpu) print(device:, device) # 使用 5 個類別作為已知類 known_classes [0, 1, 2, 3, 4] num_classes len(known_classes) datasets get_cifar10_datasets( data_root./data, known_classesknown_classes, ) train_loader DataLoader( datasets[known_train], batch_size128, shuffleTrue, num_workers2, drop_lastTrue ) val_loader DataLoader( datasets[known_val], batch_size256, shuffleFalse, num_workers2 ) known_test_loader DataLoader( datasets[known_test], batch_size256, shuffleFalse, num_workers2 ) unknown_test_loader DataLoader( datasets[unknown_test], batch_size256, shuffleFalse, num_workers2 ) model SmallCNN(num_classesnum_classes).to(device) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() epochs 10 for epoch in range(epochs): model.train() total_loss 0.0 correct 0 total 0 for x, y, _ in train_loader: x x.to(device) y y.to(device) logits model(x) loss criterion(logits, y) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * x.size(0) pred logits.argmax(dim1) correct (pred y).sum().item() total y.size(0) train_acc correct / max(total, 1) avg_loss total_loss / max(total, 1) print(fepoch{epoch 1}, loss{avg_loss:.4f}, train_acc{train_acc:.4f}) # 1. 在已知類驗證集上選擇閾值 val_scores, val_known collect_confidence_scores(model, val_loader, device) # val_known 應該全是 1閾值選擇只看已知類分數 threshold select_threshold_by_known_scores( val_scores, tpr_target0.95 ) print(selected threshold:, round(threshold, 4)) # 2. 在測試集上評估 known_scores, _ collect_confidence_scores(model, known_test_loader, device) unknown_scores, _ collect_confidence_scores(model, unknown_test_loader, device) results compute_metrics( known_scoresknown_scores, unknown_scoresunknown_scores, thresholdthreshold, ) print(AUROC:, round(results[auc], 4)) print(FPRTPR95:, round(results[fpr_at_tpr95], 4)) print(known_acc:, round(results[known_acc], 4)) print(unknown_acc:, round(results[unknown_acc], 4)) print(confusion_matrix:, results[confusion_matrix]) if __name__ __main__: main()這里有幾個工程細節需要解釋。第一模型訓練使用交叉熵損失輸出維度是 5。訓練過程中只使用 known_classes 對應的 5 類數據未知類數據完全沒有參與訓練。這保證了驗證基線考察的是模型面對全新類別的泛化能力而不是記憶能力。第二閾值的選擇來自驗證集。select_threshold_by_known_scores會找出一個閾值使得約 95% 的已知類驗證樣本能夠被正確保留。然后我們固定這個閾值再到 known_test 和 unknown_test 上評估。第三所有測試都使用torch.no_grad()包裹并讓模型進入 eval 模式。如果忘記調用model.eval()BatchNorm 層在推理時仍然會使用當前 batch 的均值和方差導致結果不穩定。5. 運行與結果分析5.1 運行命令在項目目錄下直接運行python run_demo.py第一次運行會自動從互聯網下載 CIFAR-10 數據集。下載完成后控制臺會輸出類似下面的內容device: cpu epoch1, loss1.5321, train_acc0.3892 epoch2, loss1.3147, train_acc0.5021 epoch3, loss1.1842, train_acc0.5621 ... epoch10, loss0.9313, train_acc0.6881 selected threshold: 0.4213 AUROC: 0.7721 FPRTPR95: 0.4215 known_acc: 0.6924 unknown_acc: 0.6203 confusion_matrix: {tp: 3421, fn: 1579, fp: 1898, tn: 3102}以上輸出只是格式示意。不同機器、不同隨機種子和不同訓練輪數一定會產生差異重點不是記住某個具體數字而是理解每一行結果的含義。5.2 怎樣解讀這些結果AUROC 如果明顯高于 0.5說明模型已經具備一定的已知/未知區分能力。AUROC 很高不代表系統可以直接上線因為線上場景的未知數據分布會更復雜。FPRTPR95 則告訴我們如果業務要求漏掉已知類的比例不超過 5%那么會有多少未知類樣本被誤判為已知。這個數字越低越好0 表示不存在誤報1 表示全部誤報。再看混淆矩陣。TP 是正確保留的已知類樣本FN 是漏掉的已知類樣本FP 是誤判為已知的未知類樣本TN 是正確拒識的未知類樣本。當我們要設定業務閾值時往往是在 FN 和 FP 之間做權衡。不能說“哪個數字越小越好”因為降低 FN 通常會導致 FP 上升反之亦然。5.3 結果可視化的一種思路如果想讓驗證結論更直觀可以增加一個簡單的可視化代碼片段。這里不要求必須畫圖但展示思路import matplotlib.pyplot as plt def plot_score_distribution(known_scores, unknown_scores, threshold): plt.hist(known_scores, bins50, alpha0.6, labelknown) plt.hist(unknown_scores, bins50, alpha0.6, labelunknown) plt.axvline(threshold, colorred, linestyle--, labelthreshold) plt.xlabel(confidence score) plt.ylabel(count) plt.legend() plt.show()把 known_scores 和 unknown_scores 的分布畫出來可以很直觀地看到兩個分布的重疊程度。重疊越少模型對已知和未知的區分越好重疊越大說明模型在很多未知樣本上也給出了高置信度這是非常危險的信號。6. 常見問題與排查思路6.1 高頻問題排查表問題現象常見原因解決思路訓練 loss 不下降學習率過大或模型結構有誤降低學習率檢查數據標簽是否錯位AUROC 接近 0.5Unknown Data 與 Known Data 太相似或模型訓練不充分換更難的數據集增加訓練輪數檢查特征提取能力FPRTPR95 接近 1模型對所有樣本都給出高置信度考慮引入溫度縮放、Energy Score 等更復雜的 OOD 方法驗證集上指標高、測試集指標低閾值在驗證集上過擬合擴大驗證集規模使用交叉驗證多次重復實驗同一份代碼兩次運行結果不同沒固定隨機種子或 GPU 計算存在非確定性固定 PyTorch 和 NumPy 種子多次運行取均值Unknown Data 混入訓練集后指標異常偏高數據泄漏排查數據劃分的類別交集確保 unknown 完全不參與訓練6.2 為什么 MSP 是最簡單也是最穩的基線Maximum Softmax Probability也就是最大 Softmax 概率是驗證任務里最常見的 Baseline。它的核心假設是模型對已知類樣本會給出更高的最大概率對未知類樣本會比較猶豫。這個方案實現簡單只需要在模型輸出后取 Softmax 最大值幾乎不需要修改網絡結構所以它非常適合作為 Verification Baseline。但它的缺點也很明顯深度神經網絡往往過度自信對分布外輸入也會給出較高的 Softmax 概率。正因為如此MSP 通常是一個“下限”參考。如果某個新方法連 MSP 都打不過那說明提升可能來自實驗設置問題而不是算法本身。6.3 一個非常容易忽略的坑同源數據劃分在本文示例中我們讓 CIFAR-10 的 5 個類作為已知類另外 5 個類作為未知類。這種做法雖然方便但會讓任務偏簡單。因為同一個數據集內部的圖像在風格、分辨率、背景上高度一致模型很可能僅僅因為紋理或者顏色分布差異就把未知類區分開。更貼近真實場景的做法是使用另一個數據集作為未知類。例如用 CIFAR-10 作為已知類分布用 CIFAR-100 或 SVHN 作為未知類分布。這樣才能驗證模型是否學到了“對已知類別的語義邊界”而不是數據集的某種表面特征。在正式實驗中建議至少準備多個不同難度的 Unknown Data 集合避免單點結論。7. 最佳實踐與工程建議7.1 實驗配置要可追溯驗證基線的核心價值之一是“可復現”。我建議在項目中保存一份固定配置記錄下列信息已知類列表和未知類列表訓練集、驗證集、測試集的大小模型結構、預訓練權重來源優化器、學習率、訓練輪數隨機種子每天運行使用的框架版本。一個可行的方式是把這些配置寫入 JSON 或 YAML 文件并將結果和配置一起存檔。這樣即使三個月后再回看實驗結果也能知道當前指標是在什么條件下產生的。7.2 盡量使用多次獨立重復實驗深度學習的訓練過程有隨機性單次實驗不足以證明方法優劣。工程上建議固定一個隨機種子集合比如[0, 1, 2, 42]分別跑完實驗后匯報 AUROC 的均值和標準差。穩定性和平均值同樣重要。如果某個方法在 seed0 時表現很好但換一個 seed 就大幅下降那么它對實際業務的價值要打折扣。7.3 先定評估協議再做模型改進很多團隊拿到 Unknown Data 分類需求后第一反應是換更好的 Backbone 或者更復雜的損失函數。但如果沒有先建立 Verification Baseline后續所有改進都缺乏參照。更好的順序是固定數據劃分固定評估指標跑最簡單的 MSP 基線記錄 baseline 結果再逐步引入更復雜的算法例如溫度縮放、ODIN、能量分數、OpenMax每次只改變一個變量比較結果。這套做法看起來不夠炫酷卻是保證技術演進方向正確的關鍵。尤其在安全敏感場景中謹慎的評估流程比技巧更重要。7.4 對未知樣本保持“可解釋的拒絕”真實工程系統里模型除了輸出分類結果還需要輸出一個“不確定”信號。當置信度分數低于閾值時系統應該走一條獨立的分支比如人工審核、二次校驗或者直接拒絕。這個閾值的確定不應該只依賴算法人員的經驗而要和產品、安全團隊共同協商。驗證基線提供的是模型能力量化結果業務側則要結合誤報成本和漏報成本選擇一個對業務最合理的閾值。8. 總結與下一步方向本文從驗證基線設計的角度拆解了 Unknown Data 分類問題。我們先明確了 Unknown Data 與 OOD、OSR 的關系然后設計了一套可復現的數據劃分和評估協議。在代碼實現部分基于 CIFAR-10 構建了已知類與未知類集合并以最大 Softmax 概率作為 Baseline計算了 AUROC、FPRTPR95 和混淆矩陣。這套流程的核心價值并不是把模型準確率提得有多高而是幫助你在上線前回答三個問題模型是否對未知數據保持謹慎現有決策閾值會造成多少誤報新的改進算法是否真的比 Baseline 更可靠如果你正在處理視覺分類相關的工程問題可以把這套代碼作為起點先把評估體系固化下來。后續可以繼續探索的方向包括使用不同難度的 Unknown Data 集合做壓力測試引入 ODIN 中的溫度縮放策略嘗試基于能量的分數函數在分類網絡中加入 OpenMax 層來對開放集進行建模。不同方法的適用場景差異很大但只要驗證基線足夠穩健所有方法都可以在同一個尺度下公平比較。如果本文對你有幫助可以收藏備用也歡迎在實踐中結合實際數據繼續驗證和調整這套流程。