【転移学習で顔認識モデルを作ってみた】

## はじめに
後追いながら、機械学習が熱いということで転移学習を用いて、人物画像を識別する学習モデルを作成してみました。
画像の前処理や水増しから学習モデルの構築などの過程をまとめています。

## モデルの想定ユースケース
人物画像の識別となると思いたのが、マンションのセキュリティ用監視カメラでした。
登録された住人以外の人が入り込んだ際に、不審者として識別してタグづけするようなものを考えていたのですが、
動画などは難易度が高いため、今回は顔画像を使っての顔認識モデルを作成しました。

## 画像について
### 画像枚数
今回、学習用の元画像50枚と学習モデルのテスト用に20枚ほど用意しました。
正直画像の収集が一番大変な作業でした、、
- 人物画像の元画像: 計50枚(10枚✖️住人5人)
- 学習モデルのテスト用: 計20枚(4枚✖️)

### 人物画像例
学習には知り合いの顔画像を利用しており、以下のように顔のみや上半身が写っているものなど少々ばらつきがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437253/0bda64f3-3e43-9a40-0bfa-b6704ada9188.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437253/17606cd3-1d57-e042-88ef-14a5073feaaa.png)

## 環境
- Google Colab
- Python

## 流れ
基本的に機械学習の流れとしては、データ収集 -> データの選別 -> データの前処理 -> 学習モデルの作成 -> 学習モデルの評価 となるかと思います。
データの前処理以降を実際のソースと一緒にまとめて、今回はデータの選別までは割愛しています。

