PythonのChainerでのニューラルネットの書き方

お久しぶりです。最近Webプログラミング以外にPythonも扱い始めたので今回はその話題です。

何かと話題になっている深層学習(ディープラーニング)ですが、プログラミングで深層学習をするためのフレームワークというのも存在します。よく使われるのは、PythonのChainerというものと、TensorFlowの2種類です。私は最近PythonのChainerを使い始めましたので、Chainerを使ったディープニューラルネットの書き方を少し解説していきたいと思います。

まずはChainerの導入


Chainerを使うには環境を設定しなければいけませんが、これが一番厄介かもしれません。Pythonをまずは入れるところからなのですが、macなんかを使っている人はデフォルトでPythonが導入されていることがあると思います。できればデフォルトのものより、新しく環境を整える意味でも違うバージョンを入れ直した方がいいかもしれません。私の場合は2.7.3を導入しました。

Chainerの導入方法については他サイトでも紹介されていますのでそちらを参考にしてみてください。

下記サイトなど参考になるかと思います。

http://qiita.com/shinji1105/items/3c54feb6dd5e7ea63a62

サンプルプログラムを見てみる。

今回はChainerに用意されているサンプルプログラムを使ってニューラルネットの構築方法を見ていこうと思います。

一番わかりやすいのはmnist.pyですね。これエムニストって読むらしいです・・・余談

moist.pyは何をしているプログラムかというと、0から9までの手書き数字が書かれた画像をニューラルネットを使って学習し判別しています。画像はプログラム中でダウンロードしてきているようです。

ひとまず以下にプログラムと、個人的に説明文を挿入したものを示します。

・mnist.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function

try:
import matplotlib
matplotlib.use('Agg')
except ImportError:
pass

import argparse

import chainer
import chainer.functions as F
import chainer.links as L
from chainer import training
from chainer.training import extensions

# Network definition
class MLP(chainer.Chain):#MLP->レイヤー構成を定義するメソッド

def __init__(self, n_units, n_out):#定義したユニット数1000ー>n_units,10ー>n_outに入る
super(MLP, self).__init__()
with self.init_scope():
# the size of the inputs to each layer will be inferred Liner->全結合
self.l1 = L.Linear(None, n_units)  # n_in -> n_units 第一層 入力数は自動で推測?出力のユニット数は1000
self.l2 = L.Linear(None, n_units)  # n_units -> n_units 第二層 入力数は自動で推測?出力のユニット数は1000
self.l3 = L.Linear(None, n_out)  # n_units -> n_out 第三層 入力数は自動で推測?出力のユニット数は10

def __call__(self, x):#順伝播処理、学習ループで連鎖的に呼ばれる
h1 =
F.relu(self.l1(x))#reluー>活性化関数
h2 = F.relu(self.l2(h1))
return self.l3(h2)

def main():
parser = argparse.ArgumentParser(description='Chainer example: MNIST')
parser.add_argument('--batchsize', '-b', type=int, default=100,
help='Number of images in each mini-batch')
parser.add_argument('--epoch', '-e', type=int, default=20,
help='Number of sweeps over the dataset to train')#学習の繰り返し数の定義
parser.add_argument('--frequency', '-f', type=int, default=-1,
help='Frequency of taking a snapshot')#スナップショット撮影の頻度?
parser.add_argument('--gpu', '-g', type=int, default=-1,
help='GPU ID (negative value indicates CPU)')#-1(negative)ならcpu,1(positive)ならgpu
parser.add_argument('--out', '-o', default='result',
help='Directory to output the result')#結果出力のディレクトリ名(result)
parser.add_argument('--resume', '-r', default='',
help='Resume the training from snapshot')#スナップショットからの学習再開?
parser.add_argument('--unit', '-u', type=int, default=1000,

help='Number of units')#ユニット数定義
args = parser.parse_args()

print('GPU: {}'.format(args.gpu))
print('# unit: {}'.format(args.unit))
print('# Minibatch-size: {}'.format(args.batchsize))
print('# epoch: {}'.format(args.epoch))
print('')

# Set up a neural network to train
# Classifier
reports softmax cross entropy loss and accuracy at every
# iteration, which will be used by the PrintReport extension below.
model = L.Classifier(MLP(args.unit, 10))#MLPにargs.unit(ユニット数)と10を引数として渡してモデル作成
#Classifierクラス内ではデフォルトがソフトマックス・クロスエントロピー関数
if args.gpu >= 0:#gpuを使うか否か
# Make a specified GPU current
chainer.cuda.get_device_from_id(args.gpu).use()
model.to_gpu()  # Copy the model to the GPU

# Setup an optimizer オプティマイザーの役割は重み、バイアスの更新
optimizer = chainer.optimizers.Adam()#Adamというアルゴリズムで更新
optimizer.setup(model)

