「なぜ、stratifyがデフォルトで必須ではないのか?」
stratifyの効果を知れば知るほど、そのように感じます。
もう、今後はstratifyを指定せずにtrain_test_split関数を使えません。
それぐらい重要なパラメータです。
この記事では、その重要性を実際の動作検証をもとに解説しています。
本記事の内容
- train_test_split関数におけるstratifyとは?
- stratifyを動作検証[基本]
- stratifyを動作検証[実践]
それでは、上記に沿って解説していきます。
train_test_split関数におけるstratifyとは?
stratifyとは、scikit-learn(sklearn)のtrain_test_split関数のパラメータです。
詳細は、次の記事で解説しています。
機械学習を行うために、train_test_split関数はほぼ必須です。
その機械学習の質をさらに上げたいなら、stratifyを利用すべきでしょう。
次に、実際のstratifyを設定した場合の動きを確認していきます。
stratifyを動作検証[基本]
まずは、基本からです。
基本的な動作を理解していきます。
CSVファイルを用意します。
CSVには、全部で100行のデータが存在します。
その内訳は、以下。
- a 20個
- b 30個
- c 50個
このCSVを用いて、stratifyの基本的な動作を確認します。
なお、動作検証で示すプログラム上ではデータフレームを利用します。
そのため、Pandasを利用できるようにしておいてください。
stratify=Noneの場合
base_none.py
import pandas as pd from sklearn.model_selection import train_test_split # CSV読み込み df = pd.read_csv('test.csv', header=None) # 学習80% テスト20%で分割 df_train, df_test = train_test_split(df, test_size=0.2) # 各値の割合 df_ratio = df.value_counts(normalize=True) df_train_ratio = df_train.value_counts(normalize=True) df_test_ratio = df_test.value_counts(normalize=True) # 結果の表示 print(df_ratio) print(df_train_ratio) print(df_test_ratio)
プログラムの内容は、コメントで大体わかるでしょう。
利用しているvalue_counts関数は、非常に便利です。
value_counts関数は、値が発生する頻度をカウントします。
つまり、df(test.csv)におけるa,b,cの各値の行数をカウントするということです。
さらに、normalize=Trueをパラメータで指定すると割合を表示します。
では、上記プログラムを実行した結果を確認します。
c 0.5 b 0.3 a 0.2 dtype: float64 c 0.5250 b 0.3375 a 0.1375 dtype: float64 a 0.45 c 0.40 b 0.15 dtype: float64
df_ratioの結果は、CSVファイルの各値の割合通りです。
この比率は固定です。
df_train_ratioとdf_test_ratioの結果は、プログラムを実行する度に変わります。
この割合を見て、問題があると感じませんか?
学習データとテストデータで各値の割合が、異なっているのです。
割合を同じにした方が、学習とテストをより同じ条件で実施できると思いませんか?
実際、そのように思う人はたくさんいます。
そう思って、プログラムで工夫して同じ割合にしている場合があります。
でも、そんな苦労はしたくありません。
コードもその分だけ複雑になってしまいますからね。
そこで、stratifyの出番ということになります。
その効果を以下で確認しましょう。
stratifyに値を設定する場合
上記で示したbase_none.pyのtrain_test_split関数呼び出し部分を変更します。
df_train, df_test = train_test_split(df, test_size=0.2, stratify=df)
「stratify=df」をtrain_test_split関数のパラメータに追加しています。
変更したのは、これだけです。
この場合の結果を確認します。
c 0.5 b 0.3 a 0.2 dtype: float64 c 0.5 b 0.3 a 0.2 dtype: float64 c 0.5 b 0.3 a 0.2 dtype: float64
すべての結果が、同じ割合になりました。
以上、stratifyを設定したときの動きを理解できたはずです。
次に、より実践的に使うケースを確認します。
stratifyを動作検証[実践]
実践的なデータを用いて、stratifyを確認します。
この場合に利用するのは、scikit-learnのサンプルデータです。
scikit-learnをインストールしていれば、簡単に利用できます。
利用するサンプルデータの詳細は、以下。
https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_wine.html
Classes | 3 |
Samples per class | [59,71,48] |
Samples total | 178 |
Dimensionality | 13 |
Features | real, positive |
全部で178行のワインに関するデータが、登録されています。
目的変数(ラベル)は、[0,1,2]の3つが存在。
ワインの等級(分類)が3つあるということですね。
それぞれの個数は、[59,71,48]となります。
stratify=Noneの場合
practice_none.py
import pandas as pd from sklearn.datasets import load_wine from sklearn.model_selection import train_test_split # サンプルデータの読み込み data = load_wine() # 説明変数 X = pd.DataFrame(data=data.data, columns=data.feature_names) # 目的変数(ラベル) y = pd.DataFrame(data=data.target) # 学習80% テスト20%で分割 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) # 各値の割合 y_ratio = y.value_counts(normalize=True) y_train_ratio = y_train.value_counts(normalize=True) y_test_ratio = y_test.value_counts(normalize=True) # 結果の表示 print(y_ratio) print('\n') print(y_train_ratio) print('\n') print(y_test_ratio)
プログラム内容は、コメントを見てください。
先ほどのbase_none.pyと同じような構成です。
ただ、説明変数と目的変数で分けているのが大きく異なります。
より実践的なコードと言えるでしょう。
上記コードの結果は、以下。
1 0.398876 0 0.331461 2 0.269663 dtype: float64 1 0.408451 0 0.330986 2 0.260563 dtype: float64 1 0.361111 0 0.333333 2 0.305556 dtype: float64
y_ratioは固定です。
割り切れる数字でないため、細かい小数点表示となります。
y_train_ratioとy_test_ratioは、プログラムを実行する度に異なります。
だからこそ、stratifyを設定するのでした。
ここで設定する値は、何を設定しましょうか?
説明変数であるXですか?
それとも、目的変数であるyでしょうか?
はたまた、別の値を設定するのでしょうか?
入力データの説明変数yと同じ割合に統一したいはずでした。
だから、設定するのは「y」となります。
practice_none.pyの一部を以下のように変更。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y)
この変更をして、実行した結果は以下。
1 0.398876 0 0.331461 2 0.269663 dtype: float64 1 0.401408 0 0.330986 2 0.267606 dtype: float64 1 0.388889 0 0.333333 2 0.277778 dtype: float64
「同じではないぞ!!おかしいぞ!!」
このように思う方がいるかもしれません。
ただ、今回のデータはそもそもが綺麗に割り切れません。
割り切れないものをさらに8:2に分割しています。
そうすると、綺麗な数字にはなることはありません。
念のために、割合の算出根拠となる値を見ていきましょう。
それは簡単に確認できます。
value_counts関数のパラメータ「normalize=True」を無効にするだけです。
項目自体を削除するか、TrueをFalseに変更するだけでOK。
y_ratio = y.value_counts(normalize=False) y_train_ratio = y_train.value_counts(normalize=False) y_test_ratio = y_test.value_counts(normalize=False)
上記のように変更して、実行した結果は以下。
1 71 0 59 2 48 dtype: int64 1 57 0 47 2 38 dtype: int64 1 14 0 12 2 10 dtype: int64
まだ気になる方は、さらに実際に計算してみてください。
いづれせよ、stratifyを設定しないと毎回結果が異なります。
stratifyを設定すると、結果は固定化されます。