しぷぜん

しぷぜん

素人プログラマなおいがStep Zero to Oneしていくブログ

畳み込みニューラルネットワークでウォーリーを探そうと思う(その2)

にほんブログ村 IT技術ブログ Pythonへ

こんにちは、なおいです。

あるきっかけがあり画像認識に関する機械学習の勉強を始めたので、折角だから楽しいことしようと今回は私が子供頃に熱中した絵本の「ウォーリーを探せ」の鬼畜なページを攻略することを目標に畳み込みニューラルネットワーク(CNN)を組んでいこうかと思います。

前回は、データを探そうと思ったらネットに落ちてなくて本を買った所までを書かせていただきました。今回は、買った本から教師用データを集めて学習をさせていこうと思います。

 

その前に、前の記事はこちら

ct-innovation01.hateblo.jp

 

 

 

 

 

教師用データを集める

 まずは、私が実際に購入した本からウォーリーを探して教師用データを集めていきます。あ、これが機械学習のエンジニアがその道のプロになるという所以かなとかくだらないことを考えつつ、ウォーリーを探しては、携帯で写真を撮り、撮った画像をPCに送ってウォーリーの所だけを切り抜いて適当なサイズに保存していく作業を行いました。

 

おかげで、ウォーリーの画像が携帯の写真フォルダを埋め尽くしており、他の人が見たら気持ち悪いぐらいウォーリー好きな人になってます。

ともかく順調に作業をすすめていたのですが、集まっていく内に二つ問題が立ちはだかっていることに若干の不安を覚え始めました。

 

問題その1:画質粗い問題

たくさんの教師データがあった方がイイだろうという事と財布との相談の結果、今まで販売されている全ての本を一冊にまとめた本で他のとおんなじぐらいの値段だという事で、この本を購入しました。

 

が、これ一つだけ問題がありまして、、見開きで大体、原本の1/4ぐらいのサイズに縮小されているんです。

つまり、画質が粗い! 

 

はい。ケチった私が悪いんです。(でも、遊びの為に1500円の本5-6冊も買えない)

 

これは教師データとしてはやってしまったな感が既に否めませんが、買ってしまったものは仕方ないのでこれで強行していきます。

ま、まあプーリングを最初からしてもらってるとポジティブに考えて、プーリング層をなくすなりして、学習側で飲み込んでいくしかない。

 

問題その2:あれウォーリー顔違くね?問題

どちらかというとこっちの方が大きな問題です。

タイトルの通りなんですが、よく漫画とかで初期のころと最後の方で絵のタッチとかが変わることってあると思うんですが、それと似たような現象がウォーリーを探せにも起こっています。

 

機械学習をさせる側にしては大きな問題ですよ、これ。

いくら表記ゆれを考慮する為にプーリングをかけてもそれを超えるレベルで絵柄が変わられたらどうしようもないですから。

 

 

 

ちなみに抜き出したデータをいくつか貼りますがこんな感じ。 

f:id:ct-innovation01:20171027150642p:plain f:id:ct-innovation01:20171027150657p:plain f:id:ct-innovation01:20171027153714p:plain

 

あれ?ウォーリー顔違くない????ってなりました。

あと、前回の記事でネットで見つけたのが以下の画像なのですが比較するとわかりますが、ほとんど正面見てやがらないこいつ・・・・

 

f:id:ct-innovation01:20171026090425p:plain

 

まだデータを集め始めたばかりなのに、早々に心を折ってくるとは、ウォーリーさん流石です。しかし、2000円使っちゃったので泣き言も言ってられません。

 

どのライブラリを使うか

機械学習を行うにあたってPythonを使うことは決めていましたが、まだライブラリを選択していませんでした。機械学習を行うためによく使われているライブラリで画像に強そうなものは下に羅列したものが有名です。

  • TensorFlow
  • Chainer
  • Caffe
  • Pylearn2

今回は、Chainerを選択することにしました。

なぜ、Chainerにしたか

どのライブラリも非常に強力で素晴らしいのですが、私のスキルとPCの都合なども鑑みて考慮した結果、以下の2つを理由にChainerに決めました。ただ、いつかTensorFlowの環境を構築して機械学習に挑戦はしてみたいです。

1.インストールが簡単

 他のライブラリの多くは、依存するライブラリがいくつもあったりしてそもそも機械学習を始めるまでにいろんなハードルを越えなきゃいけないのに対し、Chainerはさくっとインストールしてすぐに作業が始められると聞いた。

2.記法が直観的でわかりやすい

どのようにネットワークを構築していくのか(入力層と出力層の設定)などが非常にシンプルにわかりやすく書けるので、プログラム2年生の私でも書けそう。

 

ということで、とりあえずChainerを使うことに決めたのでインストールを手短に済ませて(本当に簡単に終わった。)作業に入っていきます。

何層のCNNにするか

以下のサイトから少し画像をお借りして、どうするか考えてみます。

一般的かどうかは分からないがよくこんな形で畳み込み層とプーリング(サブサンプリング)層を組合せてから全結合してクラス分類をする画像がよく見つかりますね。

f:id:ct-innovation01:20171030133054j:plain

PARsE | Education | GPU Cluster | Efficient mapping of the training of Convolutional Neural Networks to a CUDA-based cluster

私も、例に倣いこの形で行こうかと思ったいましたが先ほどの教師用データの画像、切り抜いた段階で60×60pxにした。これ以上プーリング(サブサンプリング)を行うと画像がつぶれすぎるのでは?と感じたので、ここからプーリング層を外して実装することにしました。(絵で言うところの紫の変換を無くします。)

教師用データはどれぐらい用意したか

正解データであるウォーリーの画像の切り抜きは約20枚、不正解のデータは適当にネットから約60枚用意しました。結構少ないですね。ダメだったら増やしていきます。

 

実際のプログラム

御託はいいから早く見せろという方お待たせしました。それではプログラム(抜粋)を書いていきます。

クラスの作成

ニューラルネットワークのクラスを作り込むことにします。

# coding=utf-8
from chainer import Chain
import chainer.links as L
import chainer.functions as F


class MyCNN(Chain):
    def __init__(self,  n_out):
        super(MyCNN, self).__init__(
            conv1=L.Convolution2D(in_channels=1, out_channels=10,
                    ksize=5, stride=1, pad=2), conv2=L.Convolution2D(in_channels=None, out_channels=20,
                    ksize=5, stride=1, pad=2), l1=L.Linear(None, 500), l2=L.Linear(500, 500), l3=L.Linear(500, n_out) ) def forward(self, x): h1 = self.conv1(x) h2 = self.conv2(h1) h3 = F.relu(self.l1(h2)) h4 = F.relu(self.l2(h3)) out = self.l3(h4) return out 

入力層と出力層を合わせて6層のディープラーニングを行うことにします。

それぞれの層でどういう事をしてほしいか書くだけなので結構分かりやすい。

 

ただ、conv1とconv2ってわざわざわける意味ない気がとてもする。プーリングしてないんだからこれまとめてもいいかなぁ?

あと、forwardかけるときのh1とh2を何も考えず、恒等関数として処理したけどいいのかな?ダメだったら、考え直すことにしよう。

 

学習をさせる

ここは、いろんなサイトで書いてある通りの記述で進めてみようと思います。

気になった点とか、カスタマイズしたいところが出てきたら直していくことにして、以下の感じで記述してみました。

model = cnn.MyCNN(2)
optimizer = optimizers.Adam()
optimizer.setup(model)

n_epoch = 40
batch_size = 30
for epoch in range(n_epoch):
    sum_loss = 0
    sum_accuracy = 0
    perm = np.random.permutation(N)
    for i in range(0, N, batch_size):
        x = Variable(x_train_data_reshape[perm[i:i+batch_size]])
        t = Variable(t_train_data_np[perm[i:i+batch_size]])
        y = model.forward(x)
        model.zerograds()
        loss = F.softmax_cross_entropy(y, t)
        acc = F.accuracy(y, t)
        loss.backward()
        optimizer.update()
        sum_loss += loss.data*batch_size
        sum_accuracy += acc.data*batch_size
    print("epoch: {}, mean loss: {}, mean accuracy: {}".format(epoch, sum_loss/N, sum_accuracy/N))

serializers.save_npz("mymodel.npz", model)

実際は、このほかに画像をデータにする部分や正規化する部分などあるが割愛してます。恐らく重要ではないと勝手に判断したのと、とてもプログラム長くなるので・・・

 

このプログラムを稼働させて損失率のプロットをして言った結果が以下の通りでした。

 f:id:ct-innovation01:20171031171744p:plain

結構早い段階で収束してました。次回はこれで、実際に判別できるかテストしてみます。では。