からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

ステップ47:多値分類の出力層の計算【ゼロつく3のノート(数学)】

はじめに

 『ゼロから作るDeep Learning 3』の初学者向け攻略ノートです。『ゼロつく3』の学習の補助となるように適宜解説を加えていきます。本と一緒に読んでください。

 本で登場する数学的な内容をもう少し深堀りして解説していきます。

 この記事は、主にステップ47「ソフトマックス関数と交差エントロピー誤差」を補足する内容です。
 オーバーフロー対策をしたLogSumExpの計算と、LogSumExpを用いたSoftmax関数と交差エントロピー誤差の計算を確認します。

【前ステップの内容】

www.anarchive-beta.com

【他の記事一覧】

www.anarchive-beta.com

【この記事の内容】

・多値分類の出力層の計算

 $K$クラスの多値分類におけるソフトマックス関数(Softmax関数)と交差エントロピー誤差の計算について考えます。

・多値分類の出力層

 ニューラルネットワークの(1つの)出力データを要素数が$K$のベクトル$\mathbf{x} = (x_1, x_2, \cdots, x_K)$とします。$N$個のバッチデータ($N \times K$の行列)$\mathbf{X}$の$n$番目のデータ$\mathbf{x}_n = (x_{n,1}, x_{n,2}, \cdots, x_{n,K})$として考えてください。またここでは、ニューラルネットワークの出力を想定していますが、この記事の内容には影響しません。

 $\mathbf{x}$の各要素をソフトマックス関数により活性化します。

$$ p_k = \frac{\exp(x_k)}{\sum_{k=1}^K \exp(x_k)} $$

 ソフトマックス関数の出力を$\mathbf{p} = (p_1, p_2, \cdots, p_K)$とします。$\mathbf{x}$がソフトマックス関数により正規化され、$0 \leq p_k \leq 1$、$\sum_{k=1}^K p_k = 1$となります。よって、$p_k$は「入力のクラスが$k$である確率」、$\mathbf{p}$は「$K$個のクラスの確率分布」として解釈できます。
 ソフトマックス関数については、「3.5:出力層の設計【ゼロつく1のノート(実装)】 - からっぽのしょこ」を参照してください。

 予測値$\mathbf{p}$と教師データ$\mathbf{t} = (t_1, t_2, \cdots, t_K)$から損失を求めます。ここでは、損失として交差エントロピー誤差を用います。

$$ L = - \sum_{k=1}^K t_k \log p_k $$

 $\mathbf{t}$について、$\mathbf{x}$のクラスが$k$のとき、$k$番目の要素$t_k$が1でそれ以外の要素は0です。このようにして正解のクラスを表す方式をone-hotベクトル(one-hot表現)と言います。
 $p_k$は0から1の値なので、$\log p_k$は常に負の値になります。よって、$- \log p_k$は常に正の値になります。また、$- \log p_k$の最小値は$p_k = 1$のときの$\log 1 = 0$です。$p_k$の値が小さくなるほど$- \log p_k$が大きくなるので、この値を誤差として利用します。
 つまり、入力のクラスが$k$のとき($t_k = 1$のとき)、$- t_k \log p_k$は$- \log p_k$となりそれ以外の項は0になります。$K$個全ての項の和$L$も$- \log p_k$となります。したがって、正解のクラスを完全に予測できたとき($p_k = 1$のとき)の誤差は0となり、低く予測する($p_k$の値が小さい)ほど誤差が大きくなります。

 ちなみに、積や総和の計算をせずに、正解のクラスに対応する$p_k$を取り出す操作によって$L$を求められます。

 $N$個のバッチデータの場合は、次の式になります。

$$ L = - \frac{1}{N} \sum_{n=1}^N \sum_{k=1}^K t_{n,k} \log p_{n,k} $$

 全てのデータの交差エントロピー誤差を足し合わせてデータ数$N$で割ることで平均を求めています。
 交差エントロピー誤差については、「損失関数【ゼロつく1のノート(数学)】 - からっぽのしょこ」を参照してください。

 次は、$\log p_k$の計算を考えます。

