ゼロから作るDeep Learning 2 自然言語処理編 1.3 まとめ

# これはなに

社内の勉強会で発表するときに使った資料です

---

# 1.3 ニューラルネットワークの学習

---

## 1.3.1 損失関数
ニューラルネットワークで"良い推論"をするためには、最適なパラメータを設定しなければならない

ニューラルネットワークの学習には、学習がどれだけ上手くいっているかを知るための指標が必要
→**損失**という

ニューラルネットワークの損失を求めるために使うのが**損失関数**

---

- 損失関数
    - 2乗誤差（ゼロから始めるDeep Learning 1 にあった）
        - @ohakutsu 回帰に使うのかな？
    - 交差エントロピー誤差
        - 多クラス分類に用いられることが多い

---

今節では、損失を求めるのに以下のようなレイヤ構成にする

![1e49abb0-2cb2-dd5d-d881-5ee1cf946b46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/e52896a0-dc52-c5a6-3699-d4357e5ce6d1.png)

---

Softmax と Cross Entropy Error のレイヤをまとめて、

![bd2831b7-351e-9951-be8e-115321114a48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/13d9f359-61e5-9415-203d-a0ad5895b097.png)

Softmax with Loss とする

---

Softmaxとはなんぞや
→**ソフトマックス関数**

```math
y_k =  \frac {exp(s_k)}{\displaystyle \sum _{i=1}^{n} exp(s_i)}
```

- 特徴
    - 出力が0.0〜1.0の実数
    - 出力をすべて足すと1.0になる
        - 確率として解釈することができる

---

Cross Entropy Errorとはなんぞや
→**交差エントロピー誤差**

```math
L = - \sum_{k}t_k\space log\space y_k
```

- 特徴
    - t は`one_hot表現`（0か1）の教師ラベルなので、ラベルが1のときの自然対数を返すだけ
    - yが0に近いほど小さくなり、1に近いほど0に収束する

![logx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/277fc505-6f44-eebb-de23-c0c5d62eaa51.png)

---

ミニバッチ処理を考慮して、

```math
L = - \frac{1}{N} \sum_{n}\sum_{k}t_{nk}\space log\space y_{nk}
```

を使う

---

## 1.3.2 微分と勾配

ニューラルネットワークの学習の目標は、損失をできるだけ小さくするパラメータを見つけること
ここで重要になるのが**微分**、**勾配**である

---

