1. 前提
softmax関数とは、$n$(任意の自然数)次元ベクトルを受け取って、$n$次元ベクトルを出力する関数であって、出力には次の特徴がみられる。
- 各成分は0以上1以下
- 総和は1
$${\rm softmax}\left(\begin{array}{c}
v_{0}\\
\vdots\\
v_{n-1}
\end{array}\right) =
\left(\sum_{i=0}^{n-1}\exp{v_i}\right)^{-1}
\left(\begin{array}{c}
\exp{v_{0}}\\
\vdots\\
\exp{v_{n-1}}
\end{array}\right)$$
2.問題点
softmax関数は、その特徴から、分類問題を解くネットワークにおける出力層の活性化関数としてしばしば利用される。
例えば「猫とライオン」を見分ける問題であれば、2次元ベクトルを計算してsoftmax関数に入力することで
「各成分が0以上1以下で、しかも成分の総和が1」である2次元ベクトルを得ることができる。
その各成分を「猫である確率」「ライオンである確率」と半ば無理やり解釈することができる。
しかしだ。softmax関数の出力を「各選択肢が正解である確率」とみる手法では、「自信の強さ」が変数のスケールによって変化してしまう。
例えば
$$
{\rm softmax}\left(\begin{array}{c}
1\\-2
\end{array}\right)=
\left(\begin{array}{c}
0.953\\
0.047
\end{array}\right)$$
であるが、
$$
{\rm softmax}\left(\begin{array}{c}
0.1\\-0.2
\end{array}\right)=
\left(\begin{array}{c}
0.574\\
0.426
\end{array}\right)$$
である。
成分比が同じでも、その絶対的な大きさが小さければ小さいほど、「自信がなくなる」(出力の各成分が一様に近づく)し、
大きければ大きいほど、「自信がでる」(出力の各成分が一様でなくなる)
もちろん、この特性は「正解を予測するだけでなく、自身まで表現できて素晴らしいじゃないか」という場合もあるだろう。その場合は「分散調節済みソフトマックス関数」など不要だろう。
しかし、「遺伝的アルゴリズムにおける交叉の過程で、各遺伝子が親として選ばれる確率を
${\rm softmax}(各遺伝子のスコア)$
として求めたい」といった場合、確率の偏りの大きさがある程度決まっていた方が便利だろう。
改善
出力の各成分の分散を予め決めて置き、その分散が得られるように変数のスケールを調整する
def softmax(v):
分子 = np.exp(v)
分母 = sum(分子)
return 分子/分母
def 分散調節済みソフトマックス(入力, 分散, see_a=False):
入力 = np.array(入力)
a = 1 # 各成分のスケールを調整する係数を2分法で求める
da = 1
while not (np.var(softmax(a*入力)) > 分散[0] and np.var(softmax(a*入力)) < 分散[1]):
while np.var(softmax(a*入力)) < 分散[0]:
a += da
da /= 2
while np.var(softmax(a*入力)) > 分散[1]:
a -= da
da /= 2
if see_a: print("a:", a)
return softmax(a*入力)
>>> np.var(softmax([1,2]))
0.053388066758518156
>>> np.var(softmax([10,20]))
0.24995460419226406
>>> 分散調節済みソフトマックス([1,2], [0.01, 0.02], see_a=True) # 分散を0.01~0.02の範囲に収めたい
a: 0.5
array([0.37754067, 0.62245933])
>>> np.var(分散調節済みソフトマックス([1,2], [0.01, 0.02]))
0.014996287798405504 # 範囲内!