【Scikit-learn】Olivetti faces(顔写真)データセットで深層ニューラルネットワーク(DNN)してみる

機械学習

前回『【Scikit-learn】手書き数字データセットで深層ニューラルネットワーク(DNN)してみる』ここで、8×8の手書き数字を全結合層だけで分類(DNN)してみて、うまいこといきました。それじゃあもう少し難しい画像でも、ということで、【Scikit-learn】Olivetti facesデータセットこちらでDNNをやってみようと思います。

データの確認とか準備とか

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use("ggplot")

from keras import models
from keras import layers
from keras.callbacks import EarlyStopping
from sklearn.model_selection import train_test_split

いつものように、必要なライブラリをインポート。

from sklearn.datasets import fetch_olivetti_faces
faces=fetch_olivetti_faces()                        

で、今回はこちらを使います。どういうデータかというと、

  • 40人の方の顔写真(64×64)が各々10枚、計400枚の画像データ
  • 1人の方の10枚の画像は、みんな顔写真なんだけど、向きや表情等変えてるよ
  • 0から39でラベリングされてるので、誰の画像か判別する分類器作ってみてね

というものです。

print(faces.images.shape)
print(faces.data.shape)
print(faces.target.shape)
(400, 64, 64)
(400, 4096)
(400,)

images/data/targetに各々こんな感じでデータが入ってます。
1人目(名前:0)の方の画像を見てやると、

fig=plt.figure(figsize=(30,15))

def ax(x):
  return fig.add_subplot(2,5,x+1)

for i in range(10):
  ax(i)
  ax(i).matshow(faces.images[i])

plt.gray()
plt.tight_layout() 
plt.show()

このように、表情やら向きやらを微妙に変えておられるわけです。
で、こちらがどなたか、というと、

faces.target[0]
0

0の方、というわけです。全結合層のみでやっていくので、faces.dataこちらを使います。
1個目のデータを見てやると、

faces.data[0]
array([0.30991736, 0.3677686 , 0.41735536, ..., 0.15289256, 0.16115703,
       0.1570248 ], dtype=float32)

先のshapeで見た通り、色の濃淡が0から1までに正規化されて、4096個の数字で構成されています。
前回は64次元ベクトルだったわけですが、こちらは4096次元ベクトルとなります。
さて、全結合だけで戦えるのでしょうか?明らかに難易度上がってますよね。

print(np.unique(faces.target))
[ 0  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]

targetの方も見ておくと、このように0から39のいずれかが、400枚分入ってます。
40人が数字でラベリングされているわけです。
それでは、データを形成していきます。

x=faces.data
t_=faces.target
t_
array([ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
        3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
       ・・・・・・・
       37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
       38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
       39, 39, 39, 39, 39, 39, 39, 39, 39, 39])

1人ずつ順番に入ってますね。前回同様、最終の出力は、一個のdataベクトルに対し、0~39各々対する確率を出力させるので、入力もこの形に合わせます。

t_df=pd.get_dummies(t_)
t_df
0123456789101112131415161718192021222324252627282930313233343536373839
01000000000000000000000000000000000000000
11000000000000000000000000000000000000000
3980000000000000000000000000000000000000001
3990000000000000000000000000000000000000001

0の方から順番に格納されているので、こんな並びになります。
で、これを、データフレームからndarrayに戻して、

t=t_df.values
t
array([[1, 0, 0, ..., 0, 0, 0],
       [1, 0, 0, ..., 0, 0, 0],
       [1, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 1],
       [0, 0, 0, ..., 0, 0, 1],
       [0, 0, 0, ..., 0, 0, 1]], dtype=uint8)

学習用検証用テスト用にスプリット、

x_train,x_test,t_train,t_test=train_test_split(x,t,test_size=0.3,stratify=t,random_state=0)
x_train,x_train_val,t_train,t_train_val=train_test_split(x_train,t_train,test_size=0.2,stratify=t_train,random_state=0)
print(x_train.shape,x_train_val.shape,x_test.shape)
print(t_train.shape,t_train_val.shape,t_test.shape)
(224, 4096) (56, 4096) (120, 4096)
(224, 40) (56, 40) (120, 40)

