Help us understand the problem. What is going on with this article?

Deep Learningアプリケーション開発 (2) TensorFlow with Python

この記事について

機械学習、Deep Learningの専門家ではない人が、Deep Learningを応用したアプリケーションを作れるようになるのが目的です。MNIST数字識別する簡単なアプリケーションを、色々な方法で作ってみます。特に、組み込み向けアプリケーション(Edge AI)を意識しています。
モデルそのものには言及しません。数学的な話も出てきません。Deep Learningモデルをどうやって使うか(エッジ推論)、ということに重点を置いています。

  1. Kerasで簡単にMNIST数字識別モデルを作り、Pythonで確認
  2. TensorFlowモデルに変換してPythonで使用してみる (Windows, Linux) <--- 今回の内容
  3. TensorFlowモデルに変換してCで使用してみる (Windows, Linux)
  4. TensorFlow Liteモデルに変換してPythonで使用してみる (Windows, Linux)
  5. TensorFlow Liteモデルに変換してCで使用してみる (Linux)
  6. TensorFlow Liteモデルに変換してC++で使用してみる (Raspberry Pi)
  7. TensorFlow LiteモデルをEdge TPU上で動かしてみる (Raspberry Pi)

Google Colaboratory版

Google Colaboratory + Tensorflow2.x版を本記事の後ろに追記しました。
Google Colaboratory版

今回の内容

  • 前回作成したKeras用モデル(.h5)をTensorFlow用モデル(.pb)に変換する
  • 変換したTensorFlow用モデルを使って、入力画像から数字識別するアプリケーションを作る

ソースコードとサンプル入力画像: https://github.com/take-iwiw/CNN_NumberDetector/tree/master/02_Tensorflow_Python

なぜ必要? 想定するシナリオ

  • TensorFlow用モデルしかないとき
    • 社内の機械学習エンジニア様、データサイエンティスト様がTensorFlowでモデルを作成したら、それに対応したアプリケーションを作る必要があります (後述するように変換はできますが)
  • 今一番勢いがある?
    • ONNXやMMdnnを使用したら、異なるDeepLearning用フレームワーク間でのモデル変換が出来ます
    • TensorFlowに対応したアプリケーションの作り方を覚えておけば、大抵のことには対応できると思います
  • 動作環境にPython, Kerasがないとき
    • C言語用APIが用意されているので、ライブラリと一緒に配布すれば実行先での環境構築は不要です
      • お客様にPythonやAnacondaをインストールしてもらうわけにはいきません!
      • 今回はPythonで試しますが、C言語での実装の前段階ととらえてください

環境

  • OS: Windows 10 (64-bt)
  • OS(on VirtualBox): Ubuntu 16.04
  • CPU = Intel Core i7-6700@3.4GHz (物理コア=4、論理プロセッサ数=8)
  • GPU = NVIDIA GeForce GTX 1070 (← GPUは無くても大丈夫です)
  • 開発環境: Anaconda3 64-bit (Python3.6.8)
  • TensorFlow 1.12.0
  • パッケージ詳細はこちら Windows用Linux用

今回の内容は、WindowsとLinux(Ubuntu)のどちらでも動きますが、本記事の説明はWindowsメインで行います。

Keras用モデル(.h5)をTensorFlow用モデル(*.pb)に変換する

https://medium.com/@pipidog/how-to-convert-your-keras-models-to-tensorflow-e471400b886a のサイトを参考にさせていただき、変換用スクリプトを作りました。現在、FastGFileは非推奨とのことなので、tensorflow.gfile.GFileに置き換えています。
convert_keras_to_tensorflow 関数を呼ぶことで、Keras用モデル(.h5)をTensorFlow用モデル(*.pb)に変換できます。また、get_model_info 関数を呼ぶことで、変換したTensorFlow用モデルの情報をJSONファイルに保存します。
このスクリプトをそのまま実行すると、前回作成したKeras用モデル(conv_mnist.h5)を、TensorFlow用モデル(conv_mnist.pb)に変換してくれます。

keras_to_tensorflow.py
# -*- coding: utf-8 -*-
# Reference URL: https://medium.com/@pipidog/how-to-convert-your-keras-models-to-tensorflow-e471400b886a

import tensorflow as tf
from tensorflow.python.keras.models import load_model
from tensorflow.python.keras import backend as K
from tensorflow.python.framework.graph_util import convert_variables_to_constants
import numpy as np
import json

def freeze_session(sess, keep_var_names=None, output_names=None, clear_devices=True):
    graph = sess.graph
    with graph.as_default():
        freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))
        output_names = output_names or []
        output_names += [v.op.name for v in tf.global_variables()]
        input_graph_def = graph.as_graph_def()
        if clear_devices:
            for node in input_graph_def.node:
                node.device = ''
        frozen_graph = convert_variables_to_constants(sess, input_graph_def, output_names, freeze_var_names)
        return frozen_graph

def convert_keras_to_tensorflow(keras_model_filename, tf_model_filename):
    model = load_model(keras_model_filename)
    model.summary()
    frozen_graph = freeze_session(K.get_session(), output_names=[out.op.name for out in model.outputs])
    tf.train.write_graph(frozen_graph, './', tf_model_filename, as_text=False)


