Hatena::ブログ(Diary)

mizchi log

@mizchiの雑記帳

2011-08-28

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

2011-06-14

MongoDBなら検索エンジンが簡単に作れる


形態素解析でインデックスを作って検索する
Mongoでの全文検索 - Docs-Japanese - 10gen Confluenceを参考に、すぐ実装できた
試しに、青空文庫から走れメロス引っ張ってやってみた。

ライブラリ


MongoDB API Docs for python
> easy_install pymongo


SREngine: Sein blog PythonからMeCabを使う。
ごにょごにょやっていれる

やってみる



#!/usr/bin/env python
# encoding:utf-8

import pymongo
import MeCab
import urllib
import re

mecab = MeCab.Tagger("-Ochasen")
con = pymongo.Connection()
col = con.test.row


def get_source(url):
    return [ unicode(re.sub("<.*?>","",line),"sjis") 
             for line in urllib.urlopen(url).readlines()]

def create_index():
    lines = get_source("http://www.aozora.gr.jp/cards/000035/files/1567_14913.html"
)
    for n, line in enumerate(lines):
        words = []

        # 単語に分割 変な文字列渡すとSWIGでエラーが出るっぽい
        try:
            node = mecab.parseToNode( line.encode("utf-8") )
            words = get_words( node )
        except :
            pass
        col.insert( {"at":n, "text":line,"words":words })
    col.ensure_index("words")

def search(word):
    for i in col.find({"words":word}):
        print i["at"],i["text"]

def get_words(node):
    if not node.next:
        return [node.surface]
    return [node.surface] + get_words(node.next)

if __name__ == '__main__':
    create_index() # 一回だけ実行
    search("セリヌンティウス")


> python text-search.py
23  メロスは激怒した。必ず、かの邪智暴虐(じゃちぼうぎゃく)の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。けれども邪悪に対しては、人一倍に敏感であった。きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此(こ)のシラクスの市にやって来た。メロスには父も、母も無い。女房も無い。十六の、内気な妹と二人暮しだ。この妹は、村の或る律気な一牧人を、近々、花婿(はなむこ)として迎える事になっていた。結婚式も間近かなのである。メロスは、それゆえ、花嫁の衣裳やら祝宴の御馳走やらを買いに、はるばる市にやって来たのだ。先ず、その品々を買い集め、それから都の大路をぶらぶら歩いた。メロスには竹馬の友があった。セリヌンティウスである。今は此のシラクスの市で、石工をしている。その友を、これから訪ねてみるつもりなのだ。久しく逢わなかったのだから、訪ねて行くのが楽しみである。歩いているうちにメロスは、まちの様子を怪しく思った。ひっそりしている。もう既に日も落ちて、まちの暗いのは当りまえだが、けれども、なんだか、夜のせいばかりでは無く、市全体が、やけに寂しい。のんきなメロスも、だんだん不安になって来た。路で逢った若い衆をつかまえて、何かあったのか、二年まえに此の市に来たときは、夜でも皆が歌をうたって、まちは賑やかであった筈(はず)だが、と質問した。若い衆は、首を振って答えなかった。しばらく歩いて老爺(ろうや)に逢い、こんどはもっと、語勢を強くして質問した。老爺は答えなかった。メロスは両手で老爺のからだをゆすぶって質問を重ねた。老爺は、あたりをはばかる低声で、わずか答えた。

42 「そうです。帰って来るのです。」メロスは必死で言い張った。「私は約束を守ります。私を、三日間だけ許して下さい。妹が、私の帰りを待っているのだ。そんなに私を信じられないならば、よろしい、この市にセリヌンティウスという石工がいます。私の無二の友人だ。あれを、人質としてここに置いて行こう。私が逃げてしまって、三日目の日暮まで、ここに帰って来なかったら、あの友人を絞め殺して下さい。たのむ、そうして下さい。」

48  竹馬の友、セリヌンティウスは、深夜、王城に召された。暴君ディオニスの面前で、佳(よ)き友と佳き友は、二年ぶりで相逢うた。メロスは、友に一切の事情を語った。セリヌンティウスは無言で首肯(うなず)き、メロスをひしと抱きしめた。友と友の間は、それでよかった。セリヌンティウスは、縄打たれた。メロスは、すぐに出発した。初夏、満天の星である。

