UNISIA-SE Tech Blog

気まぐれお勉強日記

[Python] [10] 損失関数と数値微分(勾配)の実装サンプル

1. 前提条件


このページでは、損失関数と数値微分についての簡単な実装サンプルを記載する。

以下、必要な前提知識。

▼ 下記ページを理解していること。
[Python] [4] 活性化関数の実装サンプルまとめ(ステップ / シグモイド / ReLU / 恒等関数 /ソフトマックス関数)
[Python] [8] 損失関数 (2 乗和誤差、交差エントロピー誤差) と実装サンプル
[Python] [9] ミニバッチ学習 (交差エントロピー誤差) の実装サンプル

▼ Python3.6、NumPy、Matplotlibがインストールされていること。
このページでは、venvの仮想環境(Python3.6)上にNumPyをインストールした環境で、Python対話モード(Pythonインタプリタ)にて実装サンプルを記載している。

※ Python対話モード、NumPy、Matplotlibについては下記を参考。
Python対話モード:[Python] 対話モード (インタプリタ) の使用方法
NumPy:[Python] [NumPy] インストールとnumpy.ndarrayの使用方法
Matplotlib:[Python] [Matplotlib] インストールとmatplotlib.pyplot の使用方法

2. 損失関数と微分の関係


前ページでも触れた交差エントロピー誤差などの損失関数が取る値は、小さいほど正解に近づいている が、もちろんそこで終わりではなく 今の結果より正解に近い パラメータ候補 (重み、バイアス) を決めてさらに正解に近づけなければ意味がない。

そこで基準になるのが 重みパラメータの微分結果(勾配値) である。

なぜそうなるか具体的な説明は省くが、一つの重みパラメータの微分結果損失関数の結果 は、密接な関係にある。

微分結果が負 であれば、正の方向に重みパラメータを変化させることで損失関数の結果も小さくなり、同様に 微分結果が正 であれば、負の方向に重みパラメータを変化させることで損失関数の結果も小さくなる。

この要領で重みパラメータを変化幅を少しずつ減らしながらこの作業を繰り返すことで、微分結果が 0 に収束し、結果、損失関数の結果も 0 に収束する ことになる。

但し、この概念を理解する上で注意しなければならないのがステップ関数 (下記 ※1 参照) のように、\(x = 0 \) 以外の値で微分結果が 0 (勾配が 0) となるような損失関数 では、そもそも正解に近づけようがないため、微分する意味がない。

つまり、損失関数は連続 (曲線状の変化をしている) でなければ、微分する意味がない。


また、この 連続性をもつこと とは、損失関数の結果が実数である ということになるのですが、実数と実数の間に隙間はない ので、いくらでも際限なく損失関数の 極微量な区間の微分結果を探索することができる ができ、ここに微分の性質が利用されている。

3. 微分のおさらい


微分とは、簡単に言うと 極微量な区間である点 \(f(x) \) , \(f(x+\Delta x) \) の変化量 (傾き、勾配) を表したもの です。
そして、厳密に言うとこの極微量な区間 \(f(x) \) , \(f(x+\Delta x) \) の差を 限りなく 0 に近づけた時の変化量 を表しており、一般的に下記のように定義される。
\[ f’(x) = \frac{df(x)}{dx} \]
\[ \frac{df(x)}{dx} = \lim_{\Delta x \to 0} \frac{ f(x+\Delta x) - f(x) }{\Delta x} \]

\(f’(x)\) と \(\frac{df(x)}{dx} \) は、\(f(x)\) を \(x\) で微分した結果 (導関数) を表す記号。

また、このページで解説しているニューラルネットワークでの微分は、この極微量な区間 (\(f(x) \) , \(f(x+\Delta x) \)) の差を プログラム言語の型の認識できる程度の微量なもの であることを前提としている。
(プログラム言語の型の認識できるレベルで 0 に近づけるため、微量な誤差が発生するため。)

この微分を 数値微分と表現し、下記誤差がない高校数学の解析的な微分 (真の微分)と区別する。

