pybrainでニューラルネットワーク入門

勉強しつつ書いてみる。微妙な知識で書いてるので、おそらく間違ったことをたくさん書いてる。
まあせめて初学者らしく、初学者に通じるように平易な言葉で!

やりたいこと

関数(モデル)に乱数を与えて生成した訓練データから、元の関数の振る舞いを模倣(近似)できるようにする。

pybrain

Pythonで扱えるニューラルネットワークのライブラリ、だそうで。
ギッハブからインスコ

$ git clone git://github.com/pybrain/pybrain.git
$ cd pybrain
$(sudo) python setup.py install

参考: 映像奮闘記: PyBrain - a modular Machine Learning Library for Python

概要

バックプロパゲーション(誤差伝搬法)
入力層 - 隠れ層 - 出力層の三層からなり、入力層のノードは隠れ層のすべてのノードへ、隠れ層のノードは出力層へすべてのノードへ辺を持つ有向グラフである。
ニューラルネットワークでは、各ノードから次のノードへ、どれぐらい刺激が伝わるか、という「重み」の係数を持っている。

入力値と、期待される結果をペアを訓練用データとして持っておく。
訓練用の入力値から、期待される出力値と実際の出力値の差(二乗平方和)を算出し、各ノードへの重みを修正する。(誤差伝搬)

超大雑把な使い方

この入力値があったら、こういう結果が欲しい、っていうペアを用意する。
ニューラルネットワークは、入力値が与えられるたびに、その関数の振る舞いに近くなるように変化する。
つまり、よくわかんないモデルを、よくわかんないまま生成できる!(かもしれない)ってこと

今回使う関数

def f(x,y):
    return sin(x)-cos(y)+random()/10

random()/10のノイズを混ぜてある
今回は入力2、出力1。隠れ層を4とする。(適切な隠れ層の数の設定は、どうすべきなんだろう)

nn.py

#!/usr/bin/env python
from pybrain.datasets import SupervisedDataSet
from pybrain.tools.shortcuts import buildNetwork
from pybrain.supervised import BackpropTrainer
from math import sin,cos
from random import random

def f(x,y):
    return sin(x)-cos(y)+random()/10

def make_sample(params,f):
    inc = len(params[0])
    data = SupervisedDataSet(inc,1)  
    for p in params:
        data.addSample( p , f(p[0],p[1]) )
    return data

hidden_layer_count = 4
sampledata_count = 1000
training_count = 3
checkdata_count = 10
learningrate = 0.01
momentum = 0.99
verbose = True

# data
trainer_data = make_sample([ [random(),random()] for _ in range(sampledata_count)] , f)
checkdata = make_sample([ [random(),random()] for _ in range(checkdata_count)], f)

# training
network = buildNetwork(trainer_data.indim, hidden_layer_count , trainer_data.outdim,recurrent=True)
trainer = BackpropTrainer(network, trainer_data, learningrate = learningrate, momentum = momentum, verbose = verbose)
for epoch in range(0,training_count):
    trainer.train()
trainer.testOnData(checkdata, verbose= verbose)

実行結果

Total error: 0.335624543641
Total error: 0.0493996072757
Total error: 0.0110288218894

Testing on data:
out:     [ -1.108]
correct: [ -0.898]
error:  0.02208513
out:     [ -0.453]
correct: [ -0.462]
error:  0.00003415
out:     [  0.062]
correct: [  0.186]
error:  0.00776188
out:     [ -0.979]
correct: [ -0.898]
error:  0.00321050
out:     [ -0.421]
correct: [ -0.459]
error:  0.00076002
out:     [ -0.339]
correct: [ -0.314]
error:  0.00031656
out:     [ -0.606]
correct: [ -0.503]
error:  0.00526324
out:     [ -0.585]
correct: [ -0.636]
error:  0.00130296
out:     [ -0.237]
correct: [ -0.168]
error:  0.00234576
out:     [ -0.143]
correct: [ -0.142]
error:  0.00000025
All errors: [0.022085132618168034, 3.4146022027572035e-05, 0.007761880400884766, 0.0032104954404822274, 0.00076002279203014802, 0.00031655778021079389, 0.0052632400392830254, 0.0013029642051017734, 0.0023457648422505278, 2.5128484847378107e-07]
Average error: 0.00430804554253
('Max error:', 0.022085132618168034, 'Median error:', 0.0023457648422505278)

これは訓練用データを1000個用意して、3回づつ訓練してみる例。
ご覧のとおり、outとcorrectでだいたい同じような値で対応して、それっぽいモデルが生成されてる。(ちゃんと信頼区間で検定すべきなんだろうけど、面倒くさい)

check_data と trainer_data は同じ関数から生成されたデータではあるけど、trainer は 訓練用データから生成された関数を内部に持ってて、check_data と trainer_dataに直接の関係はない。が、試しに10個のテストデータを突っ込んでみると、誤差が少ないので嬉しいね、ってことになってる。
元のモデルに乱数が存在するので、確実な模倣は不可能にしているのだけど、実際に扱うデータはもっとノイズまみれなのでこんなに上手くいく例は、実際にはほとんどない。

応用

手元の「ゲーム開発者のためのAI入門」によると、アクションゲームにおけるモンスターの接近/逃走(入力層:味方HP/敵HP)、とか、ゲームだと状況が与えられて何らかの判断に用いることが多い、らしい。
今適当に思いついたのだけど、Twitterの誰かの発言を教師データに、単語の出現傾向(どうやってパラメータを抽出する?)からその人の発言かどうか判定する、なんてこともできそう。Twitterのログデータは大量に持ってるので、あとでやってみる。


リカレントネットの学習法と応用: オートマトンの抽出 http://ds9.jaist.ac.jp:8080/ResearchData/sub/99/kobayashi/exsub3.html