Sep 08, 2019

TensorFlowのofficialな学習済みResNetを動かしてみる

少し前に画像認識できるTensorFlowの学習済みモデルを探していると、https://github.com/tensorflow/modelsのofficialの下に、ImageNetデータセットで学習済みの、ResNetのPre-trained modelというのを見つけた。
ResNet以外のモデルやPre-trained modelがほとんど無いので、"official"といってもあまり注目されていないサイトなのかな、TensorFlow Hubがメインストリームなのかなと思いつつも、"official"なので信頼できそうだし、メンテナンスされているだろうからクオリティが高いだろう、難なく動かせるだろうと思って、とりあえず動かしてみようと思った。

しかし、TensorFlowのofficialなものなので、サンプルコードがすぐに見つかるだろうと思ったが、直接的なものを全く見つけることができなかった。その時点でこれは興味を持つ人が少ない、あまり良いものではないのだろうなと確信したが、何せTensorFlowのofficialなものなので、一応動かしておこうと思った。筆者は"official"という言葉に弱いのである。
しかし、TensorFlowの使い方をほとんど知らないまま、巷のサンプルコードをつぎはぎしながらでは予想以上に難しく、見ても何が悪いのかがわからない同じエラーメッセージを何時間も見続けて嫌になったが、それでもofficialなものを動かせなくては敗北と思って、さらに何時間も費やしてしまった。
最終的にはよくわからない所があるまま何らか動いたので、一応そのコードを記録する。

使用したモデルは、"ResNet in TensorFlow"のPre-trained modelの"ResNet-50 v2 (fp32, ...)"のSavedModelの所にある、"(NHWC)"と"(NHWC, JPG)"というリンクの先の、以下の2つのファイルである。
[1] resnet_v2_fp32_savedmodel_NHWC.tar.gz
[2] resnet_v2_fp32_savedmodel_NHWC_jpg.tar.gz
これらを、カレントディレクトリに展開したものとする。 すると、それぞれ
resnet_v2_fp32_savedmodel_NHWC/1538687283/
resnet_v2_fp32_savedmodel_NHWC_jpg/1538687457/
の下に、
saved_model.pb
variables/
が展開される。

それぞれ、saved_model_cliコマンドを使って、入力テンソルと出力テンソルの情報を表示してみる。

$ saved_model_cli show --dir resnet_v2_fp32_savedmodel_NHWC/1538687283/ --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['predict']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['input'] tensor_info:
        dtype: DT_FLOAT
        shape: (64, 224, 224, 3)
        name: input_tensor:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['classes'] tensor_info:
        dtype: DT_INT64
        shape: (64)
        name: ArgMax:0
    outputs['probabilities'] tensor_info:
        dtype: DT_FLOAT
        shape: (64, 1001)
        name: softmax_tensor:0
  Method name is: tensorflow/serving/predict
$ saved_model_cli show --dir resnet_v2_fp32_savedmodel_NHWC_jpg/1538687457/ --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['predict']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['image_bytes'] tensor_info:
        dtype: DT_STRING
        shape: (-1)
        name: input_tensor:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['classes'] tensor_info:
        dtype: DT_INT64
        shape: (-1)
        name: ArgMax:0
    outputs['probabilities'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1001)
        name: softmax_tensor:0
  Method name is: tensorflow/serving/predict

まず、tf.contrib.predictorを使って、"(NHWC, JPG)"[2]についてやってみた。
これは割と簡単に動いた。

●コード例1
import tensorflow as tf
from tensorflow.contrib import predictor

img_path = 'elephant.jpg'
with open(img_path, 'rb') as F:
    jpeg_bytes = F.read()

predict_fn = predictor.from_saved_model("./resnet_v2_fp32_savedmodel_NHWC_jpg/1538687457/")
result = predict_fn({'image_bytes': [jpeg_bytes]})

cls = result['classes'][0]
prob = result['probabilities'][0, cls]
print('class={} probability={:.3f}'.format(cls, prob))
●出力例1
class=386 probability=0.988

