// PART 03 · ARTICLE 13 / 15
MEDICAL-AI-ALGO OPTIMIZATION L3

最適化アルゴリズム(SGD / Adam / AdamW / 学習率スケジューラ)

— ニューラルネットを「ちゃんと学習させる」ための要素

10 min read· L3· 2026.05.23 update· by Editor

ニューラルネットワークでは、モデル構造だけでは性能は決まりません。同じ MLP や CNN でも、学習率・optimizer・Weight Decay・Early Stopping の設計で結果は変わります。リハビリテーション研究では小規模データが多く、学習が不安定になりやすいです。本稿では SGD・Momentum・Adam・AdamW・学習率スケジューラを、医療データ解析の実務に結びつけて整理します[1]

// CONTEXT

この記事は、03·12 ニューラルネットワーク基礎 の次に読む内容です。正則化の考え方は Lasso / Ridge / Elastic Net とつながります。汎化性能の確認は 過学習と正則化 とあわせて理解すると整理しやすく、学習率の調整によって過学習がどう動くかは 14·01 過学習デモ14·02 交差検証デモ でも体感できます。

// 01 · LEARN OUTCOMESこの記事で学ぶこと

  • SGD・Momentum・Adam・AdamW の違いを説明できるようになる。
  • 学習率が収束と汎化性能に与える影響を理解する。
  • Step・Cosine・Warmup + Cosine・Cyclical の役割を整理する。
  • リハ研究で Early Stopping と検証セットをどう扱うかを確認する。

// 02 · CONCLUSIONまず結論

最適化は、モデルの「能力」を増やす操作ではありません。すでに決めたモデルを、どのような手順で学習させるかを決める操作です。そのため、optimizer を変えて性能が上がったとしても、臨床的な構造を理解したことにはなりません。医療データでは、予測性能・calibration・解釈性・再現性を分けて評価します。

特にリハビリテーション領域では、症例数が限られます。FIM・SIAS・BBS・TUG・10m 歩行などの指標は、測定者や施設の違いも受けます。そのため、学習率を細かく調整して内部検証だけが良くなっても、外部検証で保たれるとは限りません。最適化は、モデル比較と検証設計の中で扱う必要があります。

// 03 · FIGURE図で見る最適化

図1では、SGD・Momentum・Adam の進み方を比較します。損失関数を地形にたとえると、学習は谷底へ向かう移動です。SGD は現在の傾きだけを見て進み、Momentum は過去の進行方向を少し残し、Adam は各パラメータの更新幅を自動で調整します。

// OPTIMIZATION TRAJECTORIES · 損失地形を谷底に向かう移動 [ A ] 損失地形と更新経路 細長い谷 → 単純 SGD は左右に揺れる 最小 SGD + Momentum Adam [ B ] 更新規則の比較 SGD θ ← θ − η · ∇L(θ) 現在の勾配だけで進む(軽い・揺れる) + MOMENTUM v ← μv + ∇L · θ ← θ − ηv 過去の方向を慣性として残す ADAM m,v ← 勾配の 1次・2次モーメント パラメータごとに更新幅を自動調整 ADAMW Adam + Weight Decay を分離(decoupled)
図1: 細長い谷の損失地形における SGD・Momentum・Adam の収束経路。SGD は左右に揺れやすく、Momentum は過去方向を残して滑らかに、Adam はパラメータごとに更新幅を自動調整して効率よく谷底へ向かいます。AdamW は Adam に Weight Decay を分離して加えたものです。

図2では、学習率スケジューラを比較します。Step は一定間隔で学習率を下げ、Cosine はなめらかに下げます。Warmup + Cosine は序盤だけ学習率を上げてから下げ、Cyclical は学習率を周期的に上下させます[6]

// LR SCHEDULES · 学習率を時間とともに変える 4 パターン STEP 一定間隔で学習率を 1/10 などに下げる η epoch → 単純・解釈が楽 COSINE η_min + 0.5(η_max−η_min)·(1+cos(πt/T)) η epoch → なめらかに減衰 WARMUP + COSINE 序盤を上げて → 後半は cosine で下げる η epoch → warmup 終端 序盤の急更新を避ける CYCLICAL η_min ↔ η_max を周期的に往復 η epoch → 局所最適から抜け出しやすい
図2: 学習率スケジューラの 4 パターン。Step(離散的な段下げ)/ Cosine(なめらかな減衰)/ Warmup + Cosine(序盤を上げてから減衰)/ Cyclical(周期的に上下)。医療データでは Cosine か Step が出発点として扱いやすく、Transformer 系では Warmup + Cosine が定番です。

// 04 · CLINICALリハ・神経・整形領域での使いどころ

// CASE 1 · 歩行 LSTM の学習

歩行加速度や関節角度の時系列を LSTM で扱う場合、損失が上下しやすくなります。まず Adam で学習の挙動を確認し、収束が不安定な場合は AdamW と Cosine schedule を検討します。勾配クリッピングも有用です。ただし、評価は同一患者の時系列が train と test にまたがらないように設計します。

// CASE 2 · 頭部 CT CNN の転移学習

頭部 CT 画像で CNN を微調整する場合、事前学習済み重みを壊さない設計が必要です。学習率 1e-5 付近から試し、Warmup を入れると序盤の大きな更新を避けやすくなります。validation loss を見ながら Early Stopping を行います。テストデータは、学習停止の判断に使いません。

// CASE 3 · MLP による FIM 予測

年齢・発症からの日数・入院時 FIM・SIAS・認知機能などを使う MLP では、標準化が重要です。SGD と Adam を比較すると、Adam の方が早く収束することがあります。一方で、外部検証では差が小さいこともあります。ロジスティック回帰正則化線形モデル を同じ分割で比較します。

// CASE 4 · 小データでの学習率設計

N=100〜300 の小規模研究では、学習率が少し高いだけで検証性能が揺れます。逆に低すぎると、十分に学習しないまま Early Stopping で止まります。1e-3・3e-4・1e-4・3e-5・1e-5 など、対数スケールで範囲を絞ります。fold ごとのばらつきも結果として示します。

これらの例に共通するのは、optimizer だけを単独で選ばない点です。データ分割・標準化・損失関数・クラス不均衡・評価指標を同時に設計します。退院先予測のような分類では、AUC が同じでも calibration が異なることがあります。予測確率を使う研究では、Brier score や calibration plot も確認します(14·04 DCA デモ も参考)。

また、学習率の探索は、研究の自由度を増やします。多くの候補を試すほど、偶然よい fold を選びやすくなります。そのため、探索範囲を事前に決め、最終評価データには触れない設計が重要です。Methods では、この探索手順を読み手が再現できる粒度で書きます。

// 05 · THEORY理論の最小限

最適化では、損失関数 L を小さくするように重みを更新します。ここでいう損失は、予測と正解のずれです。分類なら交差エントロピー、回帰なら MSE がよく使われます。勾配は「損失を大きくする方向」を示します。その逆方向へ進むのが勾配降下です。

学習率は、1 回の更新でどれだけ重みを動かすかを決めます。高すぎると、損失の谷を飛び越えます。低すぎると、収束に時間がかかり、改善しないように見えることがあります。小規模医療データでは、validation loss の揺れも大きいため、単一 epoch の値だけで判断しません。

バッチサイズも更新の性質を変えます。小さいバッチでは勾配が揺れますが、探索性が残ります。大きいバッチでは勾配は安定しますが、メモリを使い、汎化性能が変わることがあります。画像や時系列では、GPU メモリとの兼ね合いで現実的な値を選びます。

SGD:
θ_{t+1} = θ_t - η ∇L(θ_t)

η: learning rate
∇L: gradient of loss

SGD は基本形です。ミニバッチごとに勾配を計算します。計算は軽いですが、谷が細い損失地形では左右に揺れます。その揺れを抑えるために Momentum を使います[2]

Momentum:
v_{t+1} = μ v_t + ∇L(θ_t)
θ_{t+1} = θ_t - η v_{t+1}

μ: momentum coefficient

Momentum は、過去の勾配を速度として残します。坂道を下る球のように、同じ方向の更新を進めやすくします。画像分類では SGD + Momentum がよい結果を示す場合もあります。ただし、学習率調整の手間は残ります。

Adam:
m_t = β1 m_{t-1} + (1 - β1) g_t
v_t = β2 v_{t-1} + (1 - β2) g_t^2
θ_{t+1} = θ_t - η m_t / (sqrt(v_t) + eps)

Adam は、勾配の平均と二乗平均を使います。パラメータごとに更新幅を調整するため、初期探索で使いやすい optimizer です[3]。ただし、Adam を使えば検証性能が自動的に良くなるわけではありません。fold・データ規模・モデル構造で挙動は変わります。

