転移学習で猫の品種を当ててみる

実家で雑種の兄弟猫たちを飼っています。
その２匹が全然似ていないので、「**ねこ品種判定AI**」**LINEbot**をつくって品種割合を推定してみることにしました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/262969/fe0ff173-426c-93da-5066-c95138c0edc3.jpeg" width=60%>

ねこ画像をLINE画面で送ると、構成される品種の割合が返される仕様です。
<img src="https://qiita-image-store.s3.amazonaws.com/0/262969/9436534c-f8e2-f2c7-3de7-810250202319.jpeg" width=50%>

第二の目的として、Tensolflowの転移学習モデル**Inception-v3**を試してみたかったというのもあります。

では早速実装の手順を解説していきます。
結果だけ知りたい人は、スクロールして最後の章へ...

#システム構成
システム構成は簡単に書くとこんな感じ。
学習モデルをherokuサーバに置いて、WebhookでLINEbotから呼び出します。
![構成図.jpg](https://qiita-image-store.s3.amazonaws.com/0/262969/90e531c7-6d33-7e64-744a-11009a5e6167.jpeg)

#使用したデータセット
画像を集める手段はいくつかありますが、スクレイピングは枚数の制限等が面倒で、オックスフォード大が公開しているデータセットを使いました。
（http://www.robots.ox.ac.uk/~vgg/data/pets/）

<img width="905" alt="cat.png" src="https://qiita-image-store.s3.amazonaws.com/0/262969/d95e2539-612a-9b0c-c3e3-0e1e83134e1a.png">

ねこちゃんいっぱい...

少し枚数は少ないですが、品種ごとにラベリングされた犬猫画像がまとめられています。今回はこのデータセットのうち猫の画像セットを使用します。
犬の画像を使えば犬種判定AIがつくれます。

##枚数
枚数は**一品種あたり約200枚**。12品種なので**全部で2400枚**です。
DeepLearningの世界ではちょっと少な目な枚数。

##含まれる品種
・アビシニアン
・ベンガル
・バーマン
・ボンベイ
・ブリティシュショートヘア
・エジプティアンマウ
・メインクーン
・ペルシャ
・ラグドール
・ロシアンブルー
・シャム
・スフィンクス
何やら聞きなれない品種もありますが、ひとまずこれで学習します。


ちなみに、最近Googleがデータセットを検索出来るサービス**Google Dataset Search**をリリースしたようなので、今度はそれを使ってみたいです。（https://toolbox.google.com/datasetsearch）

#学習器をつくる（転移学習）
学習器はTnsolflowの**転移学習モデルInception-v3**を使いました。

**転移学習**とは、学習済みのモデルに、タスク固有のデータを追加で学習させることです。一般に、少ないデータ数でそれなりに高い精度のモデルが得られると言われています。

今回でいうと、様々なカテゴリの画像を学習済みのInception-v3モデルに、猫画像を追加で学習させて、ねこ品種判別に特化したモデルになるようパラメータを調整します。

まずはデータの前準備から。

```python:
x_size = 224
y_size = 224
kind_label = []
cat_img = []

#識別する品種のリスト
cat_list = ['Abyssinian','Bengal','Birman','Bombay','British_Shorthair','Egyptian_Mau','Maine_Coon','Persian'
            ,'Ragdoll','Russian_Blue','Siamese','Sphynx']

#データセットのロード
for cat_kind in cat_list:
    print(cat_kind)
    file_list = glob.glob('/path/to/cat/image/*.jpg')
    for file in file_list:
        img_path = file
        img = image.load_img(img_path, target_size=(x_size, y_size))
        x = image.img_to_array(img)
        x = preprocess_input(x)
        cat_img.append(x)
        kind_label.append(cat_kind) 

#品種ラベルをダミー化
Y_dummy = pd.get_dummies(kind_label)

X_train, X_test, y_train, y_test = train_test_split(
    cat_img, Y_dummy, test_size=0.2, random_state=42)
```

モデルを作っていきます。今回は最終段のみ再学習します。
以下のコードを変えることで再学習する範囲を調整することも可能です。需要があれば追記しようと思います。

```python:
model = InceptionV3(weights='imagenet')

# 中間層を出力するモデル
intermediate_layer_model = Model(inputs=model.input, outputs=model.layers[311].output)

feature = intermediate_layer_model.predict(x)
pd.DataFrame(feature.reshape(-1,1)).plot(figsize=(12, 3))

# Denseレイヤーを接続
x = intermediate_layer_model.output
x = Dense(1024, activation='relu')(x)
predictions = Dense(len(cat_list), activation='softmax')(x)

# 転移学習モデル
transfer_model = Model(inputs=intermediate_layer_model.input, outputs=predictions)

# 一旦全レイヤーをフリーズ
for layer in transfer_model.layers:
    layer.trainable = False

# 最終段のDenseだけ再学習する
transfer_model.layers[312].trainable = True
transfer_model.layers[313].trainable = True

transfer_model.compile(loss='categorical_crossentropy',
                       optimizer='adam',
                       metrics=['accuracy'])

#転移学習
transfer_model.fit(np.array(X_train), np.array(y_train), epochs=10,
                   validation_data=(np.array(X_test), np.array(y_test)))

#精度確認用に出力（必要に応じて）
loss, acc = transfer_model.evaluate(np.array(X_test), np.array(y_test))
print('Loss {}, Accuracy {}'.format(loss, acc))
```

最後にモデルをh5ファイルとして出力します。

```python:
transfer_model.save("./model.h5")
```

#LINEアカウントをつくる
以下のURLからLINE@アカウントを作ります。
https://at.line.me/jp/

「LINE@アカウントを作成する」＞「一般アカウントを作成する」からLINEアカウントにログインし、必須項目を入力してアカウントを作成する。

アカウントを作成した後のbot設定は以下の通り。
・LINE@MANAGER画面＞ Messaging API設定 ＞API申し込み
・LINE Developersで設定するボタンをクリック
・”Channel Secret”、”アクセストークン”をメモしておく
・利用可能なAPIにREPLY_MESSAGEがあることを確認
・Webhook送信を「利用する」に

#herokuでサーバーを立てる
まずはherokuのアカウント登録から。
https://www.heroku.com/

無料で使えるサーバーを立てていきます。

##heroku環境導入
ローカルにherokuインストール
`$ brew install heroku`

ログインする
`$ heroku login`

アプリを作成
`$ heroku create アプリ名`

Herokuの環境変数にトークン追加
ここで上でメモしたLINEbotの情報が登場。”Channel Secret”、”アクセストークン”を使います。
`$ heroku config:set LineMessageAPIChannelAccessToken={アクセストークン} --app アプリ名
$ heroku config:set LineMessageAPIChannelSecret={Channel Secret} --app アプリ名`


準備するファイルはこちらの記事を参考にしました。（https://qiita.com/suigin/items/0deb9451f45e351acf92）

上記事中のapp.pyのみ画像を受け取って結果を返す仕様に変更しました。
（こちらもが需要があれば公開予定です。）
先ほど作った学習モデルはここで呼び出します。学習モデルのファイルはこのコードと一緒にherokuにデプロイしましょう。

一通りherokuへのデプロイが済んだところで、
`$ heroku open`
で出力されたWebhook URLをLINE Developersの設定画面に戻って設定すれば完了！

長かった...

#完成！
やっと完成したので、ひとまずラグドールの画像で試してみたら良い感じ！
そしてかわいい...
<img src="https://qiita-image-store.s3.amazonaws.com/0/262969/482b281d-0925-5280-f18b-af9b248c9011.jpeg" width=60%>
##うちの猫たちで試してみる
うちの雑種ちゃん２匹で試してみます。
長毛くんの方は...
<img src="https://qiita-image-store.s3.amazonaws.com/0/262969/c4338516-12de-c71d-f716-4ae56ac78f2a.jpeg" width=60%>
**53.8%がラグドール、34%でバーマン**とのこと。ちゃんと長毛認識してそう。しかもどことなく面影が...?

まっくろちゃんの方は...
<img src="https://qiita-image-store.s3.amazonaws.com/0/262969/97f7ac4d-214c-628e-6588-b3cb778f56a8.jpeg" width=60%>

**98%の確率でブリティッシュショートヘアー**。確かにちょっとふてぶてしい表情とか似てる。
![BS.jpg](https://qiita-image-store.s3.amazonaws.com/0/262969/2e0f7cf5-973f-a52f-94e4-1ac8d1cb8867.jpeg)
画像引用：https://madoguchi.iyell.jp/british_short_hair_cat/


##結論
転移学習を使えば、大量の画像を用意しなくても、なんとなく当たってそうな識別器が出来た。
テストデータで正解率を見たら8割弱くらい。まだ改善の余地はあるが、少しのデータでここまでのモデルが出来るの結構すごい。