Share via


運用環境での実行用に R スクリプトを調整する

この記事では、既存の R スクリプトを取得し、Azure Machine Learning でジョブとして実行するための適切な変更を行う方法について説明します。

この記事で詳しく説明されている変更は、その大部分 (全部ではなくても) を行う必要があります。

ユーザーによる操作を削除する

R スクリプトは、無人で実行するように設計されている必要があり、コンテナー内で Rscript コマンドを使用して実行されます。 対話型の入力や出力は、必ずスクリプトから削除してください。

解析を追加する

スクリプトに何らかの入力パラメーターが必要な場合 (ほとんどのスクリプトに該当します) は、Rscript 呼び出しを使用してその入力をスクリプトに渡します。

Rscript <name-of-r-script>.R
--data_file ${{inputs.<name-of-yaml-input-1>}} 
--brand ${{inputs.<name-of-yaml-input-2>}}

R スクリプトで、入力を解析して適切な型変換を行います。 optparse パッケージの使用をお勧めします。

次のスニペットは、以下を行う方法を示しています。

  • パーサーを開始する
  • すべての入力をオプションとして追加する
  • 適切なデータ型を使用して入力を解析する

既定値を追加することもできます。これはテストで役立ちます。 スクリプトの出力がすべて保存されるように、既定値が ./outputs--output パラメーターを追加することをお勧めします。

library(optparse)

parser <- OptionParser()

parser <- add_option(
  parser,
  "--output",
  type = "character",
  action = "store",
  default = "./outputs"
)

parser <- add_option(
  parser,
  "--data_file",
  type = "character",
  action = "store",
  default = "data/myfile.csv"
)

parser <- add_option(
  parser,
  "--brand",
  type = "double",
  action = "store",
  default = 1
)
args <- parse_args(parser)

args は名前付きリストです。 これらのパラメーターはいずれも、スクリプトの後半で使用できます。

azureml_utils.R ヘルパー スクリプトをソース化する

azureml_utils.R スクリプトと呼ばれるヘルパー スクリプトを、実行される R スクリプトと同じ作業ディレクトリでソース化する必要があります。 ヘルパー スクリプトは、実行中の R スクリプトが MLflow サーバーと通信できるようにするために必要です。 実行中のジョブではトークンが短時間に変更されるため、ヘルパー スクリプトを使用することで認証トークンを継続的に取得できます。 また、ヘルパー スクリプトを使用すると、モデル、パラメーター、タグ、一般的な成果物をログするために R MLflow API で提供されているログ関数も使用できます。

  1. 次のコードを使用して、ファイル azureml_utils.R を作成します。

    # Azure ML utility to enable usage of the MLFlow R API for tracking with Azure Machine Learning (Azure ML). This utility does the following::
    # 1. Understands Azure ML MLflow tracking url by extending OSS MLflow R client.
    # 2. Manages Azure ML Token refresh for remote runs (runs that execute in Azure Machine Learning). It uses tcktk2 R libraray to schedule token refresh.
    #    Token refresh interval can be controlled by setting the environment variable MLFLOW_AML_TOKEN_REFRESH_INTERVAL and defaults to 30 seconds.
    
    library(mlflow)
    library(httr)
    library(later)
    library(tcltk2)
    
    new_mlflow_client.mlflow_azureml <- function(tracking_uri) {
      host <- paste("https", tracking_uri$path, sep = "://")
      get_host_creds <- function () {
        mlflow:::new_mlflow_host_creds(
          host = host,
          token = Sys.getenv("MLFLOW_TRACKING_TOKEN"),
          username = Sys.getenv("MLFLOW_TRACKING_USERNAME", NA),
          password = Sys.getenv("MLFLOW_TRACKING_PASSWORD", NA),
          insecure = Sys.getenv("MLFLOW_TRACKING_INSECURE", NA)
        )
      }
      cli_env <- function() {
        creds <- get_host_creds()
        res <- list(
          MLFLOW_TRACKING_USERNAME = creds$username,
          MLFLOW_TRACKING_PASSWORD = creds$password,
          MLFLOW_TRACKING_TOKEN = creds$token,
          MLFLOW_TRACKING_INSECURE = creds$insecure
        )
        res[!is.na(res)]
      }
      mlflow:::new_mlflow_client_impl(get_host_creds, cli_env, class = "mlflow_azureml_client")
    }
    
    get_auth_header <- function() {
        headers <- list()
        auth_token <- Sys.getenv("MLFLOW_TRACKING_TOKEN")
        auth_header <- paste("Bearer", auth_token, sep = " ")
        headers$Authorization <- auth_header
        headers
    }
    
    get_token <- function(host, exp_id, run_id) {
        req_headers <- do.call(httr::add_headers, get_auth_header())
        token_host <- gsub("mlflow/v1.0","history/v1.0", host)
        token_host <- gsub("azureml://","https://", token_host)
        api_url <- paste0(token_host, "/experimentids/", exp_id, "/runs/", run_id, "/token")
        GET( api_url, timeout(getOption("mlflow.rest.timeout", 30)), req_headers)
    }
    
    
    fetch_token_from_aml <- function() {
        message("Refreshing token")
        tracking_uri <- Sys.getenv("MLFLOW_TRACKING_URI")
        exp_id <- Sys.getenv("MLFLOW_EXPERIMENT_ID")
        run_id <- Sys.getenv("MLFLOW_RUN_ID")
        sleep_for <- 1
        time_left <- 30
        response <- get_token(tracking_uri, exp_id, run_id)
        while (response$status_code == 429 && time_left > 0) {
            time_left <- time_left - sleep_for
            warning(paste("Request returned with status code 429 (Rate limit exceeded). Retrying after ",
                        sleep_for, " seconds. Will continue to retry 429s for up to ", time_left,
                        " second.", sep = ""))
            Sys.sleep(sleep_for)
            sleep_for <- min(time_left, sleep_for * 2)
            response <- get_token(tracking_uri, exp_id)
        }
    
        if (response$status_code != 200){
            error_response = paste("Error fetching token will try again after sometime: ", str(response), sep = " ")
            warning(error_response)
        }
    
        if (response$status_code == 200){
            text <- content(response, "text", encoding = "UTF-8")
            json_resp <-jsonlite::fromJSON(text, simplifyVector = FALSE)
            json_resp$token
            Sys.setenv(MLFLOW_TRACKING_TOKEN = json_resp$token)
            message("Refreshing token done")
        }
    }
    
    clean_tracking_uri <- function() {
        tracking_uri <- httr::parse_url(Sys.getenv("MLFLOW_TRACKING_URI"))
        tracking_uri$query = ""
        tracking_uri <-httr::build_url(tracking_uri)
        Sys.setenv(MLFLOW_TRACKING_URI = tracking_uri)
    }
    
    clean_tracking_uri()
    tcltk2::tclTaskSchedule(as.integer(Sys.getenv("MLFLOW_TOKEN_REFRESH_INTERVAL_SECONDS", 30))*1000, fetch_token_from_aml(), id = "fetch_token_from_aml", redo = TRUE)
    
    # Set MLFlow related env vars
    Sys.setenv(MLFLOW_BIN = system("which mlflow", intern = TRUE))
    Sys.setenv(MLFLOW_PYTHON_BIN = system("which python", intern = TRUE))
    
  2. R スクリプトの先頭に次の行を記述します。

