PyTorch 学習率スケジューラ

学習率(Learning Rate)はニューラルネットワークのトレーニングにおいて最も重要なハイパーパラメータのひとつです。学習率が大きすぎると、トレーニングが振動し、場合によっては発散します。小さすぎると、収束が極めて遅く、局所最適に陥りやすくなります。

学習率スケジューラ(LR Scheduler)トレーニング中に学習率を動的に調整することで、トレーニング初期の高速な収束と後期の細かい調整を両立させます。


1. 基本概念と使用パターン

標準的な使用手順

例

import torch
import torch.nn as nn
import torch.optim as optim

model     = nn.Linear(10, 1)
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 1. スケジューラを作成し、optimizer を渡します
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

for epoch in range(100):
    # 2. トレーニング
    train(model, optimizer)

    # 3. 各エポックの終了後に scheduler.step() を呼び出します
    scheduler.step()

    # 4. 現在の学習率を確認します
    current_lr = scheduler.get_last_lr()[0]
    print(f"Epoch {epoch+1}, LR: {current_lr:.6f}")

step() の呼び出しタイミング

例

# 正しい:optimizer.step() を先に、scheduler.step() を後に
optimizer.step()
scheduler.step()

# 誤り:scheduler.step() が optimizer.step() より前にある
# PyTorch 1.1.0 以降では警告が発生し、一部のスケジューラが正常に動作しなくなります
scheduler.step()
optimizer.step()

注意:optimizer.step()必ずscheduler.step()前に呼び出してください。

学習率の状態の確認と保存

例

# 現在の学習率を取得します
current_lr = optimizer.param_groups[0]['lr']
current_lr = scheduler.get_last_lr()[0]   # 前回の step() 後の LR

# チェックポイントを保存(scheduler の状態も同時に保存が必要)
torch.save({
    'epoch':          epoch,
    'model':          model.state_dict(),
    'optimizer':      optimizer.state_dict(),
    'scheduler':      scheduler.state_dict(),   # これを忘れないでください
}, 'checkpoint.pth')

# チェックポイントから復元
ckpt = torch.load('checkpoint.pth')
model.load_state_dict(ckpt['model'])
optimizer.load_state_dict(ckpt['optimizer'])
scheduler.load_state_dict(ckpt['scheduler'])

2. 固定減衰スケジューラ

2.1 StepLR ステップごとの減衰

每隔固定step_sizeエポックごとに、学習率を掛けるgamma。最もシンプルで、最もよく使われるスケジューラの一つです。

公式: lr = lr_base * gamma^(floor(step / step_size))

例

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(
    optimizer,
    step_size=30,   # 每 30 个 epoch 衰减一次
    gamma=0.1       # 毎回 0.1 を掛ける(つまり元の 1/10 に縮小される)
)

# LR 变化:
# Epoch  0-29:  0.1
# Epoch 30-59:  0.01
# Epoch 60-89:  0.001
# Epoch 90+:    0.0001

适用场景:訓練テンポが固定され、段階が明確なタスク、例えば ImageNet における ResNet の訓練(90エポック、第30、60エポックで減衰)。


2.2 MultiStepLR マイルストーンによる減衰

指定された複数のエポック(マイルストーン)で学習率を減衰させ、StepLR よりも柔軟です。

例

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.MultiStepLR(
    optimizer,
    milestones=[30, 60, 80],   # 30、60、80 epoch で減衰
    gamma=0.1
)

# LR 变化:
# Epoch  0-29:  0.1
# Epoch 30-59:  0.01
# Epoch 60-79:  0.001
# Epoch 80+:    0.0001

适用场景:どのエポックでモデルを微調整する必要があるか分かっている場合、例えば分類モデルの中後期の精密な収束段階などです。


2.3 ExponentialLR 指数減衰

毎エポック減衰し、学習率が指数関数的に継続的に下降するため、減衰がより滑らかです。

公式: lr = lr_base * gamma^epoch

例

optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.ExponentialLR(
    optimizer,
    gamma=0.95   # 每个 epoch 乘以 0.95
)

