転移学習・ファインチューニング【実装】

#はじめに

今回は、学習済モデルを使った「転移学習及びファインチューニング」の実装をしていきます！！

ディープラーニングや畳み込みニューラルネットワークの基本については、下記の記事を参照してください。

🌟基本となるディープラーニング
https://qiita.com/hara_tatsu/items/c0e59b388823769f9704

🌟ディープラーニングの実装及び過学習対策
https://qiita.com/hara_tatsu/items/b7423e90574cf7730978

🌟「畳み込みニューラルネットワーク（CNN）まとめ」
https://qiita.com/hara_tatsu/items/8dcd0a339ad2f67932e7

🌟畳み込みニューラルネットワーク（CNN）の実装
https://qiita.com/hara_tatsu/items/d2c6536ae35cca5e97ab

#学習済モデルを使う理由

ディープラーニングで新しいモデルを作成するときの最大の悩みは、学習用データのデータ量。
少ないデータ量だと、限られた学習用データからの特徴抽出しかできない。そのため精度が悪く、過学習も起こりやすい。

そんなときに使われる手法が「転移学習又はファインチューニング」。

大量のデータセットで学習されている学習済モデルの特徴抽出を利用することで、少ないデータ量でも精度の良いモデルを作成することができる。


#転移学習とは

学習済モデルを特徴抽出器として利用する方法。

学習済モデル（特徴抽出）　→　追加する層（畳み込み層・全結合層）　→　出力層

学習済モデルから特徴を抽出した後に学習させる。

#転移学習【実装】

今回は題材として、「【SIGNATE】画像ラベリング（10種類）」を使います。
https://signate.jp/competitions/133

画像データに映っているものを10種類のラベルから分類するものです。

##データの前処理

###必要なライブラリーをインポート

```python.py
import numpy as np
import pandas as pd
from PIL import Image
import glob
```

###目的変数と説明変数の処理

目的変数を読み込んでダミー変数へ変換

```python.py
train_Y = pd.read_csv('train_master.tsv', delimiter='\t')
train_Y = train_Y.drop('file_name', axis=1)
# カテゴリー変数へ変換
from tensorflow.keras.utils import to_categorical
Y = to_categorical(train_Y)

print(Y.shape)
print(Y[:5])

(5000, 10)
array([[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]], dtype=float32)
```

説明変数（画像データ）は、「glob」を使って読み込めるが画像データの並びがバラバラに読み込まれてしまう。そのため、読み込み後に小さい数字の順番に並び替える。

```python.py
train_file = glob.glob('train_images/t*')

# 0埋めでない数値を小さい順に並び替える関数
import re
from collections import OrderedDict

def sortedStringList(array=[]):
    sortDict=OrderedDict()
    for splitList in array:
        sortDict.update({splitList:[int(x) for x in re.split("(\d+)",splitList)if bool(re.match("\d*",x).group())]})
    return [sortObjKey for sortObjKey,sortObjValue in sorted(sortDict.items(), key=lambda x:x[1])]

# 小さい順に並び替え
sort_file = sortedStringList(train_file)
print(sort_file[:5])

['train_images/train_0.jpg',
 'train_images/train_1.jpg',
 'train_images/train_2.jpg',
 'train_images/train_3.jpg',
 'train_images/train_4.jpg']
```

###説明変数（画像データ）の前処理
現在は、「glob」で画像データのファイル名を取得している状態。
PIL形式で読み込み後にnumpy化する。

※※CPU環境では学習に時間がかかる場合があるため、「image = load_img(image, target_size = (32, 32)) 」とすれば画像サイズを小さくできるので計算時間が減る。（予測精度は悪くなる可能性あり）


```python.py
from tensorflow.keras.preprocessing.image import load_img, img_to_array, array_to_img

X = []

for image in sort_file:
    # 画像ファイルのPIL形式で読み込み
    image = load_img(image) 
    #image = load_img(image, target_size = (32, 32)) 学習時間を短縮したい場合
    # PIL形式からnumpy形式へ
    image = img_to_array(image)
    # データをリストへ格納
    X.append(image)

#Xはリスト型のためnumpy型へ変換
X_np = np.array(X)
print(X_np.shape)

(5000, 96, 96, 3)
```

