2018.10.01

TensorFlow ServingでTensorFlowの学習済みモデルをDeployしてみた


こんにちは。次世代システム研究室のT.D.Qです。
今日は、TensorFlowの学習済みモデルを、サーバーで動かすためのTensorFlow Servingを紹介したいと思います。

TensorFlow Servingとは

TensorFlowの学習済みモデルを本番環境で運用することを目的に設計されたモジュールです。一言で言うと、Tensorflowのservableなオブジェクト読み込んで、gRpcでリクエストを受け付けて、実行してくれるとても速いC++で書かれたサーバーです。基本的にはDockerでサーバ構築することが推奨されています。Servingはいくつかの概念がありますが、詳しくは公式ドキュメントのArchitecture overviewをご参照ください。


TensorFlow Servingの導入流れ


TensorFlow Servingを使った機械学習モデルの配信は以下のような流れになります。
①学習済みモデルをエクスポートしてモデルサーバーを起動する
②クライアントからREST APIまたはgRPCでモデルサーバーにリクエストを送る
③モデルのバージョンを追加してモデルサーバーに反映する


開発環境構築


ここでは、MNISTの学習済みモデルを本番環境で利用する場合を例に、TensorFlow Servingの利用方法を説明したいと思います。
DockerでTensorFlowの開発環境を構築するときに一番速いので、この方法を使います。
まず、Dockerをインストールしますが、インストール手順についてはこちらのページが丁寧に説明しているのでご参照ください。
Dockerをインストールした後、下記のコマンドで実行することでGoogleが提供するTensorFlowのDocker Imageを適用し、開発環境(Python3, TensorFlow, TensorBoard, SkLearn, Keras, Jupyter Notebook, Pandas, Scipyなど)を一瞬で構築できます。
docker run -it --rm --name tf -v `pwd`:/notebooks -p 8888:8888 -p 6006:6006 tensorflow/tensorflow:latest-py3

デバッグするため、TensorBoardを起動し、「http://127.0.0.1:6006/」にアクセスするとTensorBoardが表示されるはずです。
docker exec -it tf tensorboard --logdir tf_logs/ 

TensorFlowモデルを訓練してエクスポートする

分かりやすくするため、今回は「MNIST Softmax Regression TensorFlow model」を使います。TensorFlow Servingを対応するため、このモデルに下記のように設計されます。
①トレーニングの時にパラメータ(epochやversionなど)を指定可能

  # Train model
  print('Training model...')
  mnist = mnist_input_data.read_data_sets(FLAGS.work_dir, one_hot=True)
  sess = tf.InteractiveSession()
  serialized_tf_example = tf.placeholder(tf.string, name='tf_example')
  feature_configs = {'x': tf.FixedLenFeature(shape=[784], dtype=tf.float32),}
  tf_example = tf.parse_example(serialized_tf_example, feature_configs)
  x = tf.identity(tf_example['x'], name='x')  # use tf.identity() to assign name
  y_ = tf.placeholder('float', shape=[None, 10])
  w = tf.Variable(tf.zeros([784, 10]))
  b = tf.Variable(tf.zeros([10]))
  sess.run(tf.global_variables_initializer())
  y = tf.nn.softmax(tf.matmul(x, w) + b, name='y')
  cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
  train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
  values, indices = tf.nn.top_k(y, 10)
  table = tf.contrib.lookup.index_to_string_table_from_tensor(
      tf.constant([str(i) for i in range(10)]))
  prediction_classes = table.lookup(tf.to_int64(indices))
  for _ in range(FLAGS.training_iteration):
    batch = mnist.train.next_batch(50)
    train_step.run(feed_dict={x: batch[0], y_: batch[1]})
  correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
  accuracy = tf.reduce_mean(tf.cast(correct_prediction, 'float'))
  print('training accuracy %g' % sess.run(
      accuracy, feed_dict={
          x: mnist.test.images,
          y_: mnist.test.labels
      }))
  print('Done training!')

②トレーニング後、特定の場所に学習済みモデルをエクスポートします

# Export model
  # WARNING(break-tutorial-inline-code): The following code snippet is
  # in-lined in tutorials, please update tutorial documents accordingly
  # whenever code changes.
  export_path_base = sys.argv[-1]
  export_path = os.path.join(
      tf.compat.as_bytes(export_path_base),
      tf.compat.as_bytes(str(FLAGS.model_version)))
  print('Exporting trained model to', export_path)
  builder = tf.saved_model.builder.SavedModelBuilder(export_path)

早速、モデルをトレーニングします。
mnist_saved_model.py [--training_iteration=x] [--model_version=y] export_dir

root@31db816c13c7:/notebooks# python mnist_saved_model.py --training_iteration=2000 --model_version=1  models/mnist
Training model...
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting /tmp/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting /tmp/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting /tmp/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting /tmp/t10k-labels-idx1-ubyte.gz
2018-09-30 13:31:40.178536: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2018-09-30 13:31:50.335174: W tensorflow/core/framework/allocator.cc:113] Allocation of 31360000 exceeds 10% of system memory.
training accuracy 0.9147
Done training!
Exporting trained model to b'models/mnist/1'
Done exporting!

