※以下の企画です
前回に続いてゼロつくやっていきます。
今回も4章 -ニューラルネットワークの学習-の続きです。
微分から勾配までの内容になるので、コーディングというよりかは数式のほうがいっぱいでてきそうな予感です。
それでは頑張っていきます〜
※数学的に厳密に正しいことを述べられる自信はないので、温かい目で見守ってください※
4章 ニューラルネットワークの学習
数値微分
ニューラルネットワークでは、パラメータ(重みやバイアス)を更新する際に、関数の変化率を求める必要がある。これはいわゆる微分という作業だ。
数値微分の基本的な式は以下の通り。
f'(x) = \frac{f(x+h) - f(x-h)}{2h}
-
h
は微小な値(一般的には10^{-4}
程度) - 中央差分法を使用することで精度が高まる
というのが本書から得た学び。
中央差分というのは、$f(x+h) - f(x-h)$の部分で、ある点$x$の前後$h$距離分で傾きを求める(直線を引く)イメージ。
片方だけだと、実際の傾きと誤差が生じてしまう可能性があるので、前後の二点で傾きをもとめた方が良いとのことだ。たしかに。
Pythonで数値微分を実装すると以下のようになる。
def numerical_diff(f, x):
h = 1e-4 # 0.0001
return (f(x + h) - f(x - h)) / (2 * h)
ちょっとテストしてみる。
2次関数 $y = 0.01x^2 + 0.1x$に対して、数値微分を行う。
def function_1(x):
return 0.01 * x**2 + 0.1 * x
点$x = 5$および $x = 10$で数値微分を実行してみる。
print(numerical_diff(function_1, 5))
print(numerical_diff(function_1, 10))
0.1999999999990898
0.2999999999986347
解析的な解は$\frac{df(x)}{dx} = 0.02x+0.1$であり、$x$をそれぞれ代入すると
- $x=5$のとき、$\frac{df(5)}{dx} = 0.2$
- $x=10$のとき、$\frac{df(10)}{dx} = 0.3$
なので、ちゃんと微分できていそう。おもろ。
あと、ここで地味に学びが合ったのは、関数って関数を引数にできるんだということ。
偏微分
関数が複数の変数を持つ場合、一つの変数について微分を行う操作を偏微分と呼ぶ。
学生のころめちゃくちゃやったわ。本当にちゃんと何も覚えていないけど。
例えば、$f(x_1, x_2) = x_1^2 + x_2^2$を偏微分してみる。
- $x_1$ に関する偏微分:
\frac{\partial f}{\partial x_1} = 2x_1
- $x_2$ に関する偏微分:
\frac{\partial f}{\partial x_2} = 2x_2
上式の成り立ちは非常にシンプルで、偏微分しない方の変数を0にしているだけ。
数値微分を用いて偏微分を行う場合、片方の変数を定数とみなして先述の微分のコードを用いるだけで処理ができる。
あえてここでアウトプットはしない。面倒なわけではない。断じて。
勾配
勾配は、関数の出力を最も速く増加させる方向を示すベクトルのことらしい。
要は「全ての変数に対して偏微分を行ってその結果をベクトルとしてまとめたもの」っぽい。
例えば($x_0, x_1$)に対して、($\frac{d(x_0)}{dx},\frac{d(x_1)}{dx}$)を求めるという感じ。
全然関係ないが、大学院の研究で「購買効果」という文言を多用しているが、こういう記事を書いた後だと高確率で「勾配効果」と変換される。
$f(x_1, x_2) = x_1^2 + x_2^2$の勾配は以下のように求められる。
\nabla f(x) =
\begin{bmatrix}
\frac{\partial f}{\partial x_1} \\
\frac{\partial f}{\partial x_2}
\end{bmatrix}
=
\begin{bmatrix}
2x_1 \\
2x_2
\end{bmatrix}
※$\nabla$はナブラと読む。
Pythonのコードに落とし込んでみる。
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # x と同じ形状の配列を生成
for idx in range(x.size):
tmp_val = x[idx]
x[idx] = tmp_val + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tmp_val # 値を元に戻す
return grad
ややこしい感じがするが、与えられた入力(ここでは$x$)の数分だけ微分の作業をおこなっているだけ。
それではテスト実行もしてみる。
def function_2(x):
return x[0]**2 + x[1]**2 # f(x_1, x_2) = x_1^2 + x_2^2
print(numerical_gradient(function_2, np.array([3.0, 4.0])))
print(numerical_gradient(function_2, np.array([0.0, 2.0])))
print(numerical_gradient(function_2, np.array([3.0, 0.0])))
[6. 8.]
[0. 4.]
[6. 0.]
多分いい感じ!
まとめ
今回はほぼ数学の内容で、昔学んだことを思い出すことができた。
「ニューラルネットワークは勾配法を用いて学習を行う」というのはなんとかく知っているが、勾配について説明することはできなかった。
ちゃんと「勾配って何?」と聞かれたときにスマートに回答できるレベルになれるように復習していきます。