在 Fabric 中执行超参数优化(预览版)

超参数优化是一个查找过程,用于查找影响其性能的机器学习模型参数的最佳值。 它可能具有挑战性并且很耗时,尤其是在处理复杂模型和大型数据集时。 在本文中,我们将介绍如何在 Fabric 中执行超参数优化。

在本教程中,我们将使用加利福尼亚州房屋数据集,其中包含有关加州不同人口普查区中位数和其他特征的信息。 准备好数据后,我们将训练 SynapseML LightGBM 模型,以基于特征预测房屋值。 接下来,我们将使用 FLAML(一个快速的轻量级 AutoML 库)来查找 LightGBM 模型的最佳超参数。 最后,我们将优化模型的结果与使用默认参数的基线模型进行比较。

重要

此功能目前为预览版

先决条件

  • 创建新的 Fabric 环境,或确保你在 Fabric 运行时 1.2(Spark 3.4(或更高版本)和 Delta 2.4)上运行
  • 创建新的笔记本
  • 将笔记本附加到湖屋。 在笔记本左侧,选择“添加”以添加现有的湖屋或创建新湖屋。

准备训练和测试数据集

我们在本部分中将准备 LightGBM 模型的训练和测试数据集。 我们使用 Sklearn 提供的加利福尼亚州房屋数据集。 我们从数据中创建 Spark 数据帧,并使用 VectorAssembler 将特征合并到单个向量列中。

from sklearn.datasets import fetch_california_housing
from pyspark.sql import SparkSession

# Load the Scikit-learn California Housing dataset
sklearn_dataset = fetch_california_housing()

# Convert the Scikit-learn dataset to a Pandas DataFrame
import pandas as pd
pandas_df = pd.DataFrame(sklearn_dataset.data, columns=sklearn_dataset.feature_names)
pandas_df['target'] = sklearn_dataset.target

# Create a Spark DataFrame from the Pandas DataFrame
spark_df = spark.createDataFrame(pandas_df)

# Display the data
display(spark_df)

然后,我们将数据随机拆分为三个子集:训练、验证和测试,分别拆分为数据的 85%、12.75% 和 2.25%。 我们将训练集和验证集用于超参数优化,并使用测试集进行模型评测。

from pyspark.ml.feature import VectorAssembler

# Combine features into a single vector column
featurizer = VectorAssembler(inputCols=sklearn_dataset.feature_names, outputCol="features")
data = featurizer.transform(spark_df)["target", "features"]

# Split the data into training, validation, and test sets
train_data, test_data = data.randomSplit([0.85, 0.15], seed=41)
train_data_sub, val_data_sub = train_data.randomSplit([0.85, 0.15], seed=41)

设置 ML 试验

配置 MLflow

在执行超参数优化之前,我们需要定义一个训练函数,该函数可以获取不同的超参数值,并通过训练数据训练 LightGBM 模型。 我们还需要使用 R2 分数在验证数据上评测模型性能,以衡量模型与数据的拟合程度。

为此,我们首先导入必要的模块并设置 MLflow 试验。 MLflow 是用于管理端到端机器学习生命周期的开源平台。 它可以帮助我们跟踪和比较不同模型和超参数的结果。

# Import MLflow and set up the experiment name
import mlflow

mlflow.set_experiment("flaml_tune_sample")

# Enable automatic logging of parameters, metrics, and models
mlflow.autolog(exclusive=False)

设置日志记录级别

在这里,我们将日志记录级别配置为禁止 Synapse.ml 库的不必要输出,从而使日志更加简洁。

import logging
 
logging.getLogger('synapse.ml').setLevel(logging.ERROR)

训练基线模型

接下来,我们定义将四个超参数作为输入的训练函数:alpha、learningRate、numLeaves 和 numIterations。 这些是我们稍后要使用 FLAML 进行优化的超参数。

训练函数还采用两个数据帧作为输入:train_data 和 val_data,它们分别是训练数据集和验证数据集。 训练函数将返回两个输出:经过训练的模型和验证数据的 R2 分数。

# Import LightGBM and RegressionEvaluator
from synapse.ml.lightgbm import LightGBMRegressor
from pyspark.ml.evaluation import RegressionEvaluator

def train(alpha, learningRate, numLeaves, numIterations, train_data=train_data_sub, val_data=val_data_sub):
    """
    This train() function:
     - takes hyperparameters as inputs (for tuning later)
     - returns the R2 score on the validation dataset

    Wrapping code as a function makes it easier to reuse the code later for tuning.
    """
    with mlflow.start_run() as run:

        # Capture run_id for prediction later
        run_details = run.info.run_id

        # Create a LightGBM regressor with the given hyperparameters and target column
        lgr = LightGBMRegressor(
            objective="quantile",
            alpha=alpha,
            learningRate=learningRate,
            numLeaves=numLeaves,
            labelCol="target",
            numIterations=numIterations,
            dataTransferMode="bulk"
        )

        # Train the model on the training data
        model = lgr.fit(train_data)

        # Make predictions on the validation data
        predictions = model.transform(val_data)
        # Define an evaluator with R2 metric and target column
        evaluator = RegressionEvaluator(predictionCol="prediction", labelCol="target", metricName="r2")
        # Compute the R2 score on the validation data
        eval_metric = evaluator.evaluate(predictions)

        mlflow.log_metric("r2_score", eval_metric)

    # Return the model and the R2 score
    return model, eval_metric, run_details

