機械学習・深層学習 学習まとめ: 活性化関数と損失関数

# 活性化関数
- ニューラルネットワークにおいて、次の層への出力の大きさを決める関数
    - 入力値の値によって、次の層への信号のON/OFFや強弱を定める働きをもつ

## 恒等写像関数
- 定義
    - $ f(x) = x $
- 特徴
    - あらゆる入力値を、全く同じ数値として（＝そのまま）出力する
- 実装
```python
# 恒等写像関数
def identity(x):
    return x
```

## ステップ関数
- 定義
    - $ f(x) = \begin{cases} 1 &ensp; (x \geqq 0) \\\\ 0 &ensp; (x < 0) \end{cases} $
- 特徴
    - 単純パーセプトロンと同じ動作
    - $ x=0 $で微分できないため、ニューラルネットワークでは基本的に用いられない
- 実装
```python
# ステップ関数
def step_function(x):
    return np.where( x > 0, 1, 0)
```

## シグモイド関数(ロジスティック関数)
- 定義
    - $ f(x) = \dfrac{1}{1+e^{-x}} $
- 特徴
    - 正規化機能を持つ、すなわち、あらゆる実数の入力値に対し、0〜1を出力する
    - ステップ関数と似ているが、滑らかなので微分ができる
    - 勾配消失問題(vanishing gradient problem)があるため、RNN(再帰型ニューラルネットワーク)を除き、中間層での利用は減っている
- 微分
    - $ \dfrac{d}{dx}f(x) = (1-f(x))f(x) $
    - シグモイド関数の微分はシグモイド関数自体で表すことができる
    - 微分の最大値は$ x = 0 $において$ 0.25 $
- 実装
```python
# シグモイド関数（ロジスティック関数）
def sigmoid(x):
    return 1/(1 + np.exp(-x))

# シグモイド関数（ロジスティック関数）の導関数
def d_sigmoid(x):
    return (1.0 - sigmoid(x)) * sigmoid(x)
```

## tanh関数(双曲線正接関数)
- 定義
    - $ f(x) = \dfrac{e^x - e^{-x}}{e^x + e^{-x}} $
- 特徴
    - あらゆる実数の入力値に対し、-1〜1を出力する
    - 平均が0となるため、活性化値に「0中心のガウス分布」を期待した学習が行え、学習が安定・高速化しやすくなる
    - ReLU関数に置き換えられ、利用は減っている
- 微分
    - $ \dfrac{d}{dx}f(x) = 1 - f(x)^2 $
    - 微分の最大値は$ x = 0 $において$ 1 $
- 実装
```python
# tanh関数（双曲線正接関数）
def tanh(x):
    return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))

# tanh関数（双曲線正接関数）の導関数
def d_tanh(x):
    return 1.0 - (tanh(x) ** 2)
```

## ソフトプラス関数
- 定義
    - $ f(x) = \log (1+e^x) $
- 特徴
    - あらゆる実数の入力値に対し、0〜∞を出力する
        - すなわち、正規化機能を持たない
    - 今ではReLU関数を利用することが一般的になり、使用されることは減っている
- 微分
    - $ \dfrac{d}{dx}f(x) = \dfrac{1}{1+e^{-x}} $
    - 微分したものはシグモイド関数と同じになる
- 実装
```python
# シグモイド関数（ロジスティック関数）
def softplus(x):
    return np.log(1.0 + np.exp(x))

# ソフトプラス関数の導関数
def d_softplus(x):
    return 1/(1 + np.exp(-x))
```

## ReLU関数(正規化線形関数、ランプ関数)
- 定義
    - $ f(x) = \begin{cases} x &ensp; (x > 0) \\\\ 0 &ensp; (x \leqq 0) \end{cases} $
    - $ max(0, x) $とも定義できる
- 特徴
    - 正規化機能を持たないため、勾配消失問題が起きにくく、形も簡単であるため、今最も使われている
    - 値域は(0, ∞)
    - 0以下の領域でも0以外の値を出力するLeakyReLU($ max(0.01x, x) $、ここで0.01はハイパーパラメーター)といった亜種も存在する
- 微分
    - $ \dfrac{d}{dx}f(x) = \begin{cases} 1 &ensp; (x > 0) \\\\ 0 &ensp; (x \leqq 0) \end{cases} $ 
```python
# ReLU関数
def relu(x):
    return np.maximum(0, x)

# ReLU関数の導関数
def d_relu(x):
    return np.where( x > 0, 1, 0)
```

## ソフトマックス関数
- 定義
    - $ f(i, \boldsymbol{x}) = \dfrac{e^{x_i}}{\sum_{k=1}^Ke^{x_k}} $
        - $ \boldsymbol{x} $が実数からなる入力ベクトル、$ i $がその中の要素を特定
        - $ K $が分類結果のクラス数
- 特徴
    - 入力ベクトルの各要素を正規化(0〜1)し、それらを合計すると1になる確率分布に変換する
    - これにより、どのクラスに属するかの「確率」として解釈できる
    - 計算時には、数値的安定性のためにオーバーフローを防ぐ工夫が必要
        - 入力ベクトルから最大値を引くことで、安定した計算が可能になる
- 微分
    - $　\dfrac{∂y_i}{∂x_k} = y_i(δ_{ik} - y_k) $
        - $ δ_{ik} $はクロネッカーのデルタ記号、$ δ_{ik} = \begin{cases} 1 &ensp; (i = k) \\\\ 0 &ensp; (i \neq k) \end{cases} $
        - つまり、$　\dfrac{∂y_i}{∂x_k} = \begin{cases} y_i - y_i^2 &ensp; (i = k) \\\\ -y_iy_k &ensp; (i \neq k) \end{cases}　$
    - 微分の実装は損失関数である交差エントロピー誤差との組合せになることが一般的(後述)
- 実装
```python
# ソフトマックス関数
def softmax(x):
    # ミニバッチ処理用
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
    return y.T
    # 一般用
    x = x - np.max(x) # オーバーフロー対策
    return np.exp(x) / np.sum(np.exp(x))
```

## 活性化関数の用途
||中間層|出力層|
|:--|:-:|:-:|
|恒等写像関数|-|○(回帰)|
|ステップ関数|△|-|
|シグモイド関数|△(RNN)|○(二値分類)|
|tanh関数|△(RNN)|○|
|ReLU関数|○|-|
|ソフトマックス関数|-|○(多クラス分類)|


# 損失関数(誤差関数)
- 学習時におけるニューラルネットワークの出力の良し悪しを評価する指標

## 平均二乗誤差(二乗和誤差)
- 回帰問題に用いられる
- 定義
    - $ L = \dfrac{1}{2} \displaystyle \sum_{k=1}^{K}(y_k - t_k)^2 = \dfrac{1}{2} \vert\vert (y - t) \vert\vert ^2 $
    - $ y_k $は出力値、$ t_k $は正解値
- 偏微分
    - $　\dfrac{∂L}{∂y_k} = y_k - t_k $
- 実装
    - 平均二乗誤差の実装においては、$ y $が先でも$ t $が先でも同じ(2乗するので)
```python
# 平均二乗誤差
def mean_squared_error(t, y):
    return np.mean(np.square(y - t)) / 2

# 平均二乗誤差の導関数
def d_mean_squared_error(t, y):
    if type(t) == np.ndarray:
        batch_size = t.shape[0]
        dx = (y - t) / batch_size
    else:
        dx = y - t
    return dx
```

## 交差エントロピー誤差
- 分類問題に用いられる
- 定義
    - $ L = - \displaystyle \sum_{k=1}^K t_k \log y_k $
    - $ y_k $は出力値、$ t_k $は正解値
- 偏微分
    - $　\dfrac{∂L}{∂y_k} = - \dfrac{t_k}{y_k} $
- 実装
    - +1e-7はlogの対象が0になって-∞に発散するのを防ぐために加算
    - 導関数はシグモイド関数またはソフトマックス関数との組合せで実装するのが一般的
```python
# クロスエントロピー
def cross_entropy_error(t, y):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
        
    # 教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
    if t.size == y.size:
        t = t.argmax(axis=1)
             
        batch_size = y.shape[0]
        return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

# クロスエントロピーとシグモイド関数の複合導関数
def d_sigmoid_with_loss(t, y):
    return y - t

# クロスエントロピーとソフトマックス関数の複合導関数
def d_softmax_with_loss(t, y):
    batch_size = t.shape[0]
    if t.size == y.size: # 教師データがone-hot-vectorの場合
        dx = (y - t) / batch_size
    else:
        dx = y.copy()
        dx[np.arange(batch_size), t] -= 1
        dx = dx / batch_size
    return dx
```

# タスクによる活性化関数と損失関数(誤差関数)の組み合わせ
||回帰|二値分類|多クラス分類|
|:--|:-:|:-:|:-:|
|活性化関数|恒等写像関数|シグモイド関数|ソフトマックス関数|
|損失関数|二乗誤差|交差エントロピー|交差エントロピー|