Day 04 曾說過「不能省的三件事」:可重現、監控、評測。Day 08 提到「只能投資一項就選監控」。今天就講為什麼。
如果這 30 天你只讀一篇,建議這篇要讀。
軟體會壞掉,而且會大聲地壞掉:您會知道收到了 500 錯誤、服務逾時、告警大響。
模型不會。模型壞掉的時候,它依然回傳 200 OK、依然在 45 毫秒內回應、依然給出一個看起來很合理的數字。
它只是答錯了,只是開始答得不那麼準了。而且會持續答錯,直到某個業務單位的人覺得「最近怪怪的」。
這就是模型系統最難處理的問題之一:無聲失效(silent failure)。
— MLOps 這個職務存在的最核心理由。
📊 職缺訊號
在這次職缺資料中,「模型監控與可靠性」出現在 14.9% 的 MLOps 職缺裡,比例看起來不算高,但不要因此解讀成監控不重要。實際的 JD 很可能把這類責任寫在平台維運、模型生命週期、Observability、Reliability 或 Production ML 之下,不一定直接出現「模型監控」四個字。面試時如果能主動談到「模型上線後怎麼知道它正在失效」,通常也比只談部署工具更能顯示你理解 Production ML 真正困難的地方。

圖 14-1:從上游變更、漂移告警到重訓練上線的完整時間軸(模擬資料,用於呈現事件結構)。注意第 30–44 天那段:效能已經在掉,但告警還沒響。
把這條時間軸拆開:
| 時間 | 發生什麼 | 誰知道 |
|---|---|---|
| 第 0–29 天 | 模型正常,AUC 約 0.90 | — |
| 第 30 天 | 上游新增付款管道,某特徵的分布開始改變 | 沒有人 |
| 第 30–43 天 | AUC 從 0.90 一路掉到 0.79 | 沒有人 |
| 第 44 天 | PSI 超過 0.2,告警觸發 | MLOps 工程師 |
| 第 44–61 天 | 調查、準備資料、重訓、驗證 | 團隊 |
| 第 62 天 | 新模型金絲雀上線,AUC 回到 0.90 | 團隊 |
這 14 天裡,模型可能已經持續做出比較差的決策,但傳統系統監控看起來完全正常:
服務可用性:99.99% ✅
P95 延遲:45 ms ✅
HTTP 錯誤率:0.01% ✅
模型輸出:開始漂移 ❌
SRE 的服務儀表板可能一片綠,模型卻已經開始偏離原本的行為。
監控做得好不好,就看這段「沒有人知道模型正在變差」的時間,能縮短多少。

圖 14-2:監控的四層架構(示意架構)。多數團隊只做到第二層,而模型的失效發生在第三層。
關鍵洞察:
可以把監控粗略拆成四層:
第 4 層 業務結果 → 詐欺損失、轉換率、核貸違約率
第 3 層 模型 → 輸入漂移、預測漂移、模型品質
第 2 層 服務 → Latency、Error Rate、Throughput
第 1 層 基礎設施 → CPU、Memory、GPU、Disk、Network
第 1、2 層已經是成熟的 SRE/Observability 領域。
第 3 層是 MLOps 的專屬責任,而它需要的技術與前兩層完全不同——因為你要監控的不是「有沒有回應」,而是
「模型現在看到的世界,還跟訓練時一樣嗎?」
「模型現在產生的答案,還跟原本一樣嗎?」
第 3 層又分三種訊號,偵測難度遞增:
| 訊號 | 偵測方式 | 需要標籤嗎 | 延遲 |
|---|---|---|---|
| 輸入漂移 | 比對特徵分布(PSI、KS 檢定) | 否 | 即時 |
| 預測漂移 | 比對預測值分布 | 否 | 即時 |
| 效能衰退 | 比對預測與真實結果 | 是 | 依標籤取得時間 |
「需要標籤嗎」這一欄是重點。 效能衰退是最直接的訊號,但你往往要等很久才拿得到真實標籤(詐欺可能三個月後才確認、貸款違約要一年)。這就是Delayed Label(延遲標籤)問題,也是為什麼我們需要不需要標籤的漂移偵測作為早期預警。
漂移不代表模型一定壞掉。
資料分布改變,但模型效能可能完全沒受影響;反過來,模型效能也可能下降,但你挑來監控的特徵分布看起來沒什麼變化。
所以正確的關係應該是:
發現漂移
↓
值得調查
↓
確認影響
↓
才決定是否重訓
而不是:
發現漂移
↓
立刻重訓 ❌
PSI(Population Stability Index)是一個常見、容易解釋的分布差異指標。
它比較「參考期」與「目前資料」落在各區間的比例差異:
PSI = Σ (Actualᵢ − Expectedᵢ) × ln(Actualᵢ / Expectedᵢ)
實務上常見的經驗判讀方式是:
PSI < 0.1 → 分布大致穩定
0.1 ≤ PSI < 0.2 → 有變化,持續觀察
PSI ≥ 0.2 → 漂移較明顯,值得調查
但要特別注意:0.1 與 0.2 不是放諸四海皆準的統計顯著性標準。
不同資料量、特徵型態、產業風險與工具,適合的門檻都可能不同。實務上應該用歷史資料回測後,再決定自己的告警門檻。
以下是一個簡化版實作:
"""drift.py —— Population Stability Index(PSI)"""
import numpy as np
def psi(reference, current, bins=10):
"""計算 PSI。
常見經驗判讀:
< 0.1 分布大致穩定
0.1–0.2 有變化,持續觀察
>= 0.2 漂移較明顯,值得調查
實際門檻應依資料與業務情境校正。
"""
# 使用參考期分位數切桶。
# 對偏態資料通常比單純等寬切分更容易保持每桶有足夠樣本。
quantiles = np.linspace(0, 100, bins + 1)[1:-1]
inner_edges = np.unique(np.percentile(reference, quantiles))
edges = np.concatenate(([-np.inf], inner_edges, [np.inf]))
ref_pct = np.histogram(reference, edges)[0] / len(reference)
cur_pct = np.histogram(current, edges)[0] / len(current)
# 避免 log(0)
eps = 1e-4
ref_pct = np.clip(ref_pct, eps, None)
cur_pct = np.clip(cur_pct, eps, None)
return float(
np.sum((cur_pct - ref_pct) * np.log(cur_pct / ref_pct))
)

