機械学習のデータ分割はどう決める?train / validation / test をMNISTで解説

投稿日:2025年12月2日火曜日 最終更新日:

Google Colab MNIST

X f B! P L
アイキャッチ画像 train_test_splitの割合はどう決める?初心者向けガイド

要点:初めてなら train:valid:test = 80:10:10(=80% / 10% / 10%)を目安に。

この記事では MNIST を使って 90/10・80/20・70/30 の3通りを実際に比較し、結果の読み方と実務向けの注意点を示します。

導入 — なぜデータ分割の割合が重要か

データをどのように 訓練(train)評価(test) に分けるかは、モデルの学習と評価の公平さに直結します。

訓練データが多ければ学習しやすくなりますが、評価用のデータが少ないと「評価がブレる」リスクがあります。 MNISTのようにデータ量が十分にある問題では割合の差が出にくいですが、実務データやサンプル数が少ないデータでは影響が大きくなります。

結論(先に言う)

迷ったら 80% / 10% / 10%(train/valid/test) で良いです。 データが非常に少ない場合は 90% / 10%(testを10%に)でも可。ただし評価の信頼性を上げたいなら交差検証(k-fold)も検討してください。

比較実験:MNISTで割合を変えてみる(90/10, 80/20, 70/30)

以下のコードは MNIST を使い、簡単な全結合ネットワークで3パターンを比較します。Jupyter / Colab にそのまま貼って実行できます。

実験ポイント

  • データセット:MNIST(手書き数字、合計70000サンプル)
  • モデル:シンプルな全結合ネットワーク(学習の挙動を見るのに十分)
  • 比較比率:90/10、80/20、70/30(train/test)
  • 各パターンで学習→テスト精度を算出
  • validationは train 内から10%をさらに分割。
  • 例1:90/10のパターンでは、最終的な比率はtrain:val:testがおよそ81:9:10となります。
  • 例2:80/20のパターンでは、train:val:testが72:8:20となります。

コード(そのまま実行可)

import numpy as np
from sklearn.model_selection import train_test_split
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
from tensorflow.keras import models, layers
import matplotlib.pyplot as plt

# ==============================
# Build a simple neural network
# ==============================
def build_model():
    model = models.Sequential([
        layers.Input(shape=(28, 28, 1)),
        layers.Flatten(),
        layers.Dense(128, activation='relu'),
        layers.Dense(10, activation='softmax')
    ])
    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )
    return model


# ==============================
# Load MNIST dataset
# ==============================
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# Combine all 70,000 samples (60k train + 10k test)
# Reason:
# - We want to compare different train/test ratios (90/10, 80/20, 70/30)
# - The original MNIST split (60k/10k) cannot be used for such experiments
# - So we merge everything and re-split with train_test_split
# Expand dims to (N, 28, 28, 1) for Keras CNN-like API
X = np.expand_dims(np.concatenate([x_train, x_test]) / 255.0, axis=-1)
Y = to_categorical(np.concatenate([y_train, y_test]))


# ==============================
# Split patterns to compare
# Each tuple = (train_rate, test_rate)
# ==============================
patterns = [(0.90, 0.10), (0.80, 0.20), (0.70, 0.30)]

results = []
histories = []

for train_prop, test_prop in patterns:

    print(f"Pattern : {int(train_prop*100)}%/{int(test_prop*100)}%")
    # ------------------------------------------
    # Step 1: Create train/test split from full data
    # ------------------------------------------
    X_train_full, X_test_split, Y_train_full, Y_test_split = train_test_split(
        X, Y,
        train_size=train_prop,
        random_state=42,   # Fix random seed to ensure reproducibility
        shuffle=True
    )

    # ------------------------------------------
    # Step 2: Take 10% of training data as validation
    # ------------------------------------------
    X_train, X_val, Y_train, Y_val = train_test_split(
        X_train_full, Y_train_full,
        test_size=0.1,
        random_state=42   # Also fixed for reproducibility
    )

    # ------------------------------------------
    # Step 3: Build and train the model
    # ------------------------------------------
    model = build_model()
    history = model.fit(
        X_train, Y_train,
        epochs=5,
        batch_size=128,
        validation_data=(X_val, Y_val),
        verbose=1
    )

    # Evaluate on the test split
    loss, acc = model.evaluate(X_test_split, Y_test_split, verbose=0)

    results.append((train_prop, acc))
    histories.append(history)


# ==============================
# Plot 1: Training / Validation Accuracy / Loss curves
# ==============================
plt.figure(figsize=(10,5))

for ratio, hist in zip(patterns, histories):
    plt.plot(hist.history["accuracy"], label=f"Train accuracy {int(ratio[0]*100)}%")
    plt.plot(hist.history["val_accuracy"], linestyle="--", label=f"Val accuracy {int(ratio[0]*100)}%")

plt.title("Training / Validation Accuracy (MNIST)")
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.legend()
plt.grid(True)
plt.show()


plt.figure(figsize=(10,5))

for ratio, hist in zip(patterns, histories):
    plt.plot(hist.history["loss"], label=f"Train loss {int(ratio[0]*100)}%")
    plt.plot(hist.history["val_loss"], linestyle="--", label=f"Val loss {int(ratio[0]*100)}%")