[1. 構成](#1-構成)
[2. 画像の水増し](#2-画像の水増し)
[2.1. 水増し処理準備](#21-水増し処理準備)
[2.2. オリジナル画像のリサイズ](#22-オリジナル画像のリサイズ)
[2.3. 画像パターンの水増し](#23-画像パターンの水増し)
[2.4. 水増し画像の確認](#24-水増し画像の確認)
[3. 顔認識](#3-顔認識)
[3.1. 顔認識処理準備](#31-顔認識処理準備)
[3.2. データ分割](#32-データ分割)
[3.3. モデル構築と学習](#33-モデル構築と学習)
[3.4. 正解率と損失関数と精度評価](#34-正解率と損失関数と精度評価)
[3.5. モデル保存](#35-モデル保存)
[3.6. 顔認識テスト](#36-顔認識テスト)

## 1. 構成
Googleドライブ上の`/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題`配下で以下構成でアプリを作成していきます。
処理としては、`img/original`配下に各住人の元画像を保存し、それを`画像水増し.ipynb`で水増しした画像を`img/opencsv_output`に保存していきます。そして水増しした画像を利用して`顔識別アプリ(転移学習).ipynb`で学習を行い、`face_recog_model.sav`という顔認識モデルを作成するというものです。
```
/成果物課題
    ├ 顔識別アプリ(転移学習).ipynb
    ├ 画像水増し.ipynb
    ├ face_recog_model.sav
    ├ img #以下、originalと同じ構造
       ├ original #元画像
           ├ Resident1
           ├ Resident2
           ├ Resident3
           ├ Resident4
           ├ Resident5
       ├ resized_original #リサイズ後画像
       ├ opencsv_output #水増し処理後画像
       ├ validation #検証用画像
       ├ test #評価用用画像
       ├ confirmation #学習モデル確認用画像
```
## 2. 画像の水増し
学習用画像が1人10枚程度と少量だったため、今回は画像処理ライブラリのOpenCVを用いて、48パターン分画像を水増し、計480枚(48パターン*10画像*5人分)の水増し画像を作成しました。

### 2.1. 水増し処理準備
まずは、元画像・リサイズ出力先・水増し後学習用画像、それぞれの出力先用の環境変数を定義します。
```Python:
from google.colab import drive
import os
drive.mount('/content/drive')

img_dir_path = '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/'
original_dir_path = img_dir_path + 'original/'
IMG_LABEL_LIST = os.listdir(original_dir_path)
resized_original_path = img_dir_path + 'resized_original/'
opencv_img_dir_path = img_dir_path + 'opencsv_output/'
```
### 2.2. オリジナル画像のリサイズ
実際に収集した元画像のサイズにばらつきがあったため、水増し前に一度全て同じサイズに変換します。
```Python
import os
import cv2
import os
import glob
import shutil

width_resize = 200  # 変更後の画像幅

for dir_idx, dir_name in enumerate(IMG_LABEL_LIST):
  input_path = f'{original_dir_path}{dir_name}' # オリジナル画像のパス
  files = glob.glob(input_path + '/*.jpg')
  output_path = f'{resized_original_path}{dir_name}' # リサイズ後の出力先のパス
  # 出力先の初期化
  if os.path.isdir(output_path) == True:
    shutil.rmtree(output_path)
  os.mkdir(output_path)

  for file_idx, file_name in enumerate(files):
    img = cv2.imread(file_name)  # 画像の読み出し
    height, width, ch = img.shape  # 画像の高さ、幅、チャンネル数の取得
    height_resize = int(height*width_resize/width)  # 変更後画像高さの計算
    img_resize = cv2.resize(img,dsize=(width_resize,height_resize))  #画像サイズの変更 
    cv2.imwrite(f'{resized_original_path}{dir_name}/{str(dir_idx).zfill(2)}_{str(file_idx).zfill(3)}.jpg',img_resize)  # 画像の保存  
```

### 2.3. 画像パターンの水増し
水増し処理には、画像処理ライブラリのOpenCVを用いて、回転・反転・色調整・正規化などを適応して、各画像に対して48パターン分画像を以下のように水増しします。
![0_008_011_002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437253/a286e306-07ba-1105-73f9-582be4d89e2f.jpeg)![0_008_007_000.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437253/51627805-2e56-5b8c-9545-7b4e022a362d.jpeg)

```Python
from keras.preprocessing.image import load_img, img_to_array
from keras.preprocessing.image import ImageDataGenerator
import cv2
import numpy as np
import os, send2trash
import glob
import shutil

# 画像変換条件
# 収縮・膨張用フィルタ
filter1 = np.array([[0, 1, 0],
                   [1, 0, 1],
                   [0, 1, 0]], np.uint8)
filter2 = np.ones((3, 3))

# 総計48パターン 
list_resize = [2, 3, 5, 7] #1. 1/2, 1/3, 1/5, 1/7にリサイズ = 4パターン
list_mosaic = [3, 5, 7, 10] #2. リサイズを元に戻すことでモザイク処理 = 4パターン
list_rotation = [30, 45, 60, 90] #3. 画像の回転 = 4パターン
list_flip = [0, 1, -1] #4. 左右・上下・軸反転 = 3パターン
list_cvt1 = [0] #5. BGRからLab色空間に変換 = 1パターン
list_cvt2 = [0] #6. BGRからモノクロ画像に変換 = 1パターン
list_cvt3 = [0] #7. 色反転 = 1パターン
# 閾値処理4 × 5 = 20パターン
list_THRESH_BINARY = [50, 100, 150, 200] #8.
list_THRESH_BINARY_INV = [50, 100, 150, 200] #9.
list_THRESH_TRUNC = [50, 100, 150, 200] #10.
list_THRESH_TOZERO = [50, 100, 150, 200] #11.
list_THRESH_TOZERO_INV = [50, 100, 150, 200] #12.
list_gauss = [11, 31, 51, 71] #13. ぼかし処理 = 4パターン
list_nois_gray = [0] #14. ノイズ除去（モノクロ） = 1パターン
list_nois_color = [0] #15. ノイズ除去（カラー） = 1パターン
# 2（収縮・膨張） × 2（四方・周囲８） = 4パターン
list_dilate = [filter1, filter2] #16.
list_erode = [filter1, filter2] #17.

parameters = [list_resize, list_mosaic, list_rotation, list_flip, list_cvt1, list_cvt2, list_cvt3, list_THRESH_BINARY, \
              list_THRESH_BINARY_INV, list_THRESH_TRUNC, list_THRESH_TOZERO, list_THRESH_TOZERO_INV, list_gauss, \
              list_nois_gray, list_nois_color, list_dilate, list_erode]

#　水増し画像の合計
list_sum =len(list_resize) + len(list_mosaic) + len(list_rotation) + len(list_flip) + len(list_cvt1) + len(list_cvt2) + len(list_cvt3) + \
          len(list_THRESH_BINARY) + len(list_THRESH_BINARY_INV) + len(list_THRESH_TRUNC) + len(list_THRESH_TOZERO) + \
          len(list_THRESH_TOZERO_INV) + len(list_gauss) + len(list_nois_gray) + len(list_nois_color) + \
          len(list_dilate) + len(list_erode)
print("合計：{}枚".format(list_sum))

#　実行する関数のリスト
methods = np.array([lambda img, i: cv2.resize(img, (img.shape[1] // i, img.shape[0] // i)), #1.
                      lambda img, i: cv2.resize(cv2.resize(img, (img.shape[1] // i, img.shape[0] // i)), (img.shape[1],img.shape[0])), #2.
                      lambda img, i: cv2.warpAffine(img, cv2.getRotationMatrix2D(tuple(np.array([img.shape[1] / 2, img.shape[0] /2])), i, 1), (img.shape[1], img.shape[0])), #3.
                      lambda img, i: cv2.flip(img, i), #4.
                      lambda img, i: cv2.cvtColor(img, cv2.COLOR_BGR2LAB), #5.
                      lambda img, i: cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), #6.
                      lambda img, i: cv2.bitwise_not(img), #7.
                      lambda img, i: cv2.threshold(img, i, 255, cv2.THRESH_BINARY)[1], #8.
                      lambda img, i: cv2.threshold(img, i, 255, cv2.THRESH_BINARY_INV)[1], #9.
                      lambda img, i: cv2.threshold(img, i, 255, cv2.THRESH_TRUNC)[1], #10.
                      lambda img, i: cv2.threshold(img, i, 255, cv2.THRESH_TOZERO)[1], #11.
                      lambda img, i: cv2.threshold(img, i, 255, cv2.THRESH_TOZERO_INV)[1], #12.
                      lambda img, i: cv2.GaussianBlur(img, (i, i), 0), #13.
                      lambda img, i: cv2.fastNlMeansDenoising(img, i), #14
                      lambda img, i: cv2.fastNlMeansDenoisingColored(img), #15.
                      lambda img, i: cv2.dilate(img, i), #16.
                      lambda img, i: cv2.erode(img, i) #17.
                    ])

#　水増し画僧の保存用関数
def save(img, file_path):
  cv2.imwrite(file_path, img)

#　画像の水増しと保存
for dir_idx, dir_name in enumerate(IMG_LABEL_LIST):
  # 入力画像の保存先パス
  input_path = f'{resized_original_path}{dir_name}'
  # input_path = f'{original_dir_path}{dir_name}'
  files = glob.glob(input_path + '/*.jpg')
  # 出力画像の保存先パス
  output_path = f'{opencv_img_dir_path}{dir_name}'
  # ディレクトリの初期化
  if os.path.exists(output_path):
    file_amount = os.listdir(output_path)
    if len(file_amount) != 0:
      for del_file in os.listdir(output_path):
        if del_file.endswith('.jpg'):
          send2trash.send2trash(f'{output_path}/{del_file}')
  else:
    os.mkdir(output_path)
  
  for file_idx, file in enumerate(files):
    file_img = cv2.imread(file)
    for method_idx, method in enumerate(methods):
      for pararm_idx, param in enumerate(parameters[method_idx]):
        cnv_img = method(file_img, param)
        output_file_path = f'{output_path}/{dir_idx}_{str(file_idx).zfill(3)}_{str(method_idx + 1).zfill(3)}_{str(pararm_idx).zfill(3)}.jpg'
        save(cnv_img, output_file_path)
```
```:出力結果
合計：48枚
```
### 2.4. 水増し画像の確認
水増し処理後に各住人ごとに480枚(10枚×48パターン)の画像が生成されていることを確認します。
```Python
# ファイル数確認
output_path = f'/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/opencsv_output/'
for dir_name in IMG_LABEL_LIST:
  file_path = f'{output_path}{dir_name}/*.jpg'
  file_list = glob.glob(file_path)
  print(f'{dir_name}: {len(file_list)}')
```
```:出力結果
Resident1: 480
Resident2: 480
Resident3: 480
Resident4: 480
Resident5: 480
```
これで、学習モデルに読み込ませるための画像の準備が出来ました。
## 3. 顔認識
読み込み画像の準備が出来たので、顔認識モデルの作成に移っていきます。
今回画像は以下の比率で分割して、学習時に利用していきたいと思います。
- 訓練 : 0.6
- 検証 : 0.2
- テスト: 0.2
### 3.1. 学習準備
学習用画像のパスなど環境変数を設定します。
```Python
%%time
from google.colab import drive
from keras.preprocessing.image import load_img, img_to_array
import numpy as np
import os
import glob
import tensorflow as tf
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import pickle
drive.mount('/content/drive')
TEST_IMG_NUM = 15
IMAGE_SIZE = (256,256)
IMG_DIR = '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/'
IMG_ORIGINAL_DIR = f'{IMG_DIR}original/'
IMG_STD_DIR = f'{IMG_DIR}opencsv_output/'
IMG_VALIDATION_DIR = f'{IMG_DIR}validation/'
IMG_TEST_DIR = f'{IMG_DIR}test/'
CONFIRM_IMG_DIR = f'{IMG_DIR}confirmation/'
TARGET_DIR_LIST = os.listdir(IMG_ORIGINAL_DIR)
model_filename = '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/face_recog_model.sav'
```

### 3.2. データ準備
水増しした訓練データと検証・テスト用に用意した画像データをそれぞれ読み込んでいきます。
```Python
%%time
############## 訓練データ ##############
# ファイルパス取得
file_path_array = []
for target in TARGET_DIR_LIST:
  file_path = f'{IMG_STD_DIR}{target}/*.jpg'
  # print(file_path)
  file_path_array.append(glob.glob(file_path))
# 画像読み込み
img_dataset = {}
data = []
target = []
img_dataset['data'] = data
img_dataset['target'] = target
for dir_num, target_dir in enumerate(TARGET_DIR_LIST):
  for img_file_path in file_path_array[dir_num]:
    img = load_img(img_file_path, color_mode = 'rgb', target_size=IMAGE_SIZE)
    img_list = img_to_array(img)
    img_dataset['data'].append(img_list)
    img_dataset['target'].append(dir_num)

img_X_train = np.array(img_dataset['data'])
img_Y_train = np.array(img_dataset['target'])

############## 検証データ ##############
# ファイルパス取得
file_path_array = []
for target in TARGET_DIR_LIST:
  file_path = f'{IMG_VALIDATION_DIR}{target}/*.jpg'
  # print(file_path)
  file_path_array.append(glob.glob(file_path))

# 画像読み込み
img_dataset = {}
data = []
target = []
img_dataset['data'] = data
img_dataset['target'] = target
for dir_num, target_dir in enumerate(TARGET_DIR_LIST):
  for img_file_path in file_path_array[dir_num]:
    img = load_img(img_file_path, color_mode = 'rgb', target_size=IMAGE_SIZE)
    img_list = img_to_array(img)
    img_dataset['data'].append(img_list)
    img_dataset['target'].append(dir_num)

img_X_valid = np.array(img_dataset['data'])
img_Y_valid = np.array(img_dataset['target'])

############## テストデータ ##############
# ファイルパス取得
file_path_array = []
for target in TARGET_DIR_LIST:
  file_path = f'{IMG_TEST_DIR}{target}/*.jpg'
  # print(file_path)
  file_path_array.append(glob.glob(file_path))

# 画像読み込み
img_dataset = {}
data = []
target = []
img_dataset['data'] = data
img_dataset['target'] = target
for dir_num, target_dir in enumerate(TARGET_DIR_LIST):
  for img_file_path in file_path_array[dir_num]:
    img = load_img(img_file_path, color_mode = 'rgb', target_size=IMAGE_SIZE)
    img_list = img_to_array(img)
    img_dataset['data'].append(img_list)
    img_dataset['target'].append(dir_num)

img_X_test = np.array(img_dataset['data'])
img_Y_test = np.array(img_dataset['target'])

img_X_train = np.array(img_X_train)
img_X_valid = np.array(img_X_valid)
img_X_test = np.array(img_X_test)
```

### 3.3. モデル構築と学習
アプリ作成の最終段階として、分割したテストデータを使って、モデルの構築・学習に入ります。
今回、画像認識の精度向上のために転移学習でオープンソース画像認識用学習モデルのVGG16を採用してモデルの構築・学習を行なっています。
Google ColabのTPUで大体40分ほど学習に必要だったので、Youtubeなど見つつ気長にお待ちください。
```Python
%%time 
from tensorflow.keras.layers import Dense, Dropout, Flatten, Input, BatchNormalization
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.utils import to_categorical
from tensorflow.keras import optimizers

# one-hotベクトル化
img_Y_train = to_categorical(img_Y_train)[:1440]
img_Y_valid = to_categorical(img_Y_valid)[:10]
img_Y_test = to_categorical(img_Y_test)[:10]

# 転移学習のベースモデルとしてVGG16を宣言
input_tensor = Input(shape=(256, 256, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

# VGG16以降のモデル作成
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.2))
top_model.add(Dense(128, activation='relu'))
top_model.add(Dropout(0.2))
top_model.add(Dense(64, activation='relu'))
top_model.add(Dropout(0.2))

top_model.add(Dense(5, activation='softmax'))

# VGG16と新規モデルの連結
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

# 重み付け値の固定
for layer in model.layers[:19]:
    layer.trainable = False

# モデル構築
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              run_eagerly=True,
              metrics=['accuracy'])
# モデル学習
history = model.fit(
    img_X_train, img_Y_train,
    validation_data=(img_X_valid, img_Y_valid),
    batch_size=32,
    epochs=5)
```

```:学習状況
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg16/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
58892288/58889256 [==============================] - 0s 0us/step
58900480/58889256 [==============================] - 0s 0us/step
Epoch 1/5
/usr/local/lib/python3.7/dist-packages/keras/optimizer_v2/gradient_descent.py:102: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  super(SGD, self).__init__(name, **kwargs)
45/45 [==============================] - 461s 10s/step - loss: 4.5068 - accuracy: 0.4472 - val_loss: 1.8320 - val_accuracy: 0.3750
Epoch 2/5
45/45 [==============================] - 463s 10s/step - loss: 1.2732 - accuracy: 0.7264 - val_loss: 1.8882 - val_accuracy: 0.5000
Epoch 3/5
45/45 [==============================] - 468s 10s/step - loss: 0.8042 - accuracy: 0.8076 - val_loss: 2.2419 - val_accuracy: 0.6250
Epoch 4/5
45/45 [==============================] - 467s 10s/step - loss: 0.6206 - accuracy: 0.8458 - val_loss: 2.1829 - val_accuracy: 0.6250
Epoch 5/5
45/45 [==============================] - 466s 10s/step - loss: 0.4300 - accuracy: 0.8938 - val_loss: 1.7667 - val_accuracy: 0.5000
CPU times: user 11h 17min 5s, sys: 10min 37s, total: 11h 27min 42s
Wall time: 38min 45s
```

### 3.4. 正解率と損失関数と精度評価
モデルの構築・学習が終わりましたので、これで顔認識モデルは完成です。
ここからは作成した学習モデルが本当に大丈夫なのか正解率と[損失関数](https://atmarkit.itmedia.co.jp/ait/articles/2104/15/news030.html#:~:text=%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%90%8D%E5%A4%B1%E9%96%A2%E6%95%B0,%E3%83%A2%E3%83%87%E3%83%AB%E3%82%92%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%99%E3%82%8B%E3%80%82)と精度評価から、数値が実用性のあるものか確認します。
```Python
%%time 
# 学習状況
## 訓練と検証
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

## 正解率
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1])
plt.title('Training and Validation Accuracy')

## 損失関数
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0, 10.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()

# 精度の評価
scores = model.evaluate(img_X_test, img_Y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])
```
正解率・損失率共に収束していっているので、学習自体は正常に出来ているように見えます。
また、精度評価に関しても、89%と過学習が疑われるような数値でもなく、割と高い数値なので、問題ないかと思います。
しかし、検証用の画像枚数が少ないため、精度の上下が激しくなっていました。
- 正解率・損失率結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437253/98053696-b4b0-68bc-48b4-2436017739ed.png)

```:精度評価
1/1 [==============================] - 1s 522ms/step - loss: 4.2455 - accuracy: 0.7000
Test loss: 4.245501518249512
Test accuracy: 0.699999988079071
CPU times: user 16.3 s, sys: 171 ms, total: 16.5 s
Wall time: 865 ms
```
### 3.5. モデル保存
顔認識をする度に40分もモデルの学習待ちをするのは辛いので、pickle.dumpを用いてモデルを保存します。
これで、次回からは学習済みのモデルをそのまま流用できます。
```Python
%%time 
#モデルの保存
pickle.dump(model, open(model_filename, 'wb'))
```
### 3.6. 顔認識テスト
最後に作成したモデルに画像を読み込ませて、実用に足るのかを確認してみます。
元画像が4枚ほどしか集められなかったため、計20枚の画像を読み込ませて識別します。
20枚中13枚が正しく識別されており、精度としては大体65~70%とそこそこの結果かと思います。
```Python
%%time 
from keras.applications.vgg16 import preprocess_input, decode_predictions
import matplotlib.pyplot as plt
import cv2

# テスト用画像読み込み
test_file_path_array = []
for target in TARGET_DIR_LIST:
  file_path = f'{CONFIRM_IMG_DIR}{target}/*.jpg'
  test_file_path_array.append(glob.glob(file_path))

img_test_list = []
for target_idx, target_file_list in enumerate(test_file_path_array):
  for file_idx, target_file in enumerate(target_file_list):
    file_idx+=1
    img = load_img(target_file, color_mode = 'rgb', target_size=IMAGE_SIZE)
    img_list = img_to_array(img)
    img_test_list.append(img_list)
    num = file_idx + (target_idx*3)
    plt.subplot(5, 4, num)
    plt.imshow(img)

# データセット(ndarray)に変換
img_test = np.array(img_test_list)

# モデル読み込み
loaded_model =  pickle.load(open(model_filename, 'rb'))

# 予測
print("prediction")

pred = np.argmax(loaded_model.predict(img_test), axis=1)
list(map(print, test_file_path_array))
print(pred)
```

```:出力結果
['Resident1', 'Resident2', 'Resident3', 'Resident4', 'Resident5']
[['/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident1/images-_2_.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident1/uploaded.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident1/images.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident1/images-_1_.jpg'], ['/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident2/IMG_2011.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident2/IMG_1085.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident2/IMG_2092.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident2/IMG_2091.jpg'], ['/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident3/IMG_2076.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident3/IMG_2072.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident3/IMG_2073.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident3/IMG_2070.jpg'], ['/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident4/IMG_2088.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident4/IMG_2065.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident4/IMG_2085.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident4/IMG_2084.jpg'], ['/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident5/IMG_2197.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident5/IMG_2203.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident5/IMG_2209.jpg', '/content/drive/MyDrive/Colab Notebooks/Aidemy/成果物課題/img/test/Resident5/IMG_2210.jpg']]
/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:21: MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance.  In a future version, a new instance will always be created and returned.  Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.
[1 0 0 0 3 1 0 1 3 0 3 2 3 3 3 3 3 0 3 4]
CPU times: user 31.5 s, sys: 454 ms, total: 32 s
Wall time: 3.79 s
```
# 考察
学習モデルをテストした際に55%とあまり振るわなかったです。
考えられる理由としては、学習用の元画像が少なかったことや、画像のバリエーションが少なかったことが考えられます。
また、学習時のパラメータの最適化にもまだ余地があるように感じられました。
次回、画像データ収集と学習時のパラメータの最適化に焦点を当てて改善できたらと思います。

# 終わりに
今回初めて機械学習に触れて、顔認識アプリを作成しましたが、機械学習の大まかな流れと仕組みを理解出来たのは大きな収穫でした。
学習を通して、パラメータや転移学習モデルなど色々試しましたが、やはり一番大変で効果があるのは読み込み用のオリジナル画像をどれだけ用意できたかになるかと思います。
より実用的なものを作るためには量だけでなく様々なシチュエーションに対応出来るように横顔や離れた画像など、多様な画像を用意することも必要と感じました。
今回のモデルは今後、顔認識アプリの土台として作成しています。
今後は今回投稿の発展系として、画像収集時にはスクレピイング技術などを利用してより簡単に大量のデータを扱えるようにして、学習モデルの精度を上げて、かつ動画で監視カメラのような顔識別アプリを作成していけたらなと思います。