<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Re: AutoGluon MLflow integration in Machine Learning</title>
    <link>https://community.databricks.com/t5/machine-learning/autogluon-mlflow-integration/m-p/137062#M4396</link>
    <description>&lt;P class="qt3gz91 paragraph"&gt;Hi&amp;nbsp;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/114928"&gt;@cleversuresh&lt;/a&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="qt3gz91 paragraph"&gt;Thanks for sharing the code and the context. Here are the core issues I see and how to fix them so MLflow logging works reliably on Databricks.&lt;/P&gt;
&lt;H3 class="_7uu25p0 qt3gz9c _7pq7t612 heading3 _7uu25p1"&gt;What’s breaking MLflow logging in your code&lt;/H3&gt;
&lt;UL class="qt3gz97 qt3gz92"&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;Your &lt;STRONG&gt;PyFunc wrapper loads the AutoGluon model from a local path&lt;/STRONG&gt; rather than from the MLflow model’s packaged artifacts. In &lt;CODE class="qt3gz9f"&gt;PythonModel.load_context&lt;/CODE&gt;, you must read any files from &lt;CODE class="qt3gz9f"&gt;context.artifacts[...]&lt;/CODE&gt;. Otherwise, loading or serving the model will fail when that local path doesn’t exist in the target environment.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;The &lt;STRONG&gt;&lt;CODE class="qt3gz9f"&gt;input_example&lt;/CODE&gt; and signature inference are misaligned&lt;/STRONG&gt;. You pass &lt;CODE class="qt3gz9f"&gt;self.X_train[:2]&lt;/CODE&gt;, but &lt;CODE class="qt3gz9f"&gt;self.X_train&lt;/CODE&gt; is never defined; also &lt;CODE class="qt3gz9f"&gt;input_example&lt;/CODE&gt; must match the schema you infer with &lt;CODE class="qt3gz9f"&gt;infer_signature(model_input=..., model_output=...)&lt;/CODE&gt;. Use a small slice of &lt;CODE class="qt3gz9f"&gt;train_features&lt;/CODE&gt; (DataFrame with target dropped) for both signature and example.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;&lt;CODE class="qt3gz9f"&gt;classification_report&lt;/CODE&gt; arguments are incorrect&lt;/STRONG&gt;. It expects &lt;CODE class="qt3gz9f"&gt;y_true&lt;/CODE&gt; and &lt;CODE class="qt3gz9f"&gt;y_pred&lt;/CODE&gt; (discrete labels), but you pass &lt;CODE class="qt3gz9f"&gt;X&lt;/CODE&gt; as &lt;CODE class="qt3gz9f"&gt;y_true&lt;/CODE&gt; and rounded probabilities as &lt;CODE class="qt3gz9f"&gt;y_pred&lt;/CODE&gt;. Pass &lt;CODE class="qt3gz9f"&gt;self.val_data[self.target_col]&lt;/CODE&gt; and &lt;CODE class="qt3gz9f"&gt;(self.val_predictions &amp;gt; 0.5).astype(int)&lt;/CODE&gt; (or a tuned threshold) instead.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;&lt;CODE class="qt3gz9f"&gt;brier_score_loss&lt;/CODE&gt; expects probabilities, not thresholded predictions&lt;/STRONG&gt;. Use the raw positive-class probabilities &lt;CODE class="qt3gz9f"&gt;y_pred_proba&lt;/CODE&gt; (shape &lt;CODE class="qt3gz9f"&gt;(n_samples,)&lt;/CODE&gt;) for Brier, not &lt;CODE class="qt3gz9f"&gt;(y_pred &amp;gt; 0.5)&lt;/CODE&gt;. If you need 0–1 range, set &lt;CODE class="qt3gz9f"&gt;scale_by_half=True&lt;/CODE&gt; (binary default is usually auto).&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;&lt;CODE class="qt3gz9f"&gt;evaluate_model&lt;/CODE&gt; uses undefined attributes (&lt;CODE class="qt3gz9f"&gt;self.X_train&lt;/CODE&gt;, &lt;CODE class="qt3gz9f"&gt;self.y_true&lt;/CODE&gt;)&lt;/STRONG&gt;. Use your stored train/validation splits and compute AUC with &lt;CODE class="qt3gz9f"&gt;roc_auc_score(y_true, y_score)&lt;/CODE&gt; where &lt;CODE class="qt3gz9f"&gt;y_score&lt;/CODE&gt; are positive-class probabilities.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;The &lt;STRONG&gt;AutoGluon &lt;CODE class="qt3gz9f"&gt;path&lt;/CODE&gt; pointing to &lt;CODE class="qt3gz9f"&gt;/Shared/...&lt;/CODE&gt;&lt;/STRONG&gt; is a workspace path, not a filesystem location. Use a real local/temp directory (for example via &lt;CODE class="qt3gz9f"&gt;tempfile.mkdtemp()&lt;/CODE&gt;), then package it into MLflow model artifacts with &lt;CODE class="qt3gz9f"&gt;artifacts={"ag_predictor": &amp;lt;local_dir&amp;gt;}&lt;/CODE&gt; and load with &lt;CODE class="qt3gz9f"&gt;context.artifacts[...]&lt;/CODE&gt; in your PyFunc.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;Make sure to &lt;STRONG&gt;set the MLflow experiment to a workspace path&lt;/STRONG&gt; (like &lt;CODE class="qt3gz9f"&gt;/Shared/...&lt;/CODE&gt;), which is supported on Databricks; if you want artifacts stored in UC Volumes, create the experiment with a UC volume artifact location.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;Finally, ensure &lt;STRONG&gt;runtime dependencies&lt;/STRONG&gt; (AutoGluon + its model backends, e.g., LightGBM, XGBoost, CatBoost) are present when loading/serving the model. Use &lt;CODE class="qt3gz9f"&gt;conda_env&lt;/CODE&gt; or &lt;CODE class="qt3gz9f"&gt;extra_pip_requirements&lt;/CODE&gt; in &lt;CODE class="qt3gz9f"&gt;mlflow.pyfunc.log_model&lt;/CODE&gt; so MLflow reproduces the environment cleanly.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;H1&gt;Here are some code patches:&lt;/H1&gt;
&lt;H4&gt;1) Fix PyFunc wrapper to read from packages artifacts:&lt;/H4&gt;
&lt;LI-CODE lang="python"&gt;import mlflow
import pandas as pd
from mlflow.pyfunc import PythonModel
from autogluon.tabular import TabularPredictor

