Construcción de modelos independientes de algoritmos con Mlflow | por Mena Wang, PhD | agosto de 2024

¿Suena interesante? Si es así, este artículo está aquí para ayudarte a empezar. mlflow.pyfunc. 🥂

  • En primer lugar, veamos un ejemplo sencillo de creación de un juguete. mlflow.pyfunc clase.
  • Luego, definiremos una mlflow.pyfunc Clase que encapsula una secuencia de aprendizaje automático (un estimador más cierta lógica de preprocesamiento, por ejemplo). También entrenaremos, registraremos y cargaremos esta secuencia de aprendizaje automático para inferencia.
  • Por último, profundicemos en el encapsulado. mlflow.pyfunc objeto, explore los ricos metadatos y artefactos rastreados automáticamente para nosotros por mlflowy comprender mejor todo el poder que mlflow.pyfunc ofertas.

🔗 Todo el código y la configuración están disponibles en GitHub. 🧰

Primero, vamos a crear un juguete sencillo. mlflow.pyfunc modelo y luego usarlo con el flujo de trabajo mlflow.

  • Paso 1: Crear el modelo
  • Paso 2: Registrar el modelo
  • Paso 3: Cargue el modelo registrado para realizar la inferencia
# Step 1: Create a mlflow.pyfunc model
class ToyModel(mlflow.pyfunc.PythonModel):
"""
ToyModel is a simple example implementation of an MLflow Python model.
"""

def predict(self, context, model_input):
"""
A basic predict function that takes a model_input list and returns a new list
where each element is increased by one.

Parameters:
- context (Any): An optional context parameter provided by MLflow.
- model_input (list of int or float): A list of numerical values that the model will use for prediction.

Returns:
- list of int or float: A list with each element in model_input is increased by one.
"""
return [x + 1 for x in model_input]

Como puede ver en el ejemplo anterior, puede crear un mlflow.pyfunc modelo para implementar cualquier función de Python personalizada que considere adecuada para su solución de aprendizaje automático, que no tiene por qué ser un algoritmo de aprendizaje automático estándar.

Luego puede registrar este modelo y cargarlo más tarde para realizar la inferencia.

# Step 2: log this model as an mlflow run
with mlflow.start_run():
mlflow.pyfunc.log_model(
artifact_path = "model",
python_model=ToyModel()
)
run_id = mlflow.active_run().info.run_id
# Step 3: load the logged model to perform inference
model = mlflow.pyfunc.load_model(f"runs:/{run_id}/model")
# dummy new data
x_new = [1,2,3]
# model inference for the new data
print(model.predict(x_new))
[2, 3, 4]

Ahora, vamos a crear una canalización de ML que encapsule un estimador con lógica personalizada adicional.

En el ejemplo siguiente, el XGB_PIPELINE La clase es un contenedor que integra el estimador con pasos de preprocesamiento, lo que puede ser deseable para algunas implementaciones de MLOps. mlflow.pyfuncEste contenedor es independiente del estimador y ofrece una representación uniforme del modelo. En concreto,

  • fit():En lugar de utilizar la API nativa de XGBoost (xgboost.train()), esta clase utiliza .fit()que se adhiere a las convenciones de sklearn, lo que permite una integración sencilla en las canalizaciones de sklearn y garantiza la coherencia entre diferentes estimadores.
  • DMatrix(): DMatrix es una estructura de datos central en XGBoost que optimiza los datos para el entrenamiento y la predicción. En esta clase, se explica el paso para transformar un DataFrame de pandas en un DMatrix está envuelto dentro de la clase, lo que permite una integración perfecta con pandas DataFrames como todos los demás estimadores de sklearn.
  • predict() :Este es el mlflow.pyfunc API de inferencia universal del modelo. Es coherente con esta canalización de ML, con el modelo de juguete anterior y con cualquier algoritmo de aprendizaje automático o lógica personalizada que incluyamos en un mlflow.pyfunc modelo.
