2015年から2016年にかけて流行した言葉の一つに「人工知能」があるワケだけれど、「よく知らないので使えません」なんてことを言ってる場合でもないので、仕事とは関係なくChainerの勉強を始めてみようと思う。
業務などで判別機が必要なシーンでは、SVMとかRandomForestとかを使っていたのだけれど、これから、いつもどおりにSVMで判別機を書いた時にされるであろう質問もなんとなく予想されるし(「それってDeep Learningしたらどうなるの?」→「どうもなりません」って回答だけで済ませたい)、なんだかんだで流行語には敏感な姿勢をアピールしておきたい(謎)。
というワケで、参考文献:
chainerのインストール
http://docs.chainer.org/en/stable/install.html を読んで分からなかったら、その場で退場せざるをえないくらいインストールは簡単だ。
とりあえず、自宅のMacでCUDAなんて使える環境ではないので、CUDA suuportなしのchainerのインストールを試みる。
anacondaでのサポートはどうなっている?
その前に、自宅のMacのPython環境はAnacondaで構築されているので、まずcondaでインストールできるかどうかを確認する。
% anaconda search -t conda chainer
Using Anaconda API: https://api.anaconda.org
Run 'anaconda show <USER/PACKAGE>' to get more details:
Packages:
Name | Version | Package Types | Platforms
------------------------- | ------ | --------------- | ---------------
steerapi/chainer | 0 | conda | win-64
: A flexible framework of neural networks
Found 1 packages
win-64のパッケージしか用意されていないようなので、ドキュメントどおり、pipでchainerをインストールしておく。
% pip install chainer
(中略)
Installing collected packages: chainer
Successfully installed chainer-1.19.0
ここまできて、ipython上でimport chainer
してもエラーが出なかったので、おそらくOKだと思う。
チュートリアルの実施
インストールできたら、チュートリアルを実施する。これは、「何ができるのか?」を知るのに大切だし、「やりたいことを実現するために、ドキュメントを読むにあたって、どういうキーワードで検索すれば良いか?」のヒントにもなる。
で、ここに来てよくわかったのは、ニューラルネットについて、少なくとも英語のドキュメントを読める程度に専門用語を理解している必要があるということだ。
○と→の組み合わせの有向グラフと重み付けっていうのを日本語でぼんやりなんとなくイメージできる程度だと、そもそもこのチュートリアルを読み進めるのが難しい(オレのことです)。
まあ、しかしキーワードは"Define-by-Run"である。
チュートリアル実施の前に
チュートリアルで紹介されるコードでは、以下が省略されているとのこと。
import numpy as np
import chainer
from chainer import cuda, Function, gradient_check, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.training import extensions
実際の自宅Mac環境ではCUDAを使っていないので、そのあたりは差し引く必要があるかと思う。
MNIST
チュートリアルでは、やはりMNISTの実装が紹介されている。
まずは、データを準備する。
train, test = datasets.get_mnist()
これを実行すると、図のようにMNISTで使われる例の手書き文字データがダウンロードされる。
% ls -A ~/.chainer/dataset/pfnet/chainer/mnist/
test.npz train.npz
学習用のデータセットは試行回ごとにシャッフルするけれど、テスト用のデータセットはシャッフルする必要はないでしょう、ということで、以下の様に設定せよと書いてある。
すなわち、学習用のデータセットとテスト用のデータセットでは、iteratorに与えるオプションを変える必要があるということだ。
train_iter = iterators.SerialIterator(train, batch_size=100, shuffle=True)
test_iter = iterators.SerialIterator(test, batch_size=100, repeat=False, shuffle=False)
データセットが用意できたところで、チュートリアルに従って3層のネットワーク構造を定義する。
class MLP(Chain):
def __init__(self, n_units, n_out):
super(MLP, self).__init__(
l1 = L.Linear(None, n_units),
l2 = L.Linear(None, n_units),
l3 = L.Linear(None, n_out)
)
def __call__(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
y = self.l3(h2)
return y
l1, l2, l3が連鎖的に呼ばれるからchainerなんですかね。
l1, l2, l3は、それぞれ入力があって出力をするという関数のように見えるんだけれど、これをchainerの体系ではlinkと呼んでいて、この入力を最適化するのが、この体系における目的になるという感じかな?
で、このような3層ネットワークのニューラルネットでは、通常第2層をhidden layerとして扱うんだけれど、特にこのネットワーク構造の定義においては、l2がhidden layerですよってことは明確に宣言していない。だけど、__call__された時に、入力されたxからl1にてh1が計算されて、そのh1は特に出力されないでl2に入力され、その結果として出て来るh2も特に出力されることなくl3に渡されて、計算結果のyのみが出力される構造になっているので、結果的にl2というlinkがhidden layerを指しているという理解で正しいかな?
ということは、class MLPを3層から4層、5層と多層に拡張していく際にも、__init__するときに中間層をただ追加していくだけで良さそうだ。
このネットワークの精度と損失を評価する関数は、chainer.links.Classifier
として定義されているので、ソレを呼ぶ。
model = L.Classifier(MLP(100, 10))
optimizer = optimizers.SGD()
optimizer.setup(model)
この唐突に出てくるSGD()っていうのは、確率的勾配降下法のことだ。
ここにきて、ようやく学習セットを使って学習させることが可能になった。
updater = training.StandardUpdater(train_iter, optimizer)
trainer = training.Trainer(updater, (20, 'epoch'), out='result')
これで、学習をするには、trainerのrun()を呼び出せばよいのだが、学習状況を知りたい(というか、今回、ここまで書いてきたpython scriptがちゃんと動いているところを目で見たい)場合は、extensionを設定すれば良いらしい。
trainer.extend(extensions.Evaluator(test_iter, model))
trainer.extend(extensions.LogReport())
trainer.extend(extensions.PrintReport(['epoch', 'main/accuracy', 'validation/main/accuracy']))
trainer.extend(extensions.ProgressBar())
ということで、20回繰り返しの学習を行って、判別機(Classifier)ができた。
詳しくは、resultフォルダにlogというテキストファイルで実行中のログが保存されている(extensions.LogReportを指定して、Trainerにout='result'を指定したから)。
まとめ
ここまでのまとめ。
- 入力を受けて出力をするLinkという一括り(関数? class?)
- Linkは連鎖反応的に呼び出されるように記述することができる
- link1への入力を受けて出力されたものが、link2の入力となる
- link2からの出力がlink3への入力となる
- "Define-by-Run"とは何だったのか?
- 過去の別のニューラルネットの実装と比較しないとよく分からんな
ここまでを見てみた印象では、もう少しちゃんとニューラルネットに関する知識がないといけないけれど、「ライブラリに強く依存したややこしい書き方」というのは要求されなくて、なんというか、極めてpythonっぽい書き方・考え方でイケそうな気にさせてくれるということが分かった。なるほど、人気なワケだ。
さて、http://qiita.com/fukuit/items/d69d8ca1ad558c4de014 では、OpenCVに付属のk-th nearest neighborで数字判別を行ってみたのだけれど、ソレと比べて結果はどうなのか?というと、20回の試行で0.95程度のaccuracyになっているようなので、0.91程度だったKNNよりも判別機としての性能が良いと言って良いのかもしれない。
今日のコード
チュートリアルそのまんまである。