**微分**
→ある瞬間の変化の量
@ohakutsu [中学数学からはじめるAI(人工知能)のための数学入門 - YouTube](https://www.youtube.com/watch?v=7A05OamqCyc) でわかります

```math
y = f(x)
```

の x に関する y の微分は

```math
\frac{dy}{dx}
```

と表せる

---

変数が複数あっても微分を求めることができる
x をベクトルとして、

```math
L = f(x)
```
```math
\frac{\partial L}{\partial x} = \left( \frac{\partial L}{\partial x_1}, \frac{\partial L}{\partial x_2}, ..., \frac{\partial L}{\partial x_n} \right)
```

ベクトルの各要素の微分をまとめたものを**勾配**と呼ぶ

行列の場合も同様に勾配を考えることができる
W を m×n 行列として、

```math
L = g(W)
```
```math
\frac{\partial L}{\partial W} = \left(
  \begin{array}{ccc}
    \frac{\partial L}{\partial w_{11}} & \cdots & \frac{\partial L}{\partial w_{1n}} \\
    \vdots & \ddots & \\
    \frac{\partial L}{\partial w_{m1}} & & \frac{\partial L}{\partial w_{mn}}
  \end{array}
\right)
```

---

## 1.3.3 チェインルール

学習時におけるニューラルネットワークは、学習データを与えると損失を出力する
各パラメータに関する損失の勾配が得られると、それを使ってパラメータの更新をすることができる

ニューラルネットワークの勾配をどのように求めるか
→**誤差逆伝播法**

誤差逆伝播法を理解する上でキーとなるのが**チェインルール（連鎖律）**

---

- チェインルール
    - 合成関数に関する微分の法則

↓こんなやつ

```math
y = f(x) \\
z = g(y) \\
```

書き換えて

```math
z = g(f(x)) \\
```

x に関する z の微分は

```math
\frac{\partial z}{\partial x} = \frac{\partial z}{\partial y}\frac{\partial y}{\partial x}
```

どれだけ複雑な関数を扱うとしても、その微分は個別の関数の微分で求めることができる

---

## 1.3.4 計算グラフ

計算を視覚的に表すもの

例）

```math
z = x + y
```

![097c0e7d-5245-f3a6-7445-c9e8b198efd7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/0b51a4b9-6d42-9b63-80b7-4959939ec536.png)
![e22cfed3-f846-7607-fb78-0c8299b16700.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/01addd35-2dd1-298c-446d-b72b8c340480.png)

逆方向の伝搬が「逆伝搬」

![6981c2fe-1898-d29b-0ba1-aee35c912493.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/f25de00e-c26c-c9a4-0c83-43da16ec46ac.png)

---

以下、代表的な演算ノード

- 加算ノード
![30e860a6-3518-5221-b139-f82bb44febe2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/643795af-a2ad-1f58-323a-de1f4df612e1.png)

- 乗算ノード
![951e8bee-fef4-df5b-d9a2-d7de611e25bd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/3ea5013e-2299-5ea9-54d8-bb90c9173bb2.png)

- 分岐ノード
![e4686bbe-ad63-968c-c415-7ee499f24a39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/b4bc3674-6261-f1c3-b934-fbb285dbed82.png)

- Repeat ノード
![03e79e0c-7f3e-bbf4-b838-232a872dc3eb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/21180639-c79e-0df1-b0a6-fb1771322f4e.png)

- Sum ノード
![99fe71b2-03f3-1de4-0a7a-5c416584f6ee.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/51a90640-ef36-6432-98f4-492330666afc.png)

- MatMul ノード
![744aada9-deea-379a-a8de-eb76267e1066.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/57fa7153-7fd1-058f-2904-8deae13bf725.png)

---

## 1.3.5 勾配の導出と逆伝搬の実装

各レイヤを実装していく

![bd2831b7-351e-9951-be8e-115321114a48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/13d9f359-61e5-9415-203d-a0ad5895b097.png)


---

### Sigmoidレイヤ

シグモイド関数は

```math
y =  \frac {1}{1 + exp(-x)}
```

シグモイド関数の微分は

```math
\frac{\partial y}{\partial x} = y(1 - y)
```

Sigmoidレイヤの計算グラフは

![c725a5fc-e5b6-c377-ff33-df7bc4451a17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/718220f0-25ba-4df2-7870-80c8b32b17d0.png)

---

Pythonで実装すると

```python3
class Sigmoid:
  def __init__(self):
    self.params, self.grads = [], []
    self.out = None

  def forward(self, x):
    out = 1 / (1 + np.exp(-x))
    self.out = out
    return out

  def backward(self, dout):
    dx = dout * (1.0 - self.out) * self.out
    return dx
```

---

### Affineレイヤ

Affineレイヤの順伝搬は

```python3
y = np.dot(x, W) + b
```

バイアスの加算がブロードキャストされている

![df1bab77-9c16-6624-2670-7e30a02b4abd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/a523ff92-f125-3245-81e6-95f1a3603890.png)

---

Pythonで実装すると

```python3
class Affine:
  def __init__(self, W, b):
    self.params = [W, b]
    self.grads = [np.zeros_like(W), np.zeros_like(b)]
    self.x = None

  def forward(self, x):
    W, b = self.params
    out = np.dot(x, W) + b
    self.x = x
    return out

  def backward(self, dout):
    W, b = self.params
    dx = np.dot(dout, W.T)
    dW = np.dot(self.x.T, dout)
    db = np.sum(dout, axis=0)

    self.grads[0][...] = dW
    self.grads[1][...] = db
    return dx
```

---

### Softmax with Lossレイヤ

![f9240a28-a5d5-0b1f-03aa-e22edfed5c13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/74f6c2e7-7bf5-5b16-933e-39bd08d06f48.png)

```python3
class SoftmaxWithLoss:
  def __init__(self):
    self.params, self.grads = [], []
    self.y = None  # softmaxの出力
    self.t = None  # 教師ラベル

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

    # 教師ラベルがone-hotベクトルの場合、正解のインデックスに変換
    if self.t.size == self.y.size:
      self.t = self.t.argmax(axis=1)

    loss = cross_entropy_error(self.y, self.t)
    return loss

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

    dx = self.y.copy()
    dx[np.arange(batch_size), self.t] -= 1
    dx *= dout
    dx = dx / batch_size

    return dx
```

---

## 1.3.6 重みの更新

誤差逆伝播法によって求めた勾配を使ってニューラルネットワークのパラメータを更新する

ニューラルネットワークの学習は以下の手順で行う

1. ミニバッチ
    - データが多いと、時間がかかってしまうため、データの一部を全体の近似として使う（ゼロから始めるDeep Learning 1 より）
2. 勾配の算出
    - 誤差逆伝播法で、各重みパラメータに関する損失関数の勾配を求める
3. パラメータの更新
4. 1~3を繰り返す

---

### 3. パラメータの更新

`2. 勾配の算出`で求めた勾配を使い、パラメータを勾配の逆方向（損失を減らす方向）に更新をする
→**勾配降下法**

![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/4ee84d29-f1d7-438b-d552-a3b699ac7276.png)

---

ここでは、重みの更新方法で最も単純な**SGD**を使う（他にも何種類かゼロから始めるDeep Learning 1に書いてた）

```math
W \leftarrow W - \eta \frac{\partial L}{\partial W} \\
\eta : 学習係数
```

Pythonで実装すると

```python3
class SGD:
  def __init__(self, lr=0.01):
    self.lr = lr

  def update(self, params, grads):
    for i in range(len(params)):
      params[i] -= self.lr * grads[i]
```

---

実際のニューラルネットワークのパラメータの更新は以下のようになる

```python3
model = TwoLayerNet( ... )
optimizer = SGD()

for i in range(10000):
  ...
  x_batch, t_batch = get_mini_batch( ... ) # ミニバッチの取得
  loss = model.forward(x_batch, t_batch)
  model.backward()
  optimizer.update(model.params, model.grads)
  ...
```

1.4で実際にニューラルネットワークの学習を行う

---

## おしまい

---

# リンク

- [O'Reilly Japan - ゼロから作るDeep Learning ❷](https://www.oreilly.co.jp/books/9784873118369/)
- [oreilly-japan/deep-learning-from-scratch-2: 『ゼロから作る Deep Learning ❷』(O'Reilly Japan, 2018)](https://github.com/oreilly-japan/deep-learning-from-scratch-2)