58·模型选择与调优进阶

随机搜索与贝叶斯优化

hyperparametertuning

第58课:随机搜索与贝叶斯优化

1. 学习目标

完成本课学习后,你将能够:

  • 理解网格搜索在超参数优化中的局限性。
  • 掌握随机搜索的原理与在scikit-learn中的实现。
  • 了解贝叶斯优化的核心思想(代理模型与采集函数)。
  • 应用scikit-optimize库实现贝叶斯优化进行超参数调优。
  • 比较网格搜索、随机搜索和贝叶斯优化在效率与效果上的差异。

2. 核心概念

2.1 为什么需要更智能的搜索?

在上一课中,我们学习了网格搜索。它虽然直观,但在超参数空间较大时存在一个致命问题:维度灾难。随着超参数数量增加,需要评估的点数呈指数级增长,计算成本极高。更关键的是,并非所有超参数对模型性能的影响都相同,网格搜索在“不重要”的维度上浪费了大量评估资源。

2.2 随机搜索

核心思想:在指定的超参数分布中进行随机采样,而不是在固定的网格点上遍历。

优势

  1. 更高效探索:在相同计算预算下,随机搜索能覆盖更多的超参数值组合。尤其当某些超参数对性能影响不大时,随机搜索能更早地找到“重要”维度上的好值。
  2. 易于并行:每次评估都是独立的,可以轻松并行化。

代码实现:在scikit-learn中,使用RandomizedSearchCV

2.3 贝叶斯优化

核心思想:这是一种序列化的、基于模型的全局优化策略。它不像前两种方法那样“盲目”搜索,而是根据已有评估结果,智能地选择下一个最有可能带来提升的评估点。

两大核心组件

  1. 代理模型:使用一个简单的概率模型(如高斯过程)来近似复杂的黑盒目标函数(我们的模型验证分数)。这个模型会给出每一个未评估点的预测值及其不确定性。
  2. 采集函数:基于代理模型的预测,制定一个策略来决定下一个要评估的点。常见的策略有:
    • 最大化期望改进:选择那些最有可能超过当前最佳结果的点。
    • 置信上界:在预测值高且不确定性大的区域进行探索。

流程简述:评估一个初始点 -> 更新代理模型 -> 用采集函数选择下一个点 -> 评估该点 -> 重复该过程。

代码实现:可以使用scikit-optimize库(简称skopt)。

3. 代码示例

我们将使用糖尿病数据集梯度提升回归器来演示两种优化方法。

3.1 环境准备与数据加载

# 导入必要的库
import numpy as np
from sklearn.datasets import load_diabetes
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_split, RandomizedSearchCV
from sklearn.metrics import mean_squared_error
from scipy.stats import uniform, randint
import warnings
warnings.filterwarnings('ignore')

# 加载数据
data = load_diabetes()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

print(f"训练集大小: {X_train.shape}, 测试集大小: {X_test.shape}")

3.2 使用随机搜索调优

# 定义要搜索的超参数空间
param_distributions = {
    'n_estimators': randint(50, 300),       # 决策树的数量
    'max_depth': [3, 4, 5, 6, 7, None],    # 树的最大深度
    'learning_rate': uniform(0.01, 0.2),    # 学习率
    'subsample': uniform(0.6, 0.4),         # 训练每棵树使用的样本比例
    'min_samples_split': randint(2, 20)     # 内部节点再划分所需最小样本数
}

# 创建随机搜索对象
random_search = RandomizedSearchCV(
    estimator=GradientBoostingRegressor(random_state=42),
    param_distributions=param_distributions,
    n_iter=50,              # 随机采样50组参数组合
    scoring='neg_mean_squared_error', # 使用负均方误差作为评分
    cv=3,                   # 3折交叉验证
    random_state=42,
    n_jobs=-1,              # 使用所有CPU核心
    verbose=1
)

# 执行搜索
random_search.fit(X_train, y_train)

# 输出最佳参数和分数
print("随机搜索最佳参数:", random_search.best_params_)
print("随机搜索最佳验证分数 (负MSE):", random_search.best_score_)

# 在测试集上评估
best_model_rs = random_search.best_estimator_
y_pred_rs = best_model_rs.predict(X_test)
mse_rs = mean_squared_error(y_test, y_pred_rs)
print(f"随机搜索模型在测试集上的MSE: {mse_rs:.4f}")

3.3 使用贝叶斯优化调优

首先安装scikit-optimize: pip install scikit-optimize

from skopt import BayesSearchCV
from skopt.space import Real, Integer, Categorical

