【記録用】Keras画像系その2 : 自前データセットを使ってCNNで判定させる

どうも。

前回の記事はKerasで画像を読み込んでデータセットを作るまでの記事でした。

https://qiita.com/ku_a_i/items/7f71933f6b71cf30a3a8

今回はそのデータセットを使って実際にCNNで判定させるところまでやってみます。
フォルダ構成は以下のようになっている前提です。

```Python:
'''
picture -╷- train -╷- Apple(分類1) -╷- **.png #学習用
         ╎         ╎            　  ╎  (多数の画像・・省略)
         ╎         ╎            　  ╵- **.png
         ╎         ╵- Mango(分類2) - (略)
         ╎         
         ╵-  val  -╷- Apple(分類1) - (略) #学習検証用
                   ╵- Mango(分類2) - (略)
'''
```


#0.前回のおさらい。データセットの作成

```Python:
from keras.preprocessing.image import ImageDataGenerator

#設定
classes = ['Apple', 'Mango']#リンゴとマンゴーを分類するように学習させたい場合
train_data_dir = './picture/train'#クラス分類の親フォルダを指定する(Apple,Mangoの上位フォルダ)
val_data_dir = './picture/val'
img_height = 100
img_width = 100
batch_size = 16

#トレーニングデータを作成する
#255で割ってスケーリングする
train_datagen = ImageDataGenerator(rescale = 1.0 / 255) #水増し等の設定もできるが、今回は省略する

#学習のgeneratorを設定する。
#generatorとはざっくり言えば都度(ここではバッチサイズ毎に)画像を生成するということ
train_generator = train_datagen.flow_from_directory(
    train_data_dir,
    target_size = (img_height, img_width), #次元は不要
    color_mode = 'rgb', #グレーの場合、'grayscale'と入力
    classes = classes, 
    class_mode = 'binary', #2つなのでbinary。3つ以上の場合は、'categorical'
    batch_size = batch_size,
    shuffle = True)


#バリデーションデータを作成する
val_datagen = ImageDataGenerator(rescale = 1.0 / 255)

#バリデーションのgeneratorを設定する。
val_generator = val_datagen.flow_from_directory(
    val_data_dir,
    target_size = (img_height, img_width),
    color_mode = 'rgb',
    classes = classes, 
    class_mode = 'binary',
    batch_size = batch_size,
    shuffle = True)
```
これでデータセットができたので、次にこのデータを入れ込むＣＮＮを作成していきます。


#1.CNNの作成

CNNにもいくつか種類があります。
簡単に言えば
①自分で0から設計して層を作成する
②すでに世の中の誰かが学習用にチューニングしたモデルを流用して使用する

大きくざっくり分類するとこの2つになります。
①も②もすでにQiitaにわかりやすい記事が多いので、今回はサクッと②でやってみることにします。
私がお伝えしたいのは、Kerasのgeneratorを使用したCNN学習が第一ですので。


```Python:
#VGG16をロードする

from keras.applications.vgg16 import VGG16
from keras.optimizers import Adam
from keras.layers import Input, Dense, Flatten, Dropout
from keras.models import Sequential
from keras.models import Model

input_tensor = Input(shape=(img_width, img_height, 3))

#VGGのロード部分。FC層は不要なので、include_top=Falseとする
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

#不要としたFC層は自分で設計する
model = Sequential()
model.add(Flatten(input_shape=vgg16.output_shape[1:]))
model.add(Dense(256, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

#VGG16と自作のFC層を結合してモデルを作成
vgg16_model = Model(input=vgg16.input, output=model(vgg16.output))

#最後のconv層の直前までの層を凍結する(凍結：学習から省く)
for layer in vgg16_model.layers[:15]:
    layer.trainble=False
    
#コンパイル
vgg16_model.compile(loss='binary_crossentropy',
                 optimizer=Adam(lr=1e-4),
                 metrics=['accuracy'])
```

これで学習に使用するCNNの設計が終わりました。
後は学習するだけです。
その前に、CNNの全体像を確認しましょう

```Python:
vgg16_model.summary()
```

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/66ebce7f-8d13-0c09-1a1a-acff76e21656.png)

かなり贅沢な作りですが、そこは気にせず。。

ちなみに、自作の個所はsequentialの個所ですが詳細がありません。
それに関しては以下で確認してみましょう

```Python:
model.summary()
```
![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/d2b4f94e-802d-ba8f-7f91-ad4b670df369.png)

これですべての層が明らかになりました。

#2.CNNで学習させてみる

全体像も把握できたので、いよいよ学習開始です。

```Python:
#全画像枚数を入れる　※.nで全枚数がわかるので便利
steps_per_epoch = train_generator.n
validation_steps = val_generator.n
#Epoch数の設定。後述のEarlyStoppingがあるため、大きめに設定しておけばいい
nb_epoch = 1000

#コールバックの設定
from keras.callbacks import EarlyStopping,ModelCheckpoint
es = EarlyStopping(monitor='val_loss', patience=10, verbose=1)#patienceの数字だけval_lossが更新しなければ自動停止
cp = ModelCheckpoint(filepath = './model.hdf5', save_best_only=True)#save_best_onlyでlossを更新したときにモデル保存

#CNN学習開始。fit_generatorを使用する。
history = vgg16_model.fit_generator(
    train_generator,
    steps_per_epoch = steps_per_epoch // batch_size,
    epochs = nb_epoch,
    verbose=1,
    validation_data=val_generator,
    validation_steps = validation_steps // batch_size,
    callbacks=[es, cp])
```

学習が終わったら、学習の様子をプロットして表示してみましょう

```Python:
acc = histry.history['acc']
val_acc = histry.history['val_acc']
loss = histry.history['loss']
val_loss = histry.history['val_loss']

epochs = range(1, 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 accuracy')
plt.legend()
plt.figure()

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()
```

![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/e9942430-fd78-908f-b363-bdc1956c6519.png)


#3.学習済モデルを使って判定させてみる

そして、出来上がった学習モデルで別の画像を判定させてみましょう
前回画像の読み込み方を学びましたので、load_imgで読み込んで1枚判定させてみましょう。
※判定画像は簡単のためプログラムと同じ階層に配置してます。(B.jpg)
※複数データをテストする場合は同じようにgeneratorを使ってもlist化してもどっちでもOKです

```Python:
import numpy as np
from keras.preprocessing.image import load_img,img_to_array

img_shape = (100, 100, 3) 
img = load_img('B.jpg', grayscale=False, target_size=img_shape)

#ニューラルネットワークに入れる為にnumpyのarray型に変換する
x_test = img_to_array(img) / 255

#CNNに入れるためにreshapeで(n, w, h, c)型にしてやる
#0～1の少数が出力されるので、np.roundで出力をクラスラベルに変更してやる(0or1)
print(np.round(vgg16_model.predict(x_test.reshape(1,100,100,3))))
```

すると、結果として0か1が表示されると思います。
print(train_generator.class_indices)でそれぞれのクラス分けが確認できるので
学習がうまくいってる場合、0なら今回はApple,1ならMangoとなるはず。



さて、今回はこんなところにします。いかがだったでしょうか？
そこまで難しくなかったと思います。
次回はオートエンコーダで画像復元をやるか、3以上の分類バージョンを考えています。

なお、リリースを優先したので後で見返して変な個所や説明足らない箇所は補足/修正しますのであしからず。。