Mímisbrunnr知恵の泉

← MLOps 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:実験トラッキング | 関連:デプロイパターン(バッチ・オンライン・ストリーミング)

要点(BLUF)

1. トラッキングとレジストリの違い

実験トラッキングが「すべての実験を記録する」のに対し、レジストリは「本番に値すると判断したモデルだけを昇格管理する」層です。何百 run のうち、本番候補に選ばれた一握りがレジストリに登録され、ライフサイクルを歩みます。

flowchart LR
  T["トラッキング:全runの記録"] -->|"良い run を選抜"| R["レジストリに登録(v1, v2, ...)"]
  R --> S["Staging:検証中"]
  S -->|"承認ゲート"| P["Production:本番稼働"]
  P -->|"新版に交代"| A["Archived:退役"]
  P -.->|"劣化したら前版へ"| RB["ロールバック"]

2. モデルのライフサイクル(ステージ)

ステージ意味誰が動かすか
None登録直後自動
Staging本番相当環境で検証中(シャドー等)自動/担当者
Production本番でトラフィックを受ける承認ゲート
Archived退役。ロールバック候補として保持自動/担当者

Staging → Production の遷移には承認ゲートを置きます。メトリクス基準・前版との比較・公平性チェック(機械学習公平性とバイアスへ)を満たして初めて昇格させます。

3. 動く最小例:バージョンとステージを持つレジストリ

登録・昇格・本番取得・ロールバックを行う最小レジストリです。

class ModelRegistry:
    def __init__(self):
        self.models = {}   # name -> {version: {"stage":..., "metrics":..., "obj":...}}

    def register(self, name, obj, metrics):
        versions = self.models.setdefault(name, {})
        v = max(versions.keys(), default=0) + 1
        versions[v] = {"stage": "None", "metrics": metrics, "obj": obj}
        return v

    def transition(self, name, version, stage):
        # Production は常に1つ:既存の Production を Archived に降格
        if stage == "Production":
            for v, info in self.models[name].items():
                if info["stage"] == "Production":
                    info["stage"] = "Archived"
        self.models[name][version]["stage"] = stage

    def get_production(self, name):
        for v, info in sorted(self.models[name].items()):
            if info["stage"] == "Production":
                return v, info
        return None, None

# --- 運用シナリオ ---
reg = ModelRegistry()
v1 = reg.register("churn", obj="model_v1", metrics={"f1": 0.91})
v2 = reg.register("churn", obj="model_v2", metrics={"f1": 0.94})

reg.transition("churn", v1, "Production")           # まず v1 を本番へ
pv, pinfo = reg.get_production("churn")
print(f"本番モデル : v{pv}  f1={pinfo['metrics']['f1']}")

# v2 が承認ゲートを通過 -> 本番交代
reg.transition("churn", v2, "Production")
pv, pinfo = reg.get_production("churn")
print(f"昇格後本番 : v{pv}  f1={pinfo['metrics']['f1']}  (v1は自動でArchived)")
print(f"v1のstage  : {reg.models['churn'][v1]['stage']}")

# v2 が本番で劣化 -> v1 へロールバック
reg.transition("churn", v1, "Production")
pv, pinfo = reg.get_production("churn")
print(f"ロールバック後本番 : v{pv}  f1={pinfo['metrics']['f1']}")

出力:

本番モデル : v1  f1=0.91
昇格後本番 : v2  f1=0.94  (v1は自動でArchived)
v1のstage  : Archived
ロールバック後本番 : v1  f1=0.91

出力の意味:v1 を本番にした後、より良い v2(f1=0.94)が承認を通って本番交代し、v1 は自動で Archived になりました。その後 v2 が本番で劣化したと仮定して v1 へロールバック——get_production が常に「現在の Production 版」を返すので、サービング側はコードを変えずに最新の本番モデルを取得できます。**「本番は常に1つ、過去版は退役として保持」**という不変条件が、安全な交代とロールバックを支えます。

4. 運用の勘所

なぜそうするのか

レジストリを「ステージを持つ台帳」にする理由は、デプロイとモデル選択を分離するためです。サービングは「Production を読む」とだけ知っていればよく、どの版が Production かはレジストリが管理します。これにより、新版への切り替えもロールバックも「ステージを動かすだけ」になり、サービングのコードやデプロイを触らずに安全に本番モデルを差し替えられます。

⚠️ よくある落とし穴

対応 lab

関連ノート