Chainerを使って畳み込みを実装する

前回、Chainerを使って多層パーセプトロンを実装しました。

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

今回は、前回のものをパワーアップして、畳み込み層を組み込みます。

畳み込み

まず畳み込み(Convolution)について解説します。畳み込みとはある特徴的な模様を抽出するために行う操作です。例えば、以下のようなフィルタに対して畳み込みを行うと、斜め模様の入力が入ると高い出力を返します。

0.3 0 0
0 0.4 0
0 0 0.3

プーリング

プーリングはROIの平均や最大値を返す操作です。例えば、以下のような入力に対して最大プーリングを行うと最大値である10を返します。

1 3 0
10 4 0
0 1 0

実装

畳み込みを行うと、ある特徴的な模様を抽出することができます。そして、プーリングにより抽出した模様が多少ずれても許容してくれます。例えば、数字なら縦横斜めなどの線を畳み込みにより抽出し、プーリングで多少のずれを補正してくれるというわけです。

さて、今回は

  1. 畳み込み(5パターン,縦3px,横3px)
  2. 最大プーリング(ストライドは2)
  3. 全結合層(中間層100)
  4. 全結合層

と組み込みます。実装すると以下のようになります。

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(            #層の定義
    conv1=F.Convolution2D(1,5,3,pad=0),#畳み込み: 28x28x1 -> 26x26x5
    #Max Pooling: 26x26x5 -> 13x13x5
    l1=F.Linear(13*13*5,n_units),        #中間層:14x14 -> 中間層: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で過学習を防ぐ
    h0=F.max_pooling_2d(F.relu(model.conv1(x)),2)
    h1=F.dropout(F.relu(model.l1(h0)),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))

基本的に前回のものと同じで、Modelとdef forwardの部分に畳み込みとMaxプーリングが追加されているだけです。

model=chainer.FunctionSet(            #層の定義
    conv1=F.Convolution2D(1,5,3,pad=0),#畳み込み: 28x28x1 -> 26x26x5
    #Max Pooling: 26x26x5 -> 13x13x5
    l1=F.Linear(13*13*5,n_units),        #中間層:14x14 -> 中間層: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で過学習を防ぐ
    h0=F.max_pooling_2d(F.relu(model.conv1(x)),2)
    h1=F.dropout(F.relu(model.l1(h0)),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

今回は畳み込みはパターンが5つで、縦横3pxでストライドとパディングなしです。ストライドは畳み込みをどれくらいスライドして計算するかを指定するパラメタです。通常は1つずつ移動するため1です。パディングは画像外枠に追加するピクセル数です。畳み込みの指定は

Convolution2D(入力のパターン数,畳み込みのパターン数,縦横のpx(,ストライド,パディング))

となります。3×3で畳み込むため外枠2pxが失われ、26×26の画像が出力されます。パターンが5なので、計26x26x5個の値が出力されるわけです。

次にストライド2で最大プーリングを行うため、縦横が半分になります。よって、13x13x5が出力されます。その後、13x13x5からn_units個の中間層を通し、数字の数10の出力を行い数字の判定を行います。実行結果は以下のようになります。

load MNIST dataset
train data=63000
test data=7000
epoch: 1
train mean loss: 0.443299
test accuracy: 0.948143
epoch: 2
train mean loss: 0.219795
test accuracy: 0.962143
epoch: 3
train mean loss: 0.167203
test accuracy: 0.968571
epoch: 4
train mean loss: 0.139541
test accuracy: 0.971286
epoch: 5
train mean loss: 0.120264
test accuracy: 0.976429
epoch: 6
train mean loss: 0.109857
test accuracy: 0.975286
epoch: 7
train mean loss: 0.099303
test accuracy: 0.978571
epoch: 8
train mean loss: 0.092846
test accuracy: 0.976714
epoch: 9
train mean loss: 0.089421
test accuracy: 0.980714
epoch: 10
train mean loss: 0.079648
test accuracy: 0.981286
epoch: 11
train mean loss: 0.075835
test accuracy: 0.981429
epoch: 12
train mean loss: 0.073071
test accuracy: 0.980429
epoch: 13
train mean loss: 0.070031
test accuracy: 0.981143
epoch: 14
train mean loss: 0.067061
test accuracy: 0.980857
epoch: 15
train mean loss: 0.065399
test accuracy: 0.981714
epoch: 16
train mean loss: 0.060900
test accuracy: 0.983714
epoch: 17
train mean loss: 0.062221
test accuracy: 0.983286
epoch: 18
train mean loss: 0.057714
test accuracy: 0.984000
epoch: 19
train mean loss: 0.055848
test accuracy: 0.983429
epoch: 20
train mean loss: 0.054918
test accuracy: 0.982857

前回の結果(Chainerを使って多層パーセプトロンを実装する)と比べてみると、1%ほど正答率がよくなっていることがわかります。ただし、その分、学習時間もかかりますが。

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