高校数学の下記微分 (※2) は、誤差がない解析的な微分(真の微分) であり、上記の定義に従い、区間 (\(f(x) \) , \(f(x+\Delta x) \)) の差を限りなく 0 に近づけた値を作業的に計算し求めている。
※2 \(y = x^{n} \) ⇒ \(y’ = nx^{n-1} \)

4. 数値微分の関数定義 (Python実装サンプル)


上記で触れましたが数値微分なので プログラム言語の型の認識できるレベル でなければならない。
もし、 \(h \) を下記のように \(10^{-50}\) とし、NumPy.float32の型が認識できないほどの微量な値である場合、丸め誤差 により、 \(0.0 \) となってしまう。

$ python
 >>> def num_dif(f, x):
 ...     h = 10e-50
 ...     return (f(x+h) - f(x)) / h
 ...
 >>>

従って、NumPy.float32型で丸目誤差がでないラインである \(10^{-4} = 0.0001\) となる必要がある。

$ python
 >>> def num_dif(f, x):
 ...     h = 1e-4
 ...     return (f(x+h) - f(x)) / h
 ...
 >>>

とは言っても、数学的に見ると \(10^{-4} = 0.0001\) でも十分大きな値なので、誤差を減らす工夫として、 \(f(x+\Delta x) - f(x) \) の 正の増加分 だけでなく \(f(x) - f(x-\Delta x)\) となる 負の増加分 の変化も加味した中心差分という方法を取り、下記のように実装する。

$ python
 >>> def num_dif(f, x):
 ...     h = 1e-4
 ...     return (f(x+h) - f(x-h)) / (2*h)
 ...
 >>>

5. 数値微分の例 (Python実装サンプル)


2次関数 \(y = 0.05x^{2} + 0.5x \) を例にした関数 (func_ex) を定義し、イメージしやすいようグラフ描画もしておく。

$ python
 >>> import numpy as np
 >>> import matplotlib.pyplot as plt
 >>>
 >>> def func_ex(x):
 ...     return 0.05*x**2 + 0.5*x
 >>>
 >>> x = np.arange(0.0, 20.0, 0.1)    # 区間を0~20 まで、描画制度を 0.1 刻みに設定
 >>> y = func_ex(x)
 >>>
 >>> plt.title("y = 0.05x^2+0.5x \n# arange:0, 20, 0.1, xlabel:x, ylabel:f(x)")    # グラフタイトルを設定
 Text(0.5, 1.0, 'y = 0.05x^2+0.5x \n# arange:0, 20, 0.1, xlabel:x, ylabel:f(x)')
 >>> plt.xlabel("x")    # x軸のラベルを設定
 Text(0.5, 0, 'x')
 >>> plt.ylabel("f(x)")    # y軸のラベルを設定
 Text(0, 0.5, 'f(x)')
 >>> plt.plot(x,y)    # グラフの描画
 [<matplotlib.lines.Line2D object at 0x7f1f0e6f5be0>]
 >>> plt.savefig('/var/www/vops/ops/macuos/static/macuos/img/b_id47_1.png')
 >>>


そして上記 4. 数値微分の関数定義 (Python実装サンプル) で定義した数値微分の関数「num_dif」にこの2次関数の \(x = 5 \) の場合と、\(x = 10\) の場合を例に結果を出してみる。

$ python
 >>> num_dif(func_ex, 5)
 0.9999999999976694
 >>> num_dif(func_ex, 10)
 1.4999999999965041
 >>>

真の微分では、\(f(x) = 0.05x^{2} + 0.5x \) ⇒ \(f’(x) = 0.1x + 0.5 \) より、\(x = 5 \) の場合 \(1.0 \) 、\(x = 10\) の場合 \(1.5 \) となるので、\(0.9999…\) と \(1.4999…\) という結果は、非常に小さい誤差で数値微分できている ことになる。

以上。


【参考文献】
斎藤 康毅 (2018) 『ゼロから作るDeep Learning - Pythonで学ぶディープラーニングの理論と実装』株式会社オライリー・ジャパン


Copyright UNISIA-SE All Rights Reserved.
s-hama@unisia-se.jp