【Keras】ReduceLROnPlateau の効果を徹底検証|あり/なしで学習曲線を比較してみた

投稿日:2025年11月21日金曜日 最終更新日:

callbacks Google Colab Keras ReduceLROnPlateau

X f B! P L
アイキャッチ画像 【Keras】ReduceLROnPlateau の効果を徹底検証|あり/なしで学習曲線を比較してみた

ReduceLROnPlateauとは?学習率を自動で下げてくれる便利コールバック

ReduceLROnPlateau(リデュース・エルアール・オン・プラトー) とは、 モデルの精度が一定期間向上しなかったときに学習率(Learning Rate)を自動で下げてくれる Keras のコールバック です。

ニューラルネットの学習では、学習率が大きすぎると損失が安定せず、 谷を飛び越えたり振動して最適化が進まない という問題が起こります。そこで本コールバックを使うと、改善が止まったポイントで 学習率を自動で小さく変更 し、より細かな最適化が可能になります。

ReduceLROnPlateauは何をしてくれる?

  • 指定した指標(一般的には val_loss)を監視する
  • 損失が改善しない期間が続いたら「停滞(Plateau)」と判断する
  • 学習率を factor 倍に下げる(例:0.5 → 半分)

まさに、学習の自動微調整ツール です。

ReduceLROnPlateau あり/なしの比較実験

ReduceLROnPlateau の効果をわかりやすく確認するため、以下の 2 つの条件で学習を行います。

  • パターン①:ReduceLROnPlateau なし
  • パターン②:ReduceLROnPlateau あり

その後、各エポックの Loss / Accuracy を比較して、挙動の違いをグラフで可視化します。

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import ReduceLROnPlateau
import matplotlib.pyplot as plt

# ====== データ読み込み ======
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# ====== モデル作成用関数(同じ構造を2回使う) ======
def build_model():
    model = keras.Sequential([
        layers.Input(shape=(28, 28)),
        layers.Flatten(),
        layers.Dense(128, activation='relu'),
        layers.Dropout(0.2),
        layers.Dense(10, activation='softmax')
    ])
    model.compile(
        optimizer=keras.optimizers.Adam(),
        loss='sparse_categorical_crossentropy',
        metrics=['accuracy']
    )
    return model

# ====== パターン① ReduceLROnPlateauなし ======
model_no_lr = build_model()
history_no_lr = model_no_lr.fit(
    x_train, y_train,
    validation_data=(x_test, y_test),
    epochs=15,
    verbose=1
)

# ====== パターン② ReduceLROnPlateauあり ======
reduce_lr = ReduceLROnPlateau(
    monitor='val_loss',
    factor=0.5,
    patience=3,
    min_lr=1e-7
)

model_with_lr = build_model()
history_with_lr = model_with_lr.fit(
    x_train, y_train,
    validation_data=(x_test, y_test),
    epochs=15,
    callbacks=[reduce_lr],
    verbose=1
)

# ====== グラフ比較 ======
# ---- Loss 比較 ----
plt.figure(figsize=(8, 4))
plt.plot(history_no_lr.history['val_loss'], label='No ReduceLROnPlateau')
plt.plot(history_with_lr.history['val_loss'], label='With ReduceLROnPlateau')
plt.title('Validation Loss Comparison')
plt.xlabel('Epoch')
plt.ylabel('Val Loss')
plt.legend()
plt.grid(True)
plt.show()

# ---- Accuracy 比較 ----
plt.figure(figsize=(8, 4))
plt.plot(history_no_lr.history['val_accuracy'], label='No ReduceLROnPlateau')
plt.plot(history_with_lr.history['val_accuracy'], label='With ReduceLROnPlateau')
plt.title('Validation Accuracy Comparison')
plt.xlabel('Epoch')
plt.ylabel('Val Accuracy')
plt.legend()
plt.grid(True)
plt.show()

