読者です 読者をやめる 読者になる 読者になる

戯けた姿も生きる道ですか?

鬱屈とした学生の記録。備忘録。メモ。基本的に冗談。

言うなれば橋本愛と前田敦子を識別する人工知能

ニューラルネットワーク 機械学習 TensorFlow hashimotoAI

先に言っておきますが,結果はしょぼいです.コードもちゃんとしてないです.取り敢えず動くモノを作りましたよっていうメモ.

windows, Python3, NumPy, Pillow, OpenCV, TensorFlow

 

以前,手書き文字を認識する多層パーセプトロンを作った.

odoketasugata.hatenablog.com

odoketasugata.hatenablog.com

 ↑では8×8ピクセルのモノクロ画像を多層パーセプトロンに学習させた.

今回は96×96のカラー画像を,TensorFlowを使って畳み込みニューラルネット(Convolutional Neural Network, CNN)に学習させる.

 

はじめに

好きな芸能人は?と聞かれれば「橋本愛」と答えよう.

橋本愛 似てる人」でググると結構出てくる.入山杏奈広瀬アリス生田絵梨花大政絢小島瑠璃子...

そんなとき,ある記事を見つけた.

matome.naver.jp

僕からしたら全然違うし,上であげた全員見分けがつく.しかし,このような記事があるということは橋本愛前田敦子の見分けがつかない人が居るということだろう.大事なことだからもう一度言うが,僕なら上の全員完璧に識別できる.

 

以前から機械学習に興味があったので,ニューラルネットに人の顔を高精度で識別させることが出来るのは知っていた.↓の例はあまりにも有名.

佐村河内識別システム - ぱろすけ's website

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

「何番煎じだよ」って思うかもしれないが,ディープラーニングで「橋本愛-前田敦子識別クイズ」を解く.

 

橋本愛

日本の女性ファッションモデル,女優.熊本県熊本市出身.1996年生まれ.代表作は「告白」「桐島、部活辞めるってよ」「あまちゃん」など.かわいい.

f:id:kenijl1116:20170212024008j:plain

かわいい.

 

畳み込みニューラルネット

機械学習において、畳み込みニューラルネットワーク(CNNまたはConvNet)は順伝播型人工ニューラルネットワークの一種で、個々のニューロンが視覚野と対応するような形で配置されている。畳み込みネットワークは生物学的処理に影響されたものであり、少ない事前処理で済むよう設計された多層パーセプトロンの一種である。画像や動画認識に広く使われているモデルである。」(畳み込みニューラルネットワーク - Wikipedia)

畳み込み層とプーリング層という特別な2種類の層を持つ順伝播型ネットワークで,生物の脳の視覚野をヒントに作られた.(深層学習 - 岡谷貫之)

畳み込み層は入力画像の濃淡パターン(特徴)を検出し,プーリング層は検出された特徴の位置をぼやけさせる.つまり,プーリング層のおかげで画像の中での対象物体の位置にバラつきがあっても認識できる.畳み込み層とプーリング層,そして全結合層を何層にも重ねたニューラルネットが物体認識の分野で高い成績をおさめている.これがいわゆるディープラーニング(の一つ).

 

TensorFlow

「 TensorFlow(テンソルフロー)とは、Googleが開発しオープンソースとして公開した、人工知能ソフトウェアライブラリである」(TensorFlow - Wikipedia)

TensorFlowは以前の記事で苦労して書いた多層パーセプトロンや今回扱う畳み込み層などディープラーニングに関する多くの関数が用意されているので,誤差逆伝播の煩わしい微分を考えなくてもCNNを構築できる.

インストール方法やチュートリアル公式サイトで説明されている(僕はちょっとインストールに苦労した...).

以下参考にした記事

TensorFlowがWindowsサポートしたのでインストールしてみた - デジタル・デザイン・ラボラトリーな日々

TensorFlowチュートリアル - 熟練者のためのディープMNIST(翻訳) - Qiita

TensorFlowでキルミーアイコン686枚によるキルミー的アニメ絵分類 - Qiita

TensorFlow : How To : TensorBoard: 学習を視覚化する – TensorFlow

ニューラルネットワークの計算を可視化するTensorBoardの使い方

[TF]Tensorflowの学習パラメータの保存と読み込み方法 - Qiita

