Mímisbrunnr知恵の泉

← MLOps 一覧

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

📎 前提:学習データの管理とデータバージョニング | 関連:特徴量エンジニアリングと前処理(機械学習)

要点(BLUF)

1. なぜパイプライン化するのか

ノートブックで X = (X - X.mean()) / X.std() と書くのは簡単ですが、これは罠です。推論時に「学習時の平均・分散」をどこかに保存して再利用しないと、推論データ自身の統計で標準化してしまい、学習と推論で別の変換になります(スキュー)。パイプライン化とは、この「学習で得た統計量を凍結し、推論で再利用する」を構造的に強制することです。

2. fit と transform の分離

flowchart TB
  subgraph 学習時
    A["学習データ"] --> B["fit:統計量を学ぶ(平均・分散・辞書)"]
    B --> C["統計量を保存(変換器アーティファクト)"]
    A --> D["transform:学んだ統計量で変換"]
    D --> E["特徴量 -> 学習"]
  end
  subgraph 推論時
    F["推論データ"] --> G["保存済み変換器をロード"]
    C -.->|"同じ統計量を再利用"| G
    G --> H["transform のみ(fitしない)"]
    H --> I["特徴量 -> 推論"]
  end

ポイントは推論時に fit を絶対にしないこと。推論データで fit し直すと、データごとに変換がブレてスキューになります。

3. 動く最小例:fit/transform を分離した特徴量パイプライン

scikit-learn の Pipeline で、学習で fit した統計量を保存・再利用し、推論では transform だけ行うことを確認します。

import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.base import BaseEstimator, TransformerMixin

# カスタム変換器:対数変換(学習不要だが、変換器として組み込む例)
class Log1pFeature(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return np.log1p(np.clip(X, 0, None))

# 学習データと推論データ(分布が少し違う)
rng = np.random.default_rng(0)
X_train = rng.gamma(2.0, 2.0, (500, 3))
X_serve = rng.gamma(2.0, 2.0, (5, 3))

pipe = Pipeline([
    ("log", Log1pFeature()),
    ("scale", StandardScaler()),
])

# 学習時:fit_transform(統計量を学ぶ)
Xt_train = pipe.fit_transform(X_train)
learned_mean = pipe.named_steps["scale"].mean_   # 学習で固定された平均

# 推論時:transform のみ(学習の統計量を再利用)
Xt_serve = pipe.transform(X_serve)

print(f"学習で固定したscaler平均 : {np.round(learned_mean, 3)}")
print(f"学習後の特徴量の平均(列) : {np.round(Xt_train.mean(0), 3)}  (標準化なのでほぼ0)")
print(f"推論特徴量(先頭1件)      : {np.round(Xt_serve[0], 3)}")
print(f"推論時にscalerは再fitされていない -> 平均は学習値のまま : "
      f"{np.allclose(pipe.named_steps['scale'].mean_, learned_mean)}")

出力:

学習で固定したscaler平均 : [1.449 1.502 1.426]
学習後の特徴量の平均(列) : [ 0. -0.  0.]  (標準化なのでほぼ0)
推論特徴量(先頭1件)      : [-1.865 -0.373  2.13 ]
推論時にscalerは再fitされていない -> 平均は学習値のまま : True

出力の意味:学習で固定した scaler の平均(log1p 後の平均)が、推論時にもそのまま再利用されています(最終行 True)。推論データはわずか5件で、もし推論側で再 fit すれば全く違う標準化になってしまいますが、transform だけを呼ぶことで学習時と同一の変換が保証されます。これが「変換器をアーティファクトとして保存・再利用する」設計の核です。

4. 運用の勘所

なぜそうするのか

特徴量変換をパイプライン部品にする理由は、学習と推論の一貫性をコードレベルで強制するためです。手書きの変換は「学習ではこう、推論ではこう」と二重実装になりやすく、必ずどこかでずれます。fit/transform を分けた変換器を1つだけ持ち、学習でも推論でも同じオブジェクトを使えば、ずれる余地が構造的に消えます。

⚠️ よくある落とし穴

対応 lab

関連ノート