import json
import xgboost as xgb
import mlflow.pyfunc
from typing import Any, Dict, Union
import pandas as pd

class XGB_PIPELINE(mlflow.pyfunc.PythonModel):
"""
XGBWithPreprocess is an example implementation of an MLflow Python model with XGBoost.
"""

def __init__(self, params: Dict[str, Union[str, int, float]]):
"""
Initialize the model with given parameters.

Parameters:
- params (Dict[str, Union[str, int, float]]): Parameters for the XGBoost model.
"""
self.params = params
self.xgb_model = None
self.config = None

def preprocess_input(self, model_input: pd.DataFrame) -> pd.DataFrame:
"""
Preprocess the input data.

Parameters:
- model_input (pd.DataFrame): The input data to preprocess.

Returns:
- pd.DataFrame: The preprocessed input data.
"""
processed_input = model_input.copy()
# put any desired preprocessing logic here
processed_input.drop(processed_input.columns[0], axis=1, inplace=True)

return processed_input

def fit(self, X_train: pd.DataFrame, y_train: pd.Series):
"""
Train the XGBoost model.

Parameters:
- X_train (pd.DataFrame): The training input data.
- y_train (pd.Series): The target values.
"""
processed_model_input = self.preprocess_input(X_train.copy())
dtrain = xgb.DMatrix(processed_model_input, label=y_train)
self.xgb_model = xgb.train(self.params, dtrain)

def predict(self, context: Any, model_input: pd.DataFrame) -> Any:
"""
Predict using the trained XGBoost model.

Parameters:
- context (Any): An optional context parameter provided by MLflow.
- model_input (pd.DataFrame): The input data for making predictions.

Returns:
- Any: The prediction results.
"""
processed_model_input = self.preprocess_input(model_input.copy())
dmatrix = xgb.DMatrix(processed_model_input)
return self.xgb_model.predict(dmatrix)

Ahora, entrenemos y registremos este modelo.

from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
import pandas as pd

