セミナーで学んだ内容を自分なりに噛み砕いてまとめて復習してみる。
- ニューラルネットワークとは、さながら脳のシナプス結合のようにして個別に数値処理をするノードを積み重ねてた処理のモデルである -> 世の中の事象を何らかの数列操作として表現する -> 入出力は数列で表現できる必要がある
- 個別のノードは複数の入力
x
から望ましいu
が求まる数式f(x) = w_1 * x_11 + w_2 * x_11 + ・・・
として示すことができる -> 一番最初はランダムに重みw
が振られ、それぞれの入力に対する適切な値を重回帰分析により求めて調整していくことが学習である - 中間の層と層の間でRelu関数などの非線形変換を噛ませることで世の中の多様な事象の表現を実現できる
- 学習は現状の重みを基にして望ましい数値との誤差を計算する順伝播と、その誤差から個々のノードを調整する逆伝播の繰り返しである
実際の流れとして
- 訓練データを数列として与える
- 何層かとか入出力の数列の長さとかノード間の結合といったモデルを設計して定義する -> ここシステマティックにバシッと決まるわけではないのがプログラマ的には気持ち悪い
- 誤差からの調整方法なんかを定義する -> ここも目的によっていくつか方法がある
- 2と3を用いて学習を繰り返す -> ここの回数なんかも動かして誤差の収束状況なんかと見比べて調整する感じ
- 学習済みモデルの出来上がり、上手く出来上がれば入力を突っ込むとそれらしい出力が出るようになる
これを僕の中ではスマートボール(跳ね返し板の無いピンボール)として連想している。
- 板のサイズだの球数だのは決まっている->モデルは人間が予め設計する必要がある
- 特定の位置に置いた球が落ちるべき場所に転がるように釘を調整するのが学習
そんなこんなでひとまずChainer 2.1.0にて実際にコード。実際は数値を調整したりなのでJupyter notebook上にて実行。(本当はGitHub上のipynbファイルにリンクすれば良かったんだけど、半端に外に出しちゃいけないファイルと同じディレクトリにしちゃったのは失敗だったな・・・)
-3〜3までの範囲のsin関数を対象としてみた。
0. 準備
ログ見たり収束状況確認したりする用。
# 基本的なモジュール import numpy as np import pandas as pd import matplotlib.pyplot as plt import json
ひとまず愚直に。この辺は慣れればもっとスマートな書き方にできると思う。
# Chainer関連のモジュール import chainer import chainer.links as L import chainer.functions as F from chainer import Chain from chainer import Variable from chainer import report from chainer import optimizers from chainer import training from chainer.training import extensions from chainer.datasets import split_dataset_random
1. データセット・・・球の投入位置と落ちるべき場所の関係性
x
として-3〜3までを100分割した数列、あるべき教師データt
としてそのsin関数の出力を代入。
# 標本データの作成 x = np.linspace(-3, 3, 100).reshape(100, 1) t = np.sin(x) plt.scatter(x, t) plt.show()
ちゃんと正弦波。
chainerで用意されているsplit_dataset_randomメソッドを用いて訓練データを検証データをランダムに振り分ける。
# データセットを作成して訓練データと検証データに分割 x = np.array(x, dtype = np.float32) t = np.array(t, dtype = np.float32) dataset = list(zip(x, t)) n_train = int(len(dataset) * 0.7) train, test = split_dataset_random(dataset, n_train)
2. モデルの宣言、盤面の設計
以下のようなモデルを定義。
# モデルの作成 class NN(Chain): # 中間層3層を定義 def __init__(self, n_units1, n_units2, n_output): super().__init__() with self.init_scope(): self.l1= L.Linear(None, n_units1) self.l2 = L.Linear(None, n_units2) self.l3 = L.Linear(None, n_output) # 学習用のメソッド def __call__(self, x, t, train = True): # 与えられたxから推論を行い、その結果と教師データの誤差を返す y = self.predict(x) loss = F.mean_squared_error(y, t) if train: report({'loss': loss}, self) return loss # 推論 def predict(self, x): z1 = F.relu(self.l1(x)) z2 = F.relu(self.l2(z1)) return self.l3(z2)
具体的なノード数を与えてインスタンス化。
# モデルの宣言 100層->30層->1出力 model = NN(100, 30, 1)
3. モデルの調整方法とか学習方法とかの定義・・・釘の打ち方の方針
optimizerとしてはSGDとか色々あるけど、ひとまず最初はAdamを選んでおくのが無難らしい。
# optimizerの宣言 optimizer = optimizers.Adam() optimizer.setup(model) # updaterの宣言 batchsize = 20 train_iter = chainer.iterators.SerialIterator(train, batchsize) test_iter = chainer.iterators.SerialIterator(test, batchsize, repeat = False, shuffle = False) updater = training.StandardUpdater(train_iter, optimizer)
4. 学習・・・試打しては調整を繰り返す
泥臭く書けもするけど今時はtrainerを使うのが良いらしい。ログとして誤差なんかを出力する。
# 学習 epoch = 1000 trainer = training.Trainer(updater, (epoch, 'epoch'), out = 'result') trainer.extend(extensions.Evaluator(test_iter, model)) trainer.extend(extensions.LogReport(trigger=(1, 'epoch'))) trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'validation/main/loss', 'elapsed_time']), trigger=(1, 'epoch')) trainer.run()
以下のようなログが出力される。
epoch main/loss validation/main/loss elapsed_time 1 1.05255 0.656665 0.0104949 2 0.56132 0.361226 0.0231535 3 0.327868 0.212045 0.0420246 4 0.22586 0.171785 0.0570989 5 0.193939 0.166911 0.0761545 ・・・ 996 0.00015972 0.000244043 27.8698 997 0.000186852 7.72238e-05 27.9297 998 0.000192414 0.000421348 27.9939 999 0.000270272 7.86659e-05 28.0605 1000 0.000132944 0.000277 28.1255
パッと見でも訓練データの誤差を示すmain/loss
と検証データの誤差を示すvalidation/main/loss
の値が共に下がっていっていることが分かる。
ちゃんとグラフ化すると・・・
# 学習結果の表示 with open('result/log') as f: logs = json.load(f) result_train = [log['main/loss'] for log in logs] result_test = [log['validation/main/loss'] for log in logs] plt.plot(result_train, label = 'train') plt.plot(result_test, label = 'test') plt.legend() plt.show()
良い感じに収束していっていること、これ以上学習を繰り返しても意味がなさそうなことが確認できる。
5. モデル完成・・・実際に打ち込む
そんなこんなで、訓練データたるt=sin(x)
と今回作成したモデルにx
を与えた時の出力y
を比べると・・・
# モデルを使用した予測値 y = model.predict(x).data plt.plot(x, t, label = 't') plt.scatter(x, y, label = 'y') plt.legend() plt.show()
良い感じにsin関数を再現できていることが確認できる。
じゃあってんでもっと先の数値を与えると・・・
# 範囲の違う値を予測してみる x_ = np.linspace(1, 6, 100).reshape(100, 1) x_ = np.array(x_, dtype=np.float32) plt.scatter(x_, model.predict(x_).data) plt.show()
と、このように学習で与えた範囲以降は点でsin関数とは違う値を示してしまう。今回のコードではあくまで-3〜3の範囲で擬似的にsin関数の出力を再現するモデルが作成されただけであり、sin関数そのものではないのである。
このへんは時系列(=前の値)を考慮できるLSTMを利用すると上手いこと再現できるに違いない。次はそのあたりに挑戦してみたいと思う。
- 作者: 島田直希,大浦健志
- 出版社/メーカー: 技術評論社
- 発売日: 2017/09/14
- メディア: 大型本
- この商品を含むブログを見る