本文へスキップ
ひもとくAI

勾配を自分で計算する(中学数学で分かる勾配降下法)

レッスン 6/7

損失のグラフは、中学で習った放物線

前のレッスンでは、勾配降下法を「足元の傾きだけを頼りに谷底を目指す方法」として説明しました。このレッスンでは、その「傾き」を、中学数学だけを使って実際に計算してみます。

話を分かりやすくするため、データは次の2件だけにします。

  • x=1x = 1 のとき y=2y = 2
  • x=2x = 2 のとき y=4y = 4

モデルは y=axy = ax とします(切片 bb は0に固定して、aa だけを動かして考えます)。aa の値を変えると、損失(平均二乗誤差)は次のように変わります。

損失=(a×1−2)2+(a×2−4)22\text{損失} = \frac{(a \times 1 - 2)^2 + (a \times 2 - 4)^2}{2}
aax=1x=1 の予測x=2x=2 の予測損失
00010
1122.5
2240
3362.5
44810

a=2a = 2 のとき予測がぴったり当たり、損失は0になります。そこから左右に離れるほど、損失は同じように大きくなっています(a=1a = 1 と a=3a = 3、a=0a = 0 と a=4a = 4 で損失が同じです)。

実はこの式は、中学3年で習う展開を使うと 2.5(a−2)22.5(a - 2)^2 と同じです。(2a−4)2=4(a−2)2(2a - 4)^2 = 4(a - 2)^2 なので、(a−2)2+4(a−2)2=5(a−2)2(a - 2)^2 + 4(a - 2)^2 = 5(a - 2)^2 となり、それを2で割るからです。

ここで、a−2a - 2 をひとまとまりと見て tt と置くと、損失 =2.5t2= 2.5t^2 になります。これは中学3年で習った「yy は xx の2乗に比例する」関数と同じ形(比例定数が2.5)で、グラフは放物線です。中学で習ったグラフは x=0x = 0 のところが谷底でしたが、今回は t=a−2=0t = a - 2 = 0、つまり a=2a = 2 のところが谷底 になります。

a損失01234谷底
横軸をa、縦軸を損失にしたグラフ。a=2で損失が0の谷底になる、お椀のような形の放物線です。

学習とは、この 放物線の谷底を探すこと です。今回はグラフ全体が見えているので、答えが a=2a = 2 だとすぐに分かります。しかし実際のモデルでは、動かす数値が何百万個もあるため、グラフを描いて眺めることはできません。そこで、「今いる場所の傾き」だけから進む向きを決める方法が必要になります。

勾配は「変化の割合」

中学2年の一次関数で、変化の割合 を習いました。

変化の割合=yの増加量xの増加量\text{変化の割合} = \frac{y\text{の増加量}}{x\text{の増加量}}

一次関数では変化の割合がどこでも同じ(傾きそのもの)でしたが、中学3年の「yy は xx の2乗に比例する」関数では、どの区間で計算するかによって値が変わりました。放物線は場所によって傾きが違うからです。

勾配も、この変化の割合で計算できます。例えば今 a=3a = 3 にいるとします。aa をほんの少し、0.01だけ増やしたとき、損失がどれだけ増えるかを調べます。

  • a=3a = 3 のときの損失: 2.5×(3−2)2=2.52.5 \times (3 - 2)^2 = 2.5
  • a=3.01a = 3.01 のときの損失: 2.5×(3.01−2)2=2.550252.5 \times (3.01 - 2)^2 = 2.55025
変化の割合=2.55025−2.50.01=5.025\text{変化の割合} = \frac{2.55025 - 2.5}{0.01} = 5.025

プラスの値なので、「aa を増やすと損失が増える」、つまり aa を減らせば損失が減る ことが分かります。a=1a = 1 で同じ計算をすると −4.975-4.975 とマイナスになり、今度は「aa を増やせば損失が減る」ことが分かります。

a損失01234谷底a = 3 での傾き
a=3の点での傾き(点線)。右上がりなので、谷底に近づくにはaを減らす(左に進む)必要があります。

