書籍「15Stepで踏破 自然言語処理アプリケーション開発入門」をやってみる - 3章Step08メモ「ニューラルネットワーク入門」

# 内容
[15stepで踏破 自然言語処理アプリケーション入門](https://www.amazon.co.jp/dp/4865941320) を読み進めていくにあたっての自分用のメモです。
今回は3章Step08で、自分なりのポイントをメモります。

# 準備
- 個人用MacPC：MacOS Mojave バージョン10.14.6
- docker version：Client, Server共にバージョン19.03.2

# 章の概要
3章は深層学習の基礎と自然言語処理への適用に関する基礎解説である。
Step08ではニューラルネットワーク入門として、多層パーセプトロンの概要と深層学習ライブラリKerasによってシンプルに実装する。

# 08.1 単純パーセプトロン
生物の脳細胞を構成するニューロンを数学的なモデルに押し込んだのが**パーセプトロン**で、ニューロン1個を模倣したのが**単純パーセプトロン**と呼ばれるモデルである。

- n個の入力：x1, x2, ..., xn
- n個の重み：w1, w2, ..., wn
- 固定値のバイアス：b
- 出力：z
- 出力関数：f()

`z = f(x1w1 + x2w2 + ... + xnwn + b)`

上の式をそのまま厳密にコーディングしても良いが、ベクトルの内積（NumPy）演算を用いることで簡潔に書け、処理速度も早い。
単純パーセプトロンを識別器と見た場合、重み（wとb）の適切な値を求めることが**学習**となる。

```python
import numpy as np

x = np.array([...])
w = np.array([...])
b = ..

z = b + np.dot(x, w)
``` 

# 08.2 多層パーセプトロン
脳の神経細胞と同様に、単純パーセプトロンの出力を別の単純パーセプトロンの入力にすることで多数のパーセプトロンが繋がった構造を作ることができ、**多層パーセプトロン（multi-layer perceptron;MLP）**と呼ぶ。

```python:test_mlp.py
import numpy as np

W_1 = np.array([
    [1, 2, 3],
    [4, 5, 6],
])

x = np.array([10, 20, 30])

print(np.dot(W_1, x))
print(np.dot(x, W_1))
```

```bash
[140 320]
Traceback (most recent call last):
  File "test_mlp.py", line 11, in <module>
    print(np.dot(x, W_1))
ValueError: shapes (3,) and (2,3) not aligned: 3 (dim 0) != 2 (dim 0)
```

上記では、MLPの1層目の2個のパーセプトロンに対する重みをW_1に格納している（2行3列の配列）。
これと3行1列の入力ベクトルとの内積を計算することにより、2行1列の出力を得ている（2行3列 * 3行1列 = 2行1列）。

当然、W_1とxを入れ替えて内積を計算しようとしてもエラーとなる。

単純パーセプトロンは線形分離可能な問題にしか適用できないが、**多層パーセプトロンは線形分離不可能な問題にも対応可能**である。

# 08.3 DeepLearningライブラリKeras
08.2では各層を関数化して実装していたが、ライブラリを使うことによって簡潔に記述することができる。

## ライブラリ読み込みとモデルの初期化
```python
import keras.layers import Dense
import keras.models import Sequential

model = Sequential()
```

## 各層の実装
```python
# 1層目の実装
model.add(Dense(units = 2, activation = 'relu', input_dim = 3))

# 2層目の実装
model.add(Dense(units = 1, activation = 'sigmoid'))
```

- Dense：全結合層
- input_dim：入力次元数。2層目以降は前層の出力次元がそのまま入力次元となるので省略可能
- units：出力次元数
- activation：活性化関数
    - Relu：昔はsigmoid等が使われていたが、精度や収束しやすさの面で優れている
    - Sigmoid：2クラス識別器のMLPの最終層で使われる
    - Hyperbolic tangent：2クラス識別器のMLPの最終層で使われる、sigmoidより高性能
    - Softmax：多クラス識別器のMLPの最終層で使われる
- add：Sequential()で作成したmodelインスタンスに層を追加する

## MLPの学習設定
識別器の学習では、教師データとして特徴ベクトルXと正解ラベルyのペアが与えられる。
Xを入力した際の出力がyに近くなるように識別器のパラメータ（重み）が調整される。

```python
model.compile(loss = 'binary_crossentropy', optimizer = Adam(lr = 0.001))
```

- loss：損失関数。Xとyのズレの大きさを表す関数で、モデルの識別精度を評価する
    - binary_crossentropy：2クラス識別器に対してマッチする損失関数
- optimizer：最適化手法。パラメータを調整することを最適化と呼び、様々な手法がある
    - lr：学習率。1回の更新でどの程度、重みの値を増減させるかを決めるパラメータ
        - 大きい：重みの変化が大きすぎて最適値の周辺を行ったり来たり、最悪収束せずに発散してしまう
        - 小さい：学習に時間がかかりすぎる

## Kerasにおける各層の重み
Kerasで層をインスタンス化すると、重みは暗黙的に**乱数で初期化**される。
重みにアクセスしたい場合は、`.get_weights()`、`.set_weights()`メソッドを利用する。
10.3でも書いているが、重みの初期化を指定する場合は、`model.add(Dense(.., kernel_initializer = ))`で指定することができる。

# 08.4 多層パーセプトロンの学習
```python
model.fit(X, y, batch_size = 32, epochs = 100)
```

- batch_size：学習データのうち、全件に対して何件ずつまとめて学習を行うかの値
- epochs：学習データのうち、1つの学習データを学習に何回使うかの値
- fit：学習処理。内部では誤差逆伝播法が用いられている

# 08.5 ニューラルネットワークとは？
脳細胞の模倣へのこだわりは捨てられ、コンピュータに知的な処理をさせることを目的として発展してきている。

KerasはTensorFlowのラッパーライブラリである。
TensorFlowはGoogleが開発した有名な深層学習ライブラリで低レベルAPIが多いので、高レベルAPIが提供されているKerasを用いることで実装しやすい。
ニューラルネットワークの計算の大半はベクトル・行列演算なので、より大きなニューラルネットワークを高速に学習するにはGPUを利用することが考えられる。