###学習用データ、検証用データ、テストデータへ分割

```python.py
# データの分割
from sklearn.model_selection import train_test_split

X_train, X_test, Y_train, Y_test = train_test_split(X_np, Y, test_size=0.2, random_state=0)
X_train, X_valid, Y_train, Y_valid = train_test_split(X_train, Y_train, test_size=0.2, random_state=0)

# 形状を確認
print("Y_train=", Y_train.shape, ", X_train=", X_train.shape)
print("Y_valid=", Y_valid.shape, ", X_valid=", X_valid.shape)
print("Y_test=", Y_test.shape, ", X_test=", X_test.shape)

Y_train= (3200, 10) , X_train= (3200, 96, 96, 3)
Y_valid= (800, 10) , X_valid= (800, 96, 96, 3)
Y_test= (1000, 10) , X_test= (1000, 96, 96, 3)

```

##学習用データの拡張と水増し

```python.py
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# 学習用データの拡張設定
image_gen = ImageDataGenerator(rotation_range=45, #45°回転
                               horizontal_flip = True, #左右反転
                               rescale=1./255 #正規化
                              )  

# 拡張データの生成
train_data_gen = image_gen.flow(X_train, Y_train, batch_size = 32, shuffle = False)

# 拡張データを表示する関数
def plotImages(images_arr):
    fig, axes = plt.subplots(1, 5, figsize=(20,20))
    axes = axes.flatten()
    for img, ax in zip( images_arr, axes):
        ax.imshow(img)
        ax.axis('off')
    plt.tight_layout()
    plt.show()

augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)                   
```

![スクリーンショット 2021-02-07 13.54.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672827/5e99318d-664b-eccb-4aaf-b98233e8c9ac.png)




```python.py
#学習用データの状態確認
#3200 / 32
print(len(train_data_gen))

100
```

検証用データを学習用データの状態に合わせる

```python.py
valid_gen = ImageDataGenerator(rescale=1./255 #正規化)
valid_data_gen = valid_gen.flow(X_valid, Y_valid, batch_size = 32)
#800 / 32
print(len(valid_data_gen))

25
```

##転移学習の実装

畳み込みニューラルネットワークの学習済モデルとして有名な「VGG16」を利用します。

他にも「Inception」,「ResNet」などあります。

※「ResNet」でも試したが、「VGG16」の方が精度が良かった。

```python.py
from tensorflow.keras.applications.vgg16 import VGG16

base_model = VGG16(weights = 'imagenet', #学習済みの重みを使用する
                   include_top = False, #出力層は使わない
                   input_shape = (96, 96, 3)) #入力する画像サイズの指定

base_model.summary()
```
VGG16の構造は以下のとおり
(畳み込み層のブロックが5つ)

```
Model: "vgg16"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 96, 96, 3)]       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 96, 96, 64)        1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 96, 96, 64)        36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 48, 48, 64)        0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 48, 48, 128)       73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 48, 48, 128)       147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 24, 24, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 24, 24, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 24, 24, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 24, 24, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 12, 12, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 12, 12, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 12, 12, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 12, 12, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 6, 6, 512)         0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 6, 6, 512)         2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 6, 6, 512)         2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 6, 6, 512)         2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 3, 3, 512)         0         
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
_________________________________________________________________
```

転移学習では学習済モデルの重みは固定したままにするので以下の処理を行う。

🌟重みの固定をしないと全ての重みが更新されてしまうため、学習済モデルを使うメリットがなくなってしまう。

```python.py
# 全ての重みを固定（freeze）
for layer in base_model.layers[:19]:
  layer.trainable = False

# 重みが固定されているかの確認
for layer in base_model.layers:
    print(layer, layer.trainable)
```

