AutoML折腾手记

半年前做图像分类,ResNet-50 手动调参调到实验记录一团糟,后来依次试了 Optuna、AutoKeras、AutoGluon。下面按踩坑顺序记。

动手前的几个问题

开搞前先定三个坐标:搜索空间、评估指标、时间预算。

AutoML 解决的问题主要分三类:数据预处理、模型选择、超参数优化。数据预处理还好,虽然各家实现细节不同,但目标是明确的。模型选择和超参数优化就复杂多了,这里面涉及到搜索空间、评估策略、计算资源、时间约束、模型复杂度和泛化能力的平衡。

AutoML 也不会替你定搜索空间——工程师的判断从"手拧参数"挪到了"定义搜索范围和停止条件"。

从手动调参到 Optuna

先说我的起点。半年前做图像分类任务,用的是 ResNet-50。一开始只是想提高点准确率,结果就是不停地调:

# 最开始的调参方式
model = models.resnet50(pretrained=True)
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)

# 训练循环
for epoch in range(10):
    train_one_epoch(...)
    validate(...)
    scheduler.step()

这种方式的问题很明显:每次只能试一个配置,跑了10个epoch才知道这个学习率不行;调整了一个参数又不知道和其他参数的交互;最后搞出一堆乱七八糟的实验记录,根本记不清哪个结果对应哪组参数。

后来用 Optuna,把这个问题向前推进了一步。Optuna 是一个超参数优化框架,核心思想是把调参过程形式化成一个目标函数,然后通过各种采样策略来搜索最优参数。

import optuna

def objective(trial):
    # 定义搜索空间
    lr = trial.suggest_loguniform('lr', 1e-5, 1e-3)
    weight_decay = trial.suggest_loguniform('weight_decay', 1e-6, 1e-4)
    optimizer_name = trial.suggest_categorical('optimizer', ['Adam', 'SGD', 'AdamW'])

    model = models.resnet50(pretrained=True)
    if optimizer_name == 'Adam':
        optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay)
    elif optimizer_name == 'SGD':
        optimizer = optim.SGD(model.parameters(), lr=lr, momentum=0.9, weight_decay=weight_decay)
    elif optimizer_name == 'AdamW':
        optimizer = optim.AdamW(model.parameters(), lr=lr, weight_decay=weight_decay)

    # 训练和评估
    val_accuracy = train_and_evaluate(model, optimizer, epochs=5)

    return val_accuracy

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=20)

这套流程跑下来,最大的感受是:终于不用靠猜了。Optuna 会记录每一次 trial 的参数和结果,还能通过可视化工具看各个参数对结果的影响。但我很快就踩到了第一个坑。

第一个坑是搜索空间定义。一开始我把学习率范围设成了 1e-51e-1,结果前10个 trial 都在推荐接近 1e-2 的值,模型直接不收敛。改成 1e-51e-3 之后,问题缓解了,但这就是典型的 “你先得知道大概范围,才能让算法帮你找精确值”。如果你连基础都不知道,搜索空间定义错了,AutoML 也不会帮你纠错。

第二个坑是评估时间。每个 trial 跑 5 个 epoch,20 个 trial 就要跑 100 个 epoch。虽然中途可以用 early stopping,但总体的时间成本还是很高。我后来改成用 validation set 在训练过程中监控,提前终止表现不好的 trial,但这种方式在搜索前期容易错过后期才体现优势的配置。

第三个坑更隐蔽:过拟合到验证集。当我用 validation accuracy 作为优化目标时,Optuna 会拼命找在这套数据上表现最好的配置,但这些配置可能在真正的测试集上泛化能力很差。这是一个典型的 AutoML 困境:优化目标定义得太窄,就会局部最优;定义得太宽,搜索效率又会下降。

flowchart LR A[定义搜索空间] --> B[采样策略生成参数] B --> C[训练模型] C --> D[评估性能] D --> E{是否达到停止条件} E -->|否| B E -->|是| F[返回最佳参数] style A fill:#e3f2fd style B fill:#bbdefb style C fill:#90caf9 style D fill:#64b5f6 style E fill:#42a5f5 style F fill:#2196f3

这个流程图说清楚了 Optuna 的基本工作方式,但没说清楚的是:每一步都需要工程师的判断。搜索空间定义、采样策略选择、停止条件设置、评估指标选择,这些决策最终决定了 AutoML 的上限。

从 Optuna 到 AutoKeras

Optuna 解决了超参数搜索的问题,但模型选择还是得手动。我试了一下 AutoKeras,它的定位是 “Automated Machine Learning for Deep Learning”,承诺从数据预处理到模型选择再到超参数优化都自动完成。

安装直接用 pip:

pip install autokeras tensorflow==2.13.0

