VGG16を転移学習させて「まどか☆マギカ」のキャラを見分ける

画像検索で拾った画像を約80%の精度で見分けることができた。

*VGG16を転移学習させたモデルで予測した画像*
![pred1.png](https://qiita-image-store.s3.amazonaws.com/0/225485/ea4d8fa6-43e3-98f6-8b10-d05e5a55ddbd.png)

![pred2.png](https://qiita-image-store.s3.amazonaws.com/0/225485/fd7c8f74-8f88-dbdb-3a0f-75815819580d.png)

ちなみにキャラクターは、

![homura.jpg](https://qiita-image-store.s3.amazonaws.com/0/225485/578ce042-70df-913e-b205-ccd941fcbdb0.jpeg)

**homura(暁美ほむら)**
かわいいよほむほむ。

![kyoko.jpg](https://qiita-image-store.s3.amazonaws.com/0/225485/dd6b1cf8-755f-d274-df09-ee554ac160ce.jpeg)

**kyoko(佐倉杏子)**

![madoka.jpg](https://qiita-image-store.s3.amazonaws.com/0/225485/ebb54e8f-89bb-4423-0a08-c19fa2e9d8d2.jpeg)

**madoka(鹿目まどか)**

![mami.jpg](https://qiita-image-store.s3.amazonaws.com/0/225485/fd5ce7fe-32f3-c9d0-ae88-98ca272ee372.jpeg)

**mami(巴マミ)**

<img width="151" alt="sayaka.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/43a0eb1f-73e4-0098-ae7f-46d20b17e456.png">

**sayaka(美樹さやか)**

#転移学習とは

例えば、CNN(Convolutional Neural Network)で画像認識などを一からモデル構築するとなると、大量のサンプル画像を集めなければいけないですし、さらに学習にも多くの時間がかかります。

**転移学習では、すでに学習済みのモデルを使って、より少ない画像、より短時間で学習モデルを構築することを目指します。**

VGG16という学習モデルを例にしてみます。

![vgg16.png](https://qiita-image-store.s3.amazonaws.com/0/225485/dc5248b8-06cb-9d1e-43f4-c50237660952.png)

引用：[少ない画像から画像分類を学習させる方法（kerasで転移学習：fine tuning）](https://spjai.com/keras-fine-tuning/)

VGG16は13層の畳み込み層と3層の全結合層の計16層からなります(Kerasで使う場合はMaxPoolingなども1層に含まれる)
ImageNetと呼ばれる大規模画像データセットで学習させたモデルです。

今回の実装では、VGG16の全結合層を外して新たに全結合層を追加し
、15層以降のみを学習させます。(14層までの重みは更新しない)
これによって、**VGG16の高い特徴量抽出を継承しつつ、少サンプル・短時間で精度の高い学習モデルを構築できます。**

なぜこんなことをするかというと、**CNNにおいて浅い層では縦線・横線などのおおよその特徴を抽出し、深い層(VGG16の15層以降など)では、その画像特有の特徴を抽出することがわかっています。**
つまり、深い層を取り外し、浅い層を再利用することで効率よく転移学習することができます。

#Kerasで実装

スクリプトを実装する前にデータセットを用意します。

私のGitHubのほうにデータなどが置いてあります。[GitHub](https://github.com/Kisaragi-2/VGG16)

GitHubのほうにmadoka_magica_imagesというフォルダがあると思います。この中に転移学習で使うようの画像が入っています。

<img width="125" alt="SnapCrab_NoName_2018-2-6_23-30-10_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/95dc46b9-d5ba-f8cd-922a-5fed0586e8f3.png">


画像がtrain 250枚、validation 150枚、test 412枚入ってます。displayは冒頭のような画像を表示させるときに使う用です。中身はtestと同じ。

ちなみにこのデータセットは、Googleの画像検索で延々とポチポチして自作しました。いやぁ今回やってて一番楽しかったポイントですね（遠い目

##ライブラリのインポートとモデル構築

madoka_magica_imagesをダウンロードしたら、同じ階層にスクリプトを実装しましょう。

```python
from keras.models import Model
from keras.layers import Dense, GlobalAveragePooling2D,Input
from keras.applications.vgg16 import VGG16
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import SGD
from keras.callbacks import CSVLogger

n_categories=5
batch_size=32
train_dir='madoka_magica_images/train'
validation_dir='madoka_magica_images/validation'
file_name='vgg16_madomagi_fine'

base_model=VGG16(weights='imagenet',include_top=False,
                 input_tensor=Input(shape=(224,224,3)))

#add new layers instead of FC networks
x=base_model.output
x=GlobalAveragePooling2D()(x)
x=Dense(1024,activation='relu')(x)
prediction=Dense(n_categories,activation='softmax')(x)
model=Model(inputs=base_model.input,outputs=prediction)

#fix weights before VGG16 14layers
for layer in base_model.layers[:15]:
    layer.trainable=False

model.compile(optimizer=SGD(lr=0.0001,momentum=0.9),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

model.summary()
```

VGG16は1000カテゴリを分類するモデルですが、今回は5人のキャラクターなので、n_categories=5です。

```python
base_model=VGG16(weights='imagenet',include_top=False,
                 input_tensor=Input(shape=(224,224,3)))
```

ここでVGG16のモデルをインポートします。重みはImageNetを使い、全結合層はいらないので、include_top=Falseにします。
画像は224x224のRGBなのでshape=(224,224,3)

```python
x=base_model.output
x=GlobalAveragePooling2D()(x)
x=Dense(1024,activation='relu')(x)
prediction=Dense(n_categories,activation='softmax')(x)
model=Model(inputs=base_model.input,outputs=prediction)
```

さきほどのVGG16のモデルに新しく全結合層を取り付けます。

```python
for layer in base_model.layers[:15]:
    layer.trainable=False
```

14層目までの重みを更新しません。

##画像の前処理をして学習

```python
train_datagen=ImageDataGenerator(
    rescale=1.0/255,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True)

validation_datagen=ImageDataGenerator(rescale=1.0/255)

train_generator=train_datagen.flow_from_directory(
    train_dir,
    target_size=(224,224),
    batch_size=batch_size,
    class_mode='categorical',
    shuffle=True
)

validation_generator=validation_datagen.flow_from_directory(
    validation_dir,
    target_size=(224,224),
    batch_size=batch_size,
    class_mode='categorical',
    shuffle=True
)

hist=model.fit_generator(train_generator,
                         epochs=200,
                         verbose=1,
                         validation_data=validation_generator,
                         callbacks=[CSVLogger(file_name+'.csv')])

#save weights
model.save(file_name+'.h5')
```

ImageDataGeneratorは画像を整形したり、水増しするのに便利です。
読み込むフォルダを与えれば、自動的にそのフォルダの名前をラベルにしてくれます。
もっと詳しく知りたい方はこちらの記事が参考になると思います。[Kerasによるデータ拡張](http://aidiary.hatenablog.com/entry/20161212/1481549365)

##結果

test画像でモデルを評価し、画僧を並べて表示します。

```python
from keras.models import model_from_json
import matplotlib.pyplot as plt
import numpy as np
import os,random
from keras.preprocessing.image import img_to_array, load_img
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import SGD

batch_size=32
file_name='vgg16_madomagi_fine'
test_dir='madoka_magica_images/test'
display_dir='madoka_magica_images/display'
label=['homura','kyoko','madoka','mami','sayaka']

#load model and weights
json_string=open(file_name+'.json').read()
model=model_from_json(json_string)
model.load_weights(file_name+'.h5')

model.compile(optimizer=SGD(lr=0.0001,momentum=0.9),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

#data generate
test_datagen=ImageDataGenerator(rescale=1.0/255)

test_generator=test_datagen.flow_from_directory(
    test_dir,
    target_size=(224,224),
    batch_size=batch_size,
    class_mode='categorical',
    shuffle=True
)

#evaluate model
score=model.evaluate_generator(test_generator)
print('\n test loss:',score[0])
print('\n test_acc:',score[1])

#predict model and display images
files=os.listdir(display_dir)
img=random.sample(files,25)

plt.figure(figsize=(10,10))
for i in range(25):
    temp_img=load_img(os.path.join(display_dir,img[i]),target_size=(224,224))
    plt.subplot(5,5,i+1)
    plt.imshow(temp_img)
    #Images normalization
    temp_img_array=img_to_array(temp_img)
    temp_img_array=temp_img_array.astype('float32')/255.0
    temp_img_array=temp_img_array.reshape((1,224,224,3))
    #predict image
    img_pred=model.predict(temp_img_array)
    plt.title(label[np.argmax(img_pred)])
    #eliminate xticks,yticks
    plt.xticks([]),plt.yticks([])


plt.show()
```

![pred3.png](https://qiita-image-store.s3.amazonaws.com/0/225485/882a6cd9-e3b9-d688-acb9-69022a28bfc0.png)


![vgg16_madomagi.png](https://qiita-image-store.s3.amazonaws.com/0/225485/3a0afe42-2386-adc6-edaf-55ed28891b24.png)

<img width="152" alt="SnapCrab_NoName_2018-2-6_23-22-13_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/225485/1404de2f-85c1-a587-9a57-98313aa5b3a5.png">


validation_accuracyは最終的に約80%になりました。
test accuracyも約80%。
訓練画像250枚(各キャラ50枚ずつ)、この短時間でこの精度までいくのはやはり転移学習のおかげだと思います。

**今回のまどか☆マギカのキャラはImageNetにはない画像なので、VGG16にとっては見たことのない画像です。
それでもここまで精度を上げられたのはImageNetで学習してうまく特徴量を抽出できたおかげでしょう。**

#参考文献

1.[少ない画像から画像分類を学習させる方法（kerasで転移学習：fine tuning）](https://spjai.com/keras-fine-tuning/)
2.[Kerasによるデータ拡張](http://aidiary.hatenablog.com/entry/20161212/1481549365)
3.[VGG16のFine-tuningによる犬猫認識 (1)](http://aidiary.hatenablog.com/entry/20170108/1483876657)
4.[VGG16のFine-tuningによる犬猫認識 (2)](http://aidiary.hatenablog.com/entry/20170110/1484057655)
5.[ImageDataGenerator](https://keras.io/ja/preprocessing/image/)
6.[ModelクラスAPI](https://keras.io/ja/models/model/)