source("azureml_utils.R")

データ ファイルをローカル ファイルとして読み取る

R スクリプトをジョブとして実行すると、Azure Machine Learning では、ジョブの送信に指定されたデータを受け取って、実行中のコンテナーにマウントします。 そのため、実行中のコンテナー上でローカル ファイルのようにデータ ファイルを読み取ることができます。

  • ソース データがデータ資産として登録されていることを確認します
  • ジョブ送信パラメーターにデータ資産を名前で渡します
  • ローカル ファイルを通常読み取るように、ファイルを読み取ります

パラメーターのセクションに示すように、入力パラメーターを定義します。 パラメーター data-file を使用してパス全体を指定することで、read_csv(args$data_file) を使用してデータ資産を読み取ることができるようにします。

ジョブ成果物 (イメージ、データなど) を保存する

重要

このセクションはモデルには適用されません。 モデル固有の保存とログの手順については、次の 2 つのセクションを参照してください。

Azure Machine Learning で R スクリプトによって生成される、データ ファイル、イメージ、シリアル化された R オブジェクトなどの任意のスクリプト出力を保存できます。 生成された成果物 (イメージ、モデル、データなど) を保存する ./outputs ディレクトリを作成します。./outputs に保存されたファイルは自動的に実行に含められ、実行の最後に実験にアップロードされます。 入力パラメーターのセクションで --output パラメーターの既定値を追加しているので、R スクリプトに次のコード スニペットを含めて output ディレクトリを作成します。

if (!dir.exists(args$output)) {
  dir.create(args$output)
}

ディレクトリを作成したら、そのディレクトリに成果物を保存します。 次に例を示します。

# create and save a plot
library(ggplot2)

myplot <- ggplot(...)

ggsave(myplot, 
       filename = file.path(args$output,"forecast-plot.png"))


# save an rds serialized object
saveRDS(myobject, file = file.path(args$output,"myobject.rds"))

carrier パッケージを使用してモデルを crate する

R MLflow API のドキュメントでは、R モデルは crate "モデル フレーバー" でなければならないことを指定しています。

  • R スクリプトでモデルをトレーニングし、モデル オブジェクトを生成する場合、後で Azure Machine Learning を使用してデプロイできるように、それを crate する必要があります。
  • crate 関数を使用する場合は、必要なパッケージ関数を呼び出すときに明示的な名前空間を使用します。

fable パッケージで作成された my_ts_model という時系列モデル オブジェクトがあるとします。 このモデルをそのデプロイ時に呼び出すことができるようにするには、crate を作成し、そこでモデル オブジェクトおよび期間数での予測期間を渡します。

library(carrier)
crated_model <- crate(function(x)
{
  fabletools::forecast(!!my_ts_model, h = x)
})

crated_model オブジェクトはログする対象になります。

R MLflow API を使用してモデル、パラメーター、タグ、またはその他の成果物をログする