AdamW:
Adam update step
then decoupled weight decay:
θ_{t+1} = θ_{t+1} - η λ θ_t

λ: weight decay strength

AdamW は、Weight Decay を Adam の更新から切り離します[4]。Adam に L2 正則化をそのまま足す場合と、挙動が異なります。医療データの小規模 NN では、過学習対策として Weight Decay を明示して報告します。Dropout や Early Stopping と混同しないことも重要です。

Cosine schedule:
η_t = η_min + 0.5 (η_max - η_min) (1 + cos(πt/T))

Warmup:
first few epochs: gradually increase η

スケジューラは、学習率を時間とともに変える仕組みです。序盤は大きく動き、終盤は小さく調整する設計が多く使われます。Warmup は、序盤の急な更新を避ける目的で使います。Cosine は、終盤にゆっくり学習率を下げる方法です[5]

// 06 · IMPLEMENTATION · PYTHON実装例

以下は教育用の仮想データを使った実装例です。実データでは、個人情報保護・倫理審査・施設ルール・利用規約を確認します。前処理は train fold 内だけで fit します。テストデータは、スケーリング・Early Stopping・学習率選択に使いません(09·02 データリーケージ 参照)。

PYTHON
# Educational example only.
# Use de-identified data and follow IRB, facility rules, and data-use agreements.
# The file name below is fictional and does not represent real patient data.

import random
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.metrics import roc_auc_score, brier_score_loss
from sklearn.model_selection import GroupKFold, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

SEED = 42
random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)

df = pd.read_csv("example_rehab_dataset.csv")
target = "home_discharge"
group_col = "hospital_id"
numeric_features = [
    "age", "days_from_onset", "fim_motor_admission",
    "fim_cognition_admission", "sias_total", "gait_speed",
]
categorical_features = ["sex", "stroke_type", "ward_type"]

X = df[numeric_features + categorical_features]
y = df[target].astype("float32").values
groups = df[group_col].values

preprocess = ColumnTransformer([
    ("num", Pipeline([
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler()),
    ]), numeric_features),
    ("cat", Pipeline([
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("onehot", OneHotEncoder(handle_unknown="ignore")),
    ]), categorical_features),
])

class RehabMLP(nn.Module):
    def __init__(self, input_dim, hidden=64, dropout=0.25):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(input_dim, hidden), nn.BatchNorm1d(hidden), nn.ReLU(),
            nn.Dropout(dropout), nn.Linear(hidden, hidden // 2), nn.ReLU(),
            nn.Dropout(dropout), nn.Linear(hidden // 2, 1),
        )

    def forward(self, x):
        return self.net(x).squeeze(1)

def make_loader(X_array, y_array, batch_size=32, shuffle=True):
    X_tensor = torch.tensor(X_array, dtype=torch.float32)
    y_tensor = torch.tensor(y_array, dtype=torch.float32)
    return DataLoader(TensorDataset(X_tensor, y_tensor),
                      batch_size=batch_size, shuffle=shuffle)

def train_model(X_train, y_train, X_valid, y_valid, lr=1e-3, weight_decay=1e-2):
    model = RehabMLP(input_dim=X_train.shape[1])
    criterion = nn.BCEWithLogitsLoss()
    optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=weight_decay)
    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=80)
    train_loader = make_loader(X_train, y_train)
    valid_loader = make_loader(X_valid, y_valid, shuffle=False)
    best_loss, best_state, wait, patience = float("inf"), None, 0, 12

    for epoch in range(80):
        model.train()
        for xb, yb in train_loader:
            optimizer.zero_grad()
            loss = criterion(model(xb), yb)
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=2.0)
            optimizer.step()
        scheduler.step()

        model.eval()
        with torch.no_grad():
            valid_losses = [criterion(model(xb), yb).item() for xb, yb in valid_loader]
        valid_loss = float(np.mean(valid_losses))
        if valid_loss < best_loss:
            best_loss = valid_loss
            best_state = {k: v.clone() for k, v in model.state_dict().items()}
            wait = 0
        else:
            wait += 1
        if wait >= patience:
            break

    model.load_state_dict(best_state)
    return model

def predict_proba(model, X_array):
    model.eval()
    with torch.no_grad():
        logits = model(torch.tensor(X_array, dtype=torch.float32)).numpy()
    return 1 / (1 + np.exp(-logits))

