Adattare lo script R per l'esecuzione nell'ambiente di produzione

Questo articolo illustra come acquisire uno script R esistente e apportare le modifiche appropriate per eseguirlo come processo in Azure Machine Learning.

Sarà necessario apportare la maggior parte, se non tutte, le modifiche descritte in dettaglio in questo articolo.

Rimuovere l'interazione dell'utente

Lo script R deve essere progettato per l'esecuzione automatica e verrà eseguito tramite il comando Rscript all'interno del contenitore. Assicurarsi di rimuovere eventuali input o output interattivi dallo script.

Aggiungere analisi

Se lo script richiede un qualsiasi tipo di parametro di input (la maggior parte degli script lo richiede), passare gli input allo script tramite la chiamata Rscript.

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

Nello script R analizzare gli input e apportare le conversioni di tipo appropriate. È consigliabile usare il pacchetto optparse.

Il frammento di codice seguente illustra come:

  • avviare il parser
  • aggiungere tutti gli input come opzioni
  • analizzare gli input con i tipi di dati appropriati

È anche possibile aggiungere impostazioni predefinite utili per i test. È consigliabile aggiungere un parametro --output con un valore predefinito di ./outputs in modo che qualsiasi output dello script venga archiviato.

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 è un elenco denominato. È possibile usare uno di questi parametri in un secondo momento nello script.

Origine dello script helper azureml_utils.R

È necessario generare uno script helper denominato script azureml_utils.R nella stessa directory di lavoro dello script R che verrà eseguito. Lo script helper è necessario affinché lo script R in esecuzione sia in grado di comunicare con il server MLflow. Lo script helper fornisce un metodo per recuperare continuamente il token di autenticazione, poiché il token cambia rapidamente in un processo in esecuzione. Lo script helper consente anche di usare le funzioni di registrazione fornite nell'API per R MLflow per registrare modelli, parametri, tag e artefatti generali.

  1. Creare il file, azureml_utils.R, con questo codice:

    # 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. Avviare lo script R con la riga seguente:

source("azureml_utils.R")

Leggere i file di dati come file locali

Quando si esegue uno script R come processo, Azure Machine Learning accetta i dati specificati nell'invio del processo e lo monta nel contenitore in esecuzione. Sarà quindi possibile leggere i file di dati come se fossero file locali nel contenitore in esecuzione.

  • Assicurarsi che i dati di origine siano registrati come asset di dati
  • Passare l'asset di dati in base al nome nei parametri di invio del processo
  • Leggete i file come si farebbe normalmente con un file locale

Definire il parametro di input come illustrato nella sezione parametri. Usare il parametro, data-file, per specificare un intero percorso, in modo da poter usare read_csv(args$data_file) per leggere l'asset di dati.

Salvare gli artefatti del processo (immagini, dati e così via)

Importante

Questa sezione non è applicabile ai modelli. Per le istruzioni di salvataggio e registrazione specifiche del modello, vedere le due sezioni seguenti.

È possibile archiviare output di script arbitrari, ad esempio file di dati, immagini, oggetti R serializzati e così via, generati dallo script R in Azure Machine Learning. Creare una directory ./outputs per archiviare tutti gli artefatti generati (immagini, modelli, dati e così via) Tutti i file salvati in ./outputs verranno inclusi automaticamente nell'esecuzione e caricati nell'esperimento alla fine dell'esecuzione. Poiché è stato aggiunto un valore predefinito per il parametro --output nella sezione parametri di input, includere il frammento di codice seguente nello script R per creare la directory output.

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

Dopo aver creato la directory, salvare gli artefatti in tale directory. Ad esempio:

# 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 i modelli con il pacchetto di carrier

La documentazione dell'API per R MLflow specifica che i modelli R devono essere del crate versione del modello.

  • Se lo script R esegue il training di un modello e si produce un oggetto modello, è necessario che crate sia in grado di distribuirlo in un secondo momento con Azure Machine Learning.
  • Quando si usa la funzione crate, usare spazi espliciti tra i nomi quando si chiama una funzione del pacchetto necessaria.

Si supponga di avere un oggetto modello serie temporale denominato my_ts_model creato con il pacchetto fable. Per rendere questo modello richiamabile quando viene distribuito, creare un crate in cui si passerà l'oggetto modello e un orizzonte di previsione in numero di periodi:

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

L'oggetto crated_model è quello che verrà registrato.

Modelli di log, parametri, tag o altri artefatti con l'API per R MLflow

Oltre a salvare tutti gli artefatti generati, è anche possibile registrare modelli, tag e parametri per ogni esecuzione. Per farlo, usare l'API per R MLflow.

Quando si registra un modello, si registra il modello creato come descritto nella sezione precedente.

Nota

Quando si registra un modello, il modello viene salvato e aggiunto anche agli artefatti di esecuzione. Non è necessario salvare in modo esplicito un modello a meno che non sia stato registrato.

Per registrare un modello e/o un parametro:

  1. Avviare l'esecuzione con mlflow_start_run()
  2. Elementi dei log con mlflow_log_model, mlflow_log_paramo mlflow_log_batch
  3. Non terminare l'esecuzione con mlflow_end_run(). Ignorare questa chiamata, poiché attualmente causa un errore.

Ad esempio, per registrare l'oggetto crated_model creato nella sezione precedente, è necessario includere il codice seguente nello script R:

Suggerimento

Usare models come valore per artifact_path durante la registrazione di un modello, questa è una procedura consigliata, anche se è possibile denominarla in altro modo.

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

Struttura di script ed esempio

Usare questi frammenti di codice come guida per strutturare lo script R, seguendo tutte le modifiche descritte in questo articolo.

# 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

Crea un ambiente

Per eseguire lo script R, si userà l'estensione ml per l'interfaccia della riga di comando di Azure, detta anche interfaccia della riga di comando v2. Il comando ml usa un file di definizioni di processo YAML. Per altre informazioni sull'invio di processi con az ml, vedere Eseguire il training dei modelli con l'interfaccia della riga di comando di Azure Machine Learning.

Il file di processo YAML specifica un ambiente. È necessario creare questo ambiente nell'area di lavoro prima di poter eseguire il processo.

È possibile creare l'ambiente in studio di Azure Machine Learning o con l'interfaccia della riga di comando di Azure.

Indipendentemente dal metodo usato, si userà un Dockerfile. Per lavorare in Azure Machine Learning, tutti i file di contesto Docker per gli ambienti R devono avere la specifica seguente:

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'immagine di base è rocker/tidyverse:latest, che include molti pacchetti R e le relative dipendenze già installate.

Importante

È necessario installare tutti i pacchetti R che lo script dovrà essere eseguito in anticipo. Aggiungere altre righe al file di contesto Docker in base alle esigenze.

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

Suggerimenti aggiuntivi

È possibile prendere in considerazione alcuni suggerimenti aggiuntivi:

  • Usare la funzione tryCatch di R per la gestione delle eccezioni e degli errori
  • Aggiungere la registrazione esplicita per la risoluzione dei problemi e il debug

Passaggi successivi