class AutoGluonPyFuncWrapper(PythonModel):
    """Wrapper for AutoGluon model to be logged as a PyFunc model in MLflow."""

    def __init__(self):
        self.predictor = None

    def load_context(self, context):
        # Load the predictor directory that was logged as an artifact
        predictor_dir = context.artifacts["ag_predictor"]
        self.predictor = TabularPredictor.load(predictor_dir)

    def predict(self, context, model_input):
        # Accept dict/list; convert to DataFrame
        if not isinstance(model_input, pd.DataFrame):
            model_input = pd.DataFrame(model_input)

        # Probability of the positive class
        proba_df = self.predictor.predict_proba(model_input)

        # Choose positive label robustly (prefer 1 if present)
        class_labels = list(proba_df.columns)
        pos_label = 1 if 1 in class_labels else class_labels[-1]
        return proba_df[pos_label]  # Pandas Series of positive-class probabilities&lt;/LI-CODE&gt;
&lt;H4&gt;2) Log AutoGluon predictor directory as an MLflow artifact and align the signature&lt;/H4&gt;
&lt;LI-CODE lang="python"&gt;import tempfile
import mlflow
from mlflow.models.signature import infer_signature

# Choose a real local directory for AutoGluon training output
local_model_dir = tempfile.mkdtemp(prefix="ag_predictor_")

with mlflow.start_run() as run:
    # Train AutoGluon
    self.predictor = TabularPredictor(
        problem_type="binary",
        label=self.target_col,
        eval_metric="roc_auc",
        path=local_model_dir
    ).fit(
        self.train_data,
        excluded_model_types=["KNN", "RF"],
        hyperparameters=hyperparameters,
        presets="best_quality",
        num_bag_folds=3,
        num_stack_levels=1,
        time_limit=time_limit,
        verbosity=1,
        num_cpus=4,
        num_gpus=0,
        ag_args_fit={"num_cpus": 1, "num_gpus": 0}
    )

    # Compute train/val probabilities for metrics
    train_X = self.train_data.drop(columns=[self.target_col])
    val_X = self.val_data.drop(columns=[self.target_col])
    self.train_predictions = self.predictor.predict_proba(train_X).iloc[:, -1]
    self.val_predictions = self.predictor.predict_proba(val_X).iloc[:, -1]

    # Metrics (see patch 3 below)
    self.compute_metrics(self.train_data[self.target_col], self.train_predictions, "train")
    self.compute_metrics(self.val_data[self.target_col], self.val_predictions, "validation")

    # Signature and input_example must match the wrapper’s input/output
    input_example = train_X.head(2)
    signature = infer_signature(model_input=input_example, model_output=self.train_predictions.head(2))

    # Log PyFunc model and the trained predictor directory as artifact
    mlflow.pyfunc.log_model(
        artifact_path="model",
        python_model=AutoGluonPyFuncWrapper(),
        artifacts={"ag_predictor": local_model_dir},
        signature=signature,
        input_example=input_example,
        # Strongly recommended: pin pip requirements to include AutoGluon &amp;amp; backends
        extra_pip_requirements=[
            "mlflow&amp;gt;=2.8.0",  # adjust to your workspace runtime
            "autogluon.tabular&amp;gt;=1.1.0",  # pin your version
            "xgboost&amp;gt;=1.7.0",
            "lightgbm&amp;gt;=3.3.5",
            "catboost&amp;gt;=1.2"
        ],
    )

    self.run_id = run.info.run_id&lt;/LI-CODE&gt;
&lt;H4&gt;3) Correct your metric logging&lt;/H4&gt;
&lt;LI-CODE lang="python"&gt;from sklearn.metrics import (
    roc_auc_score,
    average_precision_score,
    f1_score,
    fbeta_score,
    brier_score_loss,
    recall_score,
    precision_score,
    classification_report
)