def get_model_info(tf_model_filename):
    ops = {}
    with tf.Session() as sess:
        with tf.gfile.GFile(tf_model_filename, 'rb') as f:
            graph_def = tf.GraphDef()
            graph_def.ParseFromString(f.read())
            sess.graph.as_default()
            _ = tf.import_graph_def(graph_def)
            for op in tf.get_default_graph().get_operations():
                ops[op.name] = [str(output) for output in op.outputs]
        writer = tf.summary.FileWriter('./logs')
        writer.add_graph(sess.graph)
        writer.flush()
        writer.close()

    with open(tf_model_filename+'_param.json', 'w') as f:
        f.write(json.dumps(ops))


if __name__ == '__main__':
    # convert
    keras_model_filename = 'conv_mnist.h5'
    tf_model_filename = 'conv_mnist.pb'
    convert_keras_to_tensorflow(keras_model_filename, tf_model_filename)
    get_model_info(tf_model_filename)

モデル情報を確認する

上記スクリプトを実行すると、TensorFlow用モデル(conv_mnist.pb)と共に、モデル情報を記した(conv_mnist.pb_param.json)も出力されます。
このモデル情報は非常に重要です。変換したモデルを使用する際に入出力の名前が必要になります。

前回モデル作成時に、入力にはInputを使い、出力は全結合をするDenseを使いsoftmaxを取るようにしました。また、グラフ名はデフォルトではimportになるようです。このような情報をヒントに入出力のTensorを探します。
今回は以下であることが分かります:

  • 入力: import/input_1:0
  • 出力: import/dense_1/Softmax:0
conv_mnist.pb_param.json
{
    "import/input_1": [
        "Tensor(\"import/input_1:0\", shape=(?, 28, 28, 1), dtype=float32)"
    ],
    "import/conv2d_1/kernel": [
        "Tensor(\"import/conv2d_1/kernel:0\", shape=(3, 3, 1, 8), dtype=float32)"
    ],
~略~
    "import/dense_1/Softmax": [
        "Tensor(\"import/dense_1/Softmax:0\", shape=(?, 10), dtype=float32)"
    ],
~略~
    "import_2/training_1/Adam/Variable_11": [
        "Tensor(\"import_2/training_1/Adam/Variable_11:0\", shape=(10,), dtype=float32)"
    ]
}

変換したTensorFlow用モデルを使って、入力画像から数字識別するアプリケーションを作る

Spyder上でnumber_detector_tensorflow.pyというファイルを開き、以下のようなコードを実装します。
動作仕様は前回Keras用に作成したnumber_detector.py と同じです。TensorFlow呼び出しもこの中でやっているので、少し複雑になっています。
get_tensor_by_nameに先ほど確認した入出力名を設定して、入出力Tensorを取得しています。

number_detector_tensorflow.py
# -*- coding: utf-8 -*-
import cv2
import tensorflow as tf
import numpy as np

if __name__ == '__main__':
    img = cv2.imread('resource/4.jpg')
    cv2.imshow('image', img)

    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img = cv2.resize(img, (28, 28))
    img = 255 - img
    img = img.reshape(1, img.shape[0], img.shape[1], 1)
    img = img / 255.

    with tf.Session() as sess:
        with tf.gfile.GFile('conv_mnist.pb', 'rb') as f:
            graph_def = tf.GraphDef()
            graph_def.ParseFromString(f.read())
            sess.graph.as_default()
            _ = tf.import_graph_def(graph_def)
            tensor_input = sess.graph.get_tensor_by_name('import/input_1:0')
            tensor_output = sess.graph.get_tensor_by_name('import/dense_1/Softmax:0')
            probs = sess.run(tensor_output, {tensor_input: img})

    result = np.argmax(probs[0])
    score = probs[0][result]

    print("predicted number is {} [{:.2f}]".format(result, score))

    cv2.waitKey(0)
    cv2.destroyAllWindows()

(追記)keras用モデル(h5)をTensorflow用モデル(pb)に変換する (別の方法)

