単純パーセプトロンを１から作ってみた

この記事は，Pythonそのものの使い方かDeepLearningの仕組みまで，1から細かく学習した際のメモ書きとスライドのまとめです．
# 単純パーセプトロン（AND真偽値表の学習）
今回は，単純パーセプトロン（Perceptron）を用いて，AND真偽値表の出力を学習させることを目指す．AND真理値表とは，入力の2つが共に1である時のみ1を出力するものである．
|x1|x2|y|
|:--:|:--:|:--:|
|0|0|0|
|0|1|0|
|1|0|0|
|1|1|1|

![スライド2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/76d2bd7b-de6c-2cd4-7e1b-c1a461cdd92c.jpeg)
## はじめに
前提として，Pythonの基本的な使い方（クラスを扱える程度）を理解していることとして話を進める．
ライブラリであるNumpyを扱うが，この分野でよく使い，複雑な挙動をするnumpy.dot()と，ニューラルネットワークで一般的に使用されるシグモイド関数については，以下に解説する．
![スライド14.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/45b12f8d-5c81-5bf2-a6c4-2317e85ef9f4.jpeg)
![スライド13.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/30daa9dd-61bc-13ab-5cea-3f960f4f61fd.jpeg)

上記のnumpy.dot()の挙動については，以下のコードを用いて確認ができる．
```Python
import numpy as np
# A,Bが共に1次元配列の場合
A = np.array([1,2,3])
B = np.array([2,4,6])
ans1 = np.dot(A,B)
print("ans1 = ", ans1)

# A,Bのどちらかに2次元配列を指定した場合
A = np.array([[1,1],[2,2],[3,3]])
B = np.array([[1,2],[3,4]])
ans2 = np.dot(A,B)
print("ans2 = \n", ans2)

# A,Bの一方がスカラーの場合
A = np.array([[1,1],[2,2],[3,3]])
B = 5 #スカラー
ans3 = np.dot(A,B)
print("ans3 = \n", ans3)
```
実行結果
``` 
ans1 =  28
ans2 = 
 [[ 4  6]
 [ 8 12]
 [12 18]]
ans3 = 
 [[ 5  5]
 [10 10]
 [15 15]]
```

## モデルの作成
### 重み (weight)
今回は，入力層から出力層へ向かう重みをリストwで定義した．入力層から出力層へ向かう重みはx1→a，x2→aへ向かう2つである．したがって，w1=w[0]，w2=w[1]で定義した．

### バイアス (bias)
今回は，バイアスが１つなので，そのまま変数でbを定義した．
![スライド3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/c565b0ad-bb2c-3331-b9fc-2fa02e1173c6.jpeg)
![スライド5.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/cdca4b66-4eb6-2901-93d5-ed44a68cb810.jpeg)
![スライド4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/881df022-8b3b-a282-4597-dce9adec3927.jpeg)
![スライド6.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/39053cfe-13a3-e85a-4434-382175734afc.jpeg)
![スライド7.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/0c710acf-6074-bc8f-c81a-4e606b9a7d5f.jpeg)
![スライド8.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/224a8598-f10d-73ae-a2df-926023444be9.jpeg)
![スライド9.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/d37c5b5c-2b46-d41e-9642-61ae7d17628b.jpeg)
![スライド10.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/f6006923-3403-c6fa-00dc-e7b8e7846b7d.jpeg)
![スライド11.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/ab344676-0d1a-48d9-04e6-8f5de53a3d78.jpeg)
![スライド12.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/52e1ae5f-fdde-50c1-462c-902ed67e36cb.jpeg)