生成された成果物を保存するだけでなく、実行ごとにモデル、タグ、パラメーターをログすることもできます。 これを行うには、R MLflow API を使用します。

モデルをログするときは、前のセクションの説明に従って作成した、"クレートされたモデル" をログします。

注意

モデルをログすると、そのモデルはさらに保存されて、実行成果物に追加されます。 モデルは、それをログしていなかった場合を除き、明示的に保存する必要はありません。

モデルやパラメーターをログに記録するには、次のようにします。

  1. mlflow_start_run() を使用して実行を開始します
  2. mlflow_log_modelmlflow_log_param、または mlflow_log_batch を使用して成果物をログします
  3. 実行の終了には を使用しないでくださいmlflow_end_run()。 この呼び出しは現在エラーの原因となっているため、スキップしてください。

たとえば、前のセクションで作成した crated_model オブジェクトをログするには、R スクリプトに次のコードを含めます。

ヒント

モデルをログするときに artifact_path の値として models を使用します。(他の名前を付けることもできますが) これがベスト プラクティスです。

mlflow_start_run()

mlflow_log_model(
  model = crated_model, # the crate model object
  artifact_path = "models" # a path to save the model object to
  )

mlflow_log_param(<key-name>, <value>)

# mlflow_end_run() - causes an error, do not include mlflow_end_run()

スクリプトの構造と例

この記事で概説されているすべての変更の後に、これらのコード スニペットをガイドとして使用して R スクリプトを構成します。

# BEGIN R SCRIPT

# source the azureml_utils.R script which is needed to use the MLflow back end
# with R
source("azureml_utils.R")

# load your packages here. Make sure that they are installed in the container.
library(...)

# parse the command line arguments.
library(optparse)

parser <- OptionParser()

parser <- add_option(
  parser,
  "--output",
  type = "character",
  action = "store",
  default = "./outputs"
)

parser <- add_option(
  parser,
  "--data_file",
  type = "character",
  action = "store",
  default = "data/myfile.csv"
)

parser <- add_option(
  parser,
  "--brand",
  type = "double",
  action = "store",
  default = 1
)
args <- parse_args(parser)

# your own R code goes here
# - model building/training
# - visualizations
# - etc.

# create the ./outputs directory
if (!dir.exists(args$output)) {
  dir.create(args$output)
}

# log models and parameters to MLflow
mlflow_start_run()

mlflow_log_model(
  model = crated_model, # the crate model object
  artifact_path = "models" # a path to save the model object to
  )

mlflow_log_param(<key-name>, <value>)

# mlflow_end_run() - causes an error, do not include mlflow_end_run()
## END OF R SCRIPT

環境の作成

R スクリプトを実行するには、Azure CLI の ml 拡張機能 (CLI v2 とも呼ばれます) を使用します。 ml コマンドでは YAML ジョブ定義ファイルを使用します。 az ml を使用したジョブの送信の詳細については、Azure Machine Learning CLI を使用したモデルのトレーニングに関するページを参照してください。

YAML ジョブ ファイルでは環境を指定します。 ジョブを実行するには、まずワークスペース内にこの環境を作成しておく必要があります。

環境は、Azure Machine Learning スタジオ内で、または Azure CLI を使用して作成できます。

どの方法を使用する場合でも、Dockerfile を使用します。 Azure Machine Learning で動作するには、R 環境のすべての Docker コンテキスト ファイルに次の仕様が必要です。

FROM rocker/tidyverse:latest

# Install python
RUN apt-get update -qq && \
 apt-get install -y python3-pip tcl tk libz-dev libpng-dev

RUN ln -f /usr/bin/python3 /usr/bin/python
RUN ln -f /usr/bin/pip3 /usr/bin/pip
RUN pip install -U pip

# Install azureml-MLflow
RUN pip install azureml-MLflow
RUN pip install MLflow

# Create link for python
RUN ln -f /usr/bin/python3 /usr/bin/python

# Install R packages required for logging with MLflow (these are necessary)
RUN R -e "install.packages('mlflow', dependencies = TRUE, repos = 'https://cloud.r-project.org/')"
RUN R -e "install.packages('carrier', dependencies = TRUE, repos = 'https://cloud.r-project.org/')"
RUN R -e "install.packages('optparse', dependencies = TRUE, repos = 'https://cloud.r-project.org/')"
RUN R -e "install.packages('tcltk2', dependencies = TRUE, repos = 'https://cloud.r-project.org/')"

基本イメージは rocker/tidyverse:latest です。これには、多くの R パッケージとその依存関係が既にインストールされています。

重要

スクリプトの実行に必要となる R パッケージは事前にインストールしておく必要があります。 必要に応じて、Docker コンテキスト ファイルに行を追加します。

RUN R -e "install.packages('<package-to-install>', dependencies = TRUE, repos = 'https://cloud.r-project.org/')"

その他の推奨事項

考慮する必要があるその他の推奨事項は次のとおりです。

  • 例外とエラーの処理には R の tryCatch 関数を使用します
  • トラブルシューティングとデバッグのための明示的なログを追加します

次のステップ