😊

Deep Learning資格試験 深層学習 順伝播ネットワーク

2022/01/30に公開

はじめに

日本ディープラーニング協会の Deep Learning 資格試験(E 資格)の受験に向けて、調べた内容をまとめていきます。

ニューラルネットワーク全体像

深層学習の発展は、人間の神経回路網を模したニューラルネットワークとうい数理モデルが基となっており、解決すべき問題に応じてさまざまなモデルが提案されている。

入力層

ニューラルネットワークの全体として、入力層から中間層にデータが変換されながら渡されて、活性化関数を通して出力層へデータが出力される。

入力層はニューラルネットワークに何かしらの数値を渡される。受け取る場所をノードという。入力層から中間層へ渡すときに重みを準備する。入力ノード毎に重要な項目であれば重みが大きくなり、重要でない項目の重みは小さくなる。重みだけで表現できない場合にバイアスで調整する。

入力層の設計

  • 入力層として取るべきでないデータ
    • 欠損値が多いデータ
    • 誤差の大きいデータ
    • 出力そのもの、出力を加工した情報
    • 連続性のないデータ(背番号とか)
    • 無意味な数が割り当てられているデータ
  • 欠損値の扱い
    • ゼロで詰める
    • 欠損値を含む集合を除外する
    • 入力として採用しない
  • データの結合
  • 数値の正規化・正則化

中間層(隠れ層)

中間層の出力は、入力層の計算結果(総入力)に対して、活性化関数を通すと得られる。中間層のの出力は次のネットワークの入力値となる。

活性化関数

シグモイド関数

  • 0 ~ 1 の間を緩やかに変化する関数である。
  • 課題として、大きな値では出力の変化が微小なため、勾配消失問題を引き起こす事がある。
  • 微分すると最大値は 0.25 である。(x=0の場合)
h(x) = \frac {1}{1+e^{-1}}
  • 微分すると下記の計算となる。
f'(x) = f(x)(1-f(x))

ソースコードはゼロから作る Deep Learningから引用

python
# シグモイド関数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# シグモイド関数の導関数
def sigmoid_grad(x):
    return (1.0 - sigmoid(x)) * sigmoid(x)

ReLU 関数

  • 入力が0を超えていれば、その入力をそのまま出力し、0以下ならば0を出力する関数である。
  • 勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている。
\begin{aligned} f(x) = \begin{cases} 0 \quad (x \leqq 0) \\ x \quad (x > 0) \\ \end{cases} \end{aligned}
  • 微分すると下記の結果となる。
\begin{aligned} f'(x) = \begin{cases} 0 \quad (x \leqq 0) \\ 1 \quad (x > 0) \\ \end{cases} \end{aligned}

ソースコード

ソースコードはゼロから作る Deep Learningから引用

python
# ReLU関数
def relu(x):
    return np.maximum(0, x)

# ReLU関数の導関数
def relu_grad(x):
    grad = np.zeros_like(x)
    grad[x>=0] = 1
    return grad

ハイパボリックタンジェント(tanh)関数

  • -1.0 ~ 1.0 の範囲の数値に変換して出力する関数である。
  • 座標点(0, 0)を基点(変曲点)として点対称である。
  • 微分すると最大値は 1.0 である。(x=0の場合)
\begin{aligned} f(x) = \frac{e^x-e^{-x}}{e^x+e^{-x}} \end{aligned}
  • 微分すると下記の計算となる。
f'(x) = 1-f(x)^2

ソースコード

ソースコードはゼロから作る Deep Learningから引用

python
# tanh関数
def tanh(x):
    return np.tanh(x)

恒等写像(回帰)

\begin{aligned} f(u) = u \end{aligned}

ソフトマックス関数(多クラス分類)

\begin{aligned} f(i , u) = \frac{e^{u_i}}{\sum_{k=1}^K e^{u_K}} \end{aligned}
  • 微分すると下記の計算となる。
\begin{aligned} \frac{\partial}{\partial u} f(i , u) = f(i , u) (\delta_{ik} - f(k , u)) \end{aligned}

ここで、\delta_{ik}は、下記の値をとる記号である。

\begin{aligned} \delta_{ik} = \begin{cases} 0 \quad (i \neq k) \\ 1 \quad (i = k) \\ \end{cases} \end{aligned}

つまり、i = kの時の微分は、

\begin{aligned} \frac{\partial}{\partial u} f(i , u) = f(i , u) - f(i , u)^2 \end{aligned}

i \neq kの時の微分は、

\begin{aligned} \frac{\partial}{\partial u} f(i , u) = f(i , u) f(k , u) \end{aligned}

オーバーフロー対策について

ソフトマックス関数には、入力の各要素すべてに同じスカラーを加えても、ソフトマックス関数の出力は変わらない。

\begin{aligned} softmax(z) = softmax(z + c) \end{aligned}

この性質は、ソフトマックス関数の分子と分母に任意の定数をかけることで、導くことができる。この性質から、ソフトマックス関数は入力の差のみに依存する。

\begin{aligned} \frac{softmax(z)_i}{softmax(z)_j} = exp(z_i - z_j) \end{aligned}

この性質を用いると、オーバーフロー対策として、入力zから入力値の最大値を減じることで、ソフトマックス関数が数値的に安定する。

ソースコード

ソースコードはゼロから作る Deep Learningから引用

