深層学習
お久しぶりです。
最近、岡谷貴之先生の「深層学習」を読みました。楽しいです。

- 作者: 岡谷貴之
- 出版社/メーカー: 講談社
- 発売日: 2015/04/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (13件) を見る
大学1年次でやった微積や線形代数がバンバンたくさん出てきて、やっと今年やった数学のありがたみが少しわかった気がします。
理論を学んだら今度は実装をしようと「ゼロから作るDeepLearning」を少し読んだわけですが、どうしても横ベクトルが受け付けられず、結局自前で実装することに決めました。
今回は定番の手書き文字認識をやります。なのでソースコード自体はスッキリしていると思います。今度は畳み込み層にも対応させたいな…
以下詳細について書きますが、初学者ゆえ、間違いやお見苦しい点が多々あるかもしれません。もしお気づきになられたら指摘していただけると幸いです。
学習の大まかな流れとしては(「深層学習」 p50 - p52 参照)、まずは順伝播。各層の総入力Uと出力Zはresultとして記憶しておいて、次の誤差逆伝播法で誤差関数の重さについての勾配を求めるのに使います。
デルタは逐次的に求め、重さに関しての偏微分を全て求めて、勾配法で重みを学習していきます。
出力層のデルタは、出力層の活性化関数と誤差関数の種類によって計算方法が違うので、そこだけ個別に対応します。
手書き文字認識では、MNISTというデータセットが著名らしいので、今回はそれを使用。28 * 28 のモノクロ画像と数字 0〜9 を対応させたデータセットです。
今回、学習に用いたニューラルネットは3層からなり、以下のような構成です。
入力 | 第1層 | 第2層 | 第3層 | |
---|---|---|---|---|
ユニット数 | 28 * 28 | 200 | 40 | 10 |
活性化関数 | - | ランプ関数 | ランプ関数 | ソフトマックス関数 |
分類問題なので、誤差関数は交差エントロピーを使用します。学習係数は学習するにつれ線形に減少させます。
- test.py
import numpy as np import neitwork as nw import copy import matplotlib.pyplot as plt import pickle def read_image(path): with open(path, mode = "rb") as f: src = f.read() num = int.from_bytes(src[4 : 8], "big") img_size = int.from_bytes(src[8 : 12], "big") * int.from_bytes(src[12 : 16], "big") img = [ x / 255.0 for x in src[16 : 16 + num * img_size]] return np.array(img).reshape(-1, img_size).T def read_label(path): with open(path, mode = "rb") as f: src = f.read() num = int.from_bytes(src[4 : 8], "big") L = np.zeros((10, num)) for i in range(num): L[src[8 + i]][i] = 1.0 return L def accuracy(N, X, T): Y = nw.forward_all(N, X) Y = np.argmax(Y, axis = 0) T = np.argmax(T, axis = 0) return np.sum(Y == T) / X.shape[1] print("loading...", flush = True) # 各データセットのバイナリファイルのパス Xtr = read_image("train-images-idx3-ubyte") Ttr = read_label("train-labels-idx1-ubyte") Xts = read_image("t10k-images-idx3-ubyte") Tts = read_label("t10k-labels-idx1-ubyte") print("completed.", flush = True) iter_num = 100 call_learn_num = 50 batch_size = 100 tr_size = Xtr.shape[1] init_learning_rate = 1.0 goal_learning_rate = 1e-2 learning_rate = lambda j : init_learning_rate - (j / call_learn_num) * (init_learning_rate - goal_learning_rate) N = nw.make_network(unit_num = [28 * 28, 200, 40, 10], func = [nw.ramp, nw.ramp, nw.soft_max]) train_accuracy_list = [] test_accuracy_list = [] #100イテレーションごとに計測 for j in range(call_learn_num): N = nw.learn(N, Xtr, Ttr, nw.cross_entropy_error, lambda Y, T : Y - T, iter_num, batch_size, learning_rate(j)) train_accuracy = accuracy(N, Xtr, Ttr) test_accuracy = accuracy(N, Xts, Tts) train_accuracy_list.append(train_accuracy) test_accuracy_list.append(test_accuracy) print(train_accuracy, test_accuracy, flush = True) epoch_num = iter_num * batch_size * call_learn_num / tr_size x_axis = np.linspace(epoch_num / call_learn_num, epoch_num, call_learn_num) plt.ylim([0, 1]) plt.plot(x_axis, np.array(train_accuracy_list), label = "train accuracy") plt.plot(x_axis, np.array(test_accuracy_list), label = "test accuracy") plt.legend() plt.xlabel("epochs") plt.ylabel("accuracy") plt.savefig("1.png") with open("save.pkl", mode = "wb") as f: pickle.dump(N, f)
- neitwork.py
import numpy as np import copy class layer: def __init__(self, shape): self.W = np.empty(shape) self.b = np.empty(shape[0]) self.f = ident class forward_result: def __init__(self, shape): self.U = np.empty(shape) self.Z = np.empty(shape) def make_network(unit_num, func, init_std = 0.01): ret = [] layer_num = len(unit_num) - 1 for i in range(layer_num): l = layer((unit_num[i + 1], unit_num[i])) l.W = np.random.standard_normal(l.W.shape) * init_std l.b = np.zeros(l.b.shape) l.f = func[i] ret.append(l) return ret def forward_layer(l, X): ret = forward_result((l.W.shape[0], X.shape[1])) ret.U = np.dot(l.W, X) + np.dot(np.c_[l.b], np.ones(X.shape[1]).reshape(1, X.shape[1])) ret.Z = l.f(ret.U) return ret def forward_all(network, X): for l in network: X = forward_layer(l, X).Z return X def forward_all_with_result(network, X): ret = [] I = forward_result(X.shape) I.Z = X ret.append(I) for l in network: ret.append(forward_layer(l, ret[-1].Z)) return ret def grad_bp(network, result, delta): layer_num = len(network) N = result[0].U.shape[1] ret = [] Z = result[-2].Z dL = layer(network[-1].W.shape) dL.W = np.dot(delta, Z.T) / N dL.b = np.dot(delta, np.c_[np.ones(N)]).reshape(delta.shape[0]) / N ret.append(dL) for i in reversed(range(layer_num - 1)): #今 0-origin で i 層目のデルタを計算する W = network[i + 1].W # i + 1 層目のW U = result[i + 1].U # i 層目のU df = derivative_dict[network[i].f] # i 層目の活性化関数の導関数 delta = df(U) * np.dot(W.T, delta) Z = result[i].Z # i - 1 層目のZ dL = layer(network[i].W.shape) dL.W = np.dot(delta, Z.T) / N dL.b = np.dot(delta, np.c_[np.ones(N)]).reshape(delta.shape[0]) / N ret.insert(0, dL) return ret def learn(network, X, T, loss_function, last_delta, iter_num, batch_size, learning_rate): N = X.shape[1] # データ数 layer_num = len(network) network = copy.deepcopy(network) for i in range(iter_num): batch_mask = np.random.choice(N, batch_size) x_batch = X.T[batch_mask].T t_batch = T.T[batch_mask].T result = forward_all_with_result(network, x_batch) y_batch = result[-1].Z grad = grad_bp(network, result, last_delta(y_batch, t_batch)) for l in range(layer_num): network[l].W -= learning_rate * grad[l].W network[l].b -= learning_rate * grad[l].b return network def logistic_sigmoid(x): return 1 / (1 + np.exp(-x)) def logistic_sigmoid_d(x): return 1 / (np.exp(0.5 * x) + np.exp(-0.5 * x)) ** 2 def tanh(x): return np.tanh(x) def tanh_d(x): return 1 / np.cosh(x) ** 2 def ramp(x): return np.maximum(0, x) def ramp_d(x): return 1 * (x > 0) def ident(x): return x def ident_d(x): return np.ones_like(x) def soft_max(a): c = np.max(a, axis = 0, keepdims = True) exp_a = np.exp(a - c) sum_exp_a = np.sum(exp_a, axis = 0, keepdims = True) return exp_a / sum_exp_a def mean_squared_error(y, t): return 0.5 * np.sum((y - t) ** 2, axis = 0, keepdims = True) def cross_entropy_error(y, t): delta = 1e-7 return -np.sum(t * np.log(y + delta), axis = 0, keepdims = True) derivative_dict = { logistic_sigmoid : logistic_sigmoid_d, tanh : tanh_d, ramp : ramp_d, ident : ident_d }
活性化関数とその1階導関数の対応づけをどのようにするか悩んだのですが、もっといい方法ありそうです。
以下学習結果。
テストデータでの正解率は 97.84% 。若干過適合気味なのかな?これ以上時間をかけてもテストデータに対しての正確性は上がらないみたいです。畳み込みニューラルネットなどを使うと99%の大台に乗せることも可能らしいです。ロマンがありますね。
さて、次はいつになるかわかりませんが、畳み込みを実装して画像認識にも挑戦できたらいいなーって思っています。
終わり
参考文献
- 「深層学習」岡谷 貴之
- 「ゼロから作るDeepLearning」斎藤 康毅