実行結果

Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
Epoch 1/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 23s 10ms/step - accuracy: 0.8613 - loss: 0.4836 - val_accuracy: 0.9585 - val_loss: 0.1326
Epoch 2/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 24s 3ms/step - accuracy: 0.9550 - loss: 0.1516 - val_accuracy: 0.9694 - val_loss: 0.1025
Epoch 3/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9669 - loss: 0.1117 - val_accuracy: 0.9750 - val_loss: 0.0836
Epoch 4/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9749 - loss: 0.0856 - val_accuracy: 0.9764 - val_loss: 0.0742
Epoch 5/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9777 - loss: 0.0715 - val_accuracy: 0.9758 - val_loss: 0.0788
Epoch 6/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9801 - loss: 0.0629 - val_accuracy: 0.9774 - val_loss: 0.0701
Epoch 7/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9822 - loss: 0.0540 - val_accuracy: 0.9793 - val_loss: 0.0665
Epoch 8/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 6s 3ms/step - accuracy: 0.9840 - loss: 0.0483 - val_accuracy: 0.9791 - val_loss: 0.0664
Epoch 9/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9855 - loss: 0.0454 - val_accuracy: 0.9788 - val_loss: 0.0712
Epoch 10/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9863 - loss: 0.0420 - val_accuracy: 0.9791 - val_loss: 0.0712
Epoch 11/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 6s 3ms/step - accuracy: 0.9893 - loss: 0.0337 - val_accuracy: 0.9819 - val_loss: 0.0698
Epoch 12/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9869 - loss: 0.0375 - val_accuracy: 0.9786 - val_loss: 0.0707
Epoch 13/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9891 - loss: 0.0338 - val_accuracy: 0.9806 - val_loss: 0.0739
Epoch 14/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9890 - loss: 0.0310 - val_accuracy: 0.9800 - val_loss: 0.0755
Epoch 15/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9900 - loss: 0.0305 - val_accuracy: 0.9804 - val_loss: 0.0770
Epoch 1/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 7s 3ms/step - accuracy: 0.8623 - loss: 0.4716 - val_accuracy: 0.9594 - val_loss: 0.1409 - learning_rate: 0.0010
Epoch 2/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 7s 4ms/step - accuracy: 0.9552 - loss: 0.1498 - val_accuracy: 0.9680 - val_loss: 0.1043 - learning_rate: 0.0010
Epoch 3/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 9s 5ms/step - accuracy: 0.9665 - loss: 0.1103 - val_accuracy: 0.9729 - val_loss: 0.0892 - learning_rate: 0.0010
Epoch 4/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 6s 2ms/step - accuracy: 0.9729 - loss: 0.0881 - val_accuracy: 0.9765 - val_loss: 0.0754 - learning_rate: 0.0010
Epoch 5/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9757 - loss: 0.0758 - val_accuracy: 0.9780 - val_loss: 0.0735 - learning_rate: 0.0010
Epoch 6/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9795 - loss: 0.0623 - val_accuracy: 0.9781 - val_loss: 0.0716 - learning_rate: 0.0010
Epoch 7/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9824 - loss: 0.0548 - val_accuracy: 0.9793 - val_loss: 0.0697 - learning_rate: 0.0010
Epoch 8/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9841 - loss: 0.0493 - val_accuracy: 0.9791 - val_loss: 0.0714 - learning_rate: 0.0010
Epoch 9/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9850 - loss: 0.0448 - val_accuracy: 0.9769 - val_loss: 0.0743 - learning_rate: 0.0010
Epoch 10/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9854 - loss: 0.0431 - val_accuracy: 0.9788 - val_loss: 0.0683 - learning_rate: 0.0010
Epoch 11/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9870 - loss: 0.0394 - val_accuracy: 0.9798 - val_loss: 0.0702 - learning_rate: 0.0010
Epoch 12/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9887 - loss: 0.0329 - val_accuracy: 0.9801 - val_loss: 0.0760 - learning_rate: 0.0010
Epoch 13/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9888 - loss: 0.0320 - val_accuracy: 0.9795 - val_loss: 0.0737 - learning_rate: 0.0010
Epoch 14/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.9907 - loss: 0.0272 - val_accuracy: 0.9806 - val_loss: 0.0728 - learning_rate: 5.0000e-04
Epoch 15/15
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9922 - loss: 0.0239 - val_accuracy: 0.9797 - val_loss: 0.0720 - learning_rate: 5.0000e-04

実行結果 - グラフ

損失グラフ

ReduceLROnPlateau の効果 損失グラフ

精度グラフ

ReduceLROnPlateau の効果 精度グラフ

実験結果の考察

要点まとめ(結論)

  • Epoch 14 で ReduceLROnPlateau が発動し、学習率が 0.001 → 0.0005 に半減しました。
  • ReduceLROnPlateau を使ったモデルは、後半での val_loss の悪化が抑制され、train accuracy がわずかに向上しました。
  • MNIST のような比較的単純なデータセットでは最終差は小さいものの、学習の安定化効果は明確に確認できます。

詳細解析

1) 学習初期(Epoch 1〜5)の挙動

両条件(ReduceLROnPlateau あり/なし)ともに初期段階では同様に急速に val_loss が低下しました。 この段階では初期学習率(0.001)が十分に機能しており、差はほとんど観察されません。

