实时笔记本

你可以在 live session Binder 中运行这个笔记本,或者在 Github 上查看它。

使用 Dask 进行超参数优化

每个机器学习模型在训练开始前都有一些指定的值。这些值有助于模型适应数据,但在任何训练数据被看到之前必须给出。例如,这可能是 Scikit-learn 的 LogisiticRegression 中的 penaltyC。这些在任何训练数据之前出现的值被称为“超参数”。典型用法如下所示:

from sklearn.linear_model import LogisiticRegression
from sklearn.datasets import make_classification

X, y = make_classification()
est = LogisiticRegression(C=10, penalty="l2")
est.fit(X, y)

这些超参数影响预测的质量。例如,如果在上面的例子中 C 太小,估计器的输出将不能很好地拟合数据。

确定这些超参数的值是困难的。事实上,Scikit-learn 有一个完整的文档页面关于如何找到最佳值:https://scikit-learn.org/stable/modules/grid_search.html

Dask 为超参数优化开启了新的技术和机会。其中一个机会涉及提前停止训练以限制计算。自然地,这需要某种方式来停止和重新启动训练(在 Scikit-learn 术语中称为 partial_fitwarm_start)。

这在搜索复杂且有许多搜索参数时特别有用。好的例子是大多数深度学习模型,它们有专门处理大量数据的算法,但在提供基本超参数(例如,“学习率”、“动量”或“权重衰减”)方面有困难。

本笔记本将逐步讲解

  • 设置一个现实的例子

  • 如何使用 HyperbandSearchCV,包括

    • 理解 HyperbandSearchCV 的输入参数

    • 运行超参数优化

    • 如何从 HyperbandSearchCV 访问信息

本笔记本将*不*展示一个性能比较来激励 HyperbandSearchCV 的使用。HyperbandSearchCV 以最少的训练找到高分;然而,这是一个关于如何*使用*它的教程。所有的性能比较都被归入到 了解更多 部分。

[ ]:
%matplotlib inline

设置 Dask

[ ]:
from distributed import Client
client = Client(processes=False, threads_per_worker=4,
                n_workers=1, memory_limit='2GB')
client

创建数据

[ ]:
from sklearn.datasets import make_circles
import numpy as np
import pandas as pd

X, y = make_circles(n_samples=30_000, random_state=0, noise=0.09)

pd.DataFrame({0: X[:, 0], 1: X[:, 1], "class": y}).sample(4_000).plot.scatter(
    x=0, y=1, alpha=0.2, c="class", cmap="bwr"
);

添加随机维度

[ ]:
from sklearn.utils import check_random_state

rng = check_random_state(42)
random_feats = rng.uniform(-1, 1, size=(X.shape[0], 4))
X = np.hstack((X, random_feats))
X.shape

分割和缩放数据

[ ]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=5_000, random_state=42)
[ ]:
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
scaler = StandardScaler().fit(X_train)

X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
[ ]:
from dask.utils import format_bytes

for name, X in [("train", X_train), ("test", X_test)]:
    print("dataset =", name)
    print("shape =", X.shape)
    print("bytes =", format_bytes(X.nbytes))
    print("-" * 20)

现在我们有了训练集和测试集。

创建模型和搜索空间

让我们使用 Scikit-learn 的 MLPClassifier 作为我们的模型(为了方便)。让我们使用这个模型,包含24个神经元,并调整一些其他基本超参数。

[ ]:
import numpy as np
from sklearn.neural_network import MLPClassifier

model = MLPClassifier()

深度学习库也可以使用。特别是,PyTorch 的 Scikit-Learn 包装器 SkorchHyperbandSearchCV 配合良好。

[ ]:
params = {
    "hidden_layer_sizes": [
        (24, ),
        (12, 12),
        (6, 6, 6, 6),
        (4, 4, 4, 4, 4, 4),
        (12, 6, 3, 3),
    ],
    "activation": ["relu", "logistic", "tanh"],
    "alpha": np.logspace(-6, -3, num=1000),  # cnts
    "batch_size": [16, 32, 64, 128, 256, 512],
}

超参数优化

HyperbandSearchCV 是 Dask-ML 的元估计器,用于寻找最佳超参数。它可以作为 RandomizedSearchCV 的替代方案,通过不浪费时间在不有希望的超参数上来在更短的时间内找到相似的超参数。具体来说,它几乎可以保证在最小训练量的情况下找到高性能的模型。

