SE_BOKUのまとめノート的ブログ

SE_BOKUが知ってること・勉強したこと・考えたことetc

imagenet 1000 classで事前学習済のVGG16モデルを使った画像識別クラス/tensorflow2.0(tf.keras)

 tensorflow2.0に付属の「tf.keras.applications.VGG16」モデルを使って、1000種類の画像を識別できるPythonクラスを簡単に作ってみます。

※2019/01/21の記事をTensorflow2.0対応にリライトしました。

f:id:arakan_no_boku:20191220192814p:plain

はじめに

今回利用するモデルはVGG16です。

www.tensorflow.org

精度を追及するなら、もっと新しくて良いモデル(ResNetとかInceptionV3とか)も提供されていますが、CPUオンリーの非力なノートPCで実行するので、軽めのモデルを選んでます。

使用例のサンプルは、何故かTensorflowのサイトにはなくて、こちらのKerasのドキュメントにありました。

keras.io

VGG16のところを見ると、簡単な画像評価のサンプルが載ってます。

f:id:arakan_no_boku:20190116230614j:plain

まずは、これを参考にします。 

featuresは評価結果ではなく特徴マップであること

でも。

サンプルソース通りにすると、うまくいきません(笑)。 

そもそも。

model.predict(x) の左辺「features」は、(1,7,7,512)の特徴マップ(Feature map)であって、評価結果ではありません。

特徴マップって何?と思ったら、こちらを参照ください。

deepage.net

なので。

まず「model.predict(x)」で評価結果「shape (samples, 1000)).」を返してくれるようにオプションを変更する必要があります。

どこを修正するかというと、

model = VGG16(weights='imagenet', include_top=False)

の部分を

model = VGG16(weights='imagenet', include_top=True

 にします。

「include_top=False」だと、predict()は、特徴マップ(Feature Map)を返す。

「include_top=True」だと、評価結果「shape (samples, 1000)).」を返す。

そういう仕様です。 

結果を整形してくれるメソッドを使う

あと。

評価結果をそのまま使っても良いですけど、結果を扱いやすくしてくれる「tf.keras.applications.vgg16.decode_predictions()」というメソッドが用意されていますので、こちらを使った方が良いです。

これをつかえば・・例えば。

上記のfeaturesをうけて。

results = decode_predictions(features, top=5)[0]
for result in results:
    print(result)

みたいにすると、確率の高い順のベスト5がこんな感じで表示されます。

('n02504458', 'African_elephant', 0.77940494)
('n01871265', 'tusker', 0.19876274)
('n02504013', 'Indian_elephant', 0.021399843)
('n02437312', 'Arabian_camel', 0.00020747511)
('n02113799', 'standard_poodle', 5.550688e-05)

つまり、識別結果をひとつだけ表示させる場合は、「top=1」とすれば良くて、この結果の「results[0][1]」でクラス名がとれるわけです。

ちなみに、前に書いたオプションを「False」のままにしていると、当たり前ですが、以下のようなエラーがでて動きません。

 ValueError: decode_predictions expects a batch of predictions (i.e. a 2D array of shape (samples, 1000)). Found array with shape: (1, 7, 7, 512)

クラス化してみます

サンプル通りでは芸がないので、簡単なクラスにしてみました。

tensorflow2.0で動作確認しました。

import tensorflow as tf
import numpy as np


class Vgg16k:

    def __init__(self):
        self.model = tf.keras.applications.VGG16(
            weights='imagenet', include_top=True)
        self.IMAGE_SHAPE = (224, 224)

    def __predict(self, img):
        x = tf.keras.preprocessing.image.img_to_array(img)
        x = np.expand_dims(x, axis=0)
        x = tf.keras.applications.vgg16.preprocess_input(x)
        features = self.model.predict(x)
        results = tf.keras.applications.vgg16.decode_predictions(features, top=1)[0]
        ans = {}
        ans['pp'] = '{:.2f}'.format(results[0][2] * 100.0) + '%'
        ans['en'] = results[0][1]
        return ans

    def predict_from_path(self, imgpath):
        img = tf.keras.preprocessing.image.load_img(
            imgpath, target_size=self.IMAGE_SHAPE)
        return self.__predict(img)


if __name__ == '__main__':
    m = Vgg16k()
    a = m.predict_from_path('.\\tf20\\test.jpg')
    print(a['en'] + 'です。確率は' + a['pp'])

試しに評価してみる画像は、楽器「ファゴット」にしてみました。 

ファゴットは英語で「bassoon」です。

画像はこれです。

 

f:id:arakan_no_boku:20191220201156j:plain

 

実行結果はこちら。

f:id:arakan_no_boku:20191220201315p:plain

ちゃんと、bassoonになってます。

最初の1回だけ、学習済の重みをダウンロードするのに時間がかかります。

自分の環境だと10分くらいでした。

でも、2回目からはローカルに保存されるので速いです。

これはいいですね。

デモにはもってこいです(笑)

ではでは。