def compute_metrics(self, y_true, y_pred_proba, prefix):
    # y_pred_proba: probabilities of positive class
    y_pred_bin = (y_pred_proba &amp;gt; 0.5).astype(int)

    metrics = {
        f"{prefix}_auc": roc_auc_score(y_true, y_pred_proba),
        f"{prefix}_average_precision": average_precision_score(y_true, y_pred_proba),
        f"{prefix}_f1_score": f1_score(y_true, y_pred_bin),
        f"{prefix}_f2_score": fbeta_score(y_true, y_pred_bin, beta=2.0),
        f"{prefix}_brier_score": brier_score_loss(y_true, y_pred_proba),
        f"{prefix}_recall": recall_score(y_true, y_pred_bin),
        f"{prefix}_precision": precision_score(y_true, y_pred_bin),
    }
    for k, v in metrics.items():
        mlflow.log_metric(k, float(v))
    return metrics

def log_classification_report(self):
    # Use validation set labels and thresholded predictions
    y_true = self.val_data[self.target_col]
    y_pred_bin = (self.val_predictions &amp;gt; 0.5).astype(int)
    report = classification_report(y_true, y_pred_bin, output_dict=True)
    mlflow.log_dict(report, "classification_report.json")&lt;/LI-CODE&gt;
&lt;H4&gt;4) Fix evaluate_model to use your stored splits&lt;/H4&gt;
&lt;LI-CODE lang="python"&gt;def evaluate_model(self):
    # Use the validation set probabilities already computed
    auc_score = roc_auc_score(self.val_data[self.target_col], self.val_predictions)
    print(f"Model AUC (validation): {auc_score:.4f}")
    return auc_score&lt;/LI-CODE&gt;
