機械学習初心者向け 浜田と松本どっちがゴリラか

動機

私は、バラエティが大好きです。特に「水曜日のダウンタウン」と「M1グランプリ」が大好きです。ダウンタウン浜田雅功氏がテレビで”ゴリラ”といじられているのをたびたび見かけますが、いじっている張本人である松本人志氏の方がゴリラに似ているのでは???と私は長年疑問に思い続けてきました。その辺を大学の講義で習った画像分類というものを用いて白黒つけてみようと思った所存です。

1. 2人の顔を機械に教える

画像の取得・加工

機械は、日本人の9割以上が知っているであろうダウンタウンのことを知りません。2人の顔写真を渡して、覚えてもらう必要があります。

顔写真を取得します。大量の画像を自動取得してくる方法もあることを知り見よう見まねで試してみましたが、使えない画像の方が多く逆に面倒なことになったので、今回はgoogle chromeで「松本人志」「浜田雅功」と検索し、顔がはっきりと写っているものを20枚ずつ取得することにしました。

取得した20枚の画像には、顔だけの画像もあれば、全身の画像もあります。スーツを着ている画像もあれば、松本氏が裸で肉体美を見せつけている画像もあります。「服を着ていないからゴリラは松本氏に似ている」などと分類されてしまっては、困ります。今回はあくまで顔がどちらに似ているかを知りたいと考えているからです。

よって、画像を加工する必要があります。これまた、自動で加工する方法もあるそうなのですが、元画像のサイズや切り取る箇所は画像によりまちまちで、私には難しすぎる気がしたため、諦めました。今回は、「PhotoScape X」というアプリを使って手作業で加工することにしました。

それぞれの画像の顔の部分だけを256×256ピクセルのサイズで切り取りました。256という値は適当です。サイズを揃えないと後々面倒くさいので揃えることに意味があります。

これだけでもいいと思うのですが、松本氏は金髪の画像が多く、髪色によってゴリラが浜田氏であると分類されてしまっては嫌だったので、白黒画像にすることにしました。色のオプションで「無彩色」というものを選べたので、とても簡単に白黒画像にする事ができました(厳密には無彩色画像=白黒画像ではないらしい)。

f:id:iiko_11:20211022124220p:plainf:id:iiko_11:20211022124338p:plainf:id:iiko_11:20211022124423p:plainf:id:iiko_11:20211022124429p:plain

機械に画像を渡す

いよいよPython3でコードを書いていきます。好きな場所に新規フォルダを作成し、そこにgorilla_classify.pyというファイルとdatasetフォルダを作ります。datasetフォルダにhamadaフォルダとmatsumotoフォルダを作成し、先ほど加工した画像をそれぞれ保存します。以下、gorilla_classify.pyにコードを書き込んでいきます。

import numpy as np
import glob
from PIL import Image

num_of_hamada=20 #浜田氏の画像数
num_of_matsumoto=20 #松本氏の画像数
num_of_total=num_of_hamada+num_of_matsumoto #総画像数
N_col = 256*256*4 # 行列の列数
x = np.zeros((num_of_total, N_col)) # 松本氏浜田氏の画像情報格納のためのゼロ行列生成
y = np.zeros((num_of_total)) # 松本氏浜田氏の画像に対応するラベルを格納するためのゼロ行列生成

path_list = glob.glob("./dataset/hamada/*") #hamadaフォルダに入っている画像までのパスを全て格納
i = 0

for item in path_list:
    im = Image.open(item) #画像を取得
    im_array = np.ravel(np.asarray(im)) # 画像を配列に変換
    x[i,:] = im_array #配列xのi番目にi番目の画像を表す配列を格納
    y[i] = 0 #i番目の画像が浜田氏の画像だよってことをy[i]=0と定義
    i += 1

path_list = glob.glob("./dataset/matsumoto/*") #matsumotoフォルダに入っている画像までのパスを全て格納

for item in path_list:
    im = Image.open(item) #画像を取得
    im_array = np.ravel(np.asarray(im)) # 画像を配列に変換
    x[i,:] = im_array #配列xのi番目にi番目の画像を表す配列を格納
    y[i] = 1 #i番目の画像が松本氏の画像だよってことをy[i]=1と定義
    i +=1
これにより、配列xには各画像を表す配列、配列yにはその画像が松本氏、浜田氏のどちらであるかの情報が格納されました。

2. 機械に見分け方を学習してもらう

頭のいい人たちによって、画像の見分け方の学習方法は複数種類生み出されています。今回はSVM(サポートベクタマシン)というものを使います。人に説明できるほど理解できてないので、詳しく知りたい方は「SVM」でググってください。理解しなくても読み進められます。

見分け方の学習自体は、先ほど用意した配列xとyを使って

from sklearn import svm
clf = svm.SVC()
clf.fit(x,y) #見分け方を学習する

のたった3行のみで学習できてしまいます。先人たちに感謝です。

しかし、xとyをこのまま使うと、困ったことになるのでこのコードは使いません。次の章で説明します。

3. 学習した見分け方が正しいか検証する。

最初に用意した配列x、yを使用したいのでgorilla_classify.pyに引き続き記述していきます。

機械がゴリラをどちらに分類するかを実験することが最終目標ですが、これは機械が松本氏と浜田氏の区別がしっかりできるようになっていることが大前提です。この機械は本当に松本氏と浜田氏を見極められるようになっているのでしょうか。検証する必要があります。

