モジュール 08 · 第 2 回

勾配降下法

やみくもに試すのをやめ、一歩ごとにパラメータをどちらの方向にどれだけ変えればよいかを計算します。傾きから微分を理解し、勾配を手で計算し、勾配降下法のループを書き、学習率が小さすぎるとき、大きすぎるとき、データが標準化されていないときに何が起きるかを自分の目で確かめます。

  • 約 45 分
  • 難易度:入門
  • 検証:2026-09-14 numpy 2.5、乱数シード固定

コードと実行結果は実際に動かしたときのまま載せているため、コメントと出力は中国語です。

前の課ではグリッドサーチで最もよい直線を見つけましたが、その行き止まりも見ました。パラメータが増えると、試すべき組み合わせは天文学的な数になります。

目隠しをして山の斜面に立っていて、谷の一番低いところまで歩いていかなければならないと想像してください。山全体は見えませんが、足元の地面がどちらに傾いているかは足で感じられます。そこで下り坂の方向に小さく一歩踏み出し、また感じて、また一歩踏み出す。そうして歩き続ければ、最後には谷底に着きます。

これが勾配降下法(gradient descent)です。「山」は損失、あなたが立っている場所は今のパラメータ、「どちらに傾いているか」が勾配です。

微分:それは傾きのこと

まずパラメータ w 一つだけを見て、b = 0 に固定します。w を右にほんの少し動かすと、損失はどれだけ変わるでしょうか。変化量を動かした距離で割ったものが、その点での損失の傾きです。数値で直接測ってみましょう。

def mse(w, b, x=area, y=price):
    return np.mean((w * x + b - y) ** 2)


w, b = 1.0, 0.0
for h in [0.1, 0.01, 0.001]:
    slope = (mse(w + h, b) - mse(w, b)) / h
    print(f"  w 往右挪 {h:<6},损失变化 / 挪动距离 = {slope:10.2f}")
== 1. 在 w=1.0, b=0 这一点,损失对 w 的斜率
  w 往右挪 0.1   ,损失变化 / 挪动距离 =   -7300.21
  w 往右挪 0.01  ,损失变化 / 挪动距离 =   -8256.23
  w 往右挪 0.001 ,损失变化 / 挪动距离 =   -8351.83

動かす距離を小さくするほど、測った傾きは安定し、しだいに一定の値に近づいていきます。この「動かす距離が限りなく小さいときの傾き」が微分です。それは二つのことを教えてくれます。

  • 符号:ここではマイナスで、w を右に動かす(大きくする)と損失が小さくなるという意味です。ですから w を大きくすべきです。
  • 大きさ:絶対値が大きいのは、ここの坂が急で、損失が w にとても敏感だという意味です。

勾配:パラメータごとに一つの微分

パラメータは wb の二つです。それぞれのパラメータで微分し(w で微分するときは b を定数とみなし、逆も同じ)、得られた二つの数を合わせたものを勾配と呼びます。

数値で測る方法は遅く、正確でもありません。幸い、平均二乗誤差の微分は式として直接書けます。損失は 平均((w×x + b - y)²) なので、微分の規則に従うと次のようになります。

损失对 w 的导数 = 平均( 2 × (w×x + b - y) × x )
损失对 b 的导数 = 平均( 2 × (w×x + b - y) )

この導出を覚える必要はありません。「上で傾きを測った過程を、代数で一度に正確に計算したもの」だとわかれば十分です。コードにするとこうなります。

def gradients(w, b, x=area, y=price):
    error = w * x + b - y
    dw = np.mean(2 * error * x)  # 损失对 w 的导数
    db = np.mean(2 * error)  # 损失对 b 的导数
    return dw, db
== 2. 公式算出的梯度:dw=-8362.45,db=-79.98

dw = -8362.45 で、上で数値的に測った結果(0.001 動かしたときは -8351.83)ととても近い値です。これはよい習慣です。自分で導いた勾配の式を数値の方法で確かめると、多くの誤りを見つけられます。次の課で誤差逆伝播法を書くときも、同じようにします。

勾配降下法のループ

勾配は損失が最も速く増える方向を指しています。損失を減らしたいので、勾配の逆方向に小さく一歩進みます。

