からっぽのしょこ

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

ソフトマックス関数のオーバーフロー対策【ゼロつく1のノート(数学)】

はじめに

 「機械学習・深層学習」初学者のための『ゼロから作るDeep Learning』の攻略ノートです。『ゼロつくシリーズ』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。

 ニューラルネットワーク内部の計算について、数学的背景の解説や計算式の導出を行い、また実際の計算結果やグラフで確認していきます。

 この記事は、3.5.2項「ソフトマックス関数の実装上の注意」の内容です。オーバーフローが発生しにくいソフトマックス関数の定義式を導出します。

【元の記事】

www.anarchive-beta.com

【他の記事一覧】

www.anarchive-beta.com

【この記事の内容】

・ソフトマックス関数のオーバーフロー対策

 ソフトマックス関数(Softmax関数)は、入力を0から1の値に変換して出力する関数です。出力は小さな値ですが、途中計算において入力の指数の総和とるため、オーバーフローが発生することがあります。この記事では、オーバーフローが起きないように計算する方法を解説します。実装については「3.5:出力層の設計【ゼロつく1のノート(実装)】 - からっぽのしょこ」を参照してください。

 利用するライブラリを読み込みます。

# 利用するライブラリ
import numpy as np


・ソフトマックス関数

 まずは、ソフトマックス関数を確認します。

 ソフトマックス関数(Softmax関数)は、次の式で定義されます。

$$ y_k = \frac{\exp(x_k)}{\sum_{k'=1}^K \exp(x_{k'})} \tag{3.10} $$

 ソフトマックス関数の入力を$\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になります。

$$ 0 \leq y_k \leq 1,\ \sum_{k=1}^K y_k = 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になり、infinfで割ると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では、本に載っている式展開に解説を加えます。

 ソフトマックス関数は次の式でした。

$$ y_k = \frac{\exp(x_k)}{\sum_{k'=1}^K \exp(x_{k'})} \tag{3.10} $$

 右辺の分母分子に、ある定数$C$を掛けます。つまり、右辺に$\frac{C}{C} = 1$を掛けるので、出力$y_k$に影響しません。

$$ y_k = \frac{ C \exp(x_k) }{ C \sum_{k'=1}^K \exp(x_{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$の中に入れます。

$$ y_k = \frac{ \exp(x_k) C }{ \sum_{k'=1}^K \exp(x_{k'}) C } $$

 指数$\exp$と対数$\log$は打ち消し合うので、$C = \exp(\log C)$です。$C$を置き換えます。

$$ y_k = \frac{ \exp(x_k) \exp(\log C) }{ \sum_{k'=1}^K \exp(x_{k'}) \exp(\log C) } $$

 指数法則より$\exp(a) \exp(b) = \exp(a + b)$なので、それぞれ$\exp(\cdot)$の項をまとめます。別の書き方をすると、$e^a e^b = e^{a+b}$です。

$$ y_k = \frac{ \exp(x_k + \log C) }{ \sum_{k'=1}^K \exp(x_{k'} + \log C) } $$

 $C' = \log C$で置き換えます。

$$ y_k = \frac{ \exp(x_k + C') }{ \sum_{k'=1}^K \exp(x_{k'} + C') } \tag{3.11} $$

 よって、$C' = \log C = - x_{\mathrm{max}}$となるような$C$にすれば、全ての入力から最大値$x_{\mathrm{max}}$を引いても出力$y_k$に影響しないことが分かります。

ちなみに(クリックで展開)

 $\log C = - x_{\mathrm{max}}$を$C$に関して解くと

$$ \begin{aligned} \log C &= - x_{\mathrm{max}} \\ \Rightarrow \exp(\log C) &= \exp(- x_{\mathrm{max}}) \\ \Rightarrow C &= \frac{1}{\exp(x_{\mathrm{max}})} \end{aligned} $$

となります。1行目の両辺の指数をとると、左辺は$\exp(\log C) = C$となります。また右辺に関しては、$e^{-a} = \frac{1}{e^a}$です。


・パターン2

 パターン1では、自然対数$\log$が登場しました。パターン2では、$\log$を使わずに導出します。

 ソフトマックス関数は次の式でした。

$$ y_k = \frac{\exp(x_k)}{\sum_{k'=1}^K \exp(x_{k'})} \tag{3.10} $$

 右辺の分母分子に、入力の最大値$x_{\mathrm{max}}$の指数$\exp(x_{\mathrm{max}})$を掛けます。つまり、右辺に$\frac{\exp(x_{\mathrm{max}})}{\exp(x_{\mathrm{max}})} = 1$を掛けます。

$$ y_k = \frac{ \exp(x_{\mathrm{max}}) \exp(x_k) }{ \exp(x_{\mathrm{max}}) \sum_{k'=1}^K \exp(x_{k'}) } $$

 右辺を分割します。