# LR 变化(前 5 个 epoch):
# Epoch 1: 0.0100
# Epoch 2: 0.0095
# Epoch 3: 0.0090
# Epoch 4: 0.0086
# Epoch 5: 0.0081

gamma設定が小さすぎる(例えば0.5)と学習率が急速にゼロに近づくため、通常は0.9〜0.99の間に設定します。


3. 適応型スケジューラ

3.1 ReduceLROnPlateau メトリクス監視による減衰

最もインテリジェントなスケジューラの一つ:ある指標(検証セットの損失など)を監視し、その指標の改善が止まったときに自動的に学習率を下げます。どのエポックで減衰させるかを事前に知る必要はありません。

例

optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='min',       # 'min': 监控值越小越好(loss);'max': 越大越好(accuracy)
    factor=0.1,       # 触发时 lr = lr × factor
    patience=10,      # 指標の停滞を許容するエポック数。超過した場合は減衰する。
    threshold=1e-4,   # 改善幅がこの値未満の場合は未改善とみなす
    min_lr=1e-6,      # 学习率下限,不会低于此值
    verbose=True      # 打印衰减信息
)

for epoch in range(100):
    train_loss = train(model, optimizer)
    val_loss   = evaluate(model)

    # 他のスケジューラとは異なり、ここでは監視指標を渡す
    scheduler.step(val_loss)

Accuracy の監視の書き方

例

scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='max',       # accuracy 越大越好
    factor=0.5,
    patience=5,
)

scheduler.step(val_accuracy)

适用场景:ほぼすべてのタスクでデフォルトの第一選択であり、特に訓練エポック数が不明確な場合や、訓練が不安定な場合に適しています。


3.2 CosineAnnealingLR コサインアニーリング

学习率按余弦曲线初期値から最小値まで滑らかに下降し(eta_min)、階段状減衰の急変を回避する。

公式: lr_t = eta_min + 0.5 * (eta_max - eta_min) * (1 + cos(t * pi / T_max)

例

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.CosineAnnealingLR(
    optimizer,
    T_max=100,     # 半周期の長さ(通常は総エポック数に設定)
    eta_min=1e-6   # 学习率最小值(默认 0)
)

# LR 変化軌跡(T_max=10 の場合の図):
# 0.1 -> 0.095 -> 0.079 -> 0.055 -> 0.026 -> 0.001
#              (余弦曲线平滑下降)

适用场景:訓練エポック数が固定されているシナリオで、Vision Transformer、ResNet などの論文で広く使用され、収束品質が良好です。


3.3 CosineAnnealingWarmRestarts

コサインアニーリングのアップグレード版であり、対応する周期性重启(Warm Restarts):各周期の終了後に学習率が初期値にリセットされ、新しい余弦減衰が始まります。モデルが局所最適解から抜け出すことを可能にします。

例

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(
    optimizer,
    T_0=10,       # 最初の周期の長さ(エポック数)
    T_mult=2,     # 再起動ごとの周期長の倍数(1=等長、2=徐々に長く)
    eta_min=1e-6
)

# T_mult=2 のときの周期長:10 -> 20 -> 40 -> 80 ...
# LR 变化(T_0=10, T_mult=1):
# 0.1 -> ... -> 0 -> 0.1 -> ... -> 0 -> 0.1(每 10 个 epoch 重启一次)

适用场景:大規模モデルを訓練する場合、またはモデルが複数の収束点から最適なものを選択できるようにしたい場合、Snapshot Ensemble と併用すると顕著な効果があります。


4. ウォームアップスケジューラ

Warmup(预热)トレーニング開始後の数ステップの間、学習率を極小値から目標値まで徐々に上昇させることを指す。バッチサイズが大きいトレーニングやTransformer系のモデルでは、ほぼ必ずウォームアップが必要である。そうでなければ初期の勾配更新が急激すぎて、モデルが安定しにくい。

4.1 LinearLR 線形スケジューリング

指定した epoch 内で学習率を線形に変更します(線形ウォームアップや線形減衰に使用できます)。

例

optimizer = optim.Adam(model.parameters(), lr=0.01)

# 線形ウォームアップ:最初の 5 epoch で、lr は 0.01×0.1=0.001 から 0.01 へ線形に増加
warmup_scheduler = optim.lr_scheduler.LinearLR(
    optimizer,
    start_factor=0.1,   # 初期 lr = base_lr × start_factor
    end_factor=1.0,     # 終了 lr = base_lr × end_factor
    total_iters=5       # 5 epoch かけて完了
)

# LR 変化:0.001 -> 0.003 -> 0.005 -> 0.007 -> 0.009 -> 0.01

4.2 ConstantLR 定数段階

指定した epoch 数の間、学習率をbase_lr × factor、その後元の値に戻します。

例

# 最初の 5 epoch は base_lr × 0.5 を使用し、その後通常に戻る
scheduler = optim.lr_scheduler.ConstantLR(
    optimizer,
    factor=0.5,
    total_iters=5
)

4.3 SequentialLR 組み合わせスケジューリング

複数のスケジューラを順番に直列に接続し、「ウォームアップ + 減衰」の組み合わせ戦略を実現する標準的な方法です。

例

optimizer = optim.Adam(model.parameters(), lr=0.01)

# フェーズ1:ウォームアップ(最初の 5 epoch で、LR は 0.001 から 0.01 へ線形に増加)
warmup = optim.lr_scheduler.LinearLR(
    optimizer, start_factor=0.1, end_factor=1.0, total_iters=5
)

# フェーズ2:コサインアニーリング(残り 95 epoch)
cosine = optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=95, eta_min=1e-6
)

# 組み合わせ:最初に warmup を実行し、5 epoch 後に cosine に切り替え
scheduler = optim.lr_scheduler.SequentialLR(
    optimizer,
    schedulers=[warmup, cosine],
    milestones=[5]          # 5 epoch 目で切り替え
)

# 使用方法は通常のスケジューラと完全に同じ
for epoch in range(100):
    train(...)
    scheduler.step()

適用シナリオ:Transformer 学習の標準構成(ウォームアップ + コサインアニーリング)。BERT、GPT、ViT の事前学習はすべてこの戦略を採用しています。


5. 循環スケジューラ

5.1 CyclicLR 循環学習率

学習率はbase_lrとmax_lrの間で周期的に循環します、モデルがより広いパラメータ空間を探索し、鞍点から抜け出すのに役立ちます。

例

optimizer = optim.SGD(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.CyclicLR(
    optimizer,
    base_lr=0.001,          # 学習率の下限
    max_lr=0.01,            # 学習率の上限
    step_size_up=2000,      # base_lr から max_lr へ上昇する反復ステップ数
    step_size_down=2000,    # max_lr から base_lr へ下降する反復ステップ数(デフォルトでは step_size_up と同じ)
    mode='triangular',      # 三角形循環(等振幅)
    # mode='triangular2' # 各周期で振幅が半分になる
    # mode='exp_range' # 振幅が指数的に減衰
)

# CyclicLR は step(batch)単位で呼び出し、epoch 単位ではない
for epoch in range(num_epochs):
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        optimizer.step()
        scheduler.step()    # 各 batch の後に呼び出し

3つのモードの比較:

mode振幅の変化特徴
triangular不変安定した探索、初期に適する
triangular2各周期で半分探索してから収束
exp_range指数減衰最終的に安定して収束

5.2 OneCycleLR 単一周期戦略

最も性能の良いスケジューラの1つ、fastai が提案した 1-Cycle Policy。トレーニング全体で周期は1つだけ:学習率は最初に上昇し、その後下降します。運動量(momentum)は逆方向に変化します。

トレーニング速度が速く、通常は従来のトレーニングの1/5~1/10の epoch 数だけで同じ精度に達します。

例

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

steps_per_epoch = len(train_loader)

scheduler = optim.lr_scheduler.OneCycleLR(
    optimizer,
    max_lr=0.1,                              # 学習率の最大点
    steps_per_epoch=steps_per_epoch,         # 各 epoch の step 数
    epochs=10,                               # 総 epoch 数
    pct_start=0.3,                           # 最初の 30% をウォームアップ上昇に使用
    anneal_strategy='cos',                   # 減衰戦略('cos' または 'linear')
    div_factor=25,                           # 初期 lr = max_lr / div_factor
    final_div_factor=1e4                     # 最終 lr = max_lr / final_div_factor
)

# 初期 lr = 0.1 / 25 = 0.004
# ピーク lr = 0.1(30% の時点)
# 最終 lr = 0.1 / 10000 = 0.00001

# 同様に batch 単位で呼び出し
for epoch in range(10):
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        optimizer.step()
        scheduler.step()    # 各 batch の後に呼び出し

適用シナリオ:計算リソースが限られていて、アイデアを素早く検証する必要がある場合に最適です。max_lrLR Finder ツールを使って最適な値を決定することをお勧めします。


6. カスタムスケジューラ

6.1 LambdaLR 関数によるカスタム定義

Lambda 関数(epoch 数を受け取り、学習率の乗数を返す)を渡すことで、スケジュール戦略を完全にカスタマイズできます。

例

optimizer = optim.Adam(model.parameters(), lr=0.01)

# 例1:Transformer の古典的なウォームアップ戦略
# lr ∝ min(step^-0.5, step × warmup_steps^-1.5)
def transformer_lr(epoch, warmup_epochs=10, d_model=512):
    if epoch == 0:
        return 1e-7 / 0.01          # ゼロ除算を避ける
    step = epoch + 1
    warmup = warmup_epochs
    return (d_model ** -0.5) * min(step ** -0.5, step * warmup ** -1.5) / 0.01

scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=transformer_lr)


# 例2:多項式減衰(lr を初期値から線形/多項式で 0 まで下げる)
def polynomial_decay(epoch, total_epochs=100, power=1.0):
    return max((1 - epoch / total_epochs) ** power, 0.0)

scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=polynomial_decay)


# 例3:グループ別学習率(パラメータグループごとに異なる戦略)
optimizer = optim.Adam([
    {'params': model.backbone.parameters(), 'lr': 1e-4},
    {'params': model.head.parameters(),     'lr': 1e-3},
])

# 各パラメータグループに1つの lambda 関数
scheduler = optim.lr_scheduler.LambdaLR(
    optimizer,
    lr_lambda=[
        lambda epoch: 0.95 ** epoch,    # backbone:ゆっくり減衰
        lambda epoch: 0.85 ** epoch,    # head:速く減衰
    ]
)

6.2 LRScheduler の継承

より複雑なロジックが必要な場合は、継承LRScheduler(PyTorch >= 2.0、旧バージョンでは_LRScheduler)を継承して完全にカスタマイズします:

例

from torch.optim.lr_scheduler import LRScheduler
import math

class WarmupCosineScheduler(LRScheduler):
    """
ウォームアップ + コサインアニーリング組み合わせスケジューラ(手動実装版)
- 最初の warmup_epochs epoch で線形ウォームアップ
- その後コサインアニーリングで min_lr まで減衰
    """

    def __init__(self, optimizer, warmup_epochs, total_epochs,
                 min_lr=1e-6, last_epoch=-1):
        self.warmup_epochs = warmup_epochs
        self.total_epochs  = total_epochs
        self.min_lr        = min_lr
        super().__init__(optimizer, last_epoch)

    def get_lr(self):
        epoch = self.last_epoch

        # ウォームアップ段階:線形に増加
        if epoch < self.warmup_epochs:
            warmup_factor = (epoch + 1) / self.warmup_epochs
            return [base_lr * warmup_factor for base_lr in self.base_lrs]

        # コサインアニーリング段階
        progress = (epoch - self.warmup_epochs) / (
            self.total_epochs - self.warmup_epochs
        )
        cosine_factor = 0.5 * (1 + math.cos(math.pi * progress))
        return [
            self.min_lr + (base_lr - self.min_lr) * cosine_factor
            for base_lr in self.base_lrs
        ]


optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = WarmupCosineScheduler(
    optimizer,
    warmup_epochs=10,
    total_epochs=100,
    min_lr=1e-6
)

7. スケジューラの可視化比較

以下のコードで各スケジューラの LR 変化曲線を描画でき、直感的に比較できます:

例

import torch
import torch.optim as optim
import matplotlib.pyplot as plt

def simulate_lr(scheduler_fn, epochs=100, steps_per_epoch=None):
    """スケジューラの学習率変化をシミュレーションして記録"""
    model     = torch.nn.Linear(1, 1)
    optimizer = optim.SGD(model.parameters(), lr=0.1)
    scheduler = scheduler_fn(optimizer)
    lrs = []

    if steps_per_epoch:
        # step 単位で呼び出すスケジューラ
        for _ in range(epochs):
            for _ in range(steps_per_epoch):
                optimizer.step()
                scheduler.step()
                lrs.append(optimizer.param_groups[0]['lr'])
    else:
        # epoch 単位で呼び出すスケジューラ
        for _ in range(epochs):
            optimizer.step()
            scheduler.step()
            lrs.append(optimizer.param_groups[0]['lr'])
    return lrs

schedulers = {
    'StepLR(step=30, gamma=0.1)':
        lambda opt: optim.lr_scheduler.StepLR(opt, 30, 0.1),
    'CosineAnnealingLR':
        lambda opt: optim.lr_scheduler.CosineAnnealingLR(opt, T_max=100),
    'ExponentialLR(gamma=0.95)':
        lambda opt: optim.lr_scheduler.ExponentialLR(opt, 0.95),
    'CosineWarmRestarts(T0=25)':
        lambda opt: optim.lr_scheduler.CosineAnnealingWarmRestarts(opt, T_0=25),
}

plt.figure(figsize=(12, 5))
for name, fn in schedulers.items():
    plt.plot(simulate_lr(fn), label=name)

plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('PyTorch LR Scheduler Comparison')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lr_schedulers.png', dpi=150)
plt.show()

8. 完全なトレーニングテンプレート

ウォームアップ + コサインアニーリング、モデルの保存と復元を統合した本番用トレーニングテンプレート:

例

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import SequentialLR, LinearLR, CosineAnnealingLR

# ハイパーパラメータ
EPOCHS       = 100
WARMUP_EPOCHS = 5
BASE_LR      = 1e-3
MIN_LR       = 1e-6
SAVE_PATH    = 'best_model.pth'

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# モデル / オプティマイザ / スケジューラ
model     = MyModel().to(device)
optimizer = optim.AdamW(model.parameters(), lr=BASE_LR, weight_decay=1e-4)
criterion = nn.CrossEntropyLoss(label_smoothing=0.1)

# ウォームアップ 5 epoch + コサインアニーリング 95 epoch
warmup_sched = LinearLR(optimizer, start_factor=0.1, total_iters=WARMUP_EPOCHS)
cosine_sched = CosineAnnealingLR(optimizer, T_max=EPOCHS - WARMUP_EPOCHS, eta_min=MIN_LR)
scheduler    = SequentialLR(optimizer, [warmup_sched, cosine_sched], milestones=[WARMUP_EPOCHS])

# チェックポイントの復元
start_epoch = 0
best_acc    = 0.0

try:
    ckpt = torch.load(SAVE_PATH, map_location=device)
    model.load_state_dict(ckpt['model'])
    optimizer.load_state_dict(ckpt['optimizer'])
    scheduler.load_state_dict(ckpt['scheduler'])
    start_epoch = ckpt['epoch'] + 1
    best_acc    = ckpt['best_acc']
    print(f"Epoch {start_epoch} から復元、最高精度 {best_acc:.4f}")
except FileNotFoundError:
    print("ゼロからトレーニングを開始")