増やす幅を0.01から0.0001に小さくすると、変化の割合は5.00025になり、幅を小さくするほど 5 に近づいていきます。この「幅をどんどん小さくしたときに近づいていく値」が、その点での 勾配 です。高校数学で習う 微分 は、この値を式で正確に求める方法です。

もう一つ大事な性質があります。a=4a = 4 では勾配は10になり、a=3a = 3 の5より大きくなります。谷底から遠いほど坂が急で、勾配が大きくなる のです。勾配降下法では勾配に比例した歩幅で進むので、遠いうちは大きく、近づくと小さく動くことになります。

自分で勾配降下法を動かす

ここまでの考え方を、そのままPythonで書いてみます。使っているのは、足し算・引き算・掛け算・割り算とfor文だけです。

def loss(a):
    return ((a * 1 - 2) ** 2 + (a * 2 - 4) ** 2) / 2

def gradient(a):
    h = 0.0001   # aをほんの少しだけ増やす幅
    return (loss(a + h) - loss(a)) / h   # 変化の割合

a = 0.0     # スタート地点(適当な値)
lr = 0.1    # 学習率(1歩の大きさ)

for step in range(30):
    a = a - lr * gradient(a)   # 勾配と逆向きに少しだけ動く
    print(step + 1, a, loss(a))

実行すると、aa と損失は次のように変わっていきます(小数は丸めています)。

回数aa損失
11.02.500
21.50.625
31.750.156
41.8750.039
51.93750.010

aa が1回ごとに谷底の2へ近づき、損失が小さくなっていきます。30回繰り返すと、aa は約1.99995になり、ほぼ正解の2にたどり着きます。

ここで注目してほしいのは、このプログラムは 放物線の全体像を一度も見ていない ということです。毎回やっているのは「今いる場所で、ほんの少し動いたら損失がどう変わるか」を調べて、下る向きに一歩進むことだけです。それだけで、正しい答えにたどり着けました。

これが、第1章で学んだ「予測する → ズレ(損失)を確かめる → 少しずつ調整する → 繰り返す」という学習の正体です。

学習率の大きさと、よくある誤解

学習率が大きすぎるとどうなるか

先ほどのプログラムで、学習率lrを変えて試すと、次のようになります。

  • lr = 0.1: aa は 1.0 → 1.5 → 1.75 → … と、谷底の2に近づく
  • lr = 0.4: aa は 4 → 0 → 4 → 0 → … と、谷底を挟んで行ったり来たりし続ける
  • lr = 0.5: aa は 5 → −2.5 → 8.75 → … と、谷底からどんどん離れていく

歩幅が大きすぎると、谷底を飛び越えて反対側の坂に出てしまいます。飛び越えた先の坂がもっと急だと、次はさらに大きく飛ばされ、損失はむしろ増えていきます。逆に学習率が小さすぎると、正しい向きには進むものの、谷底に着くまでに非常に長い時間がかかります。ちょうど良い学習率を選ぶことは、実務でも重要な調整の一つです。

誤解:「微分を知らないと、勾配は計算できない?」

このレッスンで見たように、勾配は「ほんの少し動かしたときの変化の割合」として、中学数学だけで計算できます。実際のライブラリは、微分の公式を使ってもっと正確に、速く計算していますが、求めているものは同じです。

動かす数値が2つ以上あるとき

aa と bb の両方を動かすときは、「bb を固定して aa だけを少し動かしたときの変化の割合」と「aa を固定して bb だけを少し動かしたときの変化の割合」を別々に計算し、それぞれを同時に更新します。ディープラーニングのモデルでは、この動かす数値が数百万個から数千億個にもなりますが、一つ一つに対してやっていることは、このレッスンとまったく同じです。

振り返り

  • 損失のグラフは、中学3年で習う放物線と同じ形で、学習とはその谷底を探すこと
  • 勾配は、ごく小さい区間での「変化の割合」。プラスなら減らす、マイナスなら増やす
  • 勾配と逆向きに、学習率の分だけ動くことを繰り返すと、グラフを見なくても谷底にたどり着ける

演習

演習を読み込んでいます…