第 50 課 · 進階 · 19 分鐘

Python 模型監控與漂移檢查

在使用者察覺之前,先發現模型行為的變化。

模型監控資料漂移基線警示門檻值

街對面新開了一家店,銷售悄悄變了,程式碼卻一行沒改。上個月的模型已經悄悄錯了。什麼檢查能讓它早點被發現?

昨天有效的模型,今天可能因為輸入或行為變化而悄悄失效。監控把目前視窗與可信基線比較,用門檻值控制雜訊,並只記錄聚合指標而不是個人資料。這些小檢查能在漂移變成事故之前發出訊號。 你還會用分箱 PSI 比較分布,並區分輸入漂移和結果漂移。

把目前輸入與基線比較

從一個容易解釋的指標開始,例如某個數值特徵的平均值。儲存一段可信時期的基線視窗,再與最近視窗比較。差異只是需要調查的訊號,不是模型已經壞掉的證明。

baseline = [10, 12, 14]
live = [13, 15, 17]
shift = sum(live) / len(live) - sum(baseline) / len(baseline)
print(round(shift, 2))

最簡單的平均值偏移

用基線單位表示差距

原始差值很難跨不同量綱的特徵比較。除以基線標準差,可以把偏移表示成熟悉的單位。絕對值越大訊號越強,但門檻值仍取決於具體業務。

import statistics

baseline = [10, 12, 14]
live = [13, 15, 17]
gap = (sum(live) / len(live) - sum(baseline) / len(baseline)) / statistics.pstdev(baseline)
print(round(gap, 2))

三個單位約等於 1.84 個基線標準差

證據足夠才警示

很小的目前樣本可能因偶然產生誇張的平均值。要求最小樣本量、設定門檻值,並顯式處理基線標準差為零的情況。警示應觸發有記錄的檢查,而不是自動改模型。

import statistics

def should_alert(baseline, live, min_n=5, threshold=1.0):
    if len(live) < min_n:
        return False
    baseline_mean = sum(baseline) / len(baseline)
    baseline_std = statistics.pstdev(baseline)
    if not baseline_std:
        return False
    gap = (sum(live) / len(live) - baseline_mean) / baseline_std
    return abs(gap) >= threshold

baseline = [10, 11, 12, 13, 14]
print(should_alert(baseline, [14, 15, 16, 17, 18]))
print(should_alert(baseline, [15, 16]))

小樣本不會觸發警示

監控聚合值,而不是身份

記錄比率和計數,而不是原始使用者文字、提示詞或標識符。正例預測率的變化可以暴露輸入或模型行為變化,同時不洩露個人資料。保留期限和存取權限要寫清楚。

baseline = [0, 0, 0, 0, 1]
live = [0, 0, 1, 1, 1]
base_rate = sum(baseline) / len(baseline)
live_rate = sum(live) / len(live)
print(round(live_rate - base_rate, 2))

記錄比率,而不是某個人

分箱 PSI 比較分布

群體穩定性指數比較基線和即時視窗中固定分箱的佔比。很小的佔比會被替換為下限,以避免除以零。PSI 是篩查訊號,不是模型出錯的證明;要檢查移動最大的分箱。

import math

def shares(values, edges):
    counts = [0] * (len(edges) - 1)
    for value in values:
        for i in range(len(edges) - 1):
            if edges[i] <= value < edges[i + 1]:
                counts[i] += 1
                break
    total = sum(counts)
    return [count / total for count in counts]

edges = [0, 12, 16, 20, 30]
base = shares([10, 12, 14, 16, 18], edges)
live = shares([14, 16, 18, 20, 22], edges)
psi = sum((l - b) * math.log(max(l, 0.001) / max(b, 0.001)) for b, l in zip(base, live))
print([round(x, 2) for x in base])
print([round(x, 2) for x in live])
print(round(psi, 3))

比較佔比,而不是身份

區分輸入漂移和結果漂移

輸入漂移表示特徵分布移動了。結果漂移表示標籤到達後,模型的準確率或錯誤成本發生了變化。兩者可能同時出現,也可能各自出現,因此都要監控,並讓警示門檻值與最小樣本量掛鉤。

def accuracy(actual, predicted):
    return sum(a == p for a, p in zip(actual, predicted)) / len(actual)

baseline_actual = [1, 0, 1, 1, 0, 1]
baseline_pred = [1, 0, 1, 1, 0, 1]
live_actual = [1, 1, 0, 1, 0, 1]
live_pred = [1, 1, 0, 1, 0, 0]
print("baseline", round(accuracy(baseline_actual, baseline_pred), 2))
print("live", round(accuracy(live_actual, live_pred), 2))

輸入和標籤是不同訊號

本課要點

  • 基線定義模型仍被信任時的正常狀態。
  • 標準化差距讓不同特徵的偏移可以比較。
  • 最小樣本量和門檻值能減少誤報。
  • 輸入漂移和概念漂移是不同的問題。
  • 監控聚合指標,避免保留原始個人資料。
  • PSI 篩查分布移動;輸入漂移和結果漂移回答的是不同問題。

常見錯誤

  • 只憑一個觀測或極短視窗就警示。
  • 把統計偏移當成模型一定出錯的證據。
  • 本來記錄比率就夠了,卻儲存完整提示詞或使用者記錄。
  • 沒有樣本量或成本策略,卻對每一次微小的 PSI 變化都警示。
輪到你

比較基線 [0, 0, 1, 1] 與目前 [0, 1, 1, 1] 的正例預測率,列印差值並保留兩位小數。

動手試試

改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。

瀏覽器內 Python本機執行 · 無需帳號
首次執行會從 CDN 下載 Pyodide。修改後點擊「執行程式碼」。

練習本課

12 道題,作答後立即看到解釋。

查看 12 道題答案開始練習
← k-means 聚類