&lt;H3 class="_7uu25p0 qt3gz9c _7pq7t612 heading3 _7uu25p1"&gt;A couple of Databricks-specific practices to keep this robust&lt;/H3&gt;
&lt;UL class="qt3gz97 qt3gz92"&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;Set the workspace experiment path once&lt;/STRONG&gt; (recommended):&lt;BR /&gt;&lt;CODE class="qt3gz9f"&gt;mlflow.set_experiment(f"/Shared/automl_experiments/{self.experiment_name}")&lt;/CODE&gt;. If you want to store artifacts in UC Volumes, create the experiment with an artifact location at a UC Volume path first, then set it active by path.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;Package all runtime deps&lt;/STRONG&gt; with the model (pip/conda), especially AutoGluon and its tree learners. You can use &lt;CODE class="qt3gz9f"&gt;extra_pip_requirements&lt;/CODE&gt; (shown above) or supply a &lt;CODE class="qt3gz9f"&gt;conda_env&lt;/CODE&gt; dict if you prefer hard pinning Python and Conda channels.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;Always load files via &lt;CODE class="qt3gz9f"&gt;context.artifacts[...]&lt;/CODE&gt; in &lt;CODE class="qt3gz9f"&gt;load_context&lt;/CODE&gt;&lt;/STRONG&gt;. MLflow will download artifacts next to the model and pass you local paths at runtime; don’t assume workspace or DBFS paths exist when the model is rehydrated.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;Align &lt;CODE class="qt3gz9f"&gt;input_example&lt;/CODE&gt; with your signature&lt;/STRONG&gt; and wrapper input type (DataFrame rows of features). Signature/&lt;CODE class="qt3gz9f"&gt;input_example&lt;/CODE&gt; improves handoff, validation, and serving.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;</description>
    <pubDate>Fri, 31 Oct 2025 15:57:11 GMT</pubDate>
    <dc:creator>stbjelcevic</dc:creator>
    <dc:date>2025-10-31T15:57:11Z</dc:date>
    <item>
      <title>AutoGluon MLflow integration</title>
      <link>https://community.databricks.com/t5/machine-learning/autogluon-mlflow-integration/m-p/111423#M3977</link>
      <description>&lt;P&gt;I am working on a personalized price package recommendation and implemented an AutoGluon code integrating it with MLflow.&lt;/P&gt;&lt;P&gt;The code has been created in a modular fashion to be used by other team members. They just need to pass the data, target column and experiment name to create the experiment.&lt;/P&gt;&lt;P&gt;I always face some problems when logging the model with MLflow, any help would be greatly appreciated.&lt;/P&gt;&lt;P&gt;This is my code:&lt;/P&gt;&lt;P&gt;class AutoGluonPyFuncWrapper(mlflow.pyfunc.PythonModel):&lt;BR /&gt;"""Wrapper for AutoGluon model to be logged as a PyFunc model in MLflow."""&lt;BR /&gt;&lt;BR /&gt;def __init__(self, model_path):&lt;BR /&gt;self.model_path = model_path&lt;BR /&gt;self.predictor = None # Model will be loaded in predict method&lt;/P&gt;&lt;P&gt;def load_context(self, context):&lt;BR /&gt;"""Loads the AutoGluon model when MLflow loads the PyFunc model."""&lt;BR /&gt;self.predictor = TabularPredictor.load(self.model_path)&lt;/P&gt;&lt;P&gt;def predict(self, context, model_input):&lt;BR /&gt;"""&lt;BR /&gt;Predict probability scores for the given input.&lt;BR /&gt;&lt;BR /&gt;model_input: Pandas DataFrame&lt;BR /&gt;Returns: Pandas DataFrame with probability scores&lt;BR /&gt;"""&lt;BR /&gt;if isinstance(model_input, pd.DataFrame):&lt;BR /&gt;predictions = self.predictor.predict_proba(model_input)&lt;BR /&gt;else:&lt;BR /&gt;predictions = self.predictor.predict_proba(pd.DataFrame(model_input))&lt;/P&gt;&lt;P&gt;# Get the class label for positive class dynamically&lt;BR /&gt;positive_class = predictions.columns[-1] # Last column is usually the positive class&lt;BR /&gt;return predictions[[positive_class]] # Return only probability of positive class&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;class AutoGluonMLflowClassifier:&lt;BR /&gt;def __init__(self, model_data: pd.DataFrame, target_col: str, experiment_name: str):&lt;BR /&gt;"""&lt;BR /&gt;Initializes the classifier with Databricks table name, target column, and MLflow experiment name.&lt;BR /&gt;"""&lt;BR /&gt;self.model_data = model_data&lt;BR /&gt;self.target_col = target_col&lt;BR /&gt;self.experiment_name = experiment_name&lt;BR /&gt;self.predictor = None&lt;BR /&gt;self.train_predictions = None&lt;BR /&gt;self.val_predictions = None&lt;BR /&gt;self._initialize_mlflow()&lt;/P&gt;&lt;P&gt;def _initialize_mlflow(self):&lt;BR /&gt;"""Sets up MLflow experiment dynamically in Databricks."""&lt;BR /&gt;&lt;BR /&gt;# Define experiment path (store it in the user's workspace)&lt;BR /&gt;experiment_path = f"/Shared/automl_experiments/{self.experiment_name}"&lt;/P&gt;&lt;P&gt;# Check if the experiment already exists&lt;BR /&gt;experiment = mlflow.get_experiment_by_name(experiment_path)&lt;/P&gt;&lt;P&gt;if experiment is None:&lt;BR /&gt;# Create a new experiment if it does not exist&lt;BR /&gt;experiment_id = mlflow.create_experiment(experiment_path)&lt;BR /&gt;print(f"Created new MLflow experiment at: {experiment_path}")&lt;BR /&gt;else:&lt;BR /&gt;experiment_id = experiment.experiment_id&lt;BR /&gt;print(f"Using existing MLflow experiment: {experiment_path}")&lt;/P&gt;&lt;P&gt;# Set the experiment to use&lt;BR /&gt;mlflow.set_experiment(experiment_path)&lt;BR /&gt;&lt;BR /&gt;def split_data(self):&lt;BR /&gt;&lt;BR /&gt;self.train_data, self.val_data = train_test_split(self.model_data, test_size=0.2, random_state=42)&lt;BR /&gt;print(self.train_data.columns)&lt;BR /&gt;def train_model(self, time_limit: int = 200):&lt;BR /&gt;"""Trains AutoGluon model and logs parameters, metrics, and artifacts in MLflow."""&lt;BR /&gt;hyperparameters = {&lt;BR /&gt;"GBM": { # LightGBM&lt;BR /&gt;"num_boost_round": 1000, # More boosting rounds&lt;BR /&gt;"learning_rate": 0.02, # Lower learning rate for better generalization&lt;BR /&gt;"num_leaves": 31, # Leaf complexity&lt;BR /&gt;"feature_fraction": 0.8, # Feature bagging&lt;BR /&gt;"bagging_fraction": 0.8, # Sample bagging&lt;BR /&gt;"bagging_freq": 5, # Frequency of bagging&lt;BR /&gt;"min_data_in_leaf": 20, # Minimum samples per leaf&lt;BR /&gt;},&lt;BR /&gt;"XGB": { # XGBoost&lt;BR /&gt;"n_estimators": 1000,&lt;BR /&gt;"learning_rate": 0.02,&lt;BR /&gt;"max_depth": 6, # Controls complexity&lt;BR /&gt;"subsample": 0.8, # Sample fraction per tree&lt;BR /&gt;"colsample_bytree": 0.8, # Feature bagging&lt;BR /&gt;"gamma": 0.2, # Regularization&lt;BR /&gt;"lambda": 1, # L2 regularization&lt;BR /&gt;},&lt;BR /&gt;"CAT": { # CatBoost&lt;BR /&gt;"iterations": 1000,&lt;BR /&gt;"learning_rate": 0.02,&lt;BR /&gt;"depth": 6,&lt;BR /&gt;"l2_leaf_reg": 3, # L2 regularization&lt;BR /&gt;"border_count": 32, # Number of bins for numeric features&lt;BR /&gt;},&lt;BR /&gt;"NN_TORCH": { # Neural Network (PyTorch)&lt;BR /&gt;"num_epochs": 100, # Increase training epochs&lt;BR /&gt;"learning_rate": 0.001,&lt;BR /&gt;"dropout_prob": 0.1, # Dropout regularization&lt;BR /&gt;"weight_decay": 1e-5, # L2 weight regularization&lt;BR /&gt;"hidden_size": 256, # Hidden layer size&lt;BR /&gt;}&lt;BR /&gt;}&lt;BR /&gt;&lt;BR /&gt;dbfs_model_path = "dbfs:/FileStore/automl/autogluon/"&lt;BR /&gt;local_model_path = "/Shared/automl_experiments/autogluon_model/"&lt;/P&gt;&lt;P&gt;with mlflow.start_run() as run:&lt;BR /&gt;# Training AutoGluon model with AUC as the evaluation metric&lt;BR /&gt;self.predictor = TabularPredictor(problem_type = "binary",&lt;BR /&gt;label = self.target_col,&lt;BR /&gt;eval_metric = "roc_auc",&lt;BR /&gt;path = local_model_path) \&lt;BR /&gt;.fit(self.train_data,&lt;BR /&gt;excluded_model_types = ["KNN", "RF"],&lt;BR /&gt;hyperparameters = hyperparameters,&lt;BR /&gt;presets = "best_quality",&lt;BR /&gt;num_bag_folds = 3,&lt;BR /&gt;num_stack_levels = 1,&lt;BR /&gt;time_limit = time_limit,&lt;BR /&gt;verbosity = 1, # Reduce logs&lt;BR /&gt;num_cpus = 4, # Limit CPU usage&lt;BR /&gt;num_gpus = 0,&lt;BR /&gt;ag_args_fit = {"num_cpus": 1, "num_gpus": 0} # Ensure sequential training&lt;BR /&gt;)&lt;BR /&gt;&lt;BR /&gt;print(f"Model saved at: {local_model_path}")&lt;BR /&gt;dbutils.fs.rm(dbfs_model_path, recurse=True)&lt;BR /&gt;dbutils.fs.cp(f"file:{local_model_path}", dbfs_model_path, recurse=True)&lt;BR /&gt;&lt;BR /&gt;# log dataset size&lt;BR /&gt;mlflow.log_params({"trainning_data_size": self.train_data.shape[0],&lt;BR /&gt;"validation_data_size": self.val_data.shape[0]})&lt;/P&gt;&lt;P&gt;# Making predictions on training and validation datasets&lt;BR /&gt;self.train_predictions = self.predictor.predict_proba(self.train_data.drop(columns = [self.target_col])).iloc[:, -1] # Get probabilities for positive class&lt;BR /&gt;self.val_predictions = self.predictor.predict_proba(self.val_data.drop(columns = [self.target_col])).iloc[:, -1] # Get probabilities for positive class&lt;BR /&gt;print("Training predictions:", self.train_predictions)&lt;/P&gt;&lt;P&gt;# Compute and log both training and validation metrics&lt;BR /&gt;self.compute_metrics(self.train_data[self.target_col], self.train_predictions, "train")&lt;BR /&gt;self.compute_metrics(self.val_data[self.target_col], self.val_predictions, "validation")&lt;BR /&gt;&lt;BR /&gt;print("Logging model to MLflow...")&lt;BR /&gt;# generate the model signature&lt;BR /&gt;signature = infer_signature(model_input = self.train_data.drop(columns = [self.target_col]),&lt;BR /&gt;model_output = self.train_predictions)&lt;/P&gt;&lt;P&gt;model_wrapper = AutoGluonPyFuncWrapper(local_model_path)&lt;BR /&gt;artifacts = {"predictor_path": dbfs_model_path}&lt;BR /&gt;mlflow.pyfunc.log_model(artifact_path = "model",&lt;BR /&gt;python_model = model_wrapper,&lt;BR /&gt;input_example = self.X_train[:2],&lt;BR /&gt;signature = signature,&lt;BR /&gt;artifacts = artifacts)&lt;BR /&gt;&lt;BR /&gt;self.run_id = run.info.run_id # Store run ID&lt;BR /&gt;print(f"Model logged successfully. Run ID: {self.run_id}")&lt;BR /&gt;&lt;BR /&gt;# Calculating classification report&lt;BR /&gt;report = classification_report(self.val_data.drop(columns = [self.target_col]), self.val_predictions.round(), output_dict=True)&lt;BR /&gt;mlflow.log_dict(report, "classification_report.json")&lt;BR /&gt;&lt;BR /&gt;# Define metric calculation function&lt;BR /&gt;def compute_metrics(self, y_true, y_pred, prefix):&lt;BR /&gt;"""Computes and logs metrics with a specified prefix (train/validation)."""&lt;BR /&gt;metrics = {&lt;BR /&gt;f"{prefix}_auc": roc_auc_score(y_true, y_pred),&lt;BR /&gt;f"{prefix}_average_precision": average_precision_score(y_true, y_pred),&lt;BR /&gt;f"{prefix}_f1_score": f1_score(y_true, y_pred &amp;gt; 0.5),&lt;BR /&gt;f"{prefix}_f2_score": fbeta_score(y_true, y_pred &amp;gt; 0.5, beta=2.0),&lt;BR /&gt;f"{prefix}_brier_score": brier_score_loss(y_true, y_pred &amp;gt; 0.5),&lt;BR /&gt;f"{prefix}_recall": recall_score(y_true, y_pred &amp;gt; 0.5),&lt;BR /&gt;f"{prefix}_precision": precision_score(y_true, y_pred &amp;gt; 0.5),&lt;BR /&gt;}&lt;BR /&gt;for metric_name, value in metrics.items():&lt;BR /&gt;mlflow.log_metric(metric_name, value)&lt;BR /&gt;return metrics&lt;BR /&gt;&lt;BR /&gt;def evaluate_model(self):&lt;BR /&gt;"""Evaluate the model using AUC metric."""&lt;BR /&gt;y_pred_proba = self.predictor.predict_proba(self.X_train).iloc[:, -1]&lt;BR /&gt;auc_score = roc_auc_score(self.y_true, y_pred_proba)&lt;BR /&gt;print(f"Model AUC: {auc_score:.4f}")&lt;BR /&gt;return auc_score&lt;/P&gt;&lt;P&gt;def run_pipeline(self):&lt;BR /&gt;"""Complete pipeline: data generation, training, evaluation, logging, and loading."""&lt;BR /&gt;self.split_data()&lt;BR /&gt;&lt;BR /&gt;self.train_model()&lt;BR /&gt;auc_score = self.evaluate_model()&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;from ucimlrepo import fetch_ucirepo&lt;BR /&gt;&lt;BR /&gt;# fetch dataset&lt;BR /&gt;adult = fetch_ucirepo(id=2)&lt;BR /&gt;&lt;BR /&gt;# data (as pandas dataframes)&lt;BR /&gt;X = adult.data.features&lt;BR /&gt;y = adult.data.targets&lt;/P&gt;&lt;P&gt;data = X.copy()&lt;BR /&gt;data['income'] = y['income']&lt;BR /&gt;data.head()&lt;BR /&gt;data['income'] = data['income'].replace({'&amp;lt;=50K.': '&amp;lt;=50K', '&amp;gt;50K.': '&amp;gt;50K'})&lt;BR /&gt;data['income'] = data['income'].replace({'&amp;lt;=50K': 0, '&amp;gt;50K': 1})&lt;BR /&gt;data['income'].value_counts()&lt;/P&gt;&lt;P&gt;# Example Usage:&lt;BR /&gt;classifier = AutoGluonMLflowClassifier(model_data = data,&lt;BR /&gt;target_col = "income",&lt;BR /&gt;experiment_name = "autogluon_sample_experiment")&lt;BR /&gt;classifier.run_pipeline()&lt;/P&gt;</description>
      <pubDate>Fri, 28 Feb 2025 04:42:37 GMT</pubDate>
      <guid>https://community.databricks.com/t5/machine-learning/autogluon-mlflow-integration/m-p/111423#M3977</guid>
      <dc:creator>cleversuresh</dc:creator>
      <dc:date>2025-02-28T04:42:37Z</dc:date>
    </item>
    <item>
      <title>Re: AutoGluon MLflow integration</title>
      <link>https://community.databricks.com/t5/machine-learning/autogluon-mlflow-integration/m-p/137062#M4396</link>
      <description>&lt;P class="qt3gz91 paragraph"&gt;Hi&amp;nbsp;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/114928"&gt;@cleversuresh&lt;/a&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="qt3gz91 paragraph"&gt;Thanks for sharing the code and the context. Here are the core issues I see and how to fix them so MLflow logging works reliably on Databricks.&lt;/P&gt;
