トレーニングセットとテストセットの分割

機械学習の世界では、データはすべてのモデルを動かす燃料です。しかし、この燃料を正しく使うかどうかで、あなたのモデルが未来を正確に予測するスマートなエンジンになるのか、それともただ丸暗記するだけの機械になるのかが決まります。

今日は、機械学習において極めて重要かつ基本的な概念について詳しく見ていきます:トレーニングセットとテストセットの分割。これは、信頼できるモデルを構築するための第一歩であり、モデルの真の能力を評価する鍵でもあります。

簡単に言えば、トレーニングセットとテストセットの分割は、学生時代の学習と試験のようなものです:

  • トレーニングセットは学生の教材や練習問題であり、モデルはそれを使ってデータの規則性やパターンを学習します。
  • テストセットは最終の期末試験であり、モデルはそれを使って、単に練習問題(トレーニングセット)の答えを暗記したのではなく、本当に知識を身につけたかどうかを確認します。

なぜトレーニングセットとテストセットを分割する必要があるのか?

想像してみてください。もし学生が先生から渡された模擬問題だけを復習し、試験問題がまったく同じ模擬問題だった場合、満点を取ったとします。それがその学生がその科目を本当に理解していることの証明になるでしょうか?明らかにそうではありません。彼はただ答えを覚えていただけかもしれません。

機械学習では、もしすべてのデータでモデルを学習させ、次にその同じデータでその性能を評価すると、同じ間違いを犯します。モデルは異常なほど良い結果を出すでしょう。なぜなら、ノイズや偶然性を含むすべてのデータの詳細を「見て」「覚えて」しまったからです。この現象は過学習。

過学習したモデルは、例題を暗記するだけの学生のようなものです。新しい、見たことのない問題(新しいデータ)に直面すると、パフォーマンスがとても悪くなります。その「汎化能力」は弱いのです。

したがって、データを2つの部分に分割する必要があります:

  1. トレーニングセット:使うのは教モデルを学習させることです。
  2. テストセット:使うのは考モデルが見たことのない新しいデータを処理する能力を評価することです。

テストセットはトレーニングセットから完全に分離し、モデルの学習プロセス全体を通じて決してモデルに見られてはいけません。そうして初めて、テストセットでの評価結果はモデルの真の汎化能力を客観的に反映できます。


分割方法:よく使われる手法と戦略

データの分割は簡単そうに聞こえますが、実は奥が深いです。異なる分割戦略は異なる場面に適しています。

1. 単純ランダム分割

これは最も基本的で、最もよく使われる方法です。データセット全体をランダムにシャッフルし、一定の割合で2つに分割します。

例

# 例:Python の scikit-learn ライブラリを使ってランダム分割する
from sklearn.model_selection import train_test_split

# X は特徴データ、y はラベルデータとする
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

print(f"トレーニングセットのサンプル数:{len(X_train)}")
print(f"テストセットのサンプル数:{len(X_test)}")

コードの解説:

  • train_test_split:これは scikit-learn でデータを分割するための中核関数です。
  • X, y:入力する特徴データと対応するラベルです。
  • test_size=0.2:テストセットのサイズ割合を20%に指定します(つまりトレーニングセットが80%)。また、train_size=0.8で指定することもできます。
  • random_state=42:乱数シードを設定します。これにより、コードを実行するたびに分割結果が完全に同じになることが保証され、実験の再現性にとって非常に重要です。任意の整数に設定できます。

2. 層化抽出による分割

分類問題では、データセットのクラス分布が不均衡な場合(例:90%がAクラス、10%がBクラス)、単純なランダム分割を行うと、トレーニングセットとテストセットの各クラスの割合が大きく異なり、評価の公平性に影響を与える可能性があります。

層化抽出は、分割後のトレーニングセットとテストセットにおいて、各クラスの割合が元のデータセットと一致することを保証します。

例

# 例:分類問題で層化抽出を使う
from sklearn.model_selection import train_test_split

