scikit-learnでの顔判定とサポートベクターマシン学習結果分析

ある研修(勉強会)でマシンラーニング／ディープラーニングに触れてみたのでまとめてみました。本稿の内容は表記の通りscikit-learnでの顔判定（認証）です。
＜レジュメ＞
・研修課題
・開発環境
・実装内容、結果
・機械学習の結果分析


# 研修課題
機械学習または深層学習の勉強として、以下のお題が与えられました。

* 研修メンバーの4人の顔を判別するプログラムを作成する
* 材料は、１人100枚の顔画像（うち50枚はマスクつけた状態）4人分の計400画像
* 言語は基本的にPython（Pythonのお勉強も含めて）
* どの機械学習、深層学習ライブラリを使うかは自由

# 開発環境
Window10：Core i7-8550U 1.8GHz：メモリ8GB
以下、Anaconda3でまるっとインストール
python 3.7.6
scikit-learn 0.22.1
Jupyter Notebook 6.0.3
Spyder 4.0.1 

進め方はJupyter Notebookでとりあえず書いて逐次実行してみて、
実行中の変数やライブラリの中身を見たいときはSpyderでデバッグしました。


**scikit-learnと前述しましたが、TensorFlow、PyTorch、Chainer等、いろいろある機械学習/深層学習ライブラリの中で、私はscikit-learnを選択しました。**

