TensorFlowによるディープラーニングで、アイドルの顔を識別する

以前は MNISTの例を使って画像識別を試してみた けど、次はカラー画像についての識別を試してみる。

「アイドルなんてみんな同じ顔に見える」って 最近も言われてるのかどうか知らないけど、自分もつい5年前くらいまではそう思っていたわけで。その識別を機械学習でやってみよう という試み。
最近はほとんどライブに行かなくなってしまったけど大好きなももいろクローバーZちゃんを題材にしてみることに。
5人のメンバーの顔は機械学習によってどれくらい分類できるようになるのか??

CIFAR-10

CIFAR-10 という、32×32サイズのカラー画像を10種類のクラスに分類する識別課題があり、そのデータセットが公開されている。これを実際にTensorFlowで学習するための畳み込みニューラルネットワークのモデルや関数などがtensorflow.models.image.cifar10パッケージに同梱されているので、これを利用して学習させてみることにした。

画像収集

まず課題となるのが訓練用のデータセットの用意。教師あり学習を行うため、「顔の画像」と「それがどの人物の顔であるか(どう分類されるのが正解か)、を示すラベル」のセットが必要で、CIFAR-10では各6000枚の画像とラベルのセットが用意され提供されている。ももクロの5人の顔識別においては現実的にどれくらいの量が必要か分からないけど、最低でも各100くらいは用意したいところ。
ももクロちゃんはずっとアメブロを続けてきているので、そこに自撮り画像などはある程度蓄積されている。それを利用することにして、

というのを出来るwebアプリをまずrailsで作った。画像加工はRMagickでだいたいできるので便利。

face-collector
https://github.com/sugyan/face-collector

そしてこれを使って自動抽出された顔画像たちを目視で確認しながらラベル付け。これだけは残念ながら人力で行うしかない。集合知を上手く利用できればこのへんもある程度は自動化できるのかもしれないけど…。
とりあえず5人の各メンバーについて200点ずつくらいはすぐにデータを作ることができ(有安さんは安定の自撮りが多くて集めやすい、玉井さんは自撮り少なくて苦労した…あと高城さんは変顔が多くて判断に迷うことが多かったw)、メンバー以外のスタッフや共演者さんの顔画像なども幾つかあったのでそれらも「ももクロメンバーではない」という6つ目のラベルとして混ぜて、訓練用と評価用でデータセットを作成した。

管理上はユニークなデータとしていても同じ顔の写った同じ写真を複数のメンバーがブログに載せていたりもするので、実際には完全にユニークではなく数組ほぼ同じものが混ざっていたりもするかもしれない。

学習

これらを使って、 Tutorial とほぼ同様に学習させていく。CIFAR-10と同形式でファイルを用意しておけば、cifar10.input()の関数1つでファイルからのデータ読み込み、加工、キューイングまですべて済んだ入力データを得られるので便利。
実際に書いたコードはこれだけで、

cifar10.IMAGE_SIZE = 32
cifar10.NUM_CLASSES = 6
cifar10.NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 1000
cifar10.INITIAL_LEARNING_RATE = 0.08

FLAGS = tf.app.flags.FLAGS

tf.app.flags.DEFINE_integer('max_steps', 10000,
                            """Number of batches to run.""")
tf.app.flags.DEFINE_string('train_dir', 'train',
                           """Directory where to write event logs """
                           """and checkpoint.""")

def train():
    # ops
    global_step = tf.Variable(0, trainable=False)
    images, labels = cifar10.distorted_inputs()
    logits = cifar10.inference(tf.image.resize_images(images, cifar10.IMAGE_SIZE, cifar10.IMAGE_SIZE))
    loss = cifar10.loss(logits, labels)
    train_op = cifar10.train(loss, global_step)
    summary_op = tf.merge_all_summaries()

    with tf.Session() as sess:
        saver = tf.train.Saver(tf.all_variables(), max_to_keep=21)
        summary_writer = tf.train.SummaryWriter(FLAGS.train_dir)

        # restore or initialize variables
        ckpt = tf.train.get_checkpoint_state(FLAGS.train_dir)
        if ckpt and ckpt.model_checkpoint_path:
            saver.restore(sess, ckpt.model_checkpoint_path)
        else:
            sess.run(tf.initialize_all_variables())

        # Start the queue runners.
        tf.train.start_queue_runners(sess=sess)

        start = sess.run(global_step)
        for step in xrange(start, FLAGS.max_steps):
            start_time = time.time()
            _, loss_value = sess.run([train_op, loss])
            duration = time.time() - start_time

            assert not np.isnan(loss_value), 'Model diverged with loss = NaN'

            print '%d: %f (%.3f sec/batch)' % (step, loss_value, duration)

            if step % 100 == 0:
                summary_str = sess.run(summary_op)
                summary_writer.add_summary(summary_str, step)
            if step % 500 == 0 or (step + 1) == FLAGS.max_steps:
                checkpoint_path = os.path.join(FLAGS.train_dir, 'model.ckpt')
                saver.save(sess, checkpoint_path, global_step=step)

cifar10.inferenceにバッチ入力を渡すことで畳み込みニューラルネットワークモデルとその出力が作られ、cifar10.lossにその出力と正解ラベルを渡すことで誤差を計算、それをcifar10.trainに渡すことで学習が行われる。分かりやすい。
いくつかの定数値は上書きして変更することができ、ここでは

  • distorted_inputsでは32x32サイズの画像をさらにランダムに24x24サイズで切り出して入力としていたが、今回は顔画像領域に既に切り出されているし不要と判断し32x32そのまま入力とする
  • 分類数はメンバー5人+それ以外、で6種類に
  • 何度か学習を試してみたが途中でlossが発散してしまうことがあったのでINITIAL_LEARNING_RATE0.1から0.08に少し下げた

など。

入力画像の種類が少ないこともあってか、数千stepでもう十分なくらいに学習が進む。VPS上でDocker立ち上げて回してみていたけど数時間で10000stepの学習が終了した。

で、学習に使っていない評価用テストデータを使って正答率を計測してみたところ

のようになり、75%くらいまで到達した後 それ以上はもう上がらないようだった。

学習結果を使ったWebアプリ

せっかくここまで作ったのなら、前回のように実際に誰でも試せるようにWebアプリにして公開してみよう、と。

TensorFlowでのMNIST学習結果を、実際に手書きして試す - すぎゃーんメモ のときと同様に、学習済みのデータを使って画像を受け取り判定結果を返すJSON APIを用意し、それを使って判定結果を描画する。
任意の画像をDrag and Dropで受け取るので、まずはそこから判定するための顔領域だけを切り出す必要があり、自作の顔検出器では遅すぎるので ここではLIMITED PREVIEW版の Cloud Vision API を使ってみている。
色んな画像を上げて試してみてください。

https://momoclo-face-recognizer.herokuapp.com/

考察

冒頭の画像では玉井さんが高城さんと誤判定されている以外は当たっている。テストデータでの評価は75%程度だしまぁこんなものかと。
実際色んな画像で試してみるともっと残念な結果になるものの方が多いかんじ。実用的なレベルにはまだまだ達しない。

  • 訓練用の画像1000枚ではまだまだ足りていないのかも?画質の良くない自撮りや変顔なんかも多いし、もうちょっとバリエーションがあった方が良さそう
  • 入力32x32では小さすぎる、というのもある?さすがにそのくらい縮小されると自分で目視しても分かりづらかったりするし誤判定されても仕方ない気はする
    • あと顔領域の切り出し方によっても結構判定結果が変わるようだったので、やはりランダムcropは使った方が良かったのかも
  • ニューラルネットワーク自体が単純すぎる?今回のtensorflow.models.image.cifar10パッケージのものだと2階層だけの畳み込み-プーリングでそこから全結合のものとなっている。もうちょっと深いものだとまた精度が変わったりするだろうか?

色々ためしてみたいところではある。他のアイドルさんの画像も集めて分類数も増やしていきたい。

Repository