Chainerを使って多層パーセプトロンを実装する

ChainerというPythonライブラリを用いて、ディープラーニングにおける多層パーセプトロンを実装します。

MNISTという数字画像データ集

ここでは、画像データとして、MNISTにおける数字画像データを使用します。

MNISTのページ

上記のページに数字の画像データとラベルデータがまとめられたバイナリファイルがおいてあります。

  • train-images-idx3-ubyte.gz:  学習データ用の文字画像集
  • train-labels-idx1-ubyte.gz:  学習データ用の文字画像集に対応した数字
  • t10k-images-idx3-ubyte.gz:   テストデータ用の文字画像集
  • t10k-labels-idx1-ubyte.gz:   テストデータ用の文字画像集に対応した数字

となっています。バイナリファイルの詳しい構造はMNISTページの下の方に書いてあるので、参考にしましょう。ただ、Chainerにはscikit-learnというモジュールがあり、これらの画像データとラベルを簡単に使用できるようになっています。

今回つくる層の構造

今回は、入力層が28 x 28の数字画像で、中間層に100個のユニットを設置します。そして、出力層に10個のユニットをおき0から9の数字を認識できるようにします。

実装

では、実装します。Pythonで実装すると以下のようになります。

#coding: utf-8
import numpy as np
import chainer
import chainer.functions as F
from chainer import optimizers
from sklearn.datasets import fetch_mldata
from sklearn.cross_validation import train_test_split



# 数字データをMNISTから読み込む
print("load MNIST dataset")
mnist=fetch_mldata('MNIST original',data_home=".")
x=mnist.data
y=mnist.target
x=x.astype(np.float32)
y=y.astype(np.int32)

x /= x.max()	#輝度を揃える

#データを「学習データ:テストデータ=9:1」に分ける
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.1)

N=y_train.size		#学習データ数
N_test=y_test.size	#テストデータ数
print("train data=%d" % N)
print("test data=%d" % N_test)

#[画像数][色数][縦][横]の形式にする
x_train=x_train.reshape((len(x_train),1,28,28))
x_test=x_test.reshape((len(x_test),1,28,28))

batchsize=100	#バッチのサイズは100
n_epoch=20		#エポックは20回
n_units=100		#中間層のユニット数は100

model=chainer.FunctionSet(			#層の定義
	l1=F.Linear(28*28,n_units),		#入力層:28x28 -> 中間層:n_units
	l2=F.Linear(n_units,10))		#中間層:n_units -> 出力層: 10

#順伝搬関数
def forward(x_data,y_data,train=True):
	x=chainer.Variable(x_data)
	t=chainer.Variable(y_data)
	#正規化関数で出力,DropOutで過学習を防ぐ
	h1=F.dropout(F.relu(model.l1(x)),train=train)
	y=model.l2(h1)
	#出力の評価
	if train:
		loss=F.softmax_cross_entropy(y,t)
		return loss
	else:
		acc=F.accuracy(y,t)
		return acc

optimizer=optimizers.Adam()
optimizer.setup(model)

for epoch in range(1,n_epoch+1):
	print("epoch: %d" % epoch)
	perm=np.random.permutation(N)
	sum_loss=0
	for i in range(0,N,batchsize):
		#学習データの入力と答えを取得
		x_batch=np.asarray(x_train[perm[i:i+batchsize]])
		y_batch=np.asarray(y_train[perm[i:i+batchsize]])
		#勾配をゼロにしておく
		optimizer.zero_grads()
		#順伝搬
		loss=forward(x_batch,y_batch)
		#逆伝搬
		loss.backward()
		#勾配を計算し加える
		optimizer.update()
		#lossの計算
		sum_loss += float(loss.data)*len(y_batch)
	print("train mean loss: %f" % (sum_loss/N))
	#テストデータで計測
	sum_accuracy=0
	for i in range(0,N_test,batchsize):
		#テストデータの入力と答えを取得
		x_batch=np.asarray(x_test[i:i+batchsize])
		y_batch=np.asarray(y_test[i:i+batchsize])
		#順伝搬
		acc=forward(x_batch,y_batch,train=False)
		#正確さを計測
		sum_accuracy += float(acc.data)*len(y_batch)
	print("test accuracy: %f" % (sum_accuracy/N_test))

まずはじめに今回使用するモジュールをまとめてインポートしています。

import numpy as np
import chainer
import chainer.functions as F
from chainer import optimizers
from sklearn.datasets import fetch_mldata
from sklearn.cross_validation import train_test_split

次に、MNISTの画像とラベルデータをダウンロードし読み込んでいきます。ここで、輝度の幅を一定になるように各画像の最大値で割っています。

# 数字データをMNISTから読み込む
print("load MNIST dataset")
mnist=fetch_mldata('MNIST original',data_home=".")
x=mnist.data
y=mnist.target
x=x.astype(np.float32)
y=y.astype(np.int32)

x /= x.max()	#輝度を揃える

続いて、学習用の画像と、テスト画像に分けていきます。ここでは9:1の割合で分けています。test_size=0.1なので、テストサイズは全体の10%ということになります。

#データを「学習データ:テストデータ=9:1」に分ける
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.1)

N=y_train.size		#学習データ数
N_test=y_test.size	#テストデータ数
print("train data=%d" % N)
print("test data=%d" % N_test)

あとは、読み込んだ画像のデータをニューラルネットワークの入力使える形に変換します。つまり、[画像数][色数][縦][横]のテンソルへ変換します。