動作確認した環境は、macOS 10.13.6 + TensorFlow 1.13.1である。Raspberry Pi 2 v1.2(Cortex A53) + TensorFlow 1.13.1では、import行で後述のエラーが出て動かなかった。
コード中の'image_bytes'というのは、上記のsaved_model_cliの出力にある。
'elephant.jpg'はhttps://keras.io/ja/applications/の"Classify ImageNet classes with ResNet50"のコード例に合わせたものだが、officialなelephant.jpgが見つからなかったので、All-free-download.comの中から選んだ、次の画像を使った。
elephant_in_kobe_zoo_514337_resized.jpg
class=386は、別途調べた(後述)所では"Indian_elephant"なので、正解である。

次に、同じくtf.contrib.predictorを使って、"(NHWC)"[1]についてやってみた。
これはかなり苦労した。

●コード例2
import numpy as np
from PIL import Image
import tensorflow as tf
from tensorflow.contrib import predictor

img_path = 'elephant.jpg'
img = Image.open(img_path).resize((224,224))
x = np.array(img)
x = x.astype(np.float32)

predict_fn = predictor.from_saved_model("./resnet_v2_fp32_savedmodel_NHWC/1538687283/")
result = predict_fn({'input': np.array([x] * 64)})

cls = result['classes'][0]
prob = result['probabilities'][0, cls]
print('class={} probability={:.3f}'.format(cls, prob))
●出力例2
class=386 probability=0.821

上記のsaved_model_cliの出力にあるように、入力テンソルのshapeが(64, 224, 224, 3)なので、1枚の画像だけを認識したくても、必ず64枚分渡す必要がある(認識処理も64枚分まとめてなされる)のである。そのことになかなか気付かなかったので、同じエラーメッセージを嫌になるほど目にする羽目になってしまった。一体そういう仕様にすることにどういう意味があるのだろうか。これでは使い勝手が悪すぎる。
さらに、同じ形状のニューラルネットワークと同じ入力画像を使ってるのに、probabilityの値が"(NHWC, JPG)"[2]よりも悪くなっている。これについて少し調べたことを後述する。

次に、predictorを使わず、TensorFlow APIだけを使って、"(NHWC, JPG)"[2]についてやってみた。

●コード例3
import tensorflow as tf

img_path = 'elephant.jpg'
with open(img_path, 'rb') as F:
    jpeg_bytes = F.read()

with tf.Session(graph=tf.Graph()) as sess:
    tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], "./resnet_v2_fp32_savedmodel_NHWC_jpg/1538687457/")
    class_tensor = sess.graph.get_tensor_by_name('ArgMax:0')
    prob_tensor = sess.graph.get_tensor_by_name('softmax_tensor:0')
    classes, probabilities = sess.run([class_tensor, prob_tensor], {'input_tensor:0': [jpeg_bytes]})

cls = classes[0]
prob = probabilities[0, cls]
print('class={} probability={:.3f}'.format(cls, prob))
●出力例3
class=386 probability=0.988

これはRaspberry Pi 2 v1.2 + TensorFlow 1.13.1でも動いた。
コード中の'ArgMax:0', 'softmax_tensor:0', 'input_tensor:0'は、上記のsaved_model_cliの出力から拾って試行錯誤して見つけた。

最後に、同じくpredictorを使わず、TensorFlow APIだけを使って、"(NHWC)"[1]についてやってみた。

●コード例4
import numpy as np
from PIL import Image
import tensorflow as tf

img_path = 'elephant.jpg'
img = Image.open(img_path).resize((224,224))
x = np.array(img)
x = x.astype(np.float32)

with tf.Session(graph=tf.Graph()) as sess:
    tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], "./resnet_v2_fp32_savedmodel_NHWC/1538687283/")
    class_tensor = sess.graph.get_tensor_by_name('ArgMax:0')
    prob_tensor = sess.graph.get_tensor_by_name('softmax_tensor:0')
    classess, probabilities = sess.run([class_tensor, prob_tensor], {'input_tensor:0': np.array([x] * 64)})

cls = classes[0]
prob = probabilities[0, cls]
print('class={} probability={:.3f}'.format(cls, prob))
●出力例4
class=386 probability=0.821

See more ...

Posted at 18:35 in PC一般 | WriteBacks (0)
WriteBacks