9.超参数优化

1.超参数优化的直观理解

选定了模型之后,就有了优化超参数以得到最佳得分模型的问题。

假设有a,b,c三个参数,这三个参数都可以取1到10之间的整数。
如何找到使模型表现最好的参数组合呢?其中一种方法是对所有组合进行评估,即循环穷举的方法。
下面的代码是对超参数优化的直观理解。

best_accuracy = 0
best_parameters = {"a": 0, "b":0, "c":0}
for a in range(1, 11):
    for b in range(1, 11):
        for c in range(1, 11):
            model = MODEL(a, b, c)
            model.fit(train_data)
            preds = model.predict(validation_data)
            accuracy = metrics.accuracy_score(targets, preds)
            if accuracy > best_accuracy:
                best_accuracy = accuracy
                best_parameters["a"] = a
                best_parameters["b"] = b
                best_parameters["c"] = c

这个例子我们需要对模型进行(10x10x10)次拟合,计算量可能会很大。现实世界中,并不只有三个参数,每个参数也不是只有10个值。大多数模型的参数都是实数,不同的参数组合可以是无限的。

2.网格搜索(GridSearch)

让我们看看sklearn的随机森林模型。

from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier(
    n_estimators=100,
    criterion='gini',
    max_depth=None,
    min_samples_split=2,
    min_samples_leaf=1,
    min_weight_fraction_leaf=0.0,
    max_features='auto',
    max_leaf_nodes=None,
    min_impurity_decrease=0.0,
    min_impurity_split=None,
    bootstrap=True,
    oob_score=False,
    n_jobs=None,
    random_state=None,
    verbose=0,
    warm_start=False,
    class_weight=None,
    ccp_alpha=0.0,
    max_samples=None
)

这个模型总共有19个参数,所有这些参数的所有组合,以及它们可以承担的所有值,都将是无穷无尽的。通常情况下,我们没有足够的资源和时间来做这件事。所以,我们指定了一个参数网格,在这个网格上寻找最佳参数组合的搜索成为网格搜索。我们可以说n_estimators可以是100、200、250、300、400、500;max_depth可以是1、2、5、7、11、15;criterion可以是gini或entropy。这些参数看起来并不多,但如果数据集很大,计算起来会耗费大量时间。

我们可以像之前一样创建三个for循环,并在验证集上计算得分,这样就能实现网格搜索。还必须注意的是,如果要进行k折交叉验证,则需要更多的循环,意味着需要更多的时间来找到完美的参数。因此,网格搜索并不流行。

下面以根据手机配置预测手机价格范围(分类)数据集为例,看看它是如何实现的。
训练集中只有2000个样本。我们可以轻松地使用分层kfold和准确率作为评估指标。我们将使用上述参数范围的随机森林,在下面的示例中了解如何进行网格搜索。

import pandas as pd
from sklearn import ensemble
from sklearn import metrics
from sklearn import model_selection

if __name__ == "__main__":
    # 获取训练数据
    df = pd.read_csv("../input/mobile_train.csv")
    X = df.drop("price_range", axis=1).values
    y = df.price_range.values
    
    # 定义学习器
    classifier = ensemble.RandomForestClassifier(n_jobs=-1)
    
    # 定义需要进行网格搜索的参数
    param_grid = {
        "n_estimators": [100, 200, 250, 300, 400, 500],
        "max_depth": [1, 2, 5, 7, 11, 15],
        "criterion": ["gini", "entropy"]
    }
    
    # 定义网格搜索模型
    model = model_selection.GridSearchCV(
        estimator=classifier,
        param_grid=param_grid,
        scoring="accuracy",
        verbose=10,
        n_jobs=1,
        cv=5
    )
    
    # 拟合模型
    model.fit(X, y)
    
    # 打印模型最佳分数和最优参数
    print(f"Best score: {model.best_score_}")
    print("Best parameters set:")
    best_parameters = model.best_estimator_.get_params()
    for param_name in sorted(param_grid.keys()):
        print(f"\t{param_name}: {best_parameters[param_name]}")

我们可以看到,5折交叉验证最佳得分是0.889,网格搜索得到了最佳参数。

3.随机搜索(Randomized Search)

我们可以使用的下一个最佳方法是随机搜索。在随机搜索中,我们随机选择一个参数组合,然后计算交叉验证得分。这种方法消耗的时间比网格搜索少,因为不对所有不同的参数组合进行评估。我们选择要对模型进行多少次评估,决定了搜索所需的时间。如果迭代次数少,随机搜索比网格搜索速度快。

代码与上面差别不大,除GridSearchCV外,使用RandomizedSearchCV。

if __name__ == "__main__":
    # 获取训练数据
    df = pd.read_csv("../input/mobile_train.csv")
    X = df.drop("price_range", axis=1).values
    y = df.price_range.values
    
    # 定义学习器
    classifier = ensemble.RandomForestClassifier(n_jobs=-1)
    
    # 定义需要进行网格搜索的参数
    param_grid = {
        "n_estimators": np.arange(100, 1500, 100),
        "max_depth": np.arange(1, 31),
        "criterion": ["gini", "entropy"]
    }
    
    # 定义网格搜索模型
    model = model_selection.RandomizedSearchCV(
        estimator=classifier,
        param_distributions=param_grid,
        n_iter=20,
        scoring="accuracy",
        verbose=10,
        n_jobs=1,
        cv=5
    )
    
    # 拟合模型
    model.fit(X, y)
    
    # 打印模型最佳分数和最优参数
    print(f"Best score: {model.best_score_}")
    print("Best parameters set:")
    best_parameters = model.best_estimator_.get_params()
    for param_name in sorted(param_grid.keys()):
        print(f"\t{param_name}: {best_parameters[param_name]}")

