Kerasで学習率スケジューラ5種を徹底比較!CosineDecayの再評価も

2025年7月3日木曜日

Google Colab Keras LearningRate 学習率

X f B! P L
アイキャッチ画像 Kerasで学習率スケジューラ5種を徹底比較!CosineDecayの再評価も

はじめに

学習率(Learning Rate)は、ディープラーニングの学習において最も重要なハイパーパラメータのひとつです。
本記事では、Kerasで利用可能な学習率スケジューラ「StepDecay」「ExponentialDecay」「ReduceLROnPlateau」「CosineDecay」「OneCycleScheduler」を使い、MNISTデータセットを用いて性能を比較・検証していきます。

学習率スケジューラとは

学習率スケジューラ(Learning Rate Scheduler)とは、モデルの訓練中に学習率を動的に調整するための仕組みです。
訓練が進むにつれて学習率を変化させることで、より効率的な最適化や過学習の抑制が期待できます。
以下に代表的なスケジューラの特徴と用途をまとめました。

スケジューラ名 特徴 適用例
StepDecay 一定のエポックごとに学習率を段階的に減少させる 古典的な画像分類モデルなどに適用
ExponentialDecay 指数関数的に学習率を徐々に減少させる 滑らかに減衰させたい学習タスクに有効
ReduceLROnPlateau 検証精度や損失が改善しない場合に学習率を減少 過学習を避けつつ柔軟に学習を進めたいとき
CosineDecay Cos波形に沿って学習率を滑らかに減少 最終段階での性能向上を目指す高精度モデル
OneCycleScheduler 最初に学習率を増加させ、その後急激に減少 短時間で高精度を狙う高速学習に適用

データ準備

MNISTの手書き数字画像データセットを使用し、28x28のグレースケール画像を0〜1に正規化して扱います。

import tensorflow as tf
 
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train[..., None] / 255.0
x_test = x_test[..., None] / 255.0

モデル構築関数

比較に使用するのは、畳み込み層を1層だけ持つシンプルなCNNモデルです。

from tensorflow.keras import layers, models

def build_model():
    model = models.Sequential([
        layers.Input(shape=(28,28,1)),
        layers.Conv2D(32, (3,3), activation='relu'),
        layers.MaxPooling2D(),
        layers.Flatten(),
        layers.Dense(64, activation='relu'),
        layers.Dense(10, activation='softmax')
    ])
    return model

スケジューラの定義

各学習率スケジューラを定義します。

from tensorflow.keras import callbacks, optimizers