# 参考サイト
以下、お世話になったサイトです。
[scikit-learnとMacカメラで撮影した顔写真を使って、リアルタイム顔判別を行ってみた](https://qiita.com/daiarg/items/3ea91b08f0d1cb5bfc61) ← めちゃくちゃ参考にさせていただきました。
[note.nkmk.me](https://note.nkmk.me/) ← Python、特にNumpyの使い方めっちゃガン見させてもらいました。
https://scikit-learn.org/ ← scikit-learn公式サイト

# 実装
次の3つに分けて記載します。
**1. 画像データの用意・加工**
**2. 学習させる**
**3. 学習結果の検証(テスト)**

### １．画像データの用意・加工

みんなで撮影して用意した画像は以下のような感じになります（載せてるのは私の画像だけ）
撮った画像は640x480としてファイル化してます。
![kao100mai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649784/dc52d6ed-f9e7-559f-b503-189318458063.png)
<font color="Grey">*(取込みプログラムは他の方にC++＆OpenCVで作ってもらいました。私ならC#一択です。。)*</font>

これらをプログラムで読み込み64x48モノクロ画像に落とし、学習モデルに渡すために1画像[64,48]のバイト配列を一次元配列に変換し1データとしました。

![6448-3072.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649784/d1277457-d84b-7483-e8b0-c2c3165ac995.png)

これで用意した400枚の画像データは[3072,400]の2次元配列となります。

###**2. 学習させる**
学習＆判定させる分類器はサポートベクターマシンの`LinearSVC`クラスを使用します。
学習前にライブラリにおまかせで、400画像のデータを学習用データ(300画像)とテスト用データ(100画像)に分けます。
学習データとして300画像分を`X_train`に、それに対応する人のラベル(Aさん、Bさん、Cさん、Dさん)配列を`y_train`に突っ込むと学習のコードは以下になります。実質2行です。<font color="Grey">*え、たったこれだけ！:scream:*</font>

```python

#サポートベクターマシン 線形分類器
from sklearn import svm
clf_linerSVC = svm.LinearSVC() 

#学習
clf_linerSVC.fit(X_train,y_train)
```
簡単すぎです。。。
ちなみに学習にかかった処理時間は、2.5秒程度でした。

##**3. 学習結果の検証(テスト)**
テスト用に分けておいた100画像のデータ`X_test`、`y_test`を使って分類器がちゃんと認識してくれるかチェックします。

```python
#分類器にテストデータを渡し、予測(分類)させる
y_pred = clf_linerSVC.predict(X_test)

#正解データと予測データをもとに正答率を出す
from sklearn.metrics import accuracy_score
ac_score = accuracy_score(y_test,y_pred)
print("正答率 = ",ac_score)
```
正答率は98%（2画像認識誤り）でした。
評価指標等もう少し見てみます。

```python
#評価指標を出してもらう
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
```
|   | Precision(適合率)| Recall(再現率) | f1-score(調和平均) | support(データ数) |
|:-:|:-:|:-:|:-:|:-:|
| Aさん |1.00|0.94|0.97|31|
| Bさん |0.95|1.00|0.98|21|
| Cさん |1.00|1.00|1.00|25|
| Dさん |0.96|1.00|0.98|23|
| accuracy|   |   |0.98|100|

Precision(適合率)は、分類器がXさんと判定したものが実際にXさんだった割合
Recall(再現率)は、Xさんの画像をXさんと判定した割合
f1-score(調和平均)は、Precision(適合率)と Recall(再現率)を総合した値です。

```python
#認識結果のマトリクス
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_test, y_pred))
```
|  | A | B | C | D |
|:-:|:-:|:-:|:-:|:-:|
| A | 29 | 1 | 0 | 1 |
| B | 0 | 21 | 0 | 0 |
| C | 0 | 0 | 25 | 0 |
| D | 0 | 0 | 0 | 23 |

評価指標と比べてみてもわかるように
Aさんの画像のうち１つはBさん、１つはDさんと誤検出してます。

以降、分類器の分析です。

#サポートベクターマシンの学習結果の分析
###1.**サポートベクターマシン**

今回使った分類器はサポートベクターマシン(SVM)です。
最も近いサンプルとの距離（マージン）が最大となるように境界を引いてくれるやつです。
以下、説明でよく見る2次元(3次元)の絵です。

![SVM2次元の絵.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649784/fef02f4f-1011-33dc-6c62-2ab547fa23e9.png)

いや~、2次元だと素人の私でも理解できますね。
今回はこれで画像分類(顔判定)したということです。
ん？さっぱりわけわからん。全くつながらず意味不明です。
でもいいんです、AIの民主化が叫ばれている今、そんなもんなんです。気にする必要はありません。
：
やべぇ、気になる、不思議すぎる。

ということで、少しサポートベクターマシンを調べました。
ソフトマージンとかラグランジュの未定乗数法とかカーネル法とかいろいろでてきました。
無理ゲーでした。。。

でも気になる。プログラムで使った`LinearSVC`クラスは学習して、その分類手段を保持してるんでしょ？Youの腹ん中（頭の中？）はどうなんっとんじゃい！
ということで覗いてみました。

###**2. `LinearSVC`クラスが算出した式**
`LinearSVC`クラスに`coef_`、`intercept_`というメンバがいました。interceptは切片ですよね。coefは係数？[scikit-learn公式サイトのLinearSVCクラス](https://scikit-learn.org/stable/modules/generated/sklearn.svm.LinearSVC.html)をみると`coef_`は重みとありました。そして学習させたモデルの`coef_`には[3702,4]のfloat配列が格納されていました。ちなみに`intercept_`は4個のfloat値配列でした。
どうやらXが`n=3,072`の境界線の式の重み(`a`)と切片(`b`)を4人分の4式分持ってるようです。
![svm標準式2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649784/7c4b7f70-e2a6-0ec8-182f-1a79923cda50.png)

そのうち1組の値を式に表してみると以下のようになりました。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649784/2631c111-4044-515f-88ef-7c96546ff5b5.png)

おぉ、3,072次元の方程式だ。。。
ということは、1画像につき64x48=3072の値をそれぞれ1つの特徴量として演算してくれたってことですね。*そういえば`LinearSVC`クラスには画像ですよって全く伝えてなかった。というか伝える方法もないし、彼が画像に対応しているわけでもないです。* **要素数3,072の情報ひとつひとつを特徴量として計算して４つの式を生み出してくれたんですね。サポートベクターマシンクラスすげぇ！**

重みづけの値を表にプロットしてみたらちょっとおもしろい結果になったのですが、長くなるのでまたの機会に。。。

以上、ありがとうございました。

ソースはGithubに
https://github.com/nojahide/FaceRecognition