LoginSignup
0
1

More than 1 year has passed since last update.

サポートベクターマシンと畳み込みニューラルネットワーク(とディープラーニング)の比較

Posted at

自己紹介、目的

機械学習をゼロから学んでいる者です。ここまでの学習のまとめとして、3つの手法を用いて画像分類を行いましたので、その結果と簡単な分析をブログに記載します。使用した3手法は以下です。

サポートベクターマシン

ディープラーニングではない機械学習の代表格として。

ディープラーニング(CNN不使用)

結果としては全く上手くいかなかったのですが、比較のため記載します。

畳み込みニューラルネットワーク(CNN)

ディープラーニングの画像分類における有力な手法です。

環境

MacBookAir  early 2020, RAM 8GB, CPU intel core i5 (非M1)
OS: macOS Big Sur Ver. 11.4

docker上のJupyter NotebookのPython上にて行いました。ここで使用したdockerイメージには、tensorflow、scikit-learn、pandas、numpy、matplotlib、RandomUnderSamplerをインストールしています。この環境構築は以下の記事を参照してください。

結果概要

データセット

Scikit-learnから入手できるLFW people
4名の画像を各120枚づつ用意。
70%をトレーニング、30%をテストに使用。

各手法における正解率

サポートベクターマシン:正解率85%
ディープラーニング(CNN不使用):正解率25% *適当に分類しても1/4の確率で正解するので、意味ない・・・
畳み込みニューラルネットワーク(CNN):正解率79%

実行内容詳細

Jupyter Notebookではセルごとにpythonを実行できましたので、それと同様に実行した順に記載します。

データセットのインポート

from sklearn.datasets import fetch_lfw_people

LFW peopleのデータセットのインポートです。初回は少し時間がかかります。

データ確認1

face_data = fetch_lfw_people(min_faces_per_person=120)
X = face_data.data
Y = face_data.target
print("input data size:", X.shape)
print("output data size:", Y.shape)
print("label name:", face_data.target_names)

出力
input data size: (1031, 2914)
output data size: (1031,)
label name: ['Colin Powell' 'Donald Rumsfeld' 'George W Bush' 'Tony Blair']

1行目でface_dataにデータを格納しています。LFW peopleのデータセットには5749名分、13,233枚の画像が格納されていますが、人物により画像数にばらつきがあります。min_faces_per_personを指定することで人物毎の最少画像数を指定でき、120を指定すると4名の画像のみとなります。
Xにデータを、Yにターゲットを代入しました。X.shapeでXのサイズを確認して、行数1031は1031枚の画像ということ、列数2914は画像毎のデータ数は2914データ(縦62ピクセル、横47ピクセル)ということがわかりました。
4名の人物の名前を確認したところ、Colin Powell、Donald Rumsfeld、George W Bush、Tony Blairとなりました。

データ確認2

import matplotlib.pyplot as plt

fig, ax = plt.subplots(3, 4)
plt.subplots_adjust(wspace=0.8, hspace=0.5)

for i, axi in enumerate(ax.flat):
    axi.imshow(face_data.images[i], cmap='gray')
    axi.set(xticks=[], yticks=[], xlabel=face_data.target_names[face_data.target[i]])

plt.show()

出力

スクリーンショット 2021-07-30 17.55.55.png

matplotlibを用いてどのような画像か確認しました。

サンプル数確認

for i in range(len(face_data.target_names)):   
    print("{} has {} samples".format(face_data.target_names[i], (Y == i).sum()))

出力
Colin Powell has 236 samples
Donald Rumsfeld has 121 samples
George W Bush has 530 samples
Tony Blair has 144 samples

各人物毎の画像数を確認しました。データ数に偏りがあるので、次のセルでこれを是正します。

アンダーサンプリングによるデータの偏り修正

from imblearn.under_sampling import RandomUnderSampler
import pandas as pd

df=pd.DataFrame(X)
df['target']=Y

strategy = {0:120, 1:120, 2:120, 3:120}
rs=RandomUnderSampler(random_state=42, sampling_strategy = strategy)
df_sampled,_=rs.fit_resample(df,df.target)

print(df_sampled.target.value_counts())

出力
3  120
2  120
1  120
0  120
Name: target, dtype: int64

RandomUnderSamplerを用いてデータの偏りを無くしました。
Donald Rumsfeldさんが121枚でしたので、キリのいい120枚で統一しました。
出力にて120枚ずつにアンダーサンプリングできていることが確認できます。

訓練データとテストデータへの分割

