這是什麼、解決什麼問題

「模型上線後會退化,所以要監控漂移」——這句話大家都會說。

然後就沒有然後了。量什麼?門檻多少?響了要做什麼?

這三題不回答,漂移監控就會變成一個沒人看的儀表板, 或是一個一直響、大家都學會忽略的告警。


什麼時候你會用到

  • 模型上線之後(不是之前)
  • 有人問「你怎麼知道模型還準」
  • 要設計重訓的觸發條件

步驟一:三種漂移,分清楚

種類 什麼變了 多久知道
輸入漂移 進來的資料分布變了 立刻
預測漂移 模型輸出的分布變了 立刻
概念漂移 輸入和正確答案的關係變了 要等標籤

⭐ 最重要的差別在最後一欄。

前兩種你今天就量得到。第三種——也就是「模型變不準了」—— 要等真實結果回來才知道,那可能是幾天(詐欺)到幾個月(信用違約)之後。

所以實務上:用前兩種當早期警訊,用第三種當真正的判定。

不要把「輸入分布變了」講成「模型不準了」——那是推論不是量測。


步驟二:輸入漂移怎麼量——PSI

最常用的是 PSI(Population Stability Index):

import numpy as np

def psi(expected, actual, bins=10):
    edges = np.percentile(expected, np.linspace(0, 100, bins + 1))
    edges[0], edges[-1] = -np.inf, np.inf
    e = np.histogram(expected, edges)[0] / len(expected)
    a = np.histogram(actual,   edges)[0] / len(actual)
    e, a = np.clip(e, 1e-6, None), np.clip(a, 1e-6, None)
    return float(np.sum((a - e) * np.log(a / e)))

業界慣用的判讀:

PSI 一般說法
< 0.1 沒什麼變化
0.1 – 0.25 中度變化,注意
> 0.25 明顯變化

⚠️ 但這三個數字不是物理常數。 它們是經驗法則,來自信用評分模型的實務, 不保證適用於你的資料、你的樣本數、你的特徵。


步驟三:⭐ 先量雜訊,再定門檻

這是本篇最重要的一節。

在你設任何門檻之前,先回答:「什麼都沒變的時候,這個指標會抖多少?」

做法:

  1. 拿一段你確定沒有變化的歷史資料
  2. 切成兩半(或用滑動視窗)
  3. 算 PSI
  4. 重複幾十次,看分布

得到的就是雜訊水準 σ。

然後門檻應該定在雜訊之上,例如 3σ, 而不是直接抄 0.25。

抄來的門檻只有兩種結局:太鬆(永遠不響,等於沒有) 或太緊(一直響,大家學會忽略,等於沒有)。

兩種結局一樣。

下一節是我真的量出來的數字——比我預期的嚴重。

同樣的邏輯適用在模型指標本身。我在 lab 上量過 同一份設定只改隨機種子,重跑數次的分數差異—— 那個差異就是「什麼都沒改」時的波動。 比它小的變化,不值得當成訊號。


⭐ 我實際量了一次——結果比我預期的嚴重

上面那段是原則,這裡是數字。

我拿 lab 的語料(當時是 4,346 篇文章),把同一份資料隨機切兩半算 PSI, 重複 200 次。兩半來自同一個分布,所以理論上「什麼都沒變」。

📌 可重現性說明:那份語料是清理流程早期的版本,現在 repo 裡的 artifacts/data_report.json 已經是 docs_out: 10953——你去 clone 會對不到 4,346。 2026-09-21 我用現在的語料、改用「每個非空行」當樣本單位、重寫一支腳本再跑一次, 下面每一張表的形狀和數量級都重現得出來(誤報率 86% vs 85%、 5%/10%/20% 漂移的 PSI 幾乎逐位相同)。 具體數字會隨語料變,結論不會。

每半的樣本數 PSI 平均 σ 最大 3σ 門檻
100 0.1874 0.0902 0.6574 0.4581
500 0.0378 0.0172 0.0891 0.0895
1,000 0.0174 0.0087 0.0554 0.0434
2,000 0.0092 0.0046 0.0332 0.0230

看第一列。

樣本數 100 的時候,什麼都沒變的 PSI 平均是 0.187—— 按教科書的判讀,那是「中度變化,注意」。而最大值 0.657, 是「明顯變化」門檻的 2.6 倍。

換成誤報率更清楚:

樣本數 超過 0.1 的比例 超過 0.25 的比例
100 85% 24%
500 0% 0%
1,000 0% 0%

樣本數 100 的時候,你有 85% 的機率收到「資料漂移了」的告警—— 而資料一點都沒變。

這就是為什麼「抄 0.1/0.25」會失敗。那兩個數字沒有錯, 但它們預設了一個你沒注意到的前提:樣本要夠大。

另一半:門檻對了,也不代表抓得到

反過來我注入真的漂移(把文章長度整體拉長 X%),看多大才超得過 3σ 門檻:

漂移幅度 PSI 抓得到嗎
5% 0.0210 ✗ 淹在雜訊裡
10% 0.0225 ✗
20% 0.0377 ✗(3σ 門檻是 0.0434)
50% 0.1064 ✓

n=1,000、3σ 的設定下,20% 的漂移抓不到。

