女子プロゴルファーの顔診断AIを作ってみた②

#1. はじめに
[前回](https://qiita.com/ProgramWataru/items/37939189126db164a41b)までにデータ前処理の部分である画像の収集、顔部分の取得、データの水増しまでを実施しました。

今回は、実際にモデルを作成して、精度を確かめました。
・自分で構築したモデル
・転移学習(VGG16)
の２つについて書こうと思います。

#2. 訓練データとテストデータに分けたファイルの作成

```python:preparation.py
from PIL import Image
import os, glob
import numpy as np
from PIL import ImageFile
import cv2
from keras.utils.np_utils import to_categorical

# IOError: image file is truncated回避のため
ImageFile.LOAD_TRUNCATED_IMAGES = True

# 訓練データの作成
# それぞれ空のリストを作成
img_shibuno = []
img_koiwai = []
img_hara = []
img_lists = [img_shibuno, img_koiwai, img_hara] 

# face以下のパスを取得
in_dir = './face/*'
in_file = glob.glob(in_dir)

# 各フォルダ（選手)ごとに処理を行う
for num in range(len(in_file)):
    # 選手のファイルの中の各画像のパスを取得
    in_file_name = glob.glob(in_file[num]+'/*')
    # 各画像について処理を行う
    for i in range(len(in_file_name)):
        # イメージを開く
        img = Image.open(in_file_name[i])
        img = img.convert("RGB")
        # サイズを調整する
        img = img.resize((64,64))
        # ndarrayに変換
        data = np.asarray(img)
        # img_listsに追加する
        img_lists[num].append(data)

# 画像が入ったリストを結合
X_train = np.array(img_shibuno+img_koiwai+img_hara)
# それぞれ0~2までの値をいれる
y_train = np.array([0]*len(img_shibuno)  + [1]*len(img_koiwai) + [2]*len(img_hara))

# テストデータの作成
# それぞれ空のリストを作成
img_shibuno = []
img_koiwai = []
img_hara = []
img_lists = [img_shibuno, img_koiwai, img_hara] 

in_dir = './valid/*'
in_file = glob.glob(in_dir)

for num in range(len(in_file)):
    # 選手のファイルの中の各画像のパスを取得
    in_file_name = glob.glob(in_file[num]+'/*')
    # 各画像について処理を行う
    for i in range(len(in_file_name)):
        # イメージを開く
        img = Image.open(in_file_name[i])
        img = img.convert("RGB")
        # サイズを調整する
        img = img.resize((64,64))
        # ndarrayに変換
        data = np.asarray(img)
        # img_listsに追加する
        img_lists[num].append(data)

# 画像が入ったリストを結合
X_test = np.array(img_shibuno+img_koiwai+img_hara)
# それぞれ0~2までの値をいれる
y_test = np.array([0]*len(img_shibuno)  + [1]*len(img_koiwai) + [2]*len(img_hara))

# one-hot-vectorの処理を行う
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# 訓練データとバリデーションデータをファイルに保存する
xy = (X_train, X_test, y_train, y_test)
np.save('./golfer.npy', xy)
```

これでデータ前処理は完了。次はモデルの評価を行う

#3.モデルの作成と評価
自分で作成したモデルと転移学習(vgg16)のモデルそれぞれを作成

※以下のコードは説明のため分けて書いてますが一つにまとめてください。

###3-1. モジュールをインポート

```python
from keras.models import Model, Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense, Input
from keras.applications.vgg16 import VGG16
from keras.utils import np_utils
import keras
from keras import optimizers, models, layers
import numpy as np
import matplotlib.pyplot as plt

classes = ['shibuno', 'koiwai', 'hara']
num_classes = len(classes)
image_size = 64
```

###3-2. データを読み込む関数

```python
def load_data():
    X_train, X_test, y_train, y_test = np.load('./golfer.npy', allow_pickle=True)
    # 入力データの各画素値を0-1の範囲で正規化
    X_train = X_train / 255
    X_test = X_test / 255
    return X_train, y_train, X_test, y_test
```

###3-3. モデルを学習する関数
__※ 以下の①と②はいずれか一方のみ記述ください。__
#####① 自分で作成したモデル

```python
def train(X_train, y_train, X_test, y_test):
    model = Sequential()
    # Xは(296, 64, 64, 3): X.shepe[1:]で(64, 64, 3)
    model.add(Conv2D(32, (3, 3), padding='same', input_shape=X_train.shape[1:]))
    model.add(Activation('relu'))
    model.add(Conv2D(32, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.1))

    model.add(Conv2D(64, (3, 3), padding='same'))
    model.add(Activation('relu'))
    model.add(Conv2D(64, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))

    model.add(Flatten())
    model.add(Dense(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.45))
    model.add(Dense(3))
    model.add(Activation('softmax'))

    model.summary()

    # 最適化アルゴリズムRMSprop
    opt = keras.optimizers.rmsprop(lr=0.00005, decay=1e-6)

    model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
    return model
```

作成したモデルを可視化

```
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 64, 64, 3)         0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 64, 64, 64)        1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 64, 64, 64)        36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 32, 32, 64)        0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 32, 32, 128)       73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 32, 32, 128)       147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 16, 16, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 16, 16, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 16, 16, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 16, 16, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 8, 8, 256)         0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 8, 8, 512)         1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 8, 8, 512)         2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 8, 8, 512)         2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 2, 2, 512)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 2048)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 256)               524544    
_________________________________________________________________
dropout_1 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 3)                 771       
=================================================================
```

#####② 転移学習

```python
def train(X, y, X_test, y_test):
    input_tensor = Input(shape=(64, 64, 3))
    vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

    # 特徴量抽出部分のモデルを作成しています
    top_model = vgg16.output
    top_model = Flatten(input_shape=vgg16.output_shape[1:])(top_model)
    top_model = Dense(256, activation='sigmoid')(top_model)
    top_model = Dropout(0.5)(top_model)
    top_model = Dense(3, activation='softmax')(top_model)

    # vgg16とtop_modelを連結してください
    model = Model(inputs=vgg16.input, outputs=top_model)

    # 以下のfor文を完成させて、15層目までの重みを固定させてください
    for layer in model.layers[:15]:
        layer.trainable = False

    #　学習の前に、モデル構造を確認してください
    model.summary()

    model.compile(loss='categorical_crossentropy',
                optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
                metrics=['accuracy'])
    
    return model
```

作成したモデルを可視化

```
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 64, 64, 3)         0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 64, 64, 64)        1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 64, 64, 64)        36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 32, 32, 64)        0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 32, 32, 128)       73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 32, 32, 128)       147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 16, 16, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 16, 16, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 16, 16, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 16, 16, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 8, 8, 256)         0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 8, 8, 512)         1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 8, 8, 512)         2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 8, 8, 512)         2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 2, 2, 512)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 2048)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 256)               524544    
_________________________________________________________________
dropout_1 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 6)                 1542      
=================================================================
```

###3-4. 正答率と損失関数のグラフ作成の関数

```python
def compare_TV(history):
    # パラメーターを設定する
    # 学習データに対する分類の正答率
    acc = history.history['accuracy']
    # バリデーションデータに対する分類の正答率
    val_acc = history.history['val_accuracy']
    # 学習データに対する損失関数の値
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    # バリデーションデータに対する損失関数の値
    epochs = range(len(acc))

    # 1) 正答率のグラフ
    plt.plot(epochs, acc, 'bo' ,label = 'training acc')
    plt.plot(epochs, val_acc, 'b' , label= 'validation acc')
    plt.title('Training and Validation acc')
    plt.legend()

    plt.figure()

    # 2) 損失関数のグラフ
    plt.plot(epochs, loss, 'bo' ,label = 'training loss')
    plt.plot(epochs, val_loss, 'b' , label= 'validation loss')
    plt.title('Training and Validation loss')
    plt.legend()

    plt.show()
```

###3-5. データの読み込みとモデルの学習を行う関数

```python
def main():
    # データの読み込み
    X_train, y_train, X_test, y_test = load_data()
    # モデルの学習
    model = train(X_train, y_train, X_test, y_test)

    history = model.fit(X_train, y_train, batch_size=32, epochs=70, verbose=1, validation_data=(X_test, y_test))
    
    # 汎化制度の評価・表示
    score = model.evaluate(X_test, y_test, batch_size=32, verbose=0)
    print('validation loss:{0[0]}\nvalidation accuracy:{0[1]}'.format(score))
    compare_TV(history)
    model.save('./golfer.h5')
```

最後にmain()関数を記述して実行させる。

```python
# これでモデルを学習させ、学習済モデルが生成される
main()
```

#4. 学習結果
学習結果の正解率と損失関数をグラフにて確認する
####① 自分で作成したモデル
<img width="373" alt="mymodel.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/9703cb82-c3f0-4345-133f-9ae0c2485f78.png">

__正解率：0.93__

####② 転移学習
<img width="377" alt="転移学習.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/f458daa1-0251-7aef-b689-7117b4d7ba21.png">

__正解率：0.95__

わずかにですが転移学習の方が上でしたので転移学習のモデルを採用します。


モデルの作成までが完了しました。次回は、アプリケーションを作成して、herokuで公開するところまで書こうと思います。

#参考
[機械学習で乃木坂46を顏分類してみた](https://aidemy.hatenablog.com/entry/2017/12/17/214715)

##　次回
[女子プロゴルファーの顔診断AIを作ってみた③](https://qiita.com/ProgramWataru/items/87ccf81febfd6c9d5025)