Inception v3の転移学習モデルをCloud ML Engineに載せてみた。

Inception v3の転移学習モデルをCloud ML Engineに載せてみたので、その時のメモ。

# 環境

- Python 3.5
- TensorFlow 1.10.0
- Keras 2.2.2
- Cloud ML Engine 1.10

# 転移学習

転移学習については[このサイトの記事](https://employment.en-japan.com/engineerhub/entry/2017/04/28/110000#fine-tuning%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E7%94%BB%E5%83%8F%E8%AA%8D%E8%AD%98
)を参考にしました。

# モデルの変換

転移学習モデルの入力は(299,299,3)のテンソルですが、Cloud ML Engineの入力は画像ファイルのバイナリデータをBase64エンコードした文字列なので、入力層を変更する必要があります。
また、Kerasの学習モデルはhdf5形式ですが、Cloud ML Engineに載せる場合はTensorFlowのSavedModel形式にする必要があります。
下記のコードを実行すると転移学習モデル「model.04-7.89.hdf5」を読み込んで、入力層を変更後、SavedModel形式で「mdl」フォルダに保存します。

```python
import tensorflow as tf
import tensorflow.keras.backend as K
from tensorflow.keras.models import load_model
from tensorflow.python.framework import graph_util

sess = K.get_session()
K.set_learning_phase(0)
model = load_model(filepath='./model.04-7.89.hdf5')

with tf.Graph().as_default() as g_input:
    input_b64 = tf.placeholder(shape=(1,), dtype=tf.string, name='b64')
    input_bytes = tf.decode_base64(input_b64[0])
    image = tf.image.decode_image(input_bytes)
    image_f = tf.image.convert_image_dtype(image, dtype=tf.float32)
    input_image = tf.expand_dims(image_f, 0)
    output = tf.identity(input_image, name='input_image')

# 後でつなげるのでGraphDefにする
g_input_def = g_input.as_graph_def()

g_trans = sess.graph
g_trans_def = graph_util.convert_variables_to_constants(sess,
                                                        g_trans.as_graph_def(),
                                                        [model.output.name.replace(':0', '')])

with tf.Graph().as_default() as g_combined:
    x = tf.placeholder(tf.string, name="b64")

    im, = tf.import_graph_def(g_input_def,
                              input_map={'b64:0': x},
                              return_elements=["input_image:0"])

    pred, = tf.import_graph_def(g_trans_def,
                                input_map={model.input.name: im},
                                return_elements=[model.output.name])

    with tf.Session() as session:
        inputs = {"image_bytes": tf.saved_model.utils.build_tensor_info(x)}
        outputs = {"outputs":tf.saved_model.utils.build_tensor_info(pred)}
        signature =tf.saved_model.signature_def_utils.build_signature_def(
                inputs=inputs,
                outputs=outputs,
                method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME
            )

        # SavedModelとして保存
        b = tf.saved_model.builder.SavedModelBuilder('./mdl')
        b.add_meta_graph_and_variables(session,
                        [tf.saved_model.tag_constants.SERVING],
                        signature_def_map={'serving_default': signature})
        b.save()
```

# Cloud ML Engineへの登録

SavedModel形式で保存した学習モデルをCloud ML Engineに登録するには以下のコマンドを実行します。

```console
> gcloud ml-engine models create {モデル名} --regions asia-northeast1
> gcloud ml-engine versions create v1 --model {モデル名} --origin mdl --runtime-version 1.10 --staging-bucket {モデルを登録するStorageのバケット名}
```

# Cloud ML Engineへのリクエスト

Cloud API アクセススコープを設定したGCE等から以下のコードを実行します。

```python
import base64
from oauth2client.client import GoogleCredentials
from googleapiclient import discovery
from googleapiclient import errors

PROJECTID = 'hoge'
projectID = 'projects/{}'.format(PROJECTID)
modelName = 'fuga'
modelID = '{}/models/{}'.format(projectID, modelName)

credentials = GoogleCredentials.get_application_default()
ml = discovery.build('ml', 'v1', credentials=credentials)

with open('./test.jpg', 'rb') as f:
    b64_x = f.read()

b64_x = base64.urlsafe_b64encode(b64_x)
request_body = {"instances": [{"image_bytes": b64_x.decode() }]}
request = ml.projects().predict(name=modelID, body=request_body)
try:
    response = request.execute()
    print(response)
except errors.HttpError as err:
    print(err._get_reason())
```

上記のコードを実行すると、Cloud ML Engineから以下のような応答が返ってきます。

```json
{"predictions": [{"outputs": ["0.981989292"]}]}
```