softmax with loss Layerの実装 (E資格対策)

前回のE検定で出題されたらしいので書きます


#Softmax関数とは


```math
y_k=\frac{exp(x_k)}{\sum_{j=1}^{N}exp(x_j)} \\
(y_1+y_2+\cdots + y_N=1)
```

で表される関数、活性化関数で用いられます。


##Softmax関数の実装



```python
import numpy as np

def softmax(x):
  sum_x=sum(np.exp(x))
  ex_x=np.exp(x)
  ex_x/=sum_x
  return ex_x

a=np.array([1,3,5])
soft_a=softmax(a)
print(soft_a) #[0.01587624 0.11731043 0.86681333]
```

できました。
理論上はこれでいいのですが、大きい値が入ってくると正しく計算ができない場合があります。
例えば

```python
b=np.array([1000,1030,800])
soft_b=softmax(b)
print(soft_b)
```
とすると

```
[nan nan nan]
```
と計算ができなくなってしまいます。
なので先ほどの式を少しいじってみます。

```math
\begin{align}
y_k&=\frac{exp(x_k)}{\sum_{j=1}^{N}exp(x_j)} \\
~\\
&=\frac{C~exp(x_k)}{C~\sum_{j=1}^{N}exp(x_j)} \\
~\\
&=\frac{exp(x_k+log~C)}{\sum_{j=1}^{N}exp(x_j+log~C)} \\
~\\
&=\frac{exp(x_k-C')}{\sum_{j=1}^{N}exp(x_j-C')} \\

\end{align}
```

分母と分子に$C$をかけ、$C=exp(log~C)$、さらに$log~C$を新たな定数$-C'$とすれば上記の式が成立します。
今回研鑽できなかった理由は、数値が大きすぎたからです。
そこで渡された配列の最大値を$C'$に代入し、計算できるようにします。

```python
import numpy as np

def softmax(x):
  max_x=max(x)#最大値を求める
  sum_x=sum(np.exp(x-max_x))#-max_xを追加
  ex_x=np.exp(x-max_x)#-max_xを追加
  ex_x/=sum_x
  return ex_x

b=np.array([1000,1030,800])
soft_b=softmax(b)
print(soft_b)
```
とすると

```
[9.35762297e-014 1.00000000e+000 1.29499819e-100]
```
無事計算ができました。


#CrossEntropyとは

Softmax関数は出力層に多く用いられるので、損失関数とくっついて出力されます。ここの損失関数として使わているのは交差エントロピーです。
ここでの交差エントロピーEは

$$
E=- \sum_k t_{k}log~y_{k}
$$

と表せます。$t_{k}$は教師データ、$y_{k}$はNNからの出力です。

##CrossEntropyの実装

```python
import numpy as np

def cross_entropy(t,y):
  delta=1e-7
  return -sum(t*np.log(y+delta)) #log0となるとinfで計算できなくなるため対策

t=[0,1,0]
y=[0.1,0.3,0.6]
print(cross_entropy(np.array(t), np.array(y)))

```
結果

```python
1.2039724709926583
```

```math
{\begin{align}
E &= -\sum_k t_{k}log~y_{k} \\
&=-\sum_k t_k \log \biggl (\frac{\exp(x_k)}{\sum_{j=1} ^n \exp(x_j)} \biggr ) \\
&= -\sum_k t_k x_k + \sum_k t_k \log \biggl (\sum_{j=1} ^n  \exp(x_j) \biggr ) \\
&= -\sum_k t_k x_k + \log \biggl (\sum_{j=1} ^n  \exp(x_j) \biggr ) \\
\end{align}
}

```


$\sum_k t_k=1,\sum_{j=1} ^n  \exp(x_j)$はkに依存しないため、このような式に変形できます。
そして逆伝播を求めると


```math
{\begin{align}
\frac{\partial E}{\partial x_k}
&= -t_k   +\frac{\exp(x_k)}{\sum_{j=1} ^n \exp(x_j)}  \\
&= -t_k   + y_k  \\
\end{align}
}

```

めっちゃきれいな形になりました。


##合体

これまでを踏まえて合体していきます。

```python
import numpy as np

def softmax(x):
  max_x=max(x)
  sum_x=sum(np.exp(x-max_x))
  ex_x=np.exp(x-max_x)
  ex_x/=sum_x
  return ex_x

def cross_entropy(t,y):
  delta=1e-7
  return -sum(t*np.log(y+delta))


class softmax_with_loss:
  def __init__(self):
    self.loss=None#損失
    self.y=None #softmaxの出力
    self.t=None #教師データ

  def forward(self,x,t):
    self.t=t
    self.y=softmax(x)
    self.loss=cross_entropy(self.t,self.y)

    return self.loss

  def backward(self, dout=1):
    batch_size=self.t.shape[0]
    dx=(self.y-self.t)/batch_size

    return dx

t=np.array([0,1,0])
x=np.array([0.1,0.3,0.6])

sofmax=softmax_with_loss()
print(sofmax.forward(x,t))
print(sofmax.backward())

```

結果

```
1.1532862384703433
[ 0.08612988 -0.22813406  0.14200417]
```


無事計算できました！


参考文献
ゼロから作るDeep Learning: Pythonで学ぶディープラーニングの理論と実装
https://qiita.com/Yoko303/items/09efd10161d0a764833d