$$ y_k = \frac{\exp(x_k)}{\exp(x_{\mathrm{max}})} \frac{ \exp(x_{\mathrm{max}}) }{ \sum_{k'=1}^K \exp(x_{k'}) } $$

 指数法則より$\frac{\exp(a)}{\exp(b)} = \exp(a - b)$です。別の書き方をすると、$\frac{e^a}{e^b} = e^a e^{-b} = e^{a-b}$です。よって、前の項をまとめると

$$ \frac{\exp(x_k)}{\exp(x_{\mathrm{max}})} = \exp(x_k - x_{\mathrm{max}}) $$

となります。後の項は、$\frac{e^a}{e^b} = \frac{1}{e^b} \frac{1}{e^{-a}} = \frac{1}{e^b e^{-a}} = \frac{1}{e^{b-a}}$の変形をすると

$$ \frac{ \exp(x_{\mathrm{max}}) }{ \sum_{k'=1}^K \exp(x_{k'}) } = \frac{ 1 }{ \sum_{k=1}^K \exp(x_{k'}) \exp(- x_{\mathrm{max}}) } = \frac{ 1 }{ \sum_{k=1}^K \exp(x_{k'} - x_{\mathrm{max}}) } $$

となります。
 それぞれ代入します。

$$ \begin{aligned} y_k &= \exp(x_k - x_{\mathrm{max}}) \frac{ 1 }{ \sum_{k=1}^K \exp(x_{k'} - x_{\mathrm{max}}) } \\ &= \frac{ \exp(x_k - x_{\mathrm{max}}) }{ \sum_{k=1}^K \exp(x_{k'} - x_{\mathrm{max}}) } \end{aligned} $$

 オバーフロー対策を行ったソフトマックス関数の定義式が得られました。入力$\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$を使わずにソフトマックス関数の定義式を表すと、次の式になります。

$$ y_k = \frac{ \exp(x_k) }{ \exp(x_1) + \cdots + \exp(x_K) } $$

 右辺に$\frac{\exp(x_{\mathrm{max}})}{\exp(x_{\mathrm{max}})} = 1$を掛けます。

$$ y_k = \frac{ \exp(x_k) \exp(x_{\mathrm{max}}) }{ \Bigl(\exp(x_1) + \cdots + \exp(x_K)\Bigr) \exp(x_{\mathrm{max}}) } $$

 右辺を分割します。

$$ y_k = \frac{\exp(x_k)}{\exp(x_{\mathrm{max}})} \frac{ \exp(x_{\mathrm{max}}) }{ \exp(x_1) + \cdots + \exp(x_K) } $$

 指数の定義より$e^{-a} = \frac{1}{e^a}$であり、また$e^a = \frac{1}{e^{-a}}$です。$\exp(x_{\mathrm{max}})$に関して、この変形をします。

$$ y_k = \exp(x_k) \exp(- x_{\mathrm{max}}) \frac{ 1 }{ \exp(x_1) + \cdots + \exp(x_K) } \frac{ 1 }{ \exp(- x_{\mathrm{max}}) } $$

 指数法則より$\exp(a) \exp(- b) = \exp(a - b)$です。別の書き方をすると、$e^a e^{-b} = e^{a-b}$です。よって、分母の項を

$$ \begin{aligned} &\Bigl(\exp(x_1) + \cdots + \exp(x_K)\Bigr) \exp(- x_{\mathrm{max}}) \\ &= \exp(x_1) \exp(- x_{\mathrm{max}}) + \cdots + \exp(x_K) \exp(- x_{\mathrm{max}}) \\ &= \exp(x_1 - x_{\mathrm{max}}) + \cdots + \exp(x_K - x_{\mathrm{max}}) \end{aligned} $$

とまとめられます。
 分子の項もまとめて、それぞれ代入します。

$$ \begin{aligned} y_k &= \exp(x_k - x_{\mathrm{max}}) \frac{ 1 }{ \exp(x_1 - x_{\mathrm{max}}) + \cdots + \exp(x_K - x_{\mathrm{max}}) } \\ &= \frac{ \exp(x_k - x_{\mathrm{max}}) }{ \exp(x_1 - x_{\mathrm{max}}) + \cdots + \exp(x_K - x_{\mathrm{max}}) } \end{aligned} $$

 分母の$k = 1, 2, \cdots, K$の項を$\sum_{k=1}^K$でまとめると、パターン2で導出した式になります。

 以上で、オバーフロー対策版のソフトマックス関数の定義式を導出できました。3.5節で実装します。

参考文献

  • 斎藤康毅『ゼロから作るDeep Learning』オライリー・ジャパン,2016年.

おわりに

  • 2021.07.26:加筆修正しました。その際に記事を分割しました。

 微妙に間違っていました。その内容自体は難しくないのですが、自分でも勘違いしたことをどう書いたものかと迷った結果、式展開が3つになりました。

【関連する記事】

www.anarchive-beta.com