In [1]:
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import training
from chainer.training import extensions
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
%matplotlib inline
In [2]:
np.array?
iPython では ? をつけて実行することで、 Docstring (各プログラムの説明文) を簡単に参照することができる。
もし「関数はしってるんだけど、引数が分からない」という場合に試してみよう。
なお Python スクリプトにおける Docstring の書き方は
def hogehoge():
""" docstring here! """
return 0
である。(試しに次も実行してみよう)
In [3]:
def hogehoge():
""" docstring here! """
return 0
hogehoge?
In [4]:
class memory_sum:
c = None
def __init__(self, a):
self.a = a
print("run __init__ ")
def __call__(self, b):
self.c = b + self.a
print("__call__\t:", b, "+", self.a, "=", self.c)
def show_sum(self):
print("showsum()\t:", self.c)
クラスは一種の 枠組み なので 実体を用意(インスタンス) する
このとき コンストラクタ と呼ばれる、インスタンスの初期化関数 def __init__() が実行される
In [5]:
A = memory_sum(15)
インスタンスは特に関数を呼び出されない場合 def __call__() が実行される
In [6]:
A(30)
もちろん関数を呼び出すこともできる
In [7]:
A.show_sum()
またインスタンス内の変数へ、直接アクセスできる
In [8]:
A.c
Out[8]:
クラスの引き継ぎ(継承)とは、すでに定義済みのクラスを引用することである。
例として memory_sum を引きついで、引き算機能もつけた場合以下のようになる。
In [9]:
class sum_sub(memory_sum):
def sum(self, a, b):
self.a = a
self.b = b
self.c = a + b
print(self.c)
def sub(self, a, b):
self.a = a
self.b = b
self.c = a - b
print(self.c)
def show_result(self):
print(self.c)
In [10]:
B = sum_sub(30)
In [11]:
B.sum(30, 10)
In [12]:
B.sub(30, 10)
In [13]:
B.show_result()
sum_sub は memory_sum を継承しているため、 memory_sum で定義した関数も利用できる
In [14]:
B.show_sum()
これだけ知っていれば Chainer のコードも多少は読めるようになる。
オブジェクト指向に興味が湧いた場合は、
がおすすめである(特にオブジェクト指向とJavaの結びつきは強いので、言語違いとは言わず読んでみてほしい)
In [15]:
arr = np.arange(-10, 10, 0.1)
arr1 = F.relu(arr, use_cudnn=False)
plt.plot(arr, arr1.data)
Out[15]:
In [16]:
arr = np.arange(-10, 10, 0.1)
arr2 = F.sigmoid(arr, use_cudnn=False)
plt.plot(arr, arr2.data)
Out[16]:
In [17]:
arr = chainer.Variable(np.array([[-5.0, 0.5, 6.0, 10.0]], dtype=np.float32))
plt.plot(F.softmax(arr).data[0])
print("softmax適用後の値: ", F.softmax(arr).data[0])
print("総和: ", sum(F.softmax(arr).data[0]))
In [18]:
x1 = chainer.Variable(np.array([1]).astype(np.float32))
x2 = chainer.Variable(np.array([2]).astype(np.float32))
x3 = chainer.Variable(np.array([3]).astype(np.float32))
試しに下式を計算する(順方向の計算) $$ y = (x_1 - 2 x_2 - 1)^2 + (x_2 x_3 - 1)^2 + 1 $$
各パラメータを当てはめると $$ y = (1 - 2 \times 2 - 1)^2 + (2 \times 3 - 1)^2 + 1 = (-4)^2 + 5^2 + 1 = 42$$
In [19]:
y = (x1 - 2 * x2 - 1)**2 + (x2 * x3 - 1)**2 + 1
y.data
Out[19]:
では今度は y の微分値を求める(逆方向の計算)
In [20]:
y.backward()
In [21]:
x1.grad
Out[21]:
In [22]:
x2.grad
Out[22]:
In [23]:
x3.grad
Out[23]:
In [24]:
l = L.Linear(2, 3)
In [25]:
l.W.data
Out[25]:
In [26]:
l.b.data
Out[26]:
In [27]:
x = chainer.Variable(np.array(range(4)).astype(np.float32).reshape(2,2))
y = l(x)
y.data
Out[27]:
に当てはめ、確認すると同一であることがわかる
In [28]:
x.data.dot(l.W.data.T) + l.b.data # bias は 0 なので足しても足さなくとも同じ
Out[28]:
In [29]:
train, test = chainer.datasets.get_mnist()
train と test の型を確認する
In [30]:
type(train)
Out[30]:
chainer.datasets.tuple_dataset.TupleDataset ということがわかる
(実は chainer.datasets.get_mnist() を確認すれば自明である)
では train の中身はどうか
In [31]:
print(len(train[0][0]))
print(type(train[0][0]))
In [32]:
print(train[0][1])
print(type(train[0][1]))
単純に 画像データ と 正解ラベル がセットになっている
なので画像データの方を reshape(28, 28) することで、画像として表示することも可能である
(ただし 0 - 255 の値ではなく 0.0 - 1.0 になっているので注意)
また chainer.datasets.get_mnist(ndim=2) とした場合は reshape 不要になる
In [33]:
plt.imshow(train[0][0].reshape(28,28))
plt.gray() # gray scale にする
plt.grid()
In [34]:
train_iter = chainer.iterators.SerialIterator(train, 100)
np.shape(train_iter.dataset)
Out[34]:
# 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'))
# Write a log of evaluation statistics for each epoch
trainer.extend(extensions.LogReport())
で出力される log と cg.dot の処理方法についてここでは簡単に紹介する。
log実は log は単なる json ファイルなのだが、馴染みがない人にとっては扱い方が地味面倒である。
ここでは pandas を利用し、データを処理、グラフ化してみる。
すでに import pandas as pd されており、 ./result/log にターゲットとなる log があるという状態において
In [35]:
log = pd.read_json('./result/log')
とするだけで json ファイルの読み込みは完了である。
次に log のテーブルを見てみると、
In [36]:
log
Out[36]:
# 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']))
によってコンソールに出力されたものと同じものが出て来ることがわかる。
では値だけでは分からないので、訓練とテストの過程をグラフ化してみよう。
In [37]:
epoch = log['epoch']
plt.plot(epoch, log['main/accuracy'])
plt.plot(epoch, log['validation/main/accuracy'])
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend(loc='best')
Out[37]:
cg.dotcg.dot は DOT言語 で書かれており、 Wikipedia によれば、
DOT言語
DOT は、プレーンテキストを用いてデータ構造としてのグラフを表現するための、データ記述言語の一種である。コンピュータで処理しやすく、かつ目で見ても分かり易い、単純化された形式でグラフを記述することができる。DOT言語で書かれたデータのファイルは、拡張子として .dot を付けることが多い。 DOT言語の処理系は多数実装されているが、どれもDOT言語の記述をファイルから読み込み、画像を生成するか、グラフを操作することができるようになっている。そのひとつ dot はドキュメンテーションジェネレータのdoxygenで使われている。dot は Graphviz パッケージの一部である。
とのことなので、理屈ではテキストエディタで開くこともできるが、おそらく把握できないと思われるので素直に画像化しよう。
画像化するソフトとして graphviz があるので、ダウンロードページからソフトをダウンロードしインストールすれば良い。
また Linux/Mac であれば CLI ツールをターミナルからインストールすることも可能である。
apt install graphviz
yum install graphviz
brew install graphviz
ここでは ubuntu 14.04 に apt install graphviz (CLIツール) をインストールした場合で紹介する。
dot -> png 画像にする場合は、
dot -Tpng cg.dot -o cg.png
とするだけである。他のコマンドライン引数などの詳細については
man dot
dot --help
で参照してほしい。
どうしてもソフトをインストールすることができない場合 は、 webgraphviz に cg.dot の中身をコピペすれば画像化することも可能である。
train_mnist.py で MLP を実行した際のグラフ
train_mnist.py を元に、CNN も追加したコードを記載する。
#!/usr/bin/env python
from __future__ import print_function
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):
def __init__(self, n_units, n_out):
super(MLP, self).__init__(
# the size of the inputs to each layer will be inferred
l1=L.Linear(None, n_units), # n_in -> n_units
l2=L.Linear(None, n_units), # n_units -> n_units
l3=L.Linear(None, n_out), # n_units -> n_out
)
def __call__(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
return self.l3(h2)
class CNN(chainer.Chain):
def __init__(self, n_unit, n_out):
super(CNN, self).__init__(
conv1=L.Convolution2D(None, 16, ksize=3),
conv2=L.Convolution2D(None, 32, ksize=3),
conv3=L.Convolution2D(None, 64, ksize=3),
l1=L.Linear(None, n_unit), # 出力数の把握のために数値を入れるとベター、 Noneちょっとズル
l2=L.Linear(None, n_out)
)
def __call__(self, x):
h = F.max_pooling_2d(F.relu(self.conv1(x)), 2)
h = F.max_pooling_2d(F.relu(self.conv2(h)), 2)
h = F.max_pooling_2d(F.relu(self.conv3(h)), 2)
h = F.dropout(F.relu(self.l1(h)))
return self.l2(h)
def main():
parser = argparse.ArgumentParser(description='Chainer example: MNIST')
parser.add_argument('--mode', '-m', type=str, default="MLP",
help='MLP or CNN')
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('--gpu', '-g', type=int, default=-1,
help='GPU ID (negative value indicates CPU)')
parser.add_argument('--out', '-o', default='result',
help='Directory to output the 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('MODE:{}'.format(args.mode))
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.
if args.mode == 'CNN':
model = L.Classifier(CNN(args.unit, 10))
# Load the MNIST dataset
train, test = chainer.datasets.get_mnist(ndim=3) # channel, width and height
args.out = 'CNN_result' if args.out == 'result' else args.out
elif args.mode == 'MLP':
model = L.Classifier(MLP(args.unit, 10))
# Load the MNIST dataset
train, test = chainer.datasets.get_mnist(ndim=1)
else:
from sys import exit
print("Missing Classifier {} (exit)".format(args.mode))
exit()
if args.gpu >= 0:
chainer.cuda.get_device(args.gpu).use() # Make a specified GPU current
model.to_gpu() # Copy the model to the GPU
# Setup an optimizer
optimizer = chainer.optimizers.Adam()
optimizer.setup(model)
# Load the MNIST dataset
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)
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 at each epoch
trainer.extend(extensions.snapshot(), trigger=(args.epoch, 'epoch'))
# もし分類器を再利用したい場合、以下を追記すると model と optimizer を書き出す
# trainer.extend(extensions.snapshot_object(model, 'model_iter_{.updater.iteration}'), trigger=(1, 'epoch'))
# trainer.extend(extensions.snapshot_object(optimizer, 'optimizer_iter_{.updater.iteration}'), trigger=(1, 'epoch'))
# Write a log of evaluation statistics for each epoch
trainer.extend(extensions.LogReport())
# 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()