Kerasで学習曲線を可視化する方法【過学習の発見と対策まで】

投稿日:2026年5月5日火曜日 最終更新日:

CIFAR-10 Google Colab Keras matplotlib エポック数 可視化 過学習 学習曲線

X f B! P L
Kerasで学習曲線を可視化する方法【過学習の発見と対策まで】アイキャッチ画像

「学習は回っているのに、なぜかテスト精度が上がらない」——そんなとき、学習曲線を見れば原因がすぐわかります

学習曲線とは、エポックごとのloss・accuracyの推移をグラフにしたものです。過学習・未学習・学習率の問題など、数値だけでは気づけない異常をグラフが教えてくれます

なお、エポック数の目安については → 【Keras入門】エポック数はいくつが最適?過学習をグラフで見える化 もあわせてご覧ください。本記事では学習曲線の描き方と、グラフの読み方に絞って解説します。

📘 この記事でわかること

  • Kerasで学習曲線を描く最小コード
  • 過学習・未学習・正常学習をグラフで見分ける方法
  • 学習曲線から「エポック数をいくつにすべきか」を判断する方法
  • 複数モデルの学習曲線を重ねて比較する方法

学習曲線とは

Kerasの model.fit()history オブジェクトを返します。この中に、エポックごとの lossval_lossaccuracyval_accuracy が記録されています。これをmatplotlibでグラフ化したものが学習曲線です。

キー 意味
loss 訓練データに対する損失
val_loss 検証データに対する損失
accuracy 訓練データに対する精度
val_accuracy 検証データに対する精度

trainval の2本の線がどう動くかを見ることで、モデルの状態を診断できます。

実験コード

使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10です。Dropout率を0.0・0.2・0.5の3パターンで学習し、それぞれの学習曲線がどう変わるかを比較します。

環境準備(最初に一度だけ実行)

# ── 環境準備(最初に一度だけ実行)──────────────────────
!apt-get -y install fonts-ipafont-gothic
!rm -rf /root/.cache/matplotlib
!pip install -q japanize_matplotlib
print("環境準備完了")
実行結果をクリックして内容を開く
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  fonts-ipafont-mincho
The following NEW packages will be installed:
  fonts-ipafont-gothic fonts-ipafont-mincho
