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

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

![DeepLearning.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072037/1fa4364c-e9bf-243c-8e66-026cfb8ca790.jpeg)

```Python
import numpy as np

# 活性化関数の定義(Sigmoid関数)
def sigmoid(x):
    return 1/(1+np.exp(-x))
def sigmoid_grad(x):
    return (1.0 - sigmoid(x))*sigmoid(x)

class MLP:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.1):
        self.w1 = weight_init_std * np.random.randn(input_size, hidden_size)
        self.w2 = weight_init_std * np.random.randn(hidden_size, output_size)
        self.b1 = 0.0
        self.b2 = 0.0
        self.grads = {}

    def forward(self, x): # x : 入力
        self.a1 = np.dot(x, self.w1) + self.b1
        self.z1 = sigmoid(self.a1)
        self.a2 = np.dot(self.z1, self.w2) + self.b2
        self.y = sigmoid(self.a2)
        return self.y

    def backward(self, x, t): # x : 入力，y : 教師
        
        # 誤差の算出
        l = -1 * (t - self.y)
        d_a2 = sigmoid_grad(self.a2) * l
        self.grads['w2'] = np.dot(self.z1.T, d_a2)
        self.grads['b2'] = d_a2

        d_z1 = np.dot(d_a2, self.w2.T)
        d_a1 = d_z1 * sigmoid_grad(self.a1)
        self.grads['w1'] = np.dot(x.T, d_a1)
        self.grads['b1'] = d_a1
    
    def update_parameters(self, lr = 0.1): # lr : 学習率
        
        self.w1 -= lr * self.grads['w1']
        self.w2 -= lr * self.grads['w2']

        self.b1 -= lr * self.grads['b1']
        self.b2 -= lr * self.grads['b2']


def display_model_parameters(model):
    print("w1 : ", model.w1)
    print("w2 : ", model.w2)
    print("b1 : ", model.b1)
    print("b2 : ", model.b2)


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
    hidden_size = 3
    output_size = 1

    model = MLP(input_size, hidden_size, output_size)
    display_model_parameters(model)

    # ハイパーパラメータの指定
    num_train_data = 4
    epoch_num = 5000
    lerning_rate = 0.1

    for i in range(1,epoch_num+1,1):
        sum_loss = 0.0
        for j in range(0,num_train_data,1):
            x = input_data[j:j+1]
            t = label_data[j:j+1]

            y_pred = model.forward(x)
            model.backward(x, t)
            model.update_parameters(lerning_rate)

            sum_loss = np.power(t - y_pred, 2)
        
        print("epoch : {}, loss : {}".format(i, sum_loss/4))
    
    display_model_parameters(model)

    #正解率の算出
    cnt_correct = 0
    cnt_all = 0
    tolerance = 0.05 #許容範囲の設定
    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)
```

実行結果
``` 
epoch : 5000, loss : [[0.00178896]]
w1 :  [[-2.6255562  -2.0996942  -1.82297711]
 [-2.6269102  -1.89913472 -2.03126441]]
w2 :  [[-5.08765545]
 [-3.5903548 ]
 [-3.41550001]]
b1 :  [[3.23467422 2.07894497 1.93356411]]
b2 :  [[3.87595026]]
input_data : [0 0], y : [[0.0007504]]
input_data : [0 1], y : [[0.04749434]]
input_data : [1 0], y : [[0.04750903]]
input_data : [1 1], y : [[0.91554948]]
accuracy :  0.75
```

単純パーセプトロン（前回プログラム）の実行結果
``` 
epoch : 5000, loss : 0.0019097360320011203
w :  [5.82859069 5.82738022] b :  -9.028217956770213
input_data : [0 0], y : 0.00011996171284962275
input_data : [0 1], y : 0.03913420962830516
input_data : [1 0], y : 0.039179751963228676
input_data : [1 1], y : 0.9326264954936772
accuracy :  1.0
```

同じ学習率，エポック数で行った結果だが，学習損失を示したものの最後をみると，中間層を増やした多層パーセプトロンの方が，良くない結果になっていることが確認できる．

実験前までは，単純に層を増やした方が，柔軟に対応できるようになり良い結果が得られると考えていたが，この予想に反する結果になった．
そこで，学習で調整するパラメータが多いのが主な要因であると考えた．エポック数を倍の10000にして再度実行して挙動をみてみることにする．

エポック数を10000に変更して再度実行
``` 
epoch : 10000, loss : [[0.00058127]]
w1 :  [[-2.93745684 -1.50381849 -2.61486656]
 [-3.05729588 -1.25551698 -2.56420406]]
w2 :  [[-5.95868659]
 [-1.94927261]
 [-4.95068125]]
b1 :  [[3.85144893 0.67567557 3.15570244]]
b2 :  [[4.40249983]]
input_data : [0 0], y : [[0.00056819]]
input_data : [0 1], y : [[0.02691188]]
input_data : [1 0], y : [[0.02732029]]
input_data : [1 1], y : [[0.95180993]]
accuracy :  1.0
```
単純パーセプトロン（前回プログラム）も同様にエポック数を10000にて再度実行
``` 
epoch : 10000, loss : 0.000508028532782091
w :  [7.20750455 7.20687697] b :  -11.097796727457217
input_data : [0 0], y : 1.5145427708191534e-05
input_data : [0 1], y : 0.020017658200903544
input_data : [1 0], y : 0.020029973106389383
input_data : [1 1], y : 0.9649934048107632
accuracy :  1.0
```

同じエポック数では，また多層パーセプトロンの学習結果の方が劣った結果になった．
やはり，単純パーセプトロンで解ける問題は，単純パーセプトロンで解いた方が効率も精度も良いということなのか...