&lt;H3 class="_7uu25p0 qt3gz9c _7pq7t612 heading3 _7uu25p1"&gt;What’s breaking MLflow logging in your code&lt;/H3&gt;
&lt;UL class="qt3gz97 qt3gz92"&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;Your &lt;STRONG&gt;PyFunc wrapper loads the AutoGluon model from a local path&lt;/STRONG&gt; rather than from the MLflow model’s packaged artifacts. In &lt;CODE class="qt3gz9f"&gt;PythonModel.load_context&lt;/CODE&gt;, you must read any files from &lt;CODE class="qt3gz9f"&gt;context.artifacts[...]&lt;/CODE&gt;. Otherwise, loading or serving the model will fail when that local path doesn’t exist in the target environment.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;The &lt;STRONG&gt;&lt;CODE class="qt3gz9f"&gt;input_example&lt;/CODE&gt; and signature inference are misaligned&lt;/STRONG&gt;. You pass &lt;CODE class="qt3gz9f"&gt;self.X_train[:2]&lt;/CODE&gt;, but &lt;CODE class="qt3gz9f"&gt;self.X_train&lt;/CODE&gt; is never defined; also &lt;CODE class="qt3gz9f"&gt;input_example&lt;/CODE&gt; must match the schema you infer with &lt;CODE class="qt3gz9f"&gt;infer_signature(model_input=..., model_output=...)&lt;/CODE&gt;. Use a small slice of &lt;CODE class="qt3gz9f"&gt;train_features&lt;/CODE&gt; (DataFrame with target dropped) for both signature and example.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;&lt;CODE class="qt3gz9f"&gt;classification_report&lt;/CODE&gt; arguments are incorrect&lt;/STRONG&gt;. It expects &lt;CODE class="qt3gz9f"&gt;y_true&lt;/CODE&gt; and &lt;CODE class="qt3gz9f"&gt;y_pred&lt;/CODE&gt; (discrete labels), but you pass &lt;CODE class="qt3gz9f"&gt;X&lt;/CODE&gt; as &lt;CODE class="qt3gz9f"&gt;y_true&lt;/CODE&gt; and rounded probabilities as &lt;CODE class="qt3gz9f"&gt;y_pred&lt;/CODE&gt;. Pass &lt;CODE class="qt3gz9f"&gt;self.val_data[self.target_col]&lt;/CODE&gt; and &lt;CODE class="qt3gz9f"&gt;(self.val_predictions &amp;gt; 0.5).astype(int)&lt;/CODE&gt; (or a tuned threshold) instead.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;&lt;CODE class="qt3gz9f"&gt;brier_score_loss&lt;/CODE&gt; expects probabilities, not thresholded predictions&lt;/STRONG&gt;. Use the raw positive-class probabilities &lt;CODE class="qt3gz9f"&gt;y_pred_proba&lt;/CODE&gt; (shape &lt;CODE class="qt3gz9f"&gt;(n_samples,)&lt;/CODE&gt;) for Brier, not &lt;CODE class="qt3gz9f"&gt;(y_pred &amp;gt; 0.5)&lt;/CODE&gt;. If you need 0–1 range, set &lt;CODE class="qt3gz9f"&gt;scale_by_half=True&lt;/CODE&gt; (binary default is usually auto).&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;&lt;CODE class="qt3gz9f"&gt;evaluate_model&lt;/CODE&gt; uses undefined attributes (&lt;CODE class="qt3gz9f"&gt;self.X_train&lt;/CODE&gt;, &lt;CODE class="qt3gz9f"&gt;self.y_true&lt;/CODE&gt;)&lt;/STRONG&gt;. Use your stored train/validation splits and compute AUC with &lt;CODE class="qt3gz9f"&gt;roc_auc_score(y_true, y_score)&lt;/CODE&gt; where &lt;CODE class="qt3gz9f"&gt;y_score&lt;/CODE&gt; are positive-class probabilities.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;The &lt;STRONG&gt;AutoGluon &lt;CODE class="qt3gz9f"&gt;path&lt;/CODE&gt; pointing to &lt;CODE class="qt3gz9f"&gt;/Shared/...&lt;/CODE&gt;&lt;/STRONG&gt; is a workspace path, not a filesystem location. Use a real local/temp directory (for example via &lt;CODE class="qt3gz9f"&gt;tempfile.mkdtemp()&lt;/CODE&gt;), then package it into MLflow model artifacts with &lt;CODE class="qt3gz9f"&gt;artifacts={"ag_predictor": &amp;lt;local_dir&amp;gt;}&lt;/CODE&gt; and load with &lt;CODE class="qt3gz9f"&gt;context.artifacts[...]&lt;/CODE&gt; in your PyFunc.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;Make sure to &lt;STRONG&gt;set the MLflow experiment to a workspace path&lt;/STRONG&gt; (like &lt;CODE class="qt3gz9f"&gt;/Shared/...&lt;/CODE&gt;), which is supported on Databricks; if you want artifacts stored in UC Volumes, create the experiment with a UC volume artifact location.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;Finally, ensure &lt;STRONG&gt;runtime dependencies&lt;/STRONG&gt; (AutoGluon + its model backends, e.g., LightGBM, XGBoost, CatBoost) are present when loading/serving the model. Use &lt;CODE class="qt3gz9f"&gt;conda_env&lt;/CODE&gt; or &lt;CODE class="qt3gz9f"&gt;extra_pip_requirements&lt;/CODE&gt; in &lt;CODE class="qt3gz9f"&gt;mlflow.pyfunc.log_model&lt;/CODE&gt; so MLflow reproduces the environment cleanly.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;H1&gt;Here are some code patches:&lt;/H1&gt;
&lt;H4&gt;1) Fix PyFunc wrapper to read from packages artifacts:&lt;/H4&gt;
&lt;LI-CODE lang="python"&gt;import mlflow
import pandas as pd
from mlflow.pyfunc import PythonModel
from autogluon.tabular import TabularPredictor

