🎓 レベル:標準 | 重要度:A(必須)
📎 前提:MLOpsとMLライフサイクル | 関連:学習データの管理とデータバージョニング
要点(BLUF)
- ML の再現性は コード・データ・環境(依存)の三位一体で初めて成立します。モデルは「コード × データ × 環境 × 乱数シード」の関数なので、どれか1つでも固定し損ねると同じ結果は出ません。
- それぞれに専用の固定手段があります:コード→Git、データ→データバージョニング(内容ハッシュ/スナップショット)、環境→ロックファイル+コンテナ、乱数→シード固定。
- 「実験を後から完全に再走できる」状態を保つのが目標。学習成果物(モデル)には、それを生んだコード・データ・環境のバージョンをメタデータとして必ず紐付けます(→ モデルレジストリとモデルのライフサイクル)。
1. モデルは「3つの入力」の関数
モデル = f(コード, データ, 環境, 乱数シード)
研究で「結果が再現しない」とき、犯人はたいていこの4つのどれかが固定されていないことです。MLOps では、この関数の全入力をバージョン管理下に置くことを再現性と呼びます。コードだけ Git に入れても、データが上書きされていたり、ライブラリのマイナー更新で挙動が変わっていれば再現しません。
2. それぞれの固定手段
| 入力 | 固定手段 | 具体例 |
|---|---|---|
| コード | バージョン管理 | Git のコミットハッシュ |
| データ | データバージョニング | 内容ハッシュ、スナップショット、DVC/lakeFS等 → 学習データの管理とデータバージョニング |
| 環境 | 依存の固定 | requirements.txt+ロックファイル、Dockerイメージタグ → モデルのパッケージングとコンテナ化 |
| 乱数 | シード固定 | np.random.default_rng(seed)、フレームワークのseed設定 |
| 構成 | 設定管理 | ハイパーパラメータを設定ファイルに → ハイパーパラメータ管理と再現 |
データバージョニングの実装そのもの(DVC・lakeFS・データレイク)は学習データの管理とデータバージョニングとデータエンジニアリング分野へ。ここでは「学習を再現するには3つ全部のバージョンが要る」という MLOps の原則を押さえます。
3. 図解:再現可能な学習実行
flowchart LR A["コード(Gitハッシュ)"] --> D["学習実行"] B["データ(内容ハッシュ)"] --> D C["環境(イメージタグ)"] --> D S["乱数シード"] --> D D --> M["モデル成果物"] M --> R["メタデータに3つのバージョンを記録"]
成果物 M に「どのコード・データ・環境・シードで生まれたか」を刻む——これがあれば、半年後でも同じモデルを再現でき、障害時に「いつ何が変わったか」を追えます。
4. 動く最小例:データのハッシュ固定で同一モデルを再現する
データを内容ハッシュで識別し、同じハッシュなら同じモデルが再現することを確認します。
import hashlib
import numpy as np
from sklearn.tree import DecisionTreeClassifier
def data_hash(X, y):
h = hashlib.sha256()
h.update(np.ascontiguousarray(X).tobytes())
h.update(np.ascontiguousarray(y).tobytes())
return h.hexdigest()[:12]
def make_dataset(seed):
rng = np.random.default_rng(seed)
X = rng.normal(0, 1, (200, 4))
y = (X[:, 0] + X[:, 1] > 0).astype(int)
return X, y
def fingerprint(X, y, seed=42):
model = DecisionTreeClassifier(random_state=seed).fit(X, y)
# 木の構造を指紋化(再現したか判定)
return hashlib.sha256(model.tree_.threshold.tobytes()).hexdigest()[:12]
X1, y1 = make_dataset(seed=0)
X2, y2 = make_dataset(seed=0) # 同じデータ
X3, y3 = make_dataset(seed=1) # 違うデータ
print(f"データハッシュ A = {data_hash(X1, y1)}")
print(f"データハッシュ B = {data_hash(X2, y2)} (同一データ)")
print(f"データハッシュ C = {data_hash(X3, y3)} (別データ)")
print(f"モデル指紋 A = {fingerprint(X1, y1)}")
print(f"モデル指紋 B = {fingerprint(X2, y2)}")
print(f"モデル指紋 C = {fingerprint(X3, y3)}")
print(f"A と B は同一モデル? {fingerprint(X1, y1) == fingerprint(X2, y2)}")
print(f"A と C は同一モデル? {fingerprint(X1, y1) == fingerprint(X3, y3)}")
出力:
データハッシュ A = 7705ee9fb80b
データハッシュ B = 7705ee9fb80b (同一データ)
データハッシュ C = 1bfa3f8b78fd (別データ)
モデル指紋 A = fa5dc805c4d8
モデル指紋 B = fa5dc805c4d8
モデル指紋 C = c7df2c1287de
A と B は同一モデル? True
A と C は同一モデル? False
出力の意味:同じ内容のデータはハッシュが一致し、結果として学習モデル(木構造の指紋)も完全一致しました。データが変わればハッシュもモデルも変わります。つまりデータのハッシュを記録しておけば、後から「このモデルはどのデータで作られたか」を厳密に照合でき、再現性を機械的に検証できるということです。
5. 運用の勘所
- 成果物に出自を刻む:モデルを保存するとき、コード・データ・環境のバージョンを必ずメタデータに付ける(実験トラッキングがこれを自動化)。
- 環境はロック+コンテナで二重に固定:
requirements.txtのバージョン指定だけでなく、ロックファイルで推移的依存も固定し、最終的にイメージタグで丸ごと固める。 - 乱数の非決定性に注意:GPU の並列演算やマルチスレッドは完全な決定性を保証しないことがある。「ビット単位の一致」より「統計的に再現」を現実的な目標にする場合もある。
なぜそうするのか
再現性は学術的な潔癖さのためではなく、運用の生命線です。本番モデルが壊れたとき、「直前と何が変わったか」を特定できなければ復旧できません。コード・データ・環境を固定しておけば、障害時に過去の正常なモデルへ即座にロールバックでき、再学習の比較対象(ベースライン)も確保できます。
⚠️ よくある落とし穴
- データをファイル名で管理する:
data_final_v2_really.csvは再現性ゼロ。内容ハッシュかバージョン管理システムで識別する。 pip installを pin なしで使う:依存のマイナー更新で挙動が変わる。ロックファイルで固定する。- シードだけ固定して安心する:シードはコード・データ・環境が同じときに初めて効く。3点が揃っていなければ無意味。
- モデルだけ保存して出自を捨てる:
model.pklだけでは「どう作ったか」が失われる。メタデータ必須。
対応 lab
- なし(概念ノート)。成果物への出自記録の実装は 実験トラッキング の
labs/03_trackingで扱います。
関連ノート
- MLOpsとMLライフサイクル(なぜ再現性が運用の前提か)
- 学習データの管理とデータバージョニング(データ側の実装)
- 実験トラッキング(出自の自動記録)
- モデルレジストリとモデルのライフサイクル(成果物のバージョン管理)
- モデルのパッケージングとコンテナ化(環境の固定)
- 第1章 MLOpsの全体像 目次
- MLOps・AI基盤 全体目次