#[画像数][色数][縦][横]の形式にする
x_train=x_train.reshape((len(x_train),1,28,28))
x_test=x_test.reshape((len(x_test),1,28,28))

いよいよ層の設定です。FunctionSetにより、重みや活性化関数などの情報をわかるようにいれていきます。ここで、Linearは重みを線形で足すという意味です。

batchsize=100	#バッチのサイズは100
n_epoch=20		#エポックは20回
n_units=100		#中間層のユニット数は100

model=chainer.FunctionSet(			#層の定義
	l1=F.Linear(28*28,n_units),		#入力層:28x28 -> 中間層:n_units
	l2=F.Linear(n_units,10))		#中間層:n_units -> 出力層: 10

順伝搬の定義をしておきます。reluは正規化関数であり、dropoutは過学習を防ぐために使っています。ユニットのいくつかをランダムに機能不全にして局所安定化を防いでくれます。今回は中間層の数が入力に対して少ないのでdropoutはなくても問題ないです。最後に評価用の値を出力しています。

#順伝搬関数
def forward(x_data,y_data,train=True):
	x=chainer.Variable(x_data)
	t=chainer.Variable(y_data)
	#正規化関数で出力,DropOutで過学習を防ぐ
	h1=F.dropout(F.relu(model.l1(x)),train=train)
	y=model.l2(h1)
	#出力の評価
	if train:
		loss=F.softmax_cross_entropy(y,t)
		return loss
	else:
		acc=F.accuracy(y,t)
		return acc

誤差逆伝搬法による最適化の設定を行います。ここではAdamという方法により、うまく学習できる方法を使っています。勾配に対して一定量をかけて更新するのが最も単純です。しかし、はじめのうちは大きめに移動して、収束するに従いステップは小さくした方が早く正確に収束できます。このステップ量を的確に決めるアルゴリズムがAdamだと考えればいいでしょう。

optimizer=optimizers.Adam()
optimizer.setup(model)

あとは、ランダムに画像を選び、誤差逆伝搬法を行っていきます。forwardからの戻り値はただの出力ではなくこれまでの計算が記憶されています。ですから、この戻り値lossをつかってbackwardをしてやれば、勾配を計算できます。この際、勾配は加算されるので、必ずzero_gradsで勾配をゼロにしておきましょう。あとは、updateすれば、勾配を計算し重みの更新をしてくれます。

	perm=np.random.permutation(N)
	sum_loss=0
	for i in range(0,N,batchsize):
		#学習データの入力と答えを取得
		x_batch=np.asarray(x_train[perm[i:i+batchsize]])
		y_batch=np.asarray(y_train[perm[i:i+batchsize]])
		#勾配をゼロにしておく
		optimizer.zero_grads()
		#順伝搬
		loss=forward(x_batch,y_batch)
		#逆伝搬
		loss.backward()
		#勾配を計算し加える
		optimizer.update()

最後にテストデータで層を評価してやっておしまいです。

	#テストデータで計測
	sum_accuracy=0
	for i in range(0,N_test,batchsize):
		#テストデータの入力と答えを取得
		x_batch=np.asarray(x_test[i:i+batchsize])
		y_batch=np.asarray(y_test[i:i+batchsize])
		#順伝搬
		acc=forward(x_batch,y_batch,train=False)
		#正確さを計測
		sum_accuracy += float(acc.data)*len(y_batch)
	print("test accuracy: %f" % (sum_accuracy/N_test))

実行すると以下のようになります。


load MNIST dataset
train data=63000
test data=7000
epoch: 1
train mean loss: 0.509310
test accuracy: 0.933857
epoch: 2
train mean loss: 0.279787
test accuracy: 0.950857
epoch: 3
train mean loss: 0.228698
test accuracy: 0.958000
epoch: 4
train mean loss: 0.202950
test accuracy: 0.962143
epoch: 5
train mean loss: 0.185054
test accuracy: 0.964286
epoch: 6
train mean loss: 0.171868
test accuracy: 0.965000
epoch: 7
train mean loss: 0.159017
test accuracy: 0.968286
epoch: 8
train mean loss: 0.150607
test accuracy: 0.969857
epoch: 9
train mean loss: 0.143297
test accuracy: 0.972857
epoch: 10
train mean loss: 0.140820
test accuracy: 0.971429
epoch: 11
train mean loss: 0.136245
test accuracy: 0.973143
epoch: 12
train mean loss: 0.131337
test accuracy: 0.973571
epoch: 13
train mean loss: 0.126852
test accuracy: 0.971286
epoch: 14
train mean loss: 0.124598
test accuracy: 0.973571
epoch: 15
train mean loss: 0.119624
test accuracy: 0.973429
epoch: 16
train mean loss: 0.119024
test accuracy: 0.973143
epoch: 17
train mean loss: 0.117547
test accuracy: 0.973714
epoch: 18
train mean loss: 0.112782
test accuracy: 0.973571
epoch: 19
train mean loss: 0.113699
test accuracy: 0.973143
epoch: 20
train mean loss: 0.109415
test accuracy: 0.974000

解説は以上で終わります。ディープラーニングに興味がある人は以下に参考書籍を読むと良いでしょう。

基本的なディープラーニングの解説がのっています。実装する際は持っておくといいでしょう。

最近のディープラーニングの出来事が詳しく書いてあります。

著者:安井 真人(やすい まさと)