TensorFlow ServingをUbuntuにインストールして推論実行

TensorFlow Servingを理解するために訓練済モデル作成から推論リクエストまでを整理してみました。
いつも適当にTensorFlow Serving系のサービスに推論リクエストを送っていたので、理解が浅いままで気持ち悪かったので少し頑張りました。

#環境
|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|TensorFlow Serving<br>(tensorflow-model-server)|1.13.0|tensorflow-model-server-universalというのもあるそうですが、無難な方にしておきます|
|Pyenv|1.2.11|Pyenvインストールは別記事[「UbuntuにpyenvとvenvでPython開発環境構築」](https://qiita.com/FukuharaYohei/items/2820f7bda6678ce26ea7)参照|
|Python|Python3.6.8|Pyenv上でPythonバージョン管理。ライブラリはvenvで管理しています。|

## 1. 訓練済モデル作成
### 1.1. venv仮想環境作成
まっさらなvenv仮想環境を"py36"という名前で作成し、有効化とpipの最新化。詳しくは、別記事[「UbuntuにpyenvとvenvでPython開発環境構築」](https://qiita.com/FukuharaYohei/items/2820f7bda6678ce26ea7)を参照ください。

```bash
# From venv directory
python -m venv py36
source py36/bin/activate
pip install --upgrade pip
```

## 1.2. 訓練済モデル作成
訓練モデル作成をします。ローカル環境でPythonプログラムを実行してSavedModelを作るのですが、[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)で直接実行して、SavedModelをダウンロードしてもOKです。別記事[「Google Colaboratory概要と使用手順(TensorFlowもGPUも使える)」](https://qiita.com/FukuharaYohei/items/18bc4465923bdda81826)に概要を書いています。
[公式TensorFlow-Servingチュートリアル](https://www.tensorflow.org/tfx/serving/serving_basic)は[mnist](https://github.com/tensorflow/serving/tree/master/tensorflow_serving/example/mnist_saved_model.py)を使うのですが、より単純なモデルの方がわかりやすいと考え、あえてオリジナルプログラムにしています。

###1.2.1. 訓練モデル作成プログラムクローン
[GitHub](https://github.com/YoheiFukuhara/keras-for-beginner)から[簡単なSavedModel作成プログラム](https://github.com/YoheiFukuhara/keras-for-beginner/blob/master/Keras03_Serving.ipynb)をダウンロードします。
プログラム内容については以下の記事を参照ください。
- [【Keras入門(1)】単純なディープラーニングモデル定義](https://qiita.com/FukuharaYohei/items/579cc0184885b4ba75df)
- [【Keras入門(2)】訓練モデル保存(KerasモデルとSavedModel)](https://qiita.com/FukuharaYohei/items/ac6333391b8abda94bdc)

```bash
git clone https://github.com/YoheiFukuhara/keras-for-beginner.git
```

### 1.2.2. パッケージインストールとJupyter起動
訓練用に使うパッケージをインストールして、Jupyter Labを起動します。

```bash
pip install tensorflow numpy jupyterlab
jupyter lab
```

### 1.2.3. SavedModel保存
[Keras03_Serving.ipynb](https://github.com/YoheiFukuhara/keras-for-beginner/blob/master/Keras03_Serving.ipynb)を実行します。
あとはセルを実行していくだけなのですが、以下のセルでSavedModelを保存しています。

```python
tf.contrib.saved_model.save_keras_model(model, './models/keras_export')
```

「./models/keras_export」以下にフォルダがあります、保存されています。


途中、こんなセルがある部分では、「./models/keras_export」以下のフォルダ名(以下の例では「1558705852」)を変更して実行してください。自動的に命名され生成されるフォルダです。これで、SavedModelの内容が参照できます。

```bash
# フォルダ名が動的に生成されるので、都度変更が必要
!saved_model_cli show --all --dir ./models/keras_export/1558705852

MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['__saved_model_init_op']:
  The given SavedModel SignatureDef contains the following input(s):
  The given SavedModel SignatureDef contains the following output(s):
    outputs['__saved_model_init_op'] tensor_info:
        dtype: DT_INVALID
        shape: unknown_rank
        name: init_1
  Method name is: 

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['dense_input'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 2)
        name: dense_input:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['dense_1'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: dense_1/Sigmoid:0
  Method name is: tensorflow/serving/predict
```

作ったSavedModelファイル(./models/keras_export/<自動生成フォルダ>以下のファイル郡)を適当なフォルダに放り込んでおきます。私はtmpフォルダにmodel->1という階層で置いています。ここの1はモデルバージョン管理用なので1から連番の数字にします(次に訓練済モデルを更新する場合は「2」)。

```
tmp
└── model
    └── 1
        ├── assets
        │   └── saved_model.json
        ├── saved_model.pb
        └── variables
            ├── checkpoint
            ├── variables.data-00000-of-00001
            └── variables.index
```

## 2. モデルサーバー(Model Server)構築
[公式サイトの手順](https://www.tensorflow.org/tfx/serving/setup#installing_using_apt)に従ってインストールします。
今回はDockerを使わずにAPTでインストールします。Dockerを使わない理由は、単純にDocker環境に新たに色々とインストールするのが面倒だったからです。

## 2.1. パッケージソースの追加
TensorFlow Serving配布URIをパッケージソースに追加します。

```bash
echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list && \
curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
```

## 2.2. モデルサーバーインストール
APTを使ってモデルサーバー(Model Server)のインストール

```bash
sudo apt-get update && sudo apt-get install tensorflow-model-server
```

## 2.3. モデルサーバー起動
訓練済モデルを置いてモデルサーバー起動。[基本的にDockerで動かすことが推奨らしく](https://github.com/tensorflow/serving#documentation)、あまり直接起動する解説がなかったので、[公式のRunning a serving image](https://www.tensorflow.org/tfx/serving/docker#running_a_serving_image)あたりを見ながらパラメータを指定。

```bash
tensorflow_model_server --port=9000 --model_name=test --model_base_path=/tmp/model
```

# 3. 推論実行
## 3.1. 推論Python環境作成
仮想環境は共用としています。別でも問題ないですが、たくさん仮想環境を作るのが面倒だったためです。必要なパッケージをインストールします(共用としない場合は、追加でtensorflowもインストールしてください)。

```bash
source <venv>/py36/bin/activate
pip install tensorflow-serving-api request grpcio
```

私が実行した時点のバージョンは以下の通りです。

|Package|Version|
|:-:|:-:|
|tensorflow-serving-api|1.13.0|
|request|2019.4.13|
|grpcio|1.20.1|


## 3.2. 推論プログラム実行
「1.2.1. 訓練モデル作成プログラムクローン」でクローンした中の、[Keras04_Serving_client.ipynb](https://github.com/YoheiFukuhara/keras-for-beginner/blob/master/Keras04_Serving_clinet.ipynb)を実行

### 3.2.1. パッケージインポート
使うパッケージをインポート。

```python
import grpc
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc
```

### 3.2.2. 推論データ配列を作成

```python
data = [ 0.123, 0.567]
```

### 3.2.3. リクエストデータの作成
リクエストデータを作成します。request.model_spec.signature_nameとrequest.inputsには「1.2.3. SavedModel保存」のsaved_model_cliで確認した名称を指定しています。

```python
request = predict_pb2.PredictRequest()
request.model_spec.name = 'test'  #モデル名
request.model_spec.signature_name = 'serving_default'  #signature
request.inputs['dense_input'].CopyFrom(
    tf.make_tensor_proto(data, shape=[1,2]))   ##inputs
```

### 3.2.4. 推論実行
ローカルのポート9000に対して推論リクエストを投げます。

```python
channel = grpc.insecure_channel("localhost:9000")
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
print(stub.Predict(request, 10))   #10秒のtimeout
```

jsonで答えが返ってきました！投げたデータが1以下なのに対して答えが0.287...なので正解です。

```json
outputs {
  key: "dense_1"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 1
      }
    }
    float_val: 0.28794631361961365
  }
}
model_spec {
  name: "test"
  version {
    value: 1
  }
  signature_name: "serving_default"
}
```