トレーニングしたモデルを確認します。
root@31db816c13c7:/notebooks# ls -al models/mnist
total 0
drwxr-xr-x 4 root root 128 Sep 30 13:31 .
drwxr-xr-x 3 root root  96 Sep 30 11:56 ..
drwxr-xr-x 4 root root 128 Sep 30 11:56 1

root@31db816c13c7:/notebooks# ll models/mnist/1         
total 20
drwxr-xr-x 4 502 dialout   128 Sep 30 11:56 ./
drwxr-xr-x 4 502 dialout   128 Sep 30 13:31 ../
-rw-r--r-- 1 502 dialout 17946 Sep 30 11:56 saved_model.pb
drwxr-xr-x 4 502 dialout   128 Sep 30 11:56 variables/

TensorFlow Servingを起動する

まず、Servingサーバをインストールするため、Tensorflow Serving RepoからソースコードをCloneします。

git clone https://github.com/tensorflow/serving
cd serving/tensorflow_serving/tools/docker
docker build --pull -t test-tensorflow-serving .

Servingサーバ (CPUモードでもGPUでも) 下記の設定があります
・ gRPCが8500ポートを使います
・ REST APIが8501ポートを使います
・ MODEL_NAME変数名が任意設定 (デフォルトモデル名(model)を指定)
・ MODEL_BASE_PATH変数名が任意設定 (デフォルトが/models)

サーバインストール完了したあと、開発PCからトレーニングしたモデルをServingサーバーに展開します。
YINN0529:tensorflow usr0101836$ docker cp models/mnist 35ffbacf7cc5:/home/models/
YINN0529:tensorflow usr0101836$ docker exec -it 35ffbacf7cc5 /bin/bash
root@35ffbacf7cc5:/# ll /home/models/mnist/1/          
total 20
drwxr-xr-x 4 502 dialout   128 Sep 30 11:56 ./
drwxr-xr-x 4 502 dialout   128 Sep 30 13:31 ../
-rw-r--r-- 1 502 dialout 17946 Sep 30 11:56 saved_model.pb
drwxr-xr-x 4 502 dialout   128 Sep 30 11:56 variables/

下記のコマンドでServingサーバを起動します。

YINN0529:serving usr0101836$ docker run -p 8500:8500 \
>   --mount type=bind,source=$(pwd)/models/monitored,target=/models/mnist \
>   -t --entrypoint=tensorflow_model_server tensorflow/serving  --enable_batching \
>   --port=8500 --model_name=mnist --model_base_path=/models/mnist &
[1] 94804
YINN0529:serving usr0101836$ 2018-09-30 10:08:14.564773: I tensorflow_serving/model_servers/main.cc:157] Building single TensorFlow model file config:  model_name: mnist model_base_path: /models/mnist
2018-09-30 10:08:14.584282: I tensorflow_serving/model_servers/server_core.cc:462] Adding/updating models.
2018-09-30 10:08:14.584349: I tensorflow_serving/model_servers/server_core.cc:517]  (Re-)adding model: mnist
2018-09-30 10:08:14.695635: I tensorflow_serving/core/basic_manager.cc:739] Successfully reserved resources to load servable {name: mnist version: 1}
2018-09-30 10:08:14.696002: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: mnist version: 1}
2018-09-30 10:08:14.696758: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: mnist version: 1}
2018-09-30 10:08:14.697839: I external/org_tensorflow/tensorflow/contrib/session_bundle/bundle_shim.cc:360] Attempting to load native SavedModelBundle in bundle-shim from: /models/mnist/1
2018-09-30 10:08:14.698114: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:31] Reading SavedModel from: /models/mnist/1
2018-09-30 10:08:14.719260: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:54] Reading meta graph with tags { serve }
2018-09-30 10:08:14.722398: I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2018-09-30 10:08:14.738990: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:113] Restoring SavedModel bundle.
2018-09-30 10:08:14.820425: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:90] Running MainOp on SavedModel bundle.
2018-09-30 10:08:14.862450: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:233] SavedModel load for tags { serve }; Status: success. Took 164460 microseconds.
2018-09-30 10:08:14.862534: I tensorflow_serving/servables/tensorflow/saved_model_bundle_factory.cc:100] Wrapping session to perform batch processing
2018-09-30 10:08:14.862560: I tensorflow_serving/servables/tensorflow/bundle_factory_util.cc:153] Wrapping session to perform batch processing
2018-09-30 10:08:14.863618: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:83] No warmup data file found at /models/mnist/1/assets.extra/tf_serving_warmup_requests
2018-09-30 10:08:14.874383: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: mnist version: 1}
2018-09-30 10:08:14.892834: I tensorflow_serving/model_servers/main.cc:327] Running ModelServer at 0.0.0.0:8500 ...

