【问题标题】:sklearn: Hyperparameter tuning by gradient descent?sklearn:通过梯度下降进行超参数调整?
【发布时间】:2021-05-09 12:10:18
【问题描述】:

有没有办法通过梯度下降在 scikit-learn 中执行超参数调整?虽然超参数梯度的公式可能难以计算,但通过评估超参数空间中的两个接近点来计算超参数梯度的数值应该非常容易。是否有这种方法的现有实施?为什么这种方法是好主意?

【问题讨论】:

  • hyperparameter optimization 上有一篇维基百科文章讨论了评估超参数的各种方法。一节也讨论了梯度下降。文章底部是该任务的开源软件列表,其中大部分是python。

标签: python optimization parameters scikit-learn


【解决方案1】:

梯度的计算是最少的问题。至少在高级automatic differentiation 软件的时代。 (当然,对所有 sklearn 分类器以通用方式实现这一点并不容易)

虽然有些人的作品使用了这种想法,但他们只是针对一些特定且精心设计的问题(例如 SVM 调整)这样做。此外,可能还有很多假设,因为:

为什么这不是一个好主意

  • 超参数优化一般是:不平滑
    • GD 真的很喜欢平滑函数,因为零梯度没有帮助
    • (由某些离散集定义的每个超参数(例如,选择 l1 与 l2 惩罚)都会引入非光滑表面)
  • 超参数优化一般是:非凸
    • GD 的整个收敛理论假设,潜在问题是凸的
      • 好案例:您获得了一些局部最小值(可以任意差)
      • 最坏情况:GD 甚至没有收敛到某个局部最小值

我可能会补充一点,您的一般问题是人们可以考虑的最糟糕的优化问题,因为它是:

  • 非光滑、非凸
  • 甚至是随机/嘈杂,因为大多数底层算法都是启发式近似,在最终输出方面存在一些差异(通常甚至是基于 PRNG 的随机行为)

最后一部分是原因,为什么 sklearn 中提供的方法如此简单:

  • 随机搜索:
    • 如果我们因为问题太难而无法推断出某些东西,请尝试多次实例并选择最佳实例
  • 网格搜索:
    • 让我们假设有某种平滑度
      • 我们不是随机抽样,而是根据我们的平滑假设进行抽样
        • (以及其他假设,例如:param 可能很大 -> np.logspace 以分析更大的数字)

虽然有很多贝叶斯方法,包括可用的 python 软件,如 hyperoptspearmint,但许多人认为,随机搜索通常是最好的方法(这可能令人惊讶,但强调了提到的问题)。

【讨论】:

  • 非常好的答案。您能否推荐参考资料来进一步解释您所做的评论?
  • 这个答案具有误导性。有关基于梯度的超参数优化的示例,请参阅 herehere
  • 这么多年过去了,我还想对这个从一开始就困扰我的答案提出批评。你从“计算梯度是最少的问题”开始。对于大多数分类器来说,这显然是错误的。您将如何计算具有 5 棵树与 6 棵树的随机森林之间的梯度?我想我当时不应该这么快接受这个答案。正如 user76284 指出的那样,这是一种误导,也是错误的。
  • 通过使用精确导数的二阶优化,您可以解决很多您提到的问题。当然,它不适用于所有 HO 问题,但在很多情况下(岭回归、逻辑回归、泊松回归)可以使用优化器来有效地调整参数。
【解决方案2】:

以下是一些描述基于梯度的超参数优化的论文:

我们通过在整个训练过程中反向链接导数来计算关于所有超参数的交叉验证性能的精确梯度。这些梯度使我们能够优化数千个超参数,包括步长和动量调度、权重初始化分布、丰富参数化的正则化方案和神经网络架构。我们通过用动量精确反转随机梯度下降的动力学来计算超参数梯度。

我们研究了两种程序(反向模式和正向模式)来计算验证误差相对于任何迭代学习算法(例如随机梯度下降)的超参数的梯度。这些过程反映了循环神经网络计算梯度的两种方法,并且在运行时间和空间要求方面具有不同的权衡。我们对反向模式程序的制定与 Maclaurin 等人以前的工作有关。 [2015] 但不需要可逆动力学。前向模式过程适用于实时超参数更新,可以显着加快大型数据集的超参数优化。

使用任何基于梯度的机器学习算法都涉及调整优化器的超参数(例如学习率)的繁琐任务。存在许多用于自动超参数优化的技术,但它们通常会引入更多的超参数来控制超参数优化过程。我们建议通过梯度下降来学习超参数本身,并且还通过梯度下降来学习超参数,以此类推。随着这些基于梯度的优化器塔的增长,它们对顶级超参数的选择变得不那么敏感,从而减少了用户搜索最优值的负担。

【讨论】:

【解决方案3】:

对于广义线性模型(即逻辑回归、岭回归、泊松回归), 您可以有效地调整许多正则化超参数 使用精确导数和近似留一交叉验证。

但不要只停留在梯度上,计算完整的 hessian 并使用二阶优化器 - 它是 更高效、更健壮。

sklearn 目前没有此功能,但有其他可用的工具可以做到这一点。

例如,以下是如何使用 python 包bbai 来适应 岭正则化逻辑回归的超参数,以最大化 威斯康星州乳腺癌数据集的训练数据集的近似留一交叉验证。