### プログラム
```python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# ネットワークモデルに必要な活性化関数の定義(sigmoid関数)
def sigmoid(x):
    return 1/(1+np.exp(-x))

def sigmoid_grad(x):
    return (1.0 - sigmoid(x))*sigmoid(x)

# ネットワークモデルの定義(単純パーセプトロン)
class Perceptron:
    def __init__(self, input_size, weight_init_std=0.01):
        # 平均0，分散1の正規分布の乱数を生成
        self.w = weight_init_std * np.random.randn(input_size)
        # バイアスの定義
        self.b = 0.0
        # 傾きを保存しておくリストを作成
        self.grads = {}

    def forward(self, x): # x:入力
        self.a = np.dot(x, self.w) + self.b
        self.y = sigmoid(self.a)
        return self.y

    def backward(self, x, t): # x:入力, y:教師
        # 傾きを保存しておくリストを初期化
        self.grads = {}
        # 入力データと教師ラベルとの誤差を計算し損失を算出
        l = -1 * (t - self.y)
        # sigmoid関数地点での勾配を算出
        g = l * sigmoid_grad(self.y)
        # それぞれのパラメータの箇所での勾配を算出
        self.grads['w'] = np.dot(x, g)
        self.grads['b'] = 1 * g

    def update_parameters(self, lr=0.1): # lr:学習率
        self.w -= lr * self.grads['w']
        self.b -= lr * self.grads['b']

# モデルパラメータを表示させる関数
def display_model_parameters(model):
    print("w : ", model.w, "b : ", model.b)


if __name__=="__main__":
    # AND回路の学習を行うための入力データと教師ラベルの定義
    input_data = np.array([[0,0],[0,1],[1,0],[1,1]])
    label_data = np.array([0,0,0,1])

    # モデルの作成
    input_size = 2
    model = Perceptron(input_size=input_size, weight_init_std=1)
    display_model_parameters(model)

    # 学習パラメータの指定
    num_train_data = 4
    epoch_num = 5000
    learning_rate = 0.1
    train_loss_list = []

    # 学習回数分繰り返す
    for epoch in range(1, epoch_num+1, 1):
        sum_loss = 0.0
        for i in range(0, num_train_data, 1):
            # 学習1回に用いるinputデータとlabelを抽出
            input = input_data[i]
            label = label_data[i]

            y_pred = model.forward(input)
            model.backward(input, label)
            model.update_parameters(lr=learning_rate)

            sum_loss += np.power(y_pred - label ,2)
        
        train_loss_list.append(sum_loss/4)
        print("epoch : {}, loss : {}" .format(epoch, sum_loss/4))

    display_model_parameters(model)

    #正解率の算出
    print("=============検証==============")
    cnt_correct = 0
    cnt_all = 0
    tolerance = 0.1 #許容範囲の設定
    for i in range(0,len(input_data)):
        y = model.forward(input_data[i])
        print("input_data : {}, y : {}".format(input_data[i], y))
        label = label_data[i]
        if  label-tolerance < y and y < label+tolerance:
            cnt_correct += 1
        cnt_all += 1
      
    accuracy = cnt_correct/cnt_all
    print("accuracy : ", accuracy)

    #学習推移グラフの描画
    plt.plot(range(len(train_loss_list)), train_loss_list)
    plt.xlabel('epoch')
    plt.ylabel('train_loss')
    plt.show()
```
実行結果
``` 
w :  [ 0.04044971 -0.44445208] b :  0.0
epoch : 1, loss : 0.2755540842082671
epoch : 2, loss : 0.25938457825839456
epoch : 3, loss : 0.24498575229365263
epoch : 4, loss : 0.23200674181546946
            <中略>
epoch : 4997, loss : 2.064664218037121e-05
epoch : 4998, loss : 2.0638412153166655e-05
epoch : 4999, loss : 2.063018704184902e-05
epoch : 5000, loss : 2.0621966842505977e-05
w :  [10.46145475 10.46020368] b :  -15.977199379164789
=============検証==============
input_data : [0 0], y : 1.1513050864149548e-07
input_data : [0 1], y : 0.004001821880506588
input_data : [1 0], y : 0.004006811506167369
input_data : [1 1], y : 0.992927608306417
accuracy :  1.0
```
![Perceptron_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/5520850e-3e45-5749-eeda-e4ceb5e3b3e2.png)