stratifyを指定したtrain_test_splitによるデータ分割

stratifyを指定したtrain_test_splitによるデータ分割 機械学習

「なぜ、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

Classes3
Samples per class[59,71,48]
Samples total178
Dimensionality13
Featuresreal, 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を設定すると、結果は固定化されます。

タイトルとURLをコピーしました