TensorFlow学習パラメータのsave, restoreでつまった - Qiita

Windows+TensorFlowの注意(ディレクトリ) |

 

hashimotoAI ver0.0.1

一般的に機械学習の流れは,

データ収集→データ加工→学習→評価

である.

この場合,データ収集とは橋本愛前田敦子の画像をいっぱい集めることだ.Google画像検索から自動で画像を保存する(Pythonをつかった)方法を調べ実践してみたがうまく動かなかったので,地道に保存しまくった.

次にデータ加工.保存した画像には全身写ってるものや他の人も映っているものもある.そこで,OpenCVのhaarcascadesを使って画像から顔を切り抜き,後は人力で橋本愛の顔だけをピックアップした.面倒な作業ではあったが,全然苦でなかった.そして全ての顔画像を96×96にリサイズした.画像の枚数は,橋本愛が168枚,前田敦子が119枚.

学習

画像を読み込んでndarrayに変換し,訓練データとテストデータに分ける.

import glob
import numpy as np
import numpy.random as rand
from PIL import Image
"""データ読み込み"""
classes = 2
channel = 3
width = 96
height = 96
folder = ['AH96', 'AM96']

images = []
labels = []
for l, f in enumerate(folder):
# 画像フォルダ内のファイル名取得
name_list = glob.glob('./' + f + '/*')

for img_name in name_list:
# ラベル格納
if l == 0:

label = [1, 0]
labels.append(label)
else:
label = [0, 1]
labels.append(label)
img = Image.open(img_name)
# 画像データ(width, height, channel)格納
images.append(np.array(img.getdata()).reshape(width * height * channel))

images = np.array(images)
labels = np.array(labels)
NUM_DATA = len(labels)
"""データ分割"""
# 全データの90%を訓練データ,10%をテストデータに分割
NUM_TRAIN = int(NUM_DATA * 0.9)

NUM_TEST = NUM_DATA - NUM_TRAIN
shuffled_index = rand.permutation(range(NUM_DATA))
shuffled_images = images[shuffled_index]
shuffled_labels = labels[shuffled_index]
train_images = shuffled_images[:NUM_TRAIN]
train_labels = shuffled_labels[:NUM_TRAIN]
test_images = shuffled_images[NUM_TRAIN:]
test_labels = shuffled_labels[NUM_TRAIN:]

橋本愛のラベルは0で教師データは[1, 0],前田敦子が1の[0, 1] である.

ここでようやくTensorFlowの出番.モデルはDeep MNIST for Expertsのものをそのまま使った.構成とテンソルの要素数は,

       入力画像

     (96, 96, 3)

CONV1 ⇓

     (96, 96, 32)

POOL1 ⇓

     (48, 48, 32)

CONV2 ⇓

     (48, 48, 64)

POOL2 ⇓

     (25, 25, 64)

            ↓

       (40000)

FCL1    ⇓

        (1000)

FCL2    ⇓

          (2)

       出力値

という構成.下の図はTensorBoardで表示されたGraph.わっかんねぇ...

f:id:kenijl1116:20170212170530p:plain

モデルの構築は基本的にチュートリアル通りだが,TensorBoradと学習済みパラメータ保存のために所々コードを追加した.

import tensorflow as tf
def weight_variable(shape, name):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial, name=name)
def bias_variable(shape, name):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial, name=name)
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
sess = tf.InteractiveSession()
x = tf.placeholder(tf.float32, shape=[None, width * height * channel])
y_ = tf.placeholder(tf.float32, shape=[None, classes])
# CONV1
x_image = tf.reshape(x, [-1, width, height, channel])

input_channel = channel
output_channel = 32
W_conv1 = weight_variable([5, 5, input_channel, output_channel], 'W_conv1')

b_conv1 = bias_variable([output_channel], 'b_conv1')
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
# POOL1
h_pool1 = max_pool_2x2(h_conv1)

