RNN に計算問題を解かせる
この辺の続き。
参考は
実装したあれこれは後述
RNN が時系列データから次の値を予測するものならば、「時系列データ=足し算するべき二つのビット列を指定、予測したい値=足した結果のビット列」で学習させれば、確かに計算器を作成することは可能かもしれない。
この使い方は思いつきませんでした。
これ、次元数増やせばそれだけでかなり複雑な判定とか出せそうな気がします。
例えば入力系統を増やして、オペレータを指定してみるとか…
引数の状態二つ(時系列データ二つ)からのみ結果が導き出される前提があり、2 回目以降の学習で前回までの勾配を持っていると正常に機能しないので、 reset_sum_grad
で学習をリセットしてる。
なるほどなって感じ
def reset_sum_grad(self): self.grad_w = np.zeros_like(self.w) self.grad_b = np.zeros_like(self.b)
実装の本編はこんな感じ。
出力層と RNN レイヤーだが、出力層はシグモイド関数てだけで、中間層の実装は前回のパクリ。
eta = 0.1 # 学習係数 n_learn = 5001 # 学習回数 interval = 500 # 経過の表示間隔 class OutputLayer: def __init__(self, n_upper, n): self.w = np.random.randn(n_upper, n) / np.sqrt(n_upper) self.b = np.zeros(n) def activate_func(self, u): # sigmoid function return 1 / (1 + np.exp(-u)) def diff_func(self, grad_y, y): # differencial sigmoid return grad_y * (1 - y) * y def forward(self, x): self.x = x u = np.dot(x, self.w) + self.b self.y = self.activate_func(u) return self.y def backward(self, x, y, t): delta = self.diff_func(y - t, y) self.grad_w = np.dot(x.T, delta) self.grad_b = np.sum(delta, axis=0) self.grad_x = np.dot(delta, self.w.T) return self.grad_x def reset_sum_grad(self): self.grad_w = np.zeros_like(self.w) self.grad_b = np.zeros_like(self.b) def update(self, eta): self.w -= eta * self.grad_w self.b -= eta * self.grad_b class RnnBaseLayer: def __init__(self, n_upper, n): self.w = np.random.randn(n_upper, n) / np.sqrt(n_upper) self.v = np.random.randn(n, n) / np.sqrt(n) self.b = np.zeros(n) def forward(self, x, prev_y): u = np.dot(x, self.w) + np.dot(prev_y, self.v) + self.b self.y = np.tanh(u) return self.y def backward(self, x, y, prev_y, grad_y): delta = grad_y * (1 - y**2) self.grad_w += np.dot(x.T, delta) self.grad_v += np.dot(prev_y.T, delta) self.grad_b += np.sum(delta, axis=0) self.grad_x = np.dot(delta, self.w.T) self.grad_prev_y = np.dot(delta, self.v.T) return self.grad_prev_y def reset_sum_grad(self): self.grad_w = np.zeros_like(self.w) self.grad_v = np.zeros_like(self.v) self.grad_b = np.zeros_like(self.b) def update(self, eta): self.w -= eta * self.grad_w self.v -= eta * self.grad_v self.b -= eta * self.grad_b
ここに食わせる学習データだけど、これで学習させたのは、2進数の足し算訓練。
この時の学習データの形状は
n_time = 8 # 時系列の数(今回は最大ビット数) n_in = 2 # 入力層ニューロン数(二つの値を足し合わせる目的なので) n_mid = 32 # 中間層ニューロン数(適当) n_out = 1 # 出力層ニューロン数(真実はいつも一つ!) max_num = 2**n_time # = 256 binaries = np.zeros((max_num, n_time), dtype=int) for i in range(max_num): num10 = i for j in range(n_time): pow2 = 2 ** (n_time - 1 - j) binaries[i, j] = num10 // pow2 num10 %= pow2
こんな感じに書いていますが、binaries
の中身は
[0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 1] [0 0 0 0 0 0 1 0] [0 0 0 0 0 0 1 1] ... [1 1 1 1 1 1 1 1]
と、256 行 8 列のビット列。
この行列は先頭から 0, 1, 2, 3, ... と続いているので、32 の値はインデックス 32 に当たる。
足し合わせる二つの値は MAX(256) を 2 で割った値(これで 128 以下の値二つに化ける)
num1 = np.random.randint(max_num//2) num2 = np.random.randint(max_num//2) # これをビット配列に置き換えて x1= binaries[num1] x2= binaries[num2] # 引数の形状にまとめる x_in = np.zeros((1, n_time, n_in)) x_in[0, :, 0] = x1 x_in[0, :, 1] = x2 x_in = np.flip(x_in, axis=1)
形状的にはこんな感じ
13 と 33 の例 [[[1. 1.] [0. 0.] [1. 0.] [1. 0.] [0. 0.] [0. 1.] [0. 0.] [0. 0.]]]
13 + 33 = 46 = b00101110
となるので、
# 結果データ t = binaries[num1+num2] t_in = t.reshape(1, n_time, n_out) t_in = np.flip(t_in , axis=1)
コレの結果は
[[[0], [1], [1], [1], [0], [1], [0], [0]]]
これを食わせてシグモイド関数出力(MAX =1)で、0.5 を閾値に 01 判定して学習させていくという様式。
学習部分の全体像はこんな感じ
rnnLayer = RnnBaseLayer(n_in, n_mid) outputLayer = OutputLayer(n_mid, n_out) def train(x_mb, t_mb): y_rnn = np.zeros((len(x_mb), n_time+1, n_mid)) y_out = np.zeros((len(x_mb), n_time, n_out)) # Forward propergation y_prev = y_rnn[:, 0, :] for i in range(n_time): # RNN layer x = x_mb[:, i, :] y = rnnLayer.forward(x, y_prev) y_rnn[:, i + 1, :] = y y_prev = y # output layer y_out[:, i, :] = outputLayer.forward(y) # back propergation outputLayer.reset_sum_grad() rnnLayer.reset_sum_grad() grad_y = 0 for i in reversed(range(n_time)): # output layer x = y_rnn[:, i+1, :] y = y_out[:, i, :] t = t_mb[:, i, :] grad_x_out = outputLayer.backward(x, y, t) # Rnn layer x = x_mb[:, i, :] y = y_rnn[:, i+1, :] y_prev = y_rnn[:, i, :] grad_y = rnnLayer.backward(x, y, y_prev, grad_y + grad_x_out) # update rnnLayer.update(eta) outputLayer.update(eta) return y_out def get_error(y, t): return 1.0/2.0*np.sum(np.square(y - t)) for i in range(n_learn): # ランダムなインデックスを作成 num1 = np.random.randint(max_num//2) num2 = np.random.randint(max_num//2) # これをビット配列に置き換えて x1= binaries[num1] x2= binaries[num2] # 引数の形状にまとめる x_in = np.zeros((1, n_time, n_in)) x_in[0, :, 0] = x1 x_in[0, :, 1] = x2 x_in = np.flip(x_in, axis=1) # 結果データ t = binaries[num1+num2] t_in = t.reshape(1, n_time, n_out) t_in = np.flip(t_in , axis=1) # 学習 y_out = train(x_in, t_in) y = np.flip(y_out, axis=1).reshape(-1) error = get_error(y_out, t_in) if i % interval == 0: y2 = np.where(y<0.5, 0, 1) y10 = 0 for j in range(len(y)): pow2 = 2 ** (n_time-1-j) y10 += y2[j] * pow2 print("learn count:", i) print("error rate:", error) c = "Success : " if (y2 == t).all() else "Failure : " print(c + str(num1) + " + " + str(num2) + " = " + str(y10)) print("========================")