「末は博士か大臣か?」、顔から判別してみる

【イントロ】
 「末は博士か大臣か」。
優秀な子供は周りの大人から、そんな期待を背負って成長していきます。小さな頃の期待を背にぐんぐん頭角を表し、時代を代表する寵児となっていきます。
 しかし、「十で神童、十五で才子、二十歳過ぎれば只の人」。
歳を重ね、より大きな集団の中で比較されることで、かつての輝きは失われていく人も、一方では存在します。。。
 一体何が、その差を生み出すのか。やはり、顔か・・・
 でも、博士と大臣って顔つきは違うんだろうか(´・ω・`)?

そこで本記事では、「顔面から博士か大臣か判別できるか?」、調べてみました。

【方法】
 方法はかっこよく、Deep Learningを使用することにしました。
@kakinaguru_zoさんのPythonとKerasを使った猫犬分類プログラムを参考に、分類を実施しました。実際は基本的にそのままです。(コメント含めて)。参考書として、こちらのKeras本を使用していますが、丁寧に書かれていると思いました(こなみ

 とはいえ、学習用の博士と大臣画像は自分で集めないといけません。
そこで、Wikipediaから科学系のノーベル賞(物理学、化学、医学・生理学)受賞者と各国の首相・大統領の顔画像を収集するプログラムをPythonで作成しました。
その結果、博士画像488枚、大臣画像625枚を収集することができました。
(ほかのサイトからも集めてみましたが、画像サイズは同じくらいのほうが収束が良かったので、すべてWiki画像で揃えました。)
これらの画像を2:1:1くらいの割合に分割し、それぞれ「学習」・「評価」・「テスト」データとしました。この辺の割合はフィーリングです。
 次に、この画像を使って学習を実施しました。学習にはGoogle Colabを使用しました。これは、GPU使用可能なクラウド上でIpython notebook形式のPythonを実行できるアプリケーションです。すごい時代になったもんだ。。。
 学習用データをアップロードして学習した結果が下図のとおりです。

図1,学習・評価データのAccuracy

図2、学習・評価データのLoss

図1からわかるように、学習データで85%、評価データで70%程度の正確さで分類できる結果となりました。当てずっぽうだと50%なので、少しは分類ができているようです。
やはり、博士と大臣は顔つきが違う・・・?
とはいえ、図2のようにLoss関数の収束は十分ではないので改善の余地はありそうです。過学習してるのかな?

それでは、テストデータに対してどういう結果になるか調べたのが下図です。図3のように、写真下の左側の数字が博士っぽさ、右側が大臣っぽさの確率を示しています。

図3、博士(左)・大臣(右)である確率

図4、大臣ぽい大臣(日本、鳩山氏)
図5、博士っぽい大臣(ベルギー、ロンパウ氏)
図6、博士っぽい博士(日本、南部氏)
図7、大臣っぽい博士(アメリカ、バーディーン)

・・・ナニが決め手なんだ?何かしらの分類がなされていますが、根拠がわからないところが不思議なところです。Deep Learningはすごいがむずかしい(語彙)

【まとめ】
 なかなか、100%の分類は難しいです。いろいろ調整しがいはありますが、Kerasがそれぞれナニをしているか、ちゃんと勉強しないといけないなぁと思います。
次の記事では、もう少しいろんな人を分類してみたいと思います。

画像収集スクリプト
#For Wikipedia
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
import os
import sys
import time

def download_site_imgs(url, path):
    img_urls = []

    # パス(保存先)が存在しなければ新規作成
    if not os.path.exists(path):
        os.makedirs(path)

    # htmlのパース
    soup = BeautifulSoup(requests.get(url).content,'lxml')
 
    # 画像リンクなら(拡張子がjpgなど)リストに追加
    for img_url in soup.find_all("img"):
        # imgタグのsrc要素を抽出
        src = img_url.get("src")
        src="https:"+src
        print(src)
        #src要素に画像の拡張子が含まれていたらリストに追加
        if 'jpg' in src:
            img_urls.append(src)
        elif 'png' in src:
            img_urls.append(src)
        elif 'gif' in src:
            img_urls.append(src)

    # 画像リンク先のデータをダウンロード
    for img_url in img_urls:
        #temp=img_url.split("?")
        #img_url=temp[0]
        re = requests.get(img_url)
        print('Download:', img_url)
        with open(path + img_url.split('/')[-1], 'wb') as f: # imgフォルダに格納
            f.write(re.content)
        time.sleep(1.0)
 

if __name__ == '__main__':
    site=input("Input URL; ")
    download_site_imgs(site, 'img/')
    print("ok")
 

画像分類用プログラム
# coding: utf-8


# Google Colab にファイルをアップロードする
# 説明は https://colab.research.google.com/notebooks/io.ipynb などを参照
# Google Colab 自体の説明は
# https://news.mynavi.jp/article/zeropython-27/ などを参照

# ファイルアップロードはChromeブラウザでないと失敗するかも知れない
# Chromeで失敗した場合「設定」→「詳細」→「コンテンツの設定」→「Cookie」と辿って現れる
# 「サードパーティの Cookie をブロックする」をOFFにする
import google.colab.files
uploaded = google.colab.files.upload()
for filename in uploaded.keys():
    with open(filename, "wb") as f:
        f.write(uploaded[filename])
# 変数uploadedを削除してメモリーの消費を節約する
del uploaded


# アップロードを確認する
get_ipython().system('ls -l')


# zipファイルを展開する
get_ipython().system('unzip doctor-and-politician.zip')



import keras # kerasの関数を使えるように読み込む
# 個々の関数の説明は http://keras.io/ja を見て下さい

import numpy
import matplotlib.pyplot


# 画像の画素は0〜255の値であるが、ニューラルネットに大きい値を入れるとうまく動かないので1/255を掛けて0〜1の値にする
train_datagen = keras.preprocessing.image.ImageDataGenerator(
    rescale=1/255.0,
    # 以下は、学習する画像を回転させたり上下左右反転させたりして、枚数の水増しをさせる指示である
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    vertical_flip=True,
    fill_mode='nearest')
validation_datagen = keras.preprocessing.image.ImageDataGenerator(rescale=1/255.0)

train_generator = train_datagen.flow_from_directory(
    "train", # 最初に渡す引数は学習用データがあるフォルダー。trainの中にdogとcatというフォルダーがありそれぞれ犬と猫の画像が入っている。種類をフォルダー名にしてその中に画像を入れる
    target_size=(224, 224), # ニューラルネットに渡すときの画像の大きさ
    batch_size=20,
    interpolation="lanczos")
validation_generator = validation_datagen.flow_from_directory(
    "validation", # 最初に渡す引数は「検証」用データがあるフォルダー
# 検証とは、学習に用いなかったデータに対してニューラルネットがどの程度うまく動作するか確認することである
    target_size=(224, 224), # ニューラルネットに渡すときの画像の大きさ
    shuffle=False, # 同じ順番で画像を出す
    batch_size=20,
    interpolation="lanczos")


# MobileNetと呼ばれるニューラルネットを、入力画像サイズ224×224で、最後の分類のための変換を除いて、
# ImageNetデータベースで学習済みのウェイトとバイアスを持ったものとして読み込む
# MobileNetは高速に動作するので選んだ。もっと識別性能が高いものもあるのでそれを使ってもよい
mobilenet = keras.applications.mobilenet.MobileNet(input_shape=(224,224,3), weights="imagenet", include_top=False)

# MobileNetのウェイトとバイアスを更新しないように指示する
mobilenet.trainable=False
        
model = keras.models.Sequential()
# model.add() にニューラルネットの層を処理する順番に渡す。
# ここで「層」とはウェイトとバイアスと活性化関数による変換のほうを指す

# 各画素の値の範囲を0〜1から-1〜+1に変更する。MobileNetがそういう入力形式について
# 学習を行っているからである。以下の行を削除しても学習ならびに識別は出来る
model.add(keras.layers.Lambda(lambda x: x*2-1, input_shape=(224,224,3)))

model.add(mobilenet)

# Flattenは2次元以上のデータを平ら(1次元)にする変換である。学習で更新されるウェイトを持たない
model.add(keras.layers.Flatten())

# Denseは入力の実数と出力の実数がすべて結合されている変換である。256は出力する実数の数
model.add(keras.layers.Dense(256, activation="relu"))

# 分類の場合最後の活性化関数をsoftmaxにするとうまくいくことが多い
model.add(keras.layers.Dense(2, activation="softmax"))

# ネットワークの構造と学習可能・学習不可能な変数の数を表示する
model.summary()


# 学習させる前にcompileを呼ぶ必要がある。このときに損失関数(loss)などを指定する。
# 識別するときには交差エントロピー(categorical crossentropy)を用いるのがよい。
# optimizerはウェイトの更新に用いる手順で、SGDよりはRMSpropのほうが上手くいくことが多い
model.compile(optimizer=keras.optimizers.RMSprop(lr=2e-5),
    loss='categorical_crossentropy',
    metrics=['acc'])
# 上記で、optimizer="rmsprop" として標準の設定をそのまま使うと全く学習が進まない


# エポックとは、おおよそすべての訓練データを最低1度は読んで学習する単位である
# バッチサイズ×steps per epochが2000で画像の枚数は1000である
# steps per epochは1エポック内に何個のバッチを読ませるか指定している
# epochsは何エポック学習させるか指定している
# 1エポックの学習が行われるごとに検証データによる識別率の確認を行うが、
# validation_stepsはそのときに何個のバッチを読ませるか指定している
# メモリが足りない場合はworkersやmax_queue_sizeを小さくして下さい
# この例では学習は30エポック程度行うことが望ましい
history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=10,
    validation_data=validation_generator,
    validation_steps=50,
    verbose=1,
    max_queue_size=200,
    use_multiprocessing=False,
    workers=10)

#学習結果を保存する。
model.save("doctor_and_politician1.h5")
# load_modelを用いて学習結果を読み込めるがMobileNetを用いた場合は
# model = keras.models.load_model('ファイル名', custom_objects={
#                       'relu6': keras.applications.mobilenet.relu6})
# のようにcustom_objectsを用いないとエラーになる


# 以下の部分は、損失関数の値と正答率を学習データと検証データそれぞれについてグラフ化している
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
matplotlib.pyplot.figure(1)
matplotlib.pyplot.plot(epochs, acc, 'bo', label='Training acc')
matplotlib.pyplot.plot(epochs, val_acc, 'b', label='Validation acc')
matplotlib.pyplot.title('Training and validation accuracy')
matplotlib.pyplot.legend()
matplotlib.pyplot.figure(2)
matplotlib.pyplot.plot(epochs, loss, 'bo', label='Training loss')
matplotlib.pyplot.plot(epochs, val_loss, 'b', label='Validation loss')
matplotlib.pyplot.title('Training and validation loss')
matplotlib.pyplot.legend()
matplotlib.pyplot.show()


# 画像を表示する
doctor_image1=keras.preprocessing.image.load_img("test/doctor/Doctor (78).jpg", target_size=(224,224), interpolation="lanczos")
politician_image1=keras.preprocessing.image.load_img("test/politician/Politician (352).jpg", target_size=(224,224), interpolation="lanczos")
matplotlib.pyplot.figure()
matplotlib.pyplot.imshow(doctor_image1)
matplotlib.pyplot.figure()
matplotlib.pyplot.imshow(politician_image1)
matplotlib.pyplot.show()


# 政治家を分類する
politician_tensor1=keras.preprocessing.image.img_to_array(politician_image1)
politician_tensor1=numpy.expand_dims(politician_tensor1, axis=0)
politician_tensor1 /= 255.0
print(model.predict(politician_tensor1))
# 結果は博士と政治家である確率が2つ並んだベクトルである


# 番号と識別結果の関係を表示する
print(train_generator.class_indices)


# 博士を分類する
doctor_tensor1=keras.preprocessing.image.img_to_array(doctor_image1)
doctor_tensor1=numpy.expand_dims(doctor_tensor1, axis=0)
doctor_tensor1 /= 255.0
print(model.predict(doctor_tensor1))


# 以降の操作を行うと識別率がより向上する
# MobileNetの中にある層の名前を表示する
mobilenet.summary()


# 最終的な出力に近い層だけ学習可能と以下で設定する
mobilenet.trainable=True
for layer in mobilenet.layers:
    if layer.name in ["conv_pw_13_bn", "conv_pw_13", "conv_dw_13_bn", "conv_dw_13"]:
        layer.trainable=True
    else:
        layer.trainable=False


# trainableに何かを代入した場合必ずcompileをもう一度行う必要がある
model.compile(optimizer=keras.optimizers.RMSprop(lr=2e-5),
    loss='categorical_crossentropy',
    metrics=['acc'])
# 学習可能パラメータの数が前と異なることを確認する
model.summary()


history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=10,
    validation_data=validation_generator,
    validation_steps=50,
    verbose=1,
    max_queue_size=200,
    use_multiprocessing=False,
    workers=10)
model.save("doctor_and_politician2.h5")


# 以下の部分は、損失関数の値と正答率を学習データと検証データそれぞれについてグラフ化している
acc += history.history['acc']
val_acc += history.history['val_acc']
loss += history.history['loss']
val_loss += history.history['val_loss']
epochs = range(1, len(acc) + 1)
matplotlib.pyplot.figure(1)
matplotlib.pyplot.plot(epochs, acc, 'bo', label='Training acc')
matplotlib.pyplot.plot(epochs, val_acc, 'b', label='Validation acc')
matplotlib.pyplot.title('Training and validation accuracy')
matplotlib.pyplot.legend()
matplotlib.pyplot.figure(2)
matplotlib.pyplot.plot(epochs, loss, 'bo', label='Training loss')
matplotlib.pyplot.plot(epochs, val_loss, 'b', label='Validation loss')
matplotlib.pyplot.title('Training and validation loss')
matplotlib.pyplot.legend()
matplotlib.pyplot.show()


# 政治家を分類する

#画像を表示する
politician_image1=keras.preprocessing.image.load_img("test/politician/Politician (212).jpg", target_size=(224,224), interpolation="lanczos")
matplotlib.pyplot.figure()
matplotlib.pyplot.imshow(politician_image1)
matplotlib.pyplot.show()

#分類結果を表示する
politician_tensor1=keras.preprocessing.image.img_to_array(politician_image1)
politician_tensor1=numpy.expand_dims(politician_tensor1, axis=0)
politician_tensor1 /= 255.0
print("[Doctor, Politician]="+str(model.predict(politician_tensor1)))
# 結果は政治家と博士である確率が2つ並んだベクトルである


# 博士を分類する

# 画像を表示する
doctor_image1=keras.preprocessing.image.load_img("test/doctor/Doctor (45).jpg", target_size=(224,224), interpolation="lanczos")
matplotlib.pyplot.figure()
matplotlib.pyplot.imshow(doctor_image1)
matplotlib.pyplot.show()

#分類結果を表示する
doctor_tensor1=keras.preprocessing.image.img_to_array(doctor_image1)
doctor_tensor1=numpy.expand_dims(doctor_tensor1, axis=0)
doctor_tensor1 /= 255.0
print("[Doctor, Politician]="+str(model.predict(doctor_tensor1)))


コメント

このブログの人気の投稿

有機物量子スピン液体の熱伝導論争の流れを振り返る

【改題】室温超伝導ふたたび!~大丈夫じゃなかった、Natureの論文だもん!~

2023年7月の気になった論文(完全版)