# y は分類ラベルとする
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)

# 分割後のクラス割合を確認する
from collections import Counter
print("元データのクラス分布:", Counter(y))
print("トレーニングセットのクラス分布:", Counter(y_train))
print("テストセットのクラス分布:", Counter(y_test))

コードの解説:

  • stratify=y:これが重要なパラメータです。関数にラベルyのクラス分布に従って層化抽出を行うよう指示します。

3. 時系列データの分割

時系列データ(株価や毎日の気温など)では、データポイント間に時間的な依存関係があります。未来のデータを使って過去を予測することはできないため、ランダムにシャッフルすることはできません。

通常は、時間順に分割します:時間的に最初の80%のデータをトレーニングセット、後の20%のデータをテストセットとして使います。。

例

# 例:時系列データの順序分割
split_index = int(len(X) * 0.8) # 80%の位置のインデックスを計算する

X_train, X_test = X[:split_index], X[split_index:]
y_train, y_test = y[:split_index], y[split_index:]

print(f"トレーニングセットの時間範囲:最初の {split_index} サンプル")
print(f"テストセットの時間範囲:最後の {len(X) - split_index} サンプル")

分割比率はどう選ぶのか?

これはよくある質問ですが、固定の答えはありません。よく使われる比率は次のとおりです:

比率 (トレーニングセット:テストセット) 適用シーン メリット デメリット
70:30 中小規模のデータセット(数千〜数万サンプル)の定番の選択肢 トレーニングデータ量と評価の信頼性のバランスが取れている 極小のデータセットでは、30%のテストセットはサンプルが少なすぎて、評価が不安定になる可能性がある
80:20 現在ではより一般的なデフォルトの選択肢であり、特に深層学習に適している モデルが学習に使えるデータが増える テストセットが比較的小さいため、評価の分散がやや大きくなる可能性がある
90:10 または 95:5 データ量が非常に限られている場合 限られたデータを最大限に活用して学習できる テストセットが小さすぎると、評価結果が信頼できない可能性があり、信頼度が低い

核心となる原則:

  1. トレーニングセットを十分に大きくする:モデルが有効なパターンを学習するには、十分なデータが必要です。
  2. テストセットを十分に大きくする:テストセットは統計的に信頼できる性能評価を提供する必要があります。通常、テストセットには少なくとも数百のサンプルがあれば、評価結果は比較的安定します。
  3. データ量が多ければ多いほど、テストセットに割り当てる割合は相対的に小さくしてもよい。なぜなら、非常に小さな割合でも大量のサンプルを表す可能性があるからです。

発展的な概念:検証セットと交差検証

実際のプロジェクトでは、最終モデルの評価だけでなく、学習プロセス中にモデルのハイパーパラメータ(学習率や木の深さなど)。もしテストセットを直接使ってパラメータを調整すると、テストセットは再び「汚染」され、「最終試験官」としての公平性を失います。

そのために導入するのは検証セットです。。

3つのデータセットへの分割:トレーニングセット、検証セット、テストセット

  1. トレーニングセット:モデルのパラメータ学習に使います。
  2. 検証セット:学習プロセス中にハイパーパラメータの調整、モデルの選択、早期停止を行うために使います。これは「模擬試験」に相当します。
  3. テストセット:モデルとハイパーパラメータが確定した後、最終的かつ一回限りの性能評価に使います。これが「最終本番試験」です。

例

# 例:最初にトレーニング+検証セットとテストセットに分割し、次にトレーニング+検証セットから検証セットを分割する
X_temp, X_test, y_temp, y_test = train_test_split(X, y, test_size=0.15, random_state=42) # 最初に15%を最終テストセットとして分ける
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.176, random_state=42) # 残りの85%から約15%を検証セットとして分ける

# 比率の計算: 0.85 * 0.176 ≈ 0.15、最終的な比率は約 70:15:15
print(f"トレーニングセット:{len(X_train)}、検証セット:{len(X_val)}、テストセット:{len(X_test)}")

