Deep Learning でアニメ (ゆるゆり) キャラクターを識別する(2015/11/4少し追記)

注意(2015/11/4)

最近アクセスが増えきたので追記です.
Chainerのgpu周りの仕様変更により,公開しているコードではgpuモードでは動きません. 以下の記事等を参考にして,書きなおすことで動くかと思います.
chainerでcuda.initはもう使えない
また,公開している学習済みデータはgpuモードで保存しているため,使用することができません.

書いてる人

情報系修士2年.
画像認識の研究をしています.
研究でPython, Numpyを使っています.
Deep Learningについてなんとなく理解したので,実行してみたい人向きに書きます.

背景・目的

最近Deep Learningが流行っています.
研究室でもDeep Learningの輪講が始まりました.
せっかく勉強したので使ってみたいと考えました.
ちなみに輪講で使ってる教科書はこちらになります.

本記事では,Deep LearningのライブラリであるChainerを用いて,ゆるゆりごらく部キャラ4人 (京子,あかり,結衣,ちなつ) + それ以外を深層畳み込みニューラルネットワークで識別します.

関連エントリ

[1]では,Chainerによるmnist (手書き文字のデータセット) を学習するサンプルプログラムの解説をしています.
今回はこのサンプルプログラムをベースにしました.

[2], [3]では,animefaceでアニメキャラの顔画像を切り出し,Caffeを使って識別をしています.
データセットの規模などを参考にさせていただきました.
また,この記事で行うゆるゆりキャラの識別でも,顔画像をanimefaceで切り出します.

準備

以後,OSはMac OS X Yosemite 10.10.4を用いますが,Ubuntuでも似たようなプロセスかと思います.
まずはChainerをインストールします.

[bash gutter="False"]$ pip install chainer[/bash]

CPUを使わない場合は,これだけです. GPUを使う場合はCudaをインストールする必要があります.
私は以前,公式からpkg版をインストールしていたのでそれを用いました.
~/.bash_profileに

[bash gutter="False"]

for Cuda

export CUDA_ROOT=/Developer/NVIDIA/CUDA-7.0/ export PATH=$PATH:/Developer/NVIDIA/CUDA-7.0/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/Developer/NVIDIA/CUDA-7.0/lib export CPATH=$CPATH:/Developer/NVIDIA/CUDA-7.0/include export CUDA_INC_DIR=/Developer/NVIDIA/CUDA-7.0/bin:$CUDA_INC_DIR export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/Developer/NVIDIA/CUDA-7.0/lib [/bash]

という記述があったので,もしかすると必要かもしれません.
cudaをインストールしたら,

[bash gutter="False"]$ pip install chainer-cuda-deps[/bash]

で,gpu modeに必要なライブラリがインストールされます.
エラーが出る場合はpycudaのみ個別でインストールしてから上記を実行すると良いようです.

その他,OpenCVやNumpyを用いますが,わたしの環境では元から入っていたため割愛します.

実装

ソースの全文はここにあります.
本記事では,データの読み込み部分,ニューラルネットの構造部分,学習したモデルの保存部分について説明します.
それ以外の部分は,mnist_train.pyとほとんど同じなので,[1]に解説が詳しくのっています.

では,まずデータの読み込み部分です.

[python gutter="False"]

データセットの読み込み

x_train = x_test = y_train = y_test =

for i in range(0,5):

images = os.listdir('./'+str(i))

if i == 0:
    image_num = other_num
else:
    image_num = yuruyuri_num

for j in range(len(images)):

    if j == image_num:
        break

    if images[j].find('.png') > 0 or images[j].find('.jpg') > 0 or images[j].find('.jpeg') > 0:

        #ラスト100枚はテストデータ
        if image_num - j > 100:
            x_train.append(cv2.imread('./'+str(i)+'/'+images[j]))
            y_train.append(i)
        else:
            x_test.append(cv2.imread('./'+str(i)+'/'+images[j]))
            y_test.append(i)

[/python]

./0/フォルダに入ったother_num枚の画像と,
./1/ ~ ./4/フォルダに入ったyuruyuri_num枚ずつの画像を読み込んでいます.
各フォルダに入った最後の100枚はテスト用データとして,それ以外は学習用データとして読み込んでいます.
フォルダ名はクラスに対応しており,それによって正解ラベルのデータも作成します.

そして以下のコードで,読み込んだデータを0 〜 1に正規化し,ニューラルネットに突っ込める形式のデータに変換しています.

[python gutter="False"]

読み込んだデータを0~1に正規化,numpy.arrayに変換

x_train = np.array(x_train).astype(np.float32).reshape*1 / 255 y_train = np.array(y_train).astype(np.int32) x_test = np.array(x_test).astype(np.float32).reshape*2 / 255 y_test = np.array(y_test).astype(np.int32) N = len(y_train) N_test = len(y_test) [/python]

続いてニューラルネットの設定です.

[python gutter="False"]

モデルの設定 or 保存されたモデルの読み込み

if args.model != 0: if args.gpu >= 0: cuda.init(args.gpu) model = pickle.load(open(args.model,'rb')) else: model = chainer.FunctionSet(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, 5)) if args.gpu >= 0: cuda.init(args.gpu) model.to_gpu()

伝播の設定

def forward(x_data, y_data, train=True): x, t = chainer.Variable(x_data, volatile=not train), chainer.Variable(y_data, volatile=not train) 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=train) y = model.l4(h)

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

[/python]

mnist_trainを少し改良し,--modelオプションで保存済みモデルを指定すると,その続きから学習できるようにしました.
上記の設定はConvolution (畳み込み) → Max Pooling → Convolution → Max Pooling → 全結合 → 全結合という構造になっています.
ニューラルネットをどのように設計するのが良いかという知識をまだほとんど持っていないので,わりと適当な設計になっています.
なお,畳み込みニューラルネットワークの詳しい説明は[4]が,引数等の詳しい説明はChainerの公式ドキュメントがおすすめです.

最後に,モデルの保存についてです.

[python gutter="False"] pickle.dump(model, open('model'+str(epoch), 'wb'),-1) [/python]

上記のコードで各ループでのモデルを保存しています.

実験

データセットの収集は,Webサイトのクローリングによって半自動半手動で行いました.
ゆるゆりキャラ以外の顔画像は27000枚集めました.
ゆるゆりごらく部キャラの画像は300枚ずつ集め,回転,輝度変化で1200枚ずつに水増ししました.
集めた画像は50 × 50 pixelsにリサイズしました. 以下が実行結果になります.

[bash collapse="True" gutter="False"] ('epoch', 1) graph generated train mean loss=0.382325568614, accuracy=0.890312456943 test mean loss=1.00135197937, accuracy=0.573999983072 ('epoch', 2) train mean loss=0.268367117841, accuracy=0.913157367944 test mean loss=0.947610852122, accuracy=0.641999988258 ('epoch', 3) train mean loss=0.22080623009, accuracy=0.927886743672 test mean loss=0.772928168997, accuracy=0.751999992132 ('epoch', 4) train mean loss=0.19043841186, accuracy=0.939516880309 test mean loss=0.740829292685, accuracy=0.767999982834 ('epoch', 5) train mean loss=0.178433648226, accuracy=0.941817346027 test mean loss=0.927542027272, accuracy=0.735999983549 ('epoch', 6) train mean loss=0.151921003329, accuracy=0.950252390321 test mean loss=0.722337770741, accuracy=0.79999999404 ('epoch', 7) train mean loss=0.141714127697, accuracy=0.953191875707 test mean loss=0.636394675635, accuracy=0.799999988079 ('epoch', 8) train mean loss=0.13134281501, accuracy=0.955715998342 test mean loss=0.786456226092, accuracy=0.759999978542 ('epoch', 9) train mean loss=0.121130242886, accuracy=0.959805715586 test mean loss=0.730020922609, accuracy=0.789999985695 ('epoch', 10) train mean loss=0.115170597695, accuracy=0.961818623744 test mean loss=0.76192834489, accuracy=0.781999987364 ('epoch', 11) train mean loss=0.112201624817, accuracy=0.961818625458 test mean loss=0.892196842562, accuracy=0.78599998951 ('epoch', 12) train mean loss=0.101167324128, accuracy=0.965716636873 test mean loss=0.896606721275, accuracy=0.785999983549 ('epoch', 13) train mean loss=0.0983253206952, accuracy=0.964885913653 test mean loss=0.973410349432, accuracy=0.779999965429 ('epoch', 14) train mean loss=0.0904322106755, accuracy=0.968528319254 test mean loss=0.88052435412, accuracy=0.797999984026 ('epoch', 15) train mean loss=0.0897226472203, accuracy=0.96872002423 test mean loss=0.704698138358, accuracy=0.831999987364 ('epoch', 16) train mean loss=0.0789265701088, accuracy=0.972266576771 test mean loss=1.08381982204, accuracy=0.783999991417 ('epoch', 17) train mean loss=0.0785963144729, accuracy=0.97149975477 test mean loss=0.982318630628, accuracy=0.813999986649 ('epoch', 18) train mean loss=0.0768084819751, accuracy=0.972905596725 test mean loss=0.937159282516, accuracy=0.82999997735 ('epoch', 19) train mean loss=0.0725439805666, accuracy=0.973640467547 test mean loss=0.760187698994, accuracy=0.827999991179 ('epoch', 20) train mean loss=0.0722000133739, accuracy=0.975429719074 test mean loss=0.899688624265, accuracy=0.82999997735 ('epoch', 21) train mean loss=0.0683553470054, accuracy=0.976100689729 test mean loss=1.32481883196, accuracy=0.769999992847 ('epoch', 22) train mean loss=0.0592013735982, accuracy=0.980094555152 test mean loss=1.13702108954, accuracy=0.799999982119 ('epoch', 23) train mean loss=0.0649024790409, accuracy=0.977666285862 test mean loss=1.38833562732, accuracy=0.795999979973 ('epoch', 24) train mean loss=0.0554869495607, accuracy=0.980701622311 test mean loss=1.17045585215, accuracy=0.785999983549 ('epoch', 25) train mean loss=0.0659726427763, accuracy=0.97661190354 test mean loss=1.32181257887, accuracy=0.795999979973 ('epoch', 26) train mean loss=0.0547879658307, accuracy=0.980254309331 test mean loss=1.20517010959, accuracy=0.817999982834 ('epoch', 27) train mean loss=0.0546843235179, accuracy=0.981308690705 test mean loss=1.33216540692, accuracy=0.759999987483 ('epoch', 28) train mean loss=0.0512838833035, accuracy=0.982107464167 test mean loss=1.29430849315, accuracy=0.805999988317 ('epoch', 29) train mean loss=0.0572386062422, accuracy=0.979998702568 test mean loss=1.30511561837, accuracy=0.811999976635 ('epoch', 30) train mean loss=0.0510555069498, accuracy=0.981851856928 test mean loss=2.07382414509, accuracy=0.72999997437 ('epoch', 31) train mean loss=0.051815171524, accuracy=0.982011610345 test mean loss=1.50888606668, accuracy=0.795999985933 ('epoch', 32) train mean loss=0.0540901193599, accuracy=0.982458925131 test mean loss=1.43951486209, accuracy=0.803999996185 ('epoch', 33) train mean loss=0.043401594674, accuracy=0.984567686063 test mean loss=1.40570185685, accuracy=0.825999987125 ('epoch', 34) train mean loss=0.0484548595812, accuracy=0.982906237537 test mean loss=1.71314607875, accuracy=0.773999994993 ('epoch', 35) train mean loss=0.0425332464194, accuracy=0.984727440909 test mean loss=1.73361943261, accuracy=0.787999987602 ('epoch', 36) train mean loss=0.0511914892732, accuracy=0.982938188811 test mean loss=2.08511585887, accuracy=0.787999981642 ('epoch', 37) train mean loss=0.042715253547, accuracy=0.984695490587 test mean loss=1.7251040373, accuracy=0.785999983549 ('epoch', 38) train mean loss=0.0423173309936, accuracy=0.985941576553 test mean loss=1.43348839213, accuracy=0.809999978542 ('epoch', 39) train mean loss=0.055374260661, accuracy=0.982171365857 test mean loss=2.01557974137, accuracy=0.80799998045 ('epoch', 40) train mean loss=0.0450710924592, accuracy=0.984535734313 test mean loss=1.73796692197, accuracy=0.797999978065 ('epoch', 41) train mean loss=0.0367919391385, accuracy=0.986580595935 test mean loss=1.78684045997, accuracy=0.80799998641 ('epoch', 42) train mean loss=0.0409438465545, accuracy=0.985749871386 test mean loss=1.94070018663, accuracy=0.787999987602 ('epoch', 43) train mean loss=0.0421861674889, accuracy=0.985015000944 test mean loss=1.62801815795, accuracy=0.833999991417 ('epoch', 44) train mean loss=0.0385228527809, accuracy=0.986708399221 test mean loss=1.54301969217, accuracy=0.805999988317 ('epoch', 45) train mean loss=0.035483331595, accuracy=0.98763497702 test mean loss=2.08613687656, accuracy=0.785999983549 ('epoch', 46) train mean loss=0.0449592209171, accuracy=0.98539841185 test mean loss=1.63998659118, accuracy=0.799999982119 ('epoch', 47) train mean loss=0.0336347718618, accuracy=0.98872130995 test mean loss=2.52422140222, accuracy=0.777999997139 ('epoch', 48) train mean loss=0.0426796265883, accuracy=0.986484743351 test mean loss=1.73707109559, accuracy=0.77999997735 ('epoch', 49) train mean loss=0.0375382995944, accuracy=0.987283515579 test mean loss=1.98181401251, accuracy=0.785999983549 ('epoch', 50) train mean loss=0.0453394246744, accuracy=0.98450378523 test mean loss=1.87152290804, accuracy=0.813999986649 [/bash]

15回目でテストデータに対する正解率が80%を超えて以降,上がったり下がったりしています.
過学習なんでしょうか.

実データへの適用

animefaceで顔画像を切り出し,学習したモデルを使って識別するプログラムを書いてみました.
ソースはこちらで公開しています.
モデルは今回学習したモデルの中でテストデータ対するlossが小さく,accuracyの小さい15番目のものを用いています.
以下,識別結果です.

1

2

3

今回用いた実データでは,animefaceで切り出せなかったもの以外は正しく識別できているようです.

まとめと今後の課題

Chainerを用いて,ゆるゆりごらく部キャラ4人 (京子,あかり,結衣,ちなつ) + それ以外を深層畳み込みニューラルネットワークで識別しました.
今後は,識別精度の向上のため,平均0,分散1にする正規化,PCA or ZCA白色化などを試してみたいと考えています.

参考

[1]【機械学習】ディープラーニング フレームワークChainerを試しながら解説してみる
[2]Deep Learningでラブライブ!キャラを識別する
[3]ご注文はDeep Learningですか?
[4]「深層学習」第6章 畳込みニューラルネット

*1:len(x_train),3, 50, 50

*2:len(x_test),3, 50, 50