python
import numpy as np

def softmax(x):
    x = x - np.max(x, axis=-1, keepdims=True)   # オーバーフロー対策
    return np.exp(x) / np.sum(np.exp(x), axis=-1, keepdims=True)

ソフトマックス関数をニューラルネットワークに適用することを考慮して、順伝播処理と逆伝播処理を行うクラスを Softmax クラスとして実装すると、下記のようになる。

python
class Softmax():
    def  __init__(self):
        self.params , self.grads = [], []
        self.out = None

    def forward(self, x):
        self.out =  = softmax(x)
        return self.out

    def backward(self, dout):
        dx = self.out * dout
        sumdx = np.sum(dx, axis=1, keepdims=True)
        dx -= self.out * sumdx
        return dx

誤差逆伝播法(バックプロパゲーション)

誤差勾配の計算

\begin{aligned} \nabla = \frac{\partial E}{\partial w} = \left[ \frac{\partial E}{\partial w_1} \ldots \frac{\partial E}{\partial w_M} \right] \\ \end{aligned}

どう計算するか?

算出された誤差を、出力層側から順に微分し、前の層前の層へと伝播する。
最小限の計算で各パラメータでの微分値を解析的に計算する手法。

計算結果(=誤差)から微分を逆算することで、不要な再帰的計算を避けて微分を算出できる。

誤差伝搬法はどのように計算している

下記の定義がある場合に、w^{(2)}を求めたい。
つまり、\frac{\partial E}{\partial w_{ji}^{(2)}}が最終的に求めたい値である。

\begin{aligned} E(y) &= \frac{1}{2} \sum_{j=1}^J (y_j - d_j)^2 = \frac{1}{2} ||y-d||^2 \\[8px] y &= u^{(L)} \\[8px] u^l &= w^{(l)} z^{(l-1)} + b^{(l)} \end{aligned}

zは前の層の出力

\frac{\partial E}{\partial w_{ji}^{(2)}}を展開していく

\begin{aligned} \frac{\partial E}{\partial w_{ji}^{(2)}} &= \frac{\partial E}{\partial y} \frac{\partial y}{\partial u} \frac{\partial u}{\partial w_{ji}^{(2)}} \\[8px] \frac{\partial E(y)}{\partial y} &= \frac{\partial}{\partial y} \frac{1}{2} ||y-d||^2 = y-b \\[8px] \frac{\partial y(u)}{\partial u} &= \frac{\partial u}{\partial u} = 1 \\[8px] \frac{\partial E}{\partial w_{ji}} &= \frac{\partial}{\partial w_{ji}}\left( w^{(l)} z^{(l-1)} + b^{(l)} \right) \\[8px] &= \frac{\partial}{\partial w_{ji}} \left( \left[ \left[ \begin{array}{ccccccccc} w_{11}z_1 & + & \ldots & + & w_{1i}z_i & + & \ldots & + & w_{1I}z_I \\ & & & & \vdots & & & & & \\ w_{j1}z_1 & + & \ldots & + & w_{ji}z_i & + & \ldots & + & w_{jI}z_I \\ & & & & \vdots & & & & & \\ w_{J1}z_1 & + & \ldots & + & w_{Ji}z_i & + & \ldots & + & w_{JI}z_I \\ \end{array} \right] + \left[ \begin{array}{c} b_1 \\ \vdots \\ b_j \\ \vdots \\ b_J \end{array} \right] \right] \right) = \left[ \begin{array}{c} 0 \\ \vdots \\ z_i \\ \vdots \\ 0 \end{array} \right] \\[8px] \end{aligned}

Ew_{ji}で微分するため、wの行列の真ん中の値以外は微分した結果、0になる。

\begin{aligned} \frac{\partial E}{\partial y} \frac{\partial y}{\partial u} \frac{\partial u}{\partial w_{ji}^{(2)}} &= (y-d) \cdot \left[ \begin{array}{c} 0 \\ \vdots \\ z_i \\ \vdots \\ 0 \end{array} \right] = (y_j - d_j)z_i \end{aligned}

誤差関数

y_iは予測値、t_iは真の値

二乗和誤差

\begin{aligned} E_n(w) = \frac{1}{2} \sum_{i=1}^I (y_i - t_i)^2 \end{aligned}

微分すると

\begin{aligned} \frac{\partial E_n(w)}{\partial y_i} = y_i - t_i \end{aligned}

交差エントロピー

\begin{aligned} E_n(w) = - \sum_{i=1}^I t_i \log{y_i} \end{aligned}

微分すると

\begin{aligned} \frac{\partial E_n(w)}{\partial y_i} = - \frac{t_i}{y_i} \end{aligned}

ソースコード

ソースコードはゼロから作る Deep Learningから引用

python
def cross_entropy_error(y, t):
    """
    y : ソフトマックス関数の出力
    t : 正解ラベル
    """
    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]
    delta = 1e-7
    return -np.sum(np.log(y[np.arange(batch_size), t] + delta)) / batch_size

class SoftmaxWithLoss:
    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_error(self.y, self.t)

        return self.loss

    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        if self.t.size == self.y.size: # 教師データがone-hot-vectorの場合
            dx = (self.y - self.t) / batch_size
        else:
            dx = self.y.copy()
            dx[np.arange(batch_size), self.t] -= 1
            dx = dx / batch_size

        return dx

Discussion