Cosine Annealing vs ReduceLROnPlateau、学習率スケジュールどちらが有利?【Keras×CIFAR-10実験】

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

callbacks CIFAR-10 CNN Google Colab Keras Learning Rate 過学習 画像分類

X f B! P L
Cosine Annealing vs ReduceLROnPlateau、学習率スケジュールどちらが有利?【Keras×CIFAR-10実験】 アイキャッチ画像

「学習率スケジューラーって使ったほうがいいの?ReduceLROnPlateauとCosine Annealingはどう違うの?」

今回はGoogle ColabとCIFAR-10を使い、固定学習率 / ReduceLROnPlateau / Cosine Annealingの3パターンを比較しました。学習率の推移グラフとともに、どのスケジューラーが精度・安定性で優れるかを実験で確認します。

📘 この記事でわかること

  • Cosine AnnealingとReduceLROnPlateauの仕組みの違い
  • 学習率スケジューラーを使うと精度・val_lossがどう変わるか
  • どちらのスケジューラーをどんな場面で選ぶべきか

2つのスケジューラーの仕組み

ReduceLROnPlateau

val_lossが一定エポック改善しなくなったとき(patience)に、学習率を一定割合(factor)で自動的に下げるコールバックです。モデルの状態を見ながら学習率を調整する「反応型」のアプローチです。

Cosine Annealing

エポック数に応じてコサイン曲線を描くように学習率を減衰させます。改善の有無に関係なくスケジュールが固定された「計画型」のアプローチです。終盤に向かって滑らかに学習率が下がるため、局所最適解からの脱出と収束の両立が期待できます。

Cosine Annealingの学習率:\(\eta_t = \eta_{\min} + \dfrac{1}{2}(\eta_{\max} - \eta_{\min})\left(1 + \cos\dfrac{t\pi}{T}\right)\)
(\(t\):現在エポック、\(T\):総エポック数、\(\eta_{\max}\):初期学習率、\(\eta_{\min}\):最小学習率)

項目 固定学習率 ReduceLROnPlateau Cosine Annealing
調整タイミングなしval_loss停滞時毎エポック(固定スケジュール)
ハイパーパラメータlr のみfactor / patience / min_lreta_min のみ
終盤の挙動変化なし段階的に下がる滑らかにゼロ付近まで下がる
用途ベースライン汎用・手軽エポック数が決まっているとき

実験コード

使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10です。学習率スケジューラー以外の条件はすべて同一にして、スケジューラーの影響だけを取り出します。

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

# ── 環境準備(最初に一度だけ実行)──────────────────────
!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 51 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 0s (29.9 MB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 122363 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 58.8 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 numpy as np
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

EPOCHS    = 50
INIT_LR   = 1e-3

# ── モデル構築(スケジューラー以外は固定)──────────────
def build_model(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(0.2),
        keras.layers.Dense(10, activation='softmax'),
    ], name=name)

# ── 学習率を記録するカスタムコールバック ────────────────
class LRLogger(keras.callbacks.Callback):
    def __init__(self):
        super().__init__()
        self.lr_history = []

    def on_epoch_end(self, epoch, logs=None):
        lr = self.model.optimizer.learning_rate
        # tf.Variable / Schedule どちらにも対応
        if hasattr(lr, 'numpy'):
            lr = float(lr.numpy())
        else:
            lr = float(keras.backend.get_value(lr))
        self.lr_history.append(lr)
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 3s 0us/step

3パターンの学習実行

results = {}

# ── A:固定学習率 ──────────────────────────────────────
print("\n=== A:固定学習率 ===")
model_a = build_model('A_fixed_lr')
model_a.compile(optimizer=keras.optimizers.Adam(INIT_LR),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
lr_logger_a = LRLogger()
t0 = time.time()
hist_a = model_a.fit(x_train, y_train, epochs=EPOCHS, batch_size=64,
                     validation_split=0.2, callbacks=[lr_logger_a], verbose=1)
results['A_fixed'] = {
    'history': hist_a, 'time': time.time() - t0,
    'score': model_a.evaluate(x_test, y_test, verbose=0),
    'lr_log': lr_logger_a.lr_history
}

# ── B:ReduceLROnPlateau ───────────────────────────────
print("\n=== B:ReduceLROnPlateau ===")
model_b = build_model('B_reducelr')
model_b.compile(optimizer=keras.optimizers.Adam(INIT_LR),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
reduce_lr = keras.callbacks.ReduceLROnPlateau(
    monitor='val_loss', factor=0.5, patience=3, min_lr=1e-6, verbose=1)
lr_logger_b = LRLogger()
t0 = time.time()
hist_b = model_b.fit(x_train, y_train, epochs=EPOCHS, batch_size=64,
                     validation_split=0.2,
                     callbacks=[reduce_lr, lr_logger_b], verbose=1)
results['B_reducelr'] = {
    'history': hist_b, 'time': time.time() - t0,
    'score': model_b.evaluate(x_test, y_test, verbose=0),
    'lr_log': lr_logger_b.lr_history
}

# ── C:Cosine Annealing ────────────────────────────────
print("\n=== C:Cosine Annealing ===")
model_c = build_model('C_cosine')

def cosine_schedule(epoch):
    eta_min, eta_max = 1e-6, INIT_LR
    return eta_min + 0.5 * (eta_max - eta_min) * (1 + np.cos(np.pi * epoch / EPOCHS))

model_c.compile(optimizer=keras.optimizers.Adam(INIT_LR),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
cosine_cb  = keras.callbacks.LearningRateScheduler(cosine_schedule, verbose=0)
lr_logger_c = LRLogger()
t0 = time.time()
hist_c = model_c.fit(x_train, y_train, epochs=EPOCHS, batch_size=64,
                     validation_split=0.2,
                     callbacks=[cosine_cb, lr_logger_c], verbose=1)
results['C_cosine'] = {
    'history': hist_c, 'time': time.time() - t0,
    'score': model_c.evaluate(x_test, y_test, verbose=0),
    'lr_log': lr_logger_c.lr_history
}
実行結果をクリックして内容を開く
=== A:固定学習率 ===
Epoch 1/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 9ms/step - accuracy: 0.2640 - loss: 1.9268 - val_accuracy: 0.3451 - val_loss: 1.7647
Epoch 2/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3730 - loss: 1.6751 - val_accuracy: 0.4069 - val_loss: 1.6044
Epoch 3/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4268 - loss: 1.5580 - val_accuracy: 0.4699 - val_loss: 1.4842
Epoch 4/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4644 - loss: 1.4571 - val_accuracy: 0.4865 - val_loss: 1.3967
Epoch 5/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4911 - loss: 1.3910 - val_accuracy: 0.5143 - val_loss: 1.3296
Epoch 6/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.5107 - loss: 1.3436 - val_accuracy: 0.5350 - val_loss: 1.2793
Epoch 7/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 10ms/step - accuracy: 0.5270 - loss: 1.3004 - val_accuracy: 0.5429 - val_loss: 1.2479
Epoch 8/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 9s 8ms/step - accuracy: 0.5404 - loss: 1.2654 - val_accuracy: 0.5457 - val_loss: 1.2552
Epoch 9/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5508 - loss: 1.2371 - val_accuracy: 0.5706 - val_loss: 1.1819
Epoch 10/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5598 - loss: 1.2068 - val_accuracy: 0.5762 - val_loss: 1.1690
Epoch 11/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5743 - loss: 1.1764 - val_accuracy: 0.5715 - val_loss: 1.1860
Epoch 12/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5832 - loss: 1.1555 - val_accuracy: 0.5929 - val_loss: 1.1281
Epoch 13/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5894 - loss: 1.1337 - val_accuracy: 0.6051 - val_loss: 1.1029
Epoch 14/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5981 - loss: 1.1127 - val_accuracy: 0.6041 - val_loss: 1.1012
Epoch 15/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6100 - loss: 1.0900 - val_accuracy: 0.6105 - val_loss: 1.0672
Epoch 16/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6166 - loss: 1.0660 - val_accuracy: 0.6131 - val_loss: 1.0604
Epoch 17/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6210 - loss: 1.0535 - val_accuracy: 0.6155 - val_loss: 1.0618
Epoch 18/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6306 - loss: 1.0339 - val_accuracy: 0.6378 - val_loss: 1.0141
Epoch 19/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6354 - loss: 1.0179 - val_accuracy: 0.6468 - val_loss: 0.9904
Epoch 20/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6398 - loss: 1.0055 - val_accuracy: 0.6449 - val_loss: 0.9858
Epoch 21/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6444 - loss: 0.9948 - val_accuracy: 0.6265 - val_loss: 1.0479
Epoch 22/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6492 - loss: 0.9744 - val_accuracy: 0.6490 - val_loss: 0.9751
Epoch 23/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6583 - loss: 0.9582 - val_accuracy: 0.6638 - val_loss: 0.9380
Epoch 24/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6607 - loss: 0.9539 - val_accuracy: 0.6518 - val_loss: 0.9656
Epoch 25/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6640 - loss: 0.9404 - val_accuracy: 0.6512 - val_loss: 0.9610
Epoch 26/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6702 - loss: 0.9226 - val_accuracy: 0.6691 - val_loss: 0.9148
Epoch 27/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6745 - loss: 0.9120 - val_accuracy: 0.6787 - val_loss: 0.8989
Epoch 28/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6781 - loss: 0.9008 - val_accuracy: 0.6765 - val_loss: 0.9018
Epoch 29/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6805 - loss: 0.8882 - val_accuracy: 0.6768 - val_loss: 0.9047
Epoch 30/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6863 - loss: 0.8796 - val_accuracy: 0.6725 - val_loss: 0.9172
Epoch 31/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6874 - loss: 0.8702 - val_accuracy: 0.6735 - val_loss: 0.8995
Epoch 32/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6919 - loss: 0.8614 - val_accuracy: 0.6670 - val_loss: 0.9175
Epoch 33/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 7ms/step - accuracy: 0.6967 - loss: 0.8467 - val_accuracy: 0.6761 - val_loss: 0.9009
Epoch 34/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6996 - loss: 0.8411 - val_accuracy: 0.6806 - val_loss: 0.8805
Epoch 35/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7032 - loss: 0.8304 - val_accuracy: 0.6878 - val_loss: 0.8787
Epoch 36/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7039 - loss: 0.8252 - val_accuracy: 0.6922 - val_loss: 0.8691
Epoch 37/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7085 - loss: 0.8155 - val_accuracy: 0.6931 - val_loss: 0.8648
Epoch 38/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7139 - loss: 0.8038 - val_accuracy: 0.6921 - val_loss: 0.8669
Epoch 39/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7157 - loss: 0.8026 - val_accuracy: 0.6947 - val_loss: 0.8449
Epoch 40/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7181 - loss: 0.7922 - val_accuracy: 0.7004 - val_loss: 0.8484
Epoch 41/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7172 - loss: 0.7860 - val_accuracy: 0.6866 - val_loss: 0.8827
Epoch 42/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7234 - loss: 0.7769 - val_accuracy: 0.6935 - val_loss: 0.8493
Epoch 43/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7234 - loss: 0.7739 - val_accuracy: 0.7031 - val_loss: 0.8378
Epoch 44/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7269 - loss: 0.7675 - val_accuracy: 0.6939 - val_loss: 0.8562
Epoch 45/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7293 - loss: 0.7612 - val_accuracy: 0.7061 - val_loss: 0.8329
Epoch 46/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7332 - loss: 0.7523 - val_accuracy: 0.7032 - val_loss: 0.8397
Epoch 47/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7324 - loss: 0.7468 - val_accuracy: 0.6974 - val_loss: 0.8370
Epoch 48/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7359 - loss: 0.7388 - val_accuracy: 0.7103 - val_loss: 0.8302
Epoch 49/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7362 - loss: 0.7334 - val_accuracy: 0.7056 - val_loss: 0.8264
Epoch 50/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7404 - loss: 0.7258 - val_accuracy: 0.6957 - val_loss: 0.8570

=== B:ReduceLROnPlateau ===
Epoch 1/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 9ms/step - accuracy: 0.2650 - loss: 1.9224 - val_accuracy: 0.3555 - val_loss: 1.7098 - learning_rate: 0.0010
Epoch 2/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3602 - loss: 1.6970 - val_accuracy: 0.4107 - val_loss: 1.5971 - learning_rate: 0.0010
Epoch 3/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4228 - loss: 1.5704 - val_accuracy: 0.4387 - val_loss: 1.5262 - learning_rate: 0.0010
Epoch 4/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4572 - loss: 1.4769 - val_accuracy: 0.4625 - val_loss: 1.4839 - learning_rate: 0.0010
Epoch 5/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4823 - loss: 1.4151 - val_accuracy: 0.4769 - val_loss: 1.4169 - learning_rate: 0.0010
Epoch 6/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5012 - loss: 1.3622 - val_accuracy: 0.5151 - val_loss: 1.3353 - learning_rate: 0.0010
Epoch 7/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5149 - loss: 1.3270 - val_accuracy: 0.5272 - val_loss: 1.2934 - learning_rate: 0.0010
Epoch 8/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5284 - loss: 1.2921 - val_accuracy: 0.5362 - val_loss: 1.2697 - learning_rate: 0.0010
Epoch 9/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5415 - loss: 1.2576 - val_accuracy: 0.5574 - val_loss: 1.2120 - learning_rate: 0.0010
Epoch 10/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5529 - loss: 1.2309 - val_accuracy: 0.5416 - val_loss: 1.2351 - learning_rate: 0.0010
Epoch 11/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5634 - loss: 1.2011 - val_accuracy: 0.5672 - val_loss: 1.1699 - learning_rate: 0.0010
Epoch 12/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5740 - loss: 1.1746 - val_accuracy: 0.5770 - val_loss: 1.1610 - learning_rate: 0.0010
Epoch 13/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5812 - loss: 1.1569 - val_accuracy: 0.5943 - val_loss: 1.1103 - learning_rate: 0.0010
Epoch 14/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5896 - loss: 1.1299 - val_accuracy: 0.6017 - val_loss: 1.0928 - learning_rate: 0.0010
Epoch 15/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5982 - loss: 1.1104 - val_accuracy: 0.6108 - val_loss: 1.0726 - learning_rate: 0.0010
Epoch 16/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6047 - loss: 1.0905 - val_accuracy: 0.5952 - val_loss: 1.1036 - learning_rate: 0.0010
Epoch 17/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6116 - loss: 1.0745 - val_accuracy: 0.6024 - val_loss: 1.0779 - learning_rate: 0.0010
Epoch 18/50
619/625 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.6175 - loss: 1.0546
Epoch 18: ReduceLROnPlateau reducing learning rate to 0.0005000000237487257.
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6206 - loss: 1.0534 - val_accuracy: 0.6138 - val_loss: 1.0728 - learning_rate: 0.0010
Epoch 19/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6380 - loss: 1.0080 - val_accuracy: 0.6432 - val_loss: 0.9952 - learning_rate: 5.0000e-04
Epoch 20/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6414 - loss: 0.9929 - val_accuracy: 0.6391 - val_loss: 1.0007 - learning_rate: 5.0000e-04
Epoch 21/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6442 - loss: 0.9879 - val_accuracy: 0.6432 - val_loss: 0.9838 - learning_rate: 5.0000e-04
Epoch 22/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6500 - loss: 0.9753 - val_accuracy: 0.6429 - val_loss: 0.9792 - learning_rate: 5.0000e-04
Epoch 23/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6525 - loss: 0.9671 - val_accuracy: 0.6441 - val_loss: 0.9757 - learning_rate: 5.0000e-04
Epoch 24/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6536 - loss: 0.9592 - val_accuracy: 0.6559 - val_loss: 0.9551 - learning_rate: 5.0000e-04
Epoch 25/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6584 - loss: 0.9520 - val_accuracy: 0.6499 - val_loss: 0.9639 - learning_rate: 5.0000e-04
Epoch 26/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6571 - loss: 0.9458 - val_accuracy: 0.6535 - val_loss: 0.9600 - learning_rate: 5.0000e-04
Epoch 27/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6639 - loss: 0.9390 - val_accuracy: 0.6537 - val_loss: 0.9545 - learning_rate: 5.0000e-04
Epoch 28/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6659 - loss: 0.9299 - val_accuracy: 0.6565 - val_loss: 0.9422 - learning_rate: 5.0000e-04
Epoch 29/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6680 - loss: 0.9269 - val_accuracy: 0.6596 - val_loss: 0.9344 - learning_rate: 5.0000e-04
Epoch 30/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6708 - loss: 0.9185 - val_accuracy: 0.6613 - val_loss: 0.9317 - learning_rate: 5.0000e-04
Epoch 31/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6706 - loss: 0.9150 - val_accuracy: 0.6615 - val_loss: 0.9351 - learning_rate: 5.0000e-04
Epoch 32/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6751 - loss: 0.9084 - val_accuracy: 0.6705 - val_loss: 0.9223 - learning_rate: 5.0000e-04
Epoch 33/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6772 - loss: 0.8984 - val_accuracy: 0.6666 - val_loss: 0.9269 - learning_rate: 5.0000e-04
Epoch 34/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6798 - loss: 0.8941 - val_accuracy: 0.6724 - val_loss: 0.9194 - learning_rate: 5.0000e-04
Epoch 35/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6801 - loss: 0.8875 - val_accuracy: 0.6708 - val_loss: 0.9140 - learning_rate: 5.0000e-04
Epoch 36/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6841 - loss: 0.8837 - val_accuracy: 0.6755 - val_loss: 0.9002 - learning_rate: 5.0000e-04
Epoch 37/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6834 - loss: 0.8830 - val_accuracy: 0.6755 - val_loss: 0.8994 - learning_rate: 5.0000e-04
Epoch 38/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6877 - loss: 0.8716 - val_accuracy: 0.6746 - val_loss: 0.9025 - learning_rate: 5.0000e-04
Epoch 39/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6893 - loss: 0.8658 - val_accuracy: 0.6723 - val_loss: 0.9071 - learning_rate: 5.0000e-04
Epoch 40/50
618/625 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.6955 - loss: 0.8542
Epoch 40: ReduceLROnPlateau reducing learning rate to 0.0002500000118743628.
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6936 - loss: 0.8592 - val_accuracy: 0.6721 - val_loss: 0.9086 - learning_rate: 5.0000e-04
Epoch 41/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7024 - loss: 0.8331 - val_accuracy: 0.6658 - val_loss: 0.9196 - learning_rate: 2.5000e-04
Epoch 42/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7024 - loss: 0.8295 - val_accuracy: 0.6851 - val_loss: 0.8785 - learning_rate: 2.5000e-04
Epoch 43/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7033 - loss: 0.8303 - val_accuracy: 0.6875 - val_loss: 0.8745 - learning_rate: 2.5000e-04
Epoch 44/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7052 - loss: 0.8210 - val_accuracy: 0.6816 - val_loss: 0.8817 - learning_rate: 2.5000e-04
Epoch 45/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.7074 - loss: 0.8197 - val_accuracy: 0.6869 - val_loss: 0.8736 - learning_rate: 2.5000e-04
Epoch 46/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7071 - loss: 0.8194 - val_accuracy: 0.6760 - val_loss: 0.8913 - learning_rate: 2.5000e-04
Epoch 47/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7090 - loss: 0.8135 - val_accuracy: 0.6907 - val_loss: 0.8733 - learning_rate: 2.5000e-04
Epoch 48/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7124 - loss: 0.8106 - val_accuracy: 0.6903 - val_loss: 0.8611 - learning_rate: 2.5000e-04
Epoch 49/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7121 - loss: 0.8076 - val_accuracy: 0.6850 - val_loss: 0.8759 - learning_rate: 2.5000e-04
Epoch 50/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7103 - loss: 0.8084 - val_accuracy: 0.6917 - val_loss: 0.8574 - learning_rate: 2.5000e-04

=== C:Cosine Annealing ===
Epoch 1/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 8ms/step - accuracy: 0.2737 - loss: 1.9137 - val_accuracy: 0.3470 - val_loss: 1.7170 - learning_rate: 0.0010
Epoch 2/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.3782 - loss: 1.6600 - val_accuracy: 0.4212 - val_loss: 1.5543 - learning_rate: 9.9901e-04
Epoch 3/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4396 - loss: 1.5275 - val_accuracy: 0.4762 - val_loss: 1.4590 - learning_rate: 9.9606e-04
Epoch 4/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4734 - loss: 1.4460 - val_accuracy: 0.5032 - val_loss: 1.3666 - learning_rate: 9.9115e-04
Epoch 5/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4979 - loss: 1.3788 - val_accuracy: 0.4964 - val_loss: 1.3645 - learning_rate: 9.8431e-04
Epoch 6/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5168 - loss: 1.3291 - val_accuracy: 0.5367 - val_loss: 1.2738 - learning_rate: 9.7555e-04
Epoch 7/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5308 - loss: 1.2928 - val_accuracy: 0.5044 - val_loss: 1.3458 - learning_rate: 9.6492e-04
Epoch 8/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5412 - loss: 1.2616 - val_accuracy: 0.5472 - val_loss: 1.2300 - learning_rate: 9.5246e-04
Epoch 9/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5539 - loss: 1.2261 - val_accuracy: 0.5745 - val_loss: 1.1767 - learning_rate: 9.3822e-04
Epoch 10/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5650 - loss: 1.2030 - val_accuracy: 0.5606 - val_loss: 1.1788 - learning_rate: 9.2224e-04
Epoch 11/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5736 - loss: 1.1767 - val_accuracy: 0.5812 - val_loss: 1.1538 - learning_rate: 9.0460e-04
Epoch 12/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5851 - loss: 1.1509 - val_accuracy: 0.5890 - val_loss: 1.1400 - learning_rate: 8.8537e-04
Epoch 13/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5915 - loss: 1.1379 - val_accuracy: 0.6007 - val_loss: 1.0998 - learning_rate: 8.6462e-04
Epoch 14/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5963 - loss: 1.1143 - val_accuracy: 0.6139 - val_loss: 1.0755 - learning_rate: 8.4243e-04
Epoch 15/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6058 - loss: 1.0945 - val_accuracy: 0.6106 - val_loss: 1.0830 - learning_rate: 8.1889e-04
Epoch 16/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6098 - loss: 1.0808 - val_accuracy: 0.6154 - val_loss: 1.0667 - learning_rate: 7.9410e-04
Epoch 17/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6177 - loss: 1.0582 - val_accuracy: 0.6267 - val_loss: 1.0335 - learning_rate: 7.6815e-04
Epoch 18/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6222 - loss: 1.0432 - val_accuracy: 0.6336 - val_loss: 1.0243 - learning_rate: 7.4114e-04
Epoch 19/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6302 - loss: 1.0278 - val_accuracy: 0.6326 - val_loss: 1.0234 - learning_rate: 7.1318e-04
Epoch 20/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6368 - loss: 1.0097 - val_accuracy: 0.6325 - val_loss: 1.0200 - learning_rate: 6.8438e-04
Epoch 21/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6419 - loss: 0.9949 - val_accuracy: 0.6243 - val_loss: 1.0304 - learning_rate: 6.5485e-04
Epoch 22/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6458 - loss: 0.9894 - val_accuracy: 0.6489 - val_loss: 0.9791 - learning_rate: 6.2472e-04
Epoch 23/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6501 - loss: 0.9746 - val_accuracy: 0.6493 - val_loss: 0.9778 - learning_rate: 5.9410e-04
Epoch 24/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6530 - loss: 0.9626 - val_accuracy: 0.6442 - val_loss: 0.9810 - learning_rate: 5.6310e-04
Epoch 25/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6578 - loss: 0.9533 - val_accuracy: 0.6497 - val_loss: 0.9750 - learning_rate: 5.3186e-04
Epoch 26/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6621 - loss: 0.9394 - val_accuracy: 0.6638 - val_loss: 0.9414 - learning_rate: 5.0050e-04
Epoch 27/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6651 - loss: 0.9298 - val_accuracy: 0.6615 - val_loss: 0.9375 - learning_rate: 4.6914e-04
Epoch 28/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6686 - loss: 0.9197 - val_accuracy: 0.6600 - val_loss: 0.9366 - learning_rate: 4.3790e-04
Epoch 29/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6731 - loss: 0.9105 - val_accuracy: 0.6682 - val_loss: 0.9345 - learning_rate: 4.0690e-04
Epoch 30/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6787 - loss: 0.9014 - val_accuracy: 0.6627 - val_loss: 0.9439 - learning_rate: 3.7628e-04
Epoch 31/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6786 - loss: 0.8939 - val_accuracy: 0.6648 - val_loss: 0.9259 - learning_rate: 3.4615e-04
Epoch 32/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6827 - loss: 0.8862 - val_accuracy: 0.6699 - val_loss: 0.9227 - learning_rate: 3.1662e-04
Epoch 33/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6839 - loss: 0.8793 - val_accuracy: 0.6729 - val_loss: 0.9170 - learning_rate: 2.8782e-04
Epoch 34/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6883 - loss: 0.8722 - val_accuracy: 0.6758 - val_loss: 0.8988 - learning_rate: 2.5986e-04
Epoch 35/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6926 - loss: 0.8636 - val_accuracy: 0.6592 - val_loss: 0.9413 - learning_rate: 2.3285e-04
Epoch 36/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6938 - loss: 0.8586 - val_accuracy: 0.6776 - val_loss: 0.8941 - learning_rate: 2.0690e-04
Epoch 37/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6967 - loss: 0.8534 - val_accuracy: 0.6779 - val_loss: 0.8934 - learning_rate: 1.8211e-04
Epoch 38/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6959 - loss: 0.8505 - val_accuracy: 0.6813 - val_loss: 0.8941 - learning_rate: 1.5857e-04
Epoch 39/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6970 - loss: 0.8442 - val_accuracy: 0.6851 - val_loss: 0.8788 - learning_rate: 1.3638e-04
Epoch 40/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7005 - loss: 0.8392 - val_accuracy: 0.6836 - val_loss: 0.8853 - learning_rate: 1.1563e-04
Epoch 41/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6996 - loss: 0.8377 - val_accuracy: 0.6843 - val_loss: 0.8774 - learning_rate: 9.6396e-05
Epoch 42/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7040 - loss: 0.8294 - val_accuracy: 0.6855 - val_loss: 0.8762 - learning_rate: 7.8758e-05
Epoch 43/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7066 - loss: 0.8294 - val_accuracy: 0.6893 - val_loss: 0.8724 - learning_rate: 6.2785e-05
Epoch 44/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7060 - loss: 0.8259 - val_accuracy: 0.6899 - val_loss: 0.8722 - learning_rate: 4.8539e-05
Epoch 45/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7063 - loss: 0.8232 - val_accuracy: 0.6878 - val_loss: 0.8709 - learning_rate: 3.6077e-05
Epoch 46/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7063 - loss: 0.8211 - val_accuracy: 0.6858 - val_loss: 0.8710 - learning_rate: 2.5447e-05
Epoch 47/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7055 - loss: 0.8218 - val_accuracy: 0.6862 - val_loss: 0.8697 - learning_rate: 1.6693e-05
Epoch 48/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7075 - loss: 0.8195 - val_accuracy: 0.6901 - val_loss: 0.8675 - learning_rate: 9.8475e-06
Epoch 49/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7089 - loss: 0.8172 - val_accuracy: 0.6900 - val_loss: 0.8680 - learning_rate: 4.9387e-06
Epoch 50/50
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7074 - loss: 0.8195 - val_accuracy: 0.6910 - val_loss: 0.8675 - learning_rate: 1.9856e-06

⚠️ ハマりポイント:patienceが大きすぎるとReduceLROnPlateauが一度も発動しない

patience=5 は「5エポック連続でval_lossが改善しなければ学習率を下げる」という設定です。今回のように50エポック・順調に学習が進むモデルでは一度も発動しないまま終了することがあります(実際に上記の実行ログでも学習率がずっと0.0010のままでした)。これでは「固定学習率と同じ実験を2回行った」ことになり、スケジューラーの比較になりません。50エポック程度の実験では patience=3 を目安にし、学習率推移グラフで必ず発動を確認してください。

グラフ+サマリー

# ── val_accuracy / val_loss 比較 ───────────────────────
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
labels_map = {'A_fixed': '固定学習率', 'B_reducelr': 'ReduceLROnPlateau', 'C_cosine': 'Cosine Annealing'}
for key, res in results.items():
    h = res['history']
    axes[0].plot(h.history['val_accuracy'], label=labels_map[key])
    axes[1].plot(h.history['val_loss'],     label=labels_map[key])
axes[0].set_title('val_accuracy の比較(全50エポック)')
axes[1].set_title('val_loss の比較(全50エポック)')
for ax in axes:
    ax.set_xlabel('Epoch'); ax.legend(); ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lr_schedule_comparison.png', dpi=150)
plt.show()

# ── 学習率推移グラフ ───────────────────────────────────
fig2, ax2 = plt.subplots(figsize=(10, 4))
for key, res in results.items():
    ax2.plot(res['lr_log'], label=labels_map[key])
ax2.set_title('学習率の推移(全50エポック)')
ax2.set_xlabel('Epoch'); ax2.set_ylabel('Learning Rate')
ax2.legend(); ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lr_schedule_lr_curve.png', dpi=150)
plt.show()

# ── サマリー ──────────────────────────────────────────
print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>20} | {'Val Acc':>8} | {'Test Acc':>9} | {'Time(s)':>8}")
print("-" * 56)
for key, res in results.items():
    val_acc  = res['history'].history['val_accuracy'][-1]
    test_acc = res['score'][1]
    t        = res['time']
    print(f"{labels_map[key]:>20} | {val_acc:>8.4f} | {test_acc:>9.4f} | {t:>8.1f}")
print("-" * 56)

最終結果サマリー

===== 最終結果サマリー =====
             Pattern |  Val Acc |  Test Acc |  Time(s)
--------------------------------------------------------
               固定学習率 |   0.6957 |    0.6960 |    213.4
   ReduceLROnPlateau |   0.6917 |    0.6948 |    203.6
    Cosine Annealing |   0.6910 |    0.6876 |    204.2
--------------------------------------------------------

実験結果

精度グラフ

精度グラフ

損失グラフ

損失グラフ

学習率推移グラフ

学習率推移グラフ
パターン 最終 val_accuracy 最終 test_accuracy 学習時間
A:固定学習率 ⭐ 最高精度0.69570.6960213.4秒
B:ReduceLROnPlateau0.69170.6948203.6秒
C:Cosine Annealing0.69100.6876204.2秒

※3回の実験すべてで3パターンの差は0.01以内。実行ごとに順位が変わる誤差範囲内です(後述)。


考察

3回の実験すべてで差は0.01以内——スケジューラーの優劣はつかない

3回の実験を通じて、固定学習率・ReduceLROnPlateau・Cosine Annealingの test_accuracy の差は常に0.01以内でした。順位も実行のたびに入れ替わり、「どれが最強」という結論は出ません。これは「スケジューラーを使えば必ず改善する」という思い込みを崩す重要な結果です。Adam は元々パラメータごとに学習率を適応調整する仕組みを持っており、外部スケジューラーの恩恵が上乗せされにくいという特性が背景にあります。

ReduceLROnPlateauはepoch18と40の2回発動したが精度は横ばい

今回(patience=3)はepoch18で 1e-3→5e-4、epoch40で 5e-4→2.5e-4 と2回発動しました。発動直後(epoch19〜)に val_loss が一時的に改善しているものの、最終的な精度は固定学習率とほぼ同水準に留まりました。学習率を下げること自体は機能していますが、今回のモデル・データ規模では劇的な効果は出ていません。エポックが多いほど・モデルが深いほど効果が出やすくなります。

Cosine Annealingは終盤に学習率がほぼ0になり改善が止まる

ログを見るとepoch48〜50で学習率が 9.8e-6 → 4.9e-6 → 2.0e-6 まで落ちており、実質的な学習が停止しています。この「終盤に学習できなくなる」問題が固定学習率との差を生む主因です。eta_min=1e-6 の設定では下がりすぎるため、eta_min=1e-4 程度に上げるか、Cosine Annealing with Warm Restarts(SGDR)を使うと改善が期待できます。

AdamとCosine Annealingの相性問題

Cosine AnnealingはもともとSGDと組み合わせて使われることが多く、ResNetの原著論文もSGD+Cosine Annealingを採用しています。Adamのような適応型Optimizerに外部から学習率スケジュールを強制すると、Adamの内部モーメント(m/v)の調整と競合しやすくなります。AdamでCosine Annealingを使う場合は、eta_min・ウォームアップの有無を慎重に設定する必要があります。

スケジューラーの効果が出やすい条件

今回は「比較的浅いCNN・50エポック・Adam」という条件でしたが、スケジューラーの恩恵が出やすいのはエポック数が多い(100以上)・モデルが深い・SGDを使うときです。また ReduceLROnPlateau は EarlyStopping と組み合わせてエポック数を固定しない実験スタイルと相性がよく、Cosine Annealing は初期学習率を大きめ(3e-3〜5e-3)に設定してウォームアップを加えると本来の効果が出やすくなります。


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

まとめ

  • 固定学習率とReduceLROnPlateauは誤差範囲内の差。実行のたびに順位が入れ替わるレベル
  • 今回の実験ではReduceLROnPlateauが一度も発動しなかった(patience=5が緩すぎた)。50エポック程度の実験では patience=3 が目安
  • Cosine Annealingが大きく遅れた原因は学習初期から学習率が低下し始めること。ウォームアップや eta_min の調整が必要
  • AdamはすでにOptimizerレベルで学習率を適応調整しており、外部スケジューラーとの干渉が起きやすい
  • Cosine Annealingを活かしたい場合はSGD + Cosine Annealingの組み合わせ、または初期学習率を大きくしてウォームアップを加える構成が推奨
  • 学習率推移グラフを必ず確認して、スケジューラーが意図通りのタイミングで発動しているか検証する習慣をつける