「学習は回っているのに、なぜかテスト精度が上がらない」——そんなとき、学習曲線を見れば原因がすぐわかります。
学習曲線とは、エポックごとのloss・accuracyの推移をグラフにしたものです。過学習・未学習・学習率の問題など、数値だけでは気づけない異常をグラフが教えてくれます。
なお、エポック数の目安については → 【Keras入門】エポック数はいくつが最適?過学習をグラフで見える化 もあわせてご覧ください。本記事では学習曲線の描き方と、グラフの読み方に絞って解説します。
📘 この記事でわかること
- Kerasで学習曲線を描く最小コード
- 過学習・未学習・正常学習をグラフで見分ける方法
- 学習曲線から「エポック数をいくつにすべきか」を判断する方法
- 複数モデルの学習曲線を重ねて比較する方法
学習曲線とは
Kerasの model.fit() は history オブジェクトを返します。この中に、エポックごとの loss・val_loss・accuracy・val_accuracy が記録されています。これをmatplotlibでグラフ化したものが学習曲線です。
| キー | 意味 |
|---|---|
loss |
訓練データに対する損失 |
val_loss |
検証データに対する損失 |
accuracy |
訓練データに対する精度 |
val_accuracy |
検証データに対する精度 |
train と val の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.2
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が上昇しはじめる様子が確認できます。「数値では気づけない過学習の兆候をグラフが先に教えてくれる」——これが学習曲線を可視化する最大のメリットです。
関連記事もあわせてどうぞ:
- エポック数の目安 → 【Keras入門】エポック数はいくつが最適?過学習をグラフで見える化
- Dropout(過学習対策)の比較 → Dropoutの割合(0.0 vs 0.2 vs 0.5)を変えると過学習はどう変わる?【Keras×CIFAR-10実験】
- BatchNormalization vs Dropout → BatchNormalizationはDropoutと 併用すべきか?Keras×MNISTで4パターン比較
- ReLU vs GELUの比較実験 → ReLUとGELUを比較してみた:Activation関数で精度は変わる?【Keras×CIFAR-10実験】
- Dense層のユニット数の比較 → Dense層のユニット数を変えると精度はどう変わる?(32 vs 128 vs 512)【Keras×CIFAR-10実験】






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