Adaptación del script de R para que se ejecute en producción

En este artículo se explica cómo tomar un script de R existente y realizar los cambios adecuados para ejecutarlo como un trabajo en Azure Machine Learning.

Tendrá que sacar el máximo partido, si no todo, de los cambios descritos en detalle en este artículo.

Eliminación de la interacción con el usuario

El script de R se debe diseñar para ejecutarse desatendido y se ejecutará a través del comando Rscript dentro del contenedor. Asegúrese de quitar las entradas o salidas interactivas del script.

Adición de análisis

Si el script necesita cualquier tipo de parámetro de entrada (la mayoría de los scripts lo hacen), pase las entradas al script mediante la llamada Rscript.

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

En el script de R, analice las entradas y realice las conversiones de tipos adecuadas. Se recomienda usar el paquete optparse.

En el siguiente fragmento se muestra cómo hacerlo:

  • iniciar el analizador
  • agregar todas las entradas como opciones
  • analizar las entradas con los tipos de datos adecuados

También puede agregar valores predeterminados, que son útiles para las pruebas. Se recomienda agregar un parámetro --output con un valor predeterminado de ./outputs para que se almacene cualquier salida del script.

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 es una lista con nombre. Puede usar cualquiera de estos parámetros más adelante en el script.

Obtención del script auxiliar azureml_utils.R

Debe obtener un script auxiliar denominado script azureml_utils.R en el mismo directorio de trabajo del script de R que se ejecutará. El script auxiliar es necesario para que el script de R en ejecución pueda comunicarse con el servidor de MLflow. El script auxiliar proporciona un método para recuperar continuamente el token de autenticación, ya que el token cambia rápidamente en un trabajo en ejecución. El script auxiliar también permite usar las funciones de registro proporcionadas en R MLflow API para registrar modelos, parámetros, etiquetas y artefactos generales.

  1. Cree el archivo, azureml_utils.R, con este código:

    # 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. Inicie el script de R con la siguiente línea:

source("azureml_utils.R")

Lectura de archivos de datos como archivos locales

Al ejecutar un script de R como un trabajo, Azure Machine Learning toma los datos especificados en el envío del trabajo y los monta en el contenedor en ejecución. Por tanto, podrá leer los archivos de datos como si fueran archivos locales en el contenedor en ejecución.

  • Asegúrese de que los datos de origen están registrados como un recurso de datos.
  • Pase el recurso de datos por nombre en los parámetros de envío de trabajos
  • Lea los archivos como normalmente leería un archivo local

Defina el parámetro de entrada como se muestra en la sección de parámetros. Use el parámetro, data-file, para especificar una ruta de acceso completa, de modo que pueda usar read_csv(args$data_file) para leer el recurso de datos.

Guardado de artefactos de trabajo (imágenes, datos, etc.)

Importante

Esta sección no se aplica a modelos. Vea las dos secciones siguientes para obtener instrucciones de registro y guardado específicas de los modelos.

Puede almacenar salidas de script arbitrarias, como archivos de datos, imágenes, objetos de R serializados, etc. que genera el script de R en Azure Machine Learning. Cree un directorio ./outputs para almacenar los artefactos generados (imágenes, modelos, datos, etc.) Los archivos guardados en ./outputs se incluirán automáticamente en la ejecución y se cargarán en el experimento al final de la ejecución. Como ha agregado un valor predeterminado para el parámetro --output en la sección de parámetros de entrada, incluya el siguiente fragmento de código en el script de R para crear el directorio output.

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

Después de crear el directorio, guarde los artefactos en ese directorio. Por ejemplo:

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

Uso de crate en los modelos con el paquete carrier

En la documentación de R MLflow API se especifica que los modelos de R deben ser del cratetipo de modelo.

  • Si el script de R entrena un modelo y genera un objeto de modelo, deberá usar crate para poder implementarlo más adelante con Azure Machine Learning.
  • Al usar la función crate, use espacios de nombres explícitos al llamar a cualquier función de paquete que necesite.

Imagine que tiene un objeto de modelo timeseries llamado my_ts_model creado con el paquete fable. Para que se pueda llamar a este modelo cuando se implemente, cree una función crate, donde pasará el objeto de modelo y un horizonte de previsión en número de períodos:

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

El objeto crated_model es el que registrará.

Registro de modelos, parámetros, etiquetas u otros artefactos con R MLflow API

Además de guardar los artefactos generados, también puede registrar modelos, etiquetas y parámetros para cada ejecución. Use R MLflow API para hacerlo.

Al registrar un modelo, registra el modelo que ha creado como se describe en la sección anterior.

Nota

Al registrar un modelo, también se guarda y se agrega a los artefactos de ejecución. No es necesario guardar explícitamente un modelo a menos que no lo haya registrado.

Para registrar un modelo o parámetro:

  1. Inicie la ejecución con mlflow_start_run()
  2. Registre artefactos con mlflow_log_model, mlflow_log_param o mlflow_log_batch
  3. No finalice la ejecución con mlflow_end_run(). Omita esta llamada, ya que actualmente produce un error.

Por ejemplo, para registrar el objeto crated_model tal y como se ha creado en la sección anterior, incluya el código siguiente en el script de R:

Sugerencia

Use models como valor para artifact_path al registrar un modelo, se trata de un procedimiento recomendado (aunque puede llamarlo de otra forma).

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

Ejemplo y estructura de scripts

Use estos fragmentos de código como guía para estructurar el script de R siguiendo todos los cambios descritos en este artículo.

# 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

Creación de un entorno

Para ejecutar el script de R, usará la extensión ml para la CLI de Azure, también denominada CLI v2. El comando ml usa un archivo de definiciones de trabajo YAML. Para más información sobre cómo enviar trabajos con az ml, vea Entrenamiento de modelos con la CLI de Azure Machine Learning.

El archivo de trabajo YAML especifica un entorno. Deberá crear este entorno en el área de trabajo para poder ejecutar el trabajo.

Puede crear el entorno en Estudio de Azure Machine Learning o con la CLI de Azure.

Sea cual sea el método que use, usará un archivo Dockerfile. Todos los archivos de contexto de Docker para entornos de R deben tener la especificación siguiente para trabajar en 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/')"

La imagen base es rocker/tidyverse:latest, que tiene muchos paquetes de R y sus dependencias ya instaladas.

Importante

Debe instalar los paquetes de R que tenga que ejecutar de antemano el script. Agregue más líneas al archivo de contexto de Docker según sea necesario.

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

Sugerencias adicionales

Algunas sugerencias adicionales que puede tener en cuenta son las siguientes:

  • Use la función de R tryCatch para el control de excepciones y errores
  • Agregue un registro explícito para la solución de problemas y la depuración

Pasos siguientes