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

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

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

【方法】
 方法はかっこよく、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がそれぞれナニをしているか、ちゃんと勉強しないといけないなぁと思います。
次の記事では、もう少しいろんな人を分類してみたいと思います。

画像収集スクリプト
タイトル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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")

画像分類用プログラム
タイトル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# 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)))

コメント

このブログの人気の投稿

LLM技術を利用した常圧高温超伝導体の発見~AIリテラシーを高めるぞ!!!~

拙著はリジェクト また…流れるでござるよ~逆オッカムの剃刀を使って論文を魅力的に~

交代磁性があるのかないのかどっちなんだい!~RuO2の場合~