この検証で注意すべき点は、ズルをしないことです。最初に、それぞれ20枚の画像を機械に渡しました。すでに機械に答えを教えた画像に関して、「この画像は松本氏と浜田氏のどちらですか?」と聞いても、当然間違えません。そこで学習のために機械に渡した画像と検証するための画像を別のものに必要があります。しかし、画像は20枚ずつしか用意しておらず、これ以上増やすのは面倒です。よって、「40枚の画像を、学習用の画像と検証用の画像に分割し、正答率を計測する」という操作を複数回行います。

from sklearn import svm

def svc_classifier(x_train, y_train, x_test, y_test):
    clf = svm.SVC()
    clf.fit(x_train, y_train) #見分け方を学習する
    pred = clf.predict(x_test) #学習した見分け方に基づいてテスト画像を分類
    mispred = (pred != y_test).sum() #テスト画像のうち正しく分類できなかった画像の枚数
    return 1 - mispred / x_test.shape[0] #正しく分類できなかった画像の割合を返す

from sklearn.model_selection import train_test_split

def test_model(x,y,itertimes):
    now_svc=0
    for i in range(itertimes): #学習と検証をitertimes行う
        x_train, x_test, y_train, y_test = train_test_split(x, y) #x、yを学習用と検証用にランダムに分割

        now_svc+=svc_classifier(x_train,y_train,x_test,y_test)

    print(now_svc/itertimes) #正答率(正しく見分けられた割合)の平均を出力

test_model(x,y,100)

先ほど説明した、見分け方を学習するclt.fitの引数のx、yに対応する部分がx_train、y_trainに変化している事がわかると思います。

これにより、SVMアルゴリズムによって松本氏と浜田氏の見極めを正しくできるようになるのかを検証する事ができます。

python3 gorilla_classify.pyを実行したところ、0.9990000000000001と出力されました。すなわち99.9%の精度で松本氏と浜田氏の見極めができているといえます。

4. 機械にゴリラの顔を渡す

先ほどと同じ要領で、gorillaフォルダを作成し、google chromeから20枚のゴリラ画像を取得します。そして、画像加工アプリで256×256ピクセルのサイズに切り取り、無彩色画像に変更しました。

f:id:iiko_11:20211022160015p:plainf:id:iiko_11:20211022160020p:plain


引き続き、gorilla_classify.pyにコードを書いていきます。基本的には、松本氏浜田氏の顔を機械に覚えさせた時と同じです。違いは配列y_gorillaを用意する必要がない事です。ゴリラの画像を与えて、これがゴリラだよって機械に教えたい訳ではないからです。ゴリラの画像情報を機械に渡すだけでオッケーです。

num_of_gorilla=20 #ゴリラの画像数
x_gorilla=np.zeros((num_of_gorilla,N_col)) #ゴリラの画像情報格納のためのゼロ行列生成

path_list = glob.glob("./dataset/gorilla/*") #gorillaフォルダに入っている画像までのパスを全て格納
i=0

for item in path_list:
    im = Image.open(item) #画像を取得
    im_array = np.ravel(np.asarray(im)) #画像を配列に変換
    x_gorilla[i,:] = im_array #配列x_gorillaのi番目にi番目のゴリラ画像を表す配列を格納
    i += 1

5. 機械はゴリラをどちらと判断するのか

いよいよ本題に入ります。松本氏と浜田氏を正しく見極める事が可能になった機械はゴリラをどちらだと認識するのでしょうか。ここでは、モデルの検証を行う必要がないので、松本氏浜田氏の画像40枚全てを使って、学習を行います。

clf = svm.SVC()
clf.fit(x, y) #検証は終わったので、松本氏浜田氏の画像40枚全てを使って学習する
gorilla_pred = clf.predict(x_gorilla) #ゴリラの画像はどちらであるか見分ける 浜田氏と判断すれば0 松本氏と判断すれば1
hamada=0
matsumoto=0
for i in gorilla_pred: #len(gorilla_pred)=20
    if(i==0): #ゴリラの画像を浜田氏と判断した時
        hamada+=1
    else: #ゴリラの画像を松本氏と判断した時
        matsumoto+=1
print(f"hamada:{hamada}/{hamada+matsumoto}") #ゴリラの画像20枚のうち、浜田氏と判断したされた数
print(f"matsumoto:{matsumoto}/{(hamada+matsumoto)}") #ゴリラの画像20枚のうち、松本氏と判断された数

出力は、以下のようになりました。

f:id:iiko_11:20211022155546p:plain

つまり、機械にゴリラの画像を20枚渡して、「松本氏と浜田氏どっちに見える?って聞くと、機械が「全部松本氏に見える」って答えたことになります。

総括

この結果を見ると、浜田氏に対するゴリラいじりが不当極まりないものであったと結論づけることができます。また、ゴリラいじりをされた際、笑みを浮かべ受け入れているように見えてしまう浜田氏の反応は、完全に間違っており、正解は「お前の方がゴリラやろがい!」であったことがわかります。

この事実は、世間に流布している「浜田氏といえばゴリラ、ゴリラといえば浜田氏」という誤った認識を根底から覆すものであり、日本中を震撼させるものと言っても過言ではありません。周囲の人に、一刻も早く夢から醒めるように説得してほしいと切に願います。

調子に乗りました。今回の手法にも、ほんの少しの正当性はあると信じたいです。しかし、データ数が圧倒的に少ない点、加工した画像にも髪色の違いが根強く残っており、これが大きな影響を及ぼした可能性がある点などを考えると、とても正当であると言い切ることはできません。

間違いやアドバイス等ありましたら、教えていただきたく思います。拙文を読んでいただきありがとうございました。