results = []
outer_cv = GroupKFold(n_splits=5)
for fold, (train_idx, test_idx) in enumerate(outer_cv.split(X, y, groups), start=1):
    X_train_raw, X_test_raw = X.iloc[train_idx], X.iloc[test_idx]
    y_train_all, y_test = y[train_idx], y[test_idx]
    X_train_all = preprocess.fit_transform(X_train_raw).toarray()
    X_test = preprocess.transform(X_test_raw).toarray()
    X_train, X_valid, y_train, y_valid = train_test_split(
        X_train_all, y_train_all, test_size=0.2,
        stratify=y_train_all, random_state=SEED
    )

    model = train_model(X_train, y_train, X_valid, y_valid, lr=1e-3)
    proba = predict_proba(model, X_test)
    results.append({
        "fold": fold,
        "auc": roc_auc_score(y_test, proba),
        "brier": brier_score_loss(y_test, proba),
    })

print(pd.DataFrame(results))

この例では、ColumnTransformer で数値変数を補完・標準化し、カテゴリ変数を OneHot 化します。前処理は fold 内で fit するため、評価データの情報が学習に混ざりません。AdamW・CosineAnnealingLR・勾配クリッピング・Early Stopping を入れています。実際の研究では、同じ分割で ロジスティック回帰正則化線形モデル勾配ブースティング とも比較します。

実務では、optimizer の選択よりも、データ分割の妥当性が先です。同じ患者の複数評価・同じ動画から切り出した複数サンプル・同じ施設の強い偏りがあると、学習は見かけ上うまく進みます。しかし、未知データで同じ性能が出るとは限りません。最適化の議論は、患者単位・施設単位の独立性を保った後に行います。

// 07 · MYTHSよくある誤解

誤解: Adam が常に最良
Adam は初期探索で扱いやすい optimizer ですが、タスクによっては SGD + Momentum が同等以上に安定する場合があります。検証データで比較します。
誤解: Weight Decay は L2 正則化と同じ
SGD では近い意味になりますが、Adam では実装上の違いが問題になります。AdamW は Weight Decay を分離して扱います。
誤解: 学習率は 1e-3 固定でよい
1e-3 は出発点にはなりますが、画像・時系列・表形式・小データで適切値は変わります。対数スケールで探索します。
誤解: Early Stopping を入れれば過学習は解決する
Early Stopping は過学習を抑える補助です。症例数不足・施設差・ラベルのばらつき・評価時点の不一致は別に扱います。

// 08 · WRITING論文 Methods に書くこと

最適化の記載は、再現性に直結します。「Adam を用いた」だけでは不十分です。学習率・Weight Decay・バッチサイズ・エポック数・Early Stopping の基準・スケジューラ・乱数 seed を記載します。画像モデルなら、事前学習の有無と凍結した層も書きます。

// METHODS CHECK
  • optimizer 名とバージョン、主要パラメータを記載。
  • 初期学習率と探索範囲を記載。
  • 学習率スケジューラの種類と設定を記載。
  • Weight Decay・Dropout・Batch Normalization の有無を記載。
  • Early Stopping の監視指標、patience、最大 epoch を記載。
  • 検証セットとテストセットを分けた方法を記載。
  • 同じ分割で比較したベースラインモデルを記載。
  • AUC だけでなく calibration や Brier score も示す。

Results では、最終 epoch の値だけでなく、学習曲線を確認できる形が望ましいです。たとえば train loss は下がるのに validation loss が上がる場合、過学習が疑われます。train loss も validation loss も下がらない場合、学習率が低い・入力標準化が不十分・モデル容量が足りないなどを考えます。validation loss が大きく上下する場合、学習率が高い・バッチサイズが小さい・症例数が少ない可能性があります。

さらに、最適化の成功と臨床的有用性は別です。損失が下がっても、重要なサブグループで性能が落ちることがあります。たとえば重症例・認知機能低下例・高齢例・特定施設の症例で性能を確認します。これは、モデルの公平性や適用可能性を考える上でも重要です。

査読では、テストデータを使って学習率や epoch を決めていないかが問われます。また、fold ごとに Early Stopping の epoch が大きく違う場合、モデルが不安定な可能性があります。TRIPOD+AI は予測モデル報告の透明性に関係します[7]。PROBAST+AI はバイアスと適用可能性を点検する際に有用です[8]