・対数Softmax関数

 $\log p_k$は、対数をとったSoftmax関数の計算と言えます。

$$ \log p_k = \log \left\{ \frac{\exp(x_k)}{\sum_{k=1}^K \exp(x_k)} \right\} $$

 対数の性質$\log \frac{x}{y} = \log x - \log y$より、分母と分子を分解します。

$$ \log p_k = \log \Bigl\{ \exp(x_k) \Bigr\} - \log \left\{ \sum_{k=1}^K \exp(x_k) \right\} $$

 対数と指数は打ち消し合い$\log \{\exp(x)\} = x$となります。

$$ \log p_k = x_k - \log \left\{ \sum_{k=1}^K \exp(x_k) \right\} $$

 後の項は、$\mathbf{x}$に対して$\log$と$\sum$と$\exp$の計算をしています。この計算をLogSumExpと呼び、$\mathrm{LSE}(\mathbf{x})$で表すことにします。

$$ \log p_k = x_k - \mathrm{LSE}(\mathbf{x}) $$

 対数ソフトマックス関数は、$x_k$から$\mathrm{LSE}(\mathbf{x})$を引くことで求められるのが分かりました。

 続いて、$\mathrm{LSE}(\mathbf{x})$について考えます。

・LogSumExp

 実装において、LogSumExpの計算

$$ \mathrm{LSE}(\mathbf{x}) = \log \left\{ \sum_{k=1}^{N} \exp(x_k) \right\} $$

は、指数関数$\exp(x)$の計算を含むため$x$が大きいとオーバーフローすることがあります。
 そこで、実装上は次のように計算(処理)します。

 $\mathbf{x}$の最大値を$x_{\mathrm{max}}$として、$\exp(\cdot)$の中に$- x_{\mathrm{max}} + x_{\mathrm{max}} = 0$を加えます。

$$ \mathrm{LSE}(\mathbf{x}) = \log \left\{ \sum_{k=1}^K \exp \Bigl( x_k - x_{\mathrm{max}} + x_{\mathrm{max}} \Bigr) \right\} $$

 $\exp(x) = e^x$であり、指数の性質$x^{n+m} = x^n * x^m$より、$\exp(\cdot)$の項を分解します。

$$ \mathrm{LSE}(\mathbf{x}) = \log \left\{ \exp(x_{\mathrm{max}}) \sum_{k=1}^K \exp(x_k - x_{\mathrm{max}}) \right\} $$

 $\exp(x_{\mathrm{max}})$は、$\sum_{k=1}^K$とは無関係なので$\sum$の外に出しました。
 対数の性質$\log (x * y) = \log x + \log y$より、$\log (\cdot)$の項を分解します。

$$ \mathrm{LSE}(\mathbf{x}) = \log \Bigl\{ \exp(x_{\mathrm{max}}) \Bigr\} + \log \left\{ \sum_{k=1}^K \exp(x_k - x_{\mathrm{max}}) \right\} $$

 $\log \{\exp(x)\} = x$の変形を行います。

$$ \mathrm{LSE}(\mathbf{x}) = x_{\mathrm{max}} + \log \left\{ \sum_{k=1}^K \exp(x_k - x_{\mathrm{max}}) \right\} $$

 $\mathrm{LSE}(\mathbf{x})$の計算は、$\mathbf{x}$の全ての項から最大値$x_{\mathrm{max}}$を引きLogSumExpの計算$\mathrm{LSE}(\mathbf{x} - x_{\mathrm{max}})$をして、$x_{\mathrm{max}}$を加えることで求められるのが分かりました。$x_k - x_{\mathrm{max}}$の最小値が$x_{\mathrm{\min}} - x_{\mathrm{max}}$、最大値が0になるので、オーバーフローが起きにくくなります。

 LogSumExpの計算をDeZeroの関数logsumexp()としてutils.pyに実装します。