0 upgraded, 2 newly installed, 0 to remove and 42 not upgraded.
Need to get 8,237 kB of archives.
After this operation, 28.7 MB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fonts-ipafont-gothic all 00303-21ubuntu1 [3,513 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fonts-ipafont-mincho all 00303-21ubuntu1 [4,724 kB]
Fetched 8,237 kB in 3s (2,902 kB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 122354 files and directories currently installed.)
Preparing to unpack .../fonts-ipafont-gothic_00303-21ubuntu1_all.deb ...
Unpacking fonts-ipafont-gothic (00303-21ubuntu1) ...
Selecting previously unselected package fonts-ipafont-mincho.
Preparing to unpack .../fonts-ipafont-mincho_00303-21ubuntu1_all.deb ...
Unpacking fonts-ipafont-mincho (00303-21ubuntu1) ...
Setting up fonts-ipafont-mincho (00303-21ubuntu1) ...
update-alternatives: using /usr/share/fonts/opentype/ipafont-mincho/ipam.ttf to provide /usr/share/fonts/truetype/fonts-japanese-mincho.ttf (fonts-japanese-mincho.ttf) in auto mode
Setting up fonts-ipafont-gothic (00303-21ubuntu1) ...
update-alternatives: using /usr/share/fonts/opentype/ipafont-gothic/ipag.ttf to provide /usr/share/fonts/truetype/fonts-japanese-gothic.ttf (fonts-japanese-gothic.ttf) in auto mode
Processing triggers for fontconfig (2.13.1-4.2ubuntu5) ...
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 MB 82.0 MB/s eta 0:00:00
  Preparing metadata (setup.py) ... done
  Building wheel for japanize_matplotlib (setup.py) ... done
環境準備完了

import・データ準備・モデル構築関数

import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
import japanize_matplotlib
import time

(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype('float32') / 255.0
x_test  = x_test.astype('float32')  / 255.0

def build_model(dropout_rate, name):
    return keras.Sequential([
        keras.layers.Input(shape=(32, 32, 3)),
        keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
        keras.layers.MaxPooling2D((2, 2)),
        keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
        keras.layers.MaxPooling2D((2, 2)),
        keras.layers.GlobalAveragePooling2D(),
        keras.layers.Dense(128, activation='relu'),
        keras.layers.Dropout(dropout_rate),   # ← ここだけ変える
        keras.layers.Dense(10, activation='softmax'),
    ], name=name)

def compile_and_fit(model):
    model.compile(optimizer='adam',
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    start = time.time()
    history = model.fit(x_train, y_train, epochs=30, batch_size=64,
                        validation_split=0.2, verbose=1)
    return history, time.time() - start
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 14s 0us/step

3パターンの学習実行

configs = [(0.0, 'A_drop0.0'), (0.2, 'B_drop0.2'), (0.5, 'C_drop0.5')]
histories, times, scores = {}, {}, {}

for dropout_rate, name in configs:
    print(f"\n=== {name} ===")
    model = build_model(dropout_rate, name)
    h, t = compile_and_fit(model)
    s = model.evaluate(x_test, y_test, verbose=0)
    label = name.split('_')[1]
    histories[label] = h
    times[label] = t
    scores[label] = s
    print(f"学習時間:{t:.1f}秒 test_accuracy:{s[1]:.4f}")
実行結果をクリックして内容を開く
=== A_drop0.0 ===
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 9ms/step - accuracy: 0.2844 - loss: 1.8945 - val_accuracy: 0.3598 - val_loss: 1.7228
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3920 - loss: 1.6377 - val_accuracy: 0.4119 - val_loss: 1.5801
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.4489 - loss: 1.5075 - val_accuracy: 0.4701 - val_loss: 1.4799
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4850 - loss: 1.4137 - val_accuracy: 0.5065 - val_loss: 1.3675
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5073 - loss: 1.3503 - val_accuracy: 0.5231 - val_loss: 1.3110
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5309 - loss: 1.2966 - val_accuracy: 0.5316 - val_loss: 1.2880
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5432 - loss: 1.2582 - val_accuracy: 0.5359 - val_loss: 1.2646
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.5578 - loss: 1.2210 - val_accuracy: 0.5622 - val_loss: 1.2038
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5688 - loss: 1.1872 - val_accuracy: 0.5593 - val_loss: 1.1960
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5829 - loss: 1.1592 - val_accuracy: 0.5798 - val_loss: 1.1637
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5920 - loss: 1.1310 - val_accuracy: 0.5863 - val_loss: 1.1593
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 17ms/step - accuracy: 0.6002 - loss: 1.1093 - val_accuracy: 0.5888 - val_loss: 1.1232
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6115 - loss: 1.0846 - val_accuracy: 0.5998 - val_loss: 1.1135
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6187 - loss: 1.0595 - val_accuracy: 0.6149 - val_loss: 1.0685
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6226 - loss: 1.0488 - val_accuracy: 0.6033 - val_loss: 1.0892
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6302 - loss: 1.0280 - val_accuracy: 0.6185 - val_loss: 1.0590
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6365 - loss: 1.0122 - val_accuracy: 0.6238 - val_loss: 1.0527
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6429 - loss: 0.9947 - val_accuracy: 0.6305 - val_loss: 1.0392
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6474 - loss: 0.9830 - val_accuracy: 0.6375 - val_loss: 1.0215
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6506 - loss: 0.9690 - val_accuracy: 0.6451 - val_loss: 1.0014
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6582 - loss: 0.9470 - val_accuracy: 0.6460 - val_loss: 0.9867
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6622 - loss: 0.9421 - val_accuracy: 0.6458 - val_loss: 0.9944
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6665 - loss: 0.9344 - val_accuracy: 0.6435 - val_loss: 0.9845
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6776 - loss: 0.9098 - val_accuracy: 0.6571 - val_loss: 0.9509
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6775 - loss: 0.8992 - val_accuracy: 0.6622 - val_loss: 0.9430
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6803 - loss: 0.8928 - val_accuracy: 0.6706 - val_loss: 0.9237
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6832 - loss: 0.8801 - val_accuracy: 0.6632 - val_loss: 0.9492
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6912 - loss: 0.8707 - val_accuracy: 0.6697 - val_loss: 0.9276
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6968 - loss: 0.8537 - val_accuracy: 0.6680 - val_loss: 0.9371
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6959 - loss: 0.8529 - val_accuracy: 0.6817 - val_loss: 0.9058
学習時間:133.2秒 test_accuracy:0.6711

=== B_drop0.2 ===
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 8ms/step - accuracy: 0.2671 - loss: 1.9192 - val_accuracy: 0.3422 - val_loss: 1.7103
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.3804 - loss: 1.6541 - val_accuracy: 0.4481 - val_loss: 1.5201
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.4385 - loss: 1.5218 - val_accuracy: 0.4417 - val_loss: 1.4982
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4757 - loss: 1.4319 - val_accuracy: 0.4972 - val_loss: 1.3688
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4974 - loss: 1.3716 - val_accuracy: 0.5136 - val_loss: 1.3333
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5156 - loss: 1.3296 - val_accuracy: 0.5320 - val_loss: 1.2842
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5324 - loss: 1.2873 - val_accuracy: 0.5521 - val_loss: 1.2248
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5418 - loss: 1.2565 - val_accuracy: 0.5437 - val_loss: 1.2394
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5504 - loss: 1.2303 - val_accuracy: 0.5575 - val_loss: 1.2204
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5652 - loss: 1.1997 - val_accuracy: 0.5613 - val_loss: 1.1971
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5716 - loss: 1.1756 - val_accuracy: 0.5840 - val_loss: 1.1323
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5797 - loss: 1.1577 - val_accuracy: 0.5914 - val_loss: 1.1119
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5912 - loss: 1.1345 - val_accuracy: 0.5925 - val_loss: 1.1031
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5969 - loss: 1.1131 - val_accuracy: 0.5992 - val_loss: 1.0930
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6055 - loss: 1.0907 - val_accuracy: 0.6141 - val_loss: 1.0639
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6126 - loss: 1.0697 - val_accuracy: 0.6233 - val_loss: 1.0388
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6164 - loss: 1.0578 - val_accuracy: 0.6251 - val_loss: 1.0309
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6279 - loss: 1.0335 - val_accuracy: 0.6149 - val_loss: 1.0538
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6295 - loss: 1.0205 - val_accuracy: 0.6443 - val_loss: 0.9925
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6416 - loss: 0.9995 - val_accuracy: 0.6379 - val_loss: 1.0051
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6461 - loss: 0.9855 - val_accuracy: 0.6454 - val_loss: 0.9879
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6496 - loss: 0.9778 - val_accuracy: 0.6435 - val_loss: 0.9832
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6541 - loss: 0.9583 - val_accuracy: 0.6419 - val_loss: 0.9966
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6614 - loss: 0.9427 - val_accuracy: 0.6496 - val_loss: 0.9690
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6654 - loss: 0.9328 - val_accuracy: 0.6515 - val_loss: 0.9506
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6694 - loss: 0.9205 - val_accuracy: 0.6575 - val_loss: 0.9516
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6759 - loss: 0.9069 - val_accuracy: 0.6468 - val_loss: 0.9757
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6779 - loss: 0.9001 - val_accuracy: 0.6694 - val_loss: 0.9254
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6832 - loss: 0.8827 - val_accuracy: 0.6704 - val_loss: 0.9152
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6874 - loss: 0.8688 - val_accuracy: 0.6771 - val_loss: 0.8910
学習時間:123.2秒 test_accuracy:0.6714

=== C_drop0.5 ===
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 8ms/step - accuracy: 0.2449 - loss: 1.9740 - val_accuracy: 0.3225 - val_loss: 1.7742
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3321 - loss: 1.7498 - val_accuracy: 0.3874 - val_loss: 1.6468
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3824 - loss: 1.6552 - val_accuracy: 0.4176 - val_loss: 1.5631
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4179 - loss: 1.5778 - val_accuracy: 0.3959 - val_loss: 1.6234
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4461 - loss: 1.5140 - val_accuracy: 0.4841 - val_loss: 1.4163
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4633 - loss: 1.4612 - val_accuracy: 0.4995 - val_loss: 1.3711
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 7ms/step - accuracy: 0.4789 - loss: 1.4239 - val_accuracy: 0.5185 - val_loss: 1.3401
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4945 - loss: 1.3861 - val_accuracy: 0.5118 - val_loss: 1.3215
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5076 - loss: 1.3536 - val_accuracy: 0.5338 - val_loss: 1.2731
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5193 - loss: 1.3224 - val_accuracy: 0.5287 - val_loss: 1.2929
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5263 - loss: 1.3009 - val_accuracy: 0.5605 - val_loss: 1.2000
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5396 - loss: 1.2685 - val_accuracy: 0.5664 - val_loss: 1.1975
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5457 - loss: 1.2520 - val_accuracy: 0.5706 - val_loss: 1.1793
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5624 - loss: 1.2191 - val_accuracy: 0.5758 - val_loss: 1.1603
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5682 - loss: 1.2013 - val_accuracy: 0.5934 - val_loss: 1.1185
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5699 - loss: 1.1853 - val_accuracy: 0.5908 - val_loss: 1.1153
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5827 - loss: 1.1613 - val_accuracy: 0.6058 - val_loss: 1.0719
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5877 - loss: 1.1418 - val_accuracy: 0.6122 - val_loss: 1.0763
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5905 - loss: 1.1376 - val_accuracy: 0.6096 - val_loss: 1.0652
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6025 - loss: 1.1105 - val_accuracy: 0.6251 - val_loss: 1.0288
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6037 - loss: 1.1042 - val_accuracy: 0.6275 - val_loss: 1.0241
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6111 - loss: 1.0869 - val_accuracy: 0.6316 - val_loss: 1.0111
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6118 - loss: 1.0739 - val_accuracy: 0.6224 - val_loss: 1.0234
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6211 - loss: 1.0580 - val_accuracy: 0.6296 - val_loss: 1.0138
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6268 - loss: 1.0419 - val_accuracy: 0.6409 - val_loss: 0.9903
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6291 - loss: 1.0344 - val_accuracy: 0.6451 - val_loss: 0.9889
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6310 - loss: 1.0245 - val_accuracy: 0.6545 - val_loss: 0.9556
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6383 - loss: 1.0110 - val_accuracy: 0.6630 - val_loss: 0.9373
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6421 - loss: 0.9983 - val_accuracy: 0.6485 - val_loss: 0.9806
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6434 - loss: 0.9910 - val_accuracy: 0.6645 - val_loss: 0.9309
学習時間:125.0秒 test_accuracy:0.6604

学習曲線の描画コード

def plot_learning_curve(label, history, ax_acc, ax_loss):
    """1モデル分の学習曲線を描画する"""
    epochs = range(1, len(history.history['accuracy']) + 1)
    ax_acc.plot(epochs, history.history['accuracy'],     label=f'{label} train', linestyle='--')
    ax_acc.plot(epochs, history.history['val_accuracy'], label=f'{label} val')
    ax_loss.plot(epochs, history.history['loss'],        label=f'{label} train', linestyle='--')
    ax_loss.plot(epochs, history.history['val_loss'],    label=f'{label} val')

# ── ① 3パターン重ね合わせグラフ ──────────────────────
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
for label, h in histories.items():
    axes[0].plot(h.history['val_accuracy'], label=label)
    axes[1].plot(h.history['val_loss'],     label=label)
axes[0].set_title('val_accuracy の比較(全30エポック)')
axes[1].set_title('val_loss の比較(全30エポック)')
for ax in axes:
    ax.set_xlabel('Epoch'); ax.legend(); ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lc_compare.png', dpi=150)
plt.show()

# ── ② 各モデルの train vs val 乖離グラフ ─────────────
fig2, axes2 = plt.subplots(3, 1, figsize=(7, 14))
for i, (label, h) in enumerate(histories.items()):
    plot_learning_curve(label, h, axes2[i], axes2[i])
    axes2[i].set_title(f'{label}:train vs val accuracy')
    axes2[i].set_xlabel('Epoch')
    axes2[i].legend(fontsize=8)
    axes2[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lc_overfit.png', dpi=150)
plt.show()

print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>12} | {'Val Acc':>8} | {'Test Acc':>9} | {'Time(s)':>8}")
print("-" * 46)
for label in ['drop0.0', 'drop0.2', 'drop0.5']:
    val_acc  = histories[label].history['val_accuracy'][-1]
    test_acc = scores[label][1]
    t        = times[label]
    print(f"{label:>12} | {val_acc:>8.4f} | {test_acc:>9.4f} | {t:>8.1f}")
print("-" * 46)

最終結果サマリー

===== 最終結果サマリー =====
     Pattern |  Val Acc |  Test Acc |  Time(s)
----------------------------------------------
     drop0.0 |   0.6817 |    0.6711 |    133.2
     drop0.2 |   0.6771 |    0.6714 |    123.2
     drop0.5 |   0.6645 |    0.6604 |    125.0
----------------------------------------------

実験結果

val_accuracy・val_loss 比較グラフ

精度グラフ

精度グラフ

損失グラフ

損失グラフ

train vs val 乖離グラフ(過学習の確認)

Dropout 0.0

Dropout 0.0

Dropout 0.2

Dropout 0.2

Dropout 0.5

Dropout 0.5
パターン 最終 val_accuracy 最終 test_accuracy 学習時間
A:Dropout 0.0 68.17% 67.11% 133.2秒
B:Dropout 0.2 67.71% 67.14% 123.2秒
C:Dropout 0.5 66.45% 66.04% 125.0秒

考察

① Dropout 0.0 が最高精度——でも過学習の兆候あり

test_accuracyで比べると、3パターンの結果は以下のとおりです。

パターン val_accuracy test_accuracy val と test の差
A:Dropout 0.0 68.17% 67.11% −1.06%(乖離あり)
B:Dropout 0.2 67.71% 67.14% −0.57%(小さい)
C:Dropout 0.5 66.45% 66.04% −0.41%(小さい)

val_accuracyではDropout 0.0が最高(68.17%)ですが、val_accuracyとtest_accuracyの差が1.06%と最も大きい点が気になります。val(学習時に使う検証データ)で過適合が始まっている可能性を示しています。

一方、Dropout 0.2はtest_accuracyがDropout 0.0とほぼ同じ(67.14% vs 67.11%)でありながら、valとtestの乖離が0.57%と小さく安定しています。学習曲線のグラフを見ると、この傾向がより明確に現れているはずです。

② 学習曲線の3パターンを読み解く

グラフを見るときは「trainとvalの2本線がどう動いているか」に注目します。

状態 train loss val loss 見分け方
正常学習 下がる 同様に下がる 2本線が近い距離を保って下降
過学習 下がり続ける 途中から上がる 2本線がV字に開いていく
未学習 高いまま 高いまま 2本線がともに高い位置で停滞

今回の結果でいえば、Dropout 0.0はtrain lossが下がり続ける一方でval_accuracyとtest_accuracyの乖離が大きく、過学習の兆候が読み取れます。Dropout 0.5はval・test ともに精度が低く、正則化が強すぎる未学習気味の状態です。Dropout 0.2が最もバランスよく汎化できています。

③ 「適切なエポック数」を学習曲線から読む

val_lossが最も低い点がそのモデルの最適エポック数の目安です。val_lossが下げ止まりまたは上昇に転じたエポックを確認することで、「何エポック学習すべきか」の判断ができます。エポック数の選び方について詳しくは → 【Keras入門】エポック数はいくつが最適?過学習をグラフで見える化 をご覧ください。

この判断を自動化するのが EarlyStopping コールバックです。以下のように追加するだけで、val_lossが改善しなくなった時点で学習を自動停止できます。

early_stop = keras.callbacks.EarlyStopping(
    monitor='val_loss',
    patience=5,          # 5エポック改善なしで停止
    restore_best_weights=True
)
 
history = model.fit(
    x_train, y_train,
    epochs=100,          # 上限は大きく設定しておく
    batch_size=64,
    validation_split=0.2,
    callbacks=[early_stop]
)

④ 複数モデルを重ねて比較するメリット

今回のように複数モデルの val_accuracy を1枚のグラフに重ねると、「どのモデルが何エポック目から差をつけ始めたか」が一目でわかります。これはハイパーパラメータ比較の際に非常に有効な手法です。

今回の実験でも、数値だけ見るとDropout 0.0が最強に見えるですが、学習曲線のグラフを重ねて見ると後半でval lossが上昇しはじめる様子が確認できます。「数値では気づけない過学習の兆候をグラフが先に教えてくれる」——これが学習曲線を可視化する最大のメリットです。


関連記事もあわせてどうぞ: