Deep Learningの気持ちになって考えてみる
前回の記事で友利奈緒判定botを作ったのを紹介したところ、いろんな人から試してもらうことができました。集計したところ正解率としては90%程度を出していました。
この前集計した時の混同行列はこんな感じでした。最近投稿数が増えすぎて集計したくないです……。
しかし、第4世代のネットワークになっても「さすがにこれはないだろう」というような間違いを続けています。
.@dolicas_ 友利奈緒です(72%) pic.twitter.com/U9l93GVgIS
— まほろ(稼働中) (@mitra_sun22) 2015, 9月 5
.@DEGwer3456 友利奈緒です(99%) pic.twitter.com/8Yum498g4E
— まほろ(稼働中) (@mitra_sun22) September 6, 2015
Deep Learningは性能がいいということで最近もてはやされていますが、この程度で騙されるようではシンギュラリティーなんて夢のまた夢です。人工知能が人間の仕事を奪い、人間に残された労働は人工知能が作った大量の娯楽を消費することだけ……という世界の実現を望む僕としてはこれは看過しがたい事態です。
とはいえ、この画像を友利奈緒と間違えるからにはDeep Learningにもそれなりの主張があるはずです。相手の気持ちも考えずに一方的に評価してしまってはきっとDeep Learningくんも真の力を発揮してくれないでしょう。Deep Learningの気持ちになって、この画像がどのように見えるのか、本当に友利奈緒っぽいのかを見ていこうと思います。
ネットワーク構成
今回は学習画像が少ない(第4世代で2000枚程度)なので大規模なネットワークを学習するには全く足りません。そこでcaffeのcifar10のexampleに使われている小規模なネットワークを学習させています。この程度であればCPUしかないConoHaの一番安いプランのサーバーであっても十分な速度で学習・予測することができます。ネットワークの構成図は次のようになっています。
ざっと説明すると、送られてきた画像を32x32にリサイズしたデータを入力としてconvolutionとpoolingという処理を3回行い、最後に確率値を求めるというような処理を行っています。convolutionは画像から「特徴」を抜き出す操作、poolingは画像の中で多少の位置の変化を吸収するような操作であるとおおよそ説明することができます。詳細が知りたい人はDeepLearning 0.1 documentationのConvolutional Neural Networks (LeNet)(sinhrksさんによる解説)や、人工知能学会誌の記事(登録が必要)などを読むのがいいと思います。
botが判定を行う際には画像から特徴を抜き出していき、その特徴から友利奈緒かどうかを計算しています。つまり、最終的に判断に利用された特徴を目で見ることができればきっとDeep Learningの気持ちが分かるはずです。幸いなことにconvolutionで出てくる結果は2次元ベクトルなので人間が画像として解釈できます。特徴としてどんな部分が強調されるのかを画像として表示してみましょう。
特徴の可視化
画像を入れたときのconv1, conv2, conv3, pool3の出力を画像として出力するコードです。
O'Reilly Japan - Caffeをはじめようのコードを参考にしましたが、この本はInstant Recognition with Caffeを参考にしているようです。
#!/usr/bin/env python # -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as plt import os import caffe import sys from caffe.proto import caffe_pb2 # プロット設定 plt.rcParams['figure.figsize'] = (10, 10) plt.rcParams['image.interpolation'] = 'none' plt.rcParams['image.cmap'] = 'gray' # 描画関数 def vis_square(data, padsize=1, padval=0): data -= data.min() data /= data.max() n = int(np.ceil(np.sqrt(data.shape[0]))) padding = ((0, n ** 2 - data.shape[0]), (0, padsize), (0, padsize)) + ((0, 0), ) * (data.ndim - 3) data = np.pad( data, padding, mode='constant', constant_values=(padval, padval)) data = data.reshape( (n, n) + data.shape[1:]).transpose((0, 2, 1, 3) + tuple(range(4, data.ndim + 1))) data = data.reshape( (n * data.shape[1], n * data.shape[3]) + data.shape[4:]) plt.imshow(data) plt.show() caffe.set_mode_cpu() net = caffe.Net('cifar10_quick.prototxt', 'cifar10_quick_iter_6000.caffemodel', caffe.TEST) transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape}) transformer.set_transpose('data', (2, 0, 1)) mean_blob = caffe_pb2.BlobProto() with open('mean.binaryproto') as f: mean_blob.ParseFromString(f.read()) mean_array = np.asarray( mean_blob.data, dtype=np.float32).reshape( (mean_blob.channels, mean_blob.height, mean_blob.width)) transformer.set_mean('data', mean_array) transformer.set_raw_scale('data', 255) net.blobs['data'].reshape(1, 3, 32, 32) net.blobs['data'].data[...] = transformer.preprocess( 'data', caffe.io.load_image(sys.argv[1])) out = net.forward() print([(k, v.data.shape) for k, v in net.blobs.items()]) # conv1の出力 features = net.blobs['conv1'].data[0, :36] vis_square(features, padval=1) # conv2の出力 features = net.blobs['conv2'].data[0, :36] vis_square(features, padval=1) # conv3の出力 features = net.blobs['conv3'].data[0, :64] vis_square(features, padval=1) # pool3の出力 features = net.blobs['pool3'].data[0, :64] vis_square(features, padval=1) # 確率値の出力 print net.blobs['prob'].data
これにいろんな画像を入力してみます。
Deep Learningのきもちのほんとのひみつ
まずはオーソドックスな友利奈緒画像を放り込みます。
入力
conv1
目や髪の毛が特徴的だと解釈しているようで、その辺りが強調された画像になっていることが分かります。
conv2
やっぱり目の部分を特に強調するような変換が行われています。上の方が白くなっている画像もあるのでこの辺りの髪の毛の色も判定基準になっていることが伺えます。
conv3
だんだん分からなくなってきましたが、上の方や真ん中あたりを強調しようという気持ちをかすかに読み取ることができます。
pool3
もはや原形をとどめていませんが、これが全結合層に入力されると友利奈緒と判定されます。抽象派友利奈緒です。
ちなみに標準出力には
[('data', (1, 3, 32, 32)), ('conv1', (1, 32, 32, 32)), ('pool1', (1, 32, 16, 16)), ('conv2', (1, 32, 16, 16)), ('pool2', (1, 32, 8, 8)), ('conv3', (1, 64, 8, 8)), ('pool3', (1, 64, 4, 4)), ('ip1', (1, 64)), ('ip2', (1, 2)), ('prob', (1, 2))] [[ 1.00000000e+00 3.00680385e-19]]
と表示されます。
クラス0(友利奈緒である)の確率が100%になっていることが確認できます。
というわけで、標準的な顔画像はこのような変化を経てニューラルネットワークに友利奈緒であると認識されることが分かりました。
次に人間が見たら明らかに違う画像に対する出力を見ていきます。
入力画像
conv1
まだ違いが分かりますが、真ん中あたりが強調される変化を取ることは共通しています。
conv2
目や頭を強調する感じがだんだん似ているように感じます。
conv3
なんだかよく分かりませんが、似ていると言われたら納得してしまう感じです
conv4
これが最終的に全結合層に入力される値です。
真の友利奈緒の場合の画像を再掲するので比較してみてください。
もう一つやってみます
入力画像
いやさすがにこれは顔じゃないでしょ……
conv1
お分かりいただけただろうか!
今回学習したconv1の値だとなんとなくこの図式が顔に見えます。ちょうど右下を向いた矢印が顎の輪郭のようになり、数式がいい感じに鼻っぽくなっています。
conv2
目や髪に相当する部分の強調が残ります
conv3
pool3
この辺はよく分からなくなってきますが、まあ全結合相が似ていると判断してもおかしくないなぁ程度には似ているように感じます。
比較のために友利奈緒ではないと判定される画像についても出力を見ておきます。
入力
conv1
conv2
髪の毛の強調具合がそこはかとなく違う気配を感じないことがなくはないという感じです。
conv3
pool3
違うという先入観を持ってみれば確かに違うかもしれないという程度には違います。
まとめ
というわけでコンピュータと人間という種族を越えた相互理解の足がかりとして、画像からだんだん特徴が抽出されていく様子を眺めてみました。層が上がるにつれてだんだん人間には理解できなくなっていきますが、conv2くらいまではどういう意図があるかを垣間見ることが出来たように感じます。
コンピュータがどうして人間には間違えないような間違え方をするのかという研究の例としてはHOGgles: Visualizing Object Detection Featuresなどがあります。
コンピュータにとって分かりやすい特徴を取って人間には分からなくなった状態から、最もその特徴になりやすい画像を復元することができればDeep Learningのわけが分からない感じも緩和するのかもしれないなぁという適当な感想を持ちました。
コンピュータの気持ちが分かる人間になれるよう頑張りたいです。(その前に他人の気持ちが分かるようにならなければ……)