要点:初めてなら 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 のように情報量の多いデータでは割合を変えても差は小さいですが、実務データでは サンプル数・クラス分布・評価目的を考慮して柔軟に決める必要があります。



0 件のコメント:
コメントを投稿