Adapter votre script R pour qu’il s’exécute en production

Cet article explique comment prendre un script R existant et apporter les modifications appropriées pour l’exécuter en tant que travail dans Azure Machine Learning.

Vous devrez effectuer la plupart, si ce n’est la totalité, des modifications décrites en détail dans cet article.

Supprimer l’interaction utilisateur

Votre script R doit être conçu pour fonctionner sans assistance et être exécuté au moyen de la commande Rscript dans le conteneur. Veillez à supprimer du script les entrées ou sorties interactives.

Ajouter l’analyse

Si votre script nécessite un type de paramètre d’entrée (c’est le cas de la plupart des scripts), passez les entrées dans le script via l’appel Rscript.

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

Dans votre script R, analysez les entrées et effectuez les conversions de type appropriées. Nous vous recommandons d’utiliser le package optparse.

L’extrait de code suivant vous montre comment :

  • lancer l’analyseur ;
  • ajouter toutes vos entrées sous forme d’options ;
  • analyser les entrées avec les types de données appropriés.

Vous pouvez également ajouter des valeurs par défaut, qui sont pratiques pour les tests. Nous vous recommandons d’ajouter un paramètre --output avec ./outputs comme valeur par défaut, afin que toute sortie du script soit stockée.

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 est une liste nommée. Vous pouvez utiliser tous ces paramètres ultérieurement dans votre script.

Utiliser le script d’assistance azureml_utils.R comme source

Vous devez utiliser un script d’assistance nommé script azureml_utils.R comme source dans le même répertoire de travail que le script R qui sera exécuté. Le script d’assistance est nécessaire pour que le script R en cours d’exécution puisse communiquer avec le serveur MLflow. Le script d’assistance fournit une méthode permettant de récupérer en continu le jeton d’authentification, car il change rapidement dans un travail en cours d’exécution. Le script d’assistance vous permet également d’utiliser les fonctions de journalisation fournies dans l’API R MLflow pour consigner les modèles, les paramètres, les étiquettes et les artefacts généraux.

  1. Créez votre fichier, azureml_utils.R, au moyen de ce code :

    # 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. Démarrez votre script R avec la ligne suivante :

source("azureml_utils.R")

Lire les fichiers de données en tant que fichiers locaux

Lorsque vous exécutez un script R en tant que travail, Azure Machine Learning prend les données que vous spécifiez dans l’envoi du travail, puis les monte sur le conteneur en cours d’exécution. Vous pourrez ainsi lire le ou les fichiers de données, comme s’il s’agissait de fichiers locaux sur le conteneur en cours d’exécution.

  • Vérifiez que vos données sources sont inscrites comme ressource de données.
  • Passez la ressource de données par nom dans les paramètres d’envoi du travail.
  • Lisez les fichiers comme vous le feriez normalement avec un fichier local.

Définissez le paramètre d’entrée comme indiqué dans la section des paramètres. Utilisez le paramètre data-file pour spécifier un chemin complet, afin que vous puissiez utiliser read_csv(args$data_file) pour lire la ressource de données.

Enregistrer les artefacts de travail (images, données, etc.)

Important

Cette section ne s’applique pas aux modèles. Consultez les deux sections suivantes pour obtenir des instructions d’enregistrement et de journalisation spécifiques au modèle.

Vous pouvez stocker des sorties de script arbitraires, telles que des fichiers de données, des images, des objets R sérialisés, etc. ; elles sont générées par le script R dans Azure Machine Learning. Créez un répertoire ./outputs pour stocker tous les artefacts générés (images, modèles, données, etc.). Tous les fichiers enregistrés dans ./outputs seront automatiquement inclus dans l’exécution, et chargés dans l’expérience à la fin de l’exécution. Étant donné que vous avez ajouté une valeur par défaut pour le paramètre --output à la section paramètres d’entrée, incluez l’extrait de code suivant dans votre script R pour créer le répertoire output.

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

