本ページでは、Python の機械学習ライブラリの scikit-learn を用いてトレーニングデータとテストデータを作成するために、サンプリングを行なう手順を紹介します。
トレーニングデータ・テストデータとは
教師あり機械学習(回帰分析、決定木分析、ランダムフォレスト法、ナイーブベイズ法、ニューラルネットワークなど)によるモデルを作成するには、準備したデータセットをトレーニングデータ(訓練用データ、学習用データとも呼ばれます)とテストデータ(検証用データ、評価用データ、検証用データとも呼ばれます)の 2 つに分割して予測モデルの作成、評価を行なうことが一般的です。このように一定の割合でトレーニングデータとテストデータに分割することをホールドアウト (hold-out) と呼びます。
以下は、クレジットカードの解約予測の分析テーマを例に挙げて、そのイメージを説明します。
トレーニングデータとテストデータの分割
データセット全体 (20 レコード) を本例では、80 : 20 の割合でトレーニングデータ (16 件) とテストデータ (4件) に分割します。
トレーニングデータとテストデータはどのような割合 (何対何) で分割すべきといった決まりはありませんが、トレーニングデータ : テストデータを 80 % : 20 % や、75 % : 25 % 、70 % : 30 % の比率で分割することが一般的です。
トレーニングデータを用いた機械学習モデルの作成
分割したデータのうち、トレーニングデータのみを用いて、説明変数 (x) と目的変数 (y) の関係性を学習し、説明変数 (x) が与えられたときに、目的変数 (y) を返す機械学習モデルを作成します。
テストデータを用いた予測の実行
続いて、作成した機械学習モデルとテストデータの説明変数 (x) のみを用いて、予測結果を算出します。
テストデータを用いた評価
前段で求めた予測結果と、実際の解約有無を比較することで、どれだけ正確に予測できるかを確認することで、機械学習モデルの予測性能を測ります。
サンプリングを行なうときに注意すべきこと
データセット全体からレーニングデータとテストデータを分割する際に、データの特性に偏りのあるトレーニングデータやテストデータを使って機械学習モデルを作成すると、精度の悪いモデルができてしまいます。それを防ぐために、ランダムに並び替えたデータからデータセットを抽出します。そのような作業をサンプリング、特に、ランダムに抽出することをランダムサンプリングと呼びます。
train_test_split: トレーニングデータとテストデータを分割
scikit-learn には、トレーニングデータとテストデータの分割を行なうメソッドとしてsklearn.model_selection.train_test_split
が用意されています。このメソッドは、与えられたデータフレームから、指定された条件に基づいてトレーニングデータとテストデータを分割します。
train_test_split の使い方
1 |
sklearn.model_selection.train_test_split(*arrays, **options) |
train_test_split の引数
arrays | 分割対象の同じ長さを持った複数のリスト、Numpy の array, matrix, Pandasのデータフレームを指定。 |
---|---|
test_size | 小数もしくは整数を指定。小数で指定した場合、テストデータの割合を 0.0 〜 1.0 の間で指定します。整数を指定した場合は、テストデータに必ず含めるレコード件数を整数で指定します。指定しなかった場合や None を設定した場合は、train_size のサイズを補うように設定します。train_size を設定していない場合、デフォルト値として 0.25 を用います。 |
train_size | 小数もしくは整数を指定。小数で指定した場合、トレーニングデータの割合を 0.0 〜 1.0 の間で指定します。整数を指定した場合は、トレーニングデータに必ず含めるレコード件数を整数で指定します。指定しなかった場合や None を設定した場合は、データセット全体から test_size を引いた分のサイズとします。 |
random_state | 乱数生成のシードとなる整数または、RandomState インスタンスを設定します。指定しなかった場合は、Numpy のnp.random を用いて乱数をセットします。 |
shuffle | データを分割する前にランダムに並び替えを行なうかどうか。True または False で指定します。False に設定した場合、stratify を None に設定しなければいけません。(デフォルト値: True) |
stratify | Stratified Sampling (層化サンプリング) を行なう場合に、クラスを示す行列を設定します。 (デフォルト値: None) |
train_test_split の使用例
今回使用するデータフレーム (4 カラム、12 レコード) を作成します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
>>> import pandas as pd >>> from sklearn.model_selection import train_test_split >>> >>> namelist = pd.DataFrame({ ... "name" : ["Suzuki", "Tanaka", "Yamada", "Watanabe", "Yamamoto", ... "Okada", "Ueda", "Inoue", "Hayashi", "Sato", ... "Hirayama", "Shimada"], ... "age": [30, 40, 55, 29, 41, 28, 42, 24, 33, 39, 49, 53], ... "department": ["HR", "Legal", "IT", "HR", "HR", "IT", ... "Legal", "Legal", "IT", "HR", "Legal", "Legal"], ... "attendance": [1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1] ... }) >>> print(namelist) age attendance department name 0 30 1 HR Suzuki 1 40 1 Legal Tanaka 2 55 1 IT Yamada 3 29 0 HR Watanabe 4 41 1 HR Yamamoto 5 28 1 IT Okada 6 42 1 Legal Ueda 7 24 0 Legal Inoue 8 33 0 IT Hayashi 9 39 1 HR Sato 10 49 1 Legal Hirayama 11 53 1 Legal Shimada |
テストデータを 30% (test_size=0.3
) としてトレーニングデータ、テストデータに分割します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
>>> namelist_train, namelist_test = train_test_split(namelist, test_size=0.3) >>> print(namelist_train) age attendance department name 10 49 1 Legal Hirayama 1 40 1 Legal Tanaka 7 24 0 Legal Inoue 2 55 1 IT Yamada 4 41 1 HR Yamamoto 3 29 0 HR Watanabe 9 39 1 HR Sato 6 42 1 Legal Ueda >>> print(namelist_test) age attendance department name 0 30 1 HR Suzuki 8 33 0 IT Hayashi 11 53 1 Legal Shimada 5 28 1 IT Okada |
テストデータを具体的な数値で、5 件 (test_size=5
) としてトレーニングデータ、テストデータに分割します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
>>> namelist_train, namelist_test = train_test_split(namelist, test_size=5) >>> print(namelist_train) age attendance department name 3 29 0 HR Watanabe 4 41 1 HR Yamamoto 6 42 1 Legal Ueda 1 40 1 Legal Tanaka 9 39 1 HR Sato 8 33 0 IT Hayashi 7 24 0 Legal Inoue >>> print(namelist_test) age attendance department name 2 55 1 IT Yamada 10 49 1 Legal Hirayama 5 28 1 IT Okada 11 53 1 Legal Shimada 0 30 1 HR Suzuki |
トレーニングデータを 50% (training_size=0.5
) としてトレーニングデータ、テストデータに分割します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
>>> namelist_train, namelist_test = train_test_split(namelist, test_size=None, train_size=0.5) >>> print(namelist_train) age attendance department name 5 28 1 IT Okada 2 55 1 IT Yamada 3 29 0 HR Watanabe 4 41 1 HR Yamamoto 10 49 1 Legal Hirayama 0 30 1 HR Suzuki >>> print(namelist_test) age attendance department name 6 42 1 Legal Ueda 7 24 0 Legal Inoue 9 39 1 HR Sato 11 53 1 Legal Shimada 8 33 0 IT Hayashi 1 40 1 Legal Tanaka |
データの並び替え(シャッフル)を行わないで、分割のみを実施します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
>>> namelist_train, namelist_test = train_test_split(namelist, shuffle=False) >>> print(namelist_train) age attendance department name 0 30 1 HR Suzuki 1 40 1 Legal Tanaka 2 55 1 IT Yamada 3 29 0 HR Watanabe 4 41 1 HR Yamamoto 5 28 1 IT Okada 6 42 1 Legal Ueda 7 24 0 Legal Inoue 8 33 0 IT Hayashi >>> print(namelist_test) age attendance department name 9 39 1 HR Sato 10 49 1 Legal Hirayama 11 53 1 Legal Shimada |
乱数のシードを 42 に固定します。1 回目と 2 回目で全く同じサンプリングがなされていることが見て取れます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
>>> namelist_train, namelist_test = train_test_split(namelist, random_state=42) >>> print(namelist_train) age attendance department name 8 33 0 IT Hayashi 5 28 1 IT Okada 2 55 1 IT Yamada 1 40 1 Legal Tanaka 11 53 1 Legal Shimada 4 41 1 HR Yamamoto 7 24 0 Legal Inoue 3 29 0 HR Watanabe 6 42 1 Legal Ueda >>> print(namelist_test) age attendance department name 10 49 1 Legal Hirayama 9 39 1 HR Sato 0 30 1 HR Suzuki >>> namelist_train, namelist_test = train_test_split(namelist, random_state=42) >>> print(namelist_train) age attendance department name 8 33 0 IT Hayashi 5 28 1 IT Okada 2 55 1 IT Yamada 1 40 1 Legal Tanaka 11 53 1 Legal Shimada 4 41 1 HR Yamamoto 7 24 0 Legal Inoue 3 29 0 HR Watanabe 6 42 1 Legal Ueda >>> print(namelist_test) age attendance department name 10 49 1 Legal Hirayama 9 39 1 HR Sato 0 30 1 HR Suzuki |
“department” のクラスによる層化サンプリングを行います。
層化サンプリングとは、サンプリングしたデータが偏らないよう、指定した変数の出現頻度が一定になるように調整した上で、サンプリングを行なうものです。以下の例では、”department” (=部門) を stratify=namelist['department']
として指定しているので、テストデータには、各部門 (IT (情報システム), HR (人事), Legal (法務)) が、全体の分布と同じになるよう、各 1 件ずつ抽出されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
>>> namelist_train, namelist_test = train_test_split(namelist, stratify=namelist['department']) >>> print(namelist_train) age attendance department name 8 33 0 IT Hayashi 7 24 0 Legal Inoue 5 28 1 IT Okada 10 49 1 Legal Hirayama 0 30 1 HR Suzuki 4 41 1 HR Yamamoto 1 40 1 Legal Tanaka 9 39 1 HR Sato 11 53 1 Legal Shimada >>> print(namelist_test) age attendance department name 2 55 1 IT Yamada 6 42 1 Legal Ueda 3 29 0 HR Watanabe |
上記で説明した層化サンプリングを “attendance” (総会への出席状況) の列に基づいて実施した例は以下になります。テストデータには、1 が 2 件、0 が 1 件と、全体の分布とほぼ同じように抽出されていることがわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
>>> namelist_train, namelist_test = train_test_split(namelist, stratify=namelist['attendance']) >>> print(namelist_train) age attendance department name 7 24 0 Legal Inoue 8 33 0 IT Hayashi 2 55 1 IT Yamada 11 53 1 Legal Shimada 10 49 1 Legal Hirayama 0 30 1 HR Suzuki 6 42 1 Legal Ueda 1 40 1 Legal Tanaka 9 39 1 HR Sato >>> print(namelist_test) age attendance department name 5 28 1 IT Okada 3 29 0 HR Watanabe 4 41 1 HR Yamamoto |
例えば、説明変数 x (attendance 以外) と 目的変数 y (attendance) を分割し、train_test_split に 2 つ以上の引数を与えることもできます。以下の例では、データフレームとarray を渡し、データフレーム、array を 2 つずつ返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
>>> # データセットを説明変数と目的変数に分割 >>> namelist2_x = namelist.drop("attendance", axis=1) >>> namelist2_y = namelist['attendance'] >>> # 説明変数 >>> print(namelist2_x) age department name 0 30 HR Suzuki 1 40 Legal Tanaka 2 55 IT Yamada 3 29 HR Watanabe 4 41 HR Yamamoto 5 28 IT Okada 6 42 Legal Ueda 7 24 Legal Inoue 8 33 IT Hayashi 9 39 HR Sato 10 49 Legal Hirayama 11 53 Legal Shimada >>> # 目的変数 >>> print(namelist2_y) 0 1 1 1 2 1 3 0 4 1 5 1 6 1 7 0 8 0 9 1 10 1 11 1 Name: attendance, dtype: int64 |
サンプリングを実施し、トレーニングデータ、テストデータに分割します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
>>> x_train, x_test, y_train, y_test = train_test_split(namelist2_x, namelist2_y, test_size=0.3) >>> # 説明変数 (トレーニングデータ) >>> print(x_train) age department name 0 30 HR Suzuki 10 49 Legal Hirayama 2 55 IT Yamada 5 28 IT Okada 4 41 HR Yamamoto 8 33 IT Hayashi 6 42 Legal Ueda 11 53 Legal Shimada >>> # 説明変数 (テストデータ) >>> print(x_test) age department name 9 39 HR Sato 7 24 Legal Inoue 1 40 Legal Tanaka 3 29 HR Watanabe >>> # 目的変数 (トレーニングデータ) >>> print(y_train) 0 1 10 1 2 1 5 1 4 1 8 0 6 1 11 1 Name: attendance, dtype: int64 >>> # 目的変数 (テストデータ) >>> print(y_test) 9 1 7 0 1 1 3 0 Name: attendance, dtype: int64 |
参考:
sklearn.model_selection.train_test_split — scikit-learn 0.19.0 documentation