前提・実現したいこと
普段はscikit-learnなどを使っているのですが、多層パーセプトロンを誤差伝搬で学習するシンプルなネットワークをnumpyだけで実装することになりました。
深層学習(MLP出版)やゼロから作るDeepLeaningなどを参考に自分なりにプログラムを組んで、orやxorなどを学習させようとしたのですがうまくいきません。
具体的には、学習が進むとすべての入力に対して常に4つの出力の平均値である0.75を返すようになり、(0,0)に対しても結果が0.75になってしまいます。
ネットワークの構成は2-2-1で適当に決めました。一層目は(1,0)などの入力を出力するだけで2層目から重みWをかけた下層の出力xにバイアスbをかけた値yをシグモイド関数で出力するようにしています。重みの更新も普通のSGDでレートは0.01です。
誤差逆伝搬の原理は一応わかっているつもりなのですが、何が間違っているのかわかりません。
根本的に間違っているところとか、なんか怪しいところとか何でもいいのでアドバイスお願いします。
Jupyter notebookのファイルと読み込んでいるプログラムはgitにあげてあります。
https://github.com/Pickerdot/teratail_BP[リポジトリ](https://github.com/Pickerdot/teratail_BP)
Dropboxはこちらdropbox
該当のソースコード
こちらが2層目からの層を定義しているクラスです
python
1class BPneuron: 2 def __init__(self, W, b): 3 # 引数として受けた重みとバイアスをself.aramsに格納 4 self.params = [W, b] 5 # 更新前に勾配をまとめてオプティマイザーに送るための入れ物(中身はparamsに対応している必要あり) 6 self.grads = [np.zeros_like(W), np.zeros_like(b)] 7 # クラス外へ中身を持っていくための入れ物 8 self.container = np.empty(0) 9 10 def forward(self, x): 11 # クラスの初期化時に格納した重みとバイアスの取り出し 12 W, b = self.params 13 # yはニューロン内部の値 14 y = np.dot(x, W)+b 15 # Zが出力 16 z = sigmoid(y) 17 self.container = [W, b, x, y, z] 18 return z, self.container 19 20 def backward(self, dz): 21 W, b, x, y, z = self.container 22 # 出力部の逆伝搬(シグモイド版) 23 dy = sigmoid_back(z, dz) 24 db = dy 25 dW = np.dot(x.T, dy) 26 dx = np.dot(dy, W.T) 27 28 # self.gradsに更新に行かう勾配を格納 29 self.grads[0][...] = dW 30 self.grads[1][...] = db 31 32 # オプティマイザーによりself.paramsの値を更新 33 self.params = optimizer_SGD(lr, self.params, self.grads) 34 # すべての結果をself.containerに格納 35 self.container = [dy, db, dW, dx] 36 37 return dx, self.container
sigmoid, sigmoid_back,SGDは次のように定義しています。
python
1def sigmoid(x): 2 return 1 / (1 + np.exp(-x)) 3 4 5def sigmoid_back(z, dz): 6 return dz*sigmoid(z) * (1 - sigmoid(z)) 7 8def optimizer_SGD(lr, params, grads): 9 for i in range(len(params)): 10 params[i] -= lr * grads[i] 11 12 return params
このクラスをJupyter notebook上で読み込んで次のプログラムで学習をさせています。
python
1#データの生成 2test_data = np.array([[1,1],[1,0],[0,1],[0,0]]) 3target_data = np.array([[1],[1],[1],[0]]) 4 5#ネットワークの大きさ 6input_size, hidden_size, output_size = 2,2,1 7I, H, O = input_size, hidden_size, output_size 8 9# 重みとバイアスの定義 10W01 = 0*np.random.rand(I, H) 11W12 = 0*np.random.rand(H, O) 12b1 = np.zeros(H) 13b2 = np.zeros(O) 14 15#モデルの生成 16Seccond_layer = BPneuron(W01,b1) 17Third_layer = BPneuron(W12,b2) 18test_loss_layer = Loss() 19 20for i in range(3000): 21 #学習に使う配列の決定 22 test_number= np.random.randint(4) 23 x = np.array([test_data[test_number]]) 24 t = np.array([target_data[test_number]]) 25 26 # 準伝播 27 out1, container1=Seccond_layer.forward(x) 28 out2 ,container2 = Third_layer.forward(out1) 29 30 loss = test_loss_layer.forward(out2,t) 31 dout = test_loss_layer.backward() 32 33 # 逆伝搬 34 dx ,containe= Third_layer.backward(dout) 35 dinput,containe = Seccond_layer.backward(dx) 36
途中に出てくるcontainerという変数はバイアスや重みの大きさをチェックするための変数なので気にしないでください。
[追記]書き忘れていたLossクラスです。
python
1 2class Loss: 3 def __init__(self): 4 self.Loss = None 5 self.dout = None 6 7 def forward(self, out, t): 8 self.Loss = 1/2 * np.sum((out - t)**2) 9 self.dout = out - t 10 return self.Loss 11 12 def backward(self): 13 return self.dout
試したこと
4000回学習以降の中の変数を確認したところ次のようになっていました
_________ 二層目 入力x: [[0 1]] W: [[0.06000589 0.06000589] [0.0603888 0.0603888 ]] b: [0.02322472 0.02322472] y: [[0.08361352 0.08361352]] z: [[0.52089121 0.52089121]] _________ 三層目 x: [[0.52089121 0.52089121]] W: [[0.33023109] [0.33023109]] b: [0.62977926] y: [[0.97380821]] z: [[0.7258779]] _________ loss: [[-0.2741221]]
_________ 二層目 x: [[0 0]] W: [[0.06000589 0.06000589] [0.06043531 0.06043531]] b: [0.02327123 0.02327123] y: [[0.02327123 0.02327123]] z: [[0.50581754 0.50581754]] _________ 三層目 x: [[0.50581754 0.50581754]] W: [[0.33054488] [0.33054488]] b: [0.63038167] y: [[0.96477247]] z: [[0.72407631]] _________ loss: [[0.72407631]]
orのタスクを実行するには二層目のバイアスが負にならなければいけないように思えるのですが負になりません。正なので二層目の時点でシグモイドをかけたときに(0,0)と(1,0)の差がなくなってしまい三層目では二つの違いを区別できていないように思えます。
ちなみに誤差(out-target)の値は次のように推移します。
よろしくお願いします。
回答1件
あなたの回答
tips
プレビュー