第58课:随机搜索与贝叶斯优化
1. 学习目标
完成本课学习后,你将能够:
- 理解网格搜索在超参数优化中的局限性。
- 掌握随机搜索的原理与在scikit-learn中的实现。
- 了解贝叶斯优化的核心思想(代理模型与采集函数)。
- 应用
scikit-optimize库实现贝叶斯优化进行超参数调优。 - 比较网格搜索、随机搜索和贝叶斯优化在效率与效果上的差异。
2. 核心概念
2.1 为什么需要更智能的搜索?
在上一课中,我们学习了网格搜索。它虽然直观,但在超参数空间较大时存在一个致命问题:维度灾难。随着超参数数量增加,需要评估的点数呈指数级增长,计算成本极高。更关键的是,并非所有超参数对模型性能的影响都相同,网格搜索在“不重要”的维度上浪费了大量评估资源。
2.2 随机搜索
核心思想:在指定的超参数分布中进行随机采样,而不是在固定的网格点上遍历。
优势:
- 更高效探索:在相同计算预算下,随机搜索能覆盖更多的超参数值组合。尤其当某些超参数对性能影响不大时,随机搜索能更早地找到“重要”维度上的好值。
- 易于并行:每次评估都是独立的,可以轻松并行化。
代码实现:在scikit-learn中,使用RandomizedSearchCV。
2.3 贝叶斯优化
核心思想:这是一种序列化的、基于模型的全局优化策略。它不像前两种方法那样“盲目”搜索,而是根据已有评估结果,智能地选择下一个最有可能带来提升的评估点。
两大核心组件:
- 代理模型:使用一个简单的概率模型(如高斯过程)来近似复杂的黑盒目标函数(我们的模型验证分数)。这个模型会给出每一个未评估点的预测值及其不确定性。
- 采集函数:基于代理模型的预测,制定一个策略来决定下一个要评估的点。常见的策略有:
- 最大化期望改进:选择那些最有可能超过当前最佳结果的点。
- 置信上界:在预测值高且不确定性大的区域进行探索。
流程简述:评估一个初始点 -> 更新代理模型 -> 用采集函数选择下一个点 -> 评估该点 -> 重复该过程。
代码实现:可以使用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:对比分析
在一个小项目中(例如,预测波士顿房价),分别用网格搜索(小范围)、随机搜索和贝叶斯优化对同一个模型进行调优。记录:
- 每种方法找到的最佳验证分数。
- 每种方法的总运行时间(可以使用
%%time魔法命令)。 - 根据结果,讨论在什么情况下你会优先选择哪一种方法。
5. 常见错误
- 安装问题:忘记安装
scikit-optimize库。使用pip install scikit-optimize或conda install -c conda-forge scikit-optimize。 - 分布设置不当:
- 对学习率等跨越多个数量级的参数,应使用对数均匀分布(
log-uniform),否则可能在大数值区间过度采样。 - 对类别参数(如
max_depth: None)应使用Categorical。
- 对学习率等跨越多个数量级的参数,应使用对数均匀分布(
- 迭代次数误解:认为贝叶斯优化
n_iter必须和随机搜索一样大。实际上,贝叶斯优化更高效,通常更少的迭代就能找到不错的解。但为了稳健,可以设置得比随机搜索稍少,而不是更少一个数量级。 - 忽略随机种子:在
RandomizedSearchCV和BayesSearchCV中,设置random_state对于结果的可复现性至关重要。 - 过于乐观:在测试集上反复用最终结果评估来“调优”会导致数据泄露和过于乐观的估计。始终使用独立的验证集(或CV分数)来选择模型,仅用测试集进行最终评估。
6. 小结
本节课我们学习了两种比网格搜索更高效的超参数优化方法:
- 随机搜索:通过随机采样在超参数空间中探索,简单高效,特别适合初期探索或超参数空间较大的情况。
- 贝叶斯优化:一种更“智能”的序列化优化方法。它通过代理模型学习超参数与性能之间的关系,并用采集函数引导搜索向最有希望的区域进行,通常能用更少的迭代次数找到更优的解,尤其适用于评估成本高昂(如训练深度神经网络)的场景。
如何选择?
- 当评估速度很快、超参数维度不高时,随机搜索是不错的起点。
- 当评估非常耗时(如大型模型、大数据集)时,贝叶斯优化的前期投入(建立模型)会因后期更高效的搜索而得到回报。
下一课,我们将学习一个更现代化、功能更强大的自动超参数优化框架——Optuna,它内部集成了贝叶斯优化等多种策略,并提供了更便捷的API和可视化工具。