はじめに
「機械学習・深層学習」初学者のための『ゼロから作るDeep Learning』の攻略ノートです。『ゼロつくシリーズ』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。
ニューラルネットワーク内部の計算について、数学的背景の解説や計算式の導出を行い、また実際の計算結果やグラフで確認していきます。
この記事は、3.5.2項「ソフトマックス関数の実装上の注意」の内容です。オーバーフローが発生しにくいソフトマックス関数の定義式を導出します。
【元の記事】
【他の記事一覧】
【この記事の内容】
・ソフトマックス関数のオーバーフロー対策
ソフトマックス関数(Softmax関数)は、入力を0から1の値に変換して出力する関数です。出力は小さな値ですが、途中計算において入力の指数の総和とるため、オーバーフローが発生することがあります。この記事では、オーバーフローが起きないように計算する方法を解説します。実装については「3.5:ソフトマックス関数の実装【ゼロつく1のノート(実装)】 - からっぽのしょこ」を参照してください。
利用するライブラリを読み込みます。
# 利用するライブラリ import numpy as np
・ソフトマックス関数
まずは、ソフトマックス関数を確認します。
ソフトマックス関数(Softmax関数)は、次の式で定義されます。
ソフトマックス関数の入力を$\mathbf{x} = (x_1, x_2, \cdots, x_K)$、出力を$\mathbf{y} = (y_1, y_2, \cdots, y_K)$とします。
分子は各入力$x_k$の指数関数$\exp(x_k) = e^{x_k}$で、分母は全ての要素の指数の和$\sum_{k=1}^K \exp(x_k) = \exp(x_1) + \exp(x_2) + \cdots + \exp(x_K)$です。$e$はネイピア数です。
$\mathbf{y}$の各要素は0から1の値になり、またその総和は1になります。
この性質は確率の定義を満たしています。そのため、$y_k$を「入力がクラス$k$である確率」、$\mathbf{y}$を「$K$個のクラスに対する確率分布」として扱えるようになります。ソフトマックス関数は、入力を確率(のよう)に変換できることから、多クラス(多値)分類問題においてニューラルネットワークの最終層(出力層)の活性化関数に用いられます。
この性質は、「指数をとる」と「総和で割る」ためです。
簡単に確認します。(クリックで展開)
総和で割った値の総和は1になります。
# (適当に)入力を作成 x = np.array([5, 7, 8, 7, 6, 8, 9]) print(x) # 総和で割る y = x / np.sum(x) print(y) # 総和をとる print(np.sum(y))
[5 7 8 7 6 8 9]
[0.1 0.14 0.16 0.14 0.12 0.16 0.18]
1.0
総和で割った結果y
の総和が1
になりました。
ただし、負の値を入力すると計算結果も負の値になります。
# (負の値を持つ)入力を作成 x = np.array([-5, 7, 8, -7, -6, 8, 9]) print(x) # 総和で割る y = x / np.sum(x) print(np.round(y, 2)) # 総和をとる print(np.sum(y))
[-5 7 8 -7 -6 8 9]
[-0.36 0.5 0.57 -0.5 -0.43 0.57 0.64]
1.0
これでは、確率分布として扱えません。
そこで、指数をとることで全て要素が正の値になります。
# (段々大きくなる)入力を作成 x = np.arange(-3, 4) print(x) # 指数関数の計算 y = np.exp(x) print(np.round(y, 3))
[-3 -2 -1 0 1 2 3]
[ 0.05 0.135 0.368 1. 2.718 7.389 20.086]
また、指数関数は単調増加するので、要素間の大小関係が変化しません。
ソフトマックス関数の性質はこういう理由でした。
ソフトマックス関数について確認しました。次は、オーバーフローが発生する理由を確認します。
・オーバーフロー
メモリの都合上、桁を無限に持つことはできません。限度を超えたときオーバーフロー(アンダーフロー)が発生します。
指数関数は特に値が大きくなります。そのため、指数関数の計算を行うソフトマックス関数ではオーバーフローが起こりやすいです。
実際にやってみます。
# (いい感じにダメになる)値を作成 x = np.arange(705, 715) # 指数をとる exp_x = np.exp(x) print(exp_x)
[1.50525383e+306 4.09170414e+306 1.11224050e+307 3.02338314e+307
8.21840746e+307 inf inf inf
inf inf]
300桁を超える値となり、途中からオーバーフローが発生して値がinf
(infinity:無限大)になりました。
さらに、和をとってみます。
# 和をとる sum_exp_x = np.sum(exp_x) print(sum_exp_x)
inf
inf
の和もinf
になります。
では、inf
で割ってみます。
# infで割る y = exp_x / sum_exp_x print(y)
[ 0. 0. 0. 0. 0. nan nan nan nan nan]
数値をinf
で割ると0
になり、inf
をinf
で割るとnan
(not a number:非数)になります。
ちなみに、nan
でも計算できません。
# nanの計算 print(x * y) print(x / y)
[ 0. 0. 0. 0. 0. nan nan nan nan nan]
[inf inf inf inf inf nan nan nan nan nan]
数値を0
で割るとinf
になります。
オーバーフローが発生する原因を確認しました。逆に、値が小さすぎる場合はアンダーフローが発生します。
ソフトマックス関数の出力は0から1の値なので、オーバーフローが生じるのはあくまで計算過程においてです。そこで次は、オーバーフローが起きにくくなるようにソフトマックス関数の計算を工夫します。
・オーバーフロー対策
オーバーフロー対策を行ったソフトマックス関数の定義式を導出します。
ソフトマックス関数の入力$\mathbf{x}$の最大値を$x_{\mathrm{max}}$で表すことにします。指数関数$\exp(\cdot)$は単調増加するので、$\mathbf{x}$の$i$番目の項$x_i$が最大値のとき、$\exp(\mathbf{x})$も$i$番目の項$\exp(x_i)$が最大値になります。
(どういう書き方(式展開)をするのが分かりやすいのか判断できなかったので、)3つのパターンで導出しました。やっていること自体はどれも同じことです。分かりやすいと感じたのを読んでください。
・パターン1
パターン1では、本に載っている式展開に解説を加えます。
ソフトマックス関数は次の式でした。
右辺の分母分子に、ある定数$C$を掛けます。つまり、右辺に$\frac{C}{C} = 1$を掛けるので、出力$y_k$に影響しません。
総和の性質より$a \sum_{i=1}^n b_i = \sum_{i=1}^n a b_i = a b_1 + a b_2 + \cdots + a b_n$なので、$C$を$\sum$の中に入れます。
指数$\exp$と対数$\log$は打ち消し合うので、$C = \exp(\log C)$です。$C$を置き換えます。
指数法則より$\exp(a) \exp(b) = \exp(a + b)$なので、それぞれ$\exp(\cdot)$の項をまとめます。別の書き方をすると、$e^a e^b = e^{a+b}$です。
$C' = \log C$で置き換えます。
よって、$C' = \log C = - x_{\mathrm{max}}$となるような$C$にすれば、全ての入力から最大値$x_{\mathrm{max}}$を引いても出力$y_k$に影響しないことが分かります。
ちなみに(クリックで展開)
$\log C = - x_{\mathrm{max}}$を$C$に関して解くと
となります。1行目の両辺の指数をとると、左辺は$\exp(\log C) = C$となります。また右辺に関しては、$e^{-a} = \frac{1}{e^a}$です。
・パターン2
パターン1では、自然対数$\log$が登場しました。パターン2では、$\log$を使わずに導出します。
ソフトマックス関数は次の式でした。
右辺の分母分子に、入力の最大値$x_{\mathrm{max}}$の指数$\exp(x_{\mathrm{max}})$を掛けます。つまり、右辺に$\frac{\exp(x_{\mathrm{max}})}{\exp(x_{\mathrm{max}})} = 1$を掛けます。
右辺を分割します。
指数法則より$\frac{\exp(a)}{\exp(b)} = \exp(a - b)$です。別の書き方をすると、$\frac{e^a}{e^b} = e^a e^{-b} = e^{a-b}$です。よって、前の項をまとめると
となります。後の項は、$\frac{e^a}{e^b} = \frac{1}{e^b} \frac{1}{e^{-a}} = \frac{1}{e^b e^{-a}} = \frac{1}{e^{b-a}}$の変形をすると
となります。
それぞれ代入します。
オバーフロー対策を行ったソフトマックス関数の定義式が得られました。入力$\mathbf{x}$から最大値$x_{\mathrm{max}}$を引くことで、入力の値が小さくなり、指数関数$\exp(\cdot)$の影響が弱まります。
ちなみに(クリックで展開)
パターン1では始めに$\frac{C}{C}$を掛けました。また、$C = \frac{1}{\exp(x_{\mathrm{max}})}$でした。代入すると、パターン2で始めに代入した$\frac{C}{C} = \frac{\exp(x_{\mathrm{max}})}{\exp(x_{\mathrm{max}})}$となります(ここだけ見るとそりゃそうだけど)。
・パターン3
パターン3では、パターン2の式変形と同じことを総和$\sum$を使わずに書きます。
$\sum$を使わずにソフトマックス関数の定義式を表すと、次の式になります。
右辺に$\frac{\exp(x_{\mathrm{max}})}{\exp(x_{\mathrm{max}})} = 1$を掛けます。
右辺を分割します。
指数の定義より$e^{-a} = \frac{1}{e^a}$であり、また$e^a = \frac{1}{e^{-a}}$です。$\exp(x_{\mathrm{max}})$に関して、この変形をします。
指数法則より$\exp(a) \exp(- b) = \exp(a - b)$です。別の書き方をすると、$e^a e^{-b} = e^{a-b}$です。よって、分母の項を
とまとめられます。
分子の項もまとめて、それぞれ代入します。
分母の$k = 1, 2, \cdots, K$の項を$\sum_{k=1}^K$でまとめると、パターン2で導出した式になります。
以上で、オバーフロー対策版のソフトマックス関数の定義式を導出できました。3.5節で実装します。
参考文献
おわりに
- 2021.07.26:加筆修正しました。その際に記事を分割しました。
微妙に間違っていました。その内容自体は難しくないのですが、自分でも勘違いしたことをどう書いたものかと迷った結果、式展開が3つになりました。
【関連する記事】