いぬおさんのおもしろ数学実験室

おいしい紅茶でも飲みながら数学、物理、工学、プログラミング、そして読書を楽しみましょう

勾配降下法とは何か、分かりやすく説明します

 関数の最大値や最小値を求める、勾配降下法について説明します。「微分すればすぐ分かるのに……」などと思うかも知れません。しかし、関数と言ったって常に明確に式の形で与えられている保証はありません。そんなときには導関数は直に分かりません。それでも使える方法です。ここで説明するのは変数が1個のときですが、変数の数が増えてもやることは大して変わりません。実際、ぼくは手書きの「0」,「1」,……,「9」をディープラーニングを使って読み分けるプログラムを書いたときに同じ方法を用いています。そのときの変数の数は数万個とか数十万個とかです。

 さて、次の関数を考えましょう。
f:id:Inuosann:20200806141555p:plain:w150
xでの導関数の近似値(xでの微分係数の近似値)は次の式で求まります。ただしdは十分に0に近くなければなりません。
f:id:Inuosann:20200806141633p:plain:w200
もちろんこのg(x)はxでのグラフの傾きの近似値でもあります。実験なので、ここでは d=0.0001 としてみます。下の図は問題の式とは違いますが、原理の説明にはこれで十分です。下では最小値は0(x=0)です。初期値をx=2とします。
f:id:Inuosann:20200806145351p:plain:w230
2ー(Aでのグラフの傾き×e)でBのx座標が得られます。e=0.01としましょう。ここからさらに(Bでのグラフの傾き×e)を引けばCのx座標(Dのx座標)が得られます。このx座標はもとのx=2より最小値を与えるxに近づいています。これを繰り返すのです。

 Pythonで実験してみます。最小限のプログラムです。何をやっているか、上の説明と併せて読めば分かっていただけるでしょう。ここではe=0.01としていますが、根拠はありません。つまり、e=0.001がいいのか、e=0.1がいいのか、何かのルールで判断することはできません。

import sys
def f(x):
    return x ** 3 - x
#
def g(p): # x = p での微分係数(導関数の値)を求める
    return (f(p + d) - f(p))/d
#
x = 2 #xの初期値
d = 0.0001 #微分係数を求めるためのxの増分
e = 0.01 #小さめの値
#
for i in range(1000):
    x = x - e * g(x)
#
print('最小値を与える x = ', x)
print('最小値', f(x))
sys.exit()

結果は以下の通り。

最小値を与える x = 0.5773002684681432
最小値 -0.3849001751296235

実際にもとの関数を微分して最小値を求めると上の結果とほぼ一致します。eが小さすぎると効率が悪くなりそうですし、大きすぎると途中で計算されるx座標が真の答えの左右にピョン、ピョンと現れたりしてしまいます。少し試してよい値を見つけましょう。


 変数がもっとたくさんあったらどうするのか。少し書いておきましょう。例えばx、y、zだったら? xで偏微分してx=2での偏導関数の値αを求め、yで偏微分してx=2での偏導関数の値βを求め、……とやり、(x, y, z)ーe(α,β,γ)で新たなx、y、zを求めます。これを繰り返します。変数が1個のときと同様です。