概要
- cloudmlパッケージはRでGoogle Cloud ML Engineを操作するパッケージ
- ローカル環境で学習したモデルをGoogle Cloud ML Engineで使える形式で保存
- 保存したモデルファイルをcloudmlパッケージでアップロード&APIの形で呼び出し可能に
前書き
これはR Advent Calendar 2018 18日目の記事になります。
TokyoR#74のLTにて、R and Python: How to Integrate the Best of Both into Your Data Science Workflowについてと、reticulateパッケージを使おうという話をしました(reticulateパッケージに関しては前回のQiitaを参考のこと)。
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パッケージでモデルトレーニング可能なことは確認済みです)。
定義・設定
モデル学習した結果をGoogle Cloud ML Engineで使える形で保存し、cloudmlパッケージの関数を利用してデプロイするという流れになりますが、この工程で必要となる関数や定数の定義をここでは行います。合わせてモデル学習で使用するMNISTデータセットの準備もします。
# 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")
)
# 定数定義
# モデル学習スクリプトのパスと学習結果の出力先パス
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)
)
)
}
# 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への認証設定ができます。
# 実行すると選択項目が出てくるので、自身の環境に合わせて適宜設定
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()
によって変更が可能です。
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
)
モデル学習
# 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
# モデルについて
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)
モデル出力(implementation = "keras")
デフォルトないしはkeras::use_implementation(implementation = "keras")
を実行し、kerasライブラリを利用した場合はtensorflow::export_savedmodel()
を用います。
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として出力する必要があります。
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」にします。
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
)
モデル学習
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)
モデル出力
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バージョンに依存します
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)
モデル学習
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)
モデル出力
# 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項目にアクセスすると、モデルが作成されていない状態では以下のように何も表示されません(APIが有効されていないときは「有効」にする必要があります)。
この状態では何もできませんので、前節で記載した手順で保存したモデルをGoogle Cloud ML Engine上にデプロイして予測に用いられるようにします。
Kerasモデルのデプロイ
Kerasモデルは、モデル出力のときとは異なり実装に関わらずcloudml::cloudml_deploy()
を用いてそのままデプロイできます。
しかしながら、configで設定しているPythonバージョンは実装で考慮されていなくて、指定したランタイムバージョン(こちらは受け付けます)のデフォルトである2.7が採用されます。その場合でも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)」の形式になります。
ファイルコピーされた後、関数呼び出し時の引数で組み合わせられたgcloudコマンドが実行されてモデルとバージョンが作成に進みます。コマンド実行でエラーが起きずに作成が済むと、Google Cloud ML EngineのModel項目が次のようになります。
この画面からモデル名(スクリーンショットの「keras_mnist」)をクリックすると、モデルの詳細ページに遷移します。
モデルの詳細ページでは作成されたバージョンがリスト表示されます。モデルは同じ名前で複数バージョンを持たせられため、切り替えや比較が容易にできます。
リストの中かからバージョンを選ぶと、そのバージョンの詳細ページに移動します。
バージョンの詳細ページではモデルのデプロイ状況が確認できます。このKerasモデルでは、さきほど触れた通り、Pythonバージョンの項目が2.7になっています。
このデプロイ状況はgcloudコマンドからも確認ができます。ここではあらかじめ定義しておいたCloud ML Engineのバージョンに関するコマンドを実行する関数を使っています(デプロイしたモデルの削除にも利用)。
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()
でデータを渡して予測結果を得ることができます。
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()
を用いた場合に限ります。
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になるのも同じ)、スクリーンショットを使ったコンソールの説明は省略します。
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モデルと同じく予測ができるようになります。
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()
では次のようなエラーが起きてデプロイできません。
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」を追記して手実行するだけでもうまくいきます。
# エラーでも同一バージョンが作成され、すでにあるバージョンはデプロイできないため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バージョンやフレームワークを反映するように実行コマンドを修正した関数を適用すると動作するようになります。
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
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になります。
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"を指定する必要があります)。
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
# 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以外の値が反映されていない状態でした。こちらも修正した関数では受け取った形でデプロイされます。
trainingInput:
runtimeVersion: "1.10"
pythonVersion: 3.5
framework: scikit-learn
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からも呼び出せます。
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()
$ 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 > モデルのデプロイ
- CloudML - Deploying Models
- mlflow: Interface to 'MLflow'
実行環境
> 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で使える形に出力する方法がわかりませんでしたが、試したのでメモとして残しておきます。
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)