MLflow Models 與 Model Registry:
從最好的 run 到線上那一版
上一課找到了最好的 run。但「最好的 run」離「線上那一版」還差三件事:模型要能被別人載入、要有大家都認得的名字與版本、 上線那一版要能一行切換。先玩最後這件:服務程式永遠載 models:/churn-clf@champion, 按晉升或回滾,看它拿到的模型與預測怎麼變——
機率與指標都是 notebook 的實測數字(同一組亂數種子)。notebook 裡的 alias 是真的 Registry: set_registered_model_alias 一行,服務端的載入程式一個字不改。
模型+規格+環境,一個資料夾
log_model 不是存 pickle,而是產生一個資料夾:MLmodel 說明書(YAML:有哪些 flavor、signature、Python 與套件版本)、 模型本體(model.skops)、requirements.txt/python_env.yaml/conda.yaml 重建環境用、 input_example.json 一筆範例輸入——實測資料夾裡共 8 個檔案,notebook 會把 MLmodel 印給你看。
flavor 是「可以用哪些方式載入」:sklearn flavor 載回原生物件(有 feature_importances_、predict_proba); python_function(pyfunc)是統一介面——不管底層是 sklearn、PyTorch、XGBoost,都是 load_model(uri).predict(df)。 部署工具只認 pyfunc,所以每個 flavor 都附帶它。
到 notebook 的 1️⃣ 節:log_model、資料夾內容、MLmodel 說明書合約:載回來推論,餵錯資料會怎樣
有 signature,MLflow 在呼叫模型之前就把關(schema enforcement):少一欄、型別錯都直接拒絕並說清楚是哪欄;多一欄則忽略。 沒有 signature 的模型什麼都吃,錯誤延後到 scikit-learn 內部才爆,訊息難懂得多。 所以 log_model 一定要給 signature——它是模型與呼叫端之間的合約。
到 notebook 的 2️⃣ 節:三種錯誤輸入的實際反應名字、版本、alias:晉升與回滾各一行
Registry 是「有名字的模型」的目錄:一個 registered model(churn-clf)底下多個 version,每個 version 指向一個 LoggedModel, 可以掛 description 與 tag(例如 validated=true)。alias 是貼在 version 上的可移動標籤,一個 alias 同時只指一個 version; 指到不存在的 alias 會報 Registered model alias nope not found. 舊版的 stage(Staging/Production)已 deprecated,現在用 alias,名字自己取。
實測:v1 LogisticRegression AUC 0.9508、v2 RandomForest AUC 0.9684。晉升前後同一行載入程式拿到的 run id 不同、預測也換成 v2 的—— 注意 Registry 需要資料庫後端(sqlite 或 server);MLflow 3.15 起純資料夾模式 ./mlruns 已進維護模式,預設直接報錯。
到 notebook 的 3️⃣ 節:註冊、alias、晉升、壞 alias晉升不憑感覺:一行評估,兩版並排
| metric(同一份 test) | v1 logreg | v2 rf |
|---|---|---|
| accuracy | 0.882 | 0.916 |
| precision / recall | 0.912 / 0.860 | 0.927 / 0.913 |
| f1 | 0.885 | 0.920 |
| log_loss | 0.288 | 0.270 |
| roc_auc / pr_auc | 0.951 / 0.956 | 0.968 / 0.975 |
指標與 5 張圖全部記進當前 run,混淆矩陣可以直接讀回來畫。第 5 課會把「roc_auc 必須高於目前 champion」變成管線裡的自動閘門。
到 notebook 的 4️⃣ 節:evaluate 兩個版本、讀回混淆矩陣把前後處理跟模型包在一起;讓 run 記得用哪份資料
真實模型很少「餵 DataFrame 就出機率」:前面要清資料、後面要用門檻轉成決策。這些邏輯散在服務程式裡,換模型就對不上。 繼承 PythonModel 把它們包進同一個部署單位;signature 的 params 段宣告可調參數。 實測同樣 4 筆客戶:門檻 0.5 判流失 2 筆,threshold=0.9 剩 0 筆(機率 0.746/0.071/0.843/0.174)。
資料版本:mlflow.data.from_pandas(df, name=..., targets=...) 自動算內容指紋(digest), mlflow.log_input(dataset, context="training") 記進 run——兩個 run 指標不同時,先看 digest 是不是變了。
到 notebook 的 5️⃣–7️⃣ 節:ChurnWrapper、log_input、切版本拉門檻換你動手
用 mlflow.sklearn.load_model("models:/churn-clf/2") 以原生 flavor 載回 v2,印出 feature_importances_ 最高的三個特徵——pyfunc 做不到,想想為什麼還需要它。
訓一個 GradientBoosting 當 v3 註冊,evaluate 之後寫「自動晉升」:只有 roc_auc 高於目前 champion 才移 alias,否則貼 rejected=true。
把 ChurnWrapper 改成能吃缺欄位、多欄位、亂序的輸入,仍保有 signature。驗證:三種輸入的 prob 都跟原始輸入一致。
卡住了?每一題在 notebook 末節都有折疊解答——先自己做,再打開對照。
情境測驗
離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。
Q1 情境題
線上服務程式寫死 load_model("models:/churn-clf/2")。現在 v3 驗證通過要上線,而且以後每週都會有新版。最佳做法是?
alias 就是為了這件事存在:服務端永遠載同一個 URI,「線上是哪一版」由 Registry 裡的一個可移動標籤決定,晉升與回滾都是一行、不重新部署。A 能動但每次都要改程式碼+部署,回滾也一樣慢;C 是舊做法,stage 已標記 deprecated,而且只有固定的幾個名字;D 是災難:版本 2 的內容被偷換,紀錄與實際不符,永遠回不去。
Q2 錯誤診斷
模型上線第一天,服務端呼叫 pyfunc.predict(df) 就炸。最可能的原因與正確修法?
訊息說得很清楚:模型要 12 欄、輸入缺 f11。signature 在這裡發揮了它的功能——在進到模型之前就擋下,並指名缺哪欄。A 只是把錯誤延後到 scikit-learn 內部(會變成一個難懂的 shape 錯誤,甚至默默算錯);B 症狀不符,版本錯不會產生 missing inputs;D 跟 A 一樣是拆掉安全帶,且 RandomForest 對欄位順序敏感,少一欄照樣炸或算錯。
Q3 情境題
業務說「機率 ≥ 0.7 才算高風險客戶,而且這個數字每季會調」。你要把這條規則跟模型一起交付。最佳做法是?
門檻是業務規則,不是模型的一部分,但又必須跟模型一起版本化與交付——自訂 pyfunc 正是為此:模型與後處理同一個部署單位,params 讓呼叫端調整而不改包裝(notebook 實測 0.5 → 0.9,判流失從 2 筆變 0 筆)。B 把可調的規則烙進模型,每季要重訓;C 能動但規則與模型分家,換模型時容易對不上、改一次要部署一次;D 想法對但用錯地方——tag 是中繼資料,服務端每次推論去查 Registry 既慢又脆弱。
Q4 錯誤診斷
同事照舊教學寫 mlflow.set_tracking_uri("./mlruns"),第一個 run 就炸出下面這段。怎麼修最對?
訊息本身就給了答案:檔案後端進入維護模式,請改用資料庫後端。SQLite 是一個檔案、零安裝,跟資料夾模式一樣方便,卻多了 Model Registry(純檔案模式從來不支援註冊與 alias)。A 讀錯訊息,這不是權限錯誤;B 為了舊教學鎖死版本,之後所有新功能都用不到;C 是訊息提供的暫時逃生口——課堂上救急可以,但它明說「不再更新」,而且照樣沒有 Registry。
Q5 情境題
同一份訓練程式、同樣的 params、同樣的隨機種子,上週的 run AUC 0.968,今天重跑只有 0.951。第一件該查的事是?
重現 = 同樣的程式+設定+資料。程式與設定都有紀錄且相同,剩下的變數就是資料;log_input 記的 digest 是內容指紋,任何一格改了指紋就不同——先看它,一秒排除或確認。A 種子相同的話 sklearn 是決定性的,平均不會揭露原因;C 在沒搞清楚原因前換模型,只是把問題藏起來;D 逃避問題,如果資料真的變了(例如上游欄位定義改了),線上那版可能也已經不適用。
實作在 molab 跑(免費)
molab 的登入狀態進不了內嵌框架(瀏覽器的跨站 cookie 保護), 所以 notebook 要在新分頁執行——把它跟本頁並排開,左邊教學照樣對照。
- 登入 molab(GitHub / Google)
- 開啟課程 notebook,Fork 成自己的副本即可編輯
- 從第一格往下全部執行(首次安裝套件約 1–2 分鐘)——免費 CPU 環境即可,不需要 GPU;Registry 就是本機一個 SQLite 檔
不想用 molab?下載 mlflow-registry_ext.py 後在自己電腦
uvx marimo edit --sandbox mlflow-registry_ext.py,依賴會自動安裝。
molab 的線上編輯器在手機上體驗有限——動手這一段建議用電腦進行。