所以「先量雜訊」量完之後,還有第二個問題要問: 這個門檻抓得到我在乎的那種變化嗎?

如果答案是「要 50% 才抓得到」,而你在乎的是 10%—— 那不是把門檻調低就好(調低就回到誤報),是要加大樣本或換特徵。

⚠️ 誠實說明:我量的特徵是「每篇文章的字元長度」, 不是真實模型的輸入分布。具體數字不能直接搬到你的資料上—— 但「雜訊隨樣本數縮小、而教科書門檻假設了樣本夠大」這個形狀是通用的。 你要做的是拿你自己的資料跑一次上面這件事。


步驟四:用控制圖,不要用單點比較

單次超過門檻就告警,會很吵。

更好的做法是控制圖(SPC)的規則,例如:

  • 連續 3 點超過 2σ
  • 連續 7 點同方向
  • 單點超過 3σ

這些規則對「持續的小漂移」比單點門檻敏感得多, 而對單次雜訊不敏感。

Day 28 那個「連續 20 次小退步」的滑坡, 單點門檻抓不到,控制圖規則抓得到。


步驟五:響了要做什麼

告警要對應一個動作,不然它只是噪音。

訊號 動作
輸入漂移中度 記錄、觀察,不動模型
輸入漂移明顯 檢查上游資料源是不是壞了(多數時候是這個)
預測分布明顯變化 檢查是不是有異常輸入
真實指標下降 觸發重訓評估
延遲/錯誤率異常 工程問題,跟模型無關

⭐ 第二列要特別強調: 輸入漂移最常見的原因不是「世界變了」,是「上游壞了」—— 某個欄位開始傳空值、單位改了、編碼變了。

先查資料管線,再懷疑模型。


怎麼確認做對了

  檢查 判準
1 有基準資料 存下來的,不是憑印象
2 量過雜訊 σ ⭐ 有數字
3 門檻高於雜訊 而不是抄來的
4 每個告警有對應動作 寫下來
5 ⭐ 注入一個假漂移,確認會響 見下

第 5 項:故意送一批分布不同的資料,確認告警真的觸發。

這是 Day 28 說的反向測試,套在監控上一樣成立: 沒驗過會響的告警,不知道它會不會響。


常見問題

Q:多久量一次? A:看你的資料流量和決策週期。 太頻繁會被雜訊淹沒(回到步驟三:先知道雜訊多大)。 日結、週結是常見的起點。

Q:TrustyAI 能做嗎? A:ODH/RHOAI 有 TrustyAI 元件(Day 2 的清單裡), 提供偏誤偵測和部分漂移功能。 我沒有在 lab 上跑過它的漂移功能,所以不能給你實測評價—— 但要用之前,先確認它算的是哪一種漂移, 以及它的門檻是不是也是抄來的。

Q:LLM 怎麼監控? A:更難,因為沒有「正確答案」可以比對。 實務上會看:輸出長度分布、拒答率、 以及一組固定的評估題每天跑一次(golden set)。 最後一項最實在。

Q:資料要留多久才能做漂移監控? A:至少要有一個完整的業務週期(含月結、季節性)。 而且要留得下來——這是為什麼監控要在上線前就規劃, 不然你會缺一段基準。


你們的漂移告警,上次響是什麼時候?後來做了什麼?

🧪 這篇的實驗環境與 lab 檔案(最後更新 2026-08-30)

本篇的 PSI 實驗在 2026-09-21 用不同語料、不同樣本單位獨立重跑過一次:n=100 的誤報率 86%(原 85%)、超過 0.25 是 28%(原 24%),注入 5%/10%/20% 漂移的 PSI 為 0.0206/0.0215/0.0403(原 0.0210/0.0225/0.0377)。結論一致。

叢集

  • CRC 2.63.0 · OpenShift 4.22.7 · Kubernetes v1.35.6
  • 單節點:13 vCPU / 40 GiB RAM / 120 GB disk
  • 宿主:Framework Laptop 16(Ryzen AI 7 350 · 8C/16T · 64 GB RAM · 1 TB NVMe · RTX 5070 顯卡模組)
  • ⚠️ 叢集內看不到 GPU(CRC 是 VM,RTX 5070 未 passthrough)

Operator

  • opendatahub-operator.v3.5.0 ← 即 RHOAI 3.x 的上游開源版
  • cert-manager-operator.v1.20.0(3.x 的必要相依;2.x 不需要)
  • openshift-pipelines-operator-rh.v1.23.2、openshift-gitops-operator.v1.21.3

DataScienceCluster 開啟的元件

  • kserve、aipipelines、dashboard、workbenches、modelregistry、kueue(Unmanaged)
  • 其餘(ray/trustyai/feast/aigateway…)為 Removed

叢集外的依賴(跑在宿主的 podman 上,crc start 不會帶起來)

  • Harbor v2.15.2(私有 registry)· MinIO(S3)

模型端

  • Python 3.12.13 · PyTorch 2.11.0+cu128 · FastAPI + uvicorn · prometheus-client

lab 檔案

⚠️ ODH ≠ RHOAI:元件同源,但 namespace 與部分名稱不同 (我這裡是 opendatahub,商用版是 redhat-ods-* 那一套)。 指令的邏輯可以照用,字串要自己對一次。