这里有个版本问题,AutoKeras 对 TensorFlow 版本有严格要求。我一开始装了 TensorFlow 2.15,结果一运行就报错:AttributeError: module 'tensorflow' has no attribute 'keras'。查了文档才发现 AutoKeras 1.1.0 只支持到 TensorFlow 2.13,这点官方文档写得不够明显。

基础用法很简单:

import autokeras as ak
import tensorflow as tf

# 假设我们有 CIFAR-10 类型的数据
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()

clf = ak.ImageClassifier(
    max_trials=10,
    objective='val_accuracy',
    overwrite=True,
    directory='my_autokeras_dir'
)

clf.fit(x_train, y_train, epochs=20, validation_split=0.2)
results = clf.evaluate(x_test, y_test)

这套流程跑下来,效果还不错。AutoKeras 会自动搜索不同的网络结构(ResNet、DenseNet、自定义卷积网络),同时调整超参数。max_trials 控制搜索的轮数,每轮都会尝试一个新的网络结构。

但我很快就遇到了问题。第一个问题是显存。AutoKeras 默认会尝试一些较大的网络,在 GPU 有限的情况下直接爆显存:

ResourceExhaustedError: OOM when allocating tensor with shape[32,64,28,28]

解决方案是限制模型复杂度:

clf = ak.ImageClassifier(
    max_trials=10,
    objective='val_accuracy',
    overwrite=True,
    max_model_size=10000000,  # 限制模型参数数量
    directory='my_autokeras_dir'
)

第二个问题是可解释性。AutoKeras 训练完之后,你很难知道它到底选了什么网络结构,以及为什么这么选。虽然可以导出最终模型:

model = clf.export_model()
model.save('my_model.keras')

但中间的搜索过程、失败的网络结构、各轮次的对比信息,都藏在 my_autokeras_dir 里,需要手动查看日志才能理解。这对于研究和工程部署都是一个挑战。

第三个问题是最实际的:时间。每跑一轮 trial,AutoKeras 都要完整训练一个模型,10 轮下来就是 10 个完整训练周期。即使你用 early stopping,对于大型数据集来说,这个时间成本依然是不可接受的。

timeline title AutoKeras 搜索时间线 section Trial 1 初始化网络结构 : 0-5min 训练 10 epochs : 5-45min 评估和记录 : 45-50min section Trial 2-10 重复上述过程 : 50-450min section 最终阶段 模型集成 : 450-480min 导出和优化 : 480-500min

这个时间线说的是一个中型数据集的情况。对于更大的数据集,每轮 trial 的时间会线性增长。这意味着 AutoKeras 在实际生产环境中,往往只能用来做模型选择的前期探索,而不是端到端的自动化流程。

尝试 AutoGluon

AutoKeras 之后,我试了一下 AutoGluon。这是 Amazon 开源的 AutoML 框架,特点是覆盖的任务类型更广,包括表格数据、文本、图像、时间序列等。

安装:

pip install autogluon.tabular[all]
pip install autogluon.vision[all]

AutoGluon 的接口设计比 AutoKeras 更简洁一些,而且对表格数据的支持非常成熟。这是我做表格任务时的代码:

from autogluon.tabular import TabularPredictor

train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')

label = 'target'

predictor = TabularPredictor(
    label=label,
    path='ag_predictor',
    eval_metric='roc_auc'
).fit(
    train_data,
    presets='best_quality',
    time_limit=3600,  # 1小时时间限制
    num_bag_sets=1
)

results = predictor.predict(test_data)

AutoGluon 的一个亮点是 presets 参数,它可以快速切换不同的搜索策略。'best_quality' 会尝试更多模型和更长时间,而 'good_quality_faster_train' 会快速给出一个不错的基线。这种权衡设计在实际工作中很有用:你可以先用快速模式定一个基线,再在需要的时候切换到高质量模式。

另一个亮点是自动集成。AutoGluon 会训练多个模型,然后根据它们在验证集上的表现进行加权集成:

leaderboard = predictor.leaderboard()
print(leaderboard)

输出示例:

      model   score_test  score_val  pred_time_test  fit_time  pred_time_val  fit_order
0   WeightedEnsemble_L2     0.9234     0.9185          0.1234   450.210        0.1178         15
1   LightGBM_BAG_L1         0.9189     0.9145          0.0891   120.345        0.0856         10
2   XGBoost_BAG_L1          0.9167     0.9123          0.0934   135.678        0.0899         11
3   CatBoost_BAG_L1         0.9152     0.9108          0.0976   140.123        0.0923         12

从结果可以看出,集成模型的表现确实比单个模型要好一些,但提升幅度并不大(从 0.9189 到 0.9234)。这里就有一个实际判断:为了这 0.5% 的提升,是否值得多付出 2 倍的推理时间和复杂度?在生产环境中,这个答案往往是 “不一定”。