75 「フィロストラトスでございます。貴方のお友達セリヌンティウス様の弟子でございます。」その若い石工も、メロスの後について走りながら叫んだ。「もう、駄目でございます。むだでございます。走るのは、やめて下さい。もう、あの方(かた)をお助けになることは出来ません。」

83 「待て。その人を殺してはならぬ。メロスが帰って来た。約束のとおり、いま、帰って来た。」と大声で刑場の群衆にむかって叫んだつもりであったが、喉(のど)がつぶれて嗄(しわが)れた声が幽(かす)かに出たばかり、群衆は、ひとりとして彼の到着に気がつかない。すでに磔の柱が高々と立てられ、縄を打たれたセリヌンティウスは、徐々に釣り上げられてゆく。メロスはそれを目撃して最後の勇、先刻、濁流を泳いだように群衆を掻きわけ、掻きわけ、

84 「私だ、刑吏! 殺されるのは、私だ。メロスだ。彼を人質にした私は、ここにいる!」と、かすれた声で精一ぱいに叫びながら、ついに磔台に昇り、釣り上げられてゆく友の両足に、齧(かじ)りついた。群衆は、どよめいた。あっぱれ。ゆるせ、と口々にわめいた。セリヌンティウスの縄は、ほどかれたのである。

85 「セリヌンティウス。」メロスは眼に涙を浮べて言った。「私を殴れ。ちから一ぱいに頬を殴れ。私は、途中で一度、悪い夢を見た。君が若(も)し私を殴ってくれなかったら、私は君と抱擁する資格さえ無いのだ。殴れ。」

86  セリヌンティウスは、すべてを察した様子で首肯(うなず)き、刑場一ぱいに鳴り響くほど音高くメロスの右頬を殴った。殴ってから優しく微笑(ほほえ)み、

88  メロスは腕に唸(うな)りをつけてセリヌンティウスの頬を殴った。


全文検索エンジンとの比較

このようにMongoDBは、検索機能を簡単にするおもしろい機能を持っていますが、上記の通り、これは専用の全文検索エンジンではありません。

専用のエンジンは以下のようなこともできます。

組み込みのstemming機能
クエリにマッチする語句のランキング (MongoDBでもできるかもしれませんが、ユーザがそういうコードを書く必要があります)
一括でのインデックスの構築 (bulk index building)

一括でのインデックスの構築は、インデックスの作成を速くしますが、リアルタイムではないという欠点もあります。MongoDBは、リアルタイムでの検索に特に向いています。伝統的なツールは、このような使い方はあまり得意ではありません。



適材適所でやろう。MongoDB環境があるとき、気軽に実装できる、という程度。

multiprocessing で word count


検索を書いてみるついでに、並列処理でマップリデュースっぽいワードカウントをやってみた。
しかし大量のドキュメントを用意するのが面倒だったので、複数クエリでやってみる。文章と検索対象のどちらが共通か、っていう問題なので、やってることは同じ。

#!/usr/bin/env python
# encoding:utf-8

from multiprocessing import Pool
import urllib
import re

melos = "http://www.aozora.gr.jp/cards/000035/files/1567_14913.html"
words = [u"メロス" , u"セリヌンティウス" , u"王様",u"" ]

def get_source(url):
    return re.sub("<.*?>","",unicode( urllib.urlopen(url).read() ,"sjis"))

text = get_source(melos)

def count_word(word):
    return text.count(word)

if __name__ == '__main__':
    txt = get_source(melos)
    result =  Pool(len(words)).map(
                      count_word, 
                      words ) 
    print result


> python word_count.py
[78, 15, 4, 12]

参考:
Pythonでスリープソート書いてたら multiprocessing の最小構成サンプルになった

2011-05-20

Pythonでスリープソート書いてたら multiprocessing の最小構成サンプルになった


Pythonなら短く書ける。スリ〜プソ〜トはネタとして面白いが実用性は皆無。
だけど、multiprocessingはガチで実用的なモジュール。(Python2.6以降の標準ライブラリ)

#!/usr/bin/env python
from time import sleep
from multiprocessing import Pool
from random import randint

num = 20

def sleep_sort(n):
    sleep(n)
    print(n)

Pool(num).map(
    sleep_sort, 
    [randint(1, num) for i in xrange(num)]
    )

Pool#mapの返り値はそれぞれの実行結果のリストなんだけど、sleep sortでは役に立たないので割愛

常識を覆すソートアルゴリズム!その名も”sleep sort”! - Islands in the byte stream