2日目　環境構築とニューラルネットワークの構築

環境構築
Pythonの最新版である3.6にChainerをpip install chaierでインストールするが、chainerはpython3.5＋対応であって3.6は対応していない可能性があるため、3.5環境にて実行することにしました。

```bash
source activate py35
pip install chainer
```

これで、chainer4.2.0がインストールされました。 

このあとコードをQiitaに表記しようとしましたが、
みなさんのようにコードのバックの色を変えてする方法がわからず。
探してみると、とても参考になる[ページ](http://www.nyarn.tech/entry/2016/04/09/225122)がありました。

Chainerを始める前に、あらためてニューラルネットワークの構築方法から勉強します。

このページを参考にはじめました。
![alt](https://qiita.com/kiminaka/items/9ae195739093277490fe)


ただ、このままではライブラリのimportがないためエラーがでます。そのライブラリも含めて私が記入したコードを記録します。
# データを生成してプロットする

```py
import numpy as np
import matplotlib.pyplot as plt
import sklearn
from sklearn.datasets import make_moons
 
np.random.seed(0)
X, y = sklearn.datasets.make_moons(200, noise=0.20)
plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral)
plt.show()
```

ここで、私のようにubuntuのコマンドに直接入力している方は、plt.show()をするとはじめてグラフが表示されるので、注意してください。

```py
import sklearn.linear_model
clf = sklearn.linear_model.LogisticRegressionCV()
clf.fit(X, y)
plot_decision_boundary(lambda x: clf.predict(x))
plt.title("Logistic Regression")
plt.show()
```

ただ、このままでは、plot_decision_boundaryが定義されていない、とエラーが表示されます。

これに対しては、この方が[説明](http://kuroyagi.hatenablog.com/entry/2017/10/14/210757)
を補足してくれていました。ありがとうございます。

この方のコードをそのまま使わせてもらったほうが理解しやすいです。
そのため、コードをatomか何かのエディタをつかって記入し、それをpythonで実行することにしました。

```nn_train.py
import numpy as np
import sklearn.datasets
import sklearn.linear_model
import matplotlib
import matplotlib.pyplot as plt


np.random.seed(0)
X,y=sklearn.datasets.make_moons(200,noise=0.20)
plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral)

# ロジスティック回帰モデルを学習させる
clf = sklearn.linear_model.LogisticRegressionCV()
clf.fit(X, y)

def plot_decision_boundary(pred_func):
    # Set min and max values and give it some padding
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    h = 0.01
    # Generate a grid of points with distance h between them
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # Predict the function value for the whole gid
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # Plot the contour and training examples
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)

# 決定境界をプロットする
plot_decision_boundary(lambda x: clf.predict(x))
plt.title("Logistic Regression")

plt.show()
```
lsやcdコマンドでファイルのあるフォルダに移動し以下を実行

```bash
python nn_train.py
```
すると、以下の図が表示されます。
![Figure_1.png](https://qiita-image-store.s3.amazonaws.com/0/187602/a2b2998f-7763-eb7a-7708-1ecdc0edadd9.png)

ここでは、2つの変数を区分する境界をロジスティック回帰モデルで作成しています。
ロジスティック回帰モデルの説明はいろいろとありますので、まずはこの図から赤や青の領域に赤点や青点が含まれている、ということを確認しました。ある程度はひとつの境界で区分されていますが、全部ではない、ということです。

次にこれをニューラルネットワークを使って学習し、上手に区分できるモデルを作ってみます。

ただ、このままではプログラムは実行中となるため、表示されている図をクローズして、コードの入力をスタンバイ状態にします。

```nn_train1.py
import numpy as np
import sklearn.datasets
import sklearn.linear_model
import matplotlib
import matplotlib.pyplot as plt

#　ランダムにモデルからデータをピックアップ
np.random.seed(0)
X,y=sklearn.datasets.make_moons(200,noise=0.20)
plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral)

# 境界付きの絵を描くためのモデルを定義
def plot_decision_boundary(pred_func):
    # Set min and max values and give it some padding
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    h = 0.01
    # Generate a grid of points with distance h between them
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # Predict the function value for the whole gid
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # Plot the contour and training examples
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)


#　データのセット
num_examples = len(X) # 学習用データサイズ
nn_input_dim = 2 # インプット層の次元数
nn_output_dim = 2 # アウトプット層の次元数

# Gradient descent parameters (数値は一般的に使われる値を採用)
epsilon = 0.01 # gradient descentの学習率
reg_lambda = 0.01 # regularizationの強さ


# 全Lossを計算するためのHelper function
def calculate_loss(model):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # 予測を算出するためのForward propagation
    z1 = X.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    # Lossを計算
    corect_logprobs = -np.log(probs[range(num_examples), y])
    data_loss = np.sum(corect_logprobs)
    # Lossにregulatization termを与える (optional)
    data_loss += reg_lambda/2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
    return 1./num_examples * data_loss

# Helper function to predict an output (0 or 1)
def predict(model, x):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # Forward propagation
    z1 = x.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    return np.argmax(probs, axis=1)


# This function learns parameters for the neural network and returns the model.
# - nn_hdim: Number of nodes in the hidden layer
# - num_passes: Number of passes through the training data for gradient descent
# - print_loss: If True, print the loss every 1000 iterations
def build_model(nn_hdim, num_passes=20000, print_loss=False):

    # Initialize the parameters to random values. We need to learn these.
    np.random.seed(0)
    W1 = np.random.randn(nn_input_dim, nn_hdim) / np.sqrt(nn_input_dim)
    b1 = np.zeros((1, nn_hdim))
    W2 = np.random.randn(nn_hdim, nn_output_dim) / np.sqrt(nn_hdim)
    b2 = np.zeros((1, nn_output_dim))

    # This is what we return at the end
    model = {}

    # Gradient descent. For each batch...
    for i in range(0, num_passes):

        # Forward propagation
        z1 = X.dot(W1) + b1
        a1 = np.tanh(z1)
        z2 = a1.dot(W2) + b2
        exp_scores = np.exp(z2)
        probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

        # Backpropagation
        delta3 = probs
        delta3[range(num_examples), y] -= 1
        dW2 = (a1.T).dot(delta3)
        db2 = np.sum(delta3, axis=0, keepdims=True)
        delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
        dW1 = np.dot(X.T, delta2)
        db1 = np.sum(delta2, axis=0)

        # Add regularization terms (b1 and b2 don't have regularization terms)
        dW2 += reg_lambda * W2
        dW1 += reg_lambda * W1

        # Gradient descent parameter update
        W1 += -epsilon * dW1
        b1 += -epsilon * db1
        W2 += -epsilon * dW2
        b2 += -epsilon * db2

        # Assign new parameters to the model
        model = { 'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}

        # Optionally print the loss.
        # This is expensive because it uses the whole dataset, so we don't want to do it too often.
        if print_loss and i % 1000 == 0:
          print ("Loss after iteration %i: %f" %(i, calculate_loss(model)))

    return model


# 3次元の隠れ層を持つモデルを構築
model = build_model(3, print_loss=True)

# 決定境界をプロットする
plot_decision_boundary(lambda x: predict(model, x))
plt.title("Decision Boundary for hidden layer size 3")


plt.show()
```

このコードもこの[ページ](http://kuroyagi.hatenablog.com/entry/2017/10/14/210757)を引用させていただきました。ありがとうございます。

![Figure_2.png](https://qiita-image-store.s3.amazonaws.com/0/187602/5fae19dc-4a77-fdeb-6d28-5cc073e61996.png)

ロジスティック回帰に比べれば、境界をまたぐ赤や青の点がかなりへっているのがわかります。
計算結果のロスも1%以下なので、かなり上手に区分できているのがわかります。

次に記事を参考にして、隠し層の数を変えて計算しなおしました。

```nn_train3.py
# -*- coding: utf-8 -*-

import numpy as np
import sklearn.datasets
import sklearn.linear_model
import matplotlib
import matplotlib.pyplot as plt


np.random.seed(0)
X,y=sklearn.datasets.make_moons(200,noise=0.20)
plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral)

#区分境界の図をプロットするための関数
def plot_decision_boundary(pred_func):
    # Set min and max values and give it some padding
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    h = 0.01
    # Generate a grid of points with distance h between them
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # Predict the function value for the whole gid
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # Plot the contour and training examples
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)


num_examples = len(X) # 学習用データサイズ
nn_input_dim = 2 # インプット層の次元数
nn_output_dim = 2 # アウトプット層の次元数

# Gradient descent parameters (数値は一般的に使われる値を採用)
epsilon = 0.01 # gradient descentの学習率
reg_lambda = 0.01 # regularizationの強さ


# 全Lossを計算するためのHelper function
def calculate_loss(model):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # 予測を算出するためのForward propagation
    z1 = X.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    # Lossを計算
    corect_logprobs = -np.log(probs[range(num_examples), y])
    data_loss = np.sum(corect_logprobs)
    # Lossにregulatization termを与える (optional)
    data_loss += reg_lambda/2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
    return 1./num_examples * data_loss




# Helper function to predict an output (0 or 1)
def predict(model, x):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # Forward propagation
    z1 = x.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    return np.argmax(probs, axis=1)




# This function learns parameters for the neural network and returns the model.
# - nn_hdim: Number of nodes in the hidden layer
# - num_passes: Number of passes through the training data for gradient descent
# - print_loss: If True, print the loss every 1000 iterations
def build_model(nn_hdim, num_passes=20000, print_loss=False):

    # Initialize the parameters to random values. We need to learn these.
    np.random.seed(0)
    W1 = np.random.randn(nn_input_dim, nn_hdim) / np.sqrt(nn_input_dim)
    b1 = np.zeros((1, nn_hdim))
    W2 = np.random.randn(nn_hdim, nn_output_dim) / np.sqrt(nn_hdim)
    b2 = np.zeros((1, nn_output_dim))

    # This is what we return at the end
    model = {}

    # Gradient descent. For each batch...
    for i in range(0, num_passes):

        # Forward propagation
        z1 = X.dot(W1) + b1
        a1 = np.tanh(z1)
        z2 = a1.dot(W2) + b2
        exp_scores = np.exp(z2)
        probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

        # Backpropagation
        delta3 = probs
        delta3[range(num_examples), y] -= 1
        dW2 = (a1.T).dot(delta3)
        db2 = np.sum(delta3, axis=0, keepdims=True)
        delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
        dW1 = np.dot(X.T, delta2)
        db1 = np.sum(delta2, axis=0)

        # Add regularization terms (b1 and b2 don't have regularization terms)
        dW2 += reg_lambda * W2
        dW1 += reg_lambda * W1

        # Gradient descent parameter update
        W1 += -epsilon * dW1
        b1 += -epsilon * db1
        W2 += -epsilon * dW2
        b2 += -epsilon * db2

        # Assign new parameters to the model
        model = { 'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}

        # Optionally print the loss.
        # This is expensive because it uses the whole dataset, so we don't want to do it too often.
        if print_loss and i % 1000 == 0:
          print ("Loss after iteration %i: %f" %(i, calculate_loss(model)))

    return model



# 隠しモデルの数を変えて計算し直す。
plt.figure(figsize=(16, 32))
hidden_layer_dimensions = [1, 2, 3, 4, 5, 20, 50]
for i, nn_hdim in enumerate(hidden_layer_dimensions):
    plt.subplot(5, 2, i+1)
    plt.title('Hidden Layer size %d' % nn_hdim)
    model = build_model(nn_hdi0m, print_loss=True)
    plot_decision_boundary(lambda x: predict(model, x))
plt.show()
```
結果です。

![Figure_3.png](https://qiita-image-store.s3.amazonaws.com/0/187602/86463ace-9b5c-7858-8165-364faa536b0e.png)

ロスを計算させたのでそれを確認すると、
1,3,4,5,20,50のロスはそれぞれ、
0.333421, 0.325276, 0.071316, 0.050116, 0.039037, 0.030774, 0.031028
となりました。
隠れ層がおおくなるとロスは小さくなりますが、多ければいい、ということでもなさそうです。
最適解、というのがありそうですし、モデルで最適条件を見つけても、実計算（Predict)で間違いがおおいとよくないです。
ここらは、アートの感覚、というのが必要なのですが、経験が重要と考えました。
コードがあれば自分でパラメータを変えて、その関係を調べることができるため、試行錯誤していきます。

次は，[３日目　ニューラルネットワークをChainerに変換しながら学ぶ（その１）](https://qiita.com/nigo1973/items/f2e4162f91dc1fce78ec) になります．Chainerの前に，今回と同じくニューラルネットワークをnumpyで記述し，それを個別にChainerに変えて，Chainerの使い方およびその利便を知ります．