ソフトマックス関数

ソフトマックス関数

ソフトマックス関数

 $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}\]
という値を入力してみましょう。

# SOFTMAX_01-1

# NumPyをインポート
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 で次元を保持するようにします。

# SOFTMAX_01-2

# 2次元配列を受け取るソフトマックス関数
def softmax(x):
    f = np.exp(x)/np.sum(np.exp(x), axis = 1, keepdims = True)
    return f

 3 つのベクトルを渡して確認してみましょう。

# SOFTMAX_01-3

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]]

 

カラー図解 最新 Raspberry Piで学ぶ電子工作 作って動かしてしくみがわかる (ブルーバックス)

新品価格
¥1,296から
(2019/8/21 23:40時点)

ソフトマックス関数の微分

 ソフトマックス関数 の定義式
 
\[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) のようになっているということです。

 Python シグモイド関数グラフ

 今度は $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(x, axis=None)

 2 次元配列に格納されている各々のベクトル (一次元配列) に対してソフトマックス関数を適用する場合は axis=1 を指定します。

# SOFTMAX_02-1

import numpy as np
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 になることを確認しておきます。

# SOFTMAX_02-2

# mの各ベクトルについて要素の和を計算
s = m.sum(axis=1)

print(s)
[1. 1.]

 列ベクトルに対してソフトマックス関数を適用したい場合は axis=0 を指定してください。axis を省略した場合は配列の全要素に対してソフトマックス関数が適用され、得られた配列の全要素について和をとると 1 になります。