損失関数の平均値
分類問題を学習するネットワークの場合、出力値 $\boldsymbol{y}$ の各成分が入力データの属するクラスを表します。たとえば下図にあるように、ネットワークに $\boldsymbol{x}$ を入力して
\[\boldsymbol{y}=\begin{bmatrix}y_0\\y_1\\y_2\end{bmatrix}=\begin{bmatrix}0.05\\ 0.83\\ 0.12\end{bmatrix}\]
という値が出力されたとすると、入力データがクラス $0$ に属する確率は $0.05$、クラス $1$ に属する確率は $0.83$、クラス $2$ に属する確率は $0.12$ となります。
正解値(目標変数)は
\[\boldsymbol{t}=\begin{bmatrix}t_0\\ t_1\\ t_2\end{bmatrix}=\begin{bmatrix}0\\ 1\\ 0\end{bmatrix}\]
となっているので、実際に入力データが属するクラスは $1$ です。ネットワークは出力値と正解値(目標値)の誤差がなるべく少なくなるように学習を進めます。上の例では、$y_1$ が $1$ に、$y_0$ と $y_2$ が $0$ に近づくように学習します。
一般に $m$ クラス分類において、正解値(目標値)
\[\boldsymbol{t}=\begin{bmatrix}t_0\\ t_1\\ t_2\\ \vdots \\ t_{m-1}\end{bmatrix}\]
は 1 of K で表されたクラスです。ネットワークに $\boldsymbol{x}$ を入力して
\[\boldsymbol{y}=\begin{bmatrix}y_0\\ y_1\\ y_2\\ \vdots \\ y_{m-1}\end{bmatrix}\]
が出力されたとき、$y_0,\ y_1,\ y_2,\ \cdots\ y_{m-1}$ はクラス $1,\ 2,\ \cdots \ m-1$ に属する確率です。
\[\begin{align*}&P(\boldsymbol{t}=[1,\ 0,\ 0,\ \cdots ,\ 0]\mid\boldsymbol{x})=y_0\\[6pt]&P(\boldsymbol{t}=[0,\ 1,\ 0,\ \cdots ,\ 0]\mid\boldsymbol{x})=y_1\\[6pt]&P(\boldsymbol{t}=[0,\ 0,\ 1,\ \cdots ,\ 0]\mid\boldsymbol{x})=y_2\\[6pt]&\qquad\qquad\qquad\vdots\\[6pt]&P(\boldsymbol{t}=[0,\ 0,\ 0,\ \cdots ,\ 1]\mid\boldsymbol{x})=y_{m-1}\end{align*}\]
これらの式をまとめて、
\[P(\boldsymbol{t}\mid\boldsymbol{x})=y_0^{t_0}y_1^{t_1}y_2^{t_2}\ \cdots\ y_{m-1}^{t_{m-1}}\]
と表すことができます。出力 $\boldsymbol{y}$ にはネットワークで使用されたすべての重みパラメータ $w_{ji}$ が含まれているので、$P(\boldsymbol{t}\mid\boldsymbol{x})$ が最大になるように重みを最適化することによって、クラス $\boldsymbol{t}$ が生成される確率が最も高いモデルを構築することができます(最尤推定)。ここで、$P$ の自然対数をとって符号を変えた損失関数
\[\begin{align*}E&=-\log P(\boldsymbol{t}\mid\boldsymbol{x})\\[6pt]
&=-t_0\log y_0-t_1\log y_1-\ \cdots\ -t_{m-1}\log y_{m-1}\\[6pt]&=-\sum_{k=1}^{m-1}t_k\log y_k\end{align*}\]
を (1個の入力データに対する) 交差エントロピー誤差関数と定義します。実際のコードでは、この 交差エントロピー誤差 が最小となるように重み $w_{ji}$ を最適化することになります。
バッチサイズ $n$ で学習させる場合、$n$ 個のデータそれぞれについて交差エントロピー誤差を計算して平均をとります。出力値と正解値(目標変数)の右下にデータ番号を添えて $t_{k,l},\ y_{k,l}$ のように表すと、バッチ内の交差エントロピー誤差の総和は
\[-\sum_{k,l}t_{k,l}(\log y_{k,l})\]
によって計算できます。つまり、すべての $k,\ l$ の組合わせについて $t_{k,l}$ と $\log y_{k,l}$ の積を計算して合計すればよいことになります。そのために、$n$ 個のデータベクトルを横に並べて次のような行列をつくります (Python の実装では 1 次元配列を縦に並べます)。
\[\begin{align*}T&=\begin{bmatrix}t_{0,0} & t_{0,1} & \cdots & t_{0,n-1}\\t_{1,0} & t_{1,1} & \cdots & t_{1,n-1}\\t_{2,0} & t_{2,1} & \cdots & t_{2,n-1}\\\vdots & \vdots & \vdots & \vdots\\t_{m-1,0} & t_{m-1,1} & \cdots & t_{m-1,n-1}\end{bmatrix}\\[6pt]\log Y&=\begin{bmatrix}\log y_{0,0} & \log y_{0,1} & \cdots &\log y_{0,n-1}\\\log y_{1,0} & \log y_{1,1} & \cdots & \log y_{1,n-1}\\\log y_{2,0} & \log y_{2,1} & \cdots & \log y_{2,n-1}\\\vdots & \vdots & \vdots & \vdots\\\log y_{m-1,0} & \log y_{m-1,1} & \cdots & \log y_{m-1,n-1}\end{bmatrix}\end{align*}\]
行列 $A$ と $B$ の要素同士の積をとって作った行列を アダマール積 とよび、$A\circ B$ で表します(行列積とは異なる演算です)。$T$ と $\log Y$ のアダマール積をとると、
\[T\circ (\log Y)=\begin{bmatrix}t_{0,0}\log y_{0,0} & t_{0,1}\log y_{0,1} & \cdots & t_{0,n-1}\log y_{0,n-1}\\t_{1,0}\log y_{1,0} & t_{1,1}\log y_{1,1} & \cdots & t_{1,n-1}\log y_{1,n-1}\\t_{2,0}\log y_{2,0} & t_{2,1}\log y_{2,1} & \cdots & t_{2,n-1}\log y_{2,n-1}\\\vdots & \vdots & \vdots & \vdots\\t_{m-1,0}\log y_{m-1,0} & t_{m-1,1}\log y_{m-1,1} & \cdots & t_{m-1,n-1}\log y_{m-1,n-1}\\\end{bmatrix}\]
となります。この行列のすべての成分について和をとってから $n$ で割れば、バッチ内の平均交差エントロピー誤差を求めることができます。
NumPy の配列同士を「 * 」で演算すると アダマール積 を返します。また、numpy.sum() は配列のすべての要素の和をとるので、平均交差エントロピー誤差関数の実装は以下のようになります。
# Cross_Entropy # In[1] import numpy as np # 平均交差エントロピー誤差関数 def cross_entropy(y, t): ce = - np.sum(t * np.log(y + 1e-8)) / y.shape[0] return ce
y.shape[0] は配列の行数なので、データの個数、すなわちバッチサイズ $n$ です(1次元配列をベクトルに対応させているので、上の数式とはベクトルの並ぶ方向が逆になっています)。np.log() の引数に y + 1e-8 を渡しますが、これは y が 0 のときに -inf を返さないように極めて小さな値を付加しています (-inf は $-\infty$, 1e-8 は $10^{-8}$ を意味します)。cross_entropy() 関数によるエントロピー誤差の計算を試してみましょう。まず適当な出力データと正解値データ(目標変数)を用意しておきます。
# In[2] np.random.seed(10) # ソフトマックス関数 def softmax(x): f = np.exp(x)/np.sum(np.exp(x), axis = 1, keepdims = True) return f # -2~2の乱数要素をもつ3×3配列 x = np.random.uniform(-2, 2, (3, 3)) # xをソフトマックス関数に通す y = softmax(x) # 入力データのクラス(正解値) x_class = np.array([2, 1, 0]) # クラスを1-of-Kに変換 t = np.identity(3, dtype = "int8")[x_class] print("出力配列y\n{}\n".format(y)) print("正解値t\n{}".format(t)) # 出力配列y # [[0.61492068 0.03054553 0.35453379] # [0.67096688 0.24654198 0.08249113] # [0.08789538 0.83382094 0.07828368]] # 正解値t # [[0 0 1] # [0 1 0] # [1 0 0]]
cross_entropy() に y と t を渡して平均交差エントロピー誤差を計算してみます。
# In[3] # 1バッチの平均交差エントロピー誤差を計算 ce = cross_entropy(y, t) print(ce) # 1.6229274747438958
コメント
下記は誤植思われますので、ご確認ください。
「損失関数の平均」の説明文で、
P(t=[0, 1, 0,⋯, 0]∣x)=y1 の上の行に P(t=[1, 0, 0,⋯, 0]∣x)=y0 を追加。
= − t0 log y0 − t0 log y1 → = − t0 log y0 − t1 log y1
T◦(log Y) の (m-1, 1)番目の要素で、t_{m−1,0}log y_{m−1,1} → t_{m−1,1}log y_{m−1,1}
気づきにくいところを見てくださって、ありがとうございます。
助かりました。m(_ _)m
In[1] プログラムの下の文で、(1次元配列をベクトルに対応させているので、上の数式とはベクトルの並ぶ方向が逆になっています)は少しわかりにくかったので、
(t と y の 1 次元配列を行ベクトルに対応させているので、上の数式とは異なりベクトルの並ぶ方向が縦方向から横方向に変わっています)としてはどうでしょうか。「複数データの同時入力」の記事にあったような図もあったほうがいいと思いました。