Keras | ResNet50つかって自前画像をtrain・testする 

学習の記事はあっても推論まで書いてる記事は少ない気がしたのでまとめます。

# はじめに
①自前の画像使って学習・モデル保存→②保存したモデル使って推論
までの流れをやりたいと思います。
環境作るところは割愛します。足りないものはpip installしてみてください。

# なぜKerasをつかうのか
理由はずばり、簡単だからです。
普段pytorchやtensorflowも使っていますが、書き方がわかりやすいのは断然Keras。一番ドキュメントわかりやすくて充実してるのもそうな気がします。自分で書いてみるディープラーニングのファーストステップにおすすめです。

[Kerasドキュメント](https://keras.io/ja/)

# ①学習
ResNet50を使って画像の分類をやっていきます。
学習はこのあたりの記事と図書のコードが参考です
[ResNetをFine Tuningして自分が用意した画像を学習させる](https://pchun.work/resnet%E3%82%92fine-tuning%E3%81%97%E3%81%A6%E8%87%AA%E5%88%86%E3%81%8C%E7%94%A8%E6%84%8F%E3%81%97%E3%81%9F%E7%94%BB%E5%83%8F%E3%82%92%E5%AD%A6%E7%BF%92%E3%81%95%E3%81%9B%E3%82%8B/)
[詳解ディープラーニング](https://github.com/yusugomori/deeplearning-tensorflow-keras)

## コード全文

```python:train.py
import os
import random
import glob
import matplotlib.pyplot as plt 
from keras.preprocessing.image import ImageDataGenerator
from keras.applications.resnet50 import ResNet50
from keras.models import Sequential, Model
from keras.layers import Input, Flatten, Dense
from keras import optimizers
from keras.callbacks import ModelCheckpoint, EarlyStopping


classes = ['1','2','3','4','5']
nb_classes = len(classes)

#train val dir
train_dir = './data/train'
val_dir = './data/val'
model_dir = './model'

#num samples
train_samples = glob.glob(train_dir + '/*/*.jpg')
val_samples = glob.glob(val_dir + '/*/*.jpg')
train_samples = len(train_samples)
val_samples = len(val_samples)
print(train_samples)
print(val_samples)

#img size
img_w, img_h = 224,224

#keras image data generator
train_datagen = ImageDataGenerator(rescale = 1.0/255, zoom_range=0.2,horizontal_flip=True)
val_datagen = ImageDataGenerator(rescale=1.0 / 255)

train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(img_h,img_w),
    color_mode = 'rgb',
    classes = classes,
    class_mode = 'categorical',
    batch_size = 5
)

val_generator = val_datagen.flow_from_directory(
    val_dir,
    target_size=(img_h,img_w),
    color_mode = 'rgb',
    classes = classes,
    class_mode = 'categorical',
    batch_size = 5
)

#TO save model
checkpoint = ModelCheckpoint(
    filepath = os.path.join(
        model_dir,
        'model_{epoch:02d}.hdf5'
    ),
    save_best_only=True
)

#TO early stopping
early_stopping = EarlyStopping(monitor='val_loss',patience=3,verbose=0,mode='auto')

### model ###
#ResNet50
input_tensor = Input(shape=(img_w,img_h,3))
## train skratch ==>> weights=None ##
ResNet50 = ResNet50(include_top=False, weights=None ,input_tensor=input_tensor)
#def softmax
top_model = Sequential()
top_model.add(Flatten(input_shape=ResNet50.output_shape[1:]))
top_model.add(Dense(nb_classes, activation='softmax'))
model = Model(input=ResNet50.input, output=top_model(ResNet50.output))

#hyper param
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=1e-3, momentum=0.9),
              metrics=['accuracy'])

#train 
epoch = 10
history = model.fit_generator(
    train_generator,
    steps_per_epoch=train_samples,
    epochs=epoch,
    validation_data=val_generator,
    callbacks=[checkpoint,early_stopping]
)
print(history.history)

#plot 
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = epoch

plt.figure()
plt.plot(range(1,epochs+1),acc,'b',label = 'traning accracy')
plt.plot(range(1,epochs+1),val_acc,'r',label='validation accracy')
plt.title('Training and validation accuracy')
plt.legend()
plt.savefig('result_acc')


plt.figure()
plt.plot(range(1,epochs+1), loss, 'bo', label='Training loss')
plt.plot(range(1,epochs+1), val_loss, 'ro', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.savefig('result_loss')

plt.show()
```

## 大まかな解説
### クラス・ディレクトリの指定と画像の準備

この部分はご本家様の解説が詳しいのでそちらを読んでみてください。順を追って詳しく解説してくださっているのでその流れに乗って真似してみるのみです。
今回のコードでは自分の環境に合わせるため、画像の数の取り方の部分などをちょっと変更してます。
[ResNetをFine Tuningして自分が用意した画像を学習させる](https://pchun.work/resnet%E3%82%92fine-tuning%E3%81%97%E3%81%A6%E8%87%AA%E5%88%86%E3%81%8C%E7%94%A8%E6%84%8F%E3%81%97%E3%81%9F%E7%94%BB%E5%83%8F%E3%82%92%E5%AD%A6%E7%BF%92%E3%81%95%E3%81%9B%E3%82%8B/)

今回はdataディレクトリの下に、train,val,testというディレクトリを作り、それぞれの下に1,2,3,4,5というクラスのディレクトリを作ってそれ以下にそれぞれのクラスの画像を保存します。

```python:
#クラスを指定する(フォルダ名と同じにする)
classes = ['1','2','3','4','5']
#クラスの数を取得
nb_classes = len(classes)

#trainの画像・validationの画像のディレクトリ指定
train_dir = './data/train'
val_dir = './data/val'
#モデルを保存するディレクトリの指定
model_dir = './model'

#画像のリストを取得
train_samples = glob.glob(train_dir + '/*/*.jpg')
val_samples = glob.glob(val_dir + '/*/*.jpg')
#画像の数を取得
train_samples = len(train_samples)
val_samples = len(val_samples)
#確認のためのprint
print(train_samples)
print(val_samples)

#インプットする画像のサイズを定義(w,hが同じになるようにする)
img_w, img_h = 224,224

#画像にかける前処理を定義する
#train...0-1で正規化、画像ズーム、水平反転
train_datagen = ImageDataGenerator(rescale = 1.0/255, zoom_range=0.2,horizontal_flip=True)
#validation...0-1で正規化
val_datagen = ImageDataGenerator(rescale=1.0 / 255)

#学習のためのイテレーター定義
train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(img_h,img_w),
    color_mode = 'rgb',
    classes = classes,
    class_mode = 'categorical',
    batch_size = 5
)
#validationのためのイテレーター定義
val_generator = val_datagen.flow_from_directory(
    val_dir,
    target_size=(img_h,img_w),
    color_mode = 'rgb',
    classes = classes,
    class_mode = 'categorical',
    batch_size = 5
)

```

### モデルの保存とearly stopping
これは書籍を参考にしました。4章と付録部分です。
[詳解ディープラーニング](https://github.com/yusugomori/deeplearning-tensorflow-keras)
モデル保存はベストのモデルのみを保存するようにしています。save_best_only=Falseに変えるとすべてのepochのモデルを保存してくれます。
アーリーストッピングはvalidationのlossを見てstopかけてくれるようにしています。細々はドキュメンテーション読むのがはやそうです。
[Kerasドキュメント-コールバック](https://keras.io/ja/callbacks/)


```python:
#モデル保存のコールバック定義
checkpoint = ModelCheckpoint(
    filepath = os.path.join(
        model_dir,
        'model_{epoch:02d}.hdf5'
    ),
    save_best_only=True
)

#アーリーストッピングのコールバック定義
early_stopping = EarlyStopping(monitor='val_loss',patience=3,verbose=0,mode='auto')
```

### モデルの定義と学習
この部分もご本家様の解説が詳しいのでそちらを読んでみてください
kerasのResNet50モデル定義を読み込んで学習します。
[ResNetをFine Tuningして自分が用意した画像を学習させる](https://pchun.work/resnet%E3%82%92fine-tuning%E3%81%97%E3%81%A6%E8%87%AA%E5%88%86%E3%81%8C%E7%94%A8%E6%84%8F%E3%81%97%E3%81%9F%E7%94%BB%E5%83%8F%E3%82%92%E5%AD%A6%E7%BF%92%E3%81%95%E3%81%9B%E3%82%8B/)

```python:
#インプットの定義
input_tensor = Input(shape=(img_h,img_w,3))
#スクラッチから学習させたいときはweights=None 
#imagenetの重み使いたいときはweights='imagenet'などとする
ResNet50 = ResNet50(include_top=False, weights=None ,input_tensor=input_tensor)

#全結合層を新しく作る
top_model = Sequential()
top_model.add(Flatten(input_shape=ResNet50.output_shape[1:]))
top_model.add(Dense(nb_classes, activation='softmax'))
model = Model(input=ResNet50.input, output=top_model(ResNet50.output))

#ハイパーパラメータの定義
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=1e-3, momentum=0.9),
              metrics=['accuracy'])

#学習する
epoch = 10
history = model.fit_generator(
    train_generator,
    steps_per_epoch=train_samples,
    epochs=epoch,
    validation_data=val_generator,
    callbacks=[checkpoint,early_stopping]
)
```

### 結果をplotする
print(history.history)してみるとhistoryの中には4つの要素が辞書形式で入っていることがわかります。

```python:
{'val_loss': [751238512640.0], 'val_accuracy': [0.23999999463558197], 'loss': [65.4847153377533], 'accuracy': [0.344]}
```
これを使ってプロットしていきます。
プロットはより良い形があると思うのでむしろいろいろ教えてください。

```python:
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = epoch

plt.figure()
plt.plot(range(1,epochs+1),acc,'b',label = 'traning accracy')
plt.plot(range(1,epochs+1),val_acc,'r',label='validation accracy')
plt.title('Training and validation accuracy')
plt.legend()
plt.savefig('result_acc')


plt.figure()
plt.plot(range(1,epochs+1), loss, 'bo', label='Training loss')
plt.plot(range(1,epochs+1), val_loss, 'ro', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.savefig('result_loss')

plt.show()
```

# ②推論

## コード全文

```python:test.py
from keras.models import load_model
from keras.preprocessing.image import load_img, img_to_array
import numpy as np
import glob
import matplotlib.pyplot as plt 

#test and model dir
test_dir = './data/test'
model_dir = './model'

#num samples
test_samples = glob.glob(test_dir + '/*/*.jpg')
print(test_samples)

#img size
img_w, img_h = 224,224

###model###
model = load_model(model_dir + '/model_07.hdf5')

for i in enumerate(test_samples):
    print(i[1])
    x = load_img(i[1],color_mode='rgb',target_size=(img_h,img_w))
    x = img_to_array(x)/255
    x = x[None,...]
    predict = model.predict(x,batch_size=1,verbose=1)
    print(predict)
```

## 大まかに解説
前半は割愛します。

### モデルの読み込み
モデルの読み込みめっちゃ簡単でした。

```python:
from keras.models import load_model
model = load_model(model_dir + '/model_07.hdf5')
```
こんだけ！！こんだけで学習したものを全部読み込めます。すごい。
今回は10epoch学習したうちの7epochめの重みが一番良かったのでそれを読み込んでます。

### 推論
今回はフォルダ内の画像を1枚ずつ読み込んで、それぞれに対する推論値を出してほしかったのでこのようにしています。
x=x[None,...]で次元数を変えるのは今回初めて知りました。以下の解説が詳しいです。
[pythonの文法について](https://teratail.com/questions/117881)

```python
for i in enumerate(test_samples):
    print(i[1])
    x = load_img(i[1],color_mode='rgb',target_size=(img_h,img_w))
    #画像の正規化
    x = img_to_array(x)/255
    #入力次元を合わせるため3次元から4次元にする
    x = x[None,...]
    predict = model.predict(x,batch_size=1,verbose=1)
    print(predict)
```

こんなかんじで結果が出てきたら成功です。

```
./data/test\1\000001.jpg
1/1 [==============================] - 2s 2s/step
[[1. 0. 0. 0. 0.]]
./data/test\1\000002.jpg
1/1 [==============================] - 0s 258ms/step
[[1. 0. 0. 0. 0.]]
./data/test\1\000003.jpg
1/1 [==============================] - 0s 247ms/step
[[0. 1. 0. 0. 0.]]
./data/test\1\000004.jpg
1/1 [==============================] - 0s 232ms/step
[[0. 1. 0. 0. 0.]]
./data/test\1\000005.jpg
```

# おわりに
Kerasで書くの、らくちんだったから慣れていきたい