h_width = int((width - 1) // 2 + 1)
# CONV2
input_channel = output_channel

output_channel = 64
W_conv2 = weight_variable([5, 5, input_channel, output_channel], 'W_conv2')

b_conv2 = bias_variable([output_channel], 'b_conv2')
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
# POOL2
h_pool2 = max_pool_2x2(h_conv2)

h_width = int((h_width - 1) // 2 + 1)
# FCL1
input_units = int(h_width * h_width * output_channel)

output_units = 1000
W_fc1 = weight_variable([input_units, output_units], 'W_fc1')

b_fc1 = bias_variable([output_units], 'b_fc1')
h_pool2_flat = tf.reshape(h_pool2, [-1, input_units])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
# FCL2
input_units = output_units

output_units = classes
W_fc2 = weight_variable([input_units, output_units], 'W_fc2')
b_fc2 = bias_variable([output_units], 'b_fc2')
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
# Loss Function
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_conv, y_))

tf.summary.scalar("cross_entropy", cross_entropy)
# Optimize
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

# Accuracy
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.summary.scalar("accuracy", accuracy)
saver = tf.train.Saver()
sess.run(tf.global_variables_initializer())
summary_op = tf.summary.merge_all()
summary_writer = tf.summary.FileWriter('ah_am_log', sess.graph)
print("Start learning")
for i in range(1000):
print(str(i+1) + " epoch")
# batch作成
batch_index = rand.randint(0, NUM_TRAIN, 50)

x_batch = train_images[batch_index]
y_batch = train_labels[batch_index]
# ミニバッチ学習
train_step.run(feed_dict={x: x_batch, y_: y_batch, keep_prob: 0.5})

summary_str = sess.run(summary_op, feed_dict={x: x_batch, y_: y_batch, keep_prob: 0.5})
summary_writer.add_summary(summary_str, i)
# テストデータ正解率
print("test accuracy %g" % accuracy.eval(

feed_dict={x: test_images, y_: test_labels, keep_prob: 1.0}))
save_path = saver.save(sess, os.getcwd() + "\model.ckpt")

https://github.com/Jirouken/hashimotoAI_ver0.0.1/blob/master/learn_AH_AM.py

numpy.ndarrayのデータをTensorFlowにぶち込む.epoch毎に訓練データからランダムに50個のデータを選んでバッチを作成し1000回学習させた.

 

評価

訓練データでの正解率と交差エントロピーは下図のようになった.(結構気持ち悪いくらい上下してる...ドロップアウトしてるからかな...)

f:id:kenijl1116:20170212042606p:plain

f:id:kenijl1116:20170212042620p:plain

汎化性能,つまりテストデータでの正解率は96.5%となった.

クイズ

ではでは,「橋本愛-前田敦子識別クイズ」を解いてもらおう.上に書いたのと同様にこのページから画像をDLしてOpenCVを用いて顔を切り抜く.しかし,何枚かはOpenCVに認識されなかったのでペイントを使って自分で切り抜いた.

学習済みモデルを読み込み,学習時と同様に画像を読み込ませて識別させた.

saver = tf.train.Saver()
sess.run(tf.global_variables_initializer())
saver.restore(sess, os.getcwd() + "\model.ckpt")
print("Start recognition")
for (name, image) in zip(name_list, images):
print(name)
prediction = tf.argmax(y_conv, 1)
result = prediction.eval(feed_dict={x: np.array([image]), keep_prob: 1.0}, session=sess)
print("result: %g" % result)

https://github.com/Jirouken/hashimotoAI_ver0.0.1/blob/master/quiz_AH_AM.py

下の表に入力画像と正解,ニューラルネットが出した回答を示す.

f:id:kenijl1116:20170212051442p:plain

16問中12問正解.正解率75%.う~ん...汎化性能は96.5%だったのに...

 

考察

クイズの正解率と汎化性能にギャップがあるのは,

・クイズ問題数が少ない

・クイズ画像にOpenCVで認識されなかった画像が含まれている

・学習データに同じ画像が複数含まれており,訓練データとテストデータに同じ画像が存在したため汎化性能が実際より高く見える

の3点が考えられる.そもそも汎化性能も高い値と言えるのか疑問.

 

 今後の展望

・画像自動収集で学習データ拡大

・画像データのコントラストや明度のランダムな変更やノイズの付加によるデータの水増し

・学習データをTFRecordで渡す

・モデルの変更

 

おわりに

正直これはモデルもしょぼいし2値分類で正解率75%は非常に悪い結果なので改良してよりよい精度を目指したい.僕より頭悪いですこのAI.

僕は初心者ですので何卒アドバイスください...