class AutoGluonPyFuncWrapper(PythonModel):
    """Wrapper for AutoGluon model to be logged as a PyFunc model in MLflow."""

    def __init__(self):
        self.predictor = None

    def load_context(self, context):
        # Load the predictor directory that was logged as an artifact
        predictor_dir = context.artifacts["ag_predictor"]
        self.predictor = TabularPredictor.load(predictor_dir)

    def predict(self, context, model_input):
        # Accept dict/list; convert to DataFrame
        if not isinstance(model_input, pd.DataFrame):
            model_input = pd.DataFrame(model_input)

        # Probability of the positive class
        proba_df = self.predictor.predict_proba(model_input)

        # Choose positive label robustly (prefer 1 if present)
        class_labels = list(proba_df.columns)
        pos_label = 1 if 1 in class_labels else class_labels[-1]
        return proba_df[pos_label]  # Pandas Series of positive-class probabilities&lt;/LI-CODE&gt;
&lt;H4&gt;2) Log AutoGluon predictor directory as an MLflow artifact and align the signature&lt;/H4&gt;
&lt;LI-CODE lang="python"&gt;import tempfile
import mlflow
from mlflow.models.signature import infer_signature

# Choose a real local directory for AutoGluon training output
local_model_dir = tempfile.mkdtemp(prefix="ag_predictor_")