全てが「Flase」となっていれば重みの固定が成功している。

```
<tensorflow.python.keras.engine.input_layer.InputLayer object at 0x7f5cc984ff60> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cc984fb38> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbe758fd0> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f5cbd716f28> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd738080> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd738e10> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f5cbd73d4e0> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd745048> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd74b400> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd752908> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f5cbd752ac8> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd745358> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd6d9f98> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd6dc5f8> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f5cbd6e4e48> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd6e9b00> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd6f1a90> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cbd6f1198> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f5cbd6f8940> False
```

###転移学習モデルの作成

```python.py
model = keras.Sequential()
# VGG16モデル
model.add(base_model)
# 層を追加
model.add(Conv2D(64, kernel_size=3, padding="same", activation="relu"))
model.add(MaxPooling2D())
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(128, activation="relu")) 
model.add(Dense(64, activation="relu")) 
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
model.summary()

```

転移学習モデルの構造

```

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
vgg16 (Functional)           (None, 3, 3, 512)         14714688  
_________________________________________________________________
conv2d (Conv2D)              (None, 3, 3, 64)          294976    
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 1, 1, 64)          0         
_________________________________________________________________
dropout (Dropout)            (None, 1, 1, 64)          0         
_________________________________________________________________
flatten (Flatten)            (None, 64)                0         
_________________________________________________________________
dense (Dense)                (None, 256)               16640     
_________________________________________________________________
dropout_1 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 128)               32896     
_________________________________________________________________
dropout_2 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 64)                8256      
_________________________________________________________________
dropout_3 (Dropout)          (None, 64)                0         
_________________________________________________________________
dense_3 (Dense)              (None, 10)                650       
=================================================================
Total params: 15,068,106
Trainable params: 353,418
Non-trainable params: 14,714,688
_________________________________________________________________

```

訓練されるパラメーターの確認をします。

```python.py
for layer in model.layers:
    print(layer, layer.trainable )

print(len(model.trainable_weights))
```

10個のパラメータが訓練される。

```

<tensorflow.python.keras.engine.functional.Functional object at 0x7f5cbd6fe978> True
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f5cc984f668> True
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f5cbd6d4f98> True
<tensorflow.python.keras.layers.core.Dropout object at 0x7f5cbd6d4c88> True
<tensorflow.python.keras.layers.core.Flatten object at 0x7f5cbd238f98> True
<tensorflow.python.keras.layers.core.Dense object at 0x7f5cbd238240> True
<tensorflow.python.keras.layers.core.Dropout object at 0x7f5cbd240518> True
<tensorflow.python.keras.layers.core.Dense object at 0x7f5cbd240d68> True
<tensorflow.python.keras.layers.core.Dropout object at 0x7f5cbd240710> True
<tensorflow.python.keras.layers.core.Dense object at 0x7f5cbd252588> True
<tensorflow.python.keras.layers.core.Dropout object at 0x7f5cbd25a550> True
<tensorflow.python.keras.layers.core.Dense object at 0x7f5cbd25afd0> True
10
```


```python.py
model.compile(loss='categorical_crossentropy',optimizer= 'rmsprop', metrics=['accuracy'])

%%time
# 学習の実施
log = model.fit_generator(train_data_gen, #学習用データ
                          steps_per_epoch = 100,
                          epochs = 5000, #繰り返し計算する回数
                          callbacks = [keras.callbacks.EarlyStopping(monitor='val_loss',#監視する値
                                       min_delta=0, #改善とみなされる最小の量
                                       patience=50, #設定したエポック数改善がないと終了
                                       verbose=1,
                                       mode='auto' #監視する値が増減どうなったら終了か自動で推定
                                       )],
                          validation_data = valid_data_gen, #検証用データ
                          validation_steps = 25)
```

###結果の確認


```python.py
acc = log.history['accuracy']
val_acc = log.history['val_accuracy']

loss = log.history['loss']
val_loss = log.history['val_loss']

#学習回数によって変更
epochs_range = range(55)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
```

