UNISIA-SE Tech Blog

気まぐれお勉強日記

[Python] [8] 損失関数 (2 乗和誤差、交差エントロピー誤差) と実装サンプル

1. 前提条件


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

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

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

2. ニューラルネットワークで使用する損失関数


ニューラルネットワークの学習とは、訓練データを基に意図した結果となる最適な重みパラメータを判別する ことを指す。

学習するためには、損失関数 という ニューラルネットワークの性能の悪さ表す指標 を基準にして、その結果が 最も小さい値となる重み となるように 自己探索(最適な重みパラメータを探す) していくことになる。

以下、有名どころの損失関数である 2 乗和誤差交差エントロピー誤差 について、簡単なPython実装サンプルを使って解説する。

3. 2 乗和誤差の定義と実装サンプル


2 乗和誤差 (mean squared error) は、残差平方和とも呼ばれ、ターゲットとなる 2 つの変数差の2 乗を総和し、2 で割った値 を取る。

▼ 2 乗和誤差の定義
この 2 つの変数のうち \(y_{k}\) をニューラルネットワークの出力、もう 1 つの \(t_{k}\) を教師データ (訓練データ) と置く。
※ \(k\) は、データの次元数
\[ {\normalsize E = \frac{1}{2}\sum_{i=1}^{n} (y_{k}-t_{k})^2…(A) } \]
▼ 2 乗和誤差の実装サンプル
下記で触れたMNISTデータセットを用いたと想定して、10 個の要素からなるデータを例に解説する。


$ python
 >>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
 >>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]

・\(k=10\) は、10 個の要素からなるデータでMNISTで扱うデータ数 (0 ~ 9 の数字)

・\(y_{k}\) にあたる \(y\) は、ニューラルネットワークの出力でソフトマックス関数の出力値 (確率)
※ この出力値の算出処理については、下記ページの「predict 関数」を参考。

・\(t_{k}\) にあたる \(t\) は、教師データでMNISTであらかじめ準備されている正解を表す配列(1 が 正解)

そして、\(y\) と \(t\) は、それぞれの要素同士が対となっていて、下記結果を表している。
・要素 1:数字画像が 0 の確率が 10 % (0.1) ⇒ 不正解 (0)
・要素 2:数字画像が 1 の確率が 5 % (0.05) ⇒ 不正解 (0)
・要素 3:数字画像が 2 の確率が 60 % (0.6) ⇒ 正解 (1)
・要素 4:数字画像が 3 の確率が 0 % (0.0) ⇒ 不正解 (0)
・要素 5:数字画像が 4 の確率が 5 % (0.05) ⇒ 不正解 (0)
・要素 6:数字画像が 5 の確率が 10 % (0.1) ⇒ 不正解 (0)
・要素 7:数字画像が 6 の確率が 0 % (0.0) ⇒ 不正解 (0)
・要素 8:数字画像が 7 の確率が 10 % (0.1) ⇒ 不正解 (0)
・要素 9:数字画像が 8 の確率が 0 % (0.0) ⇒ 不正解 (0)
・要素 10:数字画像が 9 の確率が 0 % (0.0) ⇒ 不正解 (0)

上記 \((A)\) は、この要素別の \(y\) と \(t\) の差を2乗した総和を 2 で割ったもの。

これをPythonで書くと

0.5 * np.sum((y-t)**2)

となる。

これを関数で定義し、上記の \(y\) と \(t\) で実行してみる。

$ python
 >>> import numpy as np
 >>> def mean_squared_error(y, t):
 ...     return 0.5 * np.sum((y-t)**2)
 ...
 >>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
 >>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]
 >>>
 >>> mean_squared_error(np.array(y), np.array(t))
 0.09750000000000003
 >>>

正解である 2 が 60 %となる上記 mean_squared_error の結果は、約 0.0975 となった。

試しにわざとはずして 不正解である 7 が 60 %となる \(y\) で結果を見てみると

$ python
 >>> import numpy as np
 >>> def mean_squared_error(y, t):
 ...     return 0.5 * np.sum((y-t)**2)
 ...
 >>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
 >>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]
 >>>
 >>> mean_squared_error(np.array(y), np.array(t))
 0.5975
 >>>

結果は、0.5975 となり 約6倍 (損失が6倍) まで大きくなってしまう。

ちなみに正解である 2 が 100 %となる下記 \(y\) だと 結果は 0 となり、損失が0(間違った予測なし) ということになる。

 >>> y = [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
 >>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]
 >>> mean_squared_error(np.array(y), np.array(t))
 0.0
 >>>

4. 交差エントロピー誤差と実装サンプル


交差エントロピー誤差 (cross entropy error) は、クロスエントロピーとも呼ばれ、ターゲットとなる 2 つの変数のうち、一方を自然対数(低は \(e\))として双方の積総和にマイナスをかけた値 を取る。

▼ 交差エントロピー誤差の定義
この 2 つの変数のうち \(y_{k}\) を ニューラルネットワークの出力、もう 1 つの \(t_{k}\) を教師データ (訓練データ)と置く。
※ \(k\) は、データの次元数
\[ {\normalsize E = -\sum_{i=1}^{n} t_{k} \log \ y_{k}…(B) } \]
▼ 交差エントロピー誤差の実装サンプル
前項で触れた 3. 2 乗和誤差の定義と実装サンプル と同様に、10 個の要素からなるデータを例に解説する。

$ python
 >>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
 >>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]

\(y_{k}\) 、\(t_{k}\) と同様に \(k=10\) となる \(y\) は、ソフトマックス関数の出力値で \(t\) は、正解のみ 1不正解は 0 を取る。

そして、上記 \((B)\) の \(t_{k}\) は、不正解が 0 となる 9つあり積も必ず 0 となる。
よって、結局は正解 1 である \(y_{k}\) 、つまり 0.6 の自然対数にマイナスをかけた値である
\[ {\normalsize -\log \ 0.6=0.51 } \]
のみとなる。

前項「3. 2 乗和誤差の定義と実装サンプル」でわざとはずした下記の結果であれば
\[ {\normalsize -\log \ 0.1=2.30 } \]
となるため、結果(損失値)が高くなっていることが分かる。

$ python
 >>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
 >>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]

これをPythonで書くと

delta = le-7
-np.sum(t * np.log(y + delta))

となります。

※ delta = le - 7 は、\(y_{k}\) が 0 となる \(log(0)\) よってマイナスの無限大に発散しないよう 微量な delta を足込んでいます。

これを関数で定義し、上記の \(y\) と \(t\) で実行してみる。

$ python
 >>> import numpy as np
 >>> def cross_entropy_error(y, t):
 ...     delta = 1e-7
 ...     return -np.sum(t * np.log(y + delta))
 ...
 >>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
 >>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]
 >>> cross_entropy_error(np.array(y), np.array(t))
 0.510825457099338
 >>>

正解である 2 が 60 %となる上記 cross_entropy_error の結果は、約 0.510 となった。

わざとはずして 不正解である 2 が 10 %となる \(y\) だと

$ python
 >>> import numpy as np
 >>> def mean_squared_error(y, t):
 ...     return 0.5 * np.sum((y-t)**2)
 ...
 >>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
 >>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]
 >>>
 >>> cross_entropy_error(np.array(y), np.array(t))
 2.302584092994546
 >>>

結果は、約2.302 となり、やはり 約4倍 (損失が4倍) まで大きくなり、前項「3. 2 乗和誤差の定義と実装サンプル」と同様に妥当な 損失値が得られている ことが分かる。

以上。


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


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