1,126 views
【1】やりたいこと
PyTorchや TensorFlowなどの機械学習ライブラリを使わない場合、ライブラリが提供してくれている機能を自力実装する必要がある。
本項では、MNISTや CIFAR-10のような多クラス分類で使用する Softmax関数を実装 してみる。
機械学習ライブラリを使わない Python自力実装シリーズ は以下の通り。
(56) 逆伝播で使う tanh(x)の微分
(57) 逆伝播で使う sigmoid(x)の微分
(58) 逆伝播で使う ReLU(x)の微分
(59) 逆伝播で使う MSE(平均二乗誤差)の微分
(60) 他クラス分類で使う Softmax ←今回
(61) 機械学習で多用されるネイピア数とは?
【2】やってみる
多クラス分類タスクでは、
出力層における各クラススコアを Softmax 関数に通すことで、
全クラスに対する確率分布に変換する。
→ 扱いやすくなる!
1) Softmaxの計算式
Softmaxとは、
複数の入力値に対して、
出力値の合計が 1になるように変換・出力し、
確率っぽい値を作るロジックのこと。
| n | 出力層のニューロン数 |
| zi | 出力層のニューロン[i:1,2,3,…]の出力値 |
| pi | softmax関数の出力値(=予測確率) ※ラベルごとに確率を出す。 |
2) 分かりやすい例で確認
出力層のニューロン数 3個の場合について見てみる。
🔸出力層のニューロンの位置によって変わるのは、分子の指数だけ。
🔸また、分母には全ニューロンの出力値が含まれており、確率 p の算出には他ラベルの出力値も考慮されている。

例えば、3クラス分類タスクにおいて、出力層から得られるスコアが [2, 3, 5] であったとする。
これらの値は未正規化のスコア(logits)であり、そのままでは確率として解釈できない ため、直接的な利用が難しい。
そこで、Softmax関数を適用することで、[0.04, 0.12, 0.84] のような 総和が 1となる確率分布 に変換する。
このように正規化された出力は [0, 1, 0] のような one-hot 表現の正解ラベルと照合可能になり、損失関数(例えば交差エントロピー損失)による学習が容易になる。

積算値が 1 になるのは当然 だ。softmaxはそう定義しているのだから・・・

3) Softmaxの特徴
(1) ただの Maxではなく Softとは?
| Max関数 | 最大値を選ぶ。 |
| Softmax関数 | 最大値(=最も可能性の高いラベル)を選びつつ、他ラベルの可能性の大きさも表せる。 |
(2) e を使うと何が嬉しい?
出力層から得られるスコア zi が [2, 3, 5] の場合について、Softmax関数の出力値 pi を下図に記す。
Softmaxは線形比率ではなく指数関数で重み付けする ため、
下図に示すように zi よりも pi の方が 強者が強調 される。
これにより・・・
正解ラベルの確率を高めることができ、分類器の訓練が進みやすくなる。

4) Softmaxを Pythonで実装
"""/////////////////////////////////////////////////////////////////////////////
softmax関数
入力: x は 1次元または 2次元のnumpy配列
出力: 各要素を確率に変換した numpy配列(全要素の合計=1)
- 1次元: [z0, z1, z2] → [p0, p1, p2]
- 2次元: 各行ごとにsoftmax(バッチデータ対応)
/////////////////////////////////////////////////////////////////////////////"""
def softmax(x):
x = np.asarray(x)
#---------------------------------------------------------------------------
if x.ndim == 1: # 入力データが 1次元?(単一データ)
e_x = np.exp(x - np.max(x)) # 数値安定化(1)
#e_x = np.exp(x) # 数値安定化(2) ※(1),(2)のどちらでも同じ出力値になる。
return e_x / e_x.sum()
#---------------------------------------------------------------------------
elif x.ndim == 2: # 入力データが 2次元?(複数データ)
e_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return e_x / e_x.sum(axis=1, keepdims=True)
#---------------------------------------------------------------------------
else:
raise ValueError('入力は1次元または2次元の配列である必要があります。')
シンプルな実装に見えるが、ここには Python + Numpyの底力が隠れている。
numpyのベクトル化関数を使っているので、入力値 xは多次元配列(ndarray)を使える。
→ ミニバッチデータ等、複数データをまとめて処理できる。
※実際のデータ並列処理はライブラリ内部に隠蔽されている。
いくつか試してみる。
>>> z = np.array([5, 5]) >>> p = softmax(z) >>> p array([0.5, 0.5]) >>> >>> z = np.array([2, 3, 5]) >>> p = softmax(z) >>> p array([0.04201007, 0.1141952 , 0.84379473])
ミニバッチの場合(=複数の訓練データを一括処理する場合)
>>> z = np.array([[2, 3, 5],[1, 5, 7]])
>>> p = softmax(z)
>>> p
array([[0.04201007, 0.1141952 , 0.84379473],
[0.00217852, 0.11894324, 0.87887824]])
【3】所感
本当は、Softmax → CrossEntropy → 誤差算出 → 勾配初期値算出(CrossEntropy, Softmaxの微分)までを一気通貫で記録しておきたかった。
でも…
文量がかなり増えそうだったので、本項では順伝播時の Softmaxだけを扱った。
今後は、以下の順に投稿していこう。
・Softmaxの微分
・順伝播: CrossEntropyLossによる損失算出
・CrossEntropyLossの微分
・CrossEntropyLoss + Softmaxの導関数を使った勾配初期値算出
その後、以下のような全体の訓練の流れについて投稿する。
・順伝播
・逆伝播
・パラメータ更新
で・・・
これらを用いて、以下の応用例(基礎編だが)を作る。
・Sin関数の近似(回帰タスク)
・MNIST分類(分類タスク)
まずは 機械学習ライブラリを使わない Python自力実装シリーズ をここまで進めよう。
目標は 7/末まで。
あっ、オセロAIを全然進めていないぞ・・・