4.使用管道的方法

(这个例子涉及到文本处理和SVD,没有学过这个内容,等到以后有学习再整理)

5.高斯过程用于超参数优化

接下来让我们看看高斯过程如何用于超参数优化。这类算法需要一个可以优化的函数。
大多数情况下,都是最小化这个函数,就像我们最小化损失一样。
比方说,我们想要找到最佳参数以获得最佳准确度。我们的目的是获得最高的准确度,可以通过最小化准确度的相反数来实现。因为最小化准确度的相反数实际上就等同于最大化准确度。

这个过程可以通过scikit-optimize(skopt)库中的gp_minimize函数来实现。下面看看
如何使用该函数调整随机森林模型的参数。

import numpy as np
import pandas as pd

from functools import partial

from sklearn import ensemble
from sklearn import metrics
from sklearn import model_selection

from skopt import gp_minimize
from skopt import space

def optimize(params, param_names, x, y):
    params = dict(zip(param_names, params))
    model = ensemble.RandomForestClassifier(**params)
    kf = model_selection.StratifiedKFold(n_splits=5)
    accuracies = []
    
    for idx in kf.split(X=x, y=y):
        train_idx, test_idx = idx[0], idx[1]
        
        xtrain = x[train_idx]
        ytrain = y[train_idx]
        
        xtest = x[test_idx]
        ytest = y[test_idx]
        
        model.fit(xtrain, ytrain)
        preds = model.predict(xtest)
        
        fold_accuracy = metrics.accuracy_score(ytest, preds)
        accuracies.append(fold_accuracy)
        
    return -1 * np.mean(accuracies)

if __name__ == '__main__':
    df = pd.read_csv("../input/mobile_train.csv")
    X = df.drop("price_range", axis=1).values
    y = df.price_range.values
    
    param_space = [
        space.Integer(3, 15, name="max_depth"),
        space.Integer(100, 1500, name="n_estimators"),
        space.Categorical(["gini", "entropy"], name="criterion"),
        space.Real(0.01, 1, prior="uniform", name="max_features")
    ]
    
    param_names = [
        "max_depth",
        "n_estimators",
        "criterion",
        "max_features"
    ]
    
    optimization_function = partial(
        optimize,
        param_names=param_names,
        x=X,
        y=y
    )
    
    result = gp_minimize(
        optimization_function,
        n_calls=15,
        n_random_starts=10,
        verbose=10
    )
    
    best_params = dict(
        zip(
            param_names,
            result.clearx
        )
    )
    
    print(best_params)

在这里插入图片描述

可以通过下面的代码来绘制我们是如何实现收敛的。

from skopt.plots import plot_convergence
plot_convergence(result)

6.hyperopt用于超参数优化

有很多库可以用来做超参数优化,skopt是其中一个。
另一个可以用来做超参数优化的库是hyperopt。它使用树结构帕岑估计器(TPE)来找到最优参数。
下面的代码在上一段代码基础上做最小的变化来使用hyperopt。

import numpy as np
import pandas as pd
from functools import partial
from sklearn import ensemble
from sklearn import metrics
from sklearn import model_selection

from hyperopt import hp, fmin, tpe, Trials
from hyperopt.pyll.base import scope

def optimize(params, x, y):
    model = ensemble.RandomForestClassifier(**params)
    kf = model_selection.StratifiedKFold(n_splits=5)
    ...
    return -1 * np.mean(accuracies)

if __name__ == '__main__':
    df = pd.read_csv("../input/mobile_train.csv")
    X = df.drop("price_range", axis=1).values
    y = df.price_range.values
    
    param_space = {
        "max_depth": scope.int(hp.quniform("max_depth", 1, 15, 1)),
        "n_estimators": scope.int(
            hp.quniform("n_estimators", 100, 1500, 1)
        ), 
        "criterion": hp.choice("criterion", ["gini", "entropy"]),
        "max_features": hp.uniform("max_features", 0, 1)
    }
    
    optimization_function = partial(
        optimize, 
        x=X,
        y=y
    )
    
    trials = Trials()
    hopt = fmin(
        fn=optimization_function,
        space=param_space,
        algo=tpe.suggest,
        max_evals=15,
        trials=trials
    )
    
    print(hopt)

正如你所看到的,这与之前的代码并无太大区别。你必须以不同的格式定义参数空间,还需要改动实际优化的部分,用hyperopt代替gp_minimize,结果相当不错。我们得到了比以前更好的准确度和一组可以使用的参数。

可以参考下表,了解如何调整。RS*表示随机搜索应该更好。
在这里插入图片描述
在这里插入图片描述

7.总结

上述调整超参数的方法是最常见的,几乎适用于所有模型:线性回归、逻辑回归、基于树的方法、梯度提升模型(如xgboost、lightgbm),甚至是神经网络。

虽然这些方法已经存在,但学习时必须从手动调整超参数开始,即手工调整。手工调整可以帮助你学习基础知识,例如,在梯度提升中,当你增加深度时,你应该降低学习率。如果使用自动工具,就无法学习到这一点。

一旦你能更好地手动调整参数,你甚至可能不需要任何自动超参数调整。创建大型模型或引入大量特征时,容易造成过拟合。为避免过度拟合,需要在训练数据特征中引入噪声或对代价函数进行惩罚。这种惩罚称为正则化。比如在神经网络中,可以使用dropout、添加增强、噪声等方法对模型进行正则化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值