Dockerコンタイナーのプロセスを確認します。
YINN0529:example usr0101836$ docker ps
CONTAINER ID        IMAGE                              COMMAND                  CREATED             STATUS              PORTS                                            NAMES
35ffbacf7cc5        test-tensorflow-serving            "/bin/bash"              3 hours ago         Up 3 hours          0.0.0.0:8500-8501->8500-8501/tcp                 determined_bassi
31db816c13c7        tensorflow/tensorflow:latest-py3   "/run_jupyter.sh --a…"   3 hours ago         Up 3 hours          0.0.0.0:6006->6006/tcp, 0.0.0.0:8888->8888/tcp   tf
YINN0529:example usr0101836$ 

Servingサーバにログインして、サービスを起動します。
root@35ffbacf7cc5:/# cat /home/configs/models.conf
model_config_list: {
  config: {
    name:  "mnist",
    base_path:  "/home/models/mnist",
    model_platform: "tensorflow",
    model_version_policy: {
      all: {}
    }
  }
}

root@35ffbacf7cc5:/# tensorflow_model_server --port=8500 --rest_api_port=8501 --model_config_file=/home/configs/models.conf

ここでServingサーバが問題なく起動できたら、システム構築がほぼ完了となります。

動作確認

動作確認端末で行うため、まず下記のモジュールをインストールします

YINN0529:tensorflow_serving usr0101836$ pip install tensorflow-serving-api

モデルを確認するため、Googleが提供するサンプルクラス(mnist_client)を使います。このスクリプトはgRPCを使ってPredict RequestをServingサーバに送ります。

YINN0529:example usr0101836$ python mnist_client.py --num_tests=100 --server=127.0.0.1:8500
/anaconda3/lib/python3.6/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  from ._conv import register_converters as _register_converters
Extracting /tmp/train-images-idx3-ubyte.gz
Extracting /tmp/train-labels-idx1-ubyte.gz
Extracting /tmp/t10k-images-idx3-ubyte.gz
Extracting /tmp/t10k-labels-idx1-ubyte.gz
....................................................................................................
Inference error rate: 7.000000000000001%
Softmaxモデルの精度が90%前後を期待しているため、推論のエラーレートが7%ということで、トレーニング済みなモデルをServingで正常にロードして実行したことを確認できました。

Servingサーバに他の学習済みモデルをDeployして確認する

別で用意されるIris Classifyモデルをトレーニングした後、Servingサーバの/home/modelsフォルダーにアップロードします。今回は、バージョン1がClassificationモデルで、バージョン2がRegressionモデルとしてDeployします。

root@35ffbacf7cc5:/# ll /home/models/iris/
total 0
drwxr-xr-x 4 root root 128 Sep 30 12:30 ./
drwxr-xr-x 5 root root 160 Sep 30 13:35 ../
drwxr-xr-x 4 root root 128 Sep 30 12:30 1/
drwxr-xr-x 4 root root 128 Sep 30 12:30 2/

root@35ffbacf7cc5:/# cat /home/configs/models.conf 
model_config_list: {
  config: {
    name:  "mnist",
    base_path:  "/home/models/mnist",
    model_platform: "tensorflow",
    model_version_policy: {
      all: {}
    }
  },
  config: {
    name:  "iris",
    base_path:  "/home/models/iris",
    model_platform: "tensorflow",
    model_version_policy: {
        all: {}
    }
  }
}

REST APIで動作確認します

YINN0529:example usr0101836$ curl -X POST \
>   http://localhost:8501/v1/models/iris:classify \
>   -H 'cache-control: no-cache' \
>   -H 'postman-token: f7fb6e3f-26ba-a742-4ab3-03c953cefaf5' \
>   -d '{
>  "examples":[
>    {"x": [5.1, 3.5, 1.4, 0.2]}
>   ]
> }'
{
    "results": [[["Iris-setosa", 0.872397], ["Iris-versicolor", 0.108623], ["Iris-virginica", 0.0189799]]
    ]
}

YINN0529:example usr0101836$ curl -X POST \
>   http://localhost:8501/v1/models/iris/versions/2:predict \
>   -d '{
>  "signature_name": "predict-iris",
>  "instances":[
>   [5.1, 3.5, 1.4, 0.2]
>  ]
> }'
{
    "predictions": [[0.872397, 0.108623, 0.0189799]
    ]
}
Iris系のモデルも正常にロードされて実行されましたね。

まとめ

今回は、Dockerコンテナを利用して、TensorFlow Servingが動作する環境を作成する手順を説明しました。
また、Pythonで書かれたクライアントプログラムから、TensorFlow Servingに登録した学習済みモデルにMNISTデータを推測させる方法も確認できました。
今回説明した内容は、基本的な作業になりますので、次は自分でCI/CDツールを使って学習済みモデルを自動的にDeployしたり、KubernetesでServingサーバを本番環境に展開してみると良いでしょう。

それでは、また。


次世代システム研究室では、アプリケーション開発や設計を行うリードエンジニアを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。