第4章:网格搜索算法——全网格、稀疏网格与自适应网格
网格搜索,说白了就是暴力调参。你给出一组参数候选值,它把所有组合都试一遍。听起来很笨,对吧?但在我多年的实战经验里,这招往往是最稳的。尤其是当你对模型参数空间还不太熟悉的时候,网格搜索能帮你快速建立直觉。
4.1 全网格搜索:最笨但最可靠的方法
全网格搜索,就是穷举所有参数组合。假设你有两个参数:learning_rate 取 [0.01, 0.1, 1.0],max_depth 取 [3, 5, 7]。那么总共要跑 3×3 = 9 组实验。
我刚开始做调参时,总觉得这方法太原始。后来在一个风控模型项目里,我用随机搜索试了200次,结果还不如全网格搜索的50次效果好。为什么?因为随机搜索可能会漏掉参数空间里的「好区域」。全网格虽然慢,但它不会漏。
举个例子:
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7, 10],
'min_samples_split': [2, 5, 10]
}
# 组合数 = 3 × 4 × 3 = 36 组
grid_search = GridSearchCV(
estimator=RandomForestClassifier(),
param_grid=param_grid,
cv=5, # 5折交叉验证
scoring='accuracy'
)
grid_search.fit(X_train, y_train)
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳得分: {grid_search.best_score_:.4f}")
嗯,这里要注意:cv=5 意味着每组参数要训练5次。所以实际训练次数是 36 × 5 = 180 次。如果你的数据集有10万条,每次训练要30秒...那你就得等一个半小时。
4.2 稀疏网格:用更少的组合覆盖更大的空间
全网格搜索的问题很明显:参数一多,组合数爆炸。我记得有一次做深度学习调参,参数有6个,每个取5个值,组合数直接到了 5⁶ = 15625 组。按每组训练2分钟算,得跑21天。
稀疏网格的思路是:不取所有组合,而是只取一部分。比如每个参数只取3个值,但让这些值在空间里分布得更「稀疏」一些。
import numpy as np
# 对数尺度稀疏网格
param_grid_sparse = {
'learning_rate': np.logspace(-4, 0, 5), # [0.0001, 0.001, 0.01, 0.1, 1.0]
'batch_size': [16, 32, 64, 128],
'epochs': [10, 20, 50]
}
# 组合数 = 5 × 4 × 3 = 60 组
# 比全网格(比如每个参数取10个值)少了100倍
你想想看,稀疏网格的本质是什么?是用先验知识来减少搜索空间。如果你知道某个参数对结果不敏感,那就少取几个值。如果你知道某个参数在某个区间内效果最好,那就多取几个值。
4.3 自适应网格:让搜索变得更聪明
自适应网格,说白了就是「先粗搜,再细搜」。我习惯分两步走:
- 粗搜阶段: 用稀疏网格快速定位「好区域」
- 细搜阶段: 在好区域周围加密网格
举个例子,假设我们要调 n_estimators 和 max_depth:
# 第一步:粗搜
coarse_grid = {
'n_estimators': [10, 50, 100, 200, 500],
'max_depth': [3, 5, 7, 10, 15]
}
# 25组实验
# 假设粗搜发现最佳点在 n_estimators=100, max_depth=7 附近
# 第二步:细搜
fine_grid = {
'n_estimators': [80, 90, 100, 110, 120],
'max_depth': [5, 6, 7, 8, 9]
}
# 又是25组实验
# 总共50组,比全网格的25×25=625组少了12倍
4.4 维度灾难:网格搜索的致命弱点
维度灾难,是网格搜索最大的敌人。随着参数数量的增加,搜索空间呈指数级增长。我画个图你就明白了:
看到没?维度从1涨到5,组合数从10涨到10万。这就是维度灾难。我见过有人调参时列了10个参数,每个取10个值,组合数 10¹⁰ = 100亿。就算你有1000台机器并行跑,也得跑几年。
4.5 实战:网格搜索调参全流程
好了,理论说完了。咱们来一个完整的实战案例。我用XGBoost调参,目标是最大化AUC。
import xgboost as xgb
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import roc_auc_score
import numpy as np
# 加载数据(假设你已经有了)
# X, y = load_your_data()
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# 第一步:粗搜
print("=== 第一阶段:粗搜 ===")
param_grid_coarse = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.1, 0.3]
}
xgb_model = xgb.XGBClassifier(
objective='binary:logistic',
eval_metric='auc',
use_label_encoder=False
)
grid_coarse = GridSearchCV(
estimator=xgb_model,
param_grid=param_grid_coarse,
cv=3,
scoring='roc_auc',
n_jobs=-1,
verbose=1
)
grid_coarse.fit(X_train, y_train)
print(f"粗搜最佳参数: {grid_coarse.best_params_}")
print(f"粗搜最佳AUC: {grid_coarse.best_score_:.4f}")
# 第二步:在粗搜结果周围细搜
print("\n=== 第二阶段:细搜 ===")
best_params = grid_coarse.best_params_
# 在最佳点周围加密
param_grid_fine = {
'n_estimators': [
best_params['n_estimators'] - 20,
best_params['n_estimators'] - 10,
best_params['n_estimators'],
best_params['n_estimators'] + 10,
best_params['n_estimators'] + 20
],
'max_depth': [
best_params['max_depth'] - 1,
best_params['max_depth'],
best_params['max_depth'] + 1
],
'learning_rate': [
best_params['learning_rate'] * 0.5,
best_params['learning_rate'],
best_params['learning_rate'] * 2
]
}
# 过滤掉无效值
param_grid_fine['n_estimators'] = [
x for x in param_grid_fine['n_estimators'] if x > 0
]
param_grid_fine['max_depth'] = [
x for x in param_grid_fine['max_depth'] if x > 0
]
grid_fine = GridSearchCV(
estimator=xgb_model,
param_grid=param_grid_fine,
cv=5, # 细搜用更多折数
scoring='roc_auc',
n_jobs=-1,
verbose=1
)
grid_fine.fit(X_train, y_train)
print(f"细搜最佳参数: {grid_fine.best_params_}")
print(f"细搜最佳AUC: {grid_fine.best_score_:.4f}")
# 第三步:用最佳参数在测试集上评估
final_model = xgb.XGBClassifier(
**grid_fine.best_params_,
objective='binary:logistic',
eval_metric='auc',
use_label_encoder=False
)
final_model.fit(X_train, y_train)
y_pred = final_model.predict_proba(X_test)[:, 1]
test_auc = roc_auc_score(y_test, y_pred)
print(f"\n测试集AUC: {test_auc:.4f}")
n_jobs=-1 可以并行跑所有组合,能省不少时间。
4.6 网格搜索的实用技巧总结
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 参数少(≤3个) | 全网格搜索 | 穷举所有组合,结果最可靠 |
| 参数中等(4-6个) | 自适应网格 | 先粗搜再细搜,平衡效率与精度 |
| 参数多(≥7个) | 别用网格搜索 | 改用随机搜索或贝叶斯优化 |
| 参数范围跨度大 | 对数尺度网格 | 用 np.logspace 生成候选值 |
| 计算资源有限 | 稀疏网格 | 减少每个参数的候选数 |
最后说一句:网格搜索不是万能的,但它是你理解参数空间的最好工具。先用网格搜索跑几轮,你就能知道哪些参数重要、哪些参数不敏感、最优区域大概在哪里。有了这些直觉,再用更高级的调参方法,效果会好很多。
公众号:蓝海资料掘金营,微信deep3321