w = w - 学习率 × dw
b = b - 学习率 × db

学習率(learning rate)が一歩の大きさを決めます。w = 0, b = 0 から出発して、このステップを繰り返します。

def descend(lr, steps, x=area, y=price, report=()):
    """从 w=0, b=0 出发走 steps 步。发散时返回 None。"""
    w, b = 0.0, 0.0
    for step in range(1, steps + 1):
        dw, db = gradients(w, b, x, y)
        w -= lr * dw
        b -= lr * db
        if abs(w) > 1e6:  # 越走越远,已经发散了
            print(f"  第 {step} 步:w 已经变成 {w:.3g},发散了")
            return None
        if step in report:
            print(f"  第 {step:5d} 步:w={w:7.4f} b={b:7.3f} 损失 {mse(w, b, x, y):10.2f}")
    return w, b

これが学習の中心となるロジックのすべてです。モジュール 09 で GPT を学習させるときも、ループの構造はまったく同じで、ただパラメータが増え、勾配の計算方法が次の課の誤差逆伝播法に替わるだけです。

歩いてみる:それほど順調ではない

学習率 0.00005 で 2 万歩進みます。

== 3. 学习率 0.00005,走 20000 步
  第     1 步:w= 1.4804 b=  0.014 损失     244.38
  第    10 步:w= 1.3935 b=  0.014 损失     164.22
  第   100 步:w= 1.3934 b=  0.027 损失     164.18
  第  1000 步:w= 1.3922 b=  0.155 损失     163.81
  第  5000 步:w= 1.3871 b=  0.711 损失     162.27
  第 20000 步:w= 1.3695 b=  2.615 损失     157.43

1 歩目で、損失は数千から 244 に下がり、10 歩後には 164 になります。ところがその後はほとんど動かなくなります。2 万歩進んでも損失は 157 までしか下がらず、前の課のグリッドサーチで見つけた最小の損失は 143 です。b の遅さはさらに驚くほどで、2 万歩で 0 から 2.6 にしか動かず、最もよい値(約 14.5)にはまだほど遠いのです。

なぜこうなるのでしょうか。次の二つの実験で原因が明らかになります。

学習率:小さすぎれば遅く、大きすぎれば爆発する

学習率をいくつか替えて、どれも 1000 歩進みます。

== 4. 不同的学习率(都走 1000 步)
  学习率 1e-05  :w=1.3932 b=0.041 损失 164.14
  学习率 5e-05  :w=1.3922 b=0.155 损失 163.81
  学习率 9e-05  :w=1.3912 b=0.268 损失 163.50
  第 115 步:w 已经变成 1.03e+06,发散了
  学习率 0.0001 :发散

学習率が大きいほど速く進み、b も遠くまで動きます。ところが学習率を 0.00009 から 0.0001 に、ほんの少し上げただけで、完全に爆発しました。115 歩目で w は 100 万を超えています。

発散はこうして起きます。w 方向の坂が急すぎる(上で測った傾きは -8362)ので、少し大きく踏み出すと、一歩で谷底を飛び越え、向こう側のもっと高いところに着地します。そこの坂はもっと急なので、次の一歩はさらに遠くへ飛びます……一歩ごとに前の一歩より谷底から遠ざかり、値はどんどん大きくなって、ついには数値があふれてしまいます。

問題の根っこは、w 方向の坂が急すぎ、b 方向の坂が緩すぎることです。dw は -8362、db はわずか -80 で、100 倍も違います。学習率を w が発散しないほど小さくすると、b はきわめてゆっくりしか動けません。一つの学習率では、二つの方向を同時に面倒見られないのです。

標準化:各方向の坂の急さをそろえる

w 方向の坂はなぜこんなに急なのでしょうか。面積の値が大きく、数十から 100 平方メートル以上あるからです。勾配の式 dw = 平均(2 × 誤差 × x) では、x が大きいほど dw も大きくなります。

解決策は、面積を「平均 0、標準偏差 1」の数に換算することで、これを標準化と呼びます。

mean, std = area.mean(), area.std()
area_n = (area - mean) / std

