この用語『勾配降下法』を理解するうえで併せて押さえたい関連キーワード群です。 クリック(ホバー)で関連用語ページに飛べます。
🍰 まずはやさしく
正解に近づくための階段のような方法です。
間違いをできるだけ少なくするために使います。
スマホのアプリが学習する仕組みに似ています。
この章では基本のやり方と注意点を読みます。
勾配降下法 ── 勾配を使って損失を最小化する手法
🍰 まずはやさしく
AIの心臓部となる重要な仕組みです。
データの分析で最適な答えを出すために使います。
部活の記録から傾向を出すときなどに役立ちます。
この章では定義や使い方を順番に読みます。
深層学習の心臓部。 線形回帰、 ロジスティック回帰、 ニューラルネットすべてのパラメータ最適化はこれが基本。
本ページでは「gradient descent」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
「gradient descent」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。
🍰 まずはやさしく
霧の中での山下りにたとえられる方法です。
一番低い谷底にたどり着くために使います。
買い物で一番安い店をさがす感覚に似ています。
この章では仕組みや歩き方のコツを読みます。
山下りの比喩:
歩幅 = 学習率。 大き過ぎると崖を越えて反対側に行く、 小さ過ぎると永遠に着かない。
勾配降下法は『目的関数 $L(\theta)$ を最小化するため、 $\theta$ を勾配 $\nabla L$ の逆方向に少しずつ動かす』最適化アルゴリズム。 機械学習の学習はほぼすべてこの方法か、 その発展形(SGD, Adam, AdamW 等)で行われる。 一度に全データを使う Batch GD、 ランダム 1 サンプルの SGD、 中間の Mini-batch GD があり、 後者が深層学習の標準。
勾配降下法は SGD → Momentum (Polyak 1964) → AdaGrad (Duchi 2011) → RMSProp (Hinton 2012) → Adam (Kingma 2014) → AdamW (Loshchilov 2017) と進化し、 現代 DNN は AdamW + cosine decay が標準。 LR を 1e-3〜3e-4 で開始し warmup + decay する設定が ResNet/Transformer 系で安定し、 Llama2 学習も基本この組合せ。
本ページでは SSDSE-B-2026 を使った線形回帰を勾配降下法で解く例を示す: L(θ) = (1/2n)Σ(yᵢ - θ₀ - θ₁xᵢ)² を θ₀, θ₁ で偏微分し、 47 都道府県の (総人口, 出生数) で標準化のうえ学習率 η を変えながら損失曲線が収束する様子を観察する。 これにより、 closed-form 解 (X'X)⁻¹X'y と勾配降下解が一致することを実体験できる。
損失関数の曲線の上で、 勾配降下法がどう動くかを自分の手で動かして確かめられる。 学習率 η スライダーを動かし、 グラフをクリック(タップ)して開始点を決め、 「▶ ステップ実行」または「⏯ 自動再生」で更新式 xt+1 = xt − η · L′(xt) の 1 歩ずつを観察しよう。 η が小さい=遅い、 適度=収束、 大きすぎ=振動/発散 を目で体感できる。
| 反復 t | x | L(x) | L′(x)(勾配) |
| 0 | — | — | — |
🔎 動かして分かること
・凸関数 f(x)=0.25x²+0.5(勾配 L′=0.5x)は数学的に厳密で、 更新は xt+1 = xt(1 − 0.5η) となる。 よって η<2 で単調に谷へ、 2<η<4 で反対側へ飛び越えつつ収束、 η=4 で一定振幅の振動、 η>4 で発散。 これは凸・L-平滑な問題の収束条件 0<η<2/L(ここでは L=0.5)そのもの。
・多峰性では、 開始点の位置で落ちる谷が変わる。 浅い谷に捕まると勾配が 0 でも大域的最小には届かない ── これが 局所最適解 と 大域的最適解 の違い。 右側からスタートすると局所解に停留しやすい。
🍰 まずはやさしく
計算式で表した正解へのルートです。
数値をどう書き換えるかを決めるために使います。
テストの点数を上げるための計画表のようなものです。
この章では数式の記号の意味について読みます。
勾配降下法。 $\eta$ は学習率、 $L$ は損失関数。
数式の各記号が『何の量で、 どの空間に住み、 どんな単位を持つか』を意識すると、 暗記でなく構造として理解できます。 SSDSE-B の都道府県データに当てはめて、 各シンボルが何に対応するかを上の Python 実装で確認しましょう。
まずは本ページの『💡 30 秒で分かる結論』と『🎨 直感で掴む』で全体像を掴み、 次に『🧮 実値で計算してみる』を 手を動かして追体験するのが最短です。 数式や深い理論はその後で十分。
本ページの『🌐 関連手法・派生』『🔗 関連用語』で対比される手法を確認し、 それぞれの適用条件と得意・不得意を表で比較するのが効果的です。 SSDSE-B のような共通データセットで両方走らせて結果を見ると違いが体感できます。
最急降下法の 1 反復は $O(nd)$(全サンプル $n$ × 特徴数 $d$ を 1 回なめる)で、 反復 $T$ 回なら $O(nTd)$。 SSDSE の 47 都道府県 × 5 変数なら 1 反復が 235 回の積和で、 1000 反復しても 23.5 万回。一瞬です。 ところが $n=10^6$ になると 1 反復で 500 万回、 1000 反復で 50 億回になり、 「1 反復あたり全データを読む」ことが効いてきます。 ここで効くのが確率的勾配降下法(SGD)で、 1 反復をミニバッチ $b$ 件に絞れば $O(bTd)$。 $b=32$ なら $n$ に依存しなくなるので、 データが 100 万件でも 1 反復のコストは 47 件のときと変わりません。 「$n$ が効く反復法は、バッチ化で $n$ を切り離せないか」を最初に疑うのが定石です。
『点推定値』だけでなく『不確実性(CI、 SE、 分散)』『前提条件のチェック結果』『代替手法との比較』『データ取得日と seed』をセットで報告するのが標準。 査読・レビューで問われる典型ポイントです。
GD には数学的な収束保証がある。 関数の性質によって「何 iteration で目標精度 ε に到達するか」が定理化されている。
| 関数クラス | GD の収束 | 加速 GD の収束 |
|---|---|---|
| 凸 + L-Lipschitz 勾配 | O(1/T) | O(1/T²) |
| 強凸 + L-Lipschitz | O((1-μ/L)^T) 線型 | O((1-√(μ/L))^T) |
| 非凸(一般) | 局所最適への収束のみ保証 | 同左 |
| 非凸 + 良性(深層 NN) | 経験的にうまく動く | 理論未解明 |
深層学習が「理論なしで動く」のは、 非凸であるにも関わらず 1 次法でうまく収束するから。 この理由は active な研究分野(loss landscape, neural tangent kernel, mean field theory)。 SSDSE の線形回帰は強凸なので GD の理論保証が完全に成立する例。
1次元の例:$L(\theta) = (\theta - 3)^2$
都道府県データで線形回帰モデル『就業者数 = w × 人口 + b』を勾配降下で学習。 SSDSE-B の人口(A1101)と就業者数(C3301)を使い、 ミニバッチ(10 県ずつ)で w, b を更新していく。
| 項目 | 条件 / 入力 | 結果 / 解釈 |
|---|---|---|
| 学習率 α=0.001 | 10 epoch | loss 0.85 → 0.41 |
| 学習率 α=0.01 | 10 epoch | loss 0.85 → 0.12 |
| 学習率 α=0.1 | 10 epoch | loss 0.85 → 発散 |
| Momentum β=0.9 | α=0.01 | より速い収束 |
| Adam α=0.001 | デフォルト | ロバスト |
| SGD + warmup | 5 epoch warmup | 学習安定 |
※ 数値は SSDSE-B-2026.csv から抽出した実値、 もしくは典型的な学習設定での目安値です。 細部の数値は前処理・乱数 seed・実装により変動します。
勾配降下法には多数の派生があるが、 何が違うのかは 同じデータで損失曲線を重ね描き しないと体感できない。 SSDSE-B-2026 の総人口 → 出生数の単回帰を題材に、 4 手法を PyTorch optim で並行訓練し、 反復ごとの損失を比較する。
Vanilla GD は θ ← θ - η∇L。 SGD は同式だがミニバッチで ∇L を近似。 Momentum は v ← βv + ∇L、 θ ← θ - ηv で過去勾配を引きずる。 Adam は m, v の 1 次・2 次モーメントを推定し θ ← θ - η m̂/(√v̂+ε) で各次元の学習率を自動調整する。 SSDSE-B-2026 のように特徴量スケールが百万〜千万のとき、 Adam の自動スケーリングが効く。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import pandas as pd import numpy as np import torch import torch.nn as nn df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] xr = df['A1101'].values.astype(float) yr = df['A4101'].values.astype(float) X = torch.tensor((xr - xr.mean()) / xr.std(), dtype=torch.float32).unsqueeze(1) y = torch.tensor((yr - yr.mean()) / yr.std(), dtype=torch.float32).unsqueeze(1) def train(opt_name, lr=0.05, epochs=200): torch.manual_seed(0) model = nn.Linear(1, 1) if opt_name == 'GD': opt = torch.optim.SGD(model.parameters(), lr=lr) if opt_name == 'SGD': opt = torch.optim.SGD(model.parameters(), lr=lr) if opt_name == 'Momentum': opt = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9) if opt_name == 'Adam': opt = torch.optim.Adam(model.parameters(), lr=lr) losses = [] for _ in range(epochs): pred = model(X); loss = ((pred - y) ** 2).mean() opt.zero_grad(); loss.backward(); opt.step() losses.append(loss.item()) return losses for name in ['GD', 'SGD', 'Momentum', 'Adam']: hist = train(name) print(f'{name:9s}: final loss = {hist[-1]:.6f}, ' f'epoch_to_loss<0.05 = {next((i for i,v in enumerate(hist) if v<0.05), -1)}') |
🎯 このコードでやること: SSDSE-B-2026 の (A1101, A4101) で 4 つの optimizer を 200 epoch 学習し、 最終損失と収束 epoch を比較する。
📥 入力データ (SSDSE-B-2026 の 2023 年 47 都道府県、 標準化済み):
📤 実行結果:
💬 結果の読み方: 47 県の単回帰は線形で凸なので、 どの optimizer も最終損失はほぼ同じ 0.0091 付近($1-r^2$、 相関 $r=0.995$)に収束する。 違いは「収束速度」。 Momentum は 5 epoch で損失 0.05 を切り、 vanilla GD と SGD は 17 epoch、 Adam は 19 epoch とやや慎重に進む。 高次元・非凸(ニューラルネット)になると Adam の優位が顕著になる。
L2 正則化と等価ではない。 正しくは AdamW を使う(Loshchilov & Hutter 2017)ここでは勾配降下法(Gradient Descent, GD)の挙動を、 単に「重みを更新するアルゴリズム」として暗記するのではなく、 損失曲面の幾何・更新ベクトルの方向と大きさ・学習率の役割・確率的サンプリングの効果という 4 つの軸で再構成する。 SSDSE-B-2026(47 都道府県の社会経済指標)を用いて、 線形回帰・ロジスティック回帰・浅い MLP の 3 タスクに対し、 同一の初期値から GD/SGD/Mini-batch/Momentum/Adam を走らせ、 収束速度・最終損失・パラメータの最終位置を直接比較する。 これにより、 抽象的な「最適化アルゴリズムの選択」が、 具体的な数値挙動として腹落ちすることを目指す。
勾配降下法を理解する上で押さえるべき軸は次の 4 つである。 教科書では「式を覚えろ」とだけ書かれがちだが、 それぞれの軸が独立に効くので、 まずは分けて整理する。
軸 1:方向。 損失 $L(\theta)$ を局所的に最も急に減らす方向は 負の勾配 $-\nabla L(\theta)$ である。 これは線形近似 $L(\theta+\Delta) \approx L(\theta) + \nabla L(\theta)^\top \Delta$ から、 $\|\Delta\|$ を固定したときに右辺を最小化する $\Delta$ が $-\nabla L(\theta)/\|\nabla L(\theta)\|$ になることから導かれる。 これは「下りる方向の選択」であり、 凸でも非凸でも局所的には成立する。
軸 2:歩幅。 一歩あたりの移動量を決めるのが学習率 $\eta$ である。 大きすぎれば発散し、 小さすぎれば収束が遅い。 凸かつ $L$ が $L$-平滑(リプシッツ勾配定数 $L_{\rm smooth}$)であれば $\eta \le 1/L_{\rm smooth}$ が安全な選択であることが知られる。 実務ではこの理論値を直接使わず、 学習率探索(LR Range Test)や warmup + cosine schedule で代替する。
軸 3:サンプリング。 損失 $L(\theta) = \frac{1}{n} \sum_{i=1}^n \ell(\theta; x_i, y_i)$ の勾配を毎ステップ全データで計算するのがバッチ GD、 1 サンプルだけで近似するのが SGD、 数十〜数百サンプルで近似するのがミニバッチ GD である。 サンプリングはノイズを導入するが、 そのノイズが鞍点脱出やフラットミニマ選好に寄与することが近年わかってきた。
軸 4:履歴。 過去の勾配情報をどう活かすか。 Momentum は速度ベクトルを慣性として保ち、 Adam は各座標ごとに勾配の二乗和で歩幅を正規化する。 履歴を持つことで「過去に何度も同じ方向に押された座標は今回も同じ方向に大きく動く」「ノイズで激しく振動する座標は歩幅を縮める」といった適応的挙動が可能になる。
| アルゴリズム | サンプル数/step | 学習率の扱い | 履歴の扱い | 代表的用途 |
|---|---|---|---|---|
| バッチ GD | $n$ 全件 | 固定 or schedule | なし | 小規模・凸問題 |
| SGD | 1 件 | 固定 or decay | なし | 大規模・オンライン |
| Mini-batch GD | 32〜512 件 | 固定 or schedule | なし | 深層学習標準 |
| Momentum | ミニバッチ | 固定 or schedule | 速度ベクトル | 画像分類 SOTA |
| Nesterov | ミニバッチ | 固定 or schedule | 先読み速度 | 理論で収束加速 |
| AdaGrad | ミニバッチ | 座標別に縮小 | 勾配二乗累積 | 疎データ・NLP 初期 |
| RMSProp | ミニバッチ | 座標別に正規化 | 勾配二乗 EMA | RNN 学習 |
| Adam | ミニバッチ | 座標別に正規化 | 速度 + 二乗 EMA | 汎用標準 |
| AdamW | ミニバッチ | 座標別に正規化 | 速度 + 二乗 EMA + decoupled wd | Transformer 学習 |
読み方:「バッチ GD → Mini-batch GD → Momentum → Adam」は、 軸 3(サンプリング)→ 軸 4(履歴)の順に高度化していく流れになっている。 Adam だけを覚えても理解は深まらない。 各軸ごとに何が追加されたかを意識する。
勾配降下法は「数値が動く軌跡」「分布」「グループ間の違い」の 3 視点で観察すると、 動作原理が腹落ちする。 ここでは SSDSE-B-2026 の都道府県データを使って 3 つの異なる視点を提示する。
上の散布図は、 GD が解くべき「線形回帰タスクそのもの」を視覚化したものである。 各点までの垂直距離の二乗平均を損失 $L(w, b) = \frac{1}{n}\sum_i (y_i - (w x_i + b))^2$ として、 $(w, b)$ 空間で滑り降りるのが GD だ。 損失関数は $(w, b)$ について凸放物面になるので、 学習率さえ間違えなければ必ず大域最小に達する。
ヒストグラムから読み取れるのは「データのスケール」である。 GD の学習率はデータのスケールに依存するため、 標準化(zero mean, unit variance)またはスケーリング(min-max)が事前に必要だ。 47 都道府県のような小規模データでも、 1 特徴量で 10⁶ 倍のスケール差は普通にある。 標準化前のスケールを把握するためにヒストグラムは必須の前処理ツールである。
箱ひげ図は、 同じ optimizer を複数回走らせたときの「結果の安定性」を可視化するのに最適だ。 SGD のように確率的要素を持つ optimizer は、 ミニバッチサンプルの順序によって最終損失が異なる。 箱の高さが安定性、 中央値が平均的性能、 ひげの長さが最悪ケースを意味する。 「中央値が低くて箱も小さい」optimizer が実務的に最強だ。
SSDSE-B-2026 のうち A1101(総人口)と A4101(出生数)を選び、 標準化したうえで $y = w \cdot x + b$ の最小二乗フィットを GD で解く。 手計算と Python 実装を並べて、 学習率を 0.001 / 0.01 / 0.1 / 0.5 と変えた場合の挙動を比較する。
| 学習率 $\eta$ | 10 epoch 損失 | 50 epoch 損失 | 200 epoch 損失 | 判定 |
|---|---|---|---|---|
| 0.001 | 0.965 | 0.824 | 0.456 | 遅すぎ |
| 0.01 | 0.698 | 0.146 | 0.009 | やや遅 |
| 0.05 | 0.158 | 0.009 | 0.009 | 適切 |
| 0.1 | 0.027 | 0.009 | 0.009 | 速いが OK |
| 0.5 | 0.009 | 0.009 | 0.009 | 上限付近・高速収束 |
| 1.0 | 1.000 | 1.000 | 1.000 | 発散(振動) |
読み方:47 県の単回帰(特徴量 1 個、 サンプル 47 個、 標準化済み)の場合、 損失 $L=\mathrm{mean}(\mathrm{err}^2)$ のヘッセ行列は $2\cdot\mathrm{Var}(x)=2$ なので、 収束条件は $\eta < 2/2 = 1$。 表でも $\eta = 0.5$ までは高速に収束し(10 epoch で損失 0.009)、 $\eta = 1.0$ で損失が 1.0 のまま下がらず発散していることが見て取れる。 凸かつ平滑な問題は理論値で安全領域を計算できる。
🎯 このコードでやること:SSDSE-B-2026 の A1101(総人口) と A4101(出生数) を読み込み、 標準化後に GD ループを numpy 手書きで実装する。 ライブラリに頼らず、 勾配計算と更新式が一致するかを確認する。
📥 入力データ (SSDSE-B-2026 の 2023 年 47 都道府県、 標準化前):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import numpy as np import pandas as pd # SSDSE-B-2026 を読み込む(cp932、 単位行をスキップ) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] x_raw = df['A1101'].values.astype(float) # 総人口 y_raw = df['A4101'].values.astype(float) # 出生数 # 標準化(GD には必須の前処理) x = (x_raw - x_raw.mean()) / x_raw.std() y = (y_raw - y_raw.mean()) / y_raw.std() # 初期パラメータ w, b = 0.0, 0.0 eta = 0.05 n = len(x) # GD ループ history = [] for epoch in range(200): pred = w * x + b err = pred - y loss = (err ** 2).mean() # 勾配は ∂L/∂w = 2/n Σ err_i * x_i、 ∂L/∂b = 2/n Σ err_i grad_w = 2.0 * (err * x).mean() grad_b = 2.0 * err.mean() w -= eta * grad_w b -= eta * grad_b history.append(loss) print(f'最終パラメータ: w = {w:.4f}, b = {b:.4f}') print(f'最終損失: {history[-1]:.6f}') print(f'損失推移: epoch 0 → {history[0]:.4f}, ' f'epoch 50 → {history[50]:.4f}, epoch 200 → {history[-1]:.4f}') |
📤 実行結果:
💬 結果の読み方:標準化済みデータでは正解 $w = \mathrm{Cov}(x,y)/\mathrm{Var}(x) = \rho(x,y) \approx 0.995$、 $b \approx 0$ となる。 GD は学習率 0.05・200 epoch で $w = 0.9954$ に到達し、 最小二乗の解析解($w = 0.995425$、 閉形式で求まる)とほぼ一致する。 この問題は凸なので GD は解析解へ収束するが、 学習率が小さすぎれば(表の $\eta=0.001$ を参照)200 epoch では届かない ── 「学習率と epoch 数のトレードオフ」は常につきまとう。 解析解が存在しない非線形モデルでは GD が唯一の選択肢になる。
🎯 このコードでやること:同じデータに対し $\eta \in \{0.001, 0.01, 0.05, 0.1, 0.5, 1.0\}$ で GD を走らせ、 表 R642-1 の数値を再現する。 学習率が大きすぎると発散する現象を直接観察できる。
📥 入力データ: 直前のコードと同じ標準化済み $(x, y)$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import numpy as np import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] x = df['A1101'].values.astype(float) y = df['A4101'].values.astype(float) x = (x - x.mean()) / x.std() y = (y - y.mean()) / y.std() def run_gd(eta, epochs=200): w, b = 0.0, 0.0 history = [] for _ in range(epochs): pred = w * x + b err = pred - y loss = (err ** 2).mean() if not np.isfinite(loss): history.append(float('nan')) break history.append(loss) w -= eta * 2.0 * (err * x).mean() b -= eta * 2.0 * err.mean() return history for eta in [0.001, 0.01, 0.05, 0.1, 0.5, 1.0]: h = run_gd(eta) vals = [h[i] if i < len(h) else float('nan') for i in [9, 49, 199]] print(f'eta={eta:5.3f} | 10ep={vals[0]:.4f} | 50ep={vals[1]:.4f} | 200ep={vals[2]:.4f}') |
📤 実行結果:
💬 結果の読み方:表 R642-1 と一致する。 $\eta = 0.5$ は安定限界 $\eta < 1$ の内側なので 10 epoch で損失 0.009 まで一気に収束する。 一方 $\eta = 1.0$ では更新が対称点へ跳ね返り、 損失が 1.0 のまま 200 epoch を通じて一度も下がらない。 GD 実装では「損失曲線が下がっているか」を全 epoch にわたって確認し、 理論的な安定境界 $\eta < 1$ を超えないことが重要である。
🎯 このコードでやること:47 県を 8 サンプルずつのミニバッチに分け、 SGD / Momentum / Adam の 3 通りで 100 epoch 学習。 各 epoch の最終損失を記録し、 収束パターンを比較する。
📥 入力データ: 標準化済みの (A1101, A4101) 47 県、 ミニバッチサイズ 8。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | import numpy as np import pandas as pd np.random.seed(0) # 実行のたびに同じ結果が出るようにする df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] x = df['A1101'].values.astype(float); y = df['A4101'].values.astype(float) x = (x - x.mean()) / x.std() y = (y - y.mean()) / y.std() n = len(x) batch = 8 eta = 0.05 def sgd_step(w, b, xb, yb): err = (w * xb + b) - yb return w - eta * 2.0 * (err * xb).mean(), b - eta * 2.0 * err.mean() def momentum_step(w, b, xb, yb, vw, vb, mu=0.9): err = (w * xb + b) - yb gw = 2.0 * (err * xb).mean(); gb = 2.0 * err.mean() vw = mu * vw + gw; vb = mu * vb + gb return w - eta * vw, b - eta * vb, vw, vb def adam_step(w, b, xb, yb, t, mw, mb, vw, vb, beta1=0.9, beta2=0.999, eps=1e-8): err = (w * xb + b) - yb gw = 2.0 * (err * xb).mean(); gb = 2.0 * err.mean() mw = beta1 * mw + (1 - beta1) * gw; mb = beta1 * mb + (1 - beta1) * gb vw = beta2 * vw + (1 - beta2) * gw ** 2; vb = beta2 * vb + (1 - beta2) * gb ** 2 mhw = mw / (1 - beta1 ** t); mhb = mb / (1 - beta1 ** t) vhw = vw / (1 - beta2 ** t); vhb = vb / (1 - beta2 ** t) return (w - eta * mhw / (vhw ** 0.5 + eps), b - eta * mhb / (vhb ** 0.5 + eps), mw, mb, vw, vb) results = {} for name in ['SGD', 'Momentum', 'Adam']: w, b, vw, vb, mw, mb = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 losses = [] t = 0 for epoch in range(100): idx = np.random.RandomState(epoch).permutation(n) for start in range(0, n, batch): t += 1 ix = idx[start:start + batch] xb, yb = x[ix], y[ix] if name == 'SGD': w, b = sgd_step(w, b, xb, yb) elif name == 'Momentum': w, b, vw, vb = momentum_step(w, b, xb, yb, vw, vb) else: w, b, mw, mb, vw, vb = adam_step(w, b, xb, yb, t, mw, mb, vw, vb) # epoch 終了時の全体損失 losses.append(((w * x + b - y) ** 2).mean()) results[name] = losses for name, h in results.items(): print(f'{name:9s}: 1ep={h[0]:.4f}, 10ep={h[9]:.4f}, 50ep={h[49]:.4f}, 100ep={h[-1]:.4f}') |
📤 実行結果:
💬 結果の読み方:3 つの optimizer の最終損失はほぼ同じ(約 0.009)に到達するが、 初期の収束速度が異なる。 Momentum は 1 epoch で既に損失 0.044 まで下がり最速、 SGD は 0.285、 Adam は 0.514 と最初に出遅れる(バイアス補正の初期挙動)。 ここで重要なのは「Adam は万能ではない」点だ。 線形回帰のような単純な凸問題では Momentum の方が速い。 Adam が真価を発揮するのは、 勾配のスケールが座標ごとに大きく異なる非凸問題(深層学習)である。
🎯 このコードでやること:固定学習率 vs cosine annealing スケジュールの比較。 同じデータ、 同じ初期値、 同じアルゴリズムで、 スケジュール導入だけで収束精度がどう変わるかを観察。
📥 入力データ: 標準化済み (A1101, A4101)、 200 epoch、 初期学習率 0.1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import numpy as np import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] x = df['A1101'].values.astype(float); y = df['A4101'].values.astype(float) x = (x - x.mean()) / x.std() y = (y - y.mean()) / y.std() eta0 = 0.1 epochs = 200 def cosine_lr(epoch, eta0, T): return 0.5 * eta0 * (1.0 + np.cos(np.pi * epoch / T)) results = {} for sched in ['fixed', 'cosine']: w, b = 0.0, 0.0 losses = [] for ep in range(epochs): eta = eta0 if sched == 'fixed' else cosine_lr(ep, eta0, epochs) pred = w * x + b err = pred - y losses.append((err ** 2).mean()) w -= eta * 2.0 * (err * x).mean() b -= eta * 2.0 * err.mean() results[sched] = (w, b, losses) for sched, (w, b, h) in results.items(): print(f'{sched:7s}: w={w:.6f}, b={b:.6f}, ' f'final loss={h[-1]:.6f}') print(f'最小二乗解析解: w=0.995425, b=0.000000') |
📤 実行結果:
💬 結果の読み方:この凸問題では、 学習率 $\eta = 0.1$ は十分小さく安全な領域なので、 fixed でも 200 epoch あれば解析解に到達する。 cosine スケジュールの真価は「最初は大きな歩幅で広い領域を探索し、 後半は小さな歩幅で精密フィット」という点にあるため、 学習率を 0.5 のような大きな値から開始した場合に最も効く。 深層学習では cosine + warmup が標準だ。
SGD では「1 サンプル」を選んで勾配を計算する。 SSDSE-B-2026 のように都道府県をサンプルとする場合、 どの県を選ぶかで勾配の大きさが大きく異なる。 標準化済み線形回帰モデル $L = (wx + b - y)^2$ の勾配は、 県ごとに次のようになる。
| 順位 | 都道府県 | 標準化人口 $x$ | 標準化出生数 $y$ | 勾配 $|2xy|$(初期) |
|---|---|---|---|---|
| 1 | 東京都 | +4.13 | +4.18 | 34.52 |
| 2 | 神奈川県 | +2.38 | +2.27 | 10.80 |
| 3 | 大阪府 | +2.21 | +2.35 | 10.37 |
| 4 | 愛知県 | +1.75 | +1.94 | 6.77 |
| 5 | 埼玉県 | +1.69 | +1.57 | 5.31 |
| … | … | … | … | … |
| 43 | 新潟県 | -0.19 | -0.27 | 0.10 |
| 44 | 宮城県 | -0.14 | -0.19 | 0.05 |
| 45 | 京都府 | -0.04 | -0.09 | 0.01 |
| 46 | 広島県 | +0.03 | +0.07 | 0.00 |
| 47 | 茨城県 | +0.06 | -0.03 | 0.00 |
読み方:東京都の勾配は他県の数倍〜数十倍($|2xy|\approx34.5$)。 SGD で東京都を引いた回には大きく動き、 平均に近い茨城・広島を引いた回はほとんど動かない($|2xy|\approx0$)。 ミニバッチ化(複数県を一度に処理)するとこの偏りが平均化され、 学習が安定する。 これがミニバッチ SGD が SGD より好まれる理由の一つ。
勾配降下法の挙動を決める最大の要因は「損失曲面の形」である。 凸関数では局所最小 = 大域最小なので、 GD は必ず最良解に到達する。 非凸関数では局所最小・鞍点・プラトーが存在し、 初期値とサンプリング戦略次第で異なる解に収束する。
| 曲面タイプ | 代表モデル | GD の到達点 | 対策 |
|---|---|---|---|
| 凸放物面 | 線形回帰(MSE) | 大域最小(一意) | 学習率管理のみ |
| 凸(非放物面) | ロジスティック回帰 | 大域最小(一意) | 学習率管理のみ |
| 細長い谷 | 悪条件回帰 | 大域最小(遅い) | Momentum, 標準化 |
| 複数の極小 | 深層 MLP | 初期値依存の局所解 | SGD ノイズ, 多回試行 |
| 鞍点 | 高次元 NN | 停滞する可能性 | Momentum, ノイズ |
| プラトー | 飽和活性化を持つ NN | 勾配 ≈ 0 で停滞 | ReLU, BatchNorm |
| スケジュール名 | 数式 | 特徴 | 代表用途 |
|---|---|---|---|
| Fixed | $\eta_t = \eta_0$ | 最も単純 | 凸問題・短い学習 |
| Step Decay | $\eta_t = \eta_0 \cdot \gamma^{\lfloor t/k \rfloor}$ | 階段状に減少 | ResNet 学習で標準 |
| Exponential | $\eta_t = \eta_0 \cdot e^{-kt}$ | 滑らかに減少 | 古典 NN |
| Cosine | $\eta_t = \frac{\eta_0}{2}(1 + \cos(\pi t/T))$ | 滑らかな減衰 → 0 | 画像分類 SOTA |
| Cosine + Warmup | 線形 ramp + cosine | 初期不安定回避 | Transformer 学習 |
| One Cycle | 三角波 + 末尾減衰 | Super-convergence | fast.ai 流派 |
| Cyclic | 三角波を反復 | 鞍点脱出効果 | 非凸モデル探索 |
| タスク | 推奨 optimizer | 初期 lr | weight decay | batch size |
|---|---|---|---|---|
| 線形回帰 | バッチ GD or 解析解 | 0.01〜0.1 | 0 | 全件 |
| ロジスティック回帰 | L-BFGS or SGD | 0.01〜0.1 | 1e-4 | 全件 or 大 |
| ResNet 画像分類 | SGD + Momentum 0.9 | 0.1(warmup) | 1e-4 | 256 |
| BERT 事前学習 | AdamW | 1e-4 | 0.01 | 256〜8192 |
| GPT 事前学習 | AdamW | 6e-4 | 0.1 | 512〜数百万 |
| RL DQN | Adam | 1e-4 | 0 | 32〜256 |
(x - x.mean()) / x.std() で zero mean unit variance に。torch.nn.utils.clip_grad_norm_ で勾配ノルムを上限 1.0 程度に切る。勾配降下法は「最適化」という大樹の幹であり、 そこから多くの枝が伸びている。 上位概念・並列概念・派生概念をツリー状に整理する。
| 年 | 提唱者 | 手法 | 貢献 |
|---|---|---|---|
| 1847 | Cauchy | 最急降下法 | GD の原型を提唱 |
| 1951 | Robbins & Monro | 確率近似(SGD 原型) | 確率的勾配法の理論基盤 |
| 1964 | Polyak | Heavy-ball Momentum | 慣性項の導入 |
| 1983 | Nesterov | Nesterov Accelerated Gradient | 凸問題で理論最適収束 |
| 2011 | Duchi et al. | AdaGrad | 座標別適応学習率 |
| 2012 | Tieleman & Hinton | RMSProp | 指数移動平均で AdaGrad 改良 |
| 2014 | Kingma & Ba | Adam | Momentum + RMSProp の融合 |
| 2017 | Loshchilov & Hutter | AdamW | weight decay の分離 |
| 2017 | Smith | One Cycle Learning Rate | Super-convergence の発見 |
| 2023 | Chen et al. (Google) | Lion | 記号関数で軽量化 |
勾配降下法を理解した次に学ぶべきトピックを、 前提・並列・発展の 3 区分で整理する(実在する用語ページはリンク済み、 未整備のものは href="#" プレースホルダ)。
勾配降下法は「損失の偏微分の符号反転方向に少しだけ動く」という極めてシンプルな原理を持つ。 しかし、 この単純さに次の 4 つの工夫が乗ることで、 GPT・Stable Diffusion・AlphaGo といった現代 AI の学習を可能にしている。
SSDSE-B-2026 のような 47 サンプル × 数百列の小さなデータセットでも、 GD の挙動・学習率の感度・サンプリングノイズの効果はすべて観察できる。 数値を一度自分の手で動かすことが、 GD という概念を腹落ちさせる最短ルートである。
f(x) = (x-3)² の勾配降下で 3 ステップ更新する。
1 2 3 4 5 6 7 | def f(x): return (x-3)**2 def df(x): return 2*(x-3) x = 0.0 lr = 0.3 for i in range(3): x = x - lr * df(x) print(f"x_{i+1} = {x:.3f}, f = {f(x):.4f}") |
💬 手計算 (Step 2) x₃ = 2.808 と Python 出力が完全一致。
最小限のスニペットで動作確認できる例。 公的データ(SSDSE 等)を想定しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import numpy as np # シンプルな勾配降下 def gd(theta=0.0, lr=0.2, n_steps=20): for _ in range(n_steps): grad = 2 * (theta - 3) theta -= lr * grad return theta print(gd()) # 約 3.0 # PyTorch の SGD import torch theta = torch.zeros(1, requires_grad=True) optim = torch.optim.SGD([theta], lr=0.2) for _ in range(20): loss = (theta - 3) ** 2 optim.zero_grad(); loss.backward(); optim.step() print(theta.item()) |
公的データ SSDSE-B(47 都道府県社会・人口統計)を読み込み、 勾配降下法 を実際に動かす最小コードです。 引数のパスは平易さ優先で直書きしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932') df = df[df['SSDSE-B-2026'] == 2023] # 2023年の47都道府県のみ X = df[['A1101', 'A4101']].astype(float).values y = df['C3301'].astype(float).values # 正規化 X = (X - X.mean(0)) / X.std(0) y = (y - y.mean()) / y.std() w = np.zeros(2) b = 0.0 lr = 0.01 for ep in range(300): pred = X @ w + b err = pred - y grad_w = 2 * X.T @ err / len(y) grad_b = 2 * err.mean() w -= lr * grad_w b -= lr * grad_b if ep % 50 == 0: print(f'ep {ep}: loss={(err**2).mean():.4f}') print('最終 w:', w, 'b:', b) |
※ 上記スニペットは Python 3.10+ / pandas 2.x / numpy / scikit-learn を想定。 環境構築は『conda create -n ds python=3.11 pandas scikit-learn matplotlib』で十分です。
「最適化アルゴリズム」の世界で GD はどの位置にいるか。 階層構造で整理する。
最適化問題 min f(θ)
├── 解析解が存在
│ ├── 線形回帰 → 正規方程式
│ ├── ロジスティック回帰(小規模) → IRLS
│ └── 主成分分析 → 固有値分解
└── 反復解法
├── 0 次法(勾配を使わない)
│ ├── グリッドサーチ
│ ├── ベイズ最適化
│ └── 進化的アルゴリズム
├── 1 次法(勾配を使う) ★ GD はここ
│ ├── Batch GD / SGD / Mini-batch SGD
│ ├── Momentum / Nesterov
│ └── Adam / AdaGrad / RMSProp / LION
├── 2 次法(ヘッセ行列を使う)
│ ├── Newton 法
│ ├── 準ニュートン法 (BFGS, L-BFGS)
│ └── 信頼領域法
└── 制約付き最適化
├── 投影 GD(projected GD)
├── 交互方向乗数法 (ADMM)
└── 内点法
深層学習では 1 次法(特に Adam 系)が定番。 2 次法は精度高いが行列計算が O(n²) で大規模 NN に向かない。 L-BFGS は古典的 ML で根強い人気。 ベイズ最適化はハイパーパラメータ探索の主役。
『勾配降下法』は『最適化』カテゴリに属する重要概念で、 以下の関連概念群と密接につながっています。
最適化
├── 前提
│ └── 数学・統計の基礎
├── 勾配降下法 ← このページ
│ ├── 派生 1
│ ├── 派生 2
│ └── 応用
└── 並列・対比される手法
├── 別アプローチ A
└── 別アプローチ B
完全な概念マップは 🗺 概念マップ で確認できます。
Cauchy (1847) の最急降下法に起源。 ニューラルネットでは Rosenblatt (1958), Rumelhart (1986) で確立。 SGD (Robbins-Monro 1951), Momentum (Polyak 1964), AdaGrad (Duchi 2011), RMSProp (Tieleman 2012), Adam (Kingma 2014) と派生多数。 近年は LION (2023) など二次情報を擬似的に使う手法も。
原典は Cauchy (1847)で、 ニューラルネットどころかコンピュータより 100 年早い手法です。 Cauchy の動機は天体力学の連立方程式を解くことでした。 一方 SGD の原典 Robbins & Monro (1951) は「確率近似」という別分野の論文で、 ノイズのある観測から根を求める逐次法として提案されています。 今日の深層学習の中核 2 つが、どちらも機械学習と無関係な文脈で生まれたのは知っておく価値があります。
「坂を下る」の比喩は分かりやすいが、 実装すると 学習率・収束・鞍点・過学習といった現実問題に直面する。 ここでは SSDSE-B-2026 の人口データを使い、 「高齢者人口から総人口を予測する線形回帰モデル」を勾配降下法で学習し、 ① 手作り GD、 ② 学習率の比較、 ③ scikit-learn の SGDRegressor との比較を通して、 実務で起きるすべての問題を体験する。
47 都道府県の高齢者人口 A1303 から総人口 A1101 を予測する単純な線形回帰 $y = wx + b$ を、 平均二乗誤差を最小化する勾配降下法で学習する。 解析解(最小二乗法)が存在するため、 GD で同じ答えに到達できるかを比較できる。
$$L(w, b) = \frac{1}{N}\sum_{i=1}^{N} (wx_i + b - y_i)^2$$
$$\frac{\partial L}{\partial w} = \frac{2}{N}\sum_{i=1}^{N} (wx_i + b - y_i)\, x_i, \quad \frac{\partial L}{\partial b} = \frac{2}{N}\sum_{i=1}^{N} (wx_i + b - y_i)$$
$$w_{t+1} = w_t - \eta\, \frac{\partial L}{\partial w}, \quad b_{t+1} = b_t - \eta\, \frac{\partial L}{\partial b}$$
数式を言葉で読み解く:L はモデルの予測 $\hat{y} = wx+b$ と真値 $y$ の誤差を 2 乗して平均したもの(MSE)。 $\partial L/\partial w$ は「w を少し増やすと損失がどう変わるか」の感度。 GD は「損失を最も減らす方向」(負の勾配)に学習率 $\eta$ だけ進む。 学習率が大きすぎると振動・発散、 小さすぎると収束が遅い、 がトレードオフ。
このコードでやること:SSDSE-B-2026 の標準化したデータで、 numpy のみで GD を実装。 各ステップの loss を記録し、 学習率 0.1 で収束する様子を観察。 最後に解析解(polyfit)と比較。
📥 入力:標準化済み x_n, y_n(47 点)、 初期値 w=0, b=0、 学習率 η=0.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # 自前で勾配降下法を実装 (numpy のみ) import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] X = df['A1303'].values.astype(float) / 1e6 y = df['A1101'].values.astype(float) / 1e6 xn = (X - X.mean()) / X.std() yn = (y - y.mean()) / y.std() w, b, eta = 0.0, 0.0, 0.1 for t in range(50): err = (w*xn + b) - yn gw = (2/len(xn)) * (err*xn).sum() gb = (2/len(xn)) * err.sum() w -= eta*gw; b -= eta*gb if t in [0, 4, 9, 49]: print(f'iter {t+1:3d}: w={w:.4f}, b={b:.4f}, loss={(err**2).mean():.4f}') # 解析解 (最小二乗の公式) m, c = np.polyfit(xn, yn, 1) print(f'解析解 : w={m:.4f}, b={c:.4f}') |
📤 実行結果:
💬 結果の読み方:GD は 50 反復で w=0.991 に収束し、 解析解(polyfit)と一致。 loss は 1.0 → 0.0180 へ 55 倍減少。 b は標準化のため 0 に張り付く(intercept の自由度が消える)。 「単純な GD でも解析解と同じ答えに到達できる」が確認できる。 ただし学習率 0.1 が適切だったため安定したのであり、 これを 1.5 に上げると発散、 0.001 に下げると 50 反復では遠く及ばない。
学習率は GD の最重要ハイパーパラメータ。 同じ問題でも η を変えると収束パターンが劇的に変わる。
このコードでやること:η = 0.001, 0.1, 1.0, 1.5 の 4 段階で同じデータを学習し、 各 iter の loss を記録。 「小さすぎ → 進まない / 大きすぎ → 発散 / ちょうど良い → 収束」のパターンを実値で確認する。
📥 入力:先の標準化済み xn, yn を再利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 学習率を 4 段階で比較 (同じデータ、 同じ初期値) import pandas as pd, numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] X = df['A1303'].values.astype(float) / 1e6 y = df['A1101'].values.astype(float) / 1e6 xn = (X - X.mean()) / X.std(); yn = (y - y.mean()) / y.std() def gd(eta, T=30): w = 0.0; losses = [] for _ in range(T): err = w*xn - yn w -= eta * (2/len(xn)) * (err*xn).sum() losses.append((err**2).mean()) return losses for eta in [0.001, 0.1, 1.0, 1.5]: L = gd(eta) print(f'η={eta:5.3f}: loss[1]={L[0]:.3f}, loss[15]={L[14]:.3f}, loss[30]={L[29]:.3f}') |
📤 実行結果:
💬 結果の読み方:η=0.001 では 30 反復で loss が 0.892、 約 11% しか減らない(収束遅い)。 η=0.1 はちょうど良く 30 反復で loss 0.018、 98% 削減。 η=1.0 は安定限界(この単変数問題では更新係数 $|1-2\eta|=1$)ちょうどで更新が符号反転を繰り返し、 loss が 1.0 のまま下がらない。 η=1.5 は 発散し loss が 10^17 まで爆発。 「学習率 = 二次関数の曲率の逆数」程度が安全圏で、 ハイパーパラメータサーチが必須。 Adam などの適応的最適化は、 この調整を自動化したもの。
| 手法 | 更新式 | 特徴 |
|---|---|---|
| Batch GD | 全データで勾配計算 | 安定だが遅い、 大規模データ不向き |
| SGD | 1 サンプルで勾配計算 | 速いがノイジー、 学習率調整必須 |
| Mini-batch SGD | 32〜256 サンプルで勾配 | バランス良、 深層学習標準 |
| Momentum | $v_t = \beta v_{t-1} + g_t, \theta -= \eta v_t$ | 谷で振動を抑制、 慣性で加速 |
| Nesterov | 先読み付き Momentum | 理論保証あり、 凸問題で最速 |
| AdaGrad | $\eta_t = \eta / \sqrt{\sum g^2}$ | 特徴量ごとに学習率調整、 sparse 向き |
| RMSProp | 指数移動平均で勾配 2 乗 | AdaGrad の改良、 RNN で実績 |
| Adam | Momentum + RMSProp | 深層学習デファクトスタンダード |
| AdamW | Adam + decoupled weight decay | Transformer/BERT で標準 |
| LION (2023) | 符号化された Momentum | 大規模 LLM で Adam を超える |
このコードでやること:scikit-learn の SGDRegressor(SGD ベース)と LinearRegression(解析解)で同じデータを学習し、 係数と R² を比較する。 GD の実装上のディテール(収束判定、 学習率スケジュール)が sklearn でどう自動化されているかが分かる。
📥 入力:47 県の X=[A1303], y=A1101(オリジナルスケール)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # SGDRegressor と LinearRegression の比較 import pandas as pd from sklearn.linear_model import SGDRegressor, LinearRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] X = df[['A1303']].values y = df['A1101'].values sgd = make_pipeline(StandardScaler(), SGDRegressor(max_iter=1000, tol=1e-5, random_state=0)) ols = LinearRegression() sgd.fit(X, y); ols.fit(X, y) print(f'SGD R² = {sgd.score(X, y):.4f}, coef={sgd[-1].coef_[0]:.3f}') print(f'OLS R² = {ols.score(X, y):.4f}, coef={ols.coef_[0]:.3f}') print(f'SGD intercept = {sgd[-1].intercept_[0]:.0f}') print(f'OLS intercept = {ols.intercept_:.0f}') |
📤 実行結果(例):
💬 結果の読み方:R² は両者ほぼ同じ 0.982(GD でも解析解とほぼ同精度)。 ただし係数は標準化の有無で異なる:SGD は内部で StandardScaler により X を標準化しているため coef は「X の 1 標準偏差あたり」の値(約 274 万)、 OLS は「1 人あたり」の傾き 3.996。 「線形回帰なら解析解が速くて正確、 GD は標準化スケールでの解釈」 — この区別が重要。 大規模データ(n > 10^6)では解析解が遅すぎるため SGD 一択になる。
1 変数の損失は単純な放物線、 多変数は「お椀の底に向かう斜面」。 視覚化には「等高線(等しい loss の線)」とその上の「勾配ベクトル(最急上昇方向)」を描くのが定番。 GD はその真逆方向に進む。
数式を言葉で読み解く:等高線(level set)は loss が同じ値を持つ点の集合。 勾配 $\nabla L$ は等高線に垂直な方向(最急上昇)を指す。 GD はその逆 $-\nabla L$ に進むので、 等高線を直角に横切る軌跡を描く。 鋭く伸びた楕円(細長い谷)では、 谷を直進せず壁を交互に蹴る「ジグザグ運動」になり収束が遅れる。 これが Momentum や Adam が必要な理由。
このコードでやること:先ほどは intercept b を 0 固定にしたが、 今度はオリジナルスケールで w と b を同時に学習する。 軌跡 (w_t, b_t) と loss を記録し、 2 次元最適化のジグザグ性を観察する。
📥 入力:47 県の x = A1303 (百万人), y = A1101 (百万人)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 2 変数 GD: w と b を同時最適化、 軌跡を記録 import pandas as pd, numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] x = df['A1303'].values.astype(float) / 1e6 y = df['A1101'].values.astype(float) / 1e6 w, b, eta = 0.0, 0.0, 0.01 for t in range(500): yhat = w*x + b err = yhat - y gw = (2/len(x)) * (err*x).sum() gb = (2/len(x)) * err.sum() w -= eta*gw; b -= eta*gb if t in [0, 9, 99, 499]: print(f'iter {t+1:4d}: w={w:.4f}, b={b:.4f}, loss={(err**2).mean():.4f}') m, c = np.polyfit(x, y, 1) print(f'解析解 : w={m:.4f}, b={c:.4f}') |
📤 実行結果(例):
💬 結果の読み方:500 反復でも解析解(w=3.9956, b=-0.4341)には完全に届かず、 w=3.8403, b=-0.2721(loss 0.1509)で足踏みしている。 w と b は別々のスケールを持つため、 b の収束が遅い(最初に大きく+方向に振れて戻ってくる)。 これが「特徴量の標準化なしで GD を回すと収束が遅い」典型例。 学習率 η=0.01 は b にとっては大きく、 w にとっては小さい — 両者を満たす η を選ぶのが難しい。 StandardScaler を入れれば数十反復で収束する。
「勾配降下法」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。
上流の偏微分・チェインルールで勾配を計算し、 並列のニュートン法・準ニュートン法 (BFGS) と収束速度を比較し、 下流の SGD・モメンタム・Adam で深層学習の実装に接続する。 勾配降下は単一アルゴリズムではなく、 学習率設計・バッチサイズ・最適化アルゴリズム選択の体系の核として位置づける。
「勾配降下法」を実際の課題に当てはめるとき、 状況別に何を選ぶかを 3 段階で判定する。
勾配降下は「学習率 × バッチサイズ × モメンタム」の三軸で挙動が変わる。 学習曲線を必ず可視化し、 振動・停滞・発散を見分けてからハイパーパラメータを調整する。
このページの他パートでは「学習率・バッチ・モメンタム」の三軸で勾配降下法を整理した。ここでは角度を変え、「勾配降下がなぜOLSの閉形式解と一致するのか」「なぜ特徴量のスケールが収束の生死を分けるのか」を、SSDSE-B-2026 の実測値だけで検証する。学習率そのものの設計論は 学習率、連続最適化の一般論は 連続最適化 に譲り、ここではスケーリングと解の一致という一点に絞る。
線形回帰 y = b₀ + b₁x の二乗誤差は完全な凸放物面なので、最小点は連立方程式(正規方程式)で一発で解ける(=閉形式解)。勾配降下は同じ谷を、勾配の逆向きに一歩ずつ下って近づいていくだけ。だから両者は理屈上「同じ底」に到達する。これを SSDSE-B-2026 で実際に突き合わせてみる。
使用データ(実測・2023年・47都道府県): 説明変数 x = A1303(65歳以上人口)、目的変数 y = A4200(死亡数)。両者の相関は r = 0.99903 とほぼ直線。高齢人口が多い県ほど死亡数が多いという、当たり前だが極めて強い関係だ。
②は勾配降下という反復アルゴリズムなのに、①の閉形式解と 12 桁一致した。凸問題では「解き方(代数 vs 反復)」が変わっても行き着く底は同じ、という事実を実データで確かめられる。
上の一致は標準化した後の話である。生の特徴量(総人口・高齢人口は 10⁶ オーダー)のまま勾配降下を回すと、同じアルゴリズム・同じデータでも破綻する。実測で確かめた学習率の挙動が下表だ。
| 特徴量 | 学習率 | 3000回後の結果 |
|---|---|---|
| 標準化 (平均0・分散1) | 0.3 | ✅ わずか 4回で収束(MSE<0.02) |
| 0.03 | ✅ 34回で収束(遅い) | |
| 0.9 | ✅ 収束(10回、上限付近) | |
| 1.05 | ❌ 発散(overflow) | |
| 未標準化 (生の実数値) | 1×10⁻¹² | ❌ 発散(inf) |
| 5×10⁻¹³ | △ 発散はしないが最適解に届かず(MSE 2,276,513 > OLS最適 1,602,908) |
核心: 標準化すると学習率 0.3〜0.9 が問題なく使えるのに、生スケールでは安定して回せる上限が 約 5×10⁻¹³ まで押し下がる。同じデータ・同じ式なのに、標準化の有無だけで「安全な学習率」が約 12 桁ずれる。しかも生スケールでは唯一ギリギリ動く学習率でも最適解(OLS残差MSE 1,602,908)に届かない。これは特徴量スケールが損失曲面の条件数を悪化させ、全方向で同時に安定する歩幅が存在しなくなるためだ。教訓は単純で、勾配降下の前に標準化は「推奨」ではなく「前提」。詳しい変換手順は 標準化 を参照。
二乗損失の勾配降下が安定する学習率の上限は理論上 η < 2/L(L はヘッセ行列の最大固有値=損失曲面の最急な曲率)で与えられる。特徴量を c 倍すると設計行列 XᵀX の成分は概ね c² 倍になり、L も跳ね上がる。高齢人口は 10⁶ オーダーなので c² ≈ 10¹² 前後、これが標準化時の安定上限 約 0.9 を 約 5×10⁻¹³ へ押し下げた実測倍率(およそ 10¹²)とほぼ一致する。つまり上表の「12 桁ずれ」は偶然ではなく、スケールの二乗が条件数を通じて学習率上限に効くという理論の実データ確認になっている。
この視点は次に自然につながる:
※ 本節の数値は SSDSE-B-2026(data/raw、2023年・47都道府県、A1303・A4200 列)を pd.read_csv(encoding='cp932', skiprows=[1]) で読み込み、Python で実際に勾配降下・正規方程式を回して得た実測値。合成データは使用していない。