測試資料永遠和訓練資料不太一樣
三個貼近現實的小故事。一個腫瘤分類器用某家醫院的 MRI 影像訓練,達到放射科醫師等級的準確率,接著被安裝到城市另一頭、掃描儀設定略有不同的醫院,準確率就悄悄崩潰。一個行人偵測器用加州陽光下拍攝的數百萬幀畫面訓練,裝進車裡,第一場暴風雪就把原本自信的偵測變成危險的漏判。一個商品辨識模型用網路上清晰的型錄照片訓練,真實使用者卻在昏暗的店裡用手機對著貨架拍,這個漂亮的模型就被模糊、反光與奇怪的角度絆倒。每一個案例裡,模型本身都沒壞——只是世界遞給它的輸入,和它學過的不一樣。
這個現象的名字叫做分布偏移,而它最樸素的定義正是上面故事所展示的:模型在部署時遇到的資料,來自一個和你訓練時不同的機率分布。訓練教會模型某一塊世界的統計特性——這些相機、這種光線、這群病人、這個時代——然後你卻要它在一塊略有(或大不)相同的世界上工作。模型無從得知規則變了;它只會繼續套用學過的東西。
把它和上一篇對照會更清楚。在那裡,偏移是對抗式的:攻擊者刻意製作一個最壞情況的擾動,一個專為騙過模型而設計的微小推力。分布偏移的本質不同——它是自然且非刻意的。沒有壞人把雪「加」到路上,也沒有人偷換醫院的掃描儀。世界只是自顧自地移動,差距就自己拉開了。但別被「非刻意」哄睡:在雪中失效的自駕車,或在新診所漏掉腫瘤的癌症篩檢,其致命程度和一場攻擊完全相同。成因是良性的;後果卻不是。
機器學習工作流程的循環圖:蒐集資料、訓練模型、評估、部署、再蒐集更多資料——一個全程假設資料分布穩定的循環。
為偏移命名:協變量、標籤與概念偏移
「資料變了」這句話太模糊,無法據以行動。善於處理偏移的工程師會先精確指出「到底哪一部分」變了,因為——我們稍後會看到——名字會告訴你解法。經典的偏移有三種,要精確地討論它們,我們需要一小段機率記號,下面會仔細拆解。這裡不會超出你已經見過的概念:一個機率分布,不過就是在描述每種可能性發生的機會有多大。
分布偏移意指聯合分布在訓練與測試之間不同;把它分解成 P(y|x)·P(x),就能精確指出是哪一塊在移動。
我們慢慢讀。這裡 x 是輸入影像,y 是我們想要的標籤(比如「腫瘤」或「健康」)。P(x,y) 是聯合分布——看到某張特定影像「連同」它特定標籤的機率。左邊那條式子單純陳述分布偏移:訓練的聯合世界不等於測試的聯合世界。右邊那條是恆成立的代數拆分:任何聯合 P(x,y) 都能寫成 P(x)——輸入本身如何分布(各種影像有多常見——晴天對雪天、這台掃描儀對那台)——乘上 P(y|x)——標註規則(給定這張確切的影像,每個標籤的機率有多大)。把世界拆成「哪些輸入會出現」和「輸入如何對應到標籤」,就是整個訣竅,因為偏移可以擊中其中任一個因子。
協變量偏移(covariate shift)是指 P(x) 改變、而 P(y|x) 維持不變。輸入看起來不同了——新相機、新光線、路上的雪——但把影像對應到標籤的底層規則沒變:雪中的行人依然是行人;新掃描儀上的腫瘤依然是腫瘤,只要模型能看穿新的紋理。這是最常見的視覺偏移。型錄照片對手機照片的故事就是純粹的協變量偏移:「椅子長什麼樣」並沒有變,變的只是拍攝方式。
標籤偏移(label shift,又稱先驗偏移 prior shift)是指各類別的出現頻率 P(y) 改變了——標籤的邊際分布移動了——而每個類別的外觀 P(x|y) 維持不變。一種在你訓練族群中常見的疾病,在新族群裡變得罕見;一個曾佔 5% 零件的瑕疵,在製程改良後變成 0.5%。每顆腫瘤看起來依然像腫瘤,但數量少得多,而一個內化了「腫瘤就這麼常見」的模型,現在會過度預測它們。這就是為什麼用舊基準率調校的詐欺或疾病模型,在那些基準率漂移時會失準。
概念偏移(concept shift)最為微妙,也可說最為棘手:P(y|x) 本身改變了——標籤的意義本身漂移了,即使對完全相同的輸入也是如此。五年前明顯沒問題的同一封郵件,今天卻是「垃圾郵件」;同一張影像,隨著規範與政策演變,在「不宜(NSFW)」那條移動的界線上時內時外。這裡影像沒變,它的頻率也沒變——是人類的標註規則變了。再怎麼對舊資料重新加權都救不了這個;你需要反映新定義的全新標籤。
偵測未知:分布外偵測
在修復偏移之前,我們需要一個更謙卑的技能:模型應該知道自己什麼時候力有未逮。想像一個貓品種分類器——只用貓的照片訓練過——突然被餵了一張胸部 X 光片。正確的行為是說「我完全不知道這是什麼,轉交給人類處理」。危險的行為則是脫口而出「虎斑貓,99% 確定」,因為它只認得貓的標籤,會把這個陌生輸入硬塞進最接近的那一個。一個部署中的模型,若對它從未學過的輸入也自信地亂猜,就是一場等著發生的事故。
分布外偵測(out-of-distribution detection,OOD 偵測)這個任務,就是把落在訓練分布之外的輸入標記出來,好讓系統能棄答、求援、或把案件轉給人類,而不是吐出一個毫無意義卻很自信的答案。可以把它想成模型的煙霧警報器:它不分類火災,只是大喊「這不正常」。每一條穩健的流程都需要一個。
最簡單的基線會重用模型本來就會產出的東西:它自己的信心。取 softmax 輸出,用「最可能類別的機率」當作「分布內」的分數,然後把低於某個門檻的全部標記出來。
最大 softmax 機率(MSP)基線:以信心作為分布內分數,搭配一個切點 τ。
拆開來看:f(x) 是網路對輸入 x、為每個類別輸出的原始分數(logits)向量。softmax 函數把這些 logits 壓成一個跨類別的機率分布——一組非負、加總為一的數字。下標 k 在類別間索引,對 k 取 max 則挑出最大的那個類別機率:模型對它「首選猜測」的信心。我們把它叫做 s(x),分布內分數,並在 s(x) 掉到所選切點 τ(tau)以下時宣告輸入為 OOD。具體來說:若一張貓照片得到 max-softmax 0.96、一張 X 光片得到 0.55,那麼門檻 τ = 0.8 就乾淨地把 X 光片標為 OOD、讓貓通過。τ 要在驗證資料上調校,在「抓到怪輸入」與「無謂地拒絕正常輸入」之間取捨。
現在來看陷阱——而且是個大陷阱,直接來自第二篇。這個檢驗只有在「信心名副其實」時才有效。但現代神經網路系統性地過度自信:它們校準不良,動輒對錯誤答案給出 99%,而這種過度自信不會因為遇到陌生輸入就關掉。一張 X 光片、純雜訊、或一張對抗影像,都可能帶著爆表的 max-softmax 一路放行。所以 MSP 基線抓得到容易的案例,卻偏偏漏掉最可怕的那些。它是「值得第一個嘗試」的對的東西,也是「不該單獨信賴」的錯的東西——它的品質被模型的校準好壞給封頂。
更強的 OOD 分數是存在的;今天你不需要它們的數學,只需要三個家族的直覺。以能量為基礎(energy-based)的分數,讀的是整個 logit 向量(透過 log-sum-exp),而非只看它的峰值,給出一個比較平滑、較不受過度自信扭曲的「內/外」訊號。到訓練特徵的距離(distance-to-training-features)這類方法——馬氏距離(Mahalanobis)是經典——略過分類頭,改在網路的內部特徵空間裡問:「這個輸入離訓練樣本的雲團有多遠?」;真正新奇的輸入會落得很遠,即使 softmax 被騙了也一樣。離群暴露(outlier exposure)則走另一條路:訓練時你額外給模型看一堆已知的怪影像,明確教它對這些要不自信,讓「我不知道」變成一個學到的反應,而非一場意外。
為何在偏移下校準更加重要
回想第二篇談的校準:一個模型是「校準良好」的,當它的信心吻合它的準確率——在它所有說「八成把握」的時候,它約有八成是對的。校準是讓「機率」成為一個可據以行動的可信數字、而不只是一個排序的東西。現在我們把它和偏移撞在一起,而壞消息恰好壞在最糟的點上。
這是核心且有充分文獻支持的事實:一個在乾淨、分布內測試資料上「校準尚可」的模型,在分布偏移下會變得校準嚴重失準——而且偏移愈大,失準愈嚴重。當輸入從訓練世界漂走,準確率下降(這在意料之中),但信心下降得慢得多、甚至完全不降。於是模型在它開始出錯的同時,依舊大聲而篤定。信心恰恰在你最需要倚賴它的那一刻——世界已改變、而你沒有標籤可核對的那一刻——停止追隨準確率。
這單一的失效同時毒害兩件事。第一,前一節的 OOD 偵測:每一個以信心為基礎的分數(MSP 基線及其親戚)都假設信心是有意義的,而在偏移下它被灌水,於是「已偏移但其實仍相關」的輸入會溜過你的門檻、還錯誤地看起來像分布內。第二,任何下游的決策門檻:若你把信心高於 0.9 的案件自動核准、其餘轉給人類,那麼偏移下被灌水的信心意味著更多錯誤案件被自動核准、卻沒有任何人看過。你精心搭建的安全網,恰好在偏移到來時散架。
你能做什麼?三個務實而概念性的回應。(1) 溫度縮放(temperature scaling)——第二篇那個簡單的事後校準技巧——在它那個單一溫度是用「貼近部署」的驗證資料來擬合時效果最好;只要你能蒐集到哪怕一小份已偏移的驗證集,就在它上面校準,而不是在乾淨資料上。(2) 深度集成(deep ensembles):把數個獨立訓練的模型的預測平均起來;它們在偏移輸入上的分歧會變大,這既軟化了過度自信、又給出有用的不確定性訊號。(3) 在生產環境中把校準當作預警來監控:追蹤信心對上你能蒐集到的任何真值,而「平均信心」與「觀測準確率」之間逐漸拉大的落差,往往是漂移已開始最早可量測的徵兆——早在有人投訴之前就看得見。
縮小差距:領域適應
偵測與校準告訴你偏移正在發生;現在來談主動的修法。這個設定用到兩個詞。來源領域(source domain)是你擁有大量標註資料的地方——比如第一家醫院,有數千張由放射科醫師標註的 MRI。目標領域(target domain)則是你真正想讓模型工作的地方——掃描儀不同的第二家醫院——而關鍵在於,那裡你通常只有很少的標籤、甚至完全沒有(蒐集標籤又慢又貴)。目標是讓一個在來源訓練的模型,在目標上也能好好工作。
領域適應處理這件事的方式,是改變模型「注意什麼」。其洞見是:腫瘤真正的訊號在兩家醫院都一樣;不同的是附帶的東西——掃描儀的紋理、對比度、雜訊。所以模型應該學到「捕捉內容」(有沒有腫瘤?)卻「對領域不變」(這是哪台掃描儀拍的?)的特徵。如果內部表徵根本分辨不出兩家醫院,那麼建在它之上的分類器在兩邊就會表現一致——在來源學到的決策,乾淨地轉移到目標。
強制達成這種不變性的主流做法,是一場巧妙的遊戲,叫做對抗式領域適應(adversarial domain adaptation,DANN 架構是典範例子)。在你的特徵上加裝第二個小網路——一個領域分類器——給它一份工作:看一個特徵向量、猜它來自哪個領域,來源還是目標。接著用「相反的目標」訓練特徵抽取器:產生「讓領域分類器失敗」的特徵。如果訓練之後,領域分類器的表現不比擲硬幣好,那特徵就真的不帶領域資訊了——只有內容留存。那正是我們想要的不變性,靠競爭達成。
領域對抗目標:一場「學會任務」與「抹除領域」之間的拔河。
把它讀成兩位玩家在「共用同一份特徵」上的拔河。θ_f 是特徵抽取器的參數;θ_y 是任務頭(例如腫瘤對健康);θ_d 是領域分類器。L_task 是平常的任務損失——在有標籤的來源資料上、腫瘤預測錯得多離譜——而 L_domain 衡量領域分類器分辨來源與目標的本事有多好。特徵抽取器與任務頭(對 θ_f、θ_y 取 min)把 L_task 壓低,所以特徵對真正的工作仍然有用。但注意那個負號:同一個 min 想把 −λ·L_domain 這一項弄小,也就是把 L_domain 弄大——產生領域分類器破解不了的特徵。同時,對 θ_d 取 max 則磨利領域分類器、把 L_domain 推回去。兩者在共用特徵上往相反方向拉,而平衡點就是一個「對任務有用、卻對領域盲目」的表徵。旋鈕 λ(lambda)決定賭注:λ = 0 完全無視領域(就是普通訓練);大的 λ 要求強烈的領域不變性、即使要付出一些任務準確率的代價。你調它來在兩者間取捨。
這個 min–max 的形狀應該讓你感到熟悉:它和第三篇對抗訓練裡那個對抗式、兩玩家的結構一模一樣,那裡是一方製作攻擊、另一方學會抵抗。這個模式在機器學習裡反覆出現——把兩個目標彼此對立起來,讓平衡點產生一個你無法直接寫下的性質。在那裡,那個性質是「對擾動的穩健性」;在這裡,是「對領域的不變性」。實作上,你用一個梯度反轉層(gradient-reversal layer)來實現那個「減號」,這個小技巧在領域梯度回流進特徵抽取器的途中把它的符號翻轉,於是一次普通的反向傳播就同時訓練了兩位玩家。
# Domain-adversarial training (DANN), one step — schematic # Source batch has labels; target batch is unlabelled. feat_s = feature_extractor(x_source) # features, params theta_f feat_t = feature_extractor(x_target) # 1) Task loss: only the labelled SOURCE data (head = theta_y) loss_task = cross_entropy(task_head(feat_s), y_source) # 2) Domain loss: classify which domain each feature came from (theta_d) # label 0 = source, 1 = target — uses NO task labels feats = concat(feat_s, feat_t) d_label = concat(zeros(len(feat_s)), ones(len(feat_t))) loss_domain = cross_entropy(domain_head(grad_reverse(feats, lam)), d_label) # grad_reverse: identity forward, multiplies gradient by -lam backward. # So minimizing this total makes the domain head ACCURATE (its own params) # while pushing the FEATURES to fool it -> the min-max in one backward pass. total = loss_task + loss_domain total.backward() optimizer.step() # updates theta_f, theta_y, theta_d together
對抗式適應強大但笨重;較輕量的親戚常常就夠用,值得認識。微調(fine-tuning):只要你能標註哪怕幾百個目標樣本,就在它們上面繼續訓練來源模型——這是「有一些目標標籤」時最便宜的修法。統計對齊(statistics alignment):重新校準正規化層,例如 BatchNorm 重新校準,方法是在目標資料上重新計算移動平均與變異數——有時光是在新掃描儀上刷新這些統計量,就能補上大半的差距,完全不必重訓。以偽標籤自我訓練(self-training with pseudo-labels):在無標籤的目標影像上跑來源模型,把它最有信心的預測留作暫時的「偽標籤」,再用它們訓練、藉以踏進新領域——很強大,但它只和那份信心一樣可信,而(如前一節所言)偏移本身已讓那份信心動搖,所以要搭配高門檻、小心使用。
從頭到尾,醫院的案例現在讀起來就很清楚了。來源:A 醫院,數千張有標籤的 MRI。目標:B 醫院,掃描儀不同,目前沒有標籤。我們把腫瘤分類器和梯度反轉的領域遊戲,一起放在 A 的有標籤掃描加上 B 的無標籤掃描上訓練。特徵學會編碼「這是不是腫瘤」、同時丟棄「這是哪台掃描儀」——而那個從未見過一張有標籤 B 掃描的分類器,竟然能在 B 醫院工作,因為對它的特徵來說,B 現在看起來就跟 A 一樣。這就是領域適應發揮價值的時候。
一張特徵嵌入空間的示意圖,顯示樣本依語意內容分組、而非依它們來自哪個領域分組,說明領域不變的表徵。
打造能在真實世界存活的流程
該把一切組裝成工程實務了。一個在凍結測試集上拿滿分的模型,只證明了它在「過去」管用;一個能在部署中存活的系統,則是會「隨著世界移動而持續自我證明」的那種。差別就在一條穩健性流程——一組從第一天就內建、而非在第一次事故後才硬加上去的習慣與防護。以下是一份可用的檢查清單。
- 在「刻意偏移」的測試集上評估,而不只是隨機切出的留存集。劃出來自不同場域、時間、裝置與條件的資料(雪天的畫面、第二家醫院的掃描儀、上個月的資料),並在每一份上報告準確率與校準。單一個 i.i.d. 測試數字,恰好藏起了你真正在意的那些失效。
- 監控輸入統計量以偵測漂移。追蹤進來資料的摘要統計(亮度、解析度、類別頻率的估計、特徵空間距離),並在它們偏離訓練時的基線時告警——這能在你還不知道準確率之前,就抓到協變量偏移與標籤偏移。
- 把校準當作預警訊號來監控。只要有任何真值陸續進來,就把信心對上觀測準確率;逐漸拉大的落差,往往是「世界已偏移」最早可量測的徵兆(第四節)。
- 在流程中跑 OOD 偵測,把低信心或分布外的輸入轉給人類(或一個安全的預設)。別讓模型對它從未學過的東西亂猜(第三節)——棄答是一項功能,不是一次失敗。
- 閉合迴圈:排程重訓與適應。定期納入新的有標籤與無標籤資料、對新領域施行領域適應,並在每次重新部署前重跑整套「偏移評估」。漂移永不停歇,所以維護也不能停。
# A robust inference path: detect, calibrate, then decide.
def serve(x):
feats = feature_extractor(x)
logits = task_head(feats)
# 1) Out-of-distribution gate (Section 3)
if ood_score(feats, logits) > OOD_THRESHOLD:
log_drift_event(x)
return escalate_to_human(x) # don't guess on the unknown
# 2) Calibrate confidence before trusting it (Sections 2 & 4)
probs = temperature_scale(logits, T) # T fit on a SHIFTED val set
pred, conf = argmax(probs), max(probs)
# 3) Confidence-gated decision
if conf < DECISION_THRESHOLD:
return escalate_to_human(x)
return pred
# Runs continuously alongside serving:
# - monitor_input_stats() -> alert on covariate / label drift
# - monitor_calibration() -> alert when confidence stops tracking accuracy
# - on drift alert: collect target data, adapt (DANN / fine-tune), retrain退一步,看看第一到第四篇如何咬合成一個故事。第一篇給了你誠實的指標——準確率、精確率、召回率、ROC/AUC——讓你能說出模型到底管不管用。第二篇把它們延伸到偵測、分割、生成、以及校準,讓一個信心數字有了意義。第三篇用最壞情況、對抗輸入來壓力測試模型,讓你知道它在攻擊下如何崩壞。第四篇——也就是這一篇——處理了世界實際遞上來的「平均情況」:為分布偏移命名、用 OOD 偵測偵測未知、在偏移下守護信心、並用領域適應縮小差距。好指標告訴你模型管用;穩健性測試告訴你它何時會不管用;偏移處理則讓它在世界持續移動時持續管用。
然而——這就是通往最後一篇的轉折——一個模型可以準確、穩健、且懂得偏移,卻仍然辜負人們。它可以在平均上準確,卻對某些群體系統性地更差(不公平)。它可以是對的,卻沒人能理解為什麼(不透明)。它可以運作完美,卻被部署來欺騙(被濫用,如深偽)。技術上的正確是必要的,卻不充分。本軌的最後一篇接手那個更難的問題——偏見與公平、可解釋性、與深偽——這是「打造你真正能信賴的電腦視覺」的倫理總結。