標準化すると、面積の値はどれも -2 から 2 くらいになり、wb の二つの方向の坂の急さがほぼそろいます。これで学習率を安心して 0.1 にでき、100 歩進むだけで済みます。

== 5. 面积标准化之后(均值 98.0,标准差 32.1),学习率 0.1 只走 100 步
  第     1 步:w= 8.0605 b= 27.588 损失   13360.27
  第    10 步:w=35.9749 b=123.129 损失     381.15
  第    50 步:w=40.3018 b=137.939 损失     143.05
  第   100 步:w=40.3023 b=137.941 损失     143.05
  换算回原单位:w=1.2571 b=14.806

50 歩で最も低いところに着き、損失は 143.05 で、前の課のグリッドサーチの 143.1 より少し低いくらいです(グリッドサーチは 0.01 刻みでしか試さないので、最もよい点は二つの格子の間に落ちているかもしれません)。元の単位に換算すると、w = 1.2571, b = 14.806 です。

以前は 2 万歩でもできなかったことが、標準化すると 50 歩で済みました。これはニューラルネットワークを学習させるうえでとても実用的な経験則です。入力の値の範囲をそろえておく。この後出てくるさまざまな「正規化」(LayerNorm、BatchNorm)も、出発点はここと同じです。

勾配降下法のまとめ

  • 各パラメータについての損失の微分を合わせたものを勾配と呼び、損失が最も速く増える方向を指す。
  • 一歩ごとに勾配の逆方向に小さく一歩進み、歩幅は学習率で決まる。
  • 学習率が小さすぎれば遅く、大きすぎれば発散する。学習で最も調整が必要な数である。
  • 入力の値の範囲が大きく違うと学習の調整が難しくなるので、先に標準化する。

私たちの直線にはパラメータが二つしかないので、勾配の式は手で導けます。しかしニューラルネットワークには数百、数千のパラメータがあり、層が何重にも重なっていて、各パラメータの微分を手で導くのは不可能です。次の課では、コンピュータにすべてのパラメータの勾配を自動で計算させる方法、誤差逆伝播法を扱います。

練習問題

  1. 標準化したデータで、学習率を 0.5、1.0、1.1 に変えてそれぞれ 100 歩進み、結果を見てください。境目となる学習率はおよそいくつですか。
  2. 標準化せず、学習率 0.00009 だけを使う場合、損失が 144 未満に下がるまで何歩必要でしょうか。見積もってから、実行して確かめてください。
  3. 数値微分では、h は小さいほどよいのでしょうか。h1e-101e-15 に変えて、測った傾きを見てください。結果はかえって悪くなっていませんか。なぜか考えてみてください(ヒント:コンピュータの小数の精度には限りがあります)。

確認テスト

1. 勾配降下法で、勾配の「逆方向」に進むのはなぜですか?

勾配は損失が最も速く増える方向を指しています。私たちの目標は損失を減らすことなので、逆方向に進む必要があります。

2. 学習率を 0.00009 から 0.0001 に上げただけで、学習が発散しました。何が起きたのですか?

w の方向では、損失の坂がとても急です。学習率が少し大きくなると、一歩で最も低いところを飛び越え、向こう側のもっと高いところに着地し、そこの坂はもっと急なので、次の一歩はさらに遠くへ飛びます。一歩ごとに前の一歩より最も低いところから遠ざかり、値はどんどん大きくなって、ついにはあふれてしまいます。

3. 面積を標準化すると、学習が数百倍速くなったのはなぜですか?

標準化していないときは面積の値が大きく、w 方向の勾配が b 方向より 100 倍大きくなっていました。学習率は急な w 方向に合わせてとても小さくするしかなく、その結果 b 方向はきわめてゆっくりしか進みませんでした。標準化すると二つの方向の坂の急さがほぼそろうので、ずっと大きな学習率を使え、二つのパラメータのどちらもすぐに最も低いところにたどり着けます。

質問と議論

このレッスンでつまずいたところは、ここで質問してください。他の人の質問に答えるのも歓迎です。

質問で 3 ポイント、回答で 6 ポイント。審査を通過すると公開されます。

議論を読み込んでいます…