【DeepLearning特訓】MLPの基礎 学習アルゴリズムの Python実装
E資格向けの自習アウトプット
自分用メモ
学習アルゴリズムを、実装してみて実際の学習の推移を確認してみたいと思います
今回は Python のクラスをちゃんと用いて学習モデルを作っていきます
一気に本格的な感じに
まず挑む問題ですが、過去にも Pytorch でも取り組んだ基本中の基本の MNIST のデータセットを使います
PyTorchを使ってDeep Learningのお勉強 画像認識編(MNIST) - konchangakita
こんなふうな数字の画像と 正解ラベル「4」がセットになっています
画像データセットの読み込み
MNIST データセットを読み込み
正解ラベルを One-Hot-Vector に変換します
import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split #乱数シード指定 np.random.seed(seed=0) # MNIST画像データ読み込み from sklearn.datasets import fetch_openml mnist = fetch_openml(name='mnist_784', version=1) # 画像とラベルを取得 X, T = mnist.data, mnist.target # 訓練データとテストデータに分割 x_train, x_test, t_train, t_test = train_test_split(X, T, test_size=0.2) # ラベルデータをint型にし、one-hot-vectorに変換 t_train = np.eye(10)[t_train.astype("int")] t_test = np.eye(10)[t_test.astype("int")] # Dataframe を numpy に変換 x_train = np.array(x_train.values) x_test = np.array(x_test.values) print('訓練データ(数, サイズ) \t', x_train.shape) print('テストデータ(数, サイズ)\t', t_test.shape) 訓練データ(数, サイズ) (56000, 784) テストデータ(数, サイズ) (14000, 10)
読み込んだデータを確認します
# 先頭から5個 for i in range(5): plt.gray() plt.imshow(x_train[i].reshape((28,28))) plt.show() print("label: ", t_train[i])
画像と正解ラベルが one hot vector で表示されていればOK
学習モデルを定義
順伝播から誤差逆伝播を定義
# クロスエントロピー誤差 def cross_entropy_error(t, y): delta = 1e-8 error = -np.mean(t * np.log(y + delta)) return error # ソフトマックス def softmax(x): x = x.T _x = x - np.max(x) _x = np.exp(_x) / np.sum(np.exp(_x), axis=0) return _x.T # モデルの定義 # MNIST layer1:784 > layer2:100 > layer3:100 > layer4:10 class Net: def __init__(self, input_size=784, hidden_size=[100, 100], output_size=10, batch_size=128): self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size self.all_size = [self.input_size] + self.hidden_size + [self.output_size] self.batch_size = batch_size self.params = {} # w, b の保存 self.layers = {} # 各層の出力 self.grads = {} # 勾配 # 重みとバイアスパラメータの初期化 for idx in range(1, len(self.all_size)): self.params['w' + str(idx)] = np.random.randn(self.all_size[idx-1], self.all_size[idx]) * 0.055 self.params['b' + str(idx)] = np.zeros(self.all_size[idx], dtype=float) # 順伝播、コスト関数 def forward(self, x, t): relu = lambda x : np.maximum(0, x) self.layers[1] = x # 全層分を順伝播 for idx in range(1, len(self.all_size)): x = self.layers[idx] w = self.params['w' + str(idx)] b = self.params['b' + str(idx)] # ReLU からの最後の層だけソフトマックス if idx == len(self.all_size)-1: y = self.layers[idx + 1] = softmax(np.dot(x, w) + b) else: self.layers[idx + 1] = relu(np.dot(x, w) + b) loss = cross_entropy_error(t, y) return y, loss # 誤差逆伝播 def backward(self, t, y): layer = len(net.all_size) dout = (y - t) / y.shape[0] # 最後の層だけはソフトマックス for idx in range(layer-1, 0, -1): if idx != layer-1: dout = np.dot(dout, self.params['w' + str(idx+1)].transpose()) dout = dout * (self.layers[idx+1] > 0) self.grads['w' + str(idx)] = np.dot(self.layers[idx].transpose(), dout) self.grads['b' + str(idx)] = np.sum(dout, axis=0) return self.grads
学習アルゴリズムの実装
とりあえず、違いが分かりそうな
基本の SGD(確率的勾配降下法)と Adam を実装
# SGD class SGD: def __init__(self, lr=0.01): self.lr = lr def update(self, params, grads): for key in params.keys(): params[key] -= self.lr * grads[key] # Adam class Adam: def __init__(self, lr=0.001, beta1=0.9, beta2=0.999): self.lr = lr self.beta1 = beta1 self.beta2 = beta2 self.iter = 0 self.m = None self.v = None def update(self, params, grads): if self.m is None: self.m, self.v = {}, {} for key, val in params.items(): self.m[key] = np.zeros_like(val) self.v[key] = np.zeros_like(val) self.iter += 1 for key in params.keys(): self.m[key] = self.beta1 * self.m[key] + (1 - self.beta1) * grads[key] self.v[key] = self.beta2 * self.v[key] + (1 - self.beta2) * (grads[key]**2) m_unbias = self.m[key] / (1 - self.beta1**self.iter) v_unbias = self.v[key] / (1 - self.beta2**self.iter) params[key] -= self.lr * m_unbias / (np.sqrt(v_unbias) + 1e-7)
結果表示
学習の速度に差が出る
誤差
# クラス定義 batch_size = 128 sgd_net = Net(batch_size=batch_size) adam_net = Net(batch_size=batch_size) sgd = SGD() adam = Adam() # グラフ表示に履歴 sgd_loss = [] sgd_acc = [] adam_loss = [] adam_acc = [] # エポック回数 for epoch in range(20): # バッチサイズごとに実行 for idx in range(int(len(x_train) / batch_size + 1)): x = x_train[idx * batch_size: idx * batch_size + batch_size] t = t_train[idx * batch_size: idx * batch_size + batch_size] # SGDクラス y, loss = sgd_net.forward(x, t) grads = sgd_net.backward(t, y) sgd.update(sgd_net.params, grads) sgd_loss.append(loss) sgd_acc.append((y.argmax(axis=1) == t.argmax(axis=1)).mean()) # Adamクラス y, loss = adam_net.forward(x, t) grads = adam_net.backward(t, y) adam.update(adam_net.params, grads) #print('loss: ', loss) adam_loss.append(loss) adam_acc.append((y.argmax(axis=1) == t.argmax(axis=1)).mean())
まとめ
だんだんとそれっぽいカタチになってきました
パラメータ更新の方法以外にも、まだまだ学習精度を上げる方法はたくさんあるので、これからそちらへ