今回は偏微分と勾配降下法について、
「偏微分ってなに??」
「勾配降下法ってなに??」
「Pythonで勾配降下法を実装すると??」
などをまとめてみました。
この記事を書いている僕はシステムエンジニア6年目
普段はJavaでWebアプリを作ったりSQL書いたり・・・、
なので最近流行りのPython、数学、人工知能、デープラーニングができる人には正直憧れています。。。。
自分も一から勉強してこの辺りできるようになりたい、、画像認識モデルを作ったりして、アプリに組み込みたい!
これが機械学習、深層学習の勉強を始めたきっかけでした。
体系的に、この分野の基礎から学ぼうとJDLAのG検定の勉強をして合格するところまでいきました。
次のステップとして、
実際にPythonでコードを書きながら機械学習や深層学習の知識を深めているところです。。。
今回は「偏微分」と「勾配降下法」についてまとめてみました。
この記事を読む前に
以下の記事を読んで微分については事前に抑えておいてください。

偏微分
複数の変数からなる関数の微分を偏微分といいます。
普通の微分と異なる点は、変数が複数あるので、どの変数の微分かを区別する必要があるということです。
偏微分を数式で表すと、\(\frac{ \partial f }{ \partial x_0 }\)、\(\frac{ \partial f }{ \partial x_1 }\)のように書きます。
例えば、
$$f(x_0,x_1) = x_0^2 + x_1^2$$
上のような2つの変数\(x_0\)、\(x_1\)をもつ関数があるとき、
\(x_0 = 3\)、\(x_1 = 4\)のときの\(x_0\)に対する偏微分\(\frac{ \partial f }{ \partial x_0 }\)を求めると、
\(\frac{ \partial f }{ \partial x_0 } = 2x_0\)
\(x_0 = 3\)を代入して、\(2 \times 3 = 6\)
\(x_0 = 3\)、\(x_1 = 4\)のときの\(x_1\)に対する偏微分\(\frac{ \partial f }{ \partial x_1 }\)を求めると、
\(\frac{ \partial f }{ \partial x_0 } = 2x_1\)
\(x_1 = 4\)を代入して、\(2x \times 4 = 8\)
のように、微分対象ではない変数を定数のように扱って計算できます。
勾配
先ほどは\(x_0\)と\(x_1\)の偏微分の計算を変数ごとに計算しました。
両方の偏微分をまとめて\((\frac{ \partial f }{ \partial x_0 }, \frac{ \partial f }{ \partial x_1 })\)のようにベクトルとしてまとめたものを勾配(gradient)といいます。
今度は、
\(x_0 = 3\)、\(x_1 = 4\)のときの\((x_0,x_1)\)の両方の偏微分をまとめて、\((\frac{ \partial f }{ \partial x_0 }, \frac{ \partial f }{ \partial x_1 })\)として計算します。
先ほどは解析的に微分の計算をしましたが、
今度はPythonで数値微分してみましょう。
import numpy as np
import matplotlib.pylab as plt
def function_2(x):
return x[0]**2 + x[1]**2
def numerical_gradient(f, x):
h = 1e-4
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
x[idx] = tmp_val + h
fxh1 = f(x)
x[idx] = tmp_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val
return grad
print("x_0^2 + x_1^2の(3, 4)の勾配" + str(numerical_gradient(function_2, np.array([3.0, 4.0]))))
print("x_0^2 + x_1^2の(0, 2)の勾配" + str(numerical_gradient(function_2, np.array([0.0, 2.0]))))
print("x_0^2 + x_1^2の(3, 0)の勾配" + str(numerical_gradient(function_2, np.array([3.0, 0.0]))))
上の例では、
\(f(x_0,x_1) = x_0^2 + x_1^2\)の(3, 4)での勾配は(6, 8)、(0, 2)での勾配は(0, 4)、(3, 0)での勾配は(6, 0)となりました。
では、これら勾配はそもそも何を表しているのか??について確認していきましょう。
各点での勾配をベクトルとして図に表すと以下のようになります。
図を見ると、各点でのベクトルは関数\(f(x_0,x_1)\)の最小値を指しています。
また、最小値から離れれば離れるほどベクトルが大きくなっていることが分かります。
勾配は各地点において、関数の値を最も減らす方向を表します。
勾配法(勾配降下法)
ニューラルネットワークは学習の際、最適なパラメータ(重みとバイアス)を見つける必要があります。
最適なパラメータとは損失関数が最小値をとるときのパラメータの値です。
しかし、一般的に損失関数は複雑で、パラメータ空間は広大でどこに最小値を取る場所があるのか見当がつきません。
そこで、勾配をうまく利用して関数の最小値(またはできるだけ小さな値)を探すのが勾配法(勾配降下法)です。(関数の最大値を探すのは勾配上昇法と呼ばれます)
勾配法では、現在の場所から勾配方向に一定の距離進みます。そして、移動した先でも同様に勾配を求め、
また、その勾配方向へ進むというように、繰り返し勾配方向へ移動します。
このように勾配方向へ進むことを繰り返すことで、関数の値を徐々に減らすのが勾配法です。
絵にするとこんな感じ。
勾配法を数式で表すと、
$$x_0 = x_0 – \eta\frac{ \partial f }{ \partial x_0 }$$
$$x_1 = x_1 – \eta\frac{ \partial f }{ \partial x_1 }$$
\(\eta\)は学習率(learning rate)と呼ばれ、1回の学習でパラメータをどれだけ更新するかを決めます。
上の式は1回の更新式を表しており、このステップを繰り返し行います。
つまり、ステップ毎にパラメータ(変数)を更新していき、
そのステップを何度か繰り返すことによって、上の図のように徐々に関数の値を減らしていくのです。
また、ここでは2つの変数の場合を式で表していますが、
変数が増えた場合にも増えた変数に対する同じような式によって変数が更新されていきます。
勾配降下法を利用して\(f(x_0,x_1) = x_0^2 + x_1^2\)の最小値を求めると、
import numpy as np
import matplotlib.pylab as plt
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
init_x = np.array([-3.0, 4.0])
print("勾配降下法結果:"+str(gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)))
上のソースコードにおいて、
step_numはパラメータ更新の回数、lrは学習率をそれぞれ表しています。
また、上の例では(-3, 4)の場所からスタートして、
最終的には(0, 0)に到達しています。(誤差はありますが、、、)
パラメータ更新プロセスを図で表すと、
学習率のような、人間が手動で設定するようなパラメータをハイパーパラメータと呼びます。
ニューラルネットワークは、重みやバイアスを学習によって自動で最適な値に更新しますが、ハイパーパラメータは自動で最適な値を設定されることはありません。
まとめ
今回は勾配降下法について確認しました。
以下については抑えておきましょう。
- 関数の各パラメータ(変数)の偏微分をベクトルで表したものを勾配と呼ぶ。
- 勾配によって関数を値を減らす方向が特定できる。(最小値の方向とは限らないが、、)
- 損失関数の勾配を求め、勾配の方向に損失関数の各パラメータ(変数)を更新していくことで、ニューラルネットワークの重みやバイアスの最適なパラメータを発見できる。
勾配降下法が理解できたら、
次はニューラルネットワークに勾配降下法を適応して学習する動きを確認していきましょう。
参考にした資料