データリーク:本来使えない情報が、モデル作りに紛れ込む
第8章のミニプロジェクトで、テストデータの平均で標準化してはいけないことを学びました。このように、実際に使うときには手に入らない情報や、評価に使うデータの情報が、モデル作りに紛れ込むこと を データリーク(情報のもれ)と呼びます。
データリークが起きると、評価の結果は実力よりずっとよく見えます。ところが、本番で使うと性能が出ません。しかも、評価の数字がよいので、気づきにくいのが特徴です。
データリークの主な種類
- 答えの情報が、特徴量に紛れ込む: 例えば、「ある病気かどうか」を当てるモデルの特徴量に、「その病気の薬を処方されたか」を入れてしまう。病気と分かった後に処方されるので、予測したい時点では手に入らない情報です。評価ではほぼ全問正解しますが、本番では役に立ちません
- 分割の前に、全データで前処理や特徴量の選択をする: 標準化の平均や、「どの特徴量を使うか」を、テストデータ(や交差検証の検証用の組)も含めた全データで決めてしまう(第8章のミニプロジェクト、次のスライドの実験)
- 同じデータが、訓練とテストの両方に入る: 同じ人の同じ写真が重複していたり、同じ患者の複数回の検査が訓練とテストに分かれたりすると、テストで「見たことのあるデータ」を当てることになる
- 時間の順序を無視する: 明日の売り上げを予測するのに、明日以降のデータで学習してしまう。時間の流れがあるデータでは、過去のデータで学習し、未来のデータで評価する
どれも、「そのモデルを実際に使う時点で、その情報は手に入るか」を考えると見つけられます。
実験:でたらめなデータでも、高い正解率が出てしまう
データリークがどれほど評価を狂わせるかを、極端な実験で確かめます。
- 100件のデータに、でたらめな数 を5000個ずつ用意する(特徴量)
- 正解も、でたらめに 0か1を付ける
特徴量と正解の間には、本当は何の関係もありません。どんなモデルでも、新しいデータでの正解率は0.5(当て推量)になるはずです。
手順A(誤り):全データで特徴量を選んでから、交差検証する
5000個の特徴量の中から、全100件のデータで「正解と最も関係がありそうな20個」を選び、その20個でロジスティック回帰を学習させて、5-分割交差検証をしました。
手順B(正しい):交差検証の中で、学習用のデータだけで特徴量を選ぶ
交差検証の1回ごとに、学習用の80件だけで20個を選び、検証用の20件で評価しました。
何の関係もないでたらめなデータなのに、手順Aでは正解率0.85が出ました。
なぜ、こうなるのか
5000個もでたらめな数があれば、たまたま正解と関係があるように見える数が、いくつも見つかります。手順Aでは、検証用のデータも含めた全データで 「関係がありそうな20個」を選んでいます。つまり、検証用のデータの正解に合わせて特徴量を選んでしまっているのです。検証用のデータで当たるのは当然で、新しいデータでは当たりません。
手順Bでは、特徴量を選ぶときに検証用のデータを見ないので、正しく0.5前後(当て推量)になりました。
特徴量がとても多く、データが少ないとき(遺伝子のデータなど)に、この誤りは実際の研究でも起きてきました。
Pipelineで防ぐ、よくある誤解と振り返り
scikit-learnでは、前処理・特徴量の選択・モデルを Pipeline(パイプライン、処理をつなげたもの)にまとめると、交差検証の1回ごとに、学習用のデータだけで前処理を決めるようになります。
import numpy as np
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
rng = np.random.default_rng(0)
X = rng.normal(size=(100, 5000)) # でたらめな特徴量
y = rng.integers(0, 2, 100) # でたらめな正解
# 誤り: 全データで特徴量を選んでから、交差検証する
X_selected = SelectKBest(f_classif, k=20).fit_transform(X, y)
print(cross_val_score(LogisticRegression(max_iter=1000), X_selected, y, cv=5).mean()) # → 0.85
# 正しい: 選ぶ処理とモデルを Pipeline にまとめ、元のデータを交差検証に渡す
pipe = make_pipeline(SelectKBest(f_classif, k=20), LogisticRegression(max_iter=1000))
print(cross_val_score(pipe, X, y, cv=5).mean()) # → 0.48
SelectKBest(f_classif, k=20) は、正解との関係の強さを測って、上位20個の特徴量を選ぶ処理です
make_pipeline(処理1, 処理2, ...) は、処理を順番につなげて、1つのモデルのように扱えるようにします。cross_val_score に渡すと、1回ごとに、学習用の組だけで SelectKBest を学習させます
- 標準化(
StandardScaler)も同じです。第8章のミニプロジェクトの前処理も、Pipelineにまとめれば、自動で正しい手順になります
よくある誤解
- 「評価の数字がよければ、リークはない」: データリークがあると、数字はむしろよく見えます。よすぎる結果こそ疑いましょう
- 「特徴量の選択は前処理だから、全データでしてよい」: 特徴量の選択も、正解を見て決める処理なので、学習の一部です。検証用のデータを見てはいけません
- 「リークを防ぐには、テストデータを最後に使えば十分」: 交差検証の中の検証用の組でも、同じ注意が必要です(この実験は、テストデータを使っていないのにリークしました)
振り返り
- データリーク: 本番では手に入らない情報や、評価用のデータの情報が、モデル作りに紛れ込むこと。評価が実力よりよく見える
- 種類: 答えの情報の紛れ込み、分割前の前処理・特徴量選択、重複、時間の順序の無視
- 前処理・特徴量の選択は、学習用のデータだけで決める。Pipelineにまとめると、交差検証でも自動で守られる