sklearn:通过梯度下降调整超参数?

时间:2017-04-14 23:26:41

标签: python optimization parameters scikit-learn

有没有办法在scikit中通过渐变下降来执行超参数调整?虽然可能难以计算超参数梯度的公式,但通过评估超参数空间中的两个关闭点来对超参数梯度进行数值计算应该相当容易。这种方法是否存在实施?为什么这种方法不是一个好主意?

3 个答案:

答案 0 :(得分:17)

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

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

为什么这不是一个好主意

  • 超级参数优化通常是:非顺畅
    • GD非常喜欢平滑函数,因为渐变为零无效
    • (每个超参数由一些离散集定义(例如l1对l2惩罚的选择)引入非光滑表面)
  • 超级参数优化通常是:非凸
    • GD的整个收敛理论假设,潜在的问题是凸的
      • 好的情况:你获得了一些本地最小(可以是任意不好)
      • 最坏情况:GD甚至没有收敛到某些本地最小值

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

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

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

  • 随机搜索:
    • 如果因为问题太难而无法推断,只需尝试多个实例并选择最佳
  • 网格搜索:
    • 让我们假设有某种顺畅
      • 代替随机抽样,我们对平滑度假设进行抽样
        • (和其他假设一样:param可能很大 - > np.logspace来分析更多大数字)

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

答案 1 :(得分:2)

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

  

通过在整个训练过程中向后链接导数,我们可以针对所有超参数计算出交叉验证性能的精确梯度。这些梯度使我们能够优化数千个超参数,包括步长和动量时间表,权重初始化分布,参数丰富的正则化方案以及神经网络体系结构。我们通过用动量精确地逆转随机梯度下降的动力学来计算超参数梯度。

  

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

答案 2 :(得分:1)

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

但不要只停留在梯度上,计算完整的 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()

显示

enter image description here