Deep Learning

【やってみた】ゼロから作るDeep Learning① (3.5)

はじめに

前回の記事

【やってみた】ゼロから作るDeep Learning① (3.1~3.4)はじめに 前回の記事 https://shotslog.com/%e3%80%90%e3%82%84%e3%81%a3%e3%8...

この記事は、ゼロから作るDeep Learning(以下ゼロつく)のアウトプット学習用に書いています。
今回は3章の5を扱います。
テーマは出力層の設計についてです。

同じように機械学習・ディープラーニングを学習している方にもわかりやすいように書きたいと思います。
理解を深めるため、ぜひ書籍と併せて読んでいただければと思います。

恒等関数とソフトマックス関数

ニューラルネットワークは、分類問題も回帰問題も扱うことができます。
ただ、分類か回帰かによって活性化関数が異なる点に注意です。

  • 分類問題 ⇨ ソフトマックス関数
  • 回帰問題 ⇨ 恒等関数

恒等関数は、以前も紹介した通り、入力をそのまま出力する関数です。
ソフトマックス関数は、0から1の間の実数を出力します。その出力の総和は必ず1になります。
必ず1になるということは、(誤解を恐れずに言うと)ソフトマックス関数の出力は確率を表すと言えます。

ソフトマックス関数

ソフトマックス関数を数式で表すと次のようになります。
$$
y_k = \frac{\exp(a_k)}{\sum_{i=1}^{n} \exp(a_i)}
$$
\(exp(x)\)は\(e^x\)を表す指数関数で、単調増加関数です。
なぜソフトマックス関数にネイピア数\(e\)を使うのかについては、
https://gensasaki.hatenablog.com/entry/2018/08/30/042807
こちらの記事にわかりやすくまとまっていました。
非線形な単調増加関数で、微分をしても\(e^x\)は\(e^x\)のままなので確率と解釈するのに都合が良いということですね。
このあたりの数学的知識については、私も勉強していきたいです。

ソフトマックス関数の実装

先ほどの数式をそのままPythonで表現してみます。

a = np.array([0.3, 2.9, 4.0])

exp_a = np.exp(a)
print(exp_a)

sum_exp_a = np.sum(exp_a)
print(sum_exp_a)

y = exp_a / sum_exp_a
print(y)

# 出力
[ 1.34985881 18.17414537 54.59815003]
74.1221542101633
[0.01821127 0.24519181 0.73659691]

数値は書籍のものを使用していますが、ソフトマックス関数として実装できたことがわかります。
これを関数として定義しておきます。

def softmax(a) :
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

ソフトマックス関数のオーバーフロー問題

ソフトマックス関数の上記の実装では、問題点があります。
指数関数の計算を行う上で、指数の値が大きくなると計算結果が不安定になってしまいます。
コンピュータで計算が行われる際は、有効桁数が決まっており、それを超えると表現できなくなります。
それがオーバーフロー問題です。

改善するためには次のようにします。
$$
y_k = \frac{
C \exp(a_k)
}{
C \sum_{i=1}^n \exp(a_k)
}
$$
これは、先ほどの数式
$$
y_k = \frac{\exp(a_k)}{\sum_{i=1}^{n} \exp(a_i)}
$$
に\(C\)という任意の定数を分母分子にかけています。

そして、\(C\)は中に入れても結果が変わらないため、以下のようにします。
$$
y_k = \frac{
\exp(a_k) C
}{
\sum_{i=1}^n \exp(a_k) C
}
$$
対数\(log\)の法則を用います。
$$
y_k = \frac{
\exp(a_k) exp(logC)
}{
\sum_{i=1}^n \exp(a_k) exp(logC)
}
$$
\(exp\)でまとめます。
$$
y_k = \frac{
\exp(a_k + logC)
}{
\sum_{i=1}^n \exp(a_k + logC)
}
$$
\(logC\)を\(C’\)に置き換えます。
$$
y_k = \frac{
\exp(a_k + C’)
}{
\sum_{i=1}^n \exp(a_k + C’)
}
$$
よって、\(C’ = \log C = – a_{\mathrm{max}}\)となるような\(C’\)にすることで、足し算や引き算をしても結果が変わらなくなります。
これを踏まえて、ソフトマックス関数の定義を次のようにおきます。

def softmax(a) :
    c = np.max(a)
    exp_a = np.exp(a - c)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

出力層のニューロンの数

最後に出力層のニューロンの数についてです。
これは非常にシンプルな考え方ですが、クラス分類の場合、
分類したいクラス数 = 出力層のニューロン数とする場合が一般的なようです。
例えば、0から9までのいずれかの数値を予測する場合、ニューロンは10個に設定します。