from sklearn.model_selection import train_test_split

Y = df_sampled.target.values
X = df_sampled.iloc[:,0:2914].values
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.3,stratify=Y, random_state=0)

print(X.shape)
print(X_train.shape)
print(y_train.shape)
print(X_test.shape)
print(y_test.shape)

#念のため確認
df = pd.DataFrame(y_train)
df.value_counts()

出力
(480, 2914)
(336, 2914)
(336,)
(144, 2914)
(144,)
3 84
2 84
1 84
0 84
dtype: int64

ダウンサンプリングしたデータフレームからXとYにデータを戻します。
そして大変便利なtrain_test_splitを用いて訓練データとテストデータに分割します。
データのshapeを確認そたところ、それぞれ所望のものとなっていることが確認できました。
さらに念のためy_trainのデータ数を確認したところ、一名につき84枚としっかりデータの偏りがないことを確認しました。

サポートベクターマシン(SVC)にて分類

from sklearn import svm
from sklearn.metrics import accuracy_score

svc = svm.SVC(random_state=42,C=1, kernel='rbf', gamma=0.0000001)
svc.fit(X_train, y_train)
y_pred_svc = svc.predict(X_test)
print ("Accuracy: %.2f"%accuracy_score(y_test, y_pred))

print("predicted", y_pred_svc[0:20])
print("actual", y_test[0:20])

出力
Accuracy: 0.85
predicted [3 1 3 1 1 0 1 3 0 3 0 3 3 2 1 0 0 3 1 1]
actual [2 1 3 1 1 0 1 3 0 3 2 3 3 2 2 0 0 3 1 1]

さすがサポートベクターマシン、精度は85%を記録しました。
記述量もたったこれだけ、なんて簡単なんでしょう!さらに私の環境では1秒足らずで結果を出力します。

出力のpredictedとactualでは、testデータの最初の20枚の正誤について確認しています。
結果については後でもう少し分析します。

ディープラーニング(CNN不使用)にて分類

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Activation, Dense, Dropout, Input, BatchNormalization
from sklearn.metrics import mean_squared_error
import numpy as np
import pandas as pd

#Yデータをワンホットエンコーディング
y_train_onehot = pd.get_dummies(y_train)
y_test_onehot = pd.get_dummies(y_test)


model = Sequential()

model.add(Dense(2048, input_dim=2914))
model.add(Activation("relu"))
model.add(Dropout(rate=0.2))
model.add(Dense(64))
model.add(Activation("relu"))
model.add(Dropout(rate=0.2))

model.add(Dense(4))
model.add(Activation("softmax"))


model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"])

history = model.fit(X_train, y_train_onehot, epochs= 50, batch_size= 32, verbose=1, validation_data=(X_test, y_test_onehot) )

#スコア出力
score = model.evaluate(X_test, y_test_onehot, verbose=1)
print("evaluate loss: {0[0]}\nevaluate acc: {0[1]}".format(score))

pred = np.argmax(model.predict(X_test), axis=1)
print("predicted", pred[0:20])
print("actual", y_test[0:20])

plt.plot(history.history["accuracy"], label="accuracy", ls="-", marker="o")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.show()

