调参是机器学习工程师的日常必修课。记得我第一次训练神经网络时,整整花了两周时间手动调整学习率和批大小,那种反复修改参数、等待训练结果的过程简直让人崩溃。后来接触了网格搜索(Grid Search),感觉像是找到了救星——直到参数空间稍微复杂一点,运行时间就从几分钟变成了几天。
网格搜索的原理很简单:把每个参数的可能取值列出来,然后穷举所有组合。比如我们要调学习率(0.001, 0.01, 0.1)和批大小(32, 64, 128),就需要训练9个模型。看起来不多?但如果增加到5个参数,每个参数有5个候选值,组合数就会暴涨到3125种!我在Kaggle比赛里就遇到过这种情况,最后只能无奈地减少参数范围。
随机搜索(Random Search)确实比网格搜索高效一些。它不再遍历所有组合,而是在参数空间随机采样。实际测试中,相同次数的迭代下,随机搜索找到好解的概率更高。但问题在于,它依然是个"盲人摸象"的过程——不知道哪些区域更值得探索,只能靠运气。
这就是贝叶斯优化的用武之地。它像是个经验丰富的向导,会根据已经走过的路(历史评估结果),智能地选择下一步探索方向。我去年用贝叶斯优化调XGBoost参数,原本需要200次随机搜索才能找到的配置,贝叶斯优化50次就搞定了,而且模型AUC还提高了0.5%。
贝叶斯优化的核心在于用概率模型模拟目标函数。想象你在玩一个猜数字游戏,每次猜测后,系统会告诉你"大了"或"小了"。传统方法像网格搜索就是固定步长挨个试,随机搜索则是乱猜。而贝叶斯优化会记录所有历史猜测结果,构建一个概率分布图,然后计算每个数字可能是答案的概率。
具体实现中,最常用的概率模型是高斯过程(Gaussian Process)。它不仅能预测未知点的值,还能给出预测的不确定性。比如在调学习率时,如果0.01表现很好,0.02表现很差,高斯过程就会推测0.015附近可能有更优解,而不是继续在0.02以上浪费计算资源。
我做过一个对比实验:优化一个三峰测试函数。随机搜索100次只找到一个峰值,而贝叶斯优化30次就锁定了全部三个峰值。这就是概率模型的威力——它能捕捉目标函数的全局特征。
贝叶斯优化另一个精妙之处在于获取函数(Acquisition Function)的设计。常见的获取函数有:
以EI为例,公式看起来复杂但其实很直观:
python复制EI(x) = (μ(x) - f(x^+) - ξ)Φ(Z) + σ(x)φ(Z)
其中μ是预测值,σ是不确定性,f(x^+)是当前最优值,Φ和φ是标准正态分布的CDF和PDF。实际使用时,我们直接用现成库就行,但理解这个思想很重要。
我在调CNN的超参数时发现,初期迭代会优先探索未知区域(高σ),后期则集中在已知最优点附近(高μ)。这种自适应平衡是手动调参完全做不到的。
推荐使用scikit-optimize库(skopt),它接口简单且兼容scikit-learn。先安装:
bash复制pip install scikit-optimize pandas numpy
我们用经典的房价预测数据集演示:
python复制from sklearn.datasets import fetch_california_housing
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
housing = fetch_california_housing()
X, y = housing.data, housing.target
# 定义评估函数
def evaluate_model(params):
n_estimators = int(params['n_estimators'])
max_depth = int(params['max_depth'])
model = RandomForestRegressor(
n_estimators=n_estimators,
max_depth=max_depth,
min_samples_leaf=int(params['min_samples_leaf']),
random_state=42
)
return -cross_val_score(model, X, y, cv=3, scoring='neg_mean_squared_error').mean()
设置参数空间并启动优化:
python复制from skopt import BayesSearchCV
search_space = {
'n_estimators': (50, 200),
'max_depth': (5, 30),
'min_samples_leaf': (1, 10)
}
opt = BayesSearchCV(
RandomForestRegressor(),
search_space,
n_iter=50,
cv=3,
n_jobs=-1,
random_state=42
)
opt.fit(X, y)
print("最佳参数:", opt.best_params_)
print("最佳分数:", opt.best_score_)
实际运行时会看到,初期迭代尝试的参数差异很大(探索阶段),后期则集中在某个区域微调(利用阶段)。我通常在笔记本上跑50-100次迭代,对于复杂问题会放到服务器上跑整晚。
参数范围设置直接影响优化效果。根据经验:
我曾犯过一个错误:把学习率范围设成(0.0001, 0.1),结果优化器花了大量时间在0.1附近搜索。后来改成对数空间后,很快就找到了0.001附近的最优解。
贝叶斯优化本身是序列过程,但可以通过:
n_jobs并行评估不同参数x0和y0参数)对于耗时长的任务,建议保存中间结果:
python复制from skopt import dump, load
# 保存
dump(opt, 'optimization_result.pkl')
# 加载继续优化
loaded_opt = load('optimization_result.pkl')
loaded_opt.n_iter = 20
loaded_opt.fit(X, y)
如果优化效果不理想:
n_iter(我一般从30起步)有次我优化AUC但业务关注的是召回率,结果模型上线后效果很差。后来改用加权F1作为优化目标才解决问题。