# Generate synthetic datasets for demo
X, y = make_regression(n_samples=1000, n_features=10, noise=0.1, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# train and log the model
with mlflow.start_run(run_name = 'xgb_demo') as run:

# Create an instance of XGB_PIPELINE
params = {
'objective': 'reg:squarederror',
'max_depth': 3,
'learning_rate': 0.1,
}
model = XGB_PIPELINE(params)

# Fit the model
model.fit(X_train=pd.DataFrame(X_train), y_train=y_train)

# Log the model
model_info = mlflow.pyfunc.log_model(
artifact_path = 'model',
python_model = model,
)

run_id = mlflow.active_run().info.run_id

El modelo se ha registrado correctamente. ✌️Ahora, carguémoslo para realizar inferencias.

loaded_model = mlflow.pyfunc.load_model(model_uri=model_info.model_uri)
loaded_model.predict(pd.DataFrame(X_test))
array([ 4.11692047e+00,  7.30551958e+00, -2.36042137e+01, -1.31888123e+02,
...

El proceso anterior es bastante sencillo, ¿no? Esto representa la funcionalidad básica del mlflow.pyfunc objeto. Ahora, profundicemos más para explorar todo el poder que tiene mlflow.pyfunc tiene para ofrecer.

1. información del modelo

En el ejemplo anterior, el model_info objeto devuelto por mlflow.pyfunc.log_model() es un ejemplo de mlflow.models.model.ModelInfo clase. Contiene metadatos e información sobre el modelo registrado. Por ejemplo

Captura de pantalla que muestra algunos de los atributos del objeto model_info
Algunos atributos del objeto model_info

Siéntete libre de correr dir(model_info) Para explorar más o consultar El código fuente para todos los atributos definidos. El atributo que más utilizo es model_urique indica dónde se puede encontrar el modelo registrado dentro del mlflow sistema de seguimiento.

2. modelo_cargado

Vale la pena aclarar que la loaded_model no es una instancia de la XGB_PIPELINE clase, sino más bien un objeto contenedor proporcionado por mlflow.pyfunc para realizar inferencias independientes del algoritmo. Como se muestra a continuación, se devolverá un error si intenta recuperar los atributos de la XGB_PIPELINE clase de la loaded_model.

print(loaded_model.params)
AttributeError: 'PyFuncModel' object has no attribute 'params'

3. modelo_desenvuelto

Muy bien, te preguntarás, ¿dónde está entonces la instancia entrenada de XGB_PIPELINE¿Se registra y se puede recuperar a través de? mlflow¿también?

No te preocupes, lo guardas de forma segura y lo puedes desenvolver fácilmente, como se muestra a continuación.

unwrapped_model = loaded_model.unwrap_python_model()
print(unwrapped_model.params)
{'objective': 'reg:squarederror', 'max_depth': 3, 'learning_rate': 0.1}

Así se hace. 😎 Con el unwrapped_model¡Puedes acceder a cualquier propiedad o método de tu canalización de ML personalizada de esta manera! A veces agrego métodos útiles como explain_model o post_processing en la canalización personalizada, o incluir atributos útiles para rastrear el proceso de entrenamiento del modelo y ofrecer diagnósticos 🤩… Bueno, será mejor que me detenga aquí y deje eso para los siguientes artículos. Basta con decir que puede personalizar libremente su canalización de ML para su caso de uso y saber que

  1. Tendrá acceso a todos estos métodos y atributos personalizados para su uso posterior y
  2. Este modelo personalizado hecho a medida se envolverá dentro del uniforme. mlflow.pyfunc API de inferencia y, por lo tanto, disfruta de una migración fluida a otros estimadores si es necesario.

4. Context

Quizás hayas notado que hay una context parámetro para el predict métodos en ambos mlflow.pyfunc Clase definida anteriormente. Pero, curiosamente, este parámetro no es necesario cuando hacemos predicciones con el modelo cargado. ¿Por qué?

loaded_model = mlflow.pyfunc.load_model(model_uri)
# the context parameter is not needed when calling `predict`
loaded_model.predict(model_input)

Esto se debe a que loaded_model Arriba hay un objeto contenedor proporcionado por mlflowSi usamos el modelo sin envolver, debemos definir el contexto explícitamente, como se muestra a continuación; de lo contrario, el código devolverá un error.

unwrapped_model = loaded_model.unwrap_python_model()
# need to provide context mannually
unwrapped_model.predict(context=None, model_input)

Entonces, ¿qué es esto? context? ¿Y qué papel juega en la predict ¿método?

El context es un PythonModelContext objeto que contiene artefactospyfunc El modelo puede utilizarlo al realizar inferencias. Lo crea de forma implícita y automática el log_method() método.

Navegar hasta el mlruns subcarpeta en el repositorio de su proyecto, que se crea automáticamente por mlflow Cuando inicias sesión mlflow modelo. Busque la carpeta que lleva el nombre del modelo. run_idEn el interior, encontrará los artefactos del modelo registrados automáticamente, como se muestra a continuación.

# get run_id of a loaded model
print(loaded_model.metadata.run_id)
38a617d0f30645e8ae95eea4642a03c2
Captura de pantalla de la carpeta de artefactos en un modelo `mlflow.pyfunc` registrado
Carpeta de artefactos en un modelo `mlflow.pyfunc` registrado

Bastante ordenado, ¿no? 😁 Siéntete libre de explorar estos artefactos a tu gusto; a continuación se muestran las capturas de pantalla de los requirements y MLmodel archivo de la carpeta FYR.

El requiarements A continuación se especifican las versiones de las dependencias necesarias para recrear el entorno para ejecutar el modelo.

captura de pantalla del contenido del archivo `requirements.txt` en la carpeta
El archivo `requirements.txt` en la carpeta de artefactos

El MLmodel El documento a continuación define los metadatos y la configuración necesarios para cargar y servir el modelo en formato YAML.

Captura de pantalla del contenido del archivo MLModel en la carpeta de artefactos
El archivo `MLmodel` en la carpeta de artefactos