Last active
January 14, 2019 23:52
-
-
Save nagachika/6b189e2f2750dc5ab85b20ad5bc8e5e9 to your computer and use it in GitHub Desktop.
tf_estimator_inputs_outputs_for_serving.ipynb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"nbformat": 4, | |
"nbformat_minor": 0, | |
"metadata": { | |
"colab": { | |
"name": "tf_estimator_inputs_outputs_for_serving.ipynb", | |
"version": "0.3.2", | |
"provenance": [], | |
"collapsed_sections": [], | |
"include_colab_link": true | |
}, | |
"kernelspec": { | |
"name": "python3", | |
"display_name": "Python 3" | |
}, | |
"accelerator": "GPU" | |
}, | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "view-in-github", | |
"colab_type": "text" | |
}, | |
"source": [ | |
"<a href=\"https://colab.research.google.com/gist/nagachika/6b189e2f2750dc5ab85b20ad5bc8e5e9/tf_estimator_inputs_outputs_for_serving.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "-vkQKwY3p3rF", | |
"colab_type": "code", | |
"colab": {} | |
}, | |
"cell_type": "code", | |
"source": [ | |
"import numpy as np\n", | |
"import matplotlib.pyplot as plt\n", | |
"\n", | |
"import tensorflow as tf" | |
], | |
"execution_count": 0, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"id": "iLjiTd3f6U2l", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"# TF-Serving向けに入出力処理を追加する手法(画像分類モデル)\n", | |
"\n", | |
"以下のように、トレーニング時と TF-Serving で REST/gRPC で予測に使う時には入出力の形式を変更したい、という要望をかなえるための方法を紹介します。\n", | |
"\n", | |
"- トレーニング時はTFRecord等で既にTensor 化されてるけどServing時には画像ファイルの内容を直接受け取りたい\n", | |
"- トレーニング時はlogits(各ラベル毎のscore)や最大scoreのラベルのidを出力するけどServing時には最大scoreのラベルを名前(string)で返したい\n", | |
"\n", | |
"※ ただし TensorFlow 1.12 を前提としています(つまり 2.0 ではない)。また graph mode を前提としています。\n", | |
"\n", | |
"※ tf.estimator を利用したケースになります。tf.kerasを使った場合1.x系ではまだSavedModelへの出力が tf.contrib のモジュール依存となっていて、今回紹介するような入出力の変更がすんなりできません。tf.keras.backend などを使って内部の Session を取り出してきて力技でやる必要があります。そちらは経験がないのでスキップです。2.0 では keras のモデルも入出力のフィルタ追加ができるようになっているといいですね。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "6kNIniWi648E", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"### データセット\n", | |
"\n", | |
"データやモデルは公式サイトのチュートリアル [Basic classification](https://www.tensorflow.org/tutorials/keras/basic_classification)\n", | |
"の Fashion MNIST を使った画像分類モデルのトレーニングを下敷きにします。ただし keras ではなく tf.estimator のカスタムモデルを組む方法を使います。\n", | |
"\n", | |
"チュートリアルではデータは [tf.keras.datasets](https://www.tensorflow.org/api_docs/python/tf/keras/datasets) モジュールを使って自動的にダウンロードしたデータを使って feeding する Dataset を使っていますが、実際の活用の場合はオリジナルのデータを使うため、あらかじめ前処理を行った TFRecord のファイルを用意して、それを読み込むことが多いのではないかと思います。いずれの場合も読み込んだ時点で既に画像は (width, height, channel) のような Tensor になっています(Fashion MNIST の場合はグレイスケールなので (width, height) の2次元)。\n", | |
"画素値は 0-255 の範囲の整数になっているので、事前に 0.0-1.0 の float に変換しておきます。つまりこのままだとモデルへの入力はこの scaling も事前にしておく必要がある状態だということに注意してください。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "CQqacgF0CTiQ", | |
"colab_type": "code", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 153 | |
}, | |
"outputId": "a45a0025-bffc-4b5b-968c-94fa470dd90b" | |
}, | |
"cell_type": "code", | |
"source": [ | |
"fashion_mnist = tf.keras.datasets.fashion_mnist\n", | |
"\n", | |
"(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()\n", | |
"\n", | |
"class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', \n", | |
" 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']\n", | |
"\n", | |
"train_images = train_images / 255.0\n", | |
"test_images = test_images / 255.0" | |
], | |
"execution_count": 2, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz\n", | |
"32768/29515 [=================================] - 0s 0us/step\n", | |
"Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz\n", | |
"26427392/26421880 [==============================] - 0s 0us/step\n", | |
"Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz\n", | |
"8192/5148 [===============================================] - 0s 0us/step\n", | |
"Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz\n", | |
"4423680/4422102 [==============================] - 0s 0us/step\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "iTQpZZE9Siz-", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"## input_fn\n", | |
"\n", | |
"training, evaluate それぞれのデータセットのパイプラインを作ります。今回はデータがメモリ上に乗ってるので `tf.data.Dataset.from_tensor_slices()` を利用しますが、ファイルから読む場合は tf.data.TFRecordDataset() を使ったりすることになると思います。\n", | |
"tf.estimator の場合トレーニングや evaluate の入力はそれぞれの input_fn という関数を渡して、その中で生成するようになっています。\n", | |
"なんだか面倒ですが、これは train() や evaluate() はが呼ばれた時点で実際に実行されるので、1つパイプラインを作って渡すだけでなく必要になった時に毎回作れるように、パイプラインを作る関数を渡すという方法になってます。またモデルへの入力は Estimator の場合名前(string)から Tensor への dict を返すようにします。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "6KGwHd5rSCP5", | |
"colab_type": "code", | |
"colab": {} | |
}, | |
"cell_type": "code", | |
"source": [ | |
"def train_input_fn():\n", | |
" dataset = tf.data.Dataset.from_tensor_slices(\n", | |
" (\n", | |
" { \"images\": tf.cast(train_images, tf.float32) },\n", | |
" tf.cast(train_labels, tf.int32)\n", | |
" )\n", | |
" )\n", | |
" dataset = dataset.shuffle(256).repeat().batch(32)\n", | |
" iterator = dataset.make_one_shot_iterator()\n", | |
" return iterator.get_next()\n", | |
"\n", | |
"def eval_input_fn():\n", | |
" dataset = tf.data.Dataset.from_tensor_slices(\n", | |
" (\n", | |
" { \"images\": tf.cast(test_images, tf.float32) },\n", | |
" tf.cast(test_labels, tf.int32)\n", | |
" )\n", | |
" )\n", | |
" dataset = dataset.batch(64)\n", | |
" iterator = dataset.make_one_shot_iterator()\n", | |
" return iterator.get_next()" | |
], | |
"execution_count": 0, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"id": "f6fPiVAnAnEu", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"## model_fn\n", | |
"\n", | |
"同様にモデルの本体の定義も関数として渡します。\n", | |
"model_fn には入力の Tensor(名前からTensor への dict)と教師ラベルのTensor の他に mode という引数が渡されます。これがモデルを トレーニング用、テスト(evaluate)用、予測(predict や export) 用いずれにするかを選べます。\n", | |
"つまりここでモデルの出力部分についてはトレーニング/テストとSavedModel形式に保存する時のGraphを変更する機会があります。\n", | |
"このサンプルでは logits から最大のクラスを tf.argmax で求めて、さらに tf.contrib.lookup モジュールを使ってクラスの名前に変換したものを出力に含めるようにしています。\n", | |
"しかしここで変更できるのは出力側だけです。TensorFlow の Graph は後ろ(出力側)は後から追加したり分岐したりするのは簡単ですが、前(入力側)を後からすりかえることができません。そのためにトレーニングや評価用には input_fn があるのですが、export 用にはまた別の関数が使われます(後述)。\n", | |
"またここで features から \"key\" という名前で Tensor を取り出していますが、これについても入力側のカスタマイズのところで説明します。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "iVxnu5CfArvQ", | |
"colab_type": "code", | |
"colab": {} | |
}, | |
"cell_type": "code", | |
"source": [ | |
"def model_fn(features, labels, mode):\n", | |
" images = features[\"images\"]\n", | |
" x = tf.reshape(images, (-1, 28*28))\n", | |
" x = tf.layers.dense(inputs=x, units=128, activation=tf.nn.relu, name=\"dense1\")\n", | |
" logits = tf.layers.dense(inputs=x, units=10, activation=None, name=\"logits\")\n", | |
" \n", | |
" if mode == tf.estimator.ModeKeys.TRAIN or mode == tf.estimator.ModeKeys.EVAL:\n", | |
" loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels, name=\"xentropy\"), name=\"loss\")\n", | |
" eval_metrics = { \"accuracy\": tf.metrics.accuracy(tf.argmax(logits, 1), labels) }\n", | |
" else:\n", | |
" loss = None\n", | |
" eval_metrics = None\n", | |
"\n", | |
" if mode == tf.estimator.ModeKeys.TRAIN:\n", | |
" optimizer = tf.train.AdamOptimizer(learning_rate=1e-4)\n", | |
" update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)\n", | |
" with tf.control_dependencies(update_ops):\n", | |
" train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())\n", | |
" else:\n", | |
" train_op = None\n", | |
"\n", | |
" if mode == tf.estimator.ModeKeys.PREDICT:\n", | |
" key = features[\"key\"]\n", | |
" predict_softmax = tf.nn.softmax(logits, name=\"output/softmax\")\n", | |
" predict_class_index = tf.argmax(predict_softmax, 1)\n", | |
" class_names_tensor = tf.constant(class_names, name=\"Classes\")\n", | |
" class_map_table = tf.contrib.lookup.index_to_string_table_from_tensor(class_names_tensor, default_value=\"UNKNOWN\")\n", | |
" predict_class = class_map_table.lookup(predict_class_index)\n", | |
" predictions = {\n", | |
" \"key\": key,\n", | |
" \"class\": predict_class,\n", | |
" \"score\": predict_softmax,\n", | |
" }\n", | |
" export_outputs = { tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: tf.estimator.export.PredictOutput(predictions) }\n", | |
" else:\n", | |
" predictions = { \"logits\": logits }\n", | |
" export_outputs = None\n", | |
" \n", | |
" return tf.estimator.EstimatorSpec(\n", | |
" mode=mode,\n", | |
" loss=loss,\n", | |
" train_op=train_op,\n", | |
" eval_metric_ops=eval_metrics,\n", | |
" export_outputs=export_outputs,\n", | |
" predictions=predictions)" | |
], | |
"execution_count": 0, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"id": "WJ7daFLiGES3", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"## トレーニング\n", | |
"\n", | |
"今回は export に注目したいので細かい設定やHookの追加はせず最低限の設定でトレーニングします。step数も小さめです。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "mkRrheWV_wXO", | |
"colab_type": "code", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 887 | |
}, | |
"outputId": "36bcbdcd-9f39-48a0-9227-5abc64368cfd" | |
}, | |
"cell_type": "code", | |
"source": [ | |
"train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn, max_steps=1000)\n", | |
"eval_spec = tf.estimator.EvalSpec(input_fn=eval_input_fn, steps=None)\n", | |
"\n", | |
"estimator = tf.estimator.Estimator(model_fn=model_fn, model_dir=\"./model\")\n", | |
"\n", | |
"tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec)" | |
], | |
"execution_count": 6, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"INFO:tensorflow:Using default config.\n", | |
"INFO:tensorflow:Using config: {'_model_dir': './model', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': allow_soft_placement: true\n", | |
"graph_options {\n", | |
" rewrite_options {\n", | |
" meta_optimizer_iterations: ONE\n", | |
" }\n", | |
"}\n", | |
", '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_protocol': None, '_eval_distribute': None, '_experimental_distribute': None, '_service': None, '_cluster_spec': <tensorflow.python.training.server_lib.ClusterSpec object at 0x7efbc353c0b8>, '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}\n", | |
"INFO:tensorflow:Not using Distribute Coordinator.\n", | |
"INFO:tensorflow:Running training and evaluation locally (non-distributed).\n", | |
"INFO:tensorflow:Start train and evaluate loop. The evaluate will happen after every checkpoint. Checkpoint frequency is determined based on RunConfig arguments: save_checkpoints_steps None or save_checkpoints_secs 600.\n", | |
"INFO:tensorflow:Calling model_fn.\n", | |
"INFO:tensorflow:Done calling model_fn.\n", | |
"INFO:tensorflow:Create CheckpointSaverHook.\n", | |
"INFO:tensorflow:Graph was finalized.\n", | |
"INFO:tensorflow:Running local_init_op.\n", | |
"INFO:tensorflow:Done running local_init_op.\n", | |
"INFO:tensorflow:Saving checkpoints for 0 into ./model/model.ckpt.\n", | |
"INFO:tensorflow:loss = 2.398727, step = 0\n", | |
"INFO:tensorflow:global_step/sec: 52.6201\n", | |
"INFO:tensorflow:loss = 1.3999133, step = 100 (1.907 sec)\n", | |
"INFO:tensorflow:global_step/sec: 486.373\n", | |
"INFO:tensorflow:loss = 0.86735415, step = 200 (0.201 sec)\n", | |
"INFO:tensorflow:global_step/sec: 513.534\n", | |
"INFO:tensorflow:loss = 0.7711177, step = 300 (0.194 sec)\n", | |
"INFO:tensorflow:global_step/sec: 510.703\n", | |
"INFO:tensorflow:loss = 0.74288714, step = 400 (0.194 sec)\n", | |
"INFO:tensorflow:global_step/sec: 502.309\n", | |
"INFO:tensorflow:loss = 0.7303697, step = 500 (0.199 sec)\n", | |
"INFO:tensorflow:global_step/sec: 502.576\n", | |
"INFO:tensorflow:loss = 1.0630589, step = 600 (0.201 sec)\n", | |
"INFO:tensorflow:global_step/sec: 482.306\n", | |
"INFO:tensorflow:loss = 0.7074257, step = 700 (0.210 sec)\n", | |
"INFO:tensorflow:global_step/sec: 495.139\n", | |
"INFO:tensorflow:loss = 0.83385867, step = 800 (0.202 sec)\n", | |
"INFO:tensorflow:global_step/sec: 510.318\n", | |
"INFO:tensorflow:loss = 0.6900308, step = 900 (0.194 sec)\n", | |
"INFO:tensorflow:Saving checkpoints for 1000 into ./model/model.ckpt.\n", | |
"INFO:tensorflow:Calling model_fn.\n", | |
"INFO:tensorflow:Done calling model_fn.\n", | |
"INFO:tensorflow:Starting evaluation at 2019-01-12-23:35:57\n", | |
"INFO:tensorflow:Graph was finalized.\n", | |
"INFO:tensorflow:Restoring parameters from ./model/model.ckpt-1000\n", | |
"INFO:tensorflow:Running local_init_op.\n", | |
"INFO:tensorflow:Done running local_init_op.\n", | |
"INFO:tensorflow:Finished evaluation at 2019-01-12-23:35:58\n", | |
"INFO:tensorflow:Saving dict for global step 1000: accuracy = 0.7947, global_step = 1000, loss = 0.61482364\n", | |
"INFO:tensorflow:Saving 'checkpoint_path' summary for global step 1000: ./model/model.ckpt-1000\n", | |
"INFO:tensorflow:Loss for final step: 0.46170104.\n" | |
], | |
"name": "stdout" | |
}, | |
{ | |
"output_type": "execute_result", | |
"data": { | |
"text/plain": [ | |
"({'accuracy': 0.7947, 'global_step': 1000, 'loss': 0.61482364}, [])" | |
] | |
}, | |
"metadata": { | |
"tags": [] | |
}, | |
"execution_count": 6 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "pPuz3CD1WP1a", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"## serving_input_receiver_fn\n", | |
"\n", | |
"ここが今回の主眼です。model_fn のところでTensorFlowのGraphは入力側の変更が後からできないと書きましたが、ここで定義する serving_input_receiver_fn() が export 時に Graph を構築する時に呼ばれる関数で、トレーニング時の input_fn に相当するものです。\n", | |
"\n", | |
"serving_input_receiver_fn では string から Tensor へのマップを保持した dict を2つ作って、ServingInputReceiver というオブジェクトを作って返します。この2つの dict はそれぞれ、入力の placeholder と、model_fn の features に渡される Tensor を渡します。この2つが別になっているので、TF-Serving の API として公開される入力の signature と model_fn で構築するモデルが要求する Tensor の間に前処理を挟む余地ができるわけです。\n", | |
"\n", | |
"今回は画像を入力とするサンプルなので、API の入力にバイナリの画像ファイルの内容自体を渡して、画像の decode と resize(28x28 のサイズに揃える)、トレーニング時にはあらかじめデータセットを調整していた 0.0-1.0 の範囲への scaling もここで行います。ちょっとした Tips として、tf.image モジュールの operation たちはなぜかその他の行列操作系のものと違って batch 化された Tensor を想定していなくて、画像 1つずつ処理するようになっているので、tf.map_fn を使って mini batch の画像を一旦ばらして処理するようにしています。\n", | |
"\n", | |
"model_fn のところで説明を省略した \"key\" という名前の入力についてですが、これはトレーニング時には存在せず、予測用にexportする時にだけ作る placeholder です。そしてこれはそのままなにも変更せずに出力時にも同じ \"key\" という名前で出力しています。この \"key\" という名前には意味はないので好きなものに変更してもいいです。なんでこんなことをしているかというと、モデルの予測に複数の入力を渡した場合、結果として得られた出力も複数のオブジェクトを含むので、それぞれどれがどの入力に対応したものなのかがわからなくなるケースがあります。TF-Serving で REST API から使う時などは今のところ入力と出力の順番は保存されるようなのですが、同じ SavedModel を使ってたとえば Apache Beam や Cloud Dataflow を使ってバッチ処理で予測して結果をファイルに出力すると、分散処理された結果ファイルが複数に分割されることがあります。こういう場合だとどの結果がどの入力に対応したものかわからなくなってしまうので、このように目印として後から入力と出力を対応付けるためのIDみたいなものを入れておくのが定石です。常に1件ずつしか処理しない場合など用途によっては余計なのでそういうときは消してもいいです。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "QUmeEo6U_JfH", | |
"colab_type": "code", | |
"colab": {} | |
}, | |
"cell_type": "code", | |
"source": [ | |
"def serving_input_receiver_fn():\n", | |
" receiver_tensors = {}\n", | |
" tensors = {}\n", | |
"\n", | |
" receiver_tensors[\"key\"] = key = tf.placeholder(tf.string, shape=(None,), name=\"key\")\n", | |
" tensors[\"key\"] = tf.identity(key)\n", | |
"\n", | |
" receiver_tensors[\"images\"] = tf.placeholder(tf.string, shape=(None,), name=\"input_images\")\n", | |
" \n", | |
" def preproc_image(binary):\n", | |
" img = tf.image.decode_image(binary, channels=1)\n", | |
" img = tf.reshape(tf.image.resize_bilinear(img, [28, 28]), (28, 28))\n", | |
" return tf.cast(img, tf.float32) / 255.0\n", | |
"\n", | |
" images = tf.map_fn(preproc_image, receiver_tensors[\"images\"], dtype=tf.float32, back_prop=False)\n", | |
" tensors[\"images\"] = images\n", | |
"\n", | |
" return tf.estimator.export.ServingInputReceiver(tensors, receiver_tensors)" | |
], | |
"execution_count": 0, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"id": "54EiLtF3M83Y", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"## export\n", | |
"\n", | |
"いよいよ SavedModel 形式で保存です。serving_input_receiver_fnを指定します。\n", | |
"保存時にも model_fn が呼ばれて、checkpoints ファイルから変数がロードされます。ここにもミソというか Estimator の方針がみえかくれしますが、Estimator はモデルの変数などをメモリ上に保持するわけではなくて、Graph の構築方法を関数として、変数の値は checkpoints としてファイル上に保存しています。なんだかまだるっこしい方法ですが、たぶんそもそもメモリに乗りきらないほど巨大なモデルを扱うことも想定してこういう設計になってるんじゃないかなぁと思います。Estimator はいろいろなユースケースを考慮したノウハウの集大成として作られてきていたんですよねぇ。若干脱線しますが 2.0 で tf.keras こうしたエッジケースの考慮がちゃんとカバーされるのかというあたり気になります。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "caV73t_TdvNP", | |
"colab_type": "code", | |
"outputId": "b28ceac9-e9a7-4dfc-ee7d-0d0effd42fa1", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 292 | |
} | |
}, | |
"cell_type": "code", | |
"source": [ | |
"estimator.export_savedmodel(\"./saved_model\", serving_input_receiver_fn)" | |
], | |
"execution_count": 8, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"INFO:tensorflow:Calling model_fn.\n", | |
"INFO:tensorflow:Done calling model_fn.\n", | |
"INFO:tensorflow:Signatures INCLUDED in export for Classify: None\n", | |
"INFO:tensorflow:Signatures INCLUDED in export for Regress: None\n", | |
"INFO:tensorflow:Signatures INCLUDED in export for Predict: ['serving_default']\n", | |
"INFO:tensorflow:Signatures INCLUDED in export for Train: None\n", | |
"INFO:tensorflow:Signatures INCLUDED in export for Eval: None\n", | |
"INFO:tensorflow:Restoring parameters from ./model/model.ckpt-1000\n", | |
"WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/estimator/estimator.py:1044: calling SavedModelBuilder.add_meta_graph_and_variables (from tensorflow.python.saved_model.builder_impl) with legacy_init_op is deprecated and will be removed in a future version.\n", | |
"Instructions for updating:\n", | |
"Pass your op to the equivalent parameter main_op instead.\n", | |
"INFO:tensorflow:Assets added to graph.\n", | |
"INFO:tensorflow:No assets to write.\n", | |
"INFO:tensorflow:SavedModel written to: ./saved_model/temp-b'1547336161'/saved_model.pb\n" | |
], | |
"name": "stdout" | |
}, | |
{ | |
"output_type": "execute_result", | |
"data": { | |
"text/plain": [ | |
"b'./saved_model/1547336161'" | |
] | |
}, | |
"metadata": { | |
"tags": [] | |
}, | |
"execution_count": 8 | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "_7_ntla9ZJC1", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"結果は指定したディレクトリの下に、現在時刻を UNIX Time (1970-01-01からの秒数)であらわした数字のディレクトリに出ます。これ上書きを防ぐためなんでしょうけど、ちょっと不便なんですよねぇ……。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "CBfCF_T3xrDa", | |
"colab_type": "code", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 119 | |
}, | |
"outputId": "a69fa4ab-4474-4c8f-b087-b42b1c735f72" | |
}, | |
"cell_type": "code", | |
"source": [ | |
"! find saved_model" | |
], | |
"execution_count": 9, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"saved_model\n", | |
"saved_model/1547336161\n", | |
"saved_model/1547336161/saved_model.pb\n", | |
"saved_model/1547336161/variables\n", | |
"saved_model/1547336161/variables/variables.data-00000-of-00001\n", | |
"saved_model/1547336161/variables/variables.index\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "SeaXjdBUZaK5", | |
"colab_type": "text" | |
}, | |
"cell_type": "markdown", | |
"source": [ | |
"saved_model_cli というコマンドで SavedModel 形式のモデルの入出力を確認できます。ちょっとした予測ならこのコマンドからでもできます。\n", | |
"入力の `images` の型が string になっている(画像ファイルをそのまま渡せる)こと、`key` という入力が追加されているのが確認できます。" | |
] | |
}, | |
{ | |
"metadata": { | |
"id": "VyzqDh2ZSRsZ", | |
"colab_type": "code", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 476 | |
}, | |
"outputId": "0dca12d7-ccbe-4b5f-c954-9bd03f3fffb3" | |
}, | |
"cell_type": "code", | |
"source": [ | |
"! saved_model_cli show --all --dir saved_model/*" | |
], | |
"execution_count": 10, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"\n", | |
"MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:\n", | |
"\n", | |
"signature_def['serving_default']:\n", | |
" The given SavedModel SignatureDef contains the following input(s):\n", | |
" inputs['images'] tensor_info:\n", | |
" dtype: DT_STRING\n", | |
" shape: (-1)\n", | |
" name: input_images:0\n", | |
" inputs['key'] tensor_info:\n", | |
" dtype: DT_STRING\n", | |
" shape: (-1)\n", | |
" name: key:0\n", | |
" The given SavedModel SignatureDef contains the following output(s):\n", | |
" outputs['class'] tensor_info:\n", | |
" dtype: DT_STRING\n", | |
" shape: (-1)\n", | |
" name: index_to_string_Lookup:0\n", | |
" outputs['key'] tensor_info:\n", | |
" dtype: DT_STRING\n", | |
" shape: (-1)\n", | |
" name: Identity:0\n", | |
" outputs['score'] tensor_info:\n", | |
" dtype: DT_FLOAT\n", | |
" shape: (-1, 10)\n", | |
" name: output/softmax:0\n", | |
" Method name is: tensorflow/serving/predict\n" | |
], | |
"name": "stdout" | |
} | |
] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment