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

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

######サポートベクターマシン
ディープラーニングではない機械学習の代表格として。
######ディープラーニング（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をインストールしています。この環境構築は以下の記事を参照してください。

https://qiita.com/AAflon143/items/506c3b229c292b685f1a

https://qiita.com/AAflon143/items/913991c915524b129c2f

#結果概要
####データセット
Scikit-learnから入手できるLFW people
４名の画像を各120枚づつ用意。
70%をトレーニング、30%をテストに使用。

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


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

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

```python
from sklearn.datasets import fetch_lfw_people
```

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

###データ確認１
```python
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を指定すると４名の画像のみとなります。
Xにデータを、Yにターゲットを代入しました。X.shapeでXのサイズを確認して、行数1031は1031枚の画像ということ、列数2914は画像毎のデータ数は2914データ（縦62ピクセル、横47ピクセル）ということがわかりました。
４名の人物の名前を確認したところ、Colin Powell、Donald Rumsfeld、George W Bush、Tony Blairとなりました。

###データ確認2
```python
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710026/f94a67ef-9644-234d-1dad-0bedd4199e73.png)

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

###サンプル数確認

```python
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

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

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

```python
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枚ずつにアンダーサンプリングできていることが確認できます。

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

```python
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)にて分類

```python
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%を記録しました。
記述量もたったこれだけ、なんて簡単なんでしょう！さらに私の環境では１秒足らずで結果を出力します。

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

###ディープラーニング（CNN不使用）にて分類

```python
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710026/e53181dd-780d-bee7-c74e-054e01fff59f.png)


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

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

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

```python
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レイヤーは４次元配列をとる（バッチサイズ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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710026/261d5b5d-b680-fe46-1870-493e6369b11f.png)

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


#結果について

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

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

```python
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)の結果のまとめ

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

出力

![スクリーンショット 2021-07-31 14.21.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710026/914c9df2-51c4-1b6d-b63b-300478a8d611.png)

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

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

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

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

出力
![スクリーンショット 2021-07-31 14.26.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710026/38c7e8ad-96bc-c1cc-980f-2a8ac5b5cbd7.png)

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

#最後に

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