Optuna 和 RAPIDS HPO 入门#

超参数优化 (HPO) 自动化了为机器学习算法选择超参数值的过程,以提高模型性能。这有助于提高模型准确性,但可能资源密集,因为它可能需要为数百种超参数组合训练模型。让我们看看如何使用 Optuna 和 RAPIDS 来减少 HPO 所花费的时间。

RAPIDS#

RAPIDS 框架提供了一套库,可在 GPU 上完全执行端到端数据科学流水线。该框架中的一个库是 cuML,它使用与 scikit-learn 兼容的 API 和 GPU 加速后端来实现常见的机器学习模型。您可以在此处了解有关 RAPIDS 的更多信息。

Optuna#

Optuna 是一个用于自动超参数优化的轻量级框架。它提供了定义-运行 API,这使得它很容易适应我们已有的任何现有代码,并实现了高度模块化以及动态构建超参数空间的灵活性。通过简单地使用 Optuna 包装目标函数,我们可以对搜索空间执行并行分布式 HPO 搜索,这将在本笔记本中看到。

在本笔记本中,我们将使用 Kaggle 的 BNP Paribas Cardif 理赔管理数据集来预测索赔是否会获得加速审批。我们将探索如何将 Optuna 与 RAPIDS 结合 Dask 使用,以运行多 GPU HPO 实验,从而获得比 CPU 更快的结果。

## Run this cell to install optuna
#!pip install optuna optuna-integration
import cudf
import optuna
from cuml import LogisticRegression
from cuml.metrics import log_loss
from cuml.model_selection import train_test_split
from dask.distributed import Client, wait
from dask_cuda import LocalCUDACluster

设置 CUDA 集群#

我们启动一个本地集群,并使其准备好运行 Dask 的分布式任务。Dask 调度程序可以帮助利用集群上可用的多个节点。

LocalCUDACluster 为当前系统中的每个 GPU 启动一个 Dask 工作节点。它是 RAPIDS 项目的一部分。了解更多

# This will use all GPUs on the local host by default
cluster = LocalCUDACluster(threads_per_worker=1, ip="", dashboard_address="8081")
c = Client(cluster)

# Query the client for all connected workers
workers = c.has_what().keys()
n_workers = len(workers)
c
[I 2024-08-06 09:41:38,254] A new study created in memory with name: dask_optuna_lr_log_loss_tpe

加载数据#

数据获取#

数据集可从 Kaggle 获取:BNP Paribas Cardif 理赔管理。要下载数据集

  1. 请按照此处的说明操作:设置 Kaggle API

  2. 运行以下命令下载数据

mkdir -p ./data

kaggle competitions download \
  -c bnp-paribas-cardif-claims-management \
  --path ./data

unzip \
  -d ./data \
  ./data/bnp-paribas-cardif-claims-management.zip

这是一个匿名数据集,包含 BNP Paribas Cardif 收到的索赔的分类和数值。训练集中的“target”列是要预测的变量。对于适合加速审批的索赔,它等于 1。任务是预测索赔是否适合加速审批。我们将只使用 train.csv.zip 文件,因为 test.csv.zip 没有 target 列。

import os

file_name = "train.csv.zip"

data_dir = "data/"
INPUT_FILE = os.path.join(data_dir, file_name)

选择 N_TRIALS 作为 HPO 试验的运行次数。

N_TRIALS = 150

df = cudf.read_csv(INPUT_FILE)

# Drop ID column
df = df.drop("ID", axis=1)

# Drop non-numerical data and fill NaNs before passing to cuML RF
CAT_COLS = list(df.select_dtypes("object").columns)
df = df.drop(CAT_COLS, axis=1)
df = df.fillna(0)

df = df.astype("float32")
X, y = df.drop(["target"], axis=1), df["target"].astype("int32")

study_name = "dask_optuna_lr_log_loss_tpe"

训练和评估#

train_and_eval 函数接受不同的参数进行尝试。此函数应看起来与任何 ML 工作流程非常相似。我们将在 Optuna objective 函数中使用此函数,以展示我们可以多么轻松地将现有工作流程融入 Optuna 工作。

def train_and_eval(
    X_param, y_param, penalty="l2", C=1.0, l1_ratio=None, fit_intercept=True
):
    """
    Splits the given data into train and test split to train and evaluate the model
    for the params parameters.

    Params
    ______

    X_param:  DataFrame.
              The data to use for training and testing.
    y_param:  Series.
              The label for training
    penalty, C, l1_ratio, fit_intercept: The parameter values for Logistic Regression.

    Returns
    score: log loss of the fitted model
    """
    X_train, X_valid, y_train, y_valid = train_test_split(
        X_param, y_param, random_state=42
    )
    classifier = LogisticRegression(
        penalty=penalty,
        C=C,
        l1_ratio=l1_ratio,
        fit_intercept=fit_intercept,
        max_iter=10000,
    )
    classifier.fit(X_train, y_train)
    y_pred = classifier.predict(X_valid)
    score = log_loss(y_valid, y_pred)
    return score