# トレーニングループ
history = {'train_loss': [], 'val_acc': [], 'lr': []}

for epoch in range(start_epoch, EPOCHS):
    # トレーニング
    model.train()
    total_loss = 0.0
    for inputs, labels in train_loader:
        inputs, labels = inputs.to(device), labels.to(device)
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # 勾配クリッピング
        optimizer.step()
        total_loss += loss.item()

    # 検証
    model.eval()
    correct = 0
    with torch.no_grad():
        for inputs, labels in val_loader:
            inputs, labels = inputs.to(device), labels.to(device)
            correct += (model(inputs).argmax(1) == labels).sum().item()

    avg_loss = total_loss / len(train_loader)
    val_acc  = correct / len(val_loader.dataset)
    cur_lr   = scheduler.get_last_lr()[0]

    # 記録 & 表示
    history['train_loss'].append(avg_loss)
    history['val_acc'].append(val_acc)
    history['lr'].append(cur_lr)
    print(f"Epoch {epoch+1:3d}/{EPOCHS} | "
          f"Loss: {avg_loss:.4f} | Acc: {val_acc:.4f} | LR: {cur_lr:.2e}")

    # スケジューラ更新
    scheduler.step()

    # 最良モデルの保存
    if val_acc > best_acc:
        best_acc = val_acc
        torch.save({
            'epoch':      epoch,
            'model':      model.state_dict(),
            'optimizer':  optimizer.state_dict(),
            'scheduler':  scheduler.state_dict(),
            'best_acc':   best_acc,
        }, SAVE_PATH)
        print(f" 最良モデルを保存、Acc: {best_acc:.4f}")

print(f"\nトレーニング完了、最高検証精度: {best_acc:.4f}")

9. スケジューラ選択ガイド

タスクタイプ別のおすすめ

シナリオ推奨スケジューラ理由
高速実験 / プロトタイプ検証ReduceLROnPlateauハイパーパラメータ調整不要、適応的減衰
画像分類(固定epoch)CosineAnnealingLR滑らかな減衰、収束品質が良い
Transformer / BERTLinearLR + CosineAnnealingLRウォームアップが必須
リソースが限られている、高速トレーニングOneCycleLR1/5 の時間で同等の精度
大規模モデルの微調整(fine-tune)LinearLR(ウォームアップ) +ConstantLR低学習率で安定した微調整
epoch 数が不明ReduceLROnPlateau自動応答、固定リズムに依存しない
物体検出(YOLO/Faster-RCNN)MultiStepLR / OneCycleLR明確な減衰ポイントがある、または速度を追求
局所最適解から抜け出したいCosineAnnealingWarmRestarts周期的再起動でパラメータ空間を探索

一般的な設定の組み合わせ

例

# 組み合わせ1:Warmup + コサインアニーリング(Transformer 標準)
SequentialLR([LinearLR(5 epochs), CosineAnnealingLR(95 epochs)])

# 組み合わせ2:検証セットの監視 + 自動減衰(汎用的な第一候補)
ReduceLROnPlateau(mode='min', patience=5, factor=0.5)

# 組み合わせ3:OneCycleLR(高速トレーニングの第一候補)
OneCycleLR(max_lr=0.1, total_steps=total_steps, pct_start=0.3)

# 組み合わせ4:従来の階段状減衰(CV の古典)
MultiStepLR(milestones=[30, 60, 90], gamma=0.1)

注意事項の早見表

問題解決方法
スケジューラが効果がない確認scheduler.step()かどうかoptimizer.step() の後呼び出し
CyclicLR / OneCycleLR 変化なしこの2つはbatch呼び出しであり、epoch によるものではない
ReduceLROnPlateau が発動しない確認mode正しく設定されているか(loss には'min'、accuracy には'max')
訓練再開後の LR が正しくない保存とロードが行われたか確認するscheduler.state_dict()
複数パラメータグループの LR 設定使用LambdaLRリストを渡し、各パラメータグループに1つの lambda を指定
その他の拡張