# Load the MNIST dataset
train, test = chainer.datasets.get_mnist()#MNISTのサイトからデータセットをダウンロード

train_iter = chainer.iterators.SerialIterator(train, args.batchsize)
test_iter = chainer.iterators.SerialIterator(test, args.batchsize,
repeat=False, shuffle=False)

# Set up a trainer
updater = training.StandardUpdater(train_iter, optimizer, device=args.gpu)#学習ループに必要な初期値をセット(updaterも関わる)
trainer = training.Trainer(updater, (args.epoch, 'epoch'), out=args.out)

# Evaluate the model with the test dataset for each epoch 各学習ループのてすとでーたで評価
trainer.extend(extensions.Evaluator(test_iter, model, device=args.gpu))

# Dump a computational graph from 'loss' variable at the first iteration
#
The "main" refers to the target link of the "main" optimizer. 損失のグラフ作成?
trainer.extend(extensions.dump_graph('main/loss'))

# Take a snapshot for each specified epoch 特定の学習ループごとにスナップショットを作成
frequency = args.epoch if args.frequency == -1 else max(1, args.frequency)
trainer.extend(extensions.snapshot(), trigger=(frequency, 'epoch'))

# Write a log of evaluation statistics for each epoch ループごとに統計情報ログ作成

trainer.extend(extensions.LogReport())

# Save two plot images to the result dir プロットしたデータをresult内に保存
if extensions.PlotReport.available():
trainer.extend(
extensions.PlotReport(['main/loss', 'validation/main/loss'],
'epoch', file_name='loss.png'))
trainer.extend(
extensions.PlotReport(
['main/accuracy', 'validation/main/accuracy'],
'epoch', file_name='accuracy.png'))

# Print selected entries of the log to stdout
# Here "main" refers to the target link of the "main" optimizer again, and
# "validation" refers to the default name of the Evaluator extension.
# Entries other than 'epoch' are reported by the Classifier link, called by
# either the updater or the evaluator. 画面に各情報を出力
trainer.extend(extensions.PrintReport(
['epoch', 'main/loss', 'validation/main/loss',
'main/accuracy', 'validation/main/accuracy', 'elapsed_time']))

# Print a progress bar to stdout 進捗バーを表示
trainer.extend(extensions.ProgressBar())

if args.resume:
# Resume from a snapshot スナップショットから再開?
chainer.serializers.load_npz(args.resume, trainer)

# Run the training 学習を実行
trainer.run()

if __name__ == '__main__':
main()

 

すべて説明するとかなり長くなるので、ディープネットの構築部分だけ解説します。まず、各ニューラルネットの層を定義している部分からです。プログラム中のMLPと名付けられたクラス内の_init_で定義されています。

self.l1 = L.Linear(None, n_units)  # n_in -> n_units 第一層 入力数は自動で推測?出力のユニット数は1000
self.l2 = L.Linear(None, n_units)  # n_units -> n_units 第二層 入力数は自動で推測?出力のユニット数は1000
self.l3 = L.Linear(None, n_out)  # n_units -> n_out 第三層 入力数は自動で推測?出力のユニット数は10

Lというのは、Linksの事で、初めのインポート文import chainer.links as Lによって使えるようになっています。LinksのLinearクラスを参照していて、これは全結合層を表します。他にも、畳み込み層なら、Convolution2Dなど、層によって適したクラスが用意されているようです。
mnist.pyの場合は全結合層3層で構成されているニューラルネットということがわかります。Linearの引数は2つで、一つ目が入力の数、2つ目が出力の数となっています。Noneとすると入力は自動で入ってきた入力数分を判断するのではないでしょうか。n_unitsとn_outは_init_が呼び出される時に引数として入ってくるので、自由に変更可能ですね。

次に_call_を見ていきます。こちらもMLP内にありますね。

h1 =F.relu(self.l1(x))#reluー>活性化関数
h2 =F.relu(self.l2(h1))
return self.l3(h2)

先ほどは層を定義しただけでしたが、ここでは各層の活性化関数を適宜しています。FはFunctionsの事で、こちらもimport chainer.functions as Fにより使用できるようになっています。Functionsクラス内のreluという関数を呼び出していて、これはChainerでは最もよく使われる活性化関数のようです。他にも幾つか種類があるようです。今回は1層と2層の活性化関数をreluにしてあることがわかりますね。

実はニューラルネットの定義はこれで終わりです。細かい誤差関数の設定なんかはまた別であるのですが、ひとまず層とその結びつきに関しては、層を定義して活性化関数でつなぐだけなんですね。簡単です。

まとめ

それにしてもChainerのサンプルプログラムめっちゃ短い!と思ったのに別の関数に飛びまくってて、1つのファイルに全部書くとめちゃくちゃ長いんじゃないかこれ。。。読み解くのにも一苦労です。まあだからフレームワークになっているんでしょうけどね。