できました。

DNNモデルの構築と学習

こんなモデルでやってみます。

model=models.Sequential()
model.add(layers.Dense(1024,activation="relu",input_dim=4096))
model.add(layers.Dense(512,activation="relu"))
model.add(layers.Dense(256,activation="relu"))
model.add(layers.Dense(128,activation="relu"))
model.add(layers.Dense(64,activation="relu"))
model.add(layers.Dense(40,activation="softmax"))
model.compile(optimizer="adam",
              loss="categorical_crossentropy",
              metrics=["accuracy"])

callbacks=[EarlyStopping(monitor="val_accuracy",patience=10)]

results=model.fit(x_train,
                  t_train,
                  epochs=1000,
                  batch_size=50,
                  verbose=1,
                  callbacks=callbacks,
                  validation_data=(x_train_val,t_train_val))
Epoch 17/1000
loss: 3.6884 - accuracy: 0.0268 - val_loss: 3.6913 - val_accuracy: 0.0179
Epoch 18/1000
loss: 3.6884 - accuracy: 0.0134 - val_loss: 3.6914 - val_accuracy: 0.0179
Epoch 19/1000
loss: 3.6883 - accuracy: 0.0268 - val_loss: 3.6916 - val_accuracy: 0.0179
Epoch 20/1000
loss: 3.6883 - accuracy: 0.0268 - val_loss: 3.6918 - val_accuracy: 0.0179

長いので、最後の数行の出力結果のみを書いていますが、
見ての通り、まーーったく分類器として成立していません。。。。
いろいろニューロン数を変えてみましたが、ことごとく同じような結果でした。

from sklearn import metrics
print(metrics.accuracy_score(t_train,np.round(model.predict(x_train))))
print(metrics.accuracy_score(t_train_val,np.round(model.predict(x_train_val))))
print(metrics.accuracy_score(t_test,np.round(model.predict(x_test))))
0.0
0.0
0.0

です。前回『【Scikit-learn】手書き数字データセットで深層ニューラルネットワーク(DNN)してみる』の手書き数字8×8=64次元では戦えましたが、4096次元では、全結合のみでは全く戦えないようです。
なるほど。

lightGBMでやってみる

このままでは終われないので、みんな大好きlightGBMでもやってみようと思います。

import lightgbm as lgb

lightGBMなので、こちらをそのまま使って、データを用意。

t_
x_train,x_test,t_train,t_test=train_test_split(x,t_,test_size=0.3,stratify=t_,random_state=0)
x_train,x_train_val,t_train,t_train_val=train_test_split(x_train,t_train,test_size=0.2,stratify=t_train,random_state=0)
print(x_train.shape,x_train_val.shape,x_test.shape)
print(t_train.shape,t_train_val.shape,t_test.shape)
(224, 4096) (56, 4096) (120, 4096)
(224,) (56,) (120,)

各種ハイパーパラメータやら決めて、

lgb_train=lgb.Dataset(x_train,t_train)
lgb_eval=lgb.Dataset(x_train_val,t_train_val)
params = {"metric":"multi_logloss",
          "objective":"multiclass", 
          "num_class":40,
          "max_depth":10}

lgbm = lgb.train(params,
                lgb_train,
                valid_sets=lgb_eval,
                num_boost_round=1000,
                early_stopping_rounds=100,
                verbose_eval=100)
Training until validation scores don't improve for 100 rounds.
[100]	valid_0's multi_logloss: 0.45537
[200]	valid_0's multi_logloss: 0.330648
Early stopping, best iteration is:
[145]	valid_0's multi_logloss: 0.330648

学習完了。

さて、正解率は?!

from sklearn import metrics
print(metrics.accuracy_score(t_train_.values,np.round(lgbm.predict(x_train))))
print(metrics.accuracy_score(t_test__.values,np.round(lgbm.predict(x_test))))
1.0
0.825

ということで、さすがのlightGBMといったところでしょうか、4096次元ベクトルだろうが、
それなりの正解率を叩き出してくれました。