with mlflow.start_run() as run:
    # Train AutoGluon
    self.predictor = TabularPredictor(
        problem_type="binary",
        label=self.target_col,
        eval_metric="roc_auc",
        path=local_model_dir
    ).fit(
        self.train_data,
        excluded_model_types=["KNN", "RF"],
        hyperparameters=hyperparameters,
        presets="best_quality",
        num_bag_folds=3,
        num_stack_levels=1,
        time_limit=time_limit,
        verbosity=1,
        num_cpus=4,
        num_gpus=0,
        ag_args_fit={"num_cpus": 1, "num_gpus": 0}
    )

    # Compute train/val probabilities for metrics
    train_X = self.train_data.drop(columns=[self.target_col])
    val_X = self.val_data.drop(columns=[self.target_col])
    self.train_predictions = self.predictor.predict_proba(train_X).iloc[:, -1]
    self.val_predictions = self.predictor.predict_proba(val_X).iloc[:, -1]

    # Metrics (see patch 3 below)
    self.compute_metrics(self.train_data[self.target_col], self.train_predictions, "train")
    self.compute_metrics(self.val_data[self.target_col], self.val_predictions, "validation")

    # Signature and input_example must match the wrapper’s input/output
    input_example = train_X.head(2)
    signature = infer_signature(model_input=input_example, model_output=self.train_predictions.head(2))

    # Log PyFunc model and the trained predictor directory as artifact
    mlflow.pyfunc.log_model(
        artifact_path="model",
        python_model=AutoGluonPyFuncWrapper(),
        artifacts={"ag_predictor": local_model_dir},
        signature=signature,
        input_example=input_example,
        # Strongly recommended: pin pip requirements to include AutoGluon &amp;amp; backends
        extra_pip_requirements=[
            "mlflow&amp;gt;=2.8.0",  # adjust to your workspace runtime
            "autogluon.tabular&amp;gt;=1.1.0",  # pin your version
            "xgboost&amp;gt;=1.7.0",
            "lightgbm&amp;gt;=3.3.5",
            "catboost&amp;gt;=1.2"
        ],
    )

    self.run_id = run.info.run_id&lt;/LI-CODE&gt;
