KerasモデルをCloud ML Engineで学習してOnline Predictionしてみた

Cloud ML Engine のruntime versionが1.2になったので、Kerasが小細工なしで使えるようになりました。TensorFlowの高レベルAPIもいい感じになって来ていますが、やはりKerasのpretrained modelの多さは魅力的です。とりあえずやり方だけ把握しておこうと、せっかくなので学習だけでなくOnline PredictionもKerasモデルでserveしてみました。

**Cloud ML Engineとは**
[TensorFlowのフルマネージドな実行環境](https://cloud.google.com/ml-engine/)です。分散環境で学習、オートスケールしAPIで推論リクエスト可能なOnline Prediction等、TensorFlowの運用には最高の環境です。

# KerasをCloud ML Engine(training)で使う
注意するのは、

- Kerasのimportをtf.contribからする
- jobのsubmit時にruntimeVersionを1.2に設定する

位で、後は普通に動きます。こんな感じで書けばOK ([こちら](http://qiita.com/rindai87/items/50e48ce8569badc38542)を参考にしました）

```py
import tensorflow as tf
from tensorflow.contrib.keras.python import keras
from tensorflow.contrib.keras.python.keras.models import Sequential
from tensorflow.contrib.keras.python.keras.layers.core import Dense, Activation
from sklearn.cross_validation import train_test_split

iris = tf.contrib.learn.datasets.base.load_iris()
train_x, test_x, train_y, test_y = train_test_split(
    iris.data, iris.target, test_size=0.2)

num_classes = 3
train_y = keras.utils.to_categorical(train_y, num_classes)
test_y = keras.utils.to_categorical(test_y, num_classes)

model = Sequential()
model.add(Dense(10, activation='relu', input_shape=(4,)))
model.add(Dense(20, activation='relu'))
model.add(Dense(10, activation='relu'))
model.add(Dense(3, activation='softmax'))

model.compile(loss='categorical_crossentropy',
              optimizer='sgd',
              metrics=['accuracy'])

cb = keras.callbacks.TensorBoard(
    log_dir="gs://BUCKET/keras-mlengine", histogram_freq=1)
# train
model.fit(train_x, train_y,
          batch_size=100,
          epochs=20,
          verbose=2,
          callbacks=[cb],
          validation_data=(test_x, test_y))

# eval
score = model.evaluate(test_x, test_y, verbose=0)
pred = model.predict(test_x)
```

TensorBoardの出力先はGCSにしておけば後からローカルでもCloud Shellからでも参照できます。
**Datalab、Jupyter NotebookからJobを投げたいとき**
[cloudml-magic](https://github.com/hayatoy/cloudml-magic)をインストールしてください。runtime version 1.2にも対応しておきました。Kerasの利用例は[こちら](https://github.com/hayatoy/cloudml-magic/blob/master/examples/Keras%20Online%20Prediction%20Example.ipynb)。

ログはこんな感じに出力されます。
<img width="732" alt="Untitled.png" src="https://qiita-image-store.s3.amazonaws.com/0/162369/2f786089-6b89-f614-f221-827f252d1ac4.png">



# SavedModelを作る
Online Predictionはv1から`SavedModel`形式しか対応していないので、Kerasモデルから`SavedModel`に変換する必要があります。Sessionを取り出してSignature追加すればいいだけですが、ちょっと嵌りました。

```py
from tensorflow.contrib.keras import backend
sess = backend.get_session()
x = sess.graph.get_tensor_by_name('dense_1_input:0')
y = sess.graph.get_tensor_by_name('ArgMax_1:0')
inputs = {"dense_1_input": tf.saved_model.utils.build_tensor_info(x)}
outputs = {"ArgMax_1": tf.saved_model.utils.build_tensor_info(y)}
signature = tf.saved_model.signature_def_utils.build_signature_def(
    inputs=inputs,
    outputs=outputs,
    method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME
)

# save as SavedModel
b = tf.saved_model.builder.SavedModelBuilder('gs://BUCKET/keras-mlengine/savedmodel')
b.add_meta_graph_and_variables(sess,
                               [tf.saved_model.tag_constants.SERVING],
                               signature_def_map={'serving_default': signature})
b.save()
```
`SavedModel`の保存先をGCSに指定すれば、Online Predictionでもそのまま使えます。[Console](https://console.cloud.google.com/mlengine/models)から作成したSavedModelのGCSパスを指定すればOnline Prediction環境の出来上がり。

ちなみに入力と出力のTensorを取得するのに、Keras側のGraph生成ルールがわからず、とりあえずTensorBoardで確認して`get_tensor_by_name`でTensorを引っ張ってきています。~~`Sequential`に`placeholder`を突っ込むのが正しいやり方でしょうが、そうするとそこだけローレベルになってしまうので~~ 
**追記**
`model.inputs`、`model.outputs`で入出力Tensorが取得できるようです。
しかしそれでも面倒なので`Estimator`の`export_savedmodel`みたいなのがKerasにも欲しいですね。

# Online Prediction で推論する
ここは従来通りです。Discovery APIを使う場合はこんな感じ。

```py
from oauth2client.client import GoogleCredentials
from googleapiclient import discovery
from googleapiclient import errors

PROJECTID = 'PROJECTID'
projectID = 'projects/{}'.format(PROJECTID)
modelName = 'keras-iris'
modelID = '{}/models/{}'.format(projectID, modelName)

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

request_body = {'instances': [{'dense_1_input': [5.4,  3.9,  1.3,  0.4]},
                              {'dense_1_input': [4.4,  3.2,  1.3,  0.2]},
                              {'dense_1_input': [4.3,  3.,  1.1,  0.1]},
                              {'dense_1_input': [5.,  3.5,  1.6,  0.6]},
                              {'dense_1_input': [5.9,  3.,  4.2,  1.5]},
                              {'dense_1_input': [7.7,  3.,  6.1,  2.3]},
                              ]}

request = ml.projects().predict(name=modelID, body=request_body)
try:
    response = request.execute()
except errors.HttpError as err:
    # Something went wrong with the HTTP transaction.
    # To use logging, you need to 'import logging'.
    print('There was an HTTP error during the request:')
    print(err._get_reason())
print(response)
```

するとこんな感じで帰ってきます。

```
{u'predictions': [{u'ArgMax_1': 0},
  {u'ArgMax_1': 0},
  {u'ArgMax_1': 0},
  {u'ArgMax_1': 0},
  {u'ArgMax_1': 1},
  {u'ArgMax_1': 2}]}
```

# 終わりに
GCPすごい、Cloud ML Engineすごい！・・・のですが、あまり情報がないのが寂しいところ。慣れちゃえばとっても簡単なので、皆さんどんどん使って色々事例を出してくださいね！