加载数据集

from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler

data = load_breast_cancer()
X = data['data']
X = StandardScaler().fit_transform(X)
y = data['target']

适合模型

import bbai.glm

model = bbai.glm.LogisticRegression()
# Note: it automatically fits the C parameter to minimize the error on
# the approximate leave-one-out cross-validation.
model.fit(X, y)

因为它使用梯度和粗麻布以及有效的精确公式 (没有自动微分),它可以快速拨入精确的超参数,只需几个 评价。

YMMV,但是当我将它与带有默认参数的 sklearn 的 LogisticRegressionCV 进行比较时,它会运行 在很短的时间内。

t1 = time.time()
model = bbai.glm.LogisticRegression()
model.fit(X, y)
t2 = time.time()
print('***** approximate leave-one-out optimization')
print('C = ', model.C_)
print('time = ', (t2 - t1))

from sklearn.linear_model import LogisticRegressionCV
print('***** sklearn.LogisticRegressionCV')
t1 = time.time()
model = LogisticRegressionCV(scoring='neg_log_loss', random_state=0)
model.fit(X, y)
t2 = time.time()
print('C = ', model.C_[0])
print('time = ', (t2 - t1))

打印

***** approximate leave-one-out optimization
C =  0.6655139682151275
time =  0.03996014595031738
***** sklearn.LogisticRegressionCV
C =  0.3593813663804626
time =  0.2602980136871338

工作原理

近似留一法交叉验证 (ALOOCV) 是一种近似留一法 交叉验证更有效地评估广义线性模型。

它首先适合正则化模型。然后使用牛顿算法的一步来近似 模型权重将是我们留下一个数据点时。如果正则化成本函数为 广义线性模型表示为

那么 ALOOCV 可以计算为

在哪里

(注:H代表最优权重下成本函数的hessian)

有关 ALOOCV 的更多背景信息,您可以查看此guide

还可以计算 ALOOCV 的精确导数,从而提高优化效率。

我不会把导数公式放在这里,因为它们很复杂,但请参阅论文 Optimizing Approximate Leave-one-out Cross-validation.

如果我们绘制 ALOOCV 并与示例数据集的留一法交叉验证进行比较, 您可以看到它非常密切地跟踪它,并且 ALOOCV 最佳值几乎与 LOOCV 最优。

计算留一法交叉验证

import numpy as np

def compute_loocv(X, y, C):
    model = bbai.glm.LogisticRegression(C=C)
    n = len(y)
    loo_likelihoods = []
    for i in range(n):
        train_indexes = [i_p for i_p in range(n) if i_p != i]
        test_indexes = [i]
        X_train, X_test = X[train_indexes], X[test_indexes]
        y_train, y_test = y[train_indexes], y[test_indexes]
        model.fit(X_train, y_train)
        pred = model.predict_proba(X_test)[0]
        loo_likelihoods.append(pred[y_test[0]])
    return sum(np.log(loo_likelihoods))

计算近似留一法交叉验证

import scipy

def fit_logistic_regression(X, y, C):
    model = bbai.glm.LogisticRegression(C=C)
    model.fit(X, y)
    return np.array(list(model.coef_[0]) + list(model.intercept_))

def compute_hessian(p_vector, X, alpha):
    n, k = X.shape
    a_vector = np.sqrt((1 - p_vector)*p_vector)
    R = scipy.linalg.qr(a_vector.reshape((n, 1))*X, mode='r')[0]
    H = np.dot(R.T, R)
    for i in range(k-1):
        H[i, i] += alpha
    return H

def compute_alo(X, y, C):
    alpha = 1.0 / C
    w = fit_logistic_regression(X, y, C)
    X = np.hstack((X, np.ones((X.shape[0], 1))))
    n = X.shape[0]
    y = 2*y - 1
    u_vector = np.dot(X, w)
    p_vector = scipy.special.expit(u_vector*y)
    H = compute_hessian(p_vector, X, alpha)
    L = np.linalg.cholesky(H)
    T = scipy.linalg.solve_triangular(L, X.T, lower=True)
    h_vector = np.array([np.dot(ti, ti) for pi, ti in zip(p_vector, T.T)])
    loo_u_vector = u_vector - \
        y * (1 - p_vector)*h_vector / (1 - p_vector*(1 - p_vector)*h_vector)
    loo_likelihoods = scipy.special.expit(y*loo_u_vector)
    return sum(np.log(loo_likelihoods))

绘制结果(连同 ALOOCV 最优值)

import matplotlib.pyplot as plt

Cs = np.arange(0.1, 2.0, 0.1)
loocvs = [compute_loocv(X, y, C) for C in Cs]
alos = [compute_alo(X, y, C) for C in Cs]

fig, ax = plt.subplots()
ax.plot(Cs, loocvs, label='LOOCV', marker='o')
ax.plot(Cs, alos, label='ALO', marker='x')
ax.axvline(model.C_, color='tab:green', label='C_opt')
ax.set_xlabel('C')
ax.set_ylabel('Log-Likelihood')
ax.set_title("Breast Cancer Dataset")
ax.legend()

显示

【讨论】:

    猜你喜欢
    • 2013-02-08
    • 2011-09-27
    • 2019-08-28
    • 1970-01-01
    • 1970-01-01
    • 2016-10-27
    • 2018-04-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多