![スクリーンショット 2021-02-14 9.53.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672827/55079b72-9b81-e35e-ef3d-c994bfdd83f6.png)


```python.py
#テストデータの正規化
X_test = X_test / 255.0

# 予測
Y_pred = model.predict(X_test)
Y_pred = np.argmax(Y_pred,axis=1)
print('予測データ')
print(Y_pred[:5])

# カテゴリー変数の復元
Y_test_ = np.argmax(Y_test, axis=1)
print('正解データ')
print(Y_test_[:5])

# モデルの評価
from sklearn.metrics import classification_report

print(classification_report(Y_test_, Y_pred))

予測データ
[2 6 0 2 1]
正解データ
[2 5 0 9 1]
              precision    recall  f1-score   support

           0       0.92      0.83      0.87        93
           1       0.70      0.85      0.77       100
           2       0.98      0.82      0.89        99
           3       0.55      0.67      0.60       103
           4       0.78      0.65      0.71       117
           5       0.73      0.41      0.52        93
           6       0.70      0.77      0.73       101
           7       0.68      0.69      0.69       110
           8       0.79      0.83      0.81        88
           9       0.71      0.90      0.79        96

    accuracy                           0.74      1000
   macro avg       0.75      0.74      0.74      1000
weighted avg       0.75      0.74      0.74      1000
```

正解率74%!!

#ファインチューニングとは

学習済モデルの出力層に近い部分だけを再学習することでより作成したいモデルに適合させる。

学習済モデル（特徴抽出）　→　学習済モデル（再学習）　→　新しい層（畳み込み層・全結合層）　→　出力層

🌟出力層に近い部分は、学習済データにより具体的な特徴量を持っている。そのため、この部分を再学習することで今回の学習用データにフィットした特徴量を生み出すことができる。

#ファインチューニング【実装】

🌟前処理は転移学習を参照してください！！

```python.py
from tensorflow.keras.applications.vgg16 import VGG16

base_model = VGG16(weights = 'imagenet', #学習済の重みを使用する
                      include_top = False, #出力層を除外する（全結合層）
                      input_shape = (96, 96, 3)) #入力する画像サイズ
```

転移学習とは違い最後のConv2D層の重みを解凍して再学習します。

```python.py
# 最後の畳み込み層の直前までの層を固定
for layer in base_model.layers[:15]:
  layer.trainable = False

# 重みが固定されているかの確認
for layer in base_model.layers:
    print(layer, layer.trainable )

```

最後の畳み込み層のみ「True」となっていれば成功。
※「True」となっている部分が再学習される。

```

<tensorflow.python.keras.engine.input_layer.InputLayer object at 0x7f4214e33f98> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f420a83b710> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4209f874a8> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f4209fb3a90> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4209fba9e8> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4209fb3cc0> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f4208f3fcc0> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f46dd8> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f4b518> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f46828> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f4208f55eb8> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f59b70> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f59400> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f466a0> False
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f4208f659b0> False
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f653c8> True
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f60be0> True
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4208f726a0> True
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f4208f7ae48> True

```

###ファインチューニングモデルの作成

```python.py
# VGG16のモデルに全結合分類を追加する
model = keras.Sequential()
# VGG16モデル
model.add(base_model)
model.add(Conv2D(64, kernel_size=3, padding="same", activation="relu"))
model.add(MaxPooling2D())
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(128, activation="relu"))
model.add(Dropout(0.25))
model.add(Dense(64, activation="relu")) 
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
model.summary()

```

🌟転移学習よりも訓練するパラメーター（Trainable params）が多くなっていることがわかる。

