RND.log

Rnd.log is Not a Developer's webLOG

お前の絵柄AIで学習できるぞ~ぱちお顔データセットを作った話&ぱちお絵識別器を作ろう~

こちらはRCC OBOG Advent Calendar 2017 - Adventarの16日目の記事です.
15日目はhoroamaさんで
プリちぃCTF2017開催 · 反吐ログ
でした.

※注意:この記事は重度の身内ネタで構成されています

f:id:RND:20171203011823j:plain:w200



追記:コード上げました
github.com


2019/04/04 追記:

1年くらい研究とお絵かきに追われて放置してた。
flaskを入れてブラウザで動くようにした

opencvとtensorflowが同じコンテナにうまく入らなくて数ヶ月悩んでたけど
opencvにPATHが通ってなかっただけだった

目次

私は何者か

f:id:RND:20171203005645p:plain
HN:RND
TwitterRND.cpp (@RND_cpp) | Twitter
趣味:変なWebアプリを作ること,自然言語処理,お絵かき,ぱちおくん
RCCとの関係:RCC 2013年度入会のOBです。ぱちおくんと同期だよ^^
近況:今は修士1年生で脳の研究をしてます。あと就活(誰か雇って❤️)

お前の絵柄AIで学習できるぞ

そろそろぱちおくんの誕生日ですね。そこでぱちおくんのために最高の記事をプレゼントしたいとおもいます。
今回やったこと

  • ぱちおくんのイラストをDLしまくって顔を切り出したデータセットを作った。
  • DeepにLearningしてぱちおくんのイラストを識別する識別器を作った。
  • 言語はPythonを使ったよ。

ぱちおって誰だよ

ご存知…ないのですか…

f:id:RND:20171203011823j:plain:w100

HN:ぱちお
Twitterぱちお@超ボマス1日目C29,30 (@patioglass) | Twitter
肩書き:神絵師,小説家,歌い手,エンジニア,幼女
RCCとの関係:私と同期のOB(OGという説もある),愛すべき存在

  • RCCのコンテンツメーカーもといコンテンツそのもの
  • 神絵師
  • 高校生の時に書いた黒歴史小説が世代をまたいで事あるごとにフリー素材にされてる


ぱちお君についての詳細は以下に
ぱちお's Blog:
patioglass.hatenablog.com

ぱちおと私

世の中に ぱちおくんの素晴らしさを広めるため,そして愛すべきぱちおくんを愛するためのアプリの開発に執心しています。
RCC部員とぱちおくんのツイートを解析した話(3年前)
www.rcc.ritsumei.ac.jp
ぱちおくんのツイートから学習したベイズフィルタを使って入力文のぱちお度を出力するアプリ
patio-check.rndlog.net
ぱちおくんのツイートをノムリッシュ翻訳して呟くbot(休止中)
twitter.com

今回やった事の詳細 (ぱちおくんを知ってる方はここから読んで大丈夫です)

ぱちお顔データセットを作った話

ぱちおくんのイラスト(以下,ぱちお絵)をTwitter(2015年夏以降)とPixiv(2013年~2015年春)からDLしまくってきました。多少重複があると思いますが800枚くらいありました。

目的:イラストのぱちおらしさを算出したい!!

ぱちお絵をDeepにLearningさせればぱちお絵の特徴量、所謂ぱちお特徴量が学習できるのではないか、というアイデアRCC在籍時から幾度となく議論されてきました。しかし,学習に用いることのできるデータの少なさを理由に実現に漕ぎ着けていませんでした。

今回は当時より数年経ってぱちお絵のデータ数が増えてきたのでそろそろ頃合いだと思い学習用データセットを作成しました。
RCCの方で欲しい人は連絡してくれればお譲りします。

方針として,元画像そのままで特徴を判別しようとすると解像度が大きくなってしまい学習が面倒くさくなってしまうので顔部分のみを切り出すことにしました。
それに元画像そのままを学習させるのには学習データが足りないんじゃないかなぁと思う。(検証はしてません)
アニメ画像の分類で顔画像を用いたものは結構成功例がある*1ため,顔画像を切り出して学習させるのはある程度理にかなっているのかなと思います。

顔を切り出そう!

顔を切り出すのにはOpenCV+Pythonとアニメ顔検出器 lbpcascade_animeface*2 を使って検出しました。
正直800くらいなら自分で切り出すのもそんなに手間じゃないけど後々ぱちお絵識別APIとか作る時にデータの整形が自動化できないと厳しいので。
大体この記事↓を参考にしたらすぐできました。
qiita.com

切り出しは結構うまいこといって大体700くらいのデータが切り出せました。
顔が認識できない画像も中にはありましたが,一枚に複数のキャラが描かれたイラストで人数分の顔データを切り出しデータ数を稼ぎました。


▼非常に可愛いぱちおくんが描いたパピのイラスト
f:id:RND:20171203024652j:plain:w300


▼切り出されたパピの顔。かわいい。
f:id:RND:20171203024843j:plain:w100


切り出した画像は64*64にリサイズして使うんですが,OpenCVのリサイズで縮小するときは下記のコードみたく補間の設定をINTER_AREAにするのが大事です*3*4。(1敗)

resized_image=cv2.resize(image,(64,64),interpolation = cv2.INTER_AREA)


▼64*64pxにリサイズされたパピの顔。かわいい。
f:id:RND:20171203025050j:plain:w100


▼横顔とか目を閉じた顔でも検出できたのでlbpcascade_animefaceはすごいと思う。(こなみ感)
f:id:RND:20171203031142j:plain:w100f:id:RND:20171203031147j:plain:w100

おまけ:lbpcascade_animefaceはすごい

lbpcascade_animefaceはすごい強力で単眼キャラでも肌の色が変でもクリーチャーみの高い顔でも検出できたりします。
なんか画像からツイッターアイコンを自動生成するツールくらいなら秒で作れそう。
▼RNDさんが描いたイラスト
f:id:RND:20171203031818j:plain:w200
▼単眼キャラでも切り出せる!すごい
f:id:RND:20171203032008j:plain:w100
▼クリーチャー感が強くても顔を取り出せます
f:id:RND:20171203032440j:plain:w100f:id:RND:20171203032447j:plain:w100

ちなみに顔じゃないものが顔と間違って検出されるケースは結構あって手動でゴミ掃除する必要があります。ただ実際に絵柄判別を動かすときにもゴミが入ることを考えると学習時にあえてゴミをそのままにして学習してしまうのもアリかもしれない。

ぱちお絵識別器を作ろう

いよいよぱちお絵の顔画像を使ってDeepにLearningさせます。
DeepLearningの勉強をしたのは結構前だけど僕が読んだ本は理論的なところがメインで実装の話はあんまり載ってなかったから実際動かす上ではQiitaの記事とかTensorflowのサンプルコードが参考になりました。

▼普通の理系の学部生相当の数学ができればわりとサクサク読めると思う。分厚くなくてお手軽だしおすすめの本です。
https://www.amazon.co.jp/dp/B018K6C99A/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

▼参考にした記事。データの前処理とか結構ちゃんと書いてあって参考になった。
qiita.com

課題設定

64*64px 3チャンネルのアニメ顔画像を入力とし,それがぱちお絵(patio)か非ぱちお絵(non-patio)かを分類する二値分類を行います。
patioの学習データは前述したぱちお顔データセットを使います。non-patioの学習データはAnimeFace Character Dataset
(animeface-character-dataset)を使うことを検討していましたがなんかうまくいきませんでした。(収束はしたけど汎化性能*5が皆無だった。原因は謎)
ちょうどよく某氏から150人くらいが描いた100件ずつくらいの計15000件くらいのイラストのデータセットを譲り受けたのでそこから自分で切り出した顔画像データをnon-patioとして使うことにしました。

学習モデル

▼気持ちこんな感じのモデルです
f:id:RND:20171203040828p:plain:w600

Tensorflow TutorialのCifar10のモデルをベースに作りました。というかほとんどそのままで入力サイズが2倍になってconvolutionが一つ分増えただけ。
ダメだったらもっと手を加えるつもりだったけど普通に収束したのでこのまま使ってます。
github.com

学習データ

patio720件を訓練データ620件とテストデータ100件に分割,
non-patio 15,195件を訓練データ15,095件とテストデータ100件に分割
patioのほうOpenCVで切り出しにミスったゴミデータを除いていますが,non-patioのほうは(ゴミが入った時non-patioになるように)あえてゴミデータを残しています。
訓練データはpatioの方で同じ画像を5回ずつ使いまわして3,100件にしてpatioとnon-patioの比がだいたい1:5になるようにしました。
ここの比を1:1とかにすると未知のデータに対してぱちお側にバイアスがかかって(訓練データにない画像をとりあえずpatioとして識別してしまう)汎化性能が死んだ識別器ができたりします。(1敗)
テストデータは識別精度を見るために使用するので1:1の比率にします。チャンスレベルは50%,正解数が二項分布B(200,0.5)に従うとして片側5%を有意とするなら正解率56%(両側なら57%)以上で有意になります。(多分)

結果と考察

テストデータを用いた識別精度91%くらいを実現。

ぱちお絵識別器ができました。

ただし今回用いたテストデータは元のデータの重複を完全に無くせていないので91%は微妙に信用できない値です。(詰めが甘い)

モデルの学習後にぱちおくんが描いたイラスト(訓練データにもテストデータにも入っていない)でその精度を見てみました。
▼ぱちおくんが描いたガヴリールのイラスト。ストラトキャスターの美しいフォルムが光る。

顔を切り出します。

実行結果は

('result', [array([[ 0.27270663,  0.72729337]], dtype=float32)])
('label', 1)
('label-name', 'patio')
('patio value', 0.72729337)

patio valueは絵のぱちおらしさを表す値です。
patio valueが0.5以上のものをpatioと判断します。
ちゃんとぱちお絵として判別されています。

比較として公式のガヴちゃんをもってきます


結果は…

('result', [array([[ 0.77573705,  0.22426297]], dtype=float32)])
('label', 0)
('label-name', 'non-patio')
('patio value', 0.22426297)

ちゃんと識別されましたね。

その他の結果:
ぱちお絵

('result', [array([[ 0.01547608,  0.98452383]], dtype=float32)])
('label', 1)
('label-name', 'patio')
('patio value', 0.98452383)

RND絵

('result', [array([[ 0.91808331,  0.08191673]], dtype=float32)])
('label', 0)
('label-name', 'non-patio')
('patio value', 0.081916727)

ぱちお絵

('result', [array([[ 0.0488117 ,  0.95118833]], dtype=float32)])
('label', 1)
('label-name', 'patio')
('patio value', 0.95118833)

RND絵

('result', [array([[ 0.97699863,  0.02300139]], dtype=float32)])
('label', 0)
('label-name', 'non-patio')
('patio value', 0.023001393) 

誤認識された絵(RND絵)

('result', [array([[ 0.34179869,  0.65820134]], dtype=float32)])
('label', 1)
('label-name', 'patio')
('patio value', 0.65820134)

('result', [array([[ 0.37565264,  0.62434739]], dtype=float32)])
('label', 1)
('label-name', 'patio')
('patio value', 0.62434739) 

誤認識された絵(ぱちお絵)

('result', [array([[ 0.9369204 ,  0.06307962]], dtype=float32)])
('label', 0)
('label-name', 'non-patio')
('patio value', 0.063079618) 

('result', [array([[ 0.92333567,  0.07666432]], dtype=float32)])
('label', 0)
('label-name', 'non-patio')
('patio value', 0.076664321) 

ぱちおくんの絵でも目などのパーツの比率が違うと誤認識されたりぱちおくんの絵じゃなくてもパーツの比率がぱちお絵に近いとpatio valueが高くなるようです。
ぱちおくんの絵は顔のパーツの比率が特徴的なのでそれで判別してるのかな?
patio-value 0.7くらいに閾値を設定したらもうちょっと良くなるかも。
とりあえずflaskとかでWebのインタフェースを組んで動かせるようにしたい。
ぱちお絵識別器が作れることがわかったのであとは生成モデルと組み合わせてGAN(Generative Adversarial Network)とか学習できればぱちお絵の自動生成ができるかもしれません。夢が広がりますね。

おまけ:開発環境の話

DeepLearningといえばとにかく開発環境の構築が面倒臭い印象です。
そこで今回はDockerを使って簡単にTensorflowの環境を構築しました。
手元にNVIDIAGPUがあるならNVIDIA Dockerを使うと良さそう。
後述するfloydhubで同じコードを動かすためにもdockerで環境構築しておくと良いです。
Macなら下のサイトの手順にしたがってDocker for Macをインストールします。
docs.docker.com
あとは
docker-compose.yml

version: '2'
services:
  development:
    build:
      context: .
      dockerfile: tensorflow/Dockerfile
    command: python tensorflow_main.py
    volumes:
      - ./:/app #docker-compose.ymlと同じディレクトリに入ってるコードが読み込めるようになります
      - ./output:/output #学習済みモデルデータを出力・読み込みするディレクトリ
      - ./mydata:/mydata #学習に使うデータをおくためのディレクトリ

死ぬほど雑なDockerfile
tensorflow/Dockerfile

FROM tensorflow/tensorflow 
WORKDIR /app

という感じにDockerFileとdocker-compose.ymlを書いてしまったらdocker-compose.ymlのあるディレクトリで

docker-compose build
docker-compose up

でコンテナを作って

docker-compose run development bash

なり

docker-compose run development python ~~~~~.py

なりで動かします。Dockerはいいぞ。

おまけ:DeepLearningの学習にオススメの神サービスfloydhubの話

今回,手元にまともなGPUを積んだPCがないのでDeepLearningの環境が構築済みのGPUコンテナを700円で10時間動かし放題の神サービスfloydhubを使いました。6000円払って100時間使い放題のプランもあります。
今回のモデルだと10万ステップ動かして12時間くらいなので実質700円で1試行って感じですね。安いけどコスパがいいのかは微妙。先払いなので勝手にお金がなくなっていくような事態はおこりません。

floydhubについてはこの辺の記事にまとめられてますが何ができるのか手っ取り早く知りたいならfloydhubのドキュメントを読むのが一番いいです。
qiita.com
qiita.com
floydhubのドキュメント
FloydHub Documentation

簡単
専用のCLIツールがpipでインストールできます。
Herokuをご存知の方ならheroku-toolbeltみたいなものと思ってくれるとわかりやすいです。

floyd run --env tensorflow-1.3 --data [:user name]/datasets/[:dataset name]/[:version]:mydata --tensorboard "python tensorflow_main.py" --gpu

みたいなコマンド一発でデプロイして動かせます。データセットとして任意のディレクトリをアップロードすることもできてアップロード済みのデータセットは実行時に--dataでマウントポイントを指定してあげることができます。
tensorflow,chainer,keras,Theano,caffeなどの環境がデフォルトで使えます。
tensorflowの場合tensorboardも立ち上げることができてスマホからソシャゲ感覚でモデルの学習を見守ることができます。
▼ソシャゲ感覚でモデルの成長を見守ることができる。かわいい。


outputという名前のディレクトリに学習済みモデルデータ等を出力すると学習が終わった後にoutputディレクトリを圧縮したtarファイルがDLできます。
3GBくらいになると解凍するのが険しくなるのであんまりデータを出力するとアレかもしれない。(4敗)

おわりに

記事はここまで。だいたい2週間くらいかかりました(顔の切り出しスクリプト作成と学習モデルの作成に1週間,モデルの学習と試行錯誤にもう1週間くらい)
DeepLearningは簡単にはじめられる環境が整っているし課題によっては学習データの量もそんなに多くないので意欲のある学生各位や興味があってお金に余裕のある社会人各位はさっさと始めて、どうぞ。今回はTensorflowを使ったけどChainerだともっと簡単らしいですよ。
RCC OBOG アドベントカレンダー 2017
次はsyusui_sさんの記事です。

*1:↓この辺で結構記事がある qiita.com

*2:参考:GitHub - nagadomi/lbpcascade_animeface: A Face detector for anime/manga using OpenCV

*3:参考:画像サイズを変更する - OpenCV for Android ~ ぎーくなぁど

*4:参考:Python OpenCVの基礎 resieで画像サイズを変えてみる - Pythonの学習の過程とか

*5:学習に用いていないデータに対する識別精度。実際に使う時は訓練データに使ってない画像でもちゃんと識別してくれないと困るので一番大事な指標。