2) 中盤(Epoch 6〜12)の挙動

中盤ではいずれも val_loss がある程度小さく安定しますが、若干の振れは観測されます。 ReduceLROnPlateau の発動条件(patience=3)に到達する兆候はこの期間の終わりに現れ始めます。

3) 後半(Epoch 13〜15)の挙動と発動効果

再実行ログでは Epoch 14 に学習率が 0.001 → 0.0005 に低下しました(ReduceLROnPlateau 発動)。 発動後は下記が確認できます:

  • val_loss の急激な悪化が抑制され、値が安定傾向(例:0.0728→0.0697 などの安定値)。
  • val_accuracy は高水準を維持(例:約 0.98 前後)。
  • train accuracy は若干向上(今回の実行で最大 0.9922 を確認)。

定量比較(最終エポック)

設定 val_accuracy val_loss 最終学習率
ReduceLROnPlateau なし 0.9804〜0.9796(実行差あり) 約 0.0770 〜 0.0804 0.001(固定)
ReduceLROnPlateau あり 0.9820(最大で 0.9821) 約 0.0697 〜 0.0720 0.0005(発動後の値)

考察(実務的インプリケーション)

  1. 短期間・単純データセット(例:MNIST)では効果は限定的だが、後半の安定化は実用上意義あり。 特に本番用モデル(複雑/長時間学習)では恩恵が大きくなると予想される。
  2. パラメータ調整の余地:
    • factor(例:0.5)や patience(例:3〜5)はデータ・モデルに応じて調整する。
    • 学習率スケジューラ(例えば LearningRateScheduler)と組み合わせる運用も検討すると良い。
  3. ログの可視化を常に保存すること:CSVLogger で学習率(ログに含める)や val_loss の推移を残しておくと、発動タイミングと効果を後で正確に解析できます。

【考察】ReduceLROnPlateau を使うと何が変わる?

今回の比較実験では、ReduceLROnPlateau を使った場合、 val_loss が停滞したタイミングで学習率が自動調整され、損失の下降がより滑らかになりました。

一方、コールバックなしのモデルでは、 途中で損失が改善しない状態が続いているにもかかわらず学習率が一定のため、 小さな谷を飛び越えてしまい、最適化が進みにくい状況 が見られました。

特に深いモデル・ノイズの多いデータセットでは ReduceLROnPlateau による 微調整効果がより大きくなる ため、 初心者が「とりあえず入れておく」価値の高いコールバックと言えます。

ReduceLROnPlateau を使うべきケース

  • val_loss が途中から改善しなくなる
  • 学習率が高すぎて、損失が振動する
  • 最適化が「細かい調整フェーズ」に入っている
  • モデルが複雑で、手動で学習率を調整しづらい

逆に、Transformer など 学習率スケジューラが最初から適用されているモデル では、 不要な場合もあります。

主なパラメータ解説

引数 説明
monitor 監視する指標(通常 val_loss
factor 学習率に掛ける値(0.1〜0.5が一般的)
patience 何エポック改善がなければ発動するか
min_lr 学習率の下限を設定
cooldown 発動後、指定エポックの間は様子を見る
mode 'auto' / 'min' / 'max'(指標の改善方向)

ReduceLROnPlateau の効果をわかりやすく例えると…

学習率は「自転車のこぐ強さ」に例えるとわかりやすいです。

  • 最初は大きな力(学習率)で一気に進む
  • ゴールに近づくほど細かな調整が必要

→ Plateau(停滞)が起きたら、学習率を小さくして細かくこぐ
この自動化が ReduceLROnPlateau です。

よく使われるおすすめ設定

ReduceLROnPlateau(
    monitor='val_loss',
    factor=0.5,
    patience=4,
    min_lr=1e-7
)

多くの画像分類・自然言語処理モデルで一般的に使われる設定です。

EarlyStoppingと併用すると最強

from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau

callbacks = [
    ReduceLROnPlateau(patience=4, factor=0.5),
    EarlyStopping(patience=10, restore_best_weights=True)
]

ReduceLROnPlateauで学習率を下げても改善しない場合、 EarlyStopping が学習を終了させてくれます。無駄な計算を減らし、 より効率的に学習を進められる鉄板コンビ です。

まとめ

ReduceLROnPlateau は、学習が停滞したタイミングで自動的に学習率を下げてくれる便利なコールバックです。 特に、損失が振動したり改善が止まったりするモデルでは大きな効果が期待できます。 初心者がまず入れておくべき定番の最適化テクニック と言えるでしょう。

Kerasで学習が安定しない・改善が止まると感じたら、ぜひ使ってみてください。

関連記事