出力
--epocについては省略--
evaluate loss: 1.386299729347229
evaluate acc: 0.25
predicted [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
actual [2 1 3 1 1 0 1 3 0 3 2 3 3 2 2 0 0 3 1 1]

スクリーンショット 2021-07-30 18.26.58.png

サポートベクターマシンでできるならCNNを使用しないディープラーニングでもできるでしょ!?との思いからやってみたものの、結果は散々でした。何度パラメータを変えても精度は25%になります。predictedを見ると分かるように、適当に結果を出力しているだけでした。どこかの設定ミスったのかな・・・。

ちなみに、ディープラーニングではYのラベルはワンホットエンコーディングをしなければエラーとなりました。多値分類でディープラーニングの出力層にsoftmaxを使用しているので、ワンホットエンコーディングが必要なようです。

畳み込みニューラルネットワーク(CNN)

from tensorflow.keras.layers import Dense, Dropout, Flatten, Activation
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.utils import to_categorical, plot_model
from tensorflow.keras.callbacks import EarlyStopping
import numpy as np
import matplotlib.pyplot as plt

#Yデータをワンホットエンコーディング
y_train_onehot = pd.get_dummies(y_train)
y_test_onehot = pd.get_dummies(y_test)

#Xデータを画像形式に変換
#Convレイヤーは4次元配列をとる(バッチサイズx縦x横xチャンネル数)
X_train = X_train.reshape(-1, 62, 47 , 1)
X_test = X_test.reshape(-1, 62, 47 , 1)

# モデルの定義
model = Sequential()

model.add(Conv2D(filters=32, kernel_size=(3, 3), input_shape=(62,47,1)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.4))
model.add(Conv2D(filters=32, kernel_size=(3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.4))
model.add(Flatten())
model.add(Dense(128))
model.add(Activation('relu'))
model.add(Dropout(0.5))

model.add(Dense(4))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
#early stoppingも導入する
earlyStopping = EarlyStopping(monitor = 'val_loss', patience = 50, mode ="auto")

history = model.fit(X_train, y_train_onehot, batch_size=64, epochs=1000, verbose=1, validation_data=(X_test, y_test_onehot))

#スコア出力
score = model.evaluate(X_test, y_test_onehot, verbose=1)
print("evaluate loss: {0[0]}\nevaluate acc: {0[1]}".format(score))

pred_cnn = np.argmax(model.predict(X_test), axis=1)
print("predicted", pred_cnn[0:20])
print("actual", y_test[0:20])

plt.plot(history.history["accuracy"], label="accuracy", ls="-", marker="o")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.show()

出力
--999/1000までのepocは省略--
Epoch 1000/1000
6/6 [==============================] - 1s 112ms/step - loss: 0.0164 - accuracy: 0.9881 - val_loss: 1.3945 - val_accuracy: 0.7917
5/5 [==============================] - 0s 18ms/step - loss: 1.3945 - accuracy: 0.7917
evaluate loss: 1.3944944143295288
evaluate acc: 0.7916666865348816
predicted [3 1 3 1 1 0 1 3 0 3 2 0 3 1 3 3 0 3 1 1]
actual [2 1 3 1 1 0 1 3 0 3 2 3 3 2 2 0 0 3 1 1]

スクリーンショット 2021-07-30 18.27.07.png

いや、さすがCNN。これぞディープラーニング。epoc毎の精度の上昇は見ていて気落ちがいいです。しかしながら、trainデータには精度98〜100%が出たのですが、testデータでの精度は79%でした。完全に過学習してます。

結果について

データ分析的なこともしたいので、サポートベクターマシン(SVC)と畳み込みニューラルネットワーク(CNN)がどのような誤分類をしたのか確認しました。

分析用のデータフレーム作成

result_svc = []
for i in range(len(y_test)):
    if y_pred_svc[i] != y_test[i]:
        result_svc.append([i, y_test[i], y_pred_svc[i]])

result_svc = pd.DataFrame(result_svc, columns = ["index number","actual","predict" ])

result_cnn = []
for i in range(len(y_test)):
    if pred_cnn[i] != y_test[i]:
        result_cnn.append([i, y_test[i], pred_cnn[i]])

result_cnn = pd.DataFrame(result_cnn, columns = ["index number","actual","predict" ])

このセルにてSVCとCNNのテストデータの予想が外れた結果のみを抽出して、result_svcとresult_cnnというpandasのデータフレームを作成しました。

サポートベクターマシン(SVC)の結果のまとめ

result_svc.groupby(["actual","predict"]).count().set_axis(["回数"], axis='columns')

出力

スクリーンショット 2021-07-31 14.21.20.png

"actual"と"predict"列でgroupbyを使用して、それぞれの回数を見てみました。例えば、表の一番上の結果は「0」の人を「1」の人に誤分類する回数は1回ということを表しています。ちなみにテストデータは各人物84枚です。
SVCの結果では、「2」の人を予想することが難しく、「2」の人を「0」の人に間違えることが多い(5回)ことがわかりました。

ちなみに、0〜3の人はそれぞれ['Colin Powell' 'Donald Rumsfeld' 'George W Bush' 'Tony Blair']です。

畳み込みニューラルネットワーク(CNN)の結果のまとめ

result_cnn.groupby(["actual","predict"]).count().set_axis(["回数"], axis='columns')

出力
スクリーンショット 2021-07-31 14.26.10.png

SVCと同様に"actual"と"predict"列でgroupbyを使用して、それぞれの回数を見てみました。CNNも「2」の人を分類するのが難しく、CNNの場合は「1」の人に誤分類することが多い(6回)ようです。

最後に

CNNの精度を上げる手法として画像の水増しする方法もあるのですが、そこまでは行いませんでした。
ハイパーパラメータの調整と合わせて今後時間があったらやってみようと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1