数式なしでDeep Learning入門1 多層パーセプトロン
はじめに
対象者
この記事はDeep Learningの基礎を勉強してみたが、すぐに挫折したという方が対象です。
- 数式を使用しないでニューラルネットワークを理解した気になりたい
- 厳密な説明よりもわかった気になりたい
- なるべく単純なニューロンとニューラルネットワーク (入力層+隠れ層+出力層)で理解したい
- 簡潔なPythonスクリプトで実装したい
前提条件
Python3とNumpyが使える
注意事項
ここで紹介しているニューラルネットワークは何かと簡略化されています。ちゃんと勉強したい人は他のサイトも参考にしてください。一般的でない事項を列挙します。
理論
多層パーセプトロン (MLP: Multi Layer Perceptron)
まず、パーセプトロンの説明から。パーセプトロンは入力に重みをかけた値を全部足します。足した値が0より大きいとその値を出力します。そうでない場合は0を出力します。これをPythonで表現すると
import numpy as np x = np.array([0, 1, 1]).astype(np.float32) w = np.random.rand(3, 3).astype(np.float32) y = np.maximum(0, np.dot(w, x))
パーセプトロンを複数用意して各層をつなぎます。これが多層パーセプトロンです。パーセプトロンは神経細胞の振る舞いを単純化していて「ニューロン」とも呼ばれます。以下では単にニューロンと呼びます。
ニューロンの振る舞いを「投票」のようにも解釈することができます。入力を全部足して出力を決定するからです。ただし入力に重みをかけるため、「投票」される一票は平等ではありません。入力の大きさを「声の大きさ」、一票の重みを「信頼度」と解釈してみましょう。ニューロンはつながっている他のニューロンの声の大きさに信頼度を上乗せして、自身の声の大きさを決めます。
以下では、入力の大きさを「声」、重みを「信頼」と置き換えて説明していきます。
学習方法: 誤差逆伝播法 (Back Propagation)
次に学習についてです。学習とは「信頼」を調整することです。正解との「ずれ」があるとそのぶん「信頼」を「上げたり」「下げたり」します。われわれ人間社会でも同じことを日常的にしているかもしれません。
「信頼」の調整は、正解との「ずれ」すなわち出力層の誤差をもとに行われます。出力層から入力層に向けて誤差が伝わるので、誤差逆伝播という名前がついています。
出力層のあるニューロンの正解が1とします。実際の計算結果が0.3とします。誤差は0.7となります。このニューロンはもっと大きく反応すべきです。そのためには「信頼」を大きくする必要があります。ではどの程度「信頼」を修正すれば良いのでしょうか?まず、誤差が大きいと修正量もまた大きくなります。さらに「信頼」が大きいほど修正量も大きくなります。
Pythonで実装します。正解をans、出力層の誤差をe_out、隠れ層の誤差をe_hidとします。
ans = np.array([1, 0, 0]).astype(np.float32) e_out = ans - y_out e_hid = np.dot(e_out, w_out) * (y_hid > 0)
y_hidとy_outはそれぞれ隠れ層と出力層の出力です。
前層の影響は「信頼」と「声」で決まりました。そこで、「信頼」が大きいものほど誤差に寄与したと考えます。np.dot(w_out.T, e_out)がこれに相当します。ただし、「声」が0の場合は誤差に貢献していなので、(y_hid > 0)をかけています。
続いて「声」の影響を考慮します。単純に誤差に「声」をかけるだけです。これで入ってきた「声」に応じて修正量が大きくなります。出力層と隠れ層の「信頼」の変化量をそれぞれdw_outとdw_hidとします。また、「声」をinpとします。
dw_out += np.outer(e_out, y_hid) dw_hid += np.outer(e_hid, inp)
dw_outとdw_hidの初期値はどちらも0です。
学習は「信頼」を少しづつ変化させることで進めます。
w_out += dw_out * 0.01 w_hid += dw_hid * 0.01
0.01を学習率と呼びます。
学習は繰り返し行われます。全種類の「声」をネットワークに見せることを1 epochと呼びます。20回なら20 epochです。先人の知恵でepochごとの重み変化に0.9をかけて足すことで、学習が上手くいくことがわかっています。そこで、1 epochごとに
dw_out = dw_out*0.9 dw_hid = dw_hid*0.9
とします。0.9をモーメンタムと呼びます。
バッチ学習
ニューラルネットワークを学習させるには学習用のデータとテスト用のデータを必ず分ける必要があります。テスト用のデータを用いて学習することは決して行ってはいけません。ネットワークの性能をテスト用のデータを用いて計ります。
バッチ学習とは学習用のデータをある程度まとめてネットワークに「みせる」(入力する)ことです。100個程度まとめることが一般的なようです。手書き数字による実装例では学習用のデータを100個みせて重みを更新することを繰り返します。
実装例
MNIST
MNISTは28x28ピクセルの0~9の手書きデータです。中には「a」にしか見えない「2」があったりします。入力層のニューロン数は28x28=784となります。MNISTを使用するにはsklearnをインストールします。
sklearn Installation
$ pip3 install sklearn
sklearn Download
$ python3 >>> from sklearn.datasets import fetch_mldata >>> mnist = fetch_mldata('MNIST original')
初めての場合、ダウンロードが始まるので結構時間がかかります。ホームディレクトリのscikit_learn_data/mldata/mnist-original.matがデータの実体です。
mlp.py
いよいよ実装です。python3用のスクリプトです。入力層は784個でした。隠れ層はとりあえず64個とします。出力層は0から9の数字を識別するので10個とします。
正解の配列をあらかじめ作っておく方式に変更しました。
import numpy as np from sklearn.datasets import fetch_mldata # ネットワーク定数 n_inp = 784 n_hid = 64 n_out = 10 # 学習定数 batch = 100 learn_rate = 0.01 # 重み w_hid = np.random.rand(n_hid, n_inp).astype(np.float32)/n_inp w_out = np.random.rand(n_out, n_hid).astype(np.float32)/n_hid dw_hid = np.zeros(w_hid.shape).astype(np.float32) dw_out = np.zeros(w_out.shape).astype(np.float32) # MNIST n_train = 60000 mnist = fetch_mldata('MNIST original') data = mnist.data.astype(np.float32) target = mnist.target.astype(np.int32) x_train, x_test = np.split(data, [n_train]) d_train, d_test = np.split(target, [n_train]) n_test = d_test.size # 入力値を0~1に正規化 x_train /= 255 x_test /= 255 # 正解配列 ans = [] for i in range(n_out): x = np.zeros(n_out).astype(np.float32) x[i] = 1 ans.append(x) # 各層の出力を計算 def output(x): y_hid = np.maximum(0, np.dot(w_hid, x)) y_out = np.maximum(0, np.dot(w_out, y_hid)) return y_hid, y_out # 学習していない数字を正しく識別できるか予想 def predict(): score = 0 for i in range(0, n_test): inp = x_test[i] y_hid, y_out = output(inp) if d_test[i] == np.argmax(y_out): score += 1 print('test: {0}%'.format(score / 100)) # 学習用の数字で重みを更新 for epoch in range(1, 11): print('epoch: {0}'.format(epoch)) # ランダムな順序に学習 perm = np.random.permutation(n_train) # 100個単位で学習 for i in range(0, n_train, batch): x_batch = x_train[perm[i:i+batch]] d_batch = d_train[perm[i:i+batch]] # 学習データを100個みせる for j in range(batch): inp = x_batch[j] y_hid, y_out = output(inp) # 誤差計算 e_out = ans[d_batch[j]] - y_out e_hid = np.dot(e_out, w_out)*(y_hid > 0) dw_out += np.outer(e_out, y_hid) dw_hid += np.outer(e_hid, inp) # 100個みせたので重み更新 w_out += dw_out * learn_rate / batch w_hid += dw_hid * learn_rate / batch dw_out *= 0.9 dw_hid *= 0.9 # 学習が進んでいるか1000個ごとに識別予想 if (i%1000) == 0: predict()
実行結果
$ python3 mlp.py epoch: 1 test: 10.28% test: 10.32% test: 10.32% test: 10.66% test: 26.44% test: 35.07% test: 44.5% test: 58.98% test: 64.65% ... epoch: 10 test: 97.03% test: 96.96% test: 97.03% test: 97.06%
学習が進むにつれ、正解率が上がっていきます。10 epoch程度で97%の正解率が得られました。
さいごに
mlp.pyにはいくつかパラメーターがあります。色々いじってみると理解が深まります。
- n_hid: 隠れ層のニューロン数を16とか128にしてみる
- learn_rate: 学習率を0.1とか0.001にしてみる
- 重みを初期化する際に入力数で割るのではなく、np.sqrt(入力数)で割ってみる
冒頭で申し上げた通り、ここで紹介したニューラルネットワークはテキトーです。なんとなくわかった気になったら、他のサイトや書籍できちんと勉強してください。この記事がディープラーニング入門の手助けになれば幸いです。