🎓 レベル:標準 | 重要度:A(必須)
📎 前提:学習データの管理とデータバージョニング | 関連:特徴量エンジニアリングと前処理(機械学習)
要点(BLUF)
- 特徴量変換は使い捨てのコードでなく、再利用可能なパイプライン部品にします。学習と推論で同じ変換オブジェクトを共有することが、スキュー防止(学習推論スキューの防止)の最大の鍵です。
- 設計の核は fit / transform の分離:
fitは学習データから統計量(平均・分散・カテゴリ辞書など)を学ぶ、transformはその統計量で変換する。学習でfitし、推論ではfitせず保存済み統計量でtransformだけ行います。 - これにより特徴量変換そのものがバージョン管理・テスト可能なアーティファクトになります。特徴量の中身の作り方(どんな特徴が効くか)は機械学習分野、ここは「それを運用基盤に乗せる」話です。
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. 運用の勘所
- 変換器をモデルと一緒に保存する:学習済み
Pipeline(変換器+モデル)を1つのアーティファクトとして保存・登録する(モデルレジストリとモデルのライフサイクル)。推論時は丸ごとロードする。 - 推論で fit しない:推論コードに
fitが出てきたら設計ミス。transform/predictのみ。 - 特徴量定義をコード化する:SQL やノートブックに散らばった変換を、テスト可能な関数・変換器に集約する。
- 欠損・外れ値の扱いも変換器に含める:補完値(学習時の中央値など)も
fitで固定し推論で再利用する。
なぜそうするのか
特徴量変換をパイプライン部品にする理由は、学習と推論の一貫性をコードレベルで強制するためです。手書きの変換は「学習ではこう、推論ではこう」と二重実装になりやすく、必ずどこかでずれます。fit/transform を分けた変換器を1つだけ持ち、学習でも推論でも同じオブジェクトを使えば、ずれる余地が構造的に消えます。
⚠️ よくある落とし穴
- 推論データで再
fitする:fit_transformを推論でも呼ぶと、データごとに変換が変わりスキューになる。推論はtransformのみ。 - 変換器を保存し忘れる:モデルだけ保存して変換器を捨てると、推論時に学習時の統計量を再現できない。
- 学習データ全体で
fitしてから分割する(リーク):検証データの情報が変換に漏れる。分割後に学習データだけでfitする。 - 特徴量の作り方の議論をここに持ち込む:どんな特徴が効くかは機械学習分野(特徴量エンジニアリングと前処理)。ここは運用基盤に乗せる方法。
対応 lab
- なし(概念ノート)。本文 §3 の最小パイプラインで挙動確認済み。
関連ノート
- 学習データの管理とデータバージョニング(入力データの版管理)
- 特徴量ストア(特徴量を一元供給する基盤)
- 学習推論スキューの防止(スキューの全体像)
- モデルレジストリとモデルのライフサイクル(変換器ごと登録する)
- 特徴量エンジニアリングと前処理(機械学習・特徴量の作り方)
- 第2章 データと特徴量の基盤 目次
- MLOps・AI基盤 全体目次