AutoGluon 的问题在于配置灵活性。虽然 presets 很方便,但当你想要精细控制搜索空间时,AutoGluon 的 API 就显得有些不够用。比如你想限制某个模型的搜索范围,或者自定义集成策略,AutoGluon 的文档和示例就不够充分。

实践中的几个判断

折腾一圈,几条个人判断(场景有限,别当通识):

AutoML 省的是重复 trial 的时间,搜索空间和优化目标还得自己定。搜索空间写错、目标选窄,结果常常不如手动调参。

第二个判断:前期探索用 AutoML,后期优化用手动。AutoML 非常适合快速尝试不同的模型和参数组合,找到一个不错的基线。但当你需要挤最后 1-2% 的准确率时,手动调参往往更有效。这时候你已经对数据和模型有了理解,可以针对性地调整某些参数,而不是让算法在巨大的搜索空间里盲目搜索。

第三个判断:约束比自由更重要。AutoML 的成功很大程度上取决于你如何约束搜索空间。一开始我就犯过错误,给算法留了太大的自由度,结果它要么跑偏,要么效率低下。后来我学会了先做小范围搜索,逐步扩展空间,或者根据领域知识提前排除明显不合理的组合。

第四个判断:时间预算要明确。AutoML 很容易变成一个无底洞,你总觉得再跑一轮可能就会更好。但现实是,边际收益递减:前 5 轮 trial 可能就能提升 5%,后 50 轮可能只能提升 0.5%。设定明确的时间预算,在预算范围内做最好的搜索,比无限期地跑下去更实际。

flowchart LR A[问题定义] --> B[数据预处理] B --> C[快速基线] C --> D{是否满足需求} D -->|是| E[部署优化] D -->|否| F[AutoML搜索] F --> G{时间预算} G -->|未用完| F G -->|已用完| H[手动微调] H --> D style C fill:#c8e6c9 style F fill:#fff9c4 style H fill:#ffe0b2 style E fill:#b3e5fc

这个流程图说的是一个比较实际的 AutoML 使用方式:先快速做一个基线,判断是否需要进一步优化;如果需要,再启用 AutoML 搜索,同时控制时间预算;如果 AutoML 还是不够,就切换到手动调参。这个过程不是线性的,而是一个循环。

环境和版本说明

记录一下折腾过程中用到的环境和版本,避免大家踩同样的坑。

# Python 环境
Python 3.10.12

# 主要包版本
numpy==1.24.3
pandas==2.0.3
scikit-learn==1.3.0

# Optuna
optuna==3.3.0

# AutoKeras 和 TensorFlow
autokeras==1.1.0
tensorflow==2.13.0

# AutoGluon
autogluon.tabular==0.8.0
autogluon.vision==0.8.0

# GPU 环境
CUDA 11.8
cuDNN 8.6.0
NVIDIA GeForce RTX 3090

这些版本组合在我的环境中是稳定的,但并不代表是最佳组合。特别是 TensorFlow 版本,AutoKeras 1.1.0 只支持到 2.13,这在当前 TensorFlow 2.16 已经发布的背景下,显得有些落后。

踩坑记录

最后记录几个具体坑,避免大家重复踩。

坑一:Optuna 的 loguniform 参数范围错误。一开始我写成了 trial.suggest_loguniform('lr', 0.00001, 0.001),结果参数值一直在 0.00001 附近徘徊。改成字符串形式 '1e-5''1e-3' 才解决了问题。

坑二:AutoKeras 的 overwrite=True 不生效。我在搜索过程中改了配置,结果 AutoKluars 还是用的旧配置。后来发现需要手动删除 directory 下的内容,或者改用新的目录名。

坑三:AutoGluon 的 presets='best_quality' 在小数据集上过拟合。我有一个只有 5000 条样本的数据集,用了这个 preset 之后,验证集准确率很高,但测试集准确率直接掉了一截。后来改用 'good_quality_faster_train',反而得到了更稳定的结果。

坑四:显存优化。AutoKeras 在搜索大型网络时经常爆显存,最后通过限制 max_model_size 和降低 batch_size 解决。但 batch size 降低之后,训练时间又变长了,这是一个典型的工程权衡。

写在最后

我现在的用法:手动调参定基线 → AutoML 扩搜索(设好时间上限)→ 手动挤最后 1–2%。AutoKeras 适合前期探结构,AutoGluon 表格任务省事,Optuna 控超参最灵活。

TensorFlow 2.13 + AutoKeras 1.1.0 这套版本组合踩过坑,换环境前先看兼容性。指望一键全自动大概率失望;当搜索工具用,配合自己对数据和业务的判断,能省不少重复劳动。

版权声明: 本文首发于 指尖魔法屋-AutoML折腾手记https://blog.thinkmoon.cn/post/184-automl-practice-hyperparameter-model-selection/) 转载或引用必须申明原指尖魔法屋来源及源地址!