MATHGRAM

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

python: chainerを使って化物語キャラを認識させるよ! 〜part5.5 主要キャラで多値分類(改良編)〜

予定にありませんでしたがpart5.5でございます。

くそミスのせいでこんなことに。
どうせ5.5として書くならもう一回コードをモデルから張り直して、整理します。

-- 目次 --

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

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

今まで、コードに関しての説明はぼちぼちしてきたのでまずはコード載せていきます。

モデル

#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__(
            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, 10) #ここが10クラスの分類に変わっています。
        )

    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)

続いて学習&テストコード

#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 ------データセット作成------

#フォルダは整数で名前が付いています。
def getDataSet():
    #リストの作成
    X_train = []
    X_test = []
    y_train = []
    y_test = []

    for i in range(0,10):
        path = "/Users/path/to/dir"
        if i == 0:
            #othersは15000枚用意します。テスト用には3000枚
            cutNum = 22000
            cutNum2 = 18000
        else:
            #主要キャラたちは1800枚ずつ。テスト用には300枚
            cutNum = 4000
            cutNum2 = 3600
        imgList = os.listdir(path+str(i))
        imgNum = len(imgList)
        for j in range(cutNum):
            imgSrc = cv2.imread(path+str(i)+"/"+imgList[j])
            #imreadはゴミを吸い込んでも、エラーで止まらずNoneを返してくれます。
            #ですので読み込み結果がNoneでしたらスキップしてもらいます。
            if imgSrc is None:continue
            if j < cutNum2:
                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 = 30
    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

        for i in six.moves.range(0, testImgNum, batchNum):
            X_batch = X_test[i:i+batchNum]
            y_batch = y_test[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()

最後に未知データに対する予想をするコード

# -*- coding: utf-8 -*-
#!/usr/bin/env python
import sys

import numpy as np
import six
import cv2
import os


import chainer
from chainer import computational_graph as c
import chainer.functions as F
import chainer.serializers as S

from chainer import optimizers

from clf_bake_model import clf_bake

#モデルの読み込み
model = clf_bake()
S.load_hdf5('./4layersModel/model26', model)
#model = pickle.load(open('model30','rb'))

#キャラクターの名前
#今回は色だけで分けているので使っていませんが一応
chara_name = ['unknown', "koyomi","hitagi","tsubasa","suruga","nadeko","karen","tsukihi","shinobu","mayoi"]

#伝播の設定
def forward(x_data):
    x = chainer.Variable(x_data, volatile=False)
    h = F.max_pooling_2d(F.relu(model.conv1(x)), ksize = 5, stride = 2, pad =2)
    h = F.max_pooling_2d(F.relu(model.conv2(h)), ksize = 5, stride = 2, pad =2)
    h = F.dropout(F.relu(model.l3(h)), train=False)
    y = model.l4(h)

    return y

#顔検出関数
def detect(image, cascade_file = "/Users/path/to/lbpcascade_animeface.xml"):
    if not os.path.isfile(cascade_file):
        raise RuntimeError("%s: not found" % cascade_file)

    cascade = cv2.CascadeClassifier(cascade_file)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.equalizeHist(gray)

    faces = cascade.detectMultiScale(gray,
                                     scaleFactor = 1.1,
                                     minNeighbors = 1,
                                     minSize = (20, 20))

    print(faces)

    return faces

#検出された顔を識別する関数
def recognition(image, faces):
    face_images = []

    for (x, y, w, h) in faces:
        dst = image[y:y+h, x:x+w]
        dst = cv2.resize(dst, (50, 50))
        face_images.append(dst)

    face_images = np.array(face_images).astype(np.float32).reshape((len(face_images),3, 50, 50)) / 255

    #face_images = cuda.to_gpu(face_images)

    return forward(face_images) , image

#識別結果を描画する関数
def draw_result(image, faces, result):

    count = 0
    for (x, y, w, h) in faces:
        classNum = 0
        result_data = result.data[count]
        classNum = result_data.argmax()
        recognized_class = chara_name[result_data.argmax()]
        if classNum == 0:
                cv2.rectangle(image, (x, y), (x+w, y+h), (255,255,51), 3)
        elif classNum == 1:
                cv2.rectangle(image, (x, y), (x+w, y+h), (0,0,0), 3)
        elif classNum == 2:
                cv2.rectangle(image, (x, y), (x+w, y+h), (255,0,255), 3)
        elif classNum == 3:
                cv2.rectangle(image, (x, y), (x+w, y+h), (255,255,255), 3)
        elif classNum == 4:
                cv2.rectangle(image, (x, y), (x+w, y+h), (255,0,0), 3)
        elif classNum == 5:
                cv2.rectangle(image, (x, y), (x+w, y+h), (153,51,255), 3)
        elif classNum == 6:
                cv2.rectangle(image, (x, y), (x+w, y+h), (0,0,255), 3)
        elif classNum == 7:
                cv2.rectangle(image, (x, y), (x+w, y+h), (0,255,0), 3)
        elif classNum == 8:
                cv2.rectangle(image, (x, y), (x+w, y+h), (0,255,255), 3)
        elif classNum == 9:
                cv2.rectangle(image, (x, y), (x+w, y+h), (0,204,255), 3)

        count+=1

    return image

#ファイル読み込み
img = cv2.imread("test.jpg")

faces = detect(img)

result, image = recognition(img, faces)

image = draw_result(image, faces, result)
cv2.imwrite('out.png',image)

適用結果!

上の一連のコードで学習した結果がこちら。

epoch: 1
2016-03-23 16:07:03.075410
train mean loss=1.0787541097, accuracy=0.62730466466
test  mean loss=1.65130365632, accuracy=0.463928382222
epoch: 2
2016-03-23 16:38:50.100446
train mean loss=0.699703022108, accuracy=0.761089168048
test  mean loss=1.11648284097, accuracy=0.637704062016
epoch: 3
2016-03-23 17:10:51.170715
train mean loss=0.54584181995, accuracy=0.813959945566
test  mean loss=1.25304294185, accuracy=0.637177467181

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

epoch: 28
2016-03-24 06:28:45.132229
train mean loss=0.144246906905, accuracy=0.950086330421
test  mean loss=1.49791400061, accuracy=0.736835175373
epoch: 29
2016-03-24 06:59:53.761350
train mean loss=0.149735492156, accuracy=0.949510785031
test  mean loss=1.41343511337, accuracy=0.754081094199
epoch: 30
2016-03-24 07:31:01.595651
train mean loss=0.141215701596, accuracy=0.951158035467
test  mean loss=1.25496952082, accuracy=0.767509214906

はい。明らかに前回より良くなっていますね。そりゃそうですよ。

まだ収束仕切ってない感がありますが、model30を使ってテストしてみます!

え?時間がえぐいことになってるって?いいのいいの。CPUの限界ですわ。

未知データに適用するよ!

負例が水色
暦が黒、ひたぎが紫、翼が白、真宵がオレンジ、駿河が青、撫子がピンク、忍が黄色、火憐が赤、月火が緑
になってます。

f:id:ket-30:20160324202816p:plainf:id:ket-30:20160324202821p:plainf:id:ket-30:20160324202833p:plainf:id:ket-30:20160324202830p:plainf:id:ket-30:20160324202841p:plainf:id:ket-30:20160324202836p:plainf:id:ket-30:20160324202839p:plain

ん〜翼がダメだこれ。
多分三つ編みverとショートカットverを同じところにぶっ込んでるのがまずいんだろうなあ。

あとやっぱりカスケードが拾ってくる精度が微妙です。
fasterじゃなくてまずはsliding windowから試すべきかな?

まあとりあえず一件落着ってことで。
改良したらまた更新します。