圖 14-3:資料漂移偵測(模擬資料,PSI 為實算值)。左圖比較單一特徵在參考期與目前資料的分布;右圖逐特徵計算 PSI,協助快速定位漂移來源。
右圖比「整體漂移分數」更重要,因為真正開始調查時,工程師第一個問題通常不是:
「資料到底有沒有漂?」而是:「哪一個欄位先出問題?」
例如:
age PSI = 0.03 ✅
income PSI = 0.06 ✅
transaction_count PSI = 0.08 ✅
payment_channel PSI = 0.31 🚨
一眼就知道應該先查 payment_channel,而不是把整條資料管線全部翻一遍。
圖 14-3:資料漂移的偵測(模擬資料,PSI 為實算值)。左圖是單一特徵的分布比對,右圖是逐特徵 PSI——先找出「哪一個特徵在漂」,才知道該查哪裡。因為調查時你需要知道從哪個特徵查起,而通常只有一兩個特徵是元凶。
接著回到 Day 13 的 FastAPI 推論服務。
傳統服務監控通常已經會記錄請求數與延遲:
"""在 Day 13 的 FastAPI 服務加上可觀測性。"""
from prometheus_client import Counter, Histogram, make_asgi_app
REQUESTS = Counter(
"model_requests_total",
"請求總數",
["status"]
)
LATENCY = Histogram(
"model_latency_seconds",
"推論延遲",
buckets=[.01, .025, .05, .1, .25, .5, 1, 2.5]
)
# 第 3 層的關鍵:
# 不只監控服務,也把模型的「輸出」留下來。
PREDICTION = Histogram(
"model_prediction_score",
"預測分數分布",
buckets=[0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1.0]
)
app.mount("/metrics", make_asgi_app())
@app.post("/predict")
def predict(req: Req):
with LATENCY.time():
score = float(
MODEL["m"].predict(
pd.DataFrame([req.model_dump()])
)[0]
)
PREDICTION.observe(score) # ← 第 3 層監控的起點
REQUESTS.labels(status="ok").inc()
return {"score": round(score, 4)}
這裡最重要的其實只有一行PREDICTION.observe(score),卻是發現模型無聲失效的第一道防線。
因為從這一刻開始,Prometheus 不只知道模型有沒有回應?模型回應多快,還開始知道:
模型最近到底都在回答什麼?
例如原本預測分數大多落在 0.1 左右,最近突然整體移到 0.5;即使還沒有真實標籤,這已經是一個值得調查的訊號。
最簡單的第一版告警,可以先看平均預測分數是否明顯偏離基準:
# 最近 1 小時平均預測分數
# 與過去 7 天平均值偏離超過 0.15 時觸發預警
abs(
(
rate(model_prediction_score_sum[1h])
/
rate(model_prediction_score_count[1h])
)
-
(
rate(model_prediction_score_sum[7d])
/
rate(model_prediction_score_count[7d])
)
) > 0.15
假設:
過去 7 天平均 0.12
最近 1 小時平均 0.34
────
偏離量 0.22
0.22 > 0.15
→ 🚨 Prediction Drift Warning
這裡的 0.15 是示範門檻,實務上應該用正常期間的歷史資料回測,再決定真正適合的值。
還有一個非常重要的限制:
平均值沒變,不代表分布沒變。 例如下面兩組預測:
A:0.1、0.2、0.8、0.9
B:0.5、0.5、0.5、0.5
兩組平均值都是0.5,但模型的行為顯然完全不同。
所以這條 PromQL 只適合當作第一版、低成本的預警。更完整的做法還會觀察 Histogram bucket 的比例、分位數,甚至將一段時間的預測樣本送到專門的 drift job,計算 PSI、KS、Wasserstein 或其他分布差異指標。
這也是為什麼我們選擇 Histogram 記錄 prediction score,而不只是留下單一平均值。
告警的第一原則:每個告警都必須是「可行動的」。
不可行動的告警會造成告警疲勞——當工程師習慣性忽略告警時,監控系統的價值歸零,而且是負的(因為你以為有監控)。
| 告警設計 | 好 | 壞 |
|---|---|---|
| 觸發條件 | 基於症狀(使用者受影響) | 基於原因(CPU 高但沒人受影響) |
| 頻率 | 一週最多幾次 | 每天十幾封 |
| 內容 | 附上「該做什麼」與相關儀表板連結 | 只有一串指標名稱 |
| 分級 | 分成「立刻處理」與「上班再看」 | 全部都是 P1 |
把 SRE 的 SLO 觀念移植到模型上:
SLI(指標):模型預測的線上準確率(以每日抽樣人工複核估算)
SLO(目標):30 天滾動窗口內 ≥ 0.85
Error Budget:允許 15% 的錯誤空間
用法:
- 錯誤預算還很充足 → 可以大膽發布新模型、做實驗
- 錯誤預算消耗 80% → 凍結非必要變更,優先修復
- 錯誤預算用完 → 停止所有新功能,全員投入可靠性
這套機制的價值在於把「要不要冒險」變成數據決策,而不是誰聲音大。
漂移偵測到之後呢? 這是最多人卡住的地方——很多團隊做了漂移偵測,告警響了卻不知道該做什麼。給一份決策順序:
NULL——這不是漂移,是 bug,要修的是管線不是模型。這一步排除不掉,後面全都是白工。
第 1 步最常被跳過,也最常出錯。 如果團隊因為漂移告警而緊急重新訓練,結果是用「上游壞掉的資料」訓練出一個更糟的模型——這正是 Day 11 說「自動化會放大問題」的具體案例。
💡 給小團隊的最小可行監控
沒有 Prometheus、沒有 Grafana、沒有時間?做這三件事就好,成本不到一天:
- 記錄每次預測的輸入與輸出(寫進資料庫或日誌,含時間戳)。
- 每天跑一次腳本,算主要特徵的 PSI 與預測值的平均與標準差。
- 超過門檻就寄一封信給自己。
這個土法煉鋼的版本,就能把開頭那 14 天縮短到 1 天。有監控與沒監控的差距,遠大於監控做得精緻與粗糙的差距。
靜態門檻的偽陽性/偽陰性風險:本篇提供的經驗法則(如 $\text{PSI} \ge 0.2$ 或均值偏離 $>0.15$)在週期性、季節性波動(如黑五購物節)強烈的業務中容易頻繁誤報,若未結合動態基線或時間序列檢定,易引發告警疲勞。
高維度特徵與多重共線性侷限:逐特徵計算 PSI 僅能捕捉「單一特徵邊際分佈」的變化,無法偵測特徵間「相互關聯性改變」(Covariate Shift / Joint Distribution Shift)帶來的隱性漂移。
本篇未深入探討概念漂移(Concept Drift)的偵測困境:當特徵輸入分佈完全未變,但環境底層規則改變(如突發政策法令、市場競爭者改價),在缺乏真實標籤的情況下,單靠 PSI 與輸出分佈仍可能漏報,也請使用時留意。
PREDICTION.observe(score) 一行就是無聲壞掉的第一道防線。主軸一的最後一天,回到那個最高頻的任務訊號(58.5%):平臺與基礎設施。但我要從主管最在意的角度切入——錢。GPU 該租還是該買的損益平衡點怎麼算、為什麼你的 GPU 利用率可能只有 12%,以及「買新卡之前,先把利用率從 12% 救到 60%,等於免費多了四張卡」。