为了获得基线数字,让我们看看模型的默认性能如何。

print("Score with default parameters : ", train_and_eval(X, y))
[W] [09:34:11.132560] L-BFGS line search failed (code 3); stopping at the last valid step
Score with default parameters :  8.24908383066997

目标函数#

我们将使用Optuna Study优化目标函数。目标函数尝试我们正在调整的参数的指定值,并返回使用这些参数获得的分数。这些结果将聚合在 study.trials_dataframes() 中。

让我们通过利用 train_and_eval() 来定义此 HPO 任务的目标函数。您可以看到,我们只需选择参数的值并调用 train_and_eval 方法,这使得 Optuna 在现有工作流程中非常易于使用。

切换到不同的采样器时,目标函数无需更改,采样器是 Optuna 中内置的选项,可选择 Optuna 提供的不同采样算法。可用的一些包括 - GridSampler、RandomSampler、TPESampler 等。我们将在此演示中使用 TPESampler,但请随意尝试不同的采样器以注意性能的变化。

树结构帕尔森估计器(Tree-Structured Parzen Estimators)或 TPE 的工作原理是在每次试验期间拟合两个高斯混合模型 - 一个用于与最佳目标值相关的参数值集,另一个用于剩余的参数值。它选择使两个 GMM 之间的比率最大化的参数值

def objective(trial, X_param, y_param):
    C = trial.suggest_float("C", 0.01, 100.0, log=True)
    penalty = trial.suggest_categorical("penalty", ["none", "l1", "l2"])
    fit_intercept = trial.suggest_categorical("fit_intercept", [True, False])

    score = train_and_eval(
        X_param, y_param, penalty=penalty, C=C, fit_intercept=fit_intercept
    )
    return score

HPO 试验和研究#

Optuna 使用研究(studies)试验(trials)来跟踪 HPO 实验。简单来说,试验是目标函数的一次调用,而一组试验构成一项研究。我们将从研究中选择最佳观察到的试验,以获得在该次运行中使用的最佳参数。

在此,DaskStorage 类用于设置集群中所有工作节点共享的存储。在此处了解更多关于可使用的存储的信息:此处

optuna.create_study 用于设置研究。如您所见,它指定了研究名称、要使用的采样器、研究方向和存储。只需几行代码,我们就设置了一个分布式 HPO 实验。

storage = optuna.integration.DaskStorage()
study = optuna.create_study(
    sampler=optuna.samplers.TPESampler(seed=142),
    study_name=study_name,
    direction="minimize",
    storage=storage,
)

# Optimize in parallel on your Dask cluster
#
# Submit `n_workers` optimization tasks, where each task runs about 40 optimization trials
# for a total of about N_TRIALS trials in all
futures = [
    c.submit(
        study.optimize,
        lambda trial: objective(trial, X, y),
        n_trials=N_TRIALS // n_workers,
        pure=False,
    )
    for _ in range(n_workers)
]
wait(futures)
print(f"Best params: {study.best_params}")

print("Number of finished trials: ", len(study.trials))

您应该看到如下日志。

[I 2024-08-06 09:41:40,161] Trial 1 finished with value: 8.238207899472073 and parameters: {'C': 40.573838784392514, 'penalty': 'l2', 'fit_intercept': True}. Best is trial 1 with value: 8.238207899472073.
... 
[I 2024-08-06 09:41:58,423] Trial 143 finished with value: 8.210414278942531 and parameters: {'C': 0.3152731188939818, 'penalty': 'l1', 'fit_intercept': True}. Best is trial 52 with value: 8.205579602300705.

Best params: {'C': 1.486491072441749, 'penalty': 'l2', 'fit_intercept': True}
Number of finished trials:  144

可视化#

Optuna 通过内置图表提供了一种轻松可视化试验的方法。在此处阅读更多关于可视化的信息:此处

总结#

本笔记本展示了如何将 RAPIDS 和 Optuna 与 Dask 一起使用以运行多 GPU HPO 作业,并且可以作为任何想要开始使用该框架的人的起点。我们已经看到,只需添加几行代码,我们就能够集成这些库以进行多 GPU HPO 运行。这也可以扩展到多个节点。

下一步#

这是在小数据集上完成的,建议您在更大的数据上进行测试,并对参数进行更大范围的探索。这些实验可以提高性能。请参阅 rapidsai/cloud-ml-examples 存储库中的其他示例。

资源#

Python 中的超参数调优

超参数调优概述

如何使用 Optuna 让您的模型更出色