ソフトマックス関数
$n$ 次元の実数ベクトル
\[\boldsymbol{x}=\begin{bmatrix}x_1\\x_2\\\vdots\\x_n\end{bmatrix}\]
が与えられたとき、
\[y_i=\frac{\exp(x_i)}{\displaystyle\sum_{i=1}^{n}\exp(x_i)}\]
で表される成分をもつ実数ベクトル
\[\boldsymbol{y}=\begin{bmatrix}y_1\\y_2\\\vdots\\y_n\end{bmatrix}\]
を返す関数をソフトマックス関数 (softmax function)とよびます。$\displaystyle u=\sum_{i=1}^{n}\exp(x_i)$ とおくと、ソフトマックス関数の各成分は
\[y_i=\frac{\exp(x_i)}{u}\]
と表せます。
ソフトマックス関数の性質
$x_i$ を入力値、$y_i$ を出力値と考えると、ソフトマックス関数への入力値の大小関係はそのまま出力値に反映されます。すなわち、入力値について
\[x_1\lt x_2\lt x_3\]
であるときは、必ず
\[y_1\lt y_2\lt y_3\]
のようになります。実際に ソフトマックス関数 を実装して
\[\boldsymbol{x}=\begin{bmatrix}x_1\\x_2\\x_3\end{bmatrix}=\begin{bmatrix}1\\2\\3\end{bmatrix}\]
という値を入力してみましょう。
# PYTHON_SOFTMAX_FUNCTION # In[1] import numpy as np # ソフトマックス関数を定義 def softmax(x): u = np.sum(np.exp(x)) return np.exp(x)/u # x=(1,2,3) x = np.array([1, 2, 3]) # ソフトマックス関数にxを入力してを出力値を計算 y = softmax(x) print(y) # [0.09003057 0.24472847 0.66524096]
実行結果を見ると、出力値 $y_i$ は
\[\boldsymbol{y}=\begin{bmatrix}y_1\\y_2\\y_n\end{bmatrix}=\begin{bmatrix}\cfrac{\exp(x_1)}{\exp(x_1)+\exp(x_2)+\exp(x_3)}\\\cfrac{\exp(x_2)}{\exp(x_1)+\exp(x_2)+\exp(x_3)}\\\cfrac{\exp(x_3)}{\exp(x_1)+\exp(x_2)+\exp(x_3)}\end{bmatrix}=\begin{bmatrix}0.09003057\\0.24472847\\0.66524096\end{bmatrix}\]
となっていて、確かに $y_1\lt y_2\lt y_3$ を満たしています。また、ソフトマックス関数 の定義から明らかなように、
\[\begin{align*}&0\lt y_i\lt 1\\[6pt]&y_1+y_2+\cdots+y_n=1\end{align*}\]
という性質があるので、どのような値を入力しても出力値は確率分布の条件を満たしています。
機械学習では、ソフトマックス関数に複数のベクトルをまとめて渡したいこともあります。そのような場合は、axis 引数で和をとる方向を指定して、keepdims で次元を保持するようにします。
# PYTHON_SOFTMAX_FUNCTION_MULTI_VECTOR # In[1] # 2次元配列を受け取るソフトマックス関数 def softmax(x): f = np.exp(x)/np.sum(np.exp(x), axis=1, keepdims=True) return f
3 つのベクトルを渡して確認してみましょう。
# In[2] np.random.seed(0) # [[6 1 4] # [4 8 4] # [6 3 5]] x = np.random.randint(1, 10, (3, 3)) # ソフトマックス関数に2次元配列xを渡す y = softmax(x) print(y) # [[0.8756006 0.00589975 0.11849965] # [0.01766842 0.96466316 0.01766842] # [0.70538451 0.03511903 0.25949646]]
ソフトマックス関数 の定義式
\[y_i=\frac{\exp(x_i)}{u}\]
において、両辺に $u$ をかけると
\[uy_i=\exp(x_i)\]
となります。両辺を $x_i$ で微分すると、積の微分公式により
\[\frac{\partial u}{\partial x_i}y_i+u\frac{\partial y_i}{\partial x_i}=\exp(x_i)\]
$\displaystyle u=\sum_{i=1}^{n}\exp(x_i)$ を $x_i$ で微分すると $\exp(x_i)$ だけが残るので、上式は
\[y_i\exp(x_i)+u\frac{\partial y_i}{\partial x_i}=\exp(x_i)\]
となります。したがって、
\[\frac{\partial y_i}{\partial x_i}=\frac{(1-y_i)\exp(x_i)}{u}\]
となりますが、$y_i=\exp(x_i)/u$ なので、
\[\frac{\partial y_i}{\partial x_i}=y_i(1-y_i)\tag{1}\]
という式を得ます。この式はシグモイド関数
\[f(x)=\frac{1}{1+e^{-ax}}\quad (a\gt 0)\]
の導関数
\[\frac{dy}{dx}=ay(1-y)\]
と同じ形をしています。つまり、$y_i$ の $x_i$ 軸方向に沿った勾配の様子はシグモイド関数 (下図 a = 1) のようになっているということです。
今度は $i\neq j$ として、
\[uy_i=\exp(x_i)\]
の両辺を $x_j$ で微分してみます。
\[\frac{\partial u}{\partial x_j}y_i+u\frac{\partial y_i}{\partial x_j}=0\]
$\partial u/\partial x_j=\exp(x_j)$ を用いて上式を整理すると
\[\frac{\partial y_i}{\partial x_j}=-y_iy_j\quad (i\neq j)\tag{2}\]
が得られます。(1) と (2) をまとめると
\[\frac{\partial y_i}{\partial x_j}=y_i(\delta_{ij}-y_j)\]
という式で表すことができます。$\delta_{ij}$ はクロネッカーのデルタとよばれる記号で、$i=j$ のときに $1$, $i\neq j$ のときは $0$ となることを意味しています。
scipy.special.softmax()
SciPy 1.20 以降のバージョンには、ソフトマックス関数を計算する scipy.special.softmax() が実装されています。
scipy.special.softmax(x, axis=None)
二次元配列に格納されている各々のベクトル (一次元配列) に対してソフトマックス関数を適用する場合は axis=1 を指定します。
# SCIPY_SOFTMAX_FUNCTION # In[1] import numpy as np # SciPyパッケージからソフトマックス関数をインポート from scipy.special import softmax # 2次元配列を定義 x = np.array([[1, 1, 5], [1, 2, 9]]) # [1 1 5]と[1 2 9]それぞれにソフトマックス関数を適用 m = softmax(x, axis=1) print(m) # [[1.76684220e-02 1.76684220e-02 9.64663156e-01] # [3.35044712e-04 9.10745952e-04 9.98754209e-01]]
得られた配列 m の各々のベクトルについて要素の和をとると 1 になることを確認しておきます。
# In[2] # mの各ベクトルについて要素の和を計算 s = m.sum(axis=1) print(s) # [1. 1.]
列ベクトルに対してソフトマックス関数を適用したい場合は axis=0 を指定してください。axis を省略した場合は配列の全要素に対してソフトマックス関数が適用され、得られた配列の全要素について和をとると 1 になります。
コメント
[ChatGPT]「ソフトマックス関数って、何がソフトなの?」
そんな疑問を抱く人もいるかもしれませんが、実はこの関数は、その名の通り、「ソフトな」関数なのです。ソフトマックス関数は、機械学習の分野でよく使われる関数で、与えられた数値データを、確率分布に変換するのに使われます。要するに、数字を柔らかく、ゆるやかに変換して、それぞれが出現する確率を表すわけです。そのため、ソフトマックス関数を使えば、「どの数字が出やすいのか?」という確率的な問題を扱うことができるのです。そういうわけで、ソフトマックス関数は、ちょっとした「おせんべい」のようなものかもしれませんね。