機械学習のためのデータ前処理:訓練データとテストデータへの分割【Scikit-learn入門】
機械学習モデルを構築する際、データ前処理は非常に重要なステップです。これまでの記事では、欠損値の処理、カテゴリ変数の扱い、データのスケーリングといった具体的な前処理手法について解説してきました。
今回は、機械学習モデルの性能を正しく評価するために不可欠なステップである「データの分割」について解説します。特に、モデル学習のための「訓練データ」と、モデルの性能評価のための「テストデータ」に分ける方法とその重要性について、PythonのライブラリであるScikit-learnを使った具体的なコード例を交えながらご紹介します。
なぜデータを分割する必要があるのか
機械学習モデルは、与えられたデータ(訓練データ)からパターンや規則性を学習します。もし、モデルの評価を学習に用いたデータと同じデータで行ってしまうと、モデルはそのデータに対しては非常に高い精度を示すかもしれませんが、まだ見たことのない新しいデータに対しては全く役に立たない、という事態が発生する可能性があります。
この状態を「過学習(Overfitting)」と呼びます。過学習したモデルは、訓練データのノイズまで拾ってしまい、汎化性能(未知のデータに対する予測能力)が低くなります。
ビジネスで機械学習モデルを活用する場合、そのモデルが実際に運用された際に、未知のデータに対してどの程度の精度で予測や分類ができるのかを知ることが最も重要です。そのため、モデルを学習させるデータと、そのモデルが未知のデータに対してどれだけ通用するかを測るためのデータは、意図的に分けておく必要があります。
この目的のために、元のデータセットを通常、「訓練データ(Training Data)」と「テストデータ(Test Data)」の二つに分割します。
- 訓練データ: モデルが学習を行うために使用するデータセットです。
- テストデータ: 訓練済みのモデルが、未知のデータに対してどの程度の性能を発揮するかを評価するために使用するデータセットです。このデータは、モデルの学習プロセスでは一切使用されません。
このようにデータを分割することで、モデルの真の汎化性能をより客観的に評価することが可能になります。
Scikit-learnを使ったデータの分割方法
Pythonで機械学習を行う際によく使われるライブラリにScikit-learnがあります。Scikit-learnは、モデルの学習だけでなく、データ分割や前処理のための便利な機能も多数提供しています。
データの分割には、model_selection
モジュールに含まれるtrain_test_split
関数を使用するのが一般的です。
まずは、サンプルデータを作成してみましょう。ここでは、Pandasを使ってダミーのデータフレームを作成します。
import pandas as pd
from sklearn.model_selection import train_test_split
# ダミーデータの作成
# 特徴量X (例えば、顧客の年齢、購買回数など)
# 目的変数Y (例えば、商品購入の有無 - 0:購入しない, 1:購入する)
data = {'年齢': [25, 30, 35, 40, 45, 50, 55, 60, 28, 33],
'購買回数': [5, 10, 3, 7, 12, 8, 2, 6, 9, 4],
'購入有無': [0, 1, 0, 1, 1, 0, 0, 1, 1, 0]}
df = pd.DataFrame(data)
# 特徴量と目的変数に分割
X = df[['年齢', '購買回数']] # 特徴量 (説明変数)
y = df['購入有無'] # 目的変数 (ターゲット変数)
print("元データ (特徴量X):")
print(X)
print("\n元データ (目的変数y):")
print(y)
上記のコードでは、架空の顧客データとして年齢と購買回数を特徴量X
、商品購入の有無を目的変数y
としています。
次に、train_test_split
関数を使って、このX
とy
をそれぞれ訓練用とテスト用に分割します。
# データを訓練データとテストデータに分割
# test_size=0.3 はテストデータの割合を30%に指定
# random_state は乱数シードを指定することで、分割結果を再現可能にします
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print("\n訓練データ (特徴量 X_train):")
print(X_train)
print("\n訓練データ (目的変数 y_train):")
print(y_train)
print("\nテストデータ (特徴量 X_test):")
print(X_test)
print("\nテストデータ (目的変数 y_test):")
print(y_test)
print(f"\nデータの形状:")
print(f" X_train: {X_train.shape}")
print(f" y_train: {y_train.shape}")
print(f" X_test: {X_test.shape}")
print(f" y_test: {y_test.shape}")
train_test_split
関数は、元の特徴量データ (X
) と目的変数データ (y
) を受け取り、以下の4つのデータセットを返します。
X_train
: 訓練用の特徴量データX_test
: テスト用の特徴量データy_train
: 訓練用の目的変数データy_test
: テスト用の目的変数データ
test_size
引数でテストデータの割合を指定します。例えばtest_size=0.3
とすると、データの30%がテストデータとして使用されます。残りの70%が訓練データとなります。
random_state
引数は、データの分割方法における乱数のシードを指定します。この値を固定することで、何度実行しても同じ結果が得られるようになります。これは、コードの再現性を保つ上で重要です。
分割比率について
訓練データとテストデータの分割比率に、厳密な決まりはありません。一般的には、データの量や性質、モデルの種類などによって適切な比率は異なります。
よく使われる比率としては、以下のものがあります。
- 訓練データ: 70% / テストデータ: 30%
- 訓練データ: 80% / テストデータ: 20%
データの量が非常に多い場合は、テストデータの割合を相対的に少なくしても十分に評価できることがあります。逆に、データが少ない場合は、訓練データを多く確保するためにテストデータの割合を少なくする必要があるかもしれません。しかし、テストデータが少なすぎると、評価結果の信頼性が低下する可能性があります。
層化抽出 (Stratified Sampling) の重要性
特に分類問題の場合、目的変数(クラス)の分布が偏っていることがあります。例えば、あるデータセットで「不正取引あり」が全体のわずか1%しかないような場合です。このようなデータを単純にランダム分割すると、テストデータに「不正取引あり」のデータが全く含まれない、あるいは極端に少なくなる可能性があります。
このような状況では、モデルの性能を正しく評価することができません。テストデータが、実際のデータ分布を適切に反映している必要があるのです。
「層化抽出(Stratified Sampling)」は、このようなクラスの偏りに対応するための分割方法です。層化抽出では、元のデータセットにおける目的変数(クラス)の比率を、訓練データとテストデータでもほぼ同じ比率になるように分割を行います。
Scikit-learnのtrain_test_split
関数では、stratify
引数に目的変数 (y
) を指定することで、簡単に層化抽出を行うことができます。
先ほどのダミーデータはクラスの偏りが少ないですが、もし偏りがあるデータの場合を想定してコードを示します。
# 層化抽出を行う場合
# stratify=y と指定することで、目的変数yのクラス比率を維持して分割します
X_train_stratified, X_test_stratified, y_train_stratified, y_test_stratified = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
print("\n層化抽出後のデータの形状:")
print(f" X_train: {X_train_stratified.shape}")
print(f" y_train: {y_train_stratified.shape}")
print(f" X_test: {X_test_stratified.shape}")
print(f" y_test: {y_test_stratified.shape}")
print("\n元の目的変数yのクラス分布:")
print(y.value_counts(normalize=True))
print("\n訓練データy_trainのクラス分布 (層化抽出後):")
print(y_train_stratified.value_counts(normalize=True))
print("\nテストデータy_testのクラス分布 (層化抽出後):")
print(y_test_stratified.value_counts(normalize=True))
stratify=y
を指定すると、y
に含まれる各クラスの割合が、分割後のy_train
とy_test
でも維持されていることが確認できます。特にクラスに大きな偏りがある分類問題では、必ずstratify
引数を指定することを推奨します。
まとめ
この記事では、機械学習モデルの性能を適切に評価するために不可欠なデータ分割の考え方と、PythonのScikit-learnライブラリを使った具体的な分割方法について解説しました。
- モデルの過学習を防ぎ、汎化性能を正しく評価するために、データを訓練データとテストデータに分割します。
- Scikit-learnの
train_test_split
関数を使うことで、簡単にデータの分割ができます。 - 分類問題でクラスに偏りがある場合は、
stratify
引数を使って層化抽出を行うことが重要です。
データ分割は、機械学習プロジェクトの初期段階で行われる重要な前処理の一つです。正しくデータを分割することで、モデルの真の性能を見極め、ビジネスにおける意思決定に役立つ、信頼性の高いモデルを構築するための土台となります。
次の記事では、分割したデータを使って実際にモデルを学習・評価する基本的な流れについて解説する予定です。