saved_model を使うことで、より簡単に変換できました。
https://www.tensorflow.org/api_docs/python/tf/saved_model
(参考: https://qiita.com/t_shimmura/items/1ebd2414310f827ed608 (参考、というかほぼ使わせていただきました。ありがとうございます。))

keras_to_tensorflow2.py
import tensorflow as tf
from tensorflow.python.keras.models import load_model

input_keras_model = './conv_mnist.h5'
export_dir = './conv_mnist_pb'

if __name__ == '__main__':
    old_session = tf.keras.backend.get_session()
    sess = tf.Session()
    sess.run(tf.global_variables_initializer())
    tf.keras.backend.set_session(sess)
    model = load_model(input_keras_model)
    builder = tf.saved_model.builder.SavedModelBuilder(export_dir)
    signature = tf.saved_model.predict_signature_def(inputs={t.name:t for t in model.inputs},
                                                  outputs={t.name:t for t in model.outputs})
    builder.add_meta_graph_and_variables(sess,
                                      tags=[tf.saved_model.tag_constants.SERVING],
                                      signature_def_map={'predict': signature})
    builder.save(as_text=True)
    sess.close()
    tf.keras.backend.set_session(old_session)

    print('output_node_names:')
    for t in model.inputs:
        print(t.name)

    print('output_node_names:')
    for t in model.outputs:
        print(t.name)

上記スクリプトを実行すると、「conv_mnist_pb」というディレクトリが出来ます。この中にモデル構造を格納したファイル(.pbtxt)とweightパラメータが格納されたファイル(variables)が作られます。
また、最後に入出力名を表示しているので、下記コマンドのパラメータやTensorflowから使う際のヒントにしてください。

その後、ターミナルで以下のように打つことで、パラメータもフリーズされたpbファイル(frozen graph)が作られます。

freezeコマンド(改行区切りはWindows用なので、linuxでは\に変更してください)
freeze_graph ^
--input_saved_model_dir=./conv_mnist_pb ^
--output_graph=conv_mnist.pb ^
--output_node_names=dense_1/Softmax ^
--clear_devices

ちなみに、僕はIDEとしてSpyderを使用しているのですが、上記スクリプトを実行して、load_modelをやるたびに、ノード(op)名が、input_1, input_1_1, input_1_2, input_1_3と最後に数字のプリフィックスが付けられてどんどん増えていきます。conv_mnist_pbディレクトリに作られるpbtxtやweightファイルサイズもその分だけどんどん大きくなっていきます。最終的にfreeze_graph するときには必要なoutputだけを指定するので問題ないのですが、謎です。何かリセット的なのが必要なのでしょうか。。。
ターミナルから実行したり、Spyder上でカーネルを再起動したら問題ありません。

Google Colaboratoryで試す

本記事の内容を、Google Colaboratoryで試します。
使用するTensorflowのバージョンは、2.1.0-rc1でした。

https://github.com/iwatake2222/colaboratory_study/blob/master/DL_tutorial/DL_tutorial_02.ipynb

Keras用モデル(.h5)をTensorFlow用モデル(*.pb)に変換する

下記コードでは、Kerasモデルを僕のgoogleドライブからダウンロードしていますが、前回の続きからやる場合は不要です。
saved_modelの保存先ディレクトリに、saved_model.pb ファイルと、assetsvariableディレクトリが作られます。
最後に、生成されたsaved modelをtarにまとめています。

Keras用モデル(.h5)をTensorFlow用モデル(*.pb)に変換する
%tensorflow_version 2.x
from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf

print(tf.__version__)

# Download Keras H5 model
!wget -O conv_mnist.h5 "https://drive.google.com/uc?export=download&id=1OLR1n5Pq0CGPz7Bw5pad-fvYsgCnKvHh" 

# Load Keras H5 model, then save as Tensorflow saved model(pb)
loaded_model = tf.keras.models.load_model("conv_mnist.h5")
loaded_model.save("conv_mnist_saved_model", save_format="tf")
# tf.saved_model.save(model, 'saved_model')

!ls -la conv_mnist_saved_model
!tar zcvf conv_mnist_saved_model.tar.gz  conv_mnist_saved_model

モデル情報を見てみる

本記事では、Tensorflowモデルの情報を見るために、色々とコードを書いてみましたが、下記コマンドで見ることが出来ます。

モデル情報を見てみる
!saved_model_cli show --dir conv_mnist_saved_model  --tag_set serve --signature_def serving_default
実行結果
The given SavedModel SignatureDef contains the following input(s):
  inputs['input_image'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 28, 28, 1)
      name: serving_default_input_image:0
The given SavedModel SignatureDef contains the following output(s):
  outputs['output_scores'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 10)
      name: StatefulPartitionedCall:0
Method name is: tensorflow/serving/predict

Tensorflow saved modelによる推論テスト

Tensorflow_savedmodelによる推論テスト
import cv2
from google.colab.patches import cv2_imshow
import numpy as np

# Read input image
!wget -O 4.jpg  "https://drive.google.com/uc?export=download&id=1-3yb3qCrN8M6Bdj7ZZ9UMjONh34R2W_A" 
img = cv2.imread("4.jpg")
cv2_imshow(img)

# Pre process
## グレースケール化、リサイズ、白黒反転、価範囲を0~255 -> 0.0~1.0
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.resize(img, (28, 28))
img = 255 - img
img = img / 255.
img = img.astype(np.float32)
input_tensor = img.reshape(1, img.shape[0], img.shape[1], 1)
input_tensor = tf.convert_to_tensor(input_tensor)

# Load model
loaded_model = tf.saved_model.load("conv_mnist_saved_model")
infer = loaded_model.signatures["serving_default"]
output_name = list(infer.structured_outputs.keys())[0]

# Inference
output_tensor = infer(input_tensor)[output_name]

scores = output_tensor.numpy()
result = np.argmax(scores[0])
print("predicted number is {} [{:.2f}]".format(result, scores[0][result]))
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away