もう一つの注意点は、乱数 seed です。小規模データでは、初期値や fold の違いだけで結果が動くことがあります。可能であれば複数 seed の感度分析を行い、平均値だけでなくばらつきも示します。これは、最適化の安定性を読者に伝える助けになります。

// 09 · CHECKLIST実装前チェックリスト

  • 01学習率を対数スケールで探索した
  • 02Early Stopping にテストデータを使っていない
  • 03前処理を train fold 内だけで fit している
  • 04optimizer・scheduler・Weight Decay を Methods に書ける
  • 05fold ごとの学習曲線を確認した
  • 06線形モデルや木系モデルと同じ分割で比較した
  • 07AUC だけでなく Brier score や calibration も見た
  • 08施設差や患者単位の重複を GroupKFold などで扱った

学習が進んだかどうかは、訓練損失だけで判断しません。validation loss・AUC・Brier score・calibration を並べると、過学習や確率のずれを発見しやすくなります。研究の透明性も高まります。

// 10 · QUIZ理解度チェック

  1. Q1AdamW の説明として最も適切なのはどれですか。
    • Adam より自動的に高い AUC になる方法
    • Weight Decay を Adam から分離して扱う方法
    • 学習率探索を不要にする方法
    • 標準化を不要にする方法
    SHOW ANSWER
    B. AdamW は Decoupled Weight Decay が特徴です。
  2. Q2Early Stopping の監視に使ってはいけないデータはどれですか。
    • train fold 内の検証セット
    • inner validation
    • 最終評価用の test set
    • cross-validation の validation fold
    SHOW ANSWER
    C. テストデータは最終評価用に保ちます。
  3. Q3学習率が高すぎる場合に起こりやすい現象はどれですか。
    • 損失が不安定に上下する
    • すべての欠測が自動補完される
    • calibration が自動で改善する
    • GroupKFold が不要になる
    SHOW ANSWER
    A. 更新幅が大きく、谷底を飛び越えることがあります。
  4. Q4小規模リハ研究で NN を使うときの考え方として適切なのはどれですか。
    • NN だけを報告すれば十分
    • test set で学習率を決める
    • 線形・正則化・木系モデルと同じ分割で比較する
    • Early Stopping を入れれば外部検証は不要
    SHOW ANSWER
    C. 性能差と解釈性を同じ条件で比べます。

// 11 · FAQよくある質問

optimizer は Adam を選んでおけば安心ですか?
Adam は初期探索で扱いやすい optimizer ですが、必ずしも最良とは限りません。画像分類などでは SGD + Momentum のほうが汎化が安定することがあります。検証データで複数 optimizer を同じ条件で比較するのが基本です。
学習率はどう探索すればよいですか?
対数スケール(例: 1e-3, 3e-4, 1e-4, 3e-5, 1e-5)で範囲を絞ります。テストデータを使って学習率を選ぶと リーケージ になるため、訓練データから切り出した検証セットや CV で選びます。
Weight Decay と L2 正則化は同じですか?
SGD では近い意味になりますが、Adam ではゲイン補正と相互作用して挙動が変わります。AdamW は Weight Decay を Adam の更新ステップから分離(decoupled)して扱うため、深層学習で広く使われます。

// REF参考文献

  1. Robbins H, Monro S. A stochastic approximation method. Annals of Mathematical Statistics 1951;22(3):400-407.
  2. Polyak BT. Some methods of speeding up the convergence of iteration methods. USSR Computational Mathematics and Mathematical Physics 1964;4(5):1-17.
  3. Kingma DP, Ba J. Adam: A method for stochastic optimization. International Conference on Learning Representations 2015.
  4. Loshchilov I, Hutter F. Decoupled weight decay regularization. International Conference on Learning Representations 2019.
  5. Loshchilov I, Hutter F. SGDR: Stochastic gradient descent with warm restarts. International Conference on Learning Representations 2017.
  6. Smith LN. Cyclical learning rates for training neural networks. IEEE Winter Conference on Applications of Computer Vision 2017:464-472.
  7. Collins GS, Moons KGM, Dhiman P, et al. TRIPOD+AI statement: updated guidance for reporting clinical prediction models that use regression or machine learning methods. BMJ 2024;385:e078378.
  8. Wolff RF, Moons KGM, Riley RD, et al. PROBAST+AI: a risk of bias assessment tool for AI prediction model studies. BMJ 2025;388:e082505.