# 定义贝叶斯优化的搜索空间(更精细的定义)
bayes_search_space = {
    'n_estimators': Integer(50, 300),
    'max_depth': Categorical([3, 4, 5, 6, 7, None]), # 分类型参数
    'learning_rate': Real(0.01, 0.3, prior='log-uniform'), # 对数均匀分布更适合学习率
    'subsample': Real(0.6, 1.0, prior='uniform'),
    'min_samples_split': Integer(2, 20)
}

# 创建贝叶斯搜索对象
bayes_search = BayesSearchCV(
    estimator=GradientBoostingRegressor(random_state=42),
    search_spaces=bayes_search_space,
    n_iter=30,              # 贝叶斯优化通常需要的迭代次数更少
    scoring='neg_mean_squared_error',
    cv=3,
    random_state=42,
    n_jobs=-1,
    verbose=1
)

# 执行搜索
bayes_search.fit(X_train, y_train)

# 输出最佳参数和分数
print("\n贝叶斯优化最佳参数:", bayes_search.best_params_)
print("贝叶斯优化最佳验证分数 (负MSE):", bayes_search.best_score_)

# 在测试集上评估
best_model_bs = bayes_search.best_estimator_
y_pred_bs = best_model_bs.predict(X_test)
mse_bs = mean_squared_error(y_test, y_pred_bs)
print(f"贝叶斯优化模型在测试集上的MSE: {mse_bs:.4f}")

# 简单比较
print(f"\n比较: 随机搜索MSE={mse_rs:.4f}, 贝叶斯优化MSE={mse_bs:.4f}")

4. 实践练习

练习1:基础应用

使用随机搜索为一个分类任务(例如,load_iris()数据集 + RandomForestClassifier)调优n_estimators, max_depth, min_samples_split三个参数。设置n_iter=20,输出最佳参数和在测试集上的准确率。

练习2:进阶探索

在贝叶斯优化示例中,尝试修改BayesSearchCV的参数:

  • 将采集函数改为acq_func='LCB'(置信上界)。
  • n_iter从30减少到15,观察最佳分数是否发生显著变化,并解释原因。
  • learning_rate的分布从log-uniform改为uniform,比较结果。

练习3:对比分析

在一个小项目中(例如,预测波士顿房价),分别用网格搜索(小范围)、随机搜索和贝叶斯优化对同一个模型进行调优。记录:

  1. 每种方法找到的最佳验证分数。
  2. 每种方法的总运行时间(可以使用%%time魔法命令)。
  3. 根据结果,讨论在什么情况下你会优先选择哪一种方法。

5. 常见错误

  1. 安装问题:忘记安装scikit-optimize库。使用pip install scikit-optimizeconda install -c conda-forge scikit-optimize
  2. 分布设置不当
    • 对学习率等跨越多个数量级的参数,应使用对数均匀分布(log-uniform),否则可能在大数值区间过度采样。
    • 对类别参数(如max_depth: None)应使用Categorical
  3. 迭代次数误解:认为贝叶斯优化n_iter必须和随机搜索一样大。实际上,贝叶斯优化更高效,通常更少的迭代就能找到不错的解。但为了稳健,可以设置得比随机搜索稍少,而不是更少一个数量级。
  4. 忽略随机种子:在RandomizedSearchCVBayesSearchCV中,设置random_state对于结果的可复现性至关重要。
  5. 过于乐观:在测试集上反复用最终结果评估来“调优”会导致数据泄露和过于乐观的估计。始终使用独立的验证集(或CV分数)来选择模型,仅用测试集进行最终评估。

6. 小结

本节课我们学习了两种比网格搜索更高效的超参数优化方法:

  • 随机搜索:通过随机采样在超参数空间中探索,简单高效,特别适合初期探索超参数空间较大的情况。
  • 贝叶斯优化:一种更“智能”的序列化优化方法。它通过代理模型学习超参数与性能之间的关系,并用采集函数引导搜索向最有希望的区域进行,通常能用更少的迭代次数找到更优的解,尤其适用于评估成本高昂(如训练深度神经网络)的场景。

如何选择?

  • 当评估速度很快、超参数维度不高时,随机搜索是不错的起点。
  • 当评估非常耗时(如大型模型、大数据集)时,贝叶斯优化的前期投入(建立模型)会因后期更高效的搜索而得到回报。

下一课,我们将学习一个更现代化、功能更强大的自动超参数优化框架——Optuna,它内部集成了贝叶斯优化等多种策略,并提供了更便捷的API和可视化工具。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「Optuna 自动超参数优化框架」 以巩固所学知识。