umi no mono tomo

海の物とも山の物ともつかないものを、1.計測して、 2.モデルを構築し, 3.現象をよく説明する法則を数学的手法を用いて記述することにより, 対象から有用な情報を取り出しもって情報技術の活用を促進することをねらう.

chainer(v1.14.0)で画像分類をはじめるための覚書

2016/09/04

環境:Ubuntu14.04(LTS), Chainer(v1.14.0), CUDA(7.5), python2.7


先人達の記事に従いchainerで画像分類を始める際、バージョンの違いによって変更が必要な点があったため記述する。

基本的な手順は、①

d.hatena.ne.jp

に従い、適宜②

hi-king.hatenablog.com
を参考にした。




①の著者が作成したコードをgit cloneする。

$ cd ~/work
$ git clone https://github.com/shi3z/chainer_imagenet_tools.git
$ cd chainer_imagenet_tools


次にCaltech 101の画像データをダウンロードする。
101種類のカテゴリにつき、およそ300 x 200pxの画像データが40〜800枚収められている。

$ wget http://www.vision.caltech.edu/Image_Datasets/Caltech101/101_ObjectCategories.tar.gz
$ tar xzvf 101_ObjectCategories.tar.gz

①の著者によるスクリプトを実行。

$ python make_train_data.py 101_ObjectCategories

画像データを集めたimagesディレクトリと、train.txt, test.txt, label.txtの3ファイルが出力される。
前2ファイルはそれぞれ学習データとテストデータを決定しており、画像データのフルパスとクラスのラベル(数値)を1行に収めたものが画像の枚数分並べられている。
label.txtは元データのフォルダ名を元にクラスラベルを1行ごとに並べたもの。



次に画像データを255*255pxにリサイズする。
git cloneしたchainer_imagenet_toolsフォルダに収められているcrop.pyを使用する。
crop後のディレクトリを作成した上で、(9/4追記)
引数に元データのディレクトリとcrop後のディレクトリを指定する。

$ python crop.py images cropimages

train.txtとtest.txtに対して、画像のパスを修正した。(geditで開いて置換images→cropimages)



平均画像を作成する。
chainerのexampleに収められているcompute_mean.pyを使用する。
chainerはv1.14.0をインストールした際にgit clone したディレクトリがあったが、環境を合わせるためv1.5.0のソースコードをダウンロードした。
Release v1.5.0 · pfnet/chainer · GitHub
こちらの下部のリンクからzipをダウンロードして展開。
以下を実行する。

$ cd ~/work/chainer_imagenet_tools
$ python chainer-1.5.0/examples/imagenet/compute_mean.py train.txt

カレントフォルダにmean.npyが出力される。



学習させる。
chainer_imagenet_toolsフォルダに収められているtrain_imagenet.pyを使用する。
ここでの変更点は以下の通り。
train_imagenet.py
283行目

o.write(c.build_computational_graph((loss,), False).dump())

コメントアウトし、

o.write(c.build_computational_graph((loss,), True).dump())

を追加。(build_computational_graphの引数として、v1.14.0ではFalseが廃止されている。)

また、
310行目

pickle.dump(model, open('model', 'wb'), -1)

コメントアウトし、

serializers.save_hdf5('modelhdf5', model)

を追加。
合わせて29行目あたりに

from chainer import serializers

を追加した。

さらに、91行目の

optimizer.setup(model.collect_parameters()) 

を、

optimizer.setup(model)

に置き換える。(v1.5以降はFunctionSetが廃止されているため。)


次に、同ディレクトリ内のnin.pyについて、以下のように構成を置き換えた。

import math

import chainer
import chainer.functions as F
import numpy as np
import chainer.links as L