本节将重点介绍

  1. 理解 HyperbandSearchCV 的输入参数

  2. 使用 HyperbandSearchCV 来找到最佳的超参数

  3. 查看 HyperbandSearchCV 的其他用例

[ ]:
from dask_ml.model_selection import HyperbandSearchCV

确定输入参数

确定 HyperbandSearchCV 的输入参数的经验法则需要了解:

  1. 最长训练模型将看到的示例数量

  2. 要评估的超参数数量

让我们写下这个示例中这些应该是什么:

[ ]:
# For quick response
n_examples = 4 * len(X_train)
n_params = 8

# In practice, HyperbandSearchCV is most useful for longer searches
# n_examples = 15 * len(X_train)
# n_params = 15

在这其中,训练时间最长的模型将看到 n_examples 个示例。这是所需的数据量,通常由问题的难度设定。简单的问题可能只需要数据集的10次遍历;更复杂的问题可能需要数据集的100次遍历。

将会采样 n_params 个参数,因此将评估 n_params 个模型。得分低的模型在看到 n_examples 个示例之前将被终止。这有助于节省计算资源。

我们如何使用这些值来确定 HyperbandSearchCV 的输入?

[ ]:
max_iter = n_params  # number of times partial_fit will be called
chunks = n_examples // n_params  # number of examples each call sees

max_iter, chunks

这意味着最长的训练估计器将看到大约 n_examples 个样本(具体来说是 n_params * (n_examples // n_params)。

应用输入参数

让我们用这个块大小创建一个 Dask 数组:

[ ]:
import dask.array as da
X_train2 = da.from_array(X_train, chunks=chunks)
y_train2 = da.from_array(y_train, chunks=chunks)
X_train2

每次 partial_fit 调用将接收一个数据块。

这意味着每个块中的示例数量应该(大约)相同,并且 n_examplesn_params 应该被选择以实现这一点。(例如,有100个示例,目标是分块为 (33, 33, 34) 个示例,而不是 (48, 48, 4) 个示例)。

现在让我们使用 max_iter 来创建我们的 HyperbandSearchCV 对象:

[ ]:
search = HyperbandSearchCV(
    model,
    params,
    max_iter=max_iter,
    patience=True,
)

将执行多少计算?

目前尚不清楚如何从 max_iterchunks 确定计算量。幸运的是, HyperbandSearchCV 有一个 metadata 属性可以在之前确定这一点:

[ ]:
search.metadata["partial_fit_calls"]

这显示了在计算中将执行多少次 partial_fit 调用。metadata 还包括有关创建的模型数量的信息。

到目前为止,所有的工作都是为了准备搜索的计算(以及查看将执行多少计算)。到目前为止,所有的计算都快速且简单。

执行计算

现在,让我们进行模型选择搜索并找到最佳的超参数。这是本笔记本的核心部分。此计算将在Dask可用的所有硬件上进行。

[ ]:
%%time
search.fit(X_train2, y_train2, classes=[0, 1, 2, 3])

仪表盘将在运行时处于活动状态。它将显示哪些工作线程正在运行 partial_fitscore 调用。这大约需要10秒钟。

集成

HyperbandSearchCV 遵循 Scikit-learn API 并镜像 Scikit-learn 的 RandomizedSearchCV。这意味着它“即插即用”。所有 Scikit-learn 的属性和方法都可用:

[ ]:
search.best_score_
[ ]:
search.best_estimator_
[ ]:
cv_results = pd.DataFrame(search.cv_results_)
cv_results.head()
[ ]:
search.score(X_test, y_test)
[ ]:
search.predict(X_test)
[ ]:
search.predict(X_test).compute()

它还有一些其他属性。

[ ]:
hist = pd.DataFrame(search.history_)
hist.head()

这展示了每次 partial_fit 调用后的历史记录。还有一个属性 model_history_ 记录了每个模型的历史(它是 history_ 的重新组织)。

了解更多

本笔记本涵盖了 HyperbandSearchCV 的基本用法。以下文档和资源可能有助于进一步了解 HyperbandSearchCV,包括一些更精细的使用案例:

性能比较可以在 SciPy 2019 的演讲/论文中找到。