MATHGRAM

主に数学とプログラミング、時々趣味について。

python: chainerを使って化物語キャラを認識させるよ! 〜part3 暦orNOT暦で2値分類(モデル作成&学習編)〜

ようやくpart3でございます。

-- 目次 --

まずは2値分類してみる。

ここから主要キャラの分類

正直、2値分類をやる必要はないのかもしれませんが、
まずはどんなもんなのか結果が早く見たいのでとりあえずってことで。

コード

先にコードを載せます。
まずは作ったモデル部分。clf_bake_model.pyとして保存しています。

#coding:utf-8

import os

import chainer
from chainer import optimizers
import chainer.functions as F
import chainer.links as L
import chainer.serializers as S

import numpy as np

#chainerからChainを継承してクラスを作成します。
class clf_bake(chainer.Chain):
    def __init__(self):

        super(clf_bake, self).__init__(
            # ※1. このネットワークの部分は後々改善させます。
            conv1 =  F.Convolution2D(3, 16, 5, pad=2),
            conv2 =  F.Convolution2D(16, 32, 5, pad=2),
            l3    =  F.Linear(6272, 256),
            l4    =  F.Linear(256, 2) #最終的に2クラスに分類させるため出力は2
        )

    def clear(self):
        self.loss = None
        self.accuracy = None

    def forward(self, X_data, y_data, train=True):
        self.clear() #初期化
        X_data = chainer.Variable(np.asarray(X_data), volatile=not train)
        y_data = chainer.Variable(np.asarray(y_data), volatile=not train)
        h = F.max_pooling_2d(F.relu(self.conv1(X_data)), ksize = 5, stride = 2, pad =2)
        h = F.max_pooling_2d(F.relu(self.conv2(h)), ksize = 5, stride = 2, pad =2)
        h = F.dropout(F.relu(self.l3(h)), train=train)
        y = self.l4(h)
        return F.softmax_cross_entropy(y, y_data), F.accuracy(y, y_data)

軽い解説

今回、モデル自体をクラスとしています。後々、モデルの保存やロードのためにchainerのserializersを使っているのですが、
いまいちこの使い方がわからず(特にload_hdf5)結局こんな感じになっています。

まずメンバーとして今回ネットワークで使う関数を定義しています。

クリアモジュールは初期化を行うだけの簡単な関数。

そしてメインのフォワードモジュール。
ここの部分の作り方で、学習の精度は変わると思うのですが、ここの作り方の明確な方法がよくわからず(経験則ちっくなのかな?)
とりあえず、下記の参考サイトさまと同じようにネットワークの定義をしました。おそらくまず一番最初に取りかかるべき改善点はここになるはず。

最後のF.softmax_cross_entropy(y, y_data)の部分についてはもう少し深い解説をします。

ソフトマックス関数とは?

ソフトマックス関数とは他クラス分類で用いられる関数で以下のように定義されています。


分類クラスの出力をy_k(kはクラスの番号)とし、関数 l4(h) によって得られる出力をu_kとした時、

\displaystyle y_k = \frac{exp(u_k)}{\sum_{j=0}^K exp(u_k)}

をソフトマックス関数とする。

こいつの特徴は、

  • 出力y_kの総和が1になること
  • この層の出力全てを使って定義されている

この2点ですね。
まず1個目の特徴から想像しやすいと思いますが、ソフトマックス関数の出力は確率を表現しています。
一番確率が高かったクラスに画像を分類していく感じです。
2個目の特徴は活性化関数としてソフトマックス関数を見た時の特徴なので、あまり気にしなくてもいいです。

※いやいやこれ2値分類じゃねーか!!と思う方もいるかもしれませんが、2値分類は多クラス分類の部分集合なので大丈夫です。
後々のことも考えてソフトマックス使っちゃってます。

さっそく学習!

以下がbake_train.pyの学習&テスト用のコードです。

#coding: utf-8

#必要なライブラリのインポート
import cv2
import os
import six
import datetime

import chainer
from chainer import optimizers
import chainer.functions as F
import chainer.links as L
import chainer.serializers as S
from clf_bake_model import clf_bake

import numpy as np


#その1 ------データセット作成------

#フォルダは整数で名前が付いています。
#今回0が負例で、1が暦フォルダになっております。
def getDataSet():
    #リストの作成
    X_train = []
    X_test = []
    y_train = []
    y_test = []

    for i in range(0,2):
        #まずは2値分類を目指すので暦フォルダとothersフォルダの中身だけ引っ張ってきます。
        path = "/Users/path/to/dir/" #ここにディレクトリのパスを設定
        imgList = os.listdir(path+str(i))
        #データを4:1の割合でtrainとtestに分けます。
        imgNum = len(imgList)
        cutNum = imgNum - imgNum/5
        for j in range(len(imgList)):
            imgSrc = cv2.imread(path+str(i)+"/"+imgList[j])
            #またimreadはゴミを吸い込んでも、エラーで止まらずNoneを返してくれます。
            #ですので読み込み結果がNoneでしたらスキップしてもらいます。
            if imgSrc is None:continue

            if j < cutNum:
                X_train.append(imgSrc)
                y_train.append(i)
            else:
                X_test.append(imgSrc)
                y_test.append(i)
    return X_train,y_train,X_test,y_test



#その3 ---------学習させる-------

def train():
    #上で作った関数でデータセットを用意します。
    X_train,y_train,X_test,y_test = getDataSet()
    #このままだとchainerで読み込んでもらえないので、array型にします。
    X_train = np.array(X_train).astype(np.float32).reshape((len(X_train),3, 50, 50)) / 255
    y_train = np.array(y_train).astype(np.int32)
    X_test = np.array(X_test).astype(np.float32).reshape((len(X_test),3, 50, 50)) / 255
    y_test = np.array(y_test).astype(np.int32)
    # 諸々の初期設定
    model = clf_bake()
    optimizer = optimizers.Adam()
    optimizer.setup(model)

    epochNum = 5
    batchNum = 50
    epoch = 1

    # 学習とテスト
    while epoch <= epochNum:
        print("epoch: {}".format(epoch))
        print(datetime.datetime.now())

        trainImgNum = len(y_train)
        testImgNum = len(y_test)

        #---学習---
        sumAcr = 0
        sumLoss = 0

        perm = np.random.permutation(trainImgNum)

        for i in six.moves.range(0, trainImgNum, batchNum):
            #ランダムにbatchNumの数だけ抽出する
            X_batch = X_train[perm[i:i+batchNum]]
            y_batch = y_train[perm[i:i+batchNum]]

            optimizer.zero_grads()
            loss, acc = model.forward(X_batch, y_batch)
            loss.backward()
            optimizer.update()

            sumLoss += float(loss.data) * len(y_batch)
            sumAcr += float(acc.data) * len(y_batch)
        print('train mean loss={}, accuracy={}'.format(sumLoss / trainImgNum, sumAcr / trainImgNum))

        #---テスト---
        sumAcr = 0
        sumLoss = 0

        perm = np.random.permutation(testImgNum)

        for i in six.moves.range(0, testImgNum, batchNum):
            X_batch = X_test[perm[i:i+batchNum]]
            y_batch = y_test[perm[i:i+batchNum]]
            loss, acc = model.forward(X_batch, y_batch, train=False)

            sumLoss += float(loss.data) * len(y_batch)
            sumAcr += float(acc.data) * len(y_batch)
        print('test  mean loss={}, accuracy={}'.format(
            sumLoss / testImgNum, sumAcr / testImgNum))
        epoch += 1
        #モデルの保存
        S.save_hdf5('model'+str(epoch+1), model)

train()

なるべくコメントを入れるようにしたので、わかりづらい場合はその辺を参照してください。
この学習の推移はこんな感じです。

epoch: 1
2016-03-21 12:49:10.113708
train mean loss=0.0788115997615, accuracy=0.971096151501
test  mean loss=0.672512343231, accuracy=0.794669000601
epoch: 2
2016-03-21 12:58:13.964969
train mean loss=0.0256145460107, accuracy=0.991072739408
test  mean loss=0.731735554987, accuracy=0.816637370031
epoch: 3
2016-03-21 13:07:38.601048
train mean loss=0.0198095579206, accuracy=0.993999710844
test  mean loss=1.09153083491, accuracy=0.777387220851
epoch: 4
2016-03-21 13:17:16.501081
train mean loss=0.0147453846982, accuracy=0.995682718603
test  mean loss=0.759238675944, accuracy=0.808142935594
epoch: 5
2016-03-21 13:26:54.957321
train mean loss=0.0132983507236, accuracy=0.995755893488
test  mean loss=0.691285667368, accuracy=0.836555356405

やはりGPUを使わないとかなり時間がかかってしまいます。
学習は割と上手く行っています。

epochが少ないですが次のパートでは、この学習済みモデルを使って未知データに適用させてみます。