class NIN(chainer.Chain):

    """Network-in-Network example model."""

    insize = 227

    def __init__(self):
        w = math.sqrt(2)  # MSRA scaling
        super(NIN, self).__init__(
            mlpconv1=L.MLPConvolution2D(
                3, (96, 96, 96), 11, stride=4, wscale=w),
            mlpconv2=L.MLPConvolution2D(
                96, (256, 256, 256), 5, pad=2, wscale=w),
            mlpconv3=L.MLPConvolution2D(
                256, (384, 384, 384), 3, pad=1, wscale=w),
            mlpconv4=L.MLPConvolution2D(
                384, (1024, 1024, 1000), 3, pad=1, wscale=w),
        )
        self.train = True

    def forward(self, x_data,y_data,train=True):
        x = chainer.Variable(x_data, volatile=not train)
        t = chainer.Variable(y_data, volatile=not train)

        h = F.max_pooling_2d(F.relu(self.mlpconv1(x)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv2(h)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv3(h)), 3, stride=2)
        h = self.mlpconv4(F.dropout(h, train=self.train))
        h = F.reshape(F.average_pooling_2d(h, 6), (x.data.shape[0], 1000))

        return F.softmax_cross_entropy(h, t), F.accuracy(h, t)

    def predict(self, x_data):
        x = chainer.Variable(x_data, volatile=True)

        h = F.max_pooling_2d(F.relu(self.mlpconv1(x)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv2(h)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv3(h)), 3, stride=2)
        h = self.mlpconv4(F.dropout(h, train=False))
        h = F.reshape(F.average_pooling_2d(h, 6), (x.data.shape[0], 1000))
        return F.softmax(h)

元ファイルでは12層の畳み込み層と4層のプーリング層が定義されていたが、学習後にinspection.pyにより画像分類を実行した際に構成が合わないようであったため、やむなくchainerv1.5.0のexamplesに収録されたnin.pyの構成を、整合するように再現した。今後理解を深めた暁には原著者①の実装を試してみたい。



以上の変更を行い、学習ループを実行する。

$ python train_imagenet.py  -g 0 -E 10 train.txt test.txt 2>&1 | tee log

筆者の環境(NVIDIA GeForce GTX 750 Ti 使用)では約13分を要した。
LOGによると1秒当たり約94枚を処理しているようだ。




最後に、学習が完了したモデルを用いて画像分類をする。
①の著者による画像を255*255にリサイズするスクリプトresize.pyで画像をリサイズする。
8行目の

os.mkdir("resized")

コメントアウトし、代わりに手動でresizedディレクトリを作成した。

画像分類スクリプトinspection.pyに関しては、
73行目

serializers.HDF5Deserializer("model", model)

コメントアウトし、

serializers.load_hdf5("modelhdf5", model)

を追加した。

画像をリサイズし、分類スクリプトに渡す。
今回はこちら
http://www.trago.co.uk/ekmps/shops/tragoshop/images/wk-650i-motorbike-35kw-restricted-version-for-a2-license-available-white-blue-35677-p.jpg
http://www.trago.co.uk/ekmps/shops/tragoshop/images/wk-650i-motorbike-35kw-restricted-version-for-a2-license-available-white-blue-35677-p.jpg

を使用させていただいた。

$ python resize.py bike.jpg
$ python inspection.py resizedBike.jpg

次にその結果を示す。

#1 | Motorbikes | 85.5%
#2 | scorpion | 3.8%
#3 | joshua_tree | 3.6%
#4 | butterfly | 1.6%
#5 | watch | 1.4%
#6 | crayfish | 0.8%
#7 | cannon | 0.7%
#8 | lobster | 0.4%
#9 | cellphone | 0.3%
#10 | wild_cat | 0.3%
#11 | pigeon | 0.2%
#12 | bonsai | 0.1%
#13 | gramophone | 0.1%
#14 | octopus | 0.1%
#15 | wheelchair | 0.1%
#16 | pagoda | 0.1%
#17 | stegosaurus | 0.1%
#18 | hawksbill | 0.1%
#19 | BACKGROUND_Google | 0.1%
#20 | helicopter | 0.1%

Motorbikesが85%という結果が出た。
鮮明な写真ではあったが、これだけの精度が得られることは興味深い。
今後応用範囲を拡大していきたい。



謝辞
①および②の著者の方に、この場を借りてお礼申し上げます。
chainer開発者の方へも最大限の感謝を。