&lt;H4&gt;3) Correct your metric logging&lt;/H4&gt;
&lt;LI-CODE lang="python"&gt;from sklearn.metrics import (
    roc_auc_score,
    average_precision_score,
    f1_score,
    fbeta_score,
    brier_score_loss,
    recall_score,
    precision_score,
    classification_report
)

def compute_metrics(self, y_true, y_pred_proba, prefix):
    # y_pred_proba: probabilities of positive class
    y_pred_bin = (y_pred_proba &amp;gt; 0.5).astype(int)

    metrics = {
        f"{prefix}_auc": roc_auc_score(y_true, y_pred_proba),
        f"{prefix}_average_precision": average_precision_score(y_true, y_pred_proba),
        f"{prefix}_f1_score": f1_score(y_true, y_pred_bin),
        f"{prefix}_f2_score": fbeta_score(y_true, y_pred_bin, beta=2.0),
        f"{prefix}_brier_score": brier_score_loss(y_true, y_pred_proba),
        f"{prefix}_recall": recall_score(y_true, y_pred_bin),
        f"{prefix}_precision": precision_score(y_true, y_pred_bin),
    }
    for k, v in metrics.items():
        mlflow.log_metric(k, float(v))
    return metrics

def log_classification_report(self):
    # Use validation set labels and thresholded predictions
    y_true = self.val_data[self.target_col]
    y_pred_bin = (self.val_predictions &amp;gt; 0.5).astype(int)
    report = classification_report(y_true, y_pred_bin, output_dict=True)
    mlflow.log_dict(report, "classification_report.json")&lt;/LI-CODE&gt;
&lt;H4&gt;4) Fix evaluate_model to use your stored splits&lt;/H4&gt;
&lt;LI-CODE lang="python"&gt;def evaluate_model(self):
    # Use the validation set probabilities already computed
    auc_score = roc_auc_score(self.val_data[self.target_col], self.val_predictions)
    print(f"Model AUC (validation): {auc_score:.4f}")
    return auc_score&lt;/LI-CODE&gt;
&lt;H3 class="_7uu25p0 qt3gz9c _7pq7t612 heading3 _7uu25p1"&gt;A couple of Databricks-specific practices to keep this robust&lt;/H3&gt;
&lt;UL class="qt3gz97 qt3gz92"&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;Set the workspace experiment path once&lt;/STRONG&gt; (recommended):&lt;BR /&gt;&lt;CODE class="qt3gz9f"&gt;mlflow.set_experiment(f"/Shared/automl_experiments/{self.experiment_name}")&lt;/CODE&gt;. If you want to store artifacts in UC Volumes, create the experiment with an artifact location at a UC Volume path first, then set it active by path.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;Package all runtime deps&lt;/STRONG&gt; with the model (pip/conda), especially AutoGluon and its tree learners. You can use &lt;CODE class="qt3gz9f"&gt;extra_pip_requirements&lt;/CODE&gt; (shown above) or supply a &lt;CODE class="qt3gz9f"&gt;conda_env&lt;/CODE&gt; dict if you prefer hard pinning Python and Conda channels.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;Always load files via &lt;CODE class="qt3gz9f"&gt;context.artifacts[...]&lt;/CODE&gt; in &lt;CODE class="qt3gz9f"&gt;load_context&lt;/CODE&gt;&lt;/STRONG&gt;. MLflow will download artifacts next to the model and pass you local paths at runtime; don’t assume workspace or DBFS paths exist when the model is rehydrated.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI class="qt3gz9a"&gt;
&lt;P class="qt3gz91 paragraph"&gt;&lt;STRONG&gt;Align &lt;CODE class="qt3gz9f"&gt;input_example&lt;/CODE&gt; with your signature&lt;/STRONG&gt; and wrapper input type (DataFrame rows of features). Signature/&lt;CODE class="qt3gz9f"&gt;input_example&lt;/CODE&gt; improves handoff, validation, and serving.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;</description>
      <pubDate>Fri, 31 Oct 2025 15:57:11 GMT</pubDate>
      <guid>https://community.databricks.com/t5/machine-learning/autogluon-mlflow-integration/m-p/137062#M4396</guid>
      <dc:creator>stbjelcevic</dc:creator>
      <dc:date>2025-10-31T15:57:11Z</dc:date>
    </item>
  </channel>
</rss>