K分割交差検証

データ量が多くない場合、検証セットを別途分割すると、トレーニングデータがさらに減ってしまいます。K分割交差検証は、より強力な解決策です。

その手順は以下のとおりで、限られたデータを効果的に活用できます:

例

flowchart TD
A[元データセット] --> B[ランダムにシャッフルして均等にK分割する]
B --> C{K回のループを行う}
C --> D[i回目: i番目を検証セットとして使用]
D --> E[残りのK-1個を結合してトレーニングセットにする]
E --> F[その回のトレーニングセットでモデルを訓練する]
F --> G[その回の検証セットでスコアSiを評価する]
    G --> C
C -- K回完了後 --> H[K個のスコアの平均値を計算して最終評価とする]

例

# 例:5分割交差検証でモデルを評価する
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
scores = cross_val_score(model, X, y, cv=5) # cv=5 は5分割交差検証を意味する

print(f"各分割のスコア:{scores}")
print(f"平均スコア:{scores.mean():.4f} (+/- {scores.std()*2:.4f})") # 平均スコアと標準偏差を出力する

交差検証の利点:

  • すべてのデータを訓練と検証に最大限活用できる。
  • 評価結果がより安定して信頼できる(複数回の評価の平均のため)。
  • 中小規模のデータセットでのモデル選択やハイパーパラメータ調整のゴールドスタンダードである。

実践練習:データ分割を体験してみよう

それでは、簡単なデータセットを使って実際にやってみましょう。

例

# 1. 必要なライブラリをインポートする
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# 2. アイリスデータセットを読み込む
iris = load_iris()
X, y = iris.data, iris.target
print(f"データセット形状:特徴量 {X.shape}、ラベル {y.shape}")

# 3. 単純なランダム分割(80%訓練、20%テスト)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"ランダム分割 -> 訓練セット:{X_train.shape}、テストセット:{X_test.shape}")

# 4. 層化ランダム分割
X_train_s, X_test_s, y_train_s, y_test_s = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
print(f"層化分割 -> 訓練セット:{X_train_s.shape}、テストセット:{X_test_s.shape}")

# 5. 層化の効果を確認する
print("\n元データのクラス分布:", np.bincount(y))
print("ランダム分割後のテストセット分布:", np.bincount(y_test)) # 不均衡になる可能性がある
print("層化分割後のテストセット分布:", np.bincount(y_test_s)) # 元の分布に比例するはず

あなたのタスク:

  1. 上記のコードを実行して、出力結果を確認してください。
  2. 以下の修正を試してくださいtest_sizeを 0.3 に変更して、訓練セットとテストセットのサイズの変化を確認してください。
  3. 以下の修正を試してくださいrandom_stateを別の数字(例:7)に変更して、再度実行し、分割結果が変化するか確認してください。
  4. (チャレンジ)パラメータを設定せずにrandom_stateコードを複数回実行して、毎回の分割結果が同じかどうかを確認してください。

まとめと重要ポイント

  • 核心的な目的:訓練セットとテストセットを分割するのは、モデルの汎化能力を評価するため、過学習を防ぎ、モデルが新しいデータを処理できることを確認するためです。
  • 黄金のルール:テストセットはトレーニングプロセス全体を通して完全に秘匿する必要があります、最終評価のみに使用します。
  • 分割方法:
    • ランダム分割:最も一般的。
    • 層化分割:分類問題における不均衡データに適しています。
    • 順序分割:時系列データに適しています。
  • 分割比率:絶対的な基準はなく、「十分な訓練」と「信頼できる評価」の間でバランスを取る必要があります。80:20 または 70:30 が一般的な出発点です。
  • 発展的なツール:
    • 検証セット:モデルのハイパーパラメータ調整に使用し、テストセットの純粋性を保ちます。
    • K分割交差検証:中小規模データセットの評価とハイパーパラメータ調整に優れたツールで、結果がより堅牢です。
その他の拡張