scikit-learn でトレーニングデータとテストデータを作成する

本ページでは、Python の機械学習ライブラリの scikit-learn を用いてトレーニングデータとテストデータを作成するために、サンプリングを行なう手順を紹介します。

トレーニングデータ・テストデータとは

教師あり機械学習(回帰分析、決定木分析、ランダムフォレスト法、ナイーブベイズ法、ニューラルネットワークなど)によるモデルを作成するには、準備したデータセットをトレーニングデータ(訓練用データ、学習用データとも呼ばれます)とテストデータ(検証用データ、評価用データ、検証用データとも呼ばれます)の 2 つに分割して予測モデルの作成、評価を行なうことが一般的です。このように一定の割合でトレーニングデータとテストデータに分割することをホールドアウト (hold-out) と呼びます。

以下は、クレジットカードの解約予測の分析テーマを例に挙げて、そのイメージを説明します。

トレーニングデータとテストデータの分割

データセット全体 (20 レコード) を本例では、80 : 20 の割合でトレーニングデータ (16 件) とテストデータ (4件) に分割します。

sampling01

トレーニングデータとテストデータはどのような割合 (何対何) で分割すべきといった決まりはありませんが、トレーニングデータ : テストデータを 80 % : 20 % や、75 % : 25 % 、70 % : 30 % の比率で分割することが一般的です。

トレーニングデータを用いた機械学習モデルの作成

分割したデータのうち、トレーニングデータのみを用いて、説明変数 (x) と目的変数 (y) の関係性を学習し、説明変数 (x) が与えられたときに、目的変数 (y) を返す機械学習モデルを作成します。

sampling02

テストデータを用いた予測の実行

続いて、作成した機械学習モデルとテストデータの説明変数 (x) のみを用いて、予測結果を算出します。

sampling03

テストデータを用いた評価

前段で求めた予測結果と、実際の解約有無を比較することで、どれだけ正確に予測できるかを確認することで、機械学習モデルの予測性能を測ります。

sampling04

サンプリングを行なうときに注意すべきこと

データセット全体からレーニングデータとテストデータを分割する際に、データの特性に偏りのあるトレーニングデータやテストデータを使って機械学習モデルを作成すると、精度の悪いモデルができてしまいます。それを防ぐために、ランダムに並び替えたデータからデータセットを抽出します。そのような作業をサンプリング、特に、ランダムに抽出することをランダムサンプリングと呼びます。

train_test_split: トレーニングデータとテストデータを分割

scikit-learn には、トレーニングデータとテストデータの分割を行なうメソッドとしてsklearn.model_selection.train_test_split が用意されています。このメソッドは、与えられたデータフレームから、指定された条件に基づいてトレーニングデータとテストデータを分割します。

train_test_split の使い方

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 レコード) を作成します

>>> 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) としてトレーニングデータ、テストデータに分割します。

>>> 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) としてトレーニングデータ、テストデータに分割します。

>>> 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) としてトレーニングデータ、テストデータに分割します。

>>> 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

データの並び替え(シャッフル)を行わないで、分割のみを実施します。

 
>>> 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 回目で全く同じサンプリングがなされていることが見て取れます。

>>> 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 件ずつ抽出されています。

>>> 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 件と、全体の分布とほぼ同じように抽出されていることがわかります。

>>> 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 つずつ返します。

>>> # データセットを説明変数と目的変数に分割
>>> 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

サンプリングを実施し、トレーニングデータ、テストデータに分割します。

>>> 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