```

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
vgg16 (Functional)           (None, 3, 3, 512)         14714688  
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 3, 3, 64)          294976    
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 1, 1, 64)          0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 1, 1, 64)          0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 64)                0         
_________________________________________________________________
dense_6 (Dense)              (None, 256)               16640     
_________________________________________________________________
dropout_5 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_7 (Dense)              (None, 128)               32896     
_________________________________________________________________
dropout_6 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_8 (Dense)              (None, 64)                8256      
_________________________________________________________________
dropout_7 (Dropout)          (None, 64)                0         
_________________________________________________________________
dense_9 (Dense)              (None, 10)                650       
=================================================================
Total params: 15,068,106
Trainable params: 7,432,842
Non-trainable params: 7,635,264
_________________________________________________________________
```

訓練されるパラメーターの確認。

```python.py
for layer in model.layers:
    print(layer, layer.trainable )

print(len(model.trainable_weights))

```

16個のパラメータで訓練が行われる。

```
<tensorflow.python.keras.engine.functional.Functional object at 0x7f4208f462b0> True
<tensorflow.python.keras.layers.convolutional.Conv2D object at 0x7f4225e4a390> True
<tensorflow.python.keras.layers.pooling.MaxPooling2D object at 0x7f40e0c9d080> True
<tensorflow.python.keras.layers.core.Dropout object at 0x7f40df0d65f8> True
<tensorflow.python.keras.layers.core.Flatten object at 0x7f40e0c9d7b8> True
<tensorflow.python.keras.layers.core.Dense object at 0x7f40df07d780> True
<tensorflow.python.keras.layers.core.Dropout object at 0x7f40e0bc2da0> True
<tensorflow.python.keras.layers.core.Dense object at 0x7f40df07d978> True
<tensorflow.python.keras.layers.core.Dropout object at 0x7f40df08f2e8> True
<tensorflow.python.keras.layers.core.Dense object at 0x7f40df08f470> True
<tensorflow.python.keras.layers.core.Dropout object at 0x7f40df083278> True
<tensorflow.python.keras.layers.core.Dense object at 0x7f40df097978> True
16
```

```python.py
model.compile(loss='categorical_crossentropy',
              optimizer= keras.optimizers.SGD(lr=1e-4, momentum=0.9),  #ファインチューニング時は学習率が低いものを選択
              metrics=['accuracy'])

%%time
# 学習の実施
log = model.fit_generator(train_data_gen, #学習用データ
                          steps_per_epoch = 100,
                          epochs = 5000, #繰り返し計算する回数
                          callbacks = [keras.callbacks.EarlyStopping(monitor='val_loss',#監視する値
                                       min_delta=0, #改善とみなされる最小の量
                                       patience=30, #設定したエポック数改善がないと終了
                                       verbose=1,
                                       mode='auto' #監視する値が増減どうなったら終了か自動で推定
                                       )],
                          validation_data = valid_data_gen, #検証用データ
                          validation_steps = 25)
```

###結果の確認


```python.py
acc = log.history['accuracy']
val_acc = log.history['val_accuracy']

loss = log.history['loss']
val_loss = log.history['val_loss']

epochs_range = range(55)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
```

![スクリーンショット 2021-02-14 9.42.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672827/948d1571-5a44-4e80-6ee1-6c45ecced913.png)


```python.py
# 予測
Y_pred = model.predict(X_test)
Y_pred = np.argmax(Y_pred,axis=1)

# モデルの評価
print(classification_report(Y_test_, Y_pred))

precision    recall  f1-score   support

           0       0.89      0.92      0.91        93
           1       0.79      0.87      0.83       100
           2       0.89      0.94      0.91        99
           3       0.65      0.73      0.68       103
           4       0.76      0.82      0.79       117
           5       0.73      0.47      0.58        93
           6       0.74      0.81      0.77       101
           7       0.81      0.71      0.76       110
           8       0.91      0.89      0.90        88
           9       0.90      0.86      0.88        96

    accuracy                           0.80      1000
   macro avg       0.81      0.80      0.80      1000
weighted avg       0.80      0.80      0.80      1000
```

正解率80%!!

#まとめ

転移学習：計算コスト低い・精度落ちる
ファインチューニング：計算コスト高い・精度は高い

🌟いずれにせよGPU環境での学習でないと計算時間が非常に長くなる。