plt.title("Training / Validation Loss (MNIST)")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.grid(True)
plt.show()

# ==============================
# Plot 2: Print accuracy comparison as plain numbers
# ==============================
print("\n===== Test Accuracy Comparison (by Train Size) =====\n")
for ratio, acc in results:
    print(f"Train size {int(ratio*100)}%  ->  Test Accuracy: {acc:.4f}")
補足:上のコードは簡潔化のために stratify=None にしています。 MNIST のようにクラス数が均等なら問題ありませんが、実務データでは stratify=y の使用を推奨します(クラス不均衡対策)。

学習ログ

学習ログを開く
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
Pattern : 90%/10%
Epoch 1/5
443/443 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.8309 - loss: 0.6212 - val_accuracy: 0.9438 - val_loss: 0.1988
Epoch 2/5
443/443 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9491 - loss: 0.1783 - val_accuracy: 0.9621 - val_loss: 0.1298
Epoch 3/5
443/443 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9655 - loss: 0.1203 - val_accuracy: 0.9660 - val_loss: 0.1111
Epoch 4/5
443/443 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9741 - loss: 0.0920 - val_accuracy: 0.9746 - val_loss: 0.0929
Epoch 5/5
443/443 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step - accuracy: 0.9787 - loss: 0.0765 - val_accuracy: 0.9721 - val_loss: 0.0829
Pattern : 80%/20%
Epoch 1/5
394/394 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.8168 - loss: 0.6519 - val_accuracy: 0.9330 - val_loss: 0.2241
Epoch 2/5
394/394 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9492 - loss: 0.1808 - val_accuracy: 0.9514 - val_loss: 0.1688
Epoch 3/5
394/394 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9634 - loss: 0.1290 - val_accuracy: 0.9613 - val_loss: 0.1377
Epoch 4/5
394/394 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9721 - loss: 0.0982 - val_accuracy: 0.9663 - val_loss: 0.1230
Epoch 5/5
394/394 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9788 - loss: 0.0776 - val_accuracy: 0.9641 - val_loss: 0.1148
Pattern : 70%/30%
Epoch 1/5
345/345 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.8063 - loss: 0.6949 - val_accuracy: 0.9339 - val_loss: 0.2327
Epoch 2/5
345/345 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9414 - loss: 0.2060 - val_accuracy: 0.9453 - val_loss: 0.1844
Epoch 3/5
345/345 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9572 - loss: 0.1498 - val_accuracy: 0.9596 - val_loss: 0.1358
Epoch 4/5
345/345 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9691 - loss: 0.1121 - val_accuracy: 0.9637 - val_loss: 0.1233
Epoch 5/5
345/345 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9742 - loss: 0.0894 - val_accuracy: 0.9684 - val_loss: 0.1101

実行結果

===== Test Accuracy Comparison (by Train Size) =====

Train size 90%  ->  Test Accuracy: 0.9687
Train size 80%  ->  Test Accuracy: 0.9659
Train size 70%  ->  Test Accuracy: 0.9657

実験結果のグラフ表示

以下は学習中の 精度(accuracy)損失(loss) の推移です。 いずれも大きな過学習は見られず、データ量が減ると精度の伸びがわずかに緩やかになる程度です。

精度グラフ

精度グラフの比較

損失グラフ

損失グラフの比較

実行結果の比較(数値)

以下は、訓練データの割合を 90% / 80% / 70% に変えたときの テスト精度(Test Accuracy) の比較です。

分割(Train / Test)Test 精度
90% / 10%0.9687
80% / 20%0.9659
70% / 30%0.9657

解釈: MNIST は非常に分かりやすいデータセットのため、訓練データを 70〜90% に変えても、 テスト精度に大きな差は出にくい という結果になっています。

解釈:MNIST のようにサンプル数が多く単純なタスクでは、割合を多少変えても大きな違いは出にくいです。 逆にサンプル数が少い、あるいはクラス分布が偏っているデータであれば割合の影響が大きくなります。

実務的なアドバイス(データ別ガイド)

  • データが豊富(数万件以上):70/30〜80/20 で十分。学習量を優先するなら 80/20。
  • 中規模(数千〜数万):80/10/10(train/val/test)を推奨。
  • データが少ない(数百〜1000未満):90/10 も検討。信頼度確保のため k-fold を推奨。
  • クラスに偏りがある:必ず stratify=y でクラス比を維持する。

よくある質問(FAQ)

Q. テストを増やせば評価は確実になる?

A. 評価の「精度」は向上しませんが、評価の「信頼度(安定性)」は上がります。少ないテストで高精度が出た場合はばらつきに注意。

Q. 毎回ランダム分割して評価した方が良い?

A. 最終的な評価に不安があるなら k-fold クロスバリデーションが有効。ただし画像分類では計算コストが高い点に注意。

Q. なぜ最初にデータを全部まとめてから分割するの?

A. MNIST の “60,000 / 10,000” の事前分割を使うと 80/20・70/30 など自由な比率の再現比較ができないため。

まとめ

初心者へのおすすめ: まずは 80% / 10% / 10%(train/val/test)で進めるのが最適です。 MNIST のように情報量の多いデータでは割合を変えても差は小さいですが、実務データでは サンプル数・クラス分布・評価目的を考慮して柔軟に決める必要があります。

関連記事