引言
模型精度的一大半来自超参调得好——同样的 XGBoost,调参前后 AUC 可能差 0.05。但人工调参像「猜密码」:试到哪儿算哪儿、难以复现。**AutoML(自动化机器学习)**把「选模型 + 调超参」变成自动搜索问题。本文从网格/随机搜索讲起,落地 Optuna 的贝叶斯优化(TPE),再到 AutoGluon/FLAML 一键自动化,最后讨论「哪些能自动、哪些不能」。
前置:/ml-model-evaluation/(交叉验证与超参调优基础)、/ml-ensemble-learning/(树集成模型)、/ml-feature-engineering/(特征侧基础)。
目录
- 1. 为什么需要 AutoML
- 2. 网格搜索的局限
- 3. 随机搜索:网格的更优替代
- 4. 贝叶斯优化:让搜索记住历史
- 5. Optuna 实战:TPE 采样与剪枝
- 6. 搜索空间设计:超参的类型与范围
- 7. 早停、并行与资源控制
- 8. 特征与模型的自动选择
- 9. AutoML 工具链:AutoGluon 与 FLAML
- 10. 速查表与一句话记忆
- 延伸阅读
1. 为什么需要 AutoML
1.1 调参是「高方差高成本」劳动
| 人工调参痛点 | AutoML 解法 |
|---|---|
| 凭经验试,不可复现 | 记录每次实验参数+分数 |
| 维度多(几十个参数) | 自动搜索高维空间 |
| 每试一组要重训练 | 剪枝提前淘汰差组合 |
| 结果依赖个人水平 | 标准化的搜索算法 |
1.2 AutoML 的两层自动化
① 超参搜索(HPO):给算法找好参数
② 算法/流水线选择(CASH):连「用什么模型+怎么预处理」一起选
记忆:AutoML = 超参搜索 + 算法/流水线选择,把「猜参数」变成标准化搜索;价值是可复现 + 不漏好组合。
2. 网格搜索的局限
2.1 网格搜索原理
对每组参数组合穷举组合:
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [100, 300, 500],
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.05, 0.1],
}
# 3*3*3 = 27 组,全部训练评估
2.2 致命问题:维度爆炸
参数从 3 个变 10 个,组合数指数增长。且网格在每个维度均匀撒点,无法聚焦「有希望的区域」。
记忆:网格搜索穷举所有组合,维度一多就爆炸;且均匀撒点浪费算力,被随机/贝叶斯取代。
3. 随机搜索:网格的更优替代
3.1 随机采样而非穷举
固定预算 N 次,每次在所有维度随机取一个值:
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
param_dist = {
'n_estimators': randint(100, 800),
'max_depth': randint(3, 12),
'learning_rate': uniform(0.005, 0.2), # 连续区间
}
search = RandomizedSearchCV(
xgb.XGBClassifier(), param_dist,
n_iter=50, cv=5, scoring='roc_auc', random_state=42)
3.2 为什么随机通常更好
参数重要性不平均:随机搜索在「重要参数」上尝试更多不同值。给定预算,随机搜索的期望最优往往优于同预算的网格搜索。
记忆:随机搜索=在参数空间随机采样固定次数;维度多时通常优于网格,因为它在重要参数上撒更多点。
4. 贝叶斯优化:让搜索记住历史
4.1 核心思想
把「调参」当成「找黑箱函数最优」:用历史实验结果建立一个代理模型(猜测哪里的分数高),下一轮去最有希望的点试探。
第1-2轮:随机探索
第3轮起:用代理模型预测「哪里最可能提升」→ 去那里采样
不断用新结果更新代理模型
4.2 探索 vs 利用
| 策略 | 行为 |
|---|---|
| 探索 | 去没试过的区域(防漏掉最优) |
| 利用 | 去已知的高分区(精调) |
贝叶斯优化的美感在于自动平衡两者。
记忆:贝叶斯优化=用历史结果建模,预测最有希望的点再采样;自动平衡「探索新区域」与「利用高分区」。
5. Optuna 实战:TPE 采样与剪枝
5.1 Optuna 基础用法
import optuna
def objective(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 800),
'max_depth': trial.suggest_int('max_depth', 3, 12),
'learning_rate': trial.suggest_float('learning_rate', 0.005, 0.2, log=True),
'subsample': trial.suggest_float('subsample', 0.5, 1.0),
'reg_lambda': trial.suggest_float('reg_lambda', 0.1, 5.0, log=True),
}
model = xgb.XGBClassifier(**params, n_jobs=-1, random_state=42)
scores = cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc')
return scores.mean() # 最大化
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, n_jobs=4)
print("最佳:", study.best_params, study.best_value.round(4))
5.2 TPE:Optuna 的默认采样器
TPE(Tree-structured Parzen Estimator)用两个密度估计:一个来自「好结果」的参数分布,一个来自「差结果」的。采样时偏向好分布。
记忆:Optuna 用 trial 对象声明搜索空间,objective 返回分数,create_study+direction 设优化方向;默认 TPE 采样器会偏向「历史好结果」的参数区。
6. 搜索空间设计:超参的类型与范围
6.1 声明 API
trial.suggest_int(name, low, high) # 整数
trial.suggest_float(name, low, high, log=True) # 连续(对数可用)
trial.suggest_categorical(name, ['a','b','c']) # 类别
trial.suggest_uniform(name, low, high) # 均匀连续
6.2 参数缩放的直觉
| 参数 | 类型 | 理由 |
|---|---|---|
| n_estimators | int | 计数 |
| learning_rate | float, log | 0.01 和 0.02 差异大,0.1 和 0.11 差异小 |
| max_depth | int | 层数 |
| reg_lambda | float, log | 正则量级跨越多个数量级 |
记忆:搜索空间用 suggest_ 声明;跨数量级的参数(学习率/正则)用 log=True,计数用 int,离散选型用 categorical*。
7. 早停、并行与资源控制
7.1 剪枝(Pruning):提前淘汰
每个 trial 内部分训练集评估,表现太差的组合提前终止,把算力留给有希望的:
from optuna.integration import XGBoostPruningCallback
# 在 objective 里配合 early_stopping
model.fit(X_train, y_train,
eval_set=[(X_val, y_val)],
early_stopping_rounds=20,
callbacks=[XGBoostPruningCallback(trial, 'validation_0-auc')])
7.2 并行与预算
# 并行:n_jobs 控制同时跑几个 trial
study.optimize(objective, n_trials=200, n_jobs=4)
# 时间预算:timeout 秒
study.optimize(objective, timeout=3600)
# 结果持久化
study = optuna.create_study(storage='sqlite:///hp.db', study_name='exp1', load_if_exists=True)
记忆:剪枝提前杀差组合、n_jobs 并行加速、timeout 限时、storage 存 SQLite 断点续跑。
8. 特征与模型的自动选择
8.1 特征选择并入搜索
把「要不要这个特征」当超参,让搜索一起决定:
def objective(trial):
use_feat_a = trial.suggest_categorical('use_feat_a', [True, False])
cols = [...]
if use_feat_a: cols.append('feat_a')
X_sel = X[cols]
... # 后续训练评估
8.2 模型家族选择
def objective(trial):
family = trial.suggest_categorical('family', ['xgb', 'lgb', 'rf'])
if family == 'xgb':
model = xgb.XGBClassifier(**xgb_params(trial), random_state=42)
elif family == 'lgb':
model = lgb.LGBMClassifier(**lgb_params(trial), random_state=42)
else:
model = RandomForestClassifier(**rf_params(trial), random_state=42)
return cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc').mean()
记忆:把特征开关/模型家族声明成 categorical 超参,搜索器连「用不用这个特征、用哪个算法」一起自动决定。
9. AutoML 工具链:AutoGluon 与 FLAML
9.1 AutoGluon:少调参的精度至上
from autogluon.tabular import TabularPredictor
predictor = TabularPredictor(label='target').fit(
train_data, time_limit=600) # 限时 10 分钟
pred = predictor.predict(test_data)
# 自动尝试几十种模型 + 分层集成(stacking/bagging)
9.2 FLAML:轻量快速
from flaml import AutoML
automl = AutoML()
automl.fit(X_train, y_train, task='classification',
time_budget=300, metric='roc_auc')
# 自动选模型+调参,轻量且预算友好
9.3 何时用手动 HPO vs 完整 AutoML
| 场景 | 建议 |
|---|---|
| 有明确基模型、只精调 | Optuna 手动 HPO |
| 模型不确定、要快速基线 | AutoGluon/FLAML |
| 生产要可解释、稳定 | 手动 HPO + 少量候选 |
| 特征工程很重 | 手动为主,AutoML 只做选模 |
记忆:AutoGluon 用分层集成追求精度、FLAML 轻量快速;有确定基模型用 Optuna 精调,模型不确定用 AutoML 一键基线。
10. 速查表与一句话记忆
| 工具/方法 | 特点 | 适用 |
|---|---|---|
| 网格搜索 | 穷举组合 | 维度极低 |
| 随机搜索 | 随机采样 | 维度高、快速基线 |
| 贝叶斯优化 | 历史建模+探索利用 | 中等预算精调 |
| Optuna/TPE | 剪枝+并行+持久化 | 生产级 HPO 默认 |
| AutoGluon | 分层集成 | 精度至上、少调参 |
| FLAML | 轻量快速 | 预算紧张 |
一句话记忆:AutoML/超参调优 = 用搜索算法替代人工猜参——维度低用网格、维度高用随机、要精调用贝叶斯优化(Optuna TPE);Optuna 用 suggest_ 声明空间、剪枝淘汰差组合、并行+SQLite 持久化;特征开关和模型家族也能作为超参自动搜索;要省心用 AutoGluon/FLAML 一键跑出基线。记住「调参结果只在验证集上选、测试集只评估一次」。*
延伸阅读
- /ml-model-evaluation/ — 交叉验证与超参调优基础
- /ml-ensemble-learning/ — 树集成模型与调参对象
- /ml-feature-engineering/ — 特征侧优化
- /ml-model-deployment/ — 调优后的模型上线
- [[ai-ml]] — 算法原理深度专题
- Optuna 文档
- AutoGluon 文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。