【kaggle】中級機械学習 - データリーク【備忘翻訳】

# はじめに
当記事はkaggleのLearnの[Intro to Machine Learning](https://www.kaggle.com/learn/intro-to-machine-learning)の[Data Leakage](https://www.kaggle.com/code/alexisbcook/data-leakage)を翻訳して備忘としたものです。
拙い英語力なので間違い等あればご指摘いただけたらと思います。

まとめ:[【kaggle】翻訳記事まとめ【備忘翻訳】](https://qiita.com/leafeon00000/items/71e57af5b2d9b0b7c846)
前:[【kaggle】中級機械学習 - XGBoost【備忘翻訳】](https://qiita.com/leafeon00000/private/334e84e10e0aaccc02ea)
次:[【kaggle】データの視覚化 - Hello, Seaborn【備忘翻訳】](https://qiita.com/leafeon00000/private/6ba7222a787332d6130c)

:::note warn
当記事に含まれるコードはkaggle内のnotebook内で正常に動作します。動作を試したい場合はkaggleのnotebookで試してください。
:::

----

# データリーケージ(Data Leakege)
モデルを巧みに台無しにする、この問題を発見して修正します。

このチュートリアルでは、データリーケージとは何か、そしてそれを防ぐ方法について学習します。防ぐ方法を知っていないとリーケージが頻繁に発生し、巧妙かつ危険にモデルを台無しにしてしまうことになります。つまり、これは実践的なデータサイエティストにとって最も重要な概念の一つになります。

## イントロ
**データリーケージ(data leakage) (もしくはリーケージ(leakage))** とは、トレーニングデータにターゲットに関する情報が含まれていても、モデルで予測するときに類似のデータが利用できない場合に発生します。これにより、トレーニングセット(または、場合によっては検証データ)のパフォーマンスは高くなりますが、本番環境ではモデルのパフォーマンスが低下します。

言い換えると、リーケージはモデルを使って予測するまでは正確に見えますが、実際に予測をすると非常に不正確なモデルになる原因となります。

リーケージには主に**ターゲットリーケージ(target leakage)** と**トレインテスト汚染(train-test contamination)** の2種類があります。

### ターゲットリーケージ(Target leakage)
ターゲットリーケージは予測因子に予測を行う時点では利用できないデータが含まれている場合に発生します。ターゲットリーケージについては機能が適切な予測に役立つかどうかだけでなく、データが利用可能になるタイミングや時系列の観点から考えることが重要です。

実際の例を見ていきましょう。肺炎に罹るかどうかを予想していきましょう。生データの先頭の数行は次のようになります:
|got_pneumonia|age|weight|male|took_antibiotic_medicine|...|
|-----|-----|-----|-----|-----|-----|
|False|65|100|False|False|...|
|False|72|130|True|False|...|
|True|58|100|False|True|...|

肺炎に罹った人は治療するために抗生物質を服用します。生データではこれらの列の間に強い相関関係がみられますが、`took_antibiotic_medicine`(抗生物質を服用したか)の値は、`got_pneumonia`(肺炎に罹ったかどうか)の値が決定された後に頻繁に変更されます。これがターゲットリーケージです。

モデルは`took_antibiotic_medicine`の値が`False`の人は肺炎に罹っていないと判断します。検証データはトレーニングデータと同じソースから取得されるため、検証ではパターンが繰り返され、モデルの検証(または交差検証)スコアは高くなります。

しかしこのモデルは、その後現実世界に展開されると非常に不正確になります。なぜなら、肺炎になるであろう患者は、将来の健康状態を予測する必要があるときには、まだ誰も抗生物質を投与されていないからです。

このようなデータリーケージを防ぐには、目標値が現れた後に更新(または作成)された変数を除外する必要があります。

![y7hfTYe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547651/3dfe9249-0517-8871-ae0f-c7a0165fef98.png)

### トレイン-テスト 汚染(Train-Test Contamination)

トレーニングデータと検証データを注意深く区別しないと、別の種類のリーケージが発生します。

検証とは、モデルがこれまで考慮したことのないデータに対してどのような結果を示すかを測定するということを思い出してください。検証データが前処理の動作に影響を与える場合、このプロセスが巧妙に崩壊する可能性があります。これは**トレインテスト汚染(train-test contamination)** と呼ばれます。

例えば、`train_test_split()`を呼び出す前に前処理(欠損値に対する補完など)を実行するとします。結果はどうでしょう？モデルの検証スコアは良好で、モデルに対する信頼度は高いものの、実際にデプロイして予測を行うとパフォーマンスは低下します。

結局のところ、検証データまたはテストデータのデータを予測方法に組み込んでいるので、新しいデータに一般化できない場合でも、その特定のデータではうまく機能する可能性があります。より複雑な特徴量エンジニアリングになると、この問題はさらに巧妙(かつ危険)になります。

検証が単純なトレーニングとテストの分割に基づいている場合は、前処理手順のフィッティングを含むあらゆるタイプのフィッティングから検証データを除外します。scikit-learnパイプラインを使用すると、簡単になります。交差検証を使用する場合は、パイプライン内で前処理を実行することがさらに重要になります。

## 例
この例では、ターゲットのリーケージを検証して除去する1つの方法を学習します。

クレジットカード申請に関するデータセットを使用し、基本的なデータ設定コードは省略します。最終的に、各クレジットカード申請に関する情報はデータフレーム`X`に保存されます。これを使用して、`y`でどの申請が承認されたかを示しています。

<details><summary>データ読み込み</summary>

```python
import pandas as pd

# Read the data
data = pd.read_csv('../input/aer-credit-card-data/AER_credit_card_data.csv', 
                   true_values = ['yes'], false_values = ['no'])

# Select target
y = data.card

# Select predictors
X = data.drop(['card'], axis=1)

print("Number of rows in the dataset:", X.shape[0])
X.head()
```

</details>

```console:console
Number of rows in the dataset: 1319
```

| |reports|age|income|share|expenditure|owner|selfemp|dependents|months|majorcards|active|
|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
|0|0|37.66667|4.5200|0.033270|124.983300|True|False|3|54|1|12|
|1|0|33.25000|2.4200|0.005217|9.854167|False|False|3|34|1|13|
|2|0|33.66667|4.5000|0.004156|15.000000|True|False|4|58|1|5|
|3|0|30.50000|2.5400|0.065214|137.869200|False|False|0|25|1|7|
|4|0|32.16667|9.7867|0.067051|546.503300|True|False|2|64|1|5|

これは小さなデータセットなので、モデルの品質を正確に測定するために交差検証を使用します。

```python
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

# Since there is no preprocessing, we don't need a pipeline (used anyway as best practice!)
my_pipeline = make_pipeline(RandomForestClassifier(n_estimators=100))
cv_scores = cross_val_score(my_pipeline, X, y, 
                            cv=5,
                            scoring='accuracy')

print("Cross-validation accuracy: %f" % cv_scores.mean())
```

```console:console
Cross-validation accuracy: 0.981052
```

経験を積めば、98%の確率で正確なモデルを見つけることは非常に稀であるということが分るでしょう。起こりえることではありますが非常に稀なことなので、データをより詳細に検査してターゲットリーケージについて考えていきましょう。

以下はデータの概要です。データタブ(kaggleの当ページのタブ)でも確認できます:

* `card`: クレジットカードの申請が承認された場合は1、されなかった場合は0
* `reports`: 批判的な報告の数
* `age`: n歳 + ヵ月
* `income`: 年収(10,000で割った値)
* `share`: 毎月のクレジットカード支出額と年間収入の比率
* `expenditure`: クレジットカードの月間平均支出額
* `owner`: 持ち家の場合は1、賃貸の場合は0
* `selfempl`: 自営業の場合は1、そうでない場合は0
* `dependents`: 1 + 扶養家族の数
* `months`: 現在の住所に数でいる月数
* `majorcards`: 保有するクレジットカードの数
* `active`: 有効なクレジットカードの口座数

いくつかの変数が疑わしいようです。例えば、支出とはこのカードでの支出を意味するのでしょうか？それとも申請前に使用したカードでの支出を意味するのでしょうか？

この時点で、基本的なデータの比較が非常に役に立ちます。

```python
expenditures_cardholders = X.expenditure[y]
expenditures_noncardholders = X.expenditure[~y]

print('Fraction of those who did not receive a card and had no expenditures: %.2f' \
      %((expenditures_noncardholders == 0).mean()))
print('Fraction of those who received a card and had no expenditures: %.2f' \
      %(( expenditures_cardholders == 0).mean()))
```

```console:console
Fraction of those who did not receive a card and had no expenditures: 1.00
(カードを受け取らず、支出がなかった人の割合)
Fraction of those who received a card and had no expenditures: 0.02
(カードを受け取っても支出がなかった人の割合)
```

上記のように、カードを受け取っていない人は全員支出がなかったのに対し、カードを受け取った人のうち支出がなかったのはわずか2%でした。モデルが高精度であるように見えたのは驚くべきことではありませんでした。しかし、これもターゲットリーケージのケースで、支出はおそらく**申請したカードでの支出**を意味していると思われます。

`share`は`expenditure`によって部分的に決定されるため、これも除外する必要があります。変数`active`と`majorcards`は少し不明瞭ですが、説明からすると怪しいです。ほとんどの場合、データを作成した人を追跡して詳細を調べることができない場合は、後悔するより安全策を講じる方がよいでしょう。

ターゲットリーケージがないモデルを次のように実装します:

```python
# Drop leaky predictors from dataset
potential_leaks = ['expenditure', 'share', 'active', 'majorcards']
X2 = X.drop(potential_leaks, axis=1)

# Evaluate the model with leaky predictors removed
cv_scores = cross_val_score(my_pipeline, X2, y, 
                            cv=5,
                            scoring='accuracy')

print("Cross-val accuracy: %f" % cv_scores.mean())
```

```console:console
Cross-val accuracy: 0.830919
```

この精度はかなり低いので残念かもしれません。しかし、新しいデータで予測する場合、約80%の確率で正しいと予想できますが、データリーケージが入ったモデルではそれよりもはるかに悪い結果になる可能性があります(交差検証では見かけ上のスコアが高くなっているにも関わらず)。

## まとめ
多くのデータサイエンスアプリケーションでは、データリーケージは数百ドルの損害をもたらすミスとなる可能性があります。トレーニングデータと検証データを慎重に分離することで、リーケージの混在を防ぐことができ、パイプラインはこの分離の実装に役立ちます。同様に、注意力、常識、データ探索を組み合わせることで、目標のリーケージの特定に役立てることができます。