Après avoir créé le répertoire, enregistrez vos artefacts dans ce répertoire. Par exemple :

# 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"))

crate vos modèles avec le package carrier

La documentation de l’API R MLflow précise que vos modèles R doivent être du type saveur de modèlecrate.

  • Si votre script R entraîne un modèle et que vous produisez un objet de modèle, vous devez le crate pour pouvoir le déployer ultérieurement avec Azure Machine Learning.
  • Lorsque vous utilisez la fonction crate, utilisez des espaces de noms explicites lors de l’appel d’une fonction de package dont vous avez besoin.

Supposons que vous ayez un objet de modèle de série chronologique nommé my_ts_model, créé avec le package fable. Pour rendre ce modèle joignable lors de son déploiement, créez un élément crate dans lequel vous passerez l’objet de modèle et un horizon de prévision en nombre de périodes :

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

L’objet crated_model est celui que vous allez consigner.

Journaliser modèles, paramètres, étiquettes ou d’autres artefacts avec l’API R MLflow

En plus d’enregistrer tous les artefacts générés, vous pouvez également enregistrer des modèles, des étiquettes et des paramètres pour chaque exécution. Utilisez l’API R MLflow pour ce faire.

Lorsque vous journalisez un modèle, vous consignez dans le journal l’objet modèle emballé que vous avez créé, comme décrit à la section précédente.

Notes

Lorsque vous journalisez un modèle, le modèle est également enregistré et ajouté aux artefacts d’exécution. Il n’est pas nécessaire d’enregistrer explicitement un modèle, sauf si vous ne l’avez pas journalisé.

Pour enregistrer un modèle et/ou un paramètre :

  1. Démarrez l’exécution avec mlflow_start_run().
  2. Journalisez les artefacts avec mlflow_log_model, mlflow_log_paramou mlflow_log_batch.
  3. Ne terminez pas l’exécution avec mlflow_end_run(). Ignorez cet appel, car il provoque actuellement une erreur.

Par exemple, pour consigner l’objet crated_model tel qu’il a été créé à la section précédente, vous devez inclure le code suivant dans votre script R :

Conseil

Utilisez models comme valeur pour artifact_path lors de la journalisation d’un modèle ; il s’agit d’une bonne pratique (même si vous pouvez le nommer autrement.)

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()

Structure et exemple de script

Utilisez ces extraits de code comme guide pour structurer votre script R, tout en suivant l’ensemble des modifications décrites dans cet article.

# 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

Créer un environnement

Pour exécuter votre script R, vous allez utiliser l’extension ml pour Azure CLI, également appelée CLI v2. La commande ml utilise un fichier de définition de travail YAML. Pour plus d’informations sur l’envoi de travaux avec az ml, consultez Entraîner des modèles avec l’interface CLI Azure Machine Learning.

Le fichier de travail YAML précise un environnement. Vous devez créer cet environnement dans votre espace de travail avant de pouvoir exécuter le travail.

Vous pouvez créer l’environnement dans Azure Machine Learning studio ou avec l’interface Azure CLI.

Quelle que soit la méthode dont vous vous servez, vous utiliserez un fichier Dockerfile. Tous les fichiers de contexte Docker destinés aux environnements R doivent comporter la spécification suivante pour fonctionner sur Azure Machine Learning :

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/')"

L’image de base est rocker/tidyverse:latest, qui comprend de nombreux packages R avec leurs dépendances déjà installées.

Important

Vous devez installer tous les packages R dont votre script a besoin pour s’exécuter à l’avance. Ajoutez d’autres lignes au fichier de contexte Docker en fonction des besoins.

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

Suggestions supplémentaires

Voici quelques suggestions supplémentaires que vous pouvez envisager :

  • Utiliser la fonction tryCatch de R pour la gestion des exceptions et des erreurs
  • Ajouter une journalisation explicite pour la résolution des problèmes et le débogage

Étapes suivantes