GPUを使ってVGG16をFine Tuningして、顔認識AIを作って見た

#概要
前回書いた記事の続きです。
[KerasのCNNで、顔認識AIを作って見た〜スクレイピングからモデルまで〜](https://qiita.com/yottyann1221/items/a08300b572206075ee9f)

前回は、KerasのCNNを利用して、女優４人の分類に挑戦しました。
１から画像を識別する分類機を作成する場合、大量の画像データが必要ですが、１人400枚（水増しして１人1500~1800枚）で学習しても、精度が約８０％くらいしか出せませんでした。



そこで、すでに訓練した学習済みモデルを利用するファインチューニングという手法で再挑戦します。
また、学習時間短縮のために,Google Colaboratoryを利用して、無料でGPUを使いながらファインチューニングします。

#こんなことやりたい
**石原さとみの可能性：99.9%!!!**
<img width="395" alt="完成済みモデルを読み込み、実行する-3.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/a3cc210c-2229-1705-1204-d6da672ea14c.png">

#用語解説
- Fine Tuning :学習済みモデルの一部を再利用して、新しいモデルを構築する手法
- VGG16 :ImageNetの一般物体認識データセットで学習済みのモデル。１０００種類の画像を分類できるモデル。
- Keras :機械学習を簡単に使うためのライブラリ
- CNN :Convolutional Neural Network（畳み込みニューラルネットワーク）
- Google Colaboratory：ブラウザ上でPythonを実行できる環境を提供しているツール。Googleが機械学習の教育や研究用に提供している。

#全体の流れ
【ステップ１】
画像データの準備
【ステップ２】
Google Colaboratoryの準備
【ステップ3】
モデルの構築
【ステップ4】
モデルの学習・評価


#【ステップ１】 画像データの準備

スクレイピングでGoogle画像検索から画像を取得し、顔を検出して切り抜く。
また、画像の水増し処理については、前回の記事を参考にしてください。
[KerasのCNNで、顔認識AIを作って見た〜スクレイピングからモデルまで〜](https://qiita.com/yottyann1221/items/a08300b572206075ee9f)

![モザイ３.png](https://qiita-image-store.s3.amazonaws.com/0/322279/d4e3b221-db11-0b88-b373-84d13d5fd2d4.png)

#【ステップ２】　Google Colaboratoryの準備
無料でGPUを使うために、Google Colaboratoryを利用します。
Google Colaboratoryを使ったことがない方は、下記の記事が参考になりました。
[Google Colabの知っておくべき使い方 – Google Colaboratoryのメリット・デメリットや基本操作のまとめ](https://www.codexa.net/how-to-use-google-colaboratory/)

###GPUを設定

画面上部のメニュー
 `編集` > `ノートブックの設定`>`ハードウェア　アクセラレータ`>`GPU`に変更して保存する。
<img width="1372" alt="機械学習_-_Colaboratory.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/75534cbc-6fc1-2e33-31b3-46ed6b5412df.png">


<img width="521" alt="機械学習_-_Colaboratory-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/5b5d945b-440e-109a-1c56-a8a9fa73c4e6.png">


###GPUの割り当てを確認

セルに下記を入力して、実行
実行は`Shift`+`Enter`

```python
import tensorflow as tf
tf.test.gpu_device_name()
```

下記のように出力されていれば、GPUが割り当てられています。

```
'/device:GPU:0'
```

###Google Driveに画像データをあげる
colaboratoryを利用して、学習を行うには、Google driveに画像データをアップする必要があります。
そして、Google Driveへのマウントをすることで、colaboratoryからGoogle driveにあるデータを参照できるようになります。

前回の記事で書いた方法で水増しした画像を、Google driveにアップロードしてください。
※私の場合
`FaceEdited` トレーニングデータ用のディレクトリ（１人1500~1800枚）
`test` 　　　  バリデーション用データ用のディレクトリ（１人約400枚）

<img width="1133" alt="Colab_Notebooks_-_Google_ドライブ.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/262c4550-4009-3250-70a5-bd414a74d941.png">


ディレクトリ構造を下記に示します。

> content
>　├　gdrive
>　　　└ My Drive
>　　　　　└ Colab Notebooks
>　　　　　　　├　FaceEdited
>　　　　　　　│　　└ 広瀬すず
>　　　　　　　│　　└ 新垣結衣
>　　　　　　　│　　└ 石原さとみ
>　　　　　　　│　　└ 武井咲
>　　　　　　　├　　test
>　　　　　　　│　　└ 広瀬すず
>　　　　　　　│　　└ 新垣結衣
>　　　　　　　│　　└ 石原さとみ
>　　　　　　　│　　└ 武井咲


自分のディレクトリの確認をしたいときは、Linuxの基本コマンドを使ってください。
あとでトレーニングデータとテストデータのパスを取得する時に必要になります。

現在のディレクトリ（カレントディレクトリ）を確認


```python
!pwd
```


ディレクトリの内容を表示

```python
!ls
```

ディレクトリを移動


```python
%cd
```

###Google Driveをマウント

以下のコードで、content/gdrive/My Drive/のなかにgoogle driveのデータが入ります

> content
>　├　gdrive
>　　└ My Drive

```python
from google.colab import drive
drive.mount('/content/gdrive')
```

実行すると、URLと認証コードの入力フォームが表示されます。
URLをクリックして、googleアカウントの認証コードを取得してください。
認証コードをコピーして、入力フォームに貼り付けると、content/gdrive/My Drive/のなかにgoogle driveのデータが入ります。

#【ステップ3】モデルの構築
###ライブラリの読み込み

```python
import os
from keras.applications.vgg16 import VGG16
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential, Model
from keras.layers import Input, Activation, Dropout, Flatten, Dense
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
import numpy as np
import time
```

###事前に設定するパラメータ

```python
# 分類するクラス
classes = ["広瀬すず","新垣結衣","石原さとみ","武井咲"]
nb_classes = len(classes)
#画像の大きさを設定
img_width, img_height = 150, 150

# トレーニング用とバリデーション用の画像格納先（パスは自分で設定してください）
train_data_dir = '/content/gdrive/My Drive/Colab Notebooks/FaceEdited'
validation_data_dir = '/content/gdrive/My Drive/Colab Notebooks/test'

#トレーニングデータ用の画像数
nb_train_samples = 7000
#バリデーション用の画像数
nb_validation_samples = 1000
#バッチサイズ
batch_size = 100
#エポック数
nb_epoch = 20
```



`バッチサイズ` 
ディープラーニングは、データセットを幾つかのサブセットに分けて学習します。その際の、サブセットに含まれるデータの数のことです。

`エポック数`
１.データセットをバッチサイズに従ってN個のサブセットに分けます。
2.各サブセットを学習します。つまり、N回学習します。
1と2の手順を1回実行することを1エポックと呼びます。

トレーニングデータ用の画像数　7000
バッチサイズ 100
のとき、サブセットが7000÷100=70個できます。

エポック数 20
のとき、1と2の手順を20回実行します。


###トレーンング用、バリデーション用データを生成するジェネレータ作成

画像に対して移動、回転、拡大・縮小などの操作を加えることでデータ数を水増しします。
`ImageDataGenerator`：画像の水増しを簡単に行えるクラス。オプションで、水増し方法を決めます。
`zoom_range`:画像をランダムにズームします
`horizontal_flip`：画像を水平方向にランダムに反転します
既に画像の水増し済みの方は、`zoom_range`と`horizontal_flip`を使う必要はありません。

参考にさせていただいたサイトを載せておきます。
[Kerasによるデータ拡張](http://aidiary.hatenablog.com/entry/20161212/1481549365)

```python
# トレーンング用、バリデーション用データを生成するジェネレータ作成
train_datagen = ImageDataGenerator(
  rescale=1.0 / 255,
  #すでに画像の水増し済みの方は、下記２行は必要ありません。
  #zoom_range=0.2,
  #horizontal_flip=True
)

validation_datagen = ImageDataGenerator(rescale=1.0 / 255)
```
`flow_from_directory `：元画像が含まれるディレクトリ名を指定する
`target_size`:画像のサイズをリサイズする
`class_mode`:2クラス分類なら`binary`、多クラス分類なら`categorical`を指定する

```python
train_generator = train_datagen.flow_from_directory(
  train_data_dir,
  target_size=(img_width, img_height),
  color_mode='rgb',
  classes=classes,
  class_mode='categorical',
  batch_size=batch_size,
  shuffle=True)

validation_generator = validation_datagen.flow_from_directory(
  validation_data_dir,
  target_size=(img_width, img_height),
  color_mode='rgb',
  classes=classes,
  class_mode='categorical',
  batch_size=batch_size,
  shuffle=True)
```
上記のコードを実行すると、トレーニングデータとバリデーションデータの総数が表示されます。

<img width="862" alt="ファインチューニング_-_Colaboratory.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/33a83692-eddf-1b8a-6305-ed783fa982cf.png">

#【ステップ3】 モデルの構築
###VGG16とは
**VGG16**は、畳み込み13層とフル結合3層の計16層から成る畳み込みニューラルネットワークです。ImageNetと呼ばれる大規模な画像データセットを使って訓練したモデルで、**1000種類の画像**を分類することができます。
Kerasでは、ImageNetで事前学習した重みを利用可能なVGG16モデルを、下記のコードで簡単に使うことができます。

```python
keras.applications.vgg16.VGG16(include_top=True, weights='imagenet', input_tensor=None, input_shape=None, pooling=None, classes=1000)
```

他にも様々なモデルが利用可能です。
<img width="538" alt="スクリーンショット 2019-01-12 12.02.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/a0586fce-6231-e593-3e7c-426173f034a9.png">

詳しくはリンクを参照してください。
[Keras Documentation](https://keras.io/ja/applications/#vgg16
)


###VGG16をFine-tuning
**Fine-tuning**は、学習済モデルを、**重みデータを一部再学習**して特徴量抽出機として利用します
**転移学習**は、学習済みモデルを、**重みデータは変更せず**に特徴量抽出機として利用します
**Fine-tuning**のメリットは、ニューラルネットの重みを<font color="Red">**少量のデータ**</font>で再調整することができて、かつ、高い精度を出せることです。

今回は下の画像の
<font color="Blue">**青い層**</font>**：重みデータは変更しない(frozen)**
<font color="Gold">**黄色い層**</font>**：重みデータを再学習(Fine-tuning)**
<font color="ForestGreen">**緑の層**</font>**：フル結合層（Fine-tuning）**



<div align="center">
 <img width="30%" alt="20170110205228.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/42db31cc-adad-32e4-2c85-f27c319bc257.png">
</div>


###kerasでモデルを構築

```python
# VGG16のロード。FC層は不要なので include_top=False
input_tensor = Input(shape=(img_width, img_height, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

# VGG16の図の緑色の部分（FC層）の作成
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(nb_classes, activation='softmax'))

# VGG16とFC層を結合してモデルを作成（完成図が上の図）
vgg_model = Model(input=vgg16.input, output=top_model(vgg16.output))

# VGG16の図の青色の部分は重みを固定（frozen）
for layer in vgg_model.layers[:15]:
    layer.trainable = False

# 多クラス分類を指定
vgg_model.compile(loss='categorical_crossentropy',
          optimizer=optimizers.SGD(lr=1e-3, momentum=0.9),
          metrics=['accuracy'])
```

#【ステップ4】 モデルの学習・評価

###モデルの学習

```python
# Fine-tuning
history = vgg_model.fit_generator(
    train_generator,
    samples_per_epoch=nb_train_samples,
    nb_epoch=nb_epoch,
    validation_data=validation_generator,
    nb_val_samples=nb_validation_samples)
```

<img width="1367" alt="ファインチューニング_-_Colaboratory-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/36831098-54dd-e596-b82f-fd1dc7536014.png">

GPUを使って約３時間ほど学習に時間がかかりました。
なぜか毎回、1Epoch目の学習時間が遅いのですが、原因は不明です。。。

###モデルの評価
学習が終わったら、下記のコードで結果を描写します。

```python
# 学習結果を描写
import matplotlib.pyplot as plt

#acc, val_accのプロット
plt.plot(history.history["acc"], label="acc", ls="-", marker="o")
plt.plot(history.history["val_acc"], label="val_acc", ls="-", marker="x")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
#Final.pngという名前で、結果を保存
plt.savefig('Final.png')
plt.show()
```

![Final.png](https://qiita-image-store.s3.amazonaws.com/0/322279/f78e51ef-fc19-8051-955e-345b715fdd17.png)

２０epoch学習しました。5epochあたりから90%を超えています。最終的に98,99%あたりで落ち着きました。

前回の記事で、CNNを１から学習させたときは、80%止まりだったので、大幅に精度が上がりました！
<img width="421" alt="スクリーンショット 2018-12-24 16.43.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/236ef642-fabc-c6a1-5e3a-3e651cfebb47.png">
[KerasのCNNで、顔認識AIを作って見た〜スクレイピングからモデルまで〜](https://qiita.com/yottyann1221/items/a08300b572206075ee9f)

###女優の分類
ローカルにモデルを保存してから実行しました。詳しくは下記を参照してください。
[Google Colaboratoryで学習したモデルをローカルに保存・モデルに読み込み・実行する](https://qiita.com/yottyann1221/items/d465c033dd70b2e53598)
写真の下にある数字は、精度（%）を表しています。例えば下の写真なら、
[広瀬すず：99.3%,　新垣結衣：0.002%,　石原さとみ:0.07%,　武井咲：0.59%]
という意味です。

<img width="394" alt="完成済みモデルを読み込み、実行する.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/0c49e3b7-87c3-1561-d238-60204f19f59e.png">

<img width="381" alt="完成済みモデルを読み込み、実行する-4.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/ab976033-d416-42de-0ace-0d0f8a484aeb.png">

<img width="395" alt="完成済みモデルを読み込み、実行する-3.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/a3cc210c-2229-1705-1204-d6da672ea14c.png">

<img width="365" alt="完成済みモデルを読み込み、実行する-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/07dfc74c-37c9-524a-d759-f3734ddbbf83.png">

うまくいっていないケースもありました。
<img width="386" alt="スクリーンショット 2019-01-12 13.12.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/322279/8e65e952-5995-cddd-44ee-efe6cbe292ce.png">


前回やった、CNNと比べて大幅な精度向上に成功しました。
Kerasにはファインチューニングで利用できるモデルが複数あったので、それらを利用して、顔分類だけでなく、違うことにも挑戦してみたいです。
[ゼロから作るDeep Learning](https://www.oreilly.co.jp/books/9784873117584/)を読んだのがきっかけでしたが、楽しく遊べてとても良かったです。

#リンク
**書いた記事**
[KerasのCNNで、顔認識AIを作って見た〜スクレイピングからモデルまで〜](https://qiita.com/yottyann1221/items/a08300b572206075ee9f)
[Google Colaboratoryで学習したモデルをローカルに保存・モデルに読み込み・実行する](https://qiita.com/yottyann1221/items/d465c033dd70b2e53598)
**参考文献**
[Keras(Tensorflow)の学習済みモデルのFine-tuningで少ない画像からごちうさのキャラクターを分類する分類モデルを作成する](https://qiita.com/kazuki_hayakawa/items/c93a21313ccbd235b82b)
[VGG16のFine-tuningによる犬猫認識 (1)](http://aidiary.hatenablog.com/entry/20170108/1483876657)
[VGG16のFine-tuningによる犬猫認識 (2)
](http://aidiary.hatenablog.com/entry/20170110/1484057655)[VGG16のFine-tuningによる17種類の花の分類](http://aidiary.hatenablog.com/entry/20170131/1485864665)
[Keras Documentation](https://keras.io/ja/applications/#vgg16
)
[Kerasによるデータ拡張](http://aidiary.hatenablog.com/entry/20161212/1481549365)