Rで学習・作成したモデルをGoogle Cloud ML Engineで使う

# 概要

- cloudmlパッケージはRでGoogle Cloud ML Engineを操作するパッケージ
- ローカル環境で学習したモデルをGoogle Cloud ML Engineで使える形式で保存
- 保存したモデルファイルをcloudmlパッケージでアップロード＆APIの形で呼び出し可能に



# 前書き

　これは[R Advent Calendar 2018](https://qiita.com/advent-calendar/2018/rlang) 18日目の記事になります。

　TokyoR#74のLTにて、[R and Python: How to Integrate the Best of Both into Your Data Science Workflow](https://www.business-science.io/business/2018/10/08/python-and-r.html)についてと、reticulateパッケージを使おうという話をしました（reticulateパッケージに関しては[前回のQiita](https://qiita.com/yamano357/items/9319d4b073d82a261ed8)を参考のこと）。
　Data Science Workflowの記事中にある「Data Science Workflow Integrating R + Python」の図において、真ん中にあるscikit-learnやTensorflowの部分はもしかしてGoogle Cloud ML Engineに置き換えられてしまうのでは思ったので、RからGoogle Cloud ML Engineを操作するcloudmlパッケージを実際に試してみました。

　Google Cloud ML Engineに対応している機械学習フレームワーク毎にローカル環境でモデルを学習し、Google Cloud ML Engineで結果を利用できる形式で出力させ、それらをGoogle Cloud Platform(GCP)にデプロイして呼び出すまでをRで行う方法について書いています。
　Rのcloudmlパッケージを用いて学習からデプロイまでを行えるか検証するのが目的なので、予測精度を高めるためのチューニングはしていません。GCPのリソースを用いたモデル学習がGoogle Cloud ML Engineの別機能としてあるのでそちらを活用するのが良いでしょうから、本記事では扱っていません（cloudmlパッケージでモデルトレーニング可能なことは確認済みです）。

- [CLOUD MACHINE LEARNING ENGINE](https://cloud.google.com/ml-engine/)
- [R Interface to Google CloudML](https://tensorflow.rstudio.com/tools/cloudml/articles/getting_started.html)



# 定義・設定

　モデル学習した結果をGoogle Cloud ML Engineで使える形で保存し、cloudmlパッケージの関数を利用してデプロイするという流れになりますが、この工程で必要となる関数や定数の定義をここでは行います。合わせてモデル学習で使用するMNISTデータセットの準備もします。

```r:ライブラリ読み込み
# devtools::install_github(repo = "rstudio/tfdeploy")
pacman::p_load(
  c(
    "tidyverse", "reticulate", "tensorflow", "keras", "xgboost", 
    "cloudml", "tfdatasets", "tfestimators", "tfdeploy",
    "DiagrammeR"
  ),
  install = FALSE, character.only = TRUE
)

# reticulateパッケージで用いるPythonバージョンに3系を設定
reticulate::use_python(
  python = reticulate:::python_unix_binary(bin = "python3.6")
)
```

```r:定義部
# 定数定義
# モデル学習スクリプトのパスと学習結果の出力先パス
PATH_NAMES <- tibble::lst(
  CREATE_MODEL_DIR = "./createmodel",
  CREATE_MODEL = tibble::lst(
    KERAS = glue::glue("{PATH}/{FILE}", PATH = CREATE_MODEL_DIR, FILE = "keras-mnist.R"),
    XGBOOST = glue::glue("{PATH}/{FILE}", PATH = CREATE_MODEL_DIR, FILE = "xgboost-mnist.R"),
    SKLEARN = glue::glue("{PATH}/{FILE}", PATH = CREATE_MODEL_DIR, FILE = "scikit-mnist.R")
  ),
  SAVED_MODEL_DIR = "./savedmodel",
  SAVED_MODEL = tibble::lst(
    KERAS = glue::glue("{PATH}/{FILE}", PATH = SAVED_MODEL_DIR, FILE = "keras"),
    XGBOOST = glue::glue("{PATH}/{FILE}", PATH = SAVED_MODEL_DIR, FILE = "xgb/model.bst"),
    SKLEARN = glue::glue("{PATH}/{FILE}", PATH = SAVED_MODEL_DIR, FILE = "scikit/model.pkl")
  )
)

# デプロイ関係の定数設定
CLOUDML_DEPLOY <- tibble::lst(
  REGION = "us-central1",
  MODEL_NAME = tibble::lst(
    KERAS = "keras_mnist", XGBOOST = "xgb_mnist", SKLEARN = "scikit_mnist"
  )
)
CLOUDML_CONFIG <- tibble::lst(RUNTIME_VERSION = "1.10")

# デプロイしたモデルでのテストデータサンプリングの添え字
DEPLOY_TEST_DATA_IDX <- seq_len(length.out = 5)


# 関数定義部
# Kerasモデルのグラフ構造の可視化を行うユーティリティ関数をreticulateパッケージでインポート
keras_vis_utils <- reticulate::import(module = "keras")$utils$vis_utils

# Confusion Matrix
calcAccuracy <- function (pred, test) {
  cfm <- table(pred, test)
  return(
    list(cfm = cfm, accuracy = sum(diag(x = cfm)) / sum(cfm))
  )
}

# 「https://mrunadon.github.io/ベイズ多層ニューラルネットとベイズ畳込みニューラルネット/」にあるConfusion Matrixの可視化の書き方を修正した関数
plotAccuracy <- function (cfm, accuracy) {
  tibble::as_data_frame(cfm) %>% 
    dplyr::mutate_if(.predicate = is.character, .funs = as.factor) %>% 
    ggplot2::ggplot(mapping = ggplot2::aes(x = pred, y = test, fill = n)) +
    ggplot2::geom_tile() + ggplot2::geom_text(mapping = ggplot2::aes(label = n)) +
    ggplot2::theme_light() +
    ggplot2::scale_y_discrete(expand = c(0, 0)) + ggplot2::scale_x_discrete(expand = c(0, 0)) +
    ggplot2::scale_fill_gradient(low = "white", high = c(c("#B23AEE"))) +
    ggplot2::labs(
      title = "Confusion Matrix",
      subtitle = glue::glue("Accuracy: {acc}", acc = accuracy),
      x = "Predict", y = "Label"
    )
}

# デプロイしたモデルの操作用にgcloudコマンド実行を関数でラップ
execCloudMLVersions <- function (vname, mname, command) {
  cloudml::gcloud_exec(
    args = list(
      "ml-engine", "versions", command,
      vname, glue::glue("--model={MODEL}", MODEL = mname)
    )
  )
}
```

```r:データセット作成
# KerasパッケージにあるMNISTデータセットを取得する関数を利用
# `%<-%`はzeallotパッケージからインポート
c(c(x_train_raw, y_train_raw), c(x_test_raw, y_test_raw)) %<-% keras::dataset_mnist()

x_train <- reticulate::array_reshape(x = x_train_raw, dim = c(nrow(x = x_train_raw), 784L)) / 255
x_test <- reticulate::array_reshape(x = x_test_raw, dim = c(nrow(x = x_test_raw), 784L)) / 255

y_train <- keras::to_categorical(y = y_train_raw, num_classes = 10L)
y_test <- keras::to_categorical(y = y_test_raw, num_classes = 10L)


# データセット確認
purrr::map(.x = list(x_train_raw, y_train_raw, x_test_raw, y_test_raw), .f = dim)
#> [[1]]
#> [1] 60000    28    28

#> [[2]]
#> [1] 60000

#> [[3]]
#> [1] 10000    28    28

#> [[4]]
#> [1] 10000
```


## 事前準備

　cloudmlパッケージではGCPに対するコマンドラインインターフェースであるgcloudコマンドを使用しており、Google Cloud SDK（gcloudはGoogle Cloud SDKの一部）のインストールと認証が事前に必要になります。gcloudコマンドを実行してGoogle Cloud ML Engineを操作できる状態でしたらこの工程は不要です。
　cloudmlパッケージで提供されている`cloudml::gcloud_install()`でGoogle Cloud SDKのインストールができ、`cloudml::gcloud_init()`でGCPへの認証設定ができます。

```r:事前準備
# 実行すると選択項目が出てくるので、自身の環境に合わせて適宜設定
cloudml::gcloud_install()
cloudml::gcloud_init()
```



# モデル学習と保存

　MNISTデータセットを使用してGoogle Cloud ML Engineに対応しているTensorflow(Keras), XGBoost, scikit-learnでそれぞれ学習して、対応形式でモデルをローカルに出力します。学習の流れはさほど変わりませんが、モデル出力の方法はライブラリが対応しているかで異なります。ここではライブラリ毎に学習とモデル出力の手順を説明していきます。


## Kerasの場合

　Kerasはkerasパッケージを用いて学習を行いますが、PythonのkerasライブラリとTensorflow API(tf.Keras)のどちらの実装を用いるかによってモデル出力方法が変わります。kerasパッケージのデフォルトはkerasライブラリによる実装を用いますが、`keras::use_implementation()`によって変更が可能です。

- [keras - vignettes(Keras Backend)](https://cran.rstudio.com/web/packages/keras/vignettes/backend.html)

```r:keras-mnist.R
library(keras)
library(tensorflow)

# keras:::keras_version()
# tensorflow::tf_config()

keras::use_implementation(implementation = "keras")
keras::use_backend(backend = "tensorflow")

# keras::use_implementation(implementation = "tensorflow")
# keras::use_backend(backend = "tensorflow")


# モデル定義
keras_model <- keras::keras_model_sequential() %>% 
  keras::layer_dense(units = 256L, activation = 'relu', input_shape = c(784L)) %>%
  keras::layer_dropout(rate = 0.3) %>%
  keras::layer_dense(units = 128L, activation = 'relu') %>%
  keras::layer_dropout(rate = 0.3) %>%
  keras::layer_dense(units = 10L, activation = 'softmax')

keras_model %>% keras::compile(
  loss = keras::loss_categorical_crossentropy, optimizer = keras::optimizer_adam(),
  metrics = c("accuracy")
)

# 初期化
sess <- keras::k_get_session()
sess$run(tensorflow::tf$global_variables_initializer())

# 学習
history <- keras_model %>% keras::fit(
  x = x_train, y = y_train,
  epochs = 30, batch_size = 128, validation_split = 0.2,
  verbose = 2
)
```

### モデル学習

```r:モデル学習
# Epoch毎にプロットされる結果も更新
source(file = PATH_NAMES$CREATE_MODEL$KERAS)

#> Train on 48000 samples, validate on 12000 samples
#> Epoch 1/30
#>  - 4s - loss: 0.4152 - acc: 0.8748 - val_loss: 0.1567 - val_acc: 0.9542
#> Epoch 2/30
#>  - 3s - loss: 0.1820 - acc: 0.9455 - val_loss: 0.1128 - val_acc: 0.9664
#> Epoch 3/30
#>  - 3s - loss: 0.1354 - acc: 0.9591 - val_loss: 0.0989 - val_acc: 0.9703
#> Epoch 4/30
#>  - 3s - loss: 0.1093 - acc: 0.9660 - val_loss: 0.0925 - val_acc: 0.9716
#> Epoch 5/30
#>  - 3s - loss: 0.0929 - acc: 0.9712 - val_loss: 0.0870 - val_acc: 0.9747
#> Epoch 6/30
#>  - 3s - loss: 0.0804 - acc: 0.9759 - val_loss: 0.0843 - val_acc: 0.9745
#> Epoch 7/30
#>  - 3s - loss: 0.0741 - acc: 0.9769 - val_loss: 0.0811 - val_acc: 0.9768
#> Epoch 8/30
#>  - 3s - loss: 0.0639 - acc: 0.9798 - val_loss: 0.0775 - val_acc: 0.9786
#> Epoch 9/30
#>  - 3s - loss: 0.0599 - acc: 0.9809 - val_loss: 0.0772 - val_acc: 0.9783
#> Epoch 10/30
#>  - 4s - loss: 0.0521 - acc: 0.9832 - val_loss: 0.0829 - val_acc: 0.9774
#> Epoch 11/30
#>  - 3s - loss: 0.0495 - acc: 0.9845 - val_loss: 0.0808 - val_acc: 0.9781
#> Epoch 12/30
#>  - 4s - loss: 0.0497 - acc: 0.9832 - val_loss: 0.0770 - val_acc: 0.9790
#> Epoch 13/30
#>  - 3s - loss: 0.0419 - acc: 0.9870 - val_loss: 0.0814 - val_acc: 0.9792
#> Epoch 14/30
#>  - 3s - loss: 0.0402 - acc: 0.9871 - val_loss: 0.0797 - val_acc: 0.9782
#> Epoch 15/30
#>  - 3s - loss: 0.0420 - acc: 0.9860 - val_loss: 0.0777 - val_acc: 0.9781
#> Epoch 16/30
#>  - 4s - loss: 0.0354 - acc: 0.9883 - val_loss: 0.0828 - val_acc: 0.9783
#> Epoch 17/30
#>  - 3s - loss: 0.0343 - acc: 0.9882 - val_loss: 0.0812 - val_acc: 0.9797
#> Epoch 18/30
#>  - 3s - loss: 0.0327 - acc: 0.9898 - val_loss: 0.0826 - val_acc: 0.9798
#> Epoch 19/30
#>  - 3s - loss: 0.0339 - acc: 0.9886 - val_loss: 0.0801 - val_acc: 0.9798
#> Epoch 20/30
#>  - 4s - loss: 0.0310 - acc: 0.9903 - val_loss: 0.0853 - val_acc: 0.9784
#> Epoch 21/30
#>  - 4s - loss: 0.0288 - acc: 0.9909 - val_loss: 0.0830 - val_acc: 0.9812
#> Epoch 22/30
#>  - 4s - loss: 0.0290 - acc: 0.9901 - val_loss: 0.0851 - val_acc: 0.9795
#> Epoch 23/30
#>  - 3s - loss: 0.0275 - acc: 0.9908 - val_loss: 0.0845 - val_acc: 0.9806
#> Epoch 24/30
#>  - 3s - loss: 0.0265 - acc: 0.9911 - val_loss: 0.0890 - val_acc: 0.9792
#> Epoch 25/30
#>  - 3s - loss: 0.0250 - acc: 0.9916 - val_loss: 0.0856 - val_acc: 0.9814
#> Epoch 26/30
#>  - 4s - loss: 0.0244 - acc: 0.9920 - val_loss: 0.0832 - val_acc: 0.9819
#> Epoch 27/30
#>  - 3s - loss: 0.0279 - acc: 0.9902 - val_loss: 0.0891 - val_acc: 0.9809
#> Epoch 28/30
#>  - 3s - loss: 0.0238 - acc: 0.9919 - val_loss: 0.0855 - val_acc: 0.9803
#> Epoch 29/30
#>  - 3s - loss: 0.0244 - acc: 0.9920 - val_loss: 0.0903 - val_acc: 0.9802
#> Epoch 30/30
#>  - 3s - loss: 0.0248 - acc: 0.9918 - val_loss: 0.0954 - val
```

![keras-mnist.jpeg](https://qiita-image-store.s3.amazonaws.com/0/99957/ac4f9582-a88d-e1c1-5e07-86f3fcd358d4.jpeg)


```r
# モデルについて
print(keras_model)
Model
______________________________________________________________________________________________________________
Layer (type)                                     Output Shape                                Param #          
==============================================================================================================
dense_1 (Dense)                                  (None, 256)                                 200960           
______________________________________________________________________________________________________________
dropout_1 (Dropout)                              (None, 256)                                 0                
______________________________________________________________________________________________________________
dense_2 (Dense)                                  (None, 128)                                 32896            
______________________________________________________________________________________________________________
dropout_2 (Dropout)                              (None, 128)                                 0                
______________________________________________________________________________________________________________
dense_3 (Dense)                                  (None, 10)                                  1290             
==============================================================================================================
Total params: 235,146
Trainable params: 235,146
Non-trainable params: 0
______________________________________________________________________________________________________________

# DiagrammeR::grViz(
#  diagram = as.character(
#    x = keras_vis_utils$model_to_dot(model = keras_model, show_shapes = TRUE)$create(prog = "dot", format = "dot")
#  ),
#  engine = "dot"
#)

# テストデータで評価した結果
keras::evaluate(object = keras_model, x_test, y_test, verbose = FALSE)
#> $loss
#> [1] 0.07962815
#> 
#> $acc
#> [1] 0.9818

calcAccuracy(pred = keras::predict_classes(object = keras_model, x = x_test), test = y_test_raw) %>% 
  purrr::invoke(.f = plotAccuracy)
```

![keras-mnist-acc.png](https://qiita-image-store.s3.amazonaws.com/0/99957/610284e6-f11d-f7a8-4494-1708c9e493c5.png)


### モデル出力(implementation = "keras")

　デフォルトないしは`keras::use_implementation(implementation = "keras")`を実行し、kerasライブラリを利用した場合は`tensorflow::export_savedmodel()`を用います。

```r:implementation="keras"
tensorflow::export_savedmodel(
  object = keras_model, export_dir_base = PATH_NAMES$SAVED_MODEL$KERAS,
  overwrite = TRUE
)
#> Keras learning phase set to 0 for export (restart R session before doing additional training)

# Tensorflow 1.12.0で追加されたKerasモデルをSavedModel形式に直接出力する関数はまだ対応していないようです。
tensorflow::tf$contrib$saved_model$save_keras_model(
   model = keras_model, saved_model_path = PATH_NAMES$SAVED_MODEL$KERAS
)
#> TypeError: save_weights() got an unexpected keyword argument 'save_format'
```

### モデル出力(implementation = "tensorflow")

　`keras::use_implementation(implementation = "tensorflow")`を実行してTensorflow APIの実装を利用した場合は、`tensorflow::export_savedmodel()`をそのまま使うとエラーになります。
　そのため、KerasモデルからSessionを取り出し、Signatureを定義してtf.saved_model.BuilderでSavedModelとして出力する必要があります。

- [Save and Restore](https://www.tensorflow.org/guide/saved_model)
- [tf.saved_model.Builder](https://www.tensorflow.org/api_docs/python/tf/saved_model/Builder
)
- [KerasモデルをCloud ML Engineで学習してOnline Predictionしてみた](https://qiita.com/hayatoy/items/cbed2ebb5ae1e7bbc202)

```r:implementation="tensorflow"
tensorflow::export_savedmodel(
  object = keras_model, export_dir_base = PATH_NAMES$SAVED_MODEL$KERAS,
  overwrite = TRUE
)
#> Error in export_savedmodel.keras.engine.training.Model(object = keras_model,  : 'export_savedmodel()' is currently unsupported under the TensorFlow Keras implementation, consider using 'tfestimators::keras_model_to_estimator()'.

# エラーメッセージに従うと別のエラーになります
tfestimators::keras_model_to_estimator(
  keras_model = keras_model, model_dir = PATH_NAMES$SAVED_MODEL$KERAS
)
#> ValueError: ('Expected `model` argument to be a `Model` instance, got ', <keras.engine.sequential.Sequential object at 0x7fc9dfa7beb8>) 


# "tensorflow.python.client.session.Session"をクラスに持つRオブジェクトをtensorflow::export_savedmodel()に適用したときも同じような処理をしていますが、うまく動作しなかったので次のような流れで処理してモデルを出力しています
# https://github.com/rstudio/tensorflow/blob/master/R/save.R

# セッション取得
sess <- keras::k_get_session()

# input/output
inputs <- reticulate::dict()
inputs_name <- as.character(x = keras_model$input$name)
inputs[inputs_name] <- tensorflow::tf$saved_model$utils$build_tensor_info(
  tensor = sess$graph$get_tensor_by_name(name = inputs_name)
)
outputs <- reticulate::dict()
outputs_name <- as.character(x = keras_model$output$name)
outputs[outputs_name] <- tensorflow::tf$saved_model$utils$build_tensor_info(
  tensor = sess$graph$get_tensor_by_name(name = outputs_name)
)

# signature
signature_map <- reticulate::dict()
signature <- tensorflow::tf$saved_model$signature_def_utils$build_signature_def(
  inputs = inputs, outputs = outputs,
  method_name = tensorflow::tf$saved_model$signature_constants$PREDICT_METHOD_NAME
)
signature_map[tensorflow::tf$saved_model$signature_constants$DEFAULT_SERVING_SIGNATURE_DEF_KEY] <- signature


# SaveModel形式で保存
builder <- tensorflow::tf$saved_model$builder$SavedModelBuilder(export_dir = PATH_NAMES$SAVED_MODEL$KERAS)
builder$add_meta_graph_and_variables(
  sess = sess, tags = list(tensorflow::tf$saved_model$tag_constants$SERVING),
  signature_def_map = signature_map
)
builder$save()
#> [1] TRUE


# tfdeploy::predict_savedmodel()を用いると、ローカルに出力したモデルを読み込んで予測することも可能
# https://tensorflow.rstudio.com/tools/tfdeploy/articles/saved_models.html
purrr::array_branch(array = x_test[c(1, 2), ], margin = 1) %>% 
  tfdeploy::predict_savedmodel(model = PATH_NAMES$SAVED_MODEL$KERAS) %>% 
  purrr::flatten() %>%  
  purrr::map_dbl(.f = function(x) { return(which.max(purrr::as_vector(x)) - 1) })
#> [1] 7 2
```

## XGBoostの場合

　XGBoostはxgboostパッケージを用いて学習して、パッケージで定義されている`xgboost::xgb.save()`を適用すれば対応するバイナリ形式で保存されます。pickleまたはjoblibも利用できますが、こちらが手軽でPythonバージョンに依存しないのでおすすめします。
　`xgboost::xgb.save()`を用いて保存した場合、出力するファイル名は「model.bst」にしておく必要があります。pickleを用いた際はファイル名は「model.pkl」にして、joblibでは「model.joblib」にします。

- [Cloud ML Engine > ドキュメント > scikit-learnとXGBoost - モデルファイルの命名規則](https://cloud.google.com/ml-engine/docs/scikit/getting-started-training#model_file_naming_requirements)

```r:xgboost-mnist.R
library(xgboost)

xgb_model <- xgboost::xgb.train(
  params = list(
    max_depth = 6, eta = 0.08, silent = TRUE, nthread = 2, 
    objective = "multi:softmax", eval_metric = "mlogloss",
    num_class = 10L
  ),
  data = xgboost::xgb.DMatrix(data = x_train, label = y_train_raw),
  nrounds = 10
)
```

### モデル学習

```r:
source(file = PATH_NAMES$CREATE_MODEL$XGBOOST)

# モデルについて
print(x = xgb_model)
#> ##### xgb.Booster
#> raw: 410.8 Kb 
#> call:
#>   xgboost::xgb.train(params = list(max_depth = 6, eta = 0.08, silent = TRUE, 
#>     nthread = 2, objective = "multi:softmax", eval_metric = "mlogloss", 
#>     num_class = 10L), data = xgboost::xgb.DMatrix(data = x_train, 
#>     label = y_train_raw), nrounds = 10)
#> params (as set within xgb.train):
#>   max_depth = "6", eta = "0.08", silent = "TRUE", nthread = "2", #> objective = "multi:softmax", eval_metric = "mlogloss", num_class = "10", silent = "1"
#> xgb.attributes:
#>   niter
#> callbacks:
#>   cb.print.evaluation(period = print_every_n)
#> niter: 10
#> nfeatures : 784 

# テストデータで評価した結果
calcAccuracy(pred = predict(object = xgb_model, newdata = x_test), test = y_test_raw) %>% 
  purrr::invoke(.f = plotAccuracy)
```

![xgboost-mnist-acc.png](https://qiita-image-store.s3.amazonaws.com/0/99957/cc0ba400-25c5-4b25-d4a4-b54f7bbf70f1.png)


### モデル出力

```r:モデル出力
dir.create(path = dirname(path = PATH_NAMES$SAVED_MODEL$XGBOOST))
xgboost::xgb.save(model = xgb_model, fname = PATH_NAMES$SAVED_MODEL$XGBOOST)
#> [1] TRUE
```

## scikit-learnの場合

　scikit-learnを利用するRパッケージは今のところ見当たらないので、先日まとめたreticulateパッケージでインポートしてモデル学習し、対応するライブラリ(pickleまたはjoblib)を用いて保存します。
　出力するファイル名はXGBoostと同様に保存に用いたライブラリに応じて、model.pkl(pickle)かmodel.joblib(joblib)にしておく必要があります。ここではpickleを用いました。
　なお、scikit-learnによるモデルのデプロイは、ここで生成したPythonバージョンに依存します

```r:scikit-mnist.R
library(reticulate)

reticulate::use_python(
  python = reticulate:::python_unix_binary(bin = "python3.6")
)


LogisticRegression <- reticulate::import(module = "sklearn.linear_model")$LogisticRegression

sklearn_model <- LogisticRegression(
  random_state = 0L, solver = "lbfgs", max_iter = 500L, multi_class = "multinomial"
)
sklearn_model$fit(X = x_train, y = y_train_raw)
```

### モデル学習

```r:
source(file = PATH_NAMES$CREATE_MODEL$SKLEARN)

# モデルについて
print(x = sklearn_model)
#> LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
#>           intercept_scaling=1, max_iter=500, multi_class='multinomial',
#>           n_jobs=None, penalty='l2', random_state=0, solver='lbfgs',
#>           tol=0.0001, verbose=0, warm_start=False)

# テストデータで評価した結果
calcAccuracy(pred = sklearn_model$predict(x_test), test = y_test_raw) %>% 
  purrr::invoke(.f = plotAccuracy)
```

![sckit-mnist-acc.png](https://qiita-image-store.s3.amazonaws.com/0/99957/cc0a2a41-7f86-315a-3492-d42f05c09824.png)


### モデル出力

```r:
# joblib <- reticulate::import(module = "sklearn.externals")$joblib
# joblib$dump(value = sklearn_model, filename = "./savedmodel/scikit/model.joblib")
dir.create(path = dirname(path = PATH_NAMES$SAVED_MODEL$SKLEARN))
reticulate::py_save_object(object = sklearn_model, filename = PATH_NAMES$SAVED_MODEL$SKLEARN)
```



# Cloud MLに保存したモデルをデプロイ

　[Google Cloud ConsoleからGoogle Cloud ML EngineのModel項目にアクセス](https://console.cloud.google.com/mlengine/models)すると、モデルが作成されていない状態では以下のように何も表示されません（APIが有効されていないときは「有効」にする必要があります）。
　この状態では何もできませんので、前節で記載した手順で保存したモデルをGoogle Cloud ML Engine上にデプロイして予測に用いられるようにします。

<img width="1267" alt="ML Engine API.png" src="https://qiita-image-store.s3.amazonaws.com/0/99957/8908c8c7-52a8-f7ad-eece-c6020d6b53d3.png">


## Kerasモデルのデプロイ

　Kerasモデルは、モデル出力のときとは異なり実装に関わらず`cloudml::cloudml_deploy()`を用いてそのままデプロイできます。
　しかしながら、*config*で設定しているPythonバージョンは実装で考慮されていなくて、指定したランタイムバージョン（こちらは受け付けます）のデフォルトである2.7が採用されます。その場合でもKerasモデルに関しては問題なく動作します

```r:Kerasモデルのデプロイ
cloudml::cloudml_deploy(
  export_dir_base = PATH_NAMES$SAVED_MODEL$KERAS,
  name = CLOUDML_DEPLOY$MODEL_NAME$KERAS, version = "v1",
  region = CLOUDML_DEPLOY$REGION,
  config = list(
    trainingInput = list(
      runtimeVersion = CLOUDML_CONFIG$RUNTIME_VERSION,
      pythonVersion = 3.5,
      framework = "tensorflow"
    )
  )
)
#> Copying file://./savedmodel/keras/saved_model.pb [Content-Type=application/octet-stream]...
#> Copying file://./savedmodel/keras/variables/variables.index [Content-Type=application/octet-stream]...
#> Copying file://./savedmodel/keras/variables/variables.data-00000-of-00001 [Content-Type=application/octet-stream]...
#> \ [3/3 files][  3.7 MiB/  3.7 MiB] 100% Done                                    
#> Operation completed over 3 objects/3.7 MiB.                                      
#> Model created and available in https://console.cloud.google.com/mlengine/models/keras_mnist
```

　`cloudml::cloudml_deploy()`を適用すると、Google Cloud Storageに指定したモデルファイル群をコピーされます。コピー先のバケットは「プロジェクト名/r-cloudml/models/」＋「秒までの時間(UTC)」の形式になります。

<img width="1278" alt="gs copy.png" src="https://qiita-image-store.s3.amazonaws.com/0/99957/ec2458ee-ef87-2f9d-db3d-0bfdc0be4350.png">

　ファイルコピーされた後、関数呼び出し時の引数で組み合わせられたgcloudコマンドが実行されてモデルとバージョンが作成に進みます。コマンド実行でエラーが起きずに作成が済むと、Google Cloud ML EngineのModel項目が次のようになります。
　この画面からモデル名（スクリーンショットの「keras_mnist」）をクリックすると、モデルの詳細ページに遷移します。

<img width="1280" alt="ML Engine Model.png" src="https://qiita-image-store.s3.amazonaws.com/0/99957/7e8b8a73-9548-cac8-807c-c39a657584c7.png">

　モデルの詳細ページでは作成されたバージョンがリスト表示されます。モデルは同じ名前で複数バージョンを持たせられため、切り替えや比較が容易にできます。
　リストの中かからバージョンを選ぶと、そのバージョンの詳細ページに移動します。

<img width="1280" alt="ML Engine Model-Version.png" src="https://qiita-image-store.s3.amazonaws.com/0/99957/95b80721-2c0e-9f1a-e059-2e681611336d.png">

　バージョンの詳細ページではモデルのデプロイ状況が確認できます。このKerasモデルでは、さきほど触れた通り、Pythonバージョンの項目が2.7になっています。

<img width="1280" alt="ML Engine Model-Version-Detailed.png" src="https://qiita-image-store.s3.amazonaws.com/0/99957/553e05a7-9ebb-50cc-7a17-861a7c743762.png">

　このデプロイ状況はgcloudコマンドからも確認ができます。ここではあらかじめ定義しておいたCloud ML Engineのバージョンに関するコマンドを実行する関数を使っています（デプロイしたモデルの削除にも利用）。

```r:Kerasモデルのデプロイ状況
execCloudMLVersions(vname = "v1", mname = CLOUDML_DEPLOY$MODEL_NAME$KERAS, command = "describe")
#> createTime: '2018-12-17T12:06:57Z'
#> deploymentUri: gs://yamano-sandbox/r-cloudml/models/2018_12_17_120649806
#> etag: MSieQzxPCWE=
#> framework: TENSORFLOW
#> isDefault: true
#> machineType: mls1-c1-m2
#> name: projects/yamano-sandbox/models/keras_mnist/versions/v1
#> pythonVersion: '2.7'
#> runtimeVersion: '1.10'
#> state: READY
```

　また、デプロイできているモデルのバージョンに対して、`cloudml::cloudml_predict()`でデータを渡して予測結果を得ることができます。

```r:デプロイされたKerasモデルを利用
keras_cloudml_pred <- cloudml::cloudml_predict(
  instances = purrr::array_branch(array = x_test[DEPLOY_TEST_DATA_IDX, ], margin = 1),
  name = CLOUDML_DEPLOY$MODEL_NAME$KERAS, version = "v1"
)
purrr::map_dbl(.x = keras_cloudml_pred$predictions, .f = function(x) { return(which.max(x$dense_3) - 1) })
#> [1] 7 2 1 0 4

# ローカルで学習した結果と比較
keras::predict_classes(object = keras_model, x = x_test[DEPLOY_TEST_DATA_IDX, ])
#> [1] 7 2 1 0 4

# 今回デプロイしたモデルバージョンを削除
execCloudMLVersions(vname = "v1", mname = CLOUDML_DEPLOY$MODEL_NAME$KERAS, command = "delete")
#> This will delete version [v1]...
#> 
#> Do you want to continue (Y/n)?  
#>   Deleting version [v1]......
#> ......................................done.
```


## XGBoostモデルのデプロイ

　XGBoostによるモデルもKerasモデル同様、`cloudml::cloudml_deploy()`を用いてそのままデプロイできます。Keras(Tensorflow)と異なる部分はモデル出力したディレクトリを指定する点です。
　ただし、そのまま適用できるのはモデル出力で`xgboost::xgb.save()`を用いた場合に限ります。

- [Cloud ML Engine > ドキュメント > scikit-learnとXGBoost - モデルバージョンを作成する](https://cloud.google.com/ml-engine/docs/scikit/quickstart#create_a_model_resource)

```r:XGBoostモデルのデプロイ
cloudml::cloudml_deploy(
  # ディレクトリを指定
  export_dir_base = dirname(path = PATH_NAMES$SAVED_MODEL$XGBOOST),
  name = CLOUDML_DEPLOY$MODEL_NAME$XGBOOST, version = "v1",
  region = CLOUDML_DEPLOY$REGION,
  config = list(
    trainingInput = list(
      runtimeVersion = CLOUDML_CONFIG$RUNTIME_VERSION,
      pythonVersion = 3.5,
      framework = "xgboost"
    )
  )
)
#> Copying file://./savedmodel/xgb/model.bst [Content-Type=application/octet-stream]...
#> - [1/1 files][410.7 KiB/410.7 KiB] 100% Done                                    
#> Operation completed over 1 objects/410.7 KiB.                                    
#> Model created and available in https://console.cloud.google.com/mlengine/models/xgb_mnist
```

　`cloudml::cloudml_deploy()`の適用からデプロイが行われるまでの流れもKerasモデルと変わりませんので（Python2.7になるのも同じ）、スクリーンショットを使ったコンソールの説明は省略します。

```r:XGBoostモデルのデプロイ状況
execCloudMLVersions(vname = "v1", mname = CLOUDML_DEPLOY$MODEL_NAME$XGBOOST, command = "describe")
#> createTime: '2018-12-17T12:12:52Z'
#> deploymentUri: gs://yamano-sandbox/r-cloudml/models/2018_12_17_121247384
#> etag: gmJly3SoE4U=
#> framework: XGBOOST
#> isDefault: true
#> machineType: mls1-c1-m2
#> name: projects/yamano-sandbox/models/xgb_mnist/versions/v1
#> pythonVersion: '2.7'
#> runtimeVersion: '1.10'
#> state: READY
```

　問題なくモデルがデプロイされれば、Kerasモデルと同じく予測ができるようになります。

```r:デプロイされたXGBoostモデルを利用
xgboost_cloudml_pred <- cloudml::cloudml_predict(
  instances = purrr::array_branch(array = x_test[DEPLOY_TEST_DATA_IDX, ], margin = 1),
  name = CLOUDML_DEPLOY$MODEL_NAME$XGBOOST, version = "v1"
)
print(x = xgboost_cloudml_pred)
#> Prediction 1:
#> [1] 7
#> Prediction 2:
#> [1] 2
#> Prediction 3:
#> [1] 1
#> Prediction 4:
#> [1] 0
#> Prediction 5:
#> [1] 4

# ローカルで学習した結果と比較
predict(object = xgb_model, newdata = x_test[DEPLOY_TEST_DATA_IDX, ])
#> [1] 7 2 1 0 4

# 今回デプロイしたモデルバージョンを削除
execCloudMLVersions(vname = "v1", mname = CLOUDML_DEPLOY$MODEL_NAME$XGBOOST, command = "delete")
#> This will delete version [v1]...
#> 
#> Do you want to continue (Y/n)?  
#>   Deleting version [v1]......
#> ......................................done.
```

## scikit-learnモデルのデプロイ

　scikit-learnによるモデルは既存の`cloudml::cloudml_deploy()`では次のようなエラーが起きてデプロイできません。

```r
cloudml::cloudml_deploy(
  export_dir_base = dirname(path = PATH_NAMES$SAVED_MODEL$SKLEARN),
  name = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, version = "v1",
  region = CLOUDML_DEPLOY$REGION,
  config = list(
    trainingInput = list(
      runtimeVersion = CLOUDML_CONFIG$RUNTIME_VERSION,
      pythonVersion = 3.5,
      framework = "scikit-learn"
    )
  )
)
#> Copying file://./savedmodel/scikit/model.pkl [Content-Type=application/octet-stream]...
#> - [1/1 files][ 62.0 KiB/ 62.0 KiB] 100% Done                                    
#> Operation completed over 1 objects/62.0 KiB.                                     
#> エラー: ERROR: gcloud invocation failed [exit status 1]
#> 
#> [command]
#> /Users/yamano357/google-cloud-sdk/bin/gcloud --project yamano-sandbox ml-engine versions create v1 --model=scikit_mnist --origin=gs://yamano-sandbox/r-cloudml/models/2018_12_17_121708061 --runtime-version=1.10
#> 
#> [output]
#> 
#> 
#> [errmsg]
#> Creating version (this might take a few minutes)......
#> ..............................................................................................failed.
#> ERROR: (gcloud.ml-engine.versions.create) Bad model detected with error:  "Failed to load model: Could not load the model: /tmp/model/0001/model.pkl. unsupported pickle protocol: 4. Please make sure the model was exported using python 2. Otherwise, please specify the correct 'python_version' parameter when deploying the model. (Error code: 0)
```
　これはreticulateパッケージで指定したPython3系でモデル出力しているのに対して、gcloudコマンドが*config*で指定しているpythonVersionの値が考慮されず実行されるためです（デフォルトの2.7が使われます。前述のKerasやXGBoostのモデルでも同様に考慮されていませんが、影響は出ていません）。
　reticulateパッケージで用いるPythonを2系にしてモデル出力するとデプロイと予測はできますが、Google Cloudのコンソール上でバージョンを読み込むとエラーメッセージが出てきて動作に不安を感じます。よって、引数を考慮してコマンド実行するように修正する方が望ましいでしょう。


### 修正したgcloudコマンドを手実行してデプロイ

　すでに述べておりますが、`cloudml::cloudml_deploy()`はモデルファイル群をGoogle Cloud Storageに送り、指定した引数でgcloudコマンドで実行するという処理を行う関数ですが、runtimeVersion以外のpythonVersionやframeworkといった引数がコマンドに反映されていません。
　よってエラーになったコマンドに「--framework=SCIKIT_LEARN --python-version」を追記して手実行するだけでもうまくいきます。

- [gcloud ml-engine versions create](https://cloud.google.com/sdk/gcloud/reference/ml-engine/versions/create)

```sh:修正したgcloudコマンドの実行
# エラーでも同一バージョンが作成され、すでにあるバージョンはデプロイできないためv2に変更
$ /Users/yamano357/google-cloud-sdk/bin/gcloud --project yamano-sandbox ml-engine versions create v2 --model=scikit_mnist --origin=gs://yamano-sandbox/r-cloudml/models/2018_12_17_121708061 --runtime-version=1.10 --framework=SCIKIT_LEARN --python-version=3.5
Creating version (this might take a few minutes)......done.
```

　上記のコマンド実行で作成されたモデルでは、Pythonバージョンやフレームワーク名が正しく反映されており、予測もできています。したがって、*config*のPythonバージョンやフレームワークを反映するように実行コマンドを修正した関数を適用すると動作するようになります。

```r:scikit-learnモデルのデプロイ状況
execCloudMLVersions(vname = "v2", mname = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, command = "describe")
#> createTime: '2018-12-17T12:20:29Z'
#> deploymentUri: gs://yamano-sandbox/r-cloudml/models/2018_12_17_121708061
#> etag: cVcYbphFdjM=
#> framework: SCIKIT_LEARN
#> isDefault: true
#> machineType: mls1-c1-m2
#> name: projects/yamano-sandbox/models/scikit_mnist/versions/v2
#> pythonVersion: '3.5'
#> runtimeVersion: '1.10'
#> state: READY
```

```r:デプロイされたscikit-learnモデルを利用
scikit_cloudml_pred <- cloudml::cloudml_predict(
  instances = purrr::array_branch(array = x_test[DEPLOY_TEST_DATA_IDX, ], margin = 1),
  name = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, version = "v2"
)
print(x = scikit_cloudml_pred)
#> Prediction 1:
#> [1] 7
#> Prediction 2:
#> [1] 2
#> Prediction 3:
#> [1] 1
#> Prediction 4:
#> [1] 0
#> Prediction 5:
#> [1] 4

# ローカルで学習した結果と比較
sklearn_model$predict(X = x_test[DEPLOY_TEST_DATA_IDX, ])
#> [1] 7 2 1 0 4


# 今回デプロイしたモデルバージョンを削除
execCloudMLVersions(vname = "v1", mname = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, command = "delete")
#> This will delete version [v1]...
#> 
#> Do you want to continue (Y/n)?  
#>   Deleting version [v1]......
#> ......................................done.

execCloudMLVersions(vname = "v2", mname = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, command = "delete")
#> This will delete version [v2]...
#> 
#> Do you want to continue (Y/n)?  
#>   Deleting version [v2]......
#> ......................................done.
```


### 修正した関数を呼び出してデプロイ

　先で触れた通り、*config*のPythonバージョンやフレームワークを反映するように`cloudml::cloudml_deploy()`を修正した関数がdef_deployCloudml.Rになります。

```r:def_deployCloudml.R
deployCloudml <- function (export_dir_base, name, version = paste0(name, "_1"), region = NULL, config = NULL) {
  
  cloudml <- cloudml_config(cloudml = config)
  gcloud <- gcloud_config(gcloud = NULL)
  storage <- gs_ensure_storage(gcloud = gcloud)
  
  python_version <- ifelse(
    test = !is.null(cloudml$trainingInput$pythonVersion[1]),
    yes = cloudml$trainingInput$pythonVersion[1], no = 2.7
  )
  
  ml_frameworks <- c("tensorflow", "scikit-learn", "xgboost")
  framework <- ifelse(
    test = (cloudml$trainingInput$framework[1] %in% ml_frameworks),
    yes = stringr::str_subset(string = cloudml$trainingInput$framework[1], pattern = ml_frameworks),
    no = "tensorflow"
  )
  
  if (is.null(x = region)) {
    region <- gcloud_default_region()
  }
  if (!cloudml_model_exists(gcloud = gcloud, name = name)) {
    arguments <- MLArgumentsBuilder(gcloud = gcloud)(
      c("models", "create", name, "--regions", region)
    )
    cloudml::gcloud_exec(args = arguments(), echo = FALSE)
  }
  
  model_dest <- glue::glue("{storage}/models/{tms}", storage = storage, tms = timestamp_string())
  cloudml::gs_copy(source = export_dir_base, destination = model_dest, recursive = TRUE)
  
  arguments <- MLArgumentsBuilder(gcloud = gcloud)(
    c(
      "versions", "create", as.character(x = version),
      "--model", name, "--origin", model_dest,
      "--runtime-version", cloudml$trainingInput$runtimeVersion,
      "--python-version", python_version,
      "--framework", framework
    )
  )
  
  cloudml::gcloud_exec(args = arguments(), echo = FALSE)
  message("Model created and available in https://console.cloud.google.com/mlengine/models/", name)
  invisible(NULL)
}

# 定義後の自作関数にcloudmlパッケージの環境を与える
# これにより自作関数内でcloudmlパッケージの内部関数を呼び出す際にトリプルコロン(:::)を使わずに済ませられる
environment(fun = deployCloudml) <- environment(fun = cloudml::cloudml_deploy)
```

　scikit-learnモデルを定義しなおした関数でデプロイするとエラーなくうまくいきます（ただし、現行のCloud ML Engineの仕様ではPython3系で出力したモデルはマイナーバージョンに関わらず、"3.5"を指定する必要があります）。

- [Cloud ML Engine > ドキュメント > scikit-learnとXGBoost - モデルバージョンを作成する](https://cloud.google.com/ml-engine/docs/scikit/quickstart#create_a_model_resource)

```r:
source(file = "./def_deployCloudml.R")
deployCloudml(
  export_dir_base = dirname(path = PATH_NAMES$SAVED_MODEL$SKLEARN),
  name = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, version = "v1",
  region = CLOUDML_DEPLOY$REGION,
  config = list(
    trainingInput = list(
      runtimeVersion = CLOUDML_CONFIG$RUNTIME_VERSION,
      pythonVersion = "3.5",
      framework = "scikit-learn"
    )
  )
)
# > Copying file://./savedmodel/scikit/model.pkl [Content-Type=application/octet-stream]...
# > / [1/1 files][ 62.0 KiB/ 62.0 KiB] 100% Done                                    
# > Operation completed over 1 objects/62.0 KiB.                                     
# > Model created and available in https://console.cloud.google.com/mlengine/models/scikit_mnist
```

```r:scikit-learnモデルのデプロイ状況
# config引数が反映されている
execCloudMLVersions(vname = "v1", mname = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, command = "describe")
# > createTime: '2018-12-17T12:25:29Z'
# > deploymentUri: gs://yamano-sandbox/r-cloudml/models/2018_12_17_122524503
# > etag: pjgGx7CSoME=
# > framework: SCIKIT_LEARN
# > isDefault: true
# > machineType: mls1-c1-m2
# > name: projects/yamano-sandbox/models/scikit_mnist/versions/v1
# > pythonVersion: '3.5'
# > runtimeVersion: '1.10'
# > state: READY

# 削除
execCloudMLVersions(vname = "v1", mname = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, command = "delete")
#> This will delete version [v1]...
#> 
#> Do you want to continue (Y/n)?  
#>   Deleting version [v1]......
#> ......................................done.
```

　ymlによる定義ファイル指定は`cloudml::cloudml_deploy()`ですでに対応されていますが、runtimeVersion以外の値が反映されていない状態でした。こちらも修正した関数では受け取った形でデプロイされます。

```yml:scikit-cloudml.yml
trainingInput:
  runtimeVersion: "1.10"
  pythonVersion: 3.5
  framework: scikit-learn
```

```r:ymlファイルを指定して実行
deployCloudml(
  export_dir_base = dirname(path = PATH_NAMES$SAVED_MODEL$SKLEARN),
  name = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, version = "v1",
  region = CLOUDML_DEPLOY$REGION,
  config = "scikit-cloudml.yml"
)
# > Copying file://./savedmodel/scikit/model.pkl [Content-Type=application/octet-stream]...
# > / [1/1 files][ 62.0 KiB/ 62.0 KiB] 100% Done                                    
# > Operation completed over 1 objects/62.0 KiB.                                     
# > Model created and available in https://console.cloud.google.com/mlengine/models/scikit_mnist


# 反映されたモデルがデプロイされている
# 次の節で利用するため、このバージョンは残しておく
execCloudMLVersions(vname = "v1", mname = CLOUDML_DEPLOY$MODEL_NAME$SKLEARN, command = "describe")
# > createTime: '2018-12-17T12:32:00Z'
# > deploymentUri: gs://yamano-sandbox/r-cloudml/models/2018_12_17_123155749
# > etag: GT_yGH8FicE=
# > framework: SCIKIT_LEARN
# > isDefault: true
# > machineType: mls1-c1-m2
# > name: projects/yamano-sandbox/models/scikit_mnist/versions/v1
# > pythonVersion: '3.5'
# > runtimeVersion: '1.10'
# > state: READY
```

## Pythonからの呼び出し

　前述のようにRから作成してGoogle Cloud ML Engineにデプロイしたモデルは、次のようにしてPythonからも呼び出せます。

```py:cloudml_pred.py
from googleapiclient import discovery
from keras.datasets import mnist

PROJECTID = 'yamano-sandbox'
projectID = 'projects/{}'.format(PROJECTID)

scikitModelName = 'scikit_mnist'
scikitModelID = '{}/models/{}'.format(projectID, scikitModelName)

versionName = 'v1'
runtimeVersion = '1.10'
ml = discovery.build('ml', 'v1')


(x_train, y_train), (x_test, y_test) = mnist.load_data()
request_body = {'instances': (x_test[0] / 255).reshape([1, 784]).tolist() }

ml.projects().predict(name=scikitModelID, body=request_body).execute()
```

```py:呼び出し結果
$ python3 cloudml_pred.py
7
```


# まとめ

　cloudmlパッケージを用いてローカル環境で学習したモデルをGoogle Cloud ML Engineに配置して、作成言語を問わず呼び出せることを確認しました。reticulateパッケージを組み合わせたPythonによる機械学習ライブラリの活用と、フルマネージドクラウドサービスのリソースを用いたモデル学習・デプロイメントを組み合わせることで、機械学習の利用は言語に依らず広がっていくことでしょう。

　Google Cloud ML Engineでも可能ですが、エンジニアリソースを割けるならTensorFlow ServingやMLflow, Kubeflowなどの導入が良いでしょう。逆にエンジニアがいない状況だったり、試してみるというフェーズではGoogle Cloud ML Engineは手軽に使えて有効です。
　しかしながら、Rのcloudmlパッケージは実装上で使い勝手が良くない箇所も残っており（特に今回は書いていないトレーニング機能）、開発の進みも早くない点が懸念でもあります。



## 参考

- [Cloud ML Engine > ドキュメント > TensorFlow > モデルのデプロイ](https://cloud.google.com/ml-engine/docs/tensorflow/deploying-models)
- [CloudML - Deploying Models](https://tensorflow.rstudio.com/tools/cloudml/articles/deployment.html)
- [mlflow: Interface to 'MLflow'](https://cran.r-project.org/web/packages/mlflow/index.html)


## 実行環境

```r:R実行環境
> devtools::session_info()
─ Session info ────────────────────────────────────────────────────────────────────────────────────────
 setting  value                       
 version  R version 3.5.1 (2018-07-02)
 os       macOS High Sierra 10.13.6   
 system   x86_64, darwin15.6.0        
 ui       RStudio                     
 language (EN)                        
 collate  ja_JP.UTF-8                 
 ctype    ja_JP.UTF-8                 
 tz       Asia/Tokyo                  
 date     2018-12-18                  

─ Packages ────────────────────────────────────────────────────────────────────────────────────────────
 package      * version   date       lib source                           
 assertthat     0.2.0     2017-04-11 [1] CRAN (R 3.5.1)                   
 backports      1.1.3     2018-12-14 [1] CRAN (R 3.5.1)                   
 base64enc      0.1-3     2015-07-28 [1] CRAN (R 3.5.1)                   
 bindr          0.1.1     2018-03-13 [1] CRAN (R 3.5.1)                   
 bindrcpp     * 0.2.2     2018-03-29 [1] CRAN (R 3.5.1)                   
 brew           1.0-6     2011-04-13 [1] CRAN (R 3.5.1)                   
 broom          0.5.1     2018-12-05 [1] CRAN (R 3.5.1)                   
 callr          3.1.0     2018-12-10 [1] CRAN (R 3.5.1)                   
 cellranger     1.1.0     2016-07-27 [1] CRAN (R 3.5.1)                   
 cli            1.0.1     2018-09-25 [1] CRAN (R 3.5.1)                   
 cloudml      * 0.6.0     2018-08-27 [1] CRAN (R 3.5.1)                   
 colorspace     1.3-2     2016-12-14 [1] CRAN (R 3.5.1)                   
 config         0.3       2018-03-27 [1] CRAN (R 3.5.1)                   
 crayon         1.3.4     2017-09-16 [1] CRAN (R 3.5.1)                   
 data.table     1.11.8    2018-09-30 [1] CRAN (R 3.5.1)                   
 desc           1.2.0     2018-05-01 [1] CRAN (R 3.5.1)                   
 devtools       2.0.1     2018-10-26 [1] CRAN (R 3.5.1)                   
 DiagrammeR   * 1.0.0     2018-03-01 [1] CRAN (R 3.5.1)                   
 digest         0.6.18    2018-10-10 [1] CRAN (R 3.5.1)                   
 downloader     0.4       2015-07-09 [1] CRAN (R 3.5.1)                   
 dplyr        * 0.7.8     2018-11-10 [1] CRAN (R 3.5.1)                   
 forcats      * 0.3.0     2018-02-19 [1] CRAN (R 3.5.1)                   
 forge          0.1.0     2018-08-31 [1] CRAN (R 3.5.1)                   
 fs             1.2.6     2018-08-23 [1] CRAN (R 3.5.1)                   
 generics       0.0.2     2018-11-29 [1] CRAN (R 3.5.1)                   
 ggplot2      * 3.1.0     2018-10-25 [1] CRAN (R 3.5.1)                   
 glue           1.3.0     2018-07-17 [1] CRAN (R 3.5.1)                   
 gridExtra      2.3       2017-09-09 [1] CRAN (R 3.5.1)                   
 gtable         0.2.0     2016-02-26 [1] CRAN (R 3.5.1)                   
 haven          2.0.0     2018-11-22 [1] CRAN (R 3.5.1)                   
 hms            0.4.2     2018-03-10 [1] CRAN (R 3.5.1)                   
 htmltools      0.3.6     2017-04-28 [1] CRAN (R 3.5.1)                   
 htmlwidgets    1.3       2018-09-30 [1] CRAN (R 3.5.1)                   
 httpuv         1.4.5     2018-07-19 [1] CRAN (R 3.5.1)                   
 httr           1.4.0     2018-12-11 [1] CRAN (R 3.5.1)                   
 igraph         1.2.2     2018-07-27 [1] CRAN (R 3.5.1)                   
 influenceR     0.1.0     2015-09-03 [1] CRAN (R 3.5.1)                   
 jsonlite       1.6       2018-12-07 [1] CRAN (R 3.5.1)                   
 keras        * 2.2.4     2018-11-22 [1] CRAN (R 3.5.1)                   
 labeling       0.3       2014-08-23 [1] CRAN (R 3.5.1)                   
 later          0.7.5     2018-09-18 [1] CRAN (R 3.5.1)                   
 lattice        0.20-38   2018-11-04 [1] CRAN (R 3.5.1)                   
 lazyeval       0.2.1     2017-10-29 [1] CRAN (R 3.5.1)                   
 lubridate      1.7.4     2018-04-11 [1] CRAN (R 3.5.1)                   
 magrittr       1.5       2014-11-22 [1] CRAN (R 3.5.1)                   
 Matrix         1.2-14    2018-04-13 [1] CRAN (R 3.5.1)                   
 memoise        1.1.0     2017-04-21 [1] CRAN (R 3.5.1)                   
 modelr         0.1.2     2018-05-11 [1] CRAN (R 3.5.1)                   
 munsell        0.5.0     2018-06-12 [1] CRAN (R 3.5.1)                   
 nlme           3.1-137   2018-04-07 [1] CRAN (R 3.5.1)                   
 packrat        0.5.0     2018-11-14 [1] CRAN (R 3.5.1)                   
 pacman         0.5.0     2018-10-22 [1] CRAN (R 3.5.1)                   
 pillar         1.3.1     2018-12-15 [1] CRAN (R 3.5.1)                   
 pkgbuild       1.0.2     2018-10-16 [1] CRAN (R 3.5.1)                   
 pkgconfig      2.0.2     2018-08-16 [1] CRAN (R 3.5.1)                   
 pkgload        1.0.2     2018-10-29 [1] CRAN (R 3.5.1)                   
 plyr           1.8.4     2016-06-08 [1] CRAN (R 3.5.1)                   
 prettyunits    1.0.2     2015-07-13 [1] CRAN (R 3.5.1)                   
 processx       3.2.1     2018-12-05 [1] CRAN (R 3.5.1)                   
 promises       1.0.1     2018-04-13 [1] CRAN (R 3.5.1)                   
 ps             1.2.1     2018-11-06 [1] CRAN (R 3.5.1)                   
 purrr        * 0.2.5     2018-05-29 [1] CRAN (R 3.5.1)                   
 R6             2.3.0     2018-10-04 [1] CRAN (R 3.5.1)                   
 RColorBrewer   1.1-2     2014-12-07 [1] CRAN (R 3.5.1)                   
 Rcpp           1.0.0     2018-11-07 [1] CRAN (R 3.5.1)                   
 readr        * 1.3.0     2018-12-11 [1] CRAN (R 3.5.1)                   
 readxl         1.1.0     2018-04-20 [1] CRAN (R 3.5.1)                   
 remotes        2.0.2     2018-10-30 [1] CRAN (R 3.5.1)                   
 reticulate   * 1.10      2018-08-05 [1] CRAN (R 3.5.1)                   
 RevoUtils    * 11.0.1    2018-08-01 [1] local                            
 rgexf          0.15.3    2015-03-24 [1] CRAN (R 3.5.1)                   
 rlang          0.3.0.1   2018-10-25 [1] CRAN (R 3.5.1)                   
 Rook           1.1-1     2014-10-20 [1] CRAN (R 3.5.1)                   
 rprojroot      1.3-2     2018-01-03 [1] CRAN (R 3.5.1)                   
 rstudioapi     0.8       2018-10-02 [1] CRAN (R 3.5.1)                   
 rvest          0.3.2     2016-06-17 [1] CRAN (R 3.5.1)                   
 scales         1.0.0     2018-08-09 [1] CRAN (R 3.5.1)                   
 sessioninfo    1.1.1     2018-11-05 [1] CRAN (R 3.5.1)                   
 stringi        1.2.4     2018-07-20 [1] CRAN (R 3.5.1)                   
 stringr      * 1.3.1     2018-05-10 [1] CRAN (R 3.5.1)                   
 swagger        3.9.2     2018-03-23 [1] CRAN (R 3.5.1)                   
 tensorflow   * 1.10      2018-11-19 [1] CRAN (R 3.5.1)                   
 tfdatasets   * 1.9       2018-08-25 [1] CRAN (R 3.5.1)                   
 tfdeploy     * 0.5.1     2018-12-17 [1] Github (rstudio/tfdeploy@75f901e)
 tfestimators * 1.9.1     2018-11-07 [1] CRAN (R 3.5.1)                   
 tfruns       * 1.4       2018-08-25 [1] CRAN (R 3.5.1)                   
 tibble       * 1.4.2     2018-01-22 [1] CRAN (R 3.5.1)                   
 tidyr        * 0.8.2     2018-10-28 [1] CRAN (R 3.5.1)                   
 tidyselect     0.2.5     2018-10-11 [1] CRAN (R 3.5.1)                   
 tidyverse    * 1.2.1     2017-11-14 [1] CRAN (R 3.5.1)                   
 usethis        1.4.0     2018-08-14 [1] CRAN (R 3.5.1)                   
 viridis        0.5.1     2018-03-29 [1] CRAN (R 3.5.1)                   
 viridisLite    0.3.0     2018-02-01 [1] CRAN (R 3.5.1)                   
 visNetwork     2.0.5     2018-12-05 [1] CRAN (R 3.5.1)                   
 whisker        0.3-2     2013-04-28 [1] CRAN (R 3.5.1)                   
 withr          2.1.2     2018-03-15 [1] CRAN (R 3.5.1)                   
 xgboost      * 0.71.2    2018-06-09 [1] CRAN (R 3.5.1)                   
 XML            3.98-1.16 2018-08-19 [1] CRAN (R 3.5.1)                   
 xml2           1.2.0     2018-01-24 [1] CRAN (R 3.5.1)                   
 yaml           2.2.0     2018-07-25 [1] CRAN (R 3.5.1)                   
 zeallot        0.1.0     2018-01-28 [1] CRAN (R 3.5.1)                   

[1] /Library/Frameworks/R.framework/Versions/3.5.1-MRO/Resources/library
```


# おまけ

　Tensorflow Eagar ExecutionによるモデルはGoogle Cloud ML Engineで使える形に出力する方法がわかりませんでしたが、試したのでメモとして残しておきます。

- [Keras with Eager Execution](https://tensorflow.rstudio.com/keras/articles/eager_guide.html)
- [TensorFlow Eager Executionの紹介](https://qiita.com/kazk1018/items/69f38ec766671e4d2467)

```r:tfe-mnist.R
library(reticulate)
library(tensorflow)
library(tfdatasets)
library(keras)

reticulate::use_python(
  python = reticulate:::python_unix_binary(bin = "python3.6"),
  required = TRUE
)

keras::use_implementation(implementation = "tensorflow")
keras::use_backend(backend = "tensorflow")
tensorflow::tfe_enable_eager_execution(device_policy = "silent")
tensorflow::tf$executing_eagerly()
# > [1] TRUE


# データセット作成
c(c(x_train, y_train), c(x_test, y_test)) %<-% keras::dataset_mnist()
x_train <- reticulate::array_reshape(x = x_train, dim = c(nrow(x_train), 784L)) / 255
x_test <- reticulate::array_reshape(x = x_test, dim = c(nrow(x_test), 784L)) / 255

y_train <- keras::to_categorical(y = y_train, num_classes = 10L)
y_test <- keras::to_categorical(y = y_test, num_classes = 10L)

train_dataset <- tfdatasets::tensor_slices_dataset(list(x_train, y_train)) %>% 
  tfdatasets::dataset_batch(batch_size = 128L)
test_dataset <- tfdatasets::tensor_slices_dataset(list(x_test, y_test)) %>% 
  tfdatasets::dataset_batch(batch_size = 128L)


# モデル定義
defMnistModel <- function(name = NULL) {
  
  keras::keras_model_custom(
    name = name, 
    model_fn = function(self) {
    
      # define any number of layers here
      self$dense1 <- layer_dense(units = 256L, activation = "relu", input_shape = c(784L))
      self$dropout1 <- layer_dropout(rate = 0.4)
      self$dense2 <- layer_dense(units = 128L, activation = "relu")
      self$dropout2 <- keras::layer_dropout(rate = 0.3) 
      self$dense3 <- keras::layer_dense(units = 10L, activation = "softmax")

      # this is the "call" function that defines what happens when the model is called
      function (x, mask = NULL) {
        x %>% 
          self$dense1() %>% self$dropout1() %>%
          self$dense2() %>% self$dropout2() %>%
          self$dense3()
      }
    }
  )
}

model <- defMnistModel()
optimizer <- tf$train$AdamOptimizer()

calcLoss <- function(y_true, y_pred) {
  # loss <- tensorflow::tf$nn$sparse_softmax_cross_entropy_with_logits(
  loss <- tensorflow::tf$nn$softmax_cross_entropy_with_logits_v2(
    labels = y_true, logits = y_pred
  )
  tensorflow::tf$reduce_mean(loss)
}


# 学習
n_epochs <- 30
for (epoch in seq_len(length.out = n_epochs)) {
  
  # accumulate current epoch's loss (for display purposes only)
  total_loss <- iteration <- 0
  
  # create fresh iterator from dataset
  iter <- tfdatasets::make_iterator_one_shot(dataset = train_dataset)
  
  # loop once through the dataset
  tfdatasets::until_out_of_range(
    expr = {

      c(x, y) %<-% tfdatasets::iterator_get_next(iterator = iter)
      
      iteration <- iteration + 1
      with(tensorflow::tf$GradientTape() %as% tape, {
        
        # run model on current batch
        preds <- model(x)
        
        # compute the loss
        loss <- calcLoss(y_true = y, y_pred = preds)
      })
      
      
      total_loss <- total_loss + loss
      gradients <- tape$gradient(loss, model$variables)

      # update model weights
      optimizer$apply_gradients(
        grads_and_vars = purrr::transpose(list(gradients, model$variables)),
        global_step = tensorflow::tf$train$get_or_create_global_step()
      )
    }
  )
  
  print(
    glue::glue(
      "epoch: {epoch}, {loss} \n",
      epoch = epoch,
      loss =  (keras::k_cast_to_floatx(x = total_loss) %>% as.double()) / iteration %>% 
        round(digits = 4)
    )
  )
}
# > epoch: 1, 1.48286200484741 
# > epoch: 2, 1.48078017244969 
# > epoch: 3, 1.47878097826992 
# > epoch: 4, 1.47735608717018 
# > epoch: 5, 1.47621567976246 
# > epoch: 6, 1.47499094233076 
# > epoch: 7, 1.47397364634695 
# > epoch: 8, 1.47360912721548 
# > epoch: 9, 1.47307529734142 
# > epoch: 10, 1.47200867870469 
# > epoch: 11, 1.47171105098114 
# > epoch: 12, 1.47116824152119 
# > epoch: 13, 1.47032702350413 
# > epoch: 14, 1.47118541985941 
# > epoch: 15, 1.46978122084888 
# > epoch: 16, 1.46939731101746 
# > epoch: 17, 1.46993192172508 
# > epoch: 18, 1.46939705073961 
# > epoch: 19, 1.46943088686034 
# > epoch: 20, 1.4693312004431 
# > epoch: 21, 1.46891774907549 
# > epoch: 22, 1.46937909156783 
# > epoch: 23, 1.46856793564266 
# > epoch: 24, 1.46800052992571 
# > epoch: 25, 1.46795576213519 
# > epoch: 26, 1.46823269776952 
# > epoch: 27, 1.46751185825893 
# > epoch: 28, 1.46728606722248 
# > epoch: 29, 1.46787989114139 
# > epoch: 30, 1.46739980864372 


# 評価
tfe <- tf$contrib$eager
accuracy <- tfe$metrics$Accuracy("accuracy")
accuracy(
  tf$argmax(model(x_test), axis = 1L, output_type = tf$int64),
  tf$argmax(y_test, axis = 1L, output_type = tf$int64)
)
# > [[1]]
# > tf.Tensor([7 2 1 ... 4 5 6], shape=(10000,), dtype=int64)

# > [[2]]
# > tf.Tensor([7 2 1 ... 4 5 6], shape=(10000,), dtype=int64)

accuracy$result() %>% as.double()
# > [1] 0.9802


y_pred <- reticulate::array_reshape(x = model(x_test), dim = c(10000, 10)) %>% 
  reticulate::py_to_r() %>% 
  purrr::array_branch(array = ., margin = 1) %>% 
  purrr::map_dbl(.f = function(x) { return(which.max(x) - 1) })

calcAccuracy(pred = y_pred, test = y_test_raw) %>% 
  purrr::invoke(.f = plotAccuracy)
```

![tfe-mnist-acc.png](https://qiita-image-store.s3.amazonaws.com/0/99957/993c1ee0-c3a9-e638-edfc-cd9bce3d5987.png)