# StepDecay
def step_decay(epoch):
    initial_lr = 0.01
    drop = 0.5
    epochs_drop = 2
    return initial_lr * (drop ** (epoch // epochs_drop))
lr_scheduler_step = callbacks.LearningRateScheduler(step_decay)

# ExponentialDecay
lr_schedule_exp = optimizers.schedules.ExponentialDecay(
    initial_learning_rate=0.01,
    decay_steps=1000,
    decay_rate=0.9
)
optimizer_exp = optimizers.Adam(learning_rate=lr_schedule_exp)

# ReduceLROnPlateau
lr_scheduler_plateau = callbacks.ReduceLROnPlateau(
    monitor='val_loss',
    factor=0.5,
    patience=2,
    min_lr=1e-5
)

# CosineDecay
lr_schedule_cos = optimizers.schedules.CosineDecay(
    initial_learning_rate=0.001,
    decay_steps=1000
)
optimizer_cos = optimizers.Adam(learning_rate=lr_schedule_cos)

# OneCycleScheduler Callback
class OneCycleScheduler(callbacks.Callback):
    def __init__(self, max_lr, steps_per_epoch, epochs):
        self.max_lr = max_lr
        self.total_steps = steps_per_epoch * epochs
        self.step = 0

    def on_batch_begin(self, batch, logs=None):
        self.step += 1
        pct = self.step / self.total_steps
        lr = self.max_lr * (1 - abs(2 * pct - 1))
        if hasattr(self.model.optimizer.learning_rate, 'assign'):
            self.model.optimizer.learning_rate.assign(lr)

ReduceLROnPlateau の動作条件の補足

ReduceLROnPlateau は、val_loss が一定期間改善しない場合に学習率を自動で減らします。
patience=2 の場合、2エポック連続で改善がないと factor 倍されます。
このように“反応的”なスケジューラであり、他のスケジューラと挙動が異なる点に注意が必要です。

共通訓練関数

モデルを構築し、指定されたオプティマイザとコールバックで訓練を実施する関数です。

def train_model(name, optimizer, callback_list=None, batch_size=32):
    print(f"\nTraining with {name}")
    model = build_model()
    model.compile(optimizer=optimizer, loss='sparse_categorical_crossentropy', metrics=['accuracy'])
    history = model.fit(
        x_train, y_train,
        epochs=10,
        batch_size=batch_size,
        validation_split=0.1,
        callbacks=callback_list or [],
        verbose=1
    )
    return history

訓練の実行

上で定義した共通訓練関数を使い、各スケジューラを適用して10エポックの学習を行います。

histories = {}

# 1. StepDecay
histories['StepDecay'] = train_model('StepDecay', 'adam', [lr_scheduler_step])

# 2. ExponentialDecay
histories['ExponentialDecay'] = train_model('ExponentialDecay', optimizer_exp)

# 3. ReduceLROnPlateau
histories['ReduceLROnPlateau'] = train_model('ReduceLROnPlateau', 'adam', [lr_scheduler_plateau])

# 4. CosineDecay
histories['CosineDecay'] = train_model('CosineDecay', optimizer_cos)

# 5. OneCycleScheduler
batch_size = 32
steps_per_epoch = len(x_train) // batch_size
optimizer_onecycle = optimizers.Adam(learning_rate=1e-3)
one_cycle_callback = OneCycleScheduler(max_lr=0.001, steps_per_epoch=steps_per_epoch, epochs=10)
histories['OneCycle'] = train_model('OneCycle', optimizer_onecycle, [one_cycle_callback], batch_size)

訓練結果

Training with StepDecay
Epoch 1/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 7s 4ms/step - accuracy: 0.9255 - loss: 0.2431 - val_accuracy: 0.9765 - val_loss: 0.0788 - learning_rate: 0.0100
Epoch 2/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 8s 3ms/step - accuracy: 0.9781 - loss: 0.0725 - val_accuracy: 0.9832 - val_loss: 0.0687 - learning_rate: 0.0100
Epoch 3/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9916 - loss: 0.0272 - val_accuracy: 0.9865 - val_loss: 0.0567 - learning_rate: 0.0050
Epoch 4/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9948 - loss: 0.0155 - val_accuracy: 0.9830 - val_loss: 0.0644 - learning_rate: 0.0050
Epoch 5/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 4s 3ms/step - accuracy: 0.9973 - loss: 0.0076 - val_accuracy: 0.9895 - val_loss: 0.0621 - learning_rate: 0.0025
Epoch 6/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9991 - loss: 0.0036 - val_accuracy: 0.9893 - val_loss: 0.0697 - learning_rate: 0.0025
Epoch 7/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9998 - loss: 7.8469e-04 - val_accuracy: 0.9883 - val_loss: 0.0749 - learning_rate: 0.0012
Epoch 8/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 1.0000 - loss: 3.3885e-04 - val_accuracy: 0.9888 - val_loss: 0.0836 - learning_rate: 0.0012
Epoch 9/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 1.0000 - loss: 1.6123e-04 - val_accuracy: 0.9893 - val_loss: 0.0821 - learning_rate: 6.2500e-04
Epoch 10/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 1.0000 - loss: 5.4834e-05 - val_accuracy: 0.9892 - val_loss: 0.0846 - learning_rate: 6.2500e-04

Training with ExponentialDecay
Epoch 1/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 8s 3ms/step - accuracy: 0.9248 - loss: 0.2425 - val_accuracy: 0.9812 - val_loss: 0.0674
Epoch 2/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 9s 3ms/step - accuracy: 0.9814 - loss: 0.0568 - val_accuracy: 0.9823 - val_loss: 0.0672
Epoch 3/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 4s 3ms/step - accuracy: 0.9896 - loss: 0.0323 - val_accuracy: 0.9857 - val_loss: 0.0526
Epoch 4/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9943 - loss: 0.0186 - val_accuracy: 0.9873 - val_loss: 0.0659
Epoch 5/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9965 - loss: 0.0116 - val_accuracy: 0.9865 - val_loss: 0.0792
Epoch 6/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9974 - loss: 0.0072 - val_accuracy: 0.9870 - val_loss: 0.0825
Epoch 7/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 6s 3ms/step - accuracy: 0.9986 - loss: 0.0055 - val_accuracy: 0.9880 - val_loss: 0.0813
Epoch 8/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 4s 3ms/step - accuracy: 0.9991 - loss: 0.0030 - val_accuracy: 0.9858 - val_loss: 0.1015
Epoch 9/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9994 - loss: 0.0018 - val_accuracy: 0.9875 - val_loss: 0.1003
Epoch 10/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 1.0000 - loss: 1.9330e-04 - val_accuracy: 0.9880 - val_loss: 0.1019

Training with ReduceLROnPlateau
Epoch 1/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 8s 4ms/step - accuracy: 0.8973 - loss: 0.3425 - val_accuracy: 0.9805 - val_loss: 0.0715 - learning_rate: 0.0010
Epoch 2/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9819 - loss: 0.0616 - val_accuracy: 0.9853 - val_loss: 0.0561 - learning_rate: 0.0010
Epoch 3/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9880 - loss: 0.0386 - val_accuracy: 0.9880 - val_loss: 0.0474 - learning_rate: 0.0010
Epoch 4/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9921 - loss: 0.0264 - val_accuracy: 0.9888 - val_loss: 0.0455 - learning_rate: 0.0010
Epoch 5/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9935 - loss: 0.0195 - val_accuracy: 0.9862 - val_loss: 0.0581 - learning_rate: 0.0010
Epoch 6/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 0.9954 - loss: 0.0144 - val_accuracy: 0.9873 - val_loss: 0.0541 - learning_rate: 0.0010
Epoch 7/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9983 - loss: 0.0064 - val_accuracy: 0.9890 - val_loss: 0.0481 - learning_rate: 5.0000e-04
Epoch 8/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9994 - loss: 0.0033 - val_accuracy: 0.9910 - val_loss: 0.0491 - learning_rate: 5.0000e-04
Epoch 9/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 6s 3ms/step - accuracy: 0.9998 - loss: 0.0016 - val_accuracy: 0.9907 - val_loss: 0.0486 - learning_rate: 2.5000e-04
Epoch 10/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 1.0000 - loss: 9.1164e-04 - val_accuracy: 0.9903 - val_loss: 0.0513 - learning_rate: 2.5000e-04

Training with CosineDecay
Epoch 1/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 8s 4ms/step - accuracy: 0.8791 - loss: 0.3991 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 2/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9592 - loss: 0.1416 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 3/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9592 - loss: 0.1440 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 4/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9594 - loss: 0.1445 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 5/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9598 - loss: 0.1415 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 6/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9608 - loss: 0.1421 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 7/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 0.9588 - loss: 0.1424 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 8/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9587 - loss: 0.1444 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 9/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9587 - loss: 0.1443 - val_accuracy: 0.9693 - val_loss: 0.1203
Epoch 10/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9589 - loss: 0.1431 - val_accuracy: 0.9693 - val_loss: 0.1203

Training with OneCycle
Epoch 1/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 8s 4ms/step - accuracy: 0.5683 - loss: 1.4678 - val_accuracy: 0.9500 - val_loss: 0.1873
Epoch 2/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 8s 3ms/step - accuracy: 0.9415 - loss: 0.2016 - val_accuracy: 0.9747 - val_loss: 0.0975
Epoch 3/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 0.9694 - loss: 0.1060 - val_accuracy: 0.9770 - val_loss: 0.0779
Epoch 4/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9804 - loss: 0.0672 - val_accuracy: 0.9832 - val_loss: 0.0598
Epoch 5/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 0.9859 - loss: 0.0466 - val_accuracy: 0.9860 - val_loss: 0.0531
Epoch 6/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 6s 3ms/step - accuracy: 0.9887 - loss: 0.0359 - val_accuracy: 0.9860 - val_loss: 0.0475
Epoch 7/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 0.9924 - loss: 0.0237 - val_accuracy: 0.9882 - val_loss: 0.0492
Epoch 8/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9967 - loss: 0.0124 - val_accuracy: 0.9873 - val_loss: 0.0499
Epoch 9/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 0.9980 - loss: 0.0080 - val_accuracy: 0.9905 - val_loss: 0.0471
Epoch 10/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9994 - loss: 0.0038 - val_accuracy: 0.9898 - val_loss: 0.0520

訓練結果の可視化

各スケジューラの訓練結果を描画して比較します。

import matplotlib.pyplot as plt

# ======== 精度の比較 ========
plt.figure(figsize=(8, 5))
for name, history in histories.items():
    plt.plot(history.history['val_accuracy'], label=name)

plt.title('Validation Accuracy by Learning Rate Scheduler')
plt.xlabel('Epoch')
plt.ylabel('Validation Accuracy')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

# ======== 損失の比較 ========
plt.figure(figsize=(8, 5))
for name, history in histories.items():
    plt.plot(history.history['val_loss'], label=name)

plt.title('Validation Loss by Learning Rate Scheduler')
plt.xlabel('Epoch')
plt.ylabel('Validation Loss')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

可視化グラフ

精度グラフ

精度グラフ

損失グラフ

損失グラフ

学習率スケジューラ比較の結果まとめ

各種スケジューラ(StepDecay, ExponentialDecay, ReduceLROnPlateau, CosineDecay, OneCycle)を用いてMNIST分類タスクを10エポック訓練し、検証精度と損失の推移を比較しました。

検証結果の概要

スケジューラ 最終検証精度 最終検証損失 評価
ReduceLROnPlateau 99.03% 0.0513 精度・損失ともに最も良好
OneCycle 98.98% 0.0520 後半で急成長、安定して高精度
StepDecay 98.92% 0.0846 安定するがやや過学習傾向
ExponentialDecay 98.80% 0.1019 やや早期収束、後半改善が鈍化
CosineDecay 96.93% 0.1203 学習が機能していない可能性

CosineDecayの問題点

  • 全エポックで精度・損失がまったく改善されない。
  • 初期学習率が低すぎ(0.001)decay_stepsが小さすぎ(1000)のため、極端に早く学習率がゼロに近づいた可能性がある。

対策案

tf.keras.optimizers.schedules.CosineDecay(
    initial_learning_rate=0.01,  # ← 初期値を強めに
    decay_steps=16880            # ← 1688(batch/epoch) * 10(epoch)
)

CosineDecayは適切な学習率スケジュールを設計できれば効果的ですが、他と比べてパラメータ設計が難しいため、実験的な調整が必要です。

CosineDecay スケジューラの再評価

前回は initial_learning_rate=0.001, decay_steps=1000 という設定で CosineDecay が正常に機能していませんでしたが、今回はパラメータを適切に調整しました。CosineDecay は初期設定に注意すれば有効な学習率スケジューラであり、特に正則化やEarlyStoppingと併用することで高性能な訓練が期待できます。

訓練結果(抜粋)

Training with CosineDecay
Epoch 1/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 9s 4ms/step - accuracy: 0.9237 - loss: 0.2485 - val_accuracy: 0.9605 - val_loss: 0.1311
Epoch 2/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 7s 3ms/step - accuracy: 0.9796 - loss: 0.0677 - val_accuracy: 0.9805 - val_loss: 0.0895
Epoch 3/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9853 - loss: 0.0453 - val_accuracy: 0.9805 - val_loss: 0.0762
Epoch 4/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9891 - loss: 0.0331 - val_accuracy: 0.9825 - val_loss: 0.0947
Epoch 5/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 6s 3ms/step - accuracy: 0.9949 - loss: 0.0160 - val_accuracy: 0.9817 - val_loss: 0.1073
Epoch 6/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9974 - loss: 0.0082 - val_accuracy: 0.9850 - val_loss: 0.1051
Epoch 7/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9989 - loss: 0.0036 - val_accuracy: 0.9860 - val_loss: 0.1091
Epoch 8/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9999 - loss: 4.7606e-04 - val_accuracy: 0.9860 - val_loss: 0.1094
Epoch 9/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 1.0000 - loss: 6.5851e-05 - val_accuracy: 0.9860 - val_loss: 0.1108
Epoch 10/10
1688/1688 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 1.0000 - loss: 4.4429e-05 - val_accuracy: 0.9860 - val_loss: 0.1114

可視化グラフ

精度グラフ

精度グラフ2

損失グラフ

損失グラフ2

まとめ

  • ReduceLROnPlateauが最も安定した高精度を達成した。
  • OneCycleも後半で大きく精度を改善し、実用性は高い。
  • StepDecayは学習が安定しているが、過学習傾向に注意が必要。
  • ExponentialDecayは早期に反応が止まりやすく、調整が重要。
  • CosineDecayは初期設定のままだと機能しない場合があるため、慎重な調整が必要。

学習率スケジューラの選択は、モデル性能に大きく影響するため、検証精度だけでなく損失の変化も併せて観察しながら調整することが重要です。

参考リンク

このブログを検索

自己紹介

はじめまして、機械学習を独学中のSHOU TAKEと申します。本ブログでは、Python・Keras・Google Colabを活用した画像分類やニューラルネットワークの実験記事を中心に発信しています。初学者の方にも分かりやすく、学んだことをそのまま実験形式でまとめるスタイルです。これまで取り組んだテーマには、学習率やOptimizerの比較、Batch Sizeの検証、事前学習の活用などがあります。ご質問やご感想は、お問い合わせフォームからお気軽にどうぞ。

お問い合わせフォーム

名前

メール *

メッセージ *

プライバシーポリシー

QooQ