最后,我们使用训练函数通过超参数的默认值来训练基线模型。 我们还在测试数据上评测基线模型并打印 R2 分数。

# Train the baseline model with the default hyperparameters
init_model, init_eval_metric, init_run_id = train(alpha=0.2, learningRate=0.3, numLeaves=31, numIterations=100, train_data=train_data, val_data=test_data)
# Print the R2 score of the baseline model on the test data
print("R2 of initial model on test dataset is: ", init_eval_metric)

使用 FLAML 执行超参数优化

FLAML 是一个快速的轻量级 AutoML 库,可自动查找给定模型和数据集的最佳超参数。 它使用低成本搜索策略来适应评测指标的反馈。 在本部分中,我们将使用 FLAML 优化我们在上一部分中定义的 LightGBM 模型的超参数。

定义优化函数

要使用 FLAML,我们需要定义一个优化函数,该函数采用配置字典作为输入,并将评测指标作为键且将指标值作为值返回字典。

配置字典包含我们想要优化的超参数及其值。 优化函数将使用我们之前定义的训练函数来通过给定配置训练和评测模型。

# Import FLAML
import flaml

# Define the tune function
def flaml_tune(config):
    # Train and evaluate the model with the given config
    _, metric, run_id = train(**config)
    # Return the evaluation metric and its value
    return {"r2": metric}

定义搜索空间

接下来,我们需要为想要优化的超参数定义搜索空间。 搜索空间是一个字典,用于将超参数名称映射到要探索的值的范围。 FLAML 提供一些方便的函数来定义不同类型的范围,例如 uniform、loguniform、and randint。

在这种情况下,我们希望优化以下四个超参数:alpha、learningRate、numLeaves 和 numIterations。

# Define the search space
params = {
    # Alpha is a continuous value between 0 and 1
    "alpha": flaml.tune.uniform(0, 1),
    # Learning rate is a continuous value between 0.001 and 1
    "learningRate": flaml.tune.uniform(0.001, 1),
    # Number of leaves is an integer value between 30 and 100
    "numLeaves": flaml.tune.randint(30, 100),
    # Number of iterations is an integer value between 100 and 300
    "numIterations": flaml.tune.randint(100, 300),
}

定义超参数试用版

最后,我们需要定义一个超参数试用版,该试用版将使用 FLAML 来优化超参数。 我们需要将优化函数、搜索空间、时间预算、样本数、指标名称、模式和详细程度传递给 flaml.tune.run 函数。 我们还需要启动嵌套 MLflow 运行来跟踪试用版的结果。

flaml.tune.run function 将返回包含最佳配置和最佳指标值的分析对象。

# Start a nested MLflow run
with mlflow.start_run(nested=True, run_name="Child Run: "):
    # Run the hyperparameter trial with FLAML
    analysis = flaml.tune.run(
        # Pass the tune function
        flaml_tune,
        # Pass the search space
        params,
        # Set the time budget to 120 seconds
        time_budget_s=120,
        # Set the number of samples to 100
        num_samples=100,
        # Set the metric name to r2
        metric="r2",
        # Set the mode to max (we want to maximize the r2 score)
        mode="max",
        # Set the verbosity level to 5
        verbose=5,
        )

试用完成后,我们可以从分析对象中查看最佳配置和最佳指标值。

# Get the best config from the analysis object
flaml_config = analysis.best_config
# Print the best config
print("Best config: ", flaml_config)
print("Best score on validation data: ", analysis.best_result["r2"])

比较结果

使用 FLAML 找到最佳超参数后,我们需要评测它们是如何提高模型性能的。 为此,我们使用训练函数在完整的训练数据集上创建具有最佳超参数的新模型。 然后,我们使用测试数据集同时计算新模型和基线模型的 R2 分数。

# Train a new model with the best hyperparameters 
flaml_model, flaml_metric, flaml_run_id = train(train_data=train_data, val_data=test_data, **flaml_config)

# Print the R2 score of the baseline model on the test dataset
print("On the test dataset, the initial (untuned) model achieved R^2: ", init_eval_metric)
# Print the R2 score of the new model on the test dataset
print("On the test dataset, the final flaml (tuned) model achieved R^2: ", flaml_metric)

保存最终模型

完成超参数试验后,我们现在可以在 Fabric 中将最终优化后的模型另存为 ML 模型。

# Specify the model name and the path where you want to save it in the registry
model_name = "housing_model"  # Replace with your desired model name
model_path = f"runs:/{flaml_run_id}/model"

# Register the model to the MLflow registry
registered_model = mlflow.register_model(model_uri=model_path, name=model_name)

# Print the registered model's name and version
print(f"Model '{registered_model.name}' version {registered_model.version} registered successfully.")