・バッチ版の出力層

 最後に、バッチデータの場合を確認します。

 バッチデータに対する(オーバーフロー対策を行った)ソフトマックス関数と交差エントロピー誤差の計算をまとめると、次の式になります。

$$ L = - \frac{1}{N} \sum_{n=1}^N \sum_{k=1}^K t_{n,k} \left[ x_{n,k} - x_{n,\mathrm{max}} - \log \left\{ \sum_{k'=1}^K \exp(x_{n,k'} - x_{n,\mathrm{max}}) \right\} \right] $$

 また、各データ$\mathbf{x}_n$の正解クラスの要素を$x_{n,*}$で表すと、次の式でも表せます。

$$ L = - \frac{1}{N} \sum_{n=1}^N \left[ x_{n,*} - x_{n,\mathrm{max}} - \log \left\{ \sum_{k=1}^K \exp(x_{n,k} - x_{n,\mathrm{max}}) \right\} \right] $$

 (この2つの式と個別の式の中から分かりやすいと思う式と実装例を比べてください。)

 logsumexp()を用いて、ソフトマックス関数と交差エントロピー誤差をSoftmaxCrossEntropyクラスとしてfunctions.pyに実装します。逆伝播の計算については、「ソフトマックス関数と交差エントロピー誤差の逆伝播【ゼロつく1のノート(数学)】 - からっぽのしょこ」を参照してください。

参考文献

  • 斎藤康毅『ゼロから作るDeep Learning 3 ――フレームワーク編』オライリー・ジャパン,2020年.

おわりに

 ずいぶん前に初めて見たLogSumExpの式が、$a < b$の2項の例

$$ \begin{aligned} \log \Bigl\{ \exp(a) + \exp(b) \Bigr\} &= \log \Bigl\{ \exp(a - b + b) + \exp(b - b + b) \Bigr\} \\ &= \log \Bigl\{ \exp(a - b) \exp(b) + \exp(0) \exp(b) \Bigr\} \\ &= \log \Bigl[ \Bigl\{ \exp(a - b) + 1 \Bigr\} \exp(b) \Bigr] \\ &= \log \Bigl\{ \exp(a - b) + 1 \Bigr\} + \log \{\exp(b)\} \\ &= \log \Bigl\{ \exp(a - b) + 1 \Bigr\} + b \end{aligned} $$

の途中式がないもので、1は何?なぜ$\exp(b)$が消えて$b$に??と投げ出してしまいました。
 それが今回、最大値を引いてから総和をとってるんだよ、あと$\exp(0) = 1$なだけ、と分かってスッキリしました。分かってしまうと、大したことではなかったなぁと感じるのもいつものこと。

 ところで、記事中の様に式変形と解説文を交互に入れるのと、あとがきの様に式変形ドン!各行の解説ドン!とするのはどちらの方が分かりやすい(読みやすい)ものなのでしょうか?
 私の場合は、数式を読むときと文字列を読むときとで使ってる脳のパーツ(あるいは意識)が違うのか、後者の方が好きです。なのでこれまでに書いた記事では、数式をまとめて書くことが多かったです。それと後々読み返すときに、数式だけで理解できるのかを確認できるように分けて書いています。
 ゼロつく関連の記事では、数式の塊を見るのがキツい人(初学者時の私)を想定して、それとどちらのパターンでも書けるようになるための練習として、分けて書くようにしています。
 解説ノートを作る際にいつも悩むことの1つ。あとは、レベル感とか文体とかどこまでくだけて書くかとか。

 投稿日の前日に公開された動画をどうぞ♪

 これは一体何のタイアップ?企画?なんだ??続報ぷりーず。

【次ステップの内容】

www.anarchive-beta.com