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 理赔管理。要下载数据集
请按照此处的说明操作:设置 Kaggle API
运行以下命令下载数据
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 存储库中的其他示例。