学习 AutoML——超参数优化

0 阅读45分钟

我还记得第一次在生产环境中遇到调参很差的机器学习模型。那是 2018 年,我在一家银行工作,该银行部署了一个欺诈检测系统。这个模型是一个梯度提升分类器,特征也都选对了,概念上没有问题,但性能最多只能算平庸。误报率高到合法交易不断被标记,既让客户感到沮丧,也给公司带来了成本。问题不在算法,也不在数据,而在超参数。

后来,我们只花了两天系统性地调优超参数,结合领域直觉和带交叉验证的随机搜索,将学习率调整到 0.05,把 estimators 数量增加到 300,并将最大深度设为 4。结果,在保持相同召回率的同时,模型 precision 提升了 15%。业务影响立竿见影:误报减少了 30%,客户投诉下降,欺诈检测系统终于兑现了它原本的承诺。

这段经历让我彻底意识到一件事:超参数优化不只是技术上的锦上添花,它常常是一个模型“能用”和“不能用”的分水岭。我见过调优良好的“简单”模型,持续胜过调优很差的复杂模型。

Melis 等人在 2018 年发表的一项里程碑研究《On the State of the Art of Evaluation in Neural Language Models》非常有力地证明了这一点:他们精心调优的 LSTM 语言模型,超过了几种当时在研究社区受到大量关注的新架构,原因很简单——那些新模型没有被同等彻底地调优。该论文表明,当你控制超参数优化投入后,所谓 “state-of-the-art” 架构之间的差距往往会消失。这个令人清醒的发现,强调了正确调参的关键重要性。

然而,超参数优化仍然是机器学习中最具挑战的方面之一。搜索空间巨大,评估成本高昂,最优设置又常常违背直觉。几十年来,这一直困扰着实践者,并且至今仍是部署有效 ML 系统的重要瓶颈。

本章中,我们将探索超参数优化技术的演进,从基础搜索策略,到能够以最低计算成本找到优秀配置的复杂方法。我们会考察可以立即实现的实用方法,也会探索正在重塑模型调优思路的前沿技术。最重要的是,我们会聚焦那些在真实世界中有效的方法,因为真实世界里时间和计算资源都是有限的。

超参数优化的挑战

超参数优化本质上是一个 black-box optimization problem,也就是黑盒优化问题。与模型在训练过程中学习到的参数不同,例如神经网络中的权重,超参数是在训练开始前必须由我们选择的配置项。它们包括学习率、正则化强度、网络架构、树深度、batch size,以及大量其他会显著影响模型表现的选择。

这个问题的 “black box” 性质意味着,我们没有一个分析公式可以引导我们找到最优设置。我们不能求导,也不能直接应用梯度下降。相反,我们必须通过实际训练和验证模型来评估每一个候选配置。这个过程可能耗时几分钟,也可能耗时数天,具体取决于模型复杂度和数据集大小。

以我最近为医学图像分析调优一个计算机视觉模型时面对的挑战为例。超参数空间包括:

  • Learning rate:范围可能从 1e-51e-1,最佳实践建议在对数尺度上尝试取值。
  • Batch size:从 8 到 128 的 2 的幂值,受 GPU 内存约束。
  • Network depth:层数从 18 到 152。
  • Dropout rate:从 0.0 到 0.8。
  • Weight decay:正则化强度从 1e-61e-2
  • Optimizer choice:Adam、SGD、RMSprop 或 AdamW。
  • Learning rate schedule:constant、step decay、cosine annealing 或 exponential decay。

即使只有这 7 个超参数,搜索空间也包含数百万种可能组合。评估每个配置都需要在 10 万张图像的数据集上训练模型 50 个 epochs,使用现代 GPU 也大约需要两小时。朴素的穷举搜索需要数百年。

计算成本挑战

随着大语言模型兴起,超参数评估的计算成本变得更加令人望而却步。最近一项在 ML 社区引发关注的研究,展示了这个问题的极端状态。研究人员花费接近 100 万 GPU-hours,训练了 3700 个不同模型变体,系统性地绘制大语言模型中超参数的 scaling laws。虽然这种 brute-force 方法为研究社区带来了宝贵洞察,但对普通实践者而言完全不可行。

这种计算瓶颈推动了超参数优化中的大量创新。目标不只是找到好的超参数,而是要高效地找到它们,用尽可能少的昂贵评估完成搜索。近年来 multifidelity hyperparameter optimization 取得了显著进展,layer freezing 等技术可以在保持不同 fidelity levels 下超参数排序强相关的同时,大幅节省计算和内存。

敏感性问题

并不是所有超参数都同等重要。有些会对模型表现产生巨大影响,有些则几乎无关紧要。理解这种敏感性,对于高效优化至关重要。

根据我的经验,某些领域和模型类型对超参数选择尤其敏感:

Reinforcement learning:我见过 RL 训练因为糟糕超参数选择而失败。学习率、探索参数和 reward scaling,可能决定一个 agent 是有效学习,还是永远无法收敛。

Generative adversarial networks(GANs) :GANs 以难调著称。学习率、batch size 或架构选择上的微小变化,都可能导致训练彻底崩溃。

Large language models and transformers:训练 LLMs 需要精细协调 learning rate warm-up schedules、weight decay 和 batch size。我见过 transformer 训练因为学习率高了 2 倍而完全发散,也见过因为 warm-up steps 不足而收敛到次优解。这些超参数之间的相互作用尤其复杂——batch size 不只影响训练速度,也会影响最优学习率。

Time series forecasting:lookback window、seasonality parameters 和 trend components 往往需要针对每个数据集的时间模式仔细调优。

不过,我也学到 context 极其重要。Gijsbers 等人在 2022 年作为 AutoML Benchmark 项目的一部分发表了一项大规模研究,分析了 250 个数据集上的 2800 多万次算法运行,揭示了一个更细致的图景:对许多 ML 算法而言,默认超参数平均表现已经相当不错;然而,在某些特定数据集上,正确优化会带来变革性提升,尤其是默认表现很差的时候。这个基于 OpenML benchmark suite 的系统分析,是迄今为止关于“什么时候超参数调优最重要”的最全面证据。

这一发现塑造了我在实践中处理超参数优化的方法。对于标准数据集上的常规问题,快速调优一轮可能就足够。但对于任务关键应用或新颖问题领域,彻底的超参数优化可能是失败和 state-of-the-art 结果之间的差别。

真实世界影响

有效超参数优化的业务影响可能非常显著。下面是我咨询工作中的几个例子:

Customer churn prediction:我合作过的一家零售公司使用 random forest 模型预测客户流失,默认超参数下准确率约为 78%。经过使用 Bayesian optimization 的系统性调优后,我们将准确率提升到 85%,增加了 7 个百分点,使公司每月能够识别出多出数千名高风险客户。

Fraud detection:我前面提到的银行案例并非孤例。我在多家金融服务公司中都见过类似情况,超参数优化在保持检测率的同时,将误报率降低了 20%–40%。

Supply chain optimization:一家零售公司使用 gradient boosting 预测数千个 SKU 的需求。更好的超参数优化使预测准确率提升了 12%,每年减少了数百万美元的缺货和过量库存损失。

Grid Search 与 Random Search:打好基础

在深入复杂优化技术之前,必须先理解大多数实践者最早接触的两种 baseline 方法:grid search 和 random search。虽然这些方法看起来简单,但它们揭示了超参数优化中的基本洞察,也为更高级技术奠定基础。


Grid Search:系统但有限

Grid search 是最直观的超参数优化方法。你为每个超参数定义一组取值,然后系统性评估每一种可能组合。例如,调优 XGBoost 模型时,你可能指定:

param_grid = {
      'learning_rate': [0.01, 0.1, 0.2],
      'max_depth': [3, 6, 9],
      'n_estimators': [100, 200, 300],
      'subsample': [0.8, 0.9, 1.0]
}

这个 grid 包含 3 × 3 × 3 × 3 = 81 种组合,需要 81 次独立模型训练。

Grid search 有一些吸引人的特性:

  • 可复现:给定相同 grid,总是得到相同结果。
  • 全面:在指定范围内,不会漏掉任何组合。
  • 易于并行化:每次评估都是独立的。
  • 适合可视化:结果可以绘制成 heatmaps 或 contour plots。

不过,当问题变复杂时,grid search 的根本限制就会显现:

维度灾难:增加超参数或取值数量,会导致评估数量指数级增长。把前面的 XGBoost 示例稍微扩展一下,加入正则化参数,就可能超过 500 种组合。

资源分配低效:Grid search 会给所有维度分配相同计算资源,即使有些超参数远比其他超参数重要。

离散采样:Grid search 可能错过落在 grid points 之间的最优值。如果最佳学习率是 0.05,但你的 grid 只有 [0.01, 0.1],你永远找不到它。

Random Search:意外有效的替代方案

Random search 由 Bergstra 和 Bengio 在 2012 年引入 ML 社区,它会从指定分布中随机采样超参数组合。不是固定 grid,而是定义类似这样的分布:

param_distributions = {
      'learning_rate': uniform(0.01, 0.3),  # Uniform between 0.01 and 0.31
      'max_depth': randint(3, 10),          # Random integer between 3 and 9
      'n_estimators': randint(50, 500),     # Random integer between 50 and 499
      'subsample': uniform(0.5, 0.5)        # Uniform between 0.5 and 1.0
}

在相同计算预算下,例如 81 次评估,random search 会探索更多样化的配置集合。

让 random search 出名的关键洞察是:当只有一部分超参数显著影响表现时,random search 更有可能找到重要超参数的好取值。

原因是这样的:假设你在调 5 个超参数,但只有 1 个对当前数据集真正重要。如果每个参数设 5 个取值,grid search 会把 80% 的评估浪费在改变不重要参数上,同时固定重要参数。相比之下,random search 在大多数 trial 中都会为关键参数尝试不同取值。

一个实践对比

我用一个真实工作案例说明。曾经我帮助一家医疗健康初创公司调优一个患者再入院风险预测模型。模型有 6 个超参数,但我们的领域知识表明,regularization strength 和 feature selection threshold 最关键。

Grid search 结果(100 次评估)

  • 最佳 validation AUC:0.847
  • 找到最佳结果的时间:第 89 次评估
  • 重要参数空间覆盖:仅限 grid points

Random search 结果(100 次评估)

  • 最佳 validation AUC:0.856
  • 找到最佳结果的时间:第 34 次评估
  • 重要参数空间覆盖:采样更广

Random search 不仅找到了更好的超参数,而且在搜索过程中更早发现它们。这种早期发现非常重要,因为实践中,一旦找到足够好的结果,你往往可以提前停止搜索。

现代实现

这两种方法在现代 ML 库中都有很好支持:

from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from xgboost import XGBClassifier
 
# Grid Search
grid_search = GridSearchCV(
      XGBClassifier(),
      param_grid,
      cv=5,
      scoring='roc_auc',
      n_jobs=-1  # Use all available cores
)
 
# Random Search
random_search = RandomizedSearchCV(
      XGBClassifier(),
      param_distributions,
      n_iter=100,  # Number of random samples
      cv=5,
      scoring='roc_auc',
      n_jobs=-1
)

对于更高级的 random search,Ray Tune 和 Optuna 等库提供额外能力:

import optuna
 
def objective(trial):
      params = {
      'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
      'max_depth': trial.suggest_int('max_depth', 3, 10),
      'n_estimators': trial.suggest_int('n_estimators', 50, 500),
      'subsample': trial.suggest_float('subsample', 0.5, 1.0)
      }
 
      model = XGBClassifier(**params)
      score = 
        cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc').mean()
      return score
 
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

什么时候使用哪种方法

根据我的经验,grid 和 random search 的选择取决于几个因素。

适合使用 grid search 的情况:

  • 你对每个超参数的好取值范围有很强直觉。
  • 超参数空间维度较低,通常是 2 到 4 个参数。
  • 你想系统性可视化参数影响。
  • 可复现性至关重要。

适合使用 random search 的情况:

  • 你在探索高维空间,通常 5 个以上参数。
  • 你不确定最优取值范围。
  • 你希望在有限评估预算下最大化覆盖范围。
  • 你愿意用部分可复现性换取更好探索能力。

许多实践者会采用两阶段流程:先用 random search 识别有前景区域,再用聚焦的 grid search 精调最佳候选区域。这种混合方法结合了 random search 的探索优势和 grid search 的系统覆盖。

我的默认建议是:如果你不确定从哪里开始,就从 random search 开始。它实现简单、天然易并行,并且在超过 2 到 3 个超参数的问题上,几乎总是优于 grid search。我只在需要系统可视化参数交互,或者搜索空间真的很小的时候,才保留 grid search。

两种方法的局限

虽然 random search 通常优于 grid search,但两者都有一个根本限制:它们不会从之前的评估中学习。每个 trial 都是独立的,因此早期评估的信息不会影响后续选择。

这正是更复杂方法登场的地方。如果 random search 发现 0.05 附近的 learning rates 表现很好,那么后续 trials 是否应该集中在这个区域?这个直觉引出了下一个主题:Bayesian optimization,它会显式建模并从过往评估中学习,以指导未来搜索。

Bayesian Optimization:从经验中学习

从 random search 到 Bayesian optimization 的跨越,代表我们处理超参数优化方式的一次根本转变。它不再将每次评估视为独立事件,而是构建一个概率模型,描述超参数如何影响模型表现,并利用这个模型智能选择下一组最有前景的配置。

我第一次接触 Bayesian optimization 是在 2016 年做一个计算机视觉项目时。当时我们正在调优一个卷积神经网络,它有几十个超参数,random search 慢得令人沮丧。一位同事建议试试当时相对较新的库 Spearmint,它实现了 Bayesian optimization。结果非常惊人:相比 random search,我们只用了三分之一的评估次数,就找到了更好的超参数。

核心洞察

Bayesian optimization 解决的是 grid search 和 random search 的一个根本低效:这些方法忽略了之前评估中的宝贵信息。如果我们已经尝试了 50 种不同超参数配置,并观察到了它们的表现,为什么不利用这些信息指导下一次选择?

Bayesian 方法包含两个关键组件:

Surrogate model:一个评估成本很低的模型,用于近似真实且昂贵的 objective function。

Acquisition function:一种策略,用于基于 surrogate model 决定下一个要评估的点。

典型 Bayesian optimization 循环如下:

  1. Initialize:先随机评估几个超参数配置作为起点。
  2. Model:将 surrogate model 拟合到所有已观察到的 (hyperparameter, performance) 对上。
  3. Acquire:使用 acquisition function 选择下一个最有前景的配置。
  4. Evaluate:用这些超参数训练并验证模型。
  5. Update:将新结果加入数据集,然后从第 2 步开始重复。

Surrogate Models

Surrogate model 的选择至关重要。它需要训练和评估快,能够处理我们正在优化的超参数类型,包括 continuous、discrete、categorical,并且能提供 uncertainty estimates。

Gaussian processes(GPs)

GP 是最早的选择,至今仍然流行。GP 天然提供 uncertainty estimates,这对平衡 exploration 和 exploitation 很重要。不过,GP 在高维空间和 categorical parameters 上可能表现吃力。

Random forests

Random forests 已被证明在超参数优化中非常有效。Auto-sklearn 中使用的 SMAC 算法就使用 random forest surrogates,并在多种问题上展示了优秀表现。Random forests 可以自然处理混合类型参数,并且比 GP 更容易扩展。

Tree-structured Parzen estimators(TPE)

TPE 采用不同方法,分别建模好超参数值和坏超参数值的分布。TPE 是 Optuna 的默认方法,并且已经被证明既稳健又高效。

Acquisition Functions

Acquisition function 决定如何使用 surrogate model 来选择下一次评估点。好的 acquisition function 会平衡两个相互竞争的目标:

Exploitation:尝试 surrogate model 预测表现高的超参数区域。

Exploration:尝试 surrogate model 不确定性高的超参数区域。

标准 acquisition functions 包括:

Expected Improvement(EI) :尝试最大化相比当前最佳结果的期望提升。数学上优雅,但可能过于保守。

Upper Confidence Bound(UCB) :优化 performance 的 upper confidence bound,并用可调参数控制 exploration 和 exploitation。

Probability of Improvement(PI) :最大化超过当前最佳结果的概率。

在实践中,我发现 acquisition function 的选择不如 surrogate model 的选择重要,但当性能至关重要时,值得实验。

真实世界成功案例

Bayesian optimization 的有效性不只是理论。下面分享几个我亲身经历的具体例子。

NLP model tuning

一家银行正在构建文档分类系统。200 次评估的 random search 达到 89.2% 准确率。在相同预算下,Bayesian optimization 达到 91.7% 准确率,而且只用了 80 次评估就找到了最佳结果。

Reinforcement learning

我帮助一家金融科技初创公司调优 RL 训练超参数。RL 以超参数敏感著称,random search 的结果非常不稳定。Bayesian optimization 不仅找到了更好的超参数,而且在多次运行中更可靠。

Industrial forecasting

一家零售客户需要为数百条产品线的需求预测调优 ensemble models。Bayesian optimization 将调优时间从数周缩短到数天,同时将预测准确率提升了 8%。

现代工具和实现

Bayesian optimization 已经通过几个优秀库变得非常容易使用。

Optuna

Optuna 是我当前大多数项目的首选。它很快,能处理所有类型的超参数,并且与流行 ML 框架无缝集成:

import optuna
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
 
def objective(trial):
      # Define hyperparameter search space
      n_estimators = trial.suggest_int('n_estimators', 10, 1000)
      max_depth = trial.suggest_int('max_depth', 1, 20)
      min_samples_split = trial.suggest_int('min_samples_split', 2, 20)
      min_samples_leaf = trial.suggest_int('min_samples_leaf', 1, 20)
 
      # Create and evaluate model
      model = RandomForestClassifier(
      n_estimators=n_estimators,
      max_depth=max_depth,
      min_samples_split=min_samples_split,
      min_samples_leaf=min_samples_leaf,
      random_state=42
      )
 
      # Return metric to optimize
      accuracy = cross_val_score(model, X_train, y_train, cv=3).mean()
      return accuracy
 
# Run optimization
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
 
print(f"Best parameters: {study.best_params}")
print(f"Best value: {study.best_value}")

Scikit-Optimize

Scikit-Optimize 提供 BayesSearchCV,它将 Bayesian optimization 与 Scikit-learn 熟悉的接口集成:

from skopt import BayesSearchCV
from sklearn.ensemble import RandomForestClassifier
 
# Define search space
search_space = {
      'n_estimators': (10, 1000),
      'max_depth': (1, 20),
      'min_samples_split': (2, 20),
      'min_samples_leaf': (1, 20)
}
 
# Run Bayesian optimization
bayes_search = BayesSearchCV(
      RandomForestClassifier(random_state=42),
      search_space,
      n_iter=100,
      cv=3,
      n_jobs=-1
)
 
bayes_search.fit(X_train, y_train)

Ray Tune

当你需要将优化分布到多台机器,或者希望把 Bayesian optimization 与其他技术组合时,Ray Tune 非常出色:

from ray import tune
from ray.tune.suggest.optuna import OptunaSearch
 
def trainable(config):
      model = RandomForestClassifier(**config)
      accuracy = cross_val_score(model, X_train, y_train, cv=3).mean()
      tune.report(accuracy=accuracy)
 
search_space = {
      'n_estimators': tune.randint(10, 1000),
      'max_depth': tune.randint(1, 20),
      'min_samples_split': tune.randint(2, 20),
      'min_samples_leaf': tune.randint(1, 20)
}
 
tune.run(
      trainable,
      config=search_space,
      search_alg=OptunaSearch(),
      num_samples=100
)

AWS SageMaker Automatic Model Tuning

对于生产部署,AWS SageMaker Automatic Model Tuning,也称 SageMaker HPO,提供企业级超参数优化托管服务。考虑到 AWS 在云计算中的主导地位,大约 32% 市场份额,这通常是已经在 AWS 生态中的组织的默认选择。

SageMaker HPO 提供几个关键优势:

  • 与 SageMaker Training jobs 原生集成:无需管理基础设施。
  • 多种优化策略:Bayesian optimization、Hyperband 和 random search。
  • Warm starting:通过从之前 tuning jobs 初始化,实现 HPO 的 transfer learning。
  • Auto-scaling:跨训练实例自动并行化。
  • Cost optimization:自动 early stopping 表现差的 trials。

下面是使用 SageMaker Python SDK 的实践示例:

import sagemaker
from sagemaker.tuner import (
      HyperparameterTuner,
      ContinuousParameter,
      IntegerParameter,
      CategoricalParameter
)
from sagemaker.xgboost import XGBoost
 
# Define the estimator
xgb_estimator = XGBoost(
      entry_point='train.py',
      role=sagemaker.get_execution_role(),
      instance_count=1,
      instance_type='ml.m5.xlarge',
      framework_version='1.7-1',
      py_version='py3'
)
 
# Define hyperparameter ranges
hyperparameter_ranges = {
      'eta': ContinuousParameter(0.01, 0.3, scaling_type='Logarithmic'),
      'max_depth': IntegerParameter(3, 10),
      'num_round': IntegerParameter(50, 500),
      'subsample': ContinuousParameter(0.5, 1.0),
      'colsample_bytree': ContinuousParameter(0.5, 1.0),
      'alpha': ContinuousParameter(0, 10),
      'min_child_weight': IntegerParameter(1, 10)
}
 
# Create the tuner with Bayesian optimization
tuner = HyperparameterTuner(
      estimator=xgb_estimator,
      objective_metric_name='validation:auc',
      hyperparameter_ranges=hyperparameter_ranges,
      strategy='Bayesian',  # or 'Hyperband', 'Random'
      max_jobs=100,
      max_parallel_jobs=10,
      early_stopping_type='Auto'  # Auto-terminate poor performers
)
 
# Launch the tuning job
tuner.fit({
      'train': 's3://bucket/train',
      'validation': 's3://bucket/validation'
})
 
# Get best hyperparameters
best_params = tuner.best_training_job()

SageMaker HPO 最强大的功能之一是 warm starting,它允许你利用之前 tuning jobs 中的知识:

from sagemaker.tuner import WarmStartConfig, WarmStartTypes
 
# Create warm start config from previous tuning job
warm_start_config = WarmStartConfig(
      warm_start_type=WarmStartTypes.TRANSFER_LEARNING,
      parents=['previous-tuning-job-name']
)
 
# Create new tuner with warm start
tuner_with_warmstart = HyperparameterTuner(
      estimator=xgb_estimator,
      objective_metric_name='validation:auc',
      hyperparameter_ranges=hyperparameter_ranges,
      strategy='Bayesian',
      max_jobs=50,
      max_parallel_jobs=5,
      warm_start_config=warm_start_config  # Transfer knowledge
)

当你在新数据集上调优相似模型、迭代模型改进,或针对不同客户分群适配模型时,这尤其有价值。

对于使用 AWS 的组织,SageMaker HPO 应该成为生产超参数优化的第一考虑项,它消除了基础设施管理,同时提供稳健优化能力。

高级技术

近年来,Bayesian optimization 出现了几个重要进展:

Multi-objective optimization:我们常常希望同时优化多个指标,例如 accuracy 和 inference speed。Optuna 等现代工具支持这些场景下的 Pareto optimization。

Parallel Bayesian optimization:传统 Bayesian optimization 是顺序执行的,但我们常常希望同时评估多个配置。Batch Bayesian optimization 方法可以一次建议多个有前景的点。

Transfer learning:如果你之前调过相似模型,可以利用这些经验 warm-start 新的优化运行。当使用模型族或相似数据集时,这尤其有价值。

High-dimensional optimization:传统 Bayesian optimization 很难处理大量超参数。近期技术使用 random embeddings 或 trust regions,更有效地处理高维空间。

实践考虑

Bayesian optimization 很稳健,但并不总是正确选择。下面是我基于经验总结的指南。

适合使用 Bayesian optimization 的情况:

  • 单次评估昂贵,超过几分钟。
  • 超参数数量适中,2 到 20 个。
  • 你可以承受 surrogate modeling 的额外开销。
  • 你希望最小化总评估次数。

可以考虑其他方法的情况:

  • 单次评估非常便宜,以秒为单位。
  • 你有 50 个以上超参数。
  • Objective function 噪声非常大。
  • 你需要立即得到结果,无法承受初始探索。

局限与挑战

Bayesian optimization 并不完美。下面是我遇到过的一些局限:

Cold start problem:最初几次评估本质上是随机的,因此需要若干 trials 后,surrogate model 才真正有用。

Noisy objectives:如果 validation scores 在不同运行之间波动很大,这在 RL 或小数据集中常见,surrogate model 可能被误导。

Categorical parameters:虽然现代方法可以处理 categorical variables,但在高基数 categorical parameters 上仍然吃力。

Computational overhead:每次迭代中,构建和优化 acquisition function 都会带来额外开销。

尽管存在这些局限,当评估昂贵且耗时时,Bayesian optimization 已经成为我默认的超参数优化选择。它能从经验中学习,并把搜索努力集中在有前景区域,因此是任何 ML 实践者工具箱中的强大工具。

Early Stopping 与 Scheduling:更聪明地工作,而不是更辛苦地工作

我职业生涯中实现过的最有影响力优化之一,来自一个简单认识:大多数糟糕超参数配置在训练早期就会暴露出来。既然如此,为什么要让每个训练任务都跑到完成?我们能否提前识别并终止没有前景的配置?

这个洞察让我开始探索用于超参数优化的 early stopping 技术。这些方法可以将计算成本降低 10 倍到 100 倍,同时找到同样好甚至更好的超参数。对于那些传统方法成本过高的项目,这些技术是 game changers。

核心洞察

我第一次发现 early stopping 的威力,是在 2019 年为一家计算机视觉初创公司做项目时。当时我们正在调优一个 ResNet 模型,它有几十个超参数,每次完整训练在高端 GPU 上需要 6 小时。Random search 需要数周计算时间。

我们没有让每个配置完整训练 200 个 epochs,而是在训练 10 个 epochs 后就开始监控 validation performance。表现不佳的配置可以提前终止,把资源释放给更有前景的候选项。这个简单改变让总计算时间减少了 80%,同时找到了比完整训练更好的超参数。

Successive Halving:锦标赛式方法

Successive Halving(SHA)用锦标赛式淘汰流程形式化了这个直觉。其工作方式如下:

  1. 从很多配置开始:给大量超参数配置分配少量资源,例如 training epochs。
  2. 淘汰表现最差者:只保留前一部分,例如一半,丢弃其余配置。
  3. 给存活者更多资源:让剩余配置训练更久。
  4. 重复直到剩下一个:持续 halving 和延长训练,直到只剩最佳配置。

举一个具体例子。假设我们有 81 个 training epochs 的总预算,并想探索 27 种不同超参数配置:

  • Round 1:全部 27 个配置各训练 1 个 epoch,总计 27 epochs。
  • Round 2:保留最好的 9 个,每个再训练 3 个 epochs,总计 27 epochs。
  • Round 3:保留最好的 3 个,每个再训练 9 个 epochs,总计 27 epochs。

总预算使用:27 + 27 + 27 = 81 epochs,也就是完整训练一个模型的预算,但我们已经有意义地探索了 27 种不同配置。

WARNING

Successive Halving 假设早期表现能够合理预测最终表现。这个假设对大多数传统 ML 模型成立,但对于使用 learning rate warm-up 或复杂训练计划的 deep learning architectures,可能会失效。某个配置在 5 个 epochs 后看起来很糟,但在 warm-up 完成后的 100 个 epochs 后可能表现优秀。对于这类模型,应考虑使用更宽松的初始预算,或使用 Hyperband,因为它会通过运行多个 brackets 来对冲这一风险。

Hyperband:自动化资源分配

Successive Halving 很有效,但它需要选择初始时如何在配置数量和每个配置训练资源之间分配。Hyperband 通过运行多个使用不同资源分配的 Successive Halving “brackets” 并组合结果,优雅地解决了这个问题。

我发现 Hyperband 在不同问题类型上都非常稳健。在几乎不做问题特定调优的情况下,它能持续找到优秀超参数,同时只使用传统方法所需计算预算的一小部分。

Asynchronous Successive Halving(ASHA)

原始 Successive Halving 算法在现代并行计算环境中有一个限制:它要求 rounds 之间同步。如果你在不同机器完成时间不同的集群上运行 trials,就会出现资源空闲。

Asynchronous Successive Halving(ASHA)通过允许异步评估和晋级解决了这个问题。ASHA 不会等待一轮中的所有配置完成,而是持续晋级有前景 trials,并终止没有前景的 trials。这使它非常适合分布式超参数优化。

我最近使用 ASHA 在 32 个 GPU 的集群上调优一个大型 transformer 模型。异步特性使我们在整个搜索过程中保持了高 GPU 利用率,并在与训练单个模型相同的 wall-clock time 内完成了超参数优化。

Population-Based Training:训练期间的进化

Population-Based Training(PBT)采用不同方法,它会维护一个在训练过程中持续演化的模型 population。PBT 会周期性执行:

  • 评估 population,即检查每个模型表现如何。
  • 替换表现差的模型,即复制表现更好模型的权重。
  • 突变超参数,即对复制模型的超参数应用小幅随机变化。

这允许超参数在训练期间变化,可能发现静态优化会错过的动态 schedules,例如 learning rate decay patterns。

我在 reinforcement learning 中使用 PBT 取得过特别好的效果。它发现了能够适应训练不同阶段的学习率 schedules,而这些几乎不可能用传统超参数优化找到。

TIP

PBT 的实现复杂度显著高于前面讨论的方法。它需要用于 population management、checkpoint handling 和 asynchronous training 的基础设施。我主要建议在较长训练运行,也就是数小时到数天,且 overhead 值得投入,并且 learning rate 等超参数受益于动态调整时使用它。对于快速实验或传统 ML 模型,Hyperband 或 Bayesian optimization 等更简单方法通常能带来更好的工程投入回报。

Layer Freezing:一种新的 Fidelity 维度

Multifidelity optimization 的一个最新突破,是引入 layer freezing 作为新的 fidelity 维度。该方法不再只改变 training epochs 或 dataset size,而是把 deep learning 训练过程中训练层数与冻结层数作为 fidelity 来源。

这种技术可以显著节省计算和内存,同时在低 fidelity 与完整模型训练之间保持很强的超参数排序相关性。该方法本质上把 deep learning model training process 本身打开给优化,而不再把它视为黑盒。

实践实现

现代 ML 框架让这些技术容易使用。对于 cloud native 实现,AWS SageMaker HPO 内置支持 early stopping 和 Hyperband scheduling,无需额外代码。只要在 tuner 配置中设置 early_stopping_type='Auto'strategy='Hyperband' 即可。

使用 Optuna:

import optuna
 
def objective(trial):
      # Define hyperparameters
      lr = trial.suggest_loguniform('lr', 1e-5, 1e-1)
      batch_size = trial.suggest_categorical('batch_size', [16, 32, 64, 128])
 
      model = create_model(lr=lr, batch_size=batch_size)
 
      for epoch in range(100):
      # Train for one epoch
      loss = train_epoch(model)
      val_accuracy = validate(model)
 
      # Report intermediate result
      trial.report(val_accuracy, epoch)
 
      # Check if the trial should be pruned
      if trial.should_prune():
            raise optuna.TrialPruned()
 
      return val_accuracy
 
# Use MedianPruner for early stopping
study = optuna.create_study(
      direction='maximize',
      pruner=optuna.pruners.MedianPruner()
)
study.optimize(objective, n_trials=100)

使用 Ray Tune:

from ray import tune
from ray.tune.schedulers import ASHAScheduler
 
def trainable(config):
      model = create_model(**config)
 
      for epoch in range(100):
      loss = train_epoch(model)
      val_accuracy = validate(model)
 
      # Report intermediate result
      tune.report(accuracy=val_accuracy)
 
config = {
      'lr': tune.loguniform(1e-5, 1e-1),
      'batch_size': tune.choice([16, 32, 64, 128])
}
 
scheduler = ASHAScheduler(
      metric='accuracy',
      mode='max',
      max_t=100,           # Maximum number of epochs
      grace_period=10,     # Don't eliminate before epoch 10
      reduction_factor=2   # Keep half at each round
)
 
tune.run(
      trainable,
      config=config,
      num_samples=100,
      scheduler=scheduler
)

真实世界结果

Early stopping 技术的影响可能非常显著。以下是我近期项目中的一些结果:

Natural language processing:调优一个用于文档分类的 BERT-based 模型。传统 50 次 trials 的 random search 大约需要 4 天。ASHA 在 8 小时内找到了更好的超参数。

Computer vision:优化语义分割模型。Hyperband 将计算时间减少 15 倍,并相比 baseline configuration 将 mIoU 提高了 3.2 个百分点。

Time series forecasting:为金融预测调优 ensemble models。Early stopping 将优化时间从 3 周缩短到 2 天,同时保持预测准确率。

与 Bayesian Optimization 结合

一些最强大的超参数优化系统,会把 Bayesian optimization 和 early stopping 结合起来。BOHB(Bayesian Optimization + HyperBand)使用 Bayesian optimization 选择要评估的配置,同时利用 Hyperband 的 early stopping 机制高效分配计算资源。

BOHB 代表 multifidelity optimization 的当前 state-of-the-art,它结合了智能配置选择和高效资源分配。下面是用 Ray Tune 实现 BOHB 的方式:

from ray import tune
from ray.tune.schedulers import HyperBandForBOHB
from ray.tune.search.bohb import TuneBOHB
import ConfigSpace as CS
 
def trainable(config):
      model = create_model(**config)
 
      for epoch in range(100):
      loss = train_epoch(model)
      val_accuracy = validate(model)
      tune.report(accuracy=val_accuracy, training_iteration=epoch)
 
# Define configuration space for BOHB
config_space = CS.ConfigurationSpace()
config_space.add_hyperparameters([
      CS.UniformFloatHyperparameter('lr', lower=1e-5, upper=1e-1, log=True),
      CS.UniformIntegerHyperparameter('batch_size', lower=16, upper=128),
      CS.UniformFloatHyperparameter('dropout', lower=0.1, upper=0.5),
      CS.UniformIntegerHyperparameter('hidden_size', lower=64, upper=512)
])
 
# Create BOHB search algorithm
bohb_search = TuneBOHB(
      space=config_space,
      metric='accuracy',
      mode='max'
)
 
# Create HyperBand scheduler for BOHB
bohb_scheduler = HyperBandForBOHB(
      time_attr='training_iteration',
      metric='accuracy',
      mode='max',
      max_t=100,         # Maximum training iterations
      reduction_factor=3   # Aggressive early stopping
)
 
# Run BOHB optimization
analysis = tune.run(
      trainable,
      search_alg=bohb_search,
      scheduler=bohb_scheduler,
      num_samples=100,
      resources_per_trial={'cpu': 2, 'gpu': 1}
)
 
print(f"Best config: {analysis.best_config}")
print(f"Best accuracy: {analysis.best_result['accuracy']}")

也可以使用原始 HpBandSter 库,它提供 BOHB 的 reference implementation:

import hpbandster.core.nameserver as hpns
import hpbandster.core.result as hpres
from hpbandster.optimizers import BOHB as BOHB_optimizer
from hpbandster.core.worker import Worker
import ConfigSpace as CS
import ConfigSpace.hyperparameters as CSH
 
class MLWorker(Worker):
      def compute(self, config, budget, **kwargs):
      """
      Train model with given config and budget (number of epochs)
      """
      model = create_model(
            lr=config['lr'],
            batch_size=config['batch_size'],
            dropout=config['dropout']
      )
 
      # Train for 'budget' epochs
      for epoch in range(int(budget)):
            train_epoch(model)
 
      val_accuracy = validate(model)
      return {'loss': 1 - val_accuracy, 'info': {'val_accuracy': val_accuracy}}
 
      @staticmethod
      def get_configspace():
      cs = CS.ConfigurationSpace()
      cs.add_hyperparameters([
         CSH.UniformFloatHyperparameter('lr', lower=1e-5, upper=1e-1, log=True),
         CSH.UniformIntegerHyperparameter('batch_size', lower=16, upper=128),
         CSH.UniformFloatHyperparameter('dropout', lower=0.1, upper=0.5)
        ])
      return cs
 
# Start nameserver and worker
NS = hpns.NameServer(run_id='bohb_example', host='127.0.0.1', port=None)
NS.start()
 
worker = MLWorker(nameserver='127.0.0.1', run_id='bohb_example')
worker.run(background=True)
 
# Run BOHB
bohb = BOHB_optimizer(
      configspace=MLWorker.get_configspace(),
      run_id='bohb_example',
      min_budget=10,   # Minimum epochs
      max_budget=100,  # Maximum epochs
      eta=3              # Reduction factor
)
result = bohb.run(n_iterations=50)
 
# Get best configuration
id2config = result.get_id2config_mapping()
incumbent = result.get_incumbent_id()
print(f"Best config: {id2config[incumbent]['config']}")

这种组合往往提供两者优势:Bayesian optimization 的智能搜索引导,以及 early stopping 的计算效率。

Early Stopping 什么时候最有效

Early stopping 技术在以下情况下最有效:

  • Learning curves 具有预测性:早期表现差的配置通常后期也表现差。
  • 训练昂贵:完整评估成本足以 justify early stopping 的复杂度。
  • 你有并行资源:多个配置可以同时评估。

Early stopping 在以下情况下效果较弱:

  • Learning curves 具有欺骗性:有些模型开始慢,但后期会赶上,尽管实践中较少见。
  • 训练非常快:监控和调度的 overhead 不值得。
  • Objective 很嘈杂:早期表现无法预测最终表现。

一个警示

虽然 early stopping 很强大,但需要谨慎实现和考虑。我见过实践者犯几个常见错误:

Inconsistent evaluation:比较不同训练阶段的模型可能具有误导性。

Overly aggressive pruning:过早停止 trials 可能淘汰有前景配置。

Ignoring warm-up periods:某些模型需要时间稳定,之后其表现才具有预测性。

尽管有这些注意事项,early stopping 和 scheduling 技术仍是超参数优化中最重要的实践进展之一。它们让调优复杂模型变得可行,而这些模型原本需要高到不可接受的计算资源。

Multifidelity Optimization:不只是简单 Early Stopping

到目前为止,我们讨论的技术,即 early stopping 和 scheduling,代表了 multifidelity optimization 的一种形式,其中训练时间作为 fidelity 参数。但这个概念可以扩展得更远。Multifidelity optimization 认识到,我们可以在多个维度上用不同 fidelity levels 评估超参数:dataset size、model resolution、computational precision 等。

我第一次充分感受到 multifidelity optimization 的力量,是在 2023 年做一个计算机视觉项目时。我当时正在调优 object detection models,它们需要处理高分辨率图像,分辨率为 2048 × 1536 像素。但如果在完整数据集上使用 full-resolution images 训练,每个超参数配置需要数周。于是我设计了一个 multifidelity 方法,使用三种 fidelity:

  • Low fidelity:10% 数据,512×384 分辨率,10 epochs。
  • Medium fidelity:50% 数据,1024×768 分辨率,25 epochs。
  • High fidelity:100% 数据,2048×1536 分辨率,50 epochs。

这种方法让我能够用廉价的 low-fidelity evaluations 快速淘汰糟糕超参数配置,并把昂贵的 high-fidelity training 只留给最有前景的候选项。结果是,计算成本减少了 50 倍,同时找到的超参数比通过 exhaustive high-fidelity search 找到的更好。

至于我是如何选择这些 fidelity levels 的,10% / 50% / 100% 的数据比例是经验确定的。我在 development set 上测试了低 fidelity 和高 fidelity 排名之间的相关性,确保每一级 rank correlation 都超过 0.7。对于 epoch 数量,我使用经验法则:low fidelity 应足够长,让模型展示有意义的学习信号,通常是完整训练的 5%–10%;medium fidelity 应达到最终表现的大约 80%。你的具体值会因问题而异,关键是在投入使用前,验证 low-fidelity 排名与 high-fidelity 结果相关。

Multifidelity 范式

Multifidelity optimization 基于一个简单但强大的洞察:objective function 的不同近似可以在不同计算成本下提供有用信息。我们不再把超参数评估视为 “完整评估” 和 “不评估” 的二元选择,而是可以从一个 fidelity 光谱中选择,每个 fidelity 都提供不同的 cost 和 information quality 取舍。

常见 fidelity 维度包括:

  • Dataset size:在逐渐增大的数据子集上训练。
  • Training duration:改变 epochs 或 iterations 数量。
  • Model resolution:使用不同输入分辨率或模型大小。
  • Data quality:使用更低分辨率图像、压缩音频或下采样时间序列。
  • Computational precision:使用 reduced precision 训练,例如 FP16 与 FP32。
  • Cross-validation folds:初筛时使用更少 folds。

关键假设是,这些 lower-fidelity evaluations 与 high-fidelity performance 相关。一个在 10% 数据上表现好的超参数配置,比一个在子集上表现差的配置,更有可能在 100% 数据上表现好。

高级 Multifidelity 方法

Fabolas(Fast Bayesian Optimization on Large Datasets)是最早将 dataset size 作为 fidelity 维度纳入 Bayesian optimization 的方法之一。它不仅把 performance 建模为 hyperparameters 的函数,也建模为 dataset fraction 的函数。这让 optimizer 不只决定尝试哪些超参数,也能决定以多大数据集规模评估它们。

BOHB(Bayesian Optimization + HyperBand)代表当前 multifidelity optimization 的 state-of-the-art。它结合了 Hyperband 的资源分配策略和 Bayesian optimization 的智能搜索引导。BOHB 不会为每个 Hyperband bracket 随机选择配置,而是使用 Bayesian optimizer 建议可能表现良好的配置。

我近年在多个项目中广泛使用 BOHB,持续发现它比单独 Bayesian optimization 或 Hyperband 更稳健。它在预算有限时表现很好,类似 Hyperband;当资源更充足时,也能达到优秀的渐近表现,类似 Bayesian optimization。

实践实现

下面展示如何为一个真实的计算机视觉问题实现 multifidelity optimization:

import optuna
from torch.utils.data import Subset
import random
 
def create_data_subset(dataset, fraction):
      """Create a random subset of the dataset"""
      n_samples = int(len(dataset) * fraction)
      indices = random.sample(range(len(dataset)), n_samples)
      return Subset(dataset, indices)
 
def train_with_fidelity(config, dataset_fraction=1.0, 
  max_epochs=50, image_size=224):
      """Train model with specified fidelity parameters"""
      # Create dataset subset
      train_subset = create_data_subset(train_dataset, dataset_fraction)
 
      # Create model with hyperparameters
      model = create_model(
      lr=config['lr'],
      batch_size=config['batch_size'],
      dropout=config['dropout'],
      image_size=image_size
      )
 
      # Train for specified epochs
      best_val_acc = 0
      for epoch in range(max_epochs):
      train_loss = train_epoch(model, train_subset)
      val_acc = validate(model, val_dataset)
      best_val_acc = max(best_val_acc, val_acc)
 
      # Early stopping within fidelity level
      if should_early_stop(val_acc, epoch):
            break
 
      return best_val_acc
 
def multi_fidelity_objective(trial):
      """Multi-fidelity optimization with progressive evaluation"""
      config = {
      'lr': trial.suggest_loguniform('lr', 1e-5, 1e-1),
      'batch_size': trial.suggest_categorical('batch_size', [16, 32, 64]),
      'dropout': trial.suggest_uniform('dropout', 0.1, 0.5)
    }
 
      # Low fidelity: small dataset, low resolution, few epochs
      low_fidelity_score = train_with_fidelity(
      config,
      dataset_fraction=0.1,
      max_epochs=10,
      image_size=112
      )
 
      # Report and check if we should continue
      trial.report(low_fidelity_score, step=1)
      if trial.should_prune():
      raise optuna.TrialPruned()
 
      # Medium fidelity: larger dataset, higher resolution
      medium_fidelity_score = train_with_fidelity(
      config,
      dataset_fraction=0.5,
      max_epochs=25,
      image_size=224
      )
 
      trial.report(medium_fidelity_score, step=2)
      if trial.should_prune():
      raise optuna.TrialPruned()
 
      # High fidelity: full dataset and resolution
      high_fidelity_score = train_with_fidelity(
      config,
      dataset_fraction=1.0,
      max_epochs=50,
      image_size=224
      )
 
      return high_fidelity_score
 
# Run multi-fidelity optimization
study = optuna.create_study(
      direction='maximize',
      pruner=optuna.pruners.HyperbandPruner()
)
study.optimize(multi_fidelity_objective, n_trials=100)

案例研究:使用 Multifidelity HPO 优化个人投资组合

在这个案例研究中,我们考察一位从事个人金融应用的数据科学家如何利用 multifidelity optimization 开发投资组合推荐系统。这个真实感较强的场景展示了一个单独实践者在计算资源有限的情况下,如何使用实用 HPO 技术,同时平衡模型准确性和训练效率。

背景和问题定义

Sarah 是一家金融科技初创公司的量化分析师,需要开发一个机器学习模型,用于预测个股投资是否会在下个季度跑赢市场。她的公司服务散户投资者,这些投资者希望获得符合自己风险偏好、投资历史和当前市场条件的个性化投资建议。

挑战在于计算资源约束:Sarah 只有有限的云计算预算,却需要评估数百个潜在模型配置,同时处理需要谨慎验证流程的时间序列金融数据。传统穷举式超参数搜索成本过高,因此这是 multifidelity optimization 的理想应用场景。

数据集和特征

Sarah 的数据集结合了多个个人实践者通常可以获得的数据源:

Financial market data,来自免费 API

  • 每日股票价格和交易量
  • 技术指标,如 RSI、moving averages、Bollinger bands
  • 市场行业分类
  • 波动率度量

Economic indicators,公开数据集

  • 利率和国债收益率
  • 经济日历事件
  • 汇率
  • 市场情绪指标

Company fundamentals,季度报告

  • 收入、利润和增长指标
  • 资产负债率
  • 市盈率
  • 分析师建议

目标变量是二分类:某只股票未来 90 天是否跑赢 S&P 500 超过 2%。这构成了一个具备明确商业价值的实际分类问题。

Multifidelity 策略实现

考虑到资源约束,Sarah 使用 Python 内置优化能力实现了 multifidelity 方法。她的策略使用三个 fidelity levels,在 exploration 和计算效率之间取得平衡:

Low fidelity,快速评估

  • 数据子集:10% 可用训练数据
  • 训练 epochs:神经网络训练 5 epochs
  • 交叉验证:3-fold validation
  • 目的:快速筛选超参数组合

Medium fidelity,有前景候选项

  • 数据子集:60% 可用训练数据
  • 训练 epochs:神经网络训练 15 epochs
  • 交叉验证:5-fold validation
  • 目的:详细评估前 25% 配置

High fidelity,最终选择

  • 数据子集:100% 可用训练数据
  • 训练 epochs:神经网络训练 50 epochs
  • 交叉验证:10-fold time series split validation
  • 目的:最终评估前 5% 配置

实现

虽然下面的自定义实现展示了 multifidelity optimization 的核心概念,但生产系统应使用成熟库,例如 Optuna 的 HyperbandPruner、Ray Tune 的 ASHAScheduler,或 AWS SageMaker HPO,而不是自行编写基础设施。下面代码用于教学目的,展示这些库在底层做了什么:

# EDUCATIONAL EXAMPLE - For production, use Optuna or Ray Tune instead
class MultiFidelityOptimizer:
      """
      NOTE: This custom implementation demonstrates multi-fidelity concepts.
      For production use, prefer:
      - Optuna with HyperbandPruner
      - Ray Tune with ASHAScheduler
      - AWS SageMaker HPO with Hyperband strategy
      """
 
      def __init__(self, X_train, y_train, time_budget=3600):
      self.X_train = X_train
      self.y_train = y_train
      self.time_budget = time_budget
      self.start_time = None
      self.results = []
 
      # Define fidelity levels
      self.fidelity_levels = {
          'low': {'data_fraction': 0.2, 'cv_folds': 3, 'max_iter_mult': 0.1},
          'medium': {'data_fraction': 0.6, 'cv_folds': 5, 'max_iter_mult': 0.3},
          'high': {'data_fraction': 1.0, 'cv_folds': 10, 'max_iter_mult': 1.0}
      }
 
      def generate_random_config(self, model_type: str) -> Dict:
      """Generate random hyperparameter configuration for given model type"""
      configs = {
            'random_forest': {
                'n_estimators': random.randint(50, 500),
                'max_depth': random.choice([None] + list(range(3, 21))),
                'min_samples_split': random.randint(2, 20),
                'min_samples_leaf': random.randint(1, 10),
                'max_features': random.choice(['sqrt', 'log2', 0.3, 0.5, 0.7]),
            },
            'gradient_boosting': {
                'n_estimators': random.randint(50, 500),
                'learning_rate': 10 ** random.uniform(-3, -0.5),  # Log scale
                'max_depth': random.randint(3, 10),
                'min_samples_split': random.randint(2, 20),
                'min_samples_leaf': random.randint(1, 10),
                'subsample': random.uniform(0.5, 1.0),
            },
            'neural_network': {
                'hidden_layer_sizes': tuple(random.randint(50, 300)
                for _ in range(random.randint(1, 3))),
                'learning_rate_init': 10 ** random.uniform(-5, -2),  # Log scale
                'alpha': 10 ** random.uniform(-6, -1),  # L2 regularization
                'batch_size': random.choice([32, 64, 128, 256]),
                'max_iter': random.randint(100, 1000),
            }
      }
      return configs[model_type]

下面是使用 Optuna 的生产就绪替代方案:

import optuna
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import cross_val_score, TimeSeriesSplit
 
def portfolio_objective(trial):
      """Production-ready multi-fidelity HPO using Optuna"""
 
      # Define hyperparameters
      params = {
      'n_estimators': trial.suggest_int('n_estimators', 50, 500),
      'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.3, log=True),
      'max_depth': trial.suggest_int('max_depth', 3, 10),
      'min_samples_split': trial.suggest_int('min_samples_split', 2, 20),
      'subsample': trial.suggest_float('subsample', 0.5, 1.0)
      }
 
      model = GradientBoostingClassifier(**params, random_state=42)
 
      # Use TimeSeriesSplit for financial data
      tscv = TimeSeriesSplit(n_splits=5)
      scores = 
        cross_val_score(model, X_train, y_train, cv=tscv, scoring='roc_auc')
 
      return scores.mean()
 
# Production multi-fidelity optimization
study = optuna.create_study(
      direction='maximize',
      pruner=optuna.pruners.HyperbandPruner(
      min_resource=1,
      max_resource=100,
      reduction_factor=3
      )
)
study.optimize(portfolio_objective, n_trials=100, timeout=3600)
 
print(f"Best AUC: {study.best_value:.4f}")
print(f"Best params: {study.best_params}")

资源管理和结果

Multifidelity 方法使 Sarah 能够在 1 小时计算预算内评估 100 个不同超参数配置,而 full-fidelity evaluation 大约只能评估 15 个配置。

传统 full-fidelity 方法

  • 评估 15 个配置
  • 每个配置 60 分钟
  • 总时间:15 小时
  • 最佳 validation AUC:0.73

Multifidelity 方法

  • 100 个配置在 low fidelity 下评估,每个 2 分钟
  • 25 个配置在 medium fidelity 下评估,每个 8 分钟
  • 5 个配置在 high fidelity 下评估,每个 20 分钟
  • 总时间:60 分钟
  • 最佳 validation AUC:0.76

Multifidelity 策略不仅适配 Sarah 的预算,也通过探索更大搜索空间发现了表现更好的配置。

从 0.73 到 0.76 的提升,在五次使用不同 random seeds 的独立运行中保持一致。Multifidelity 的标准差为 ±0.008,传统方法为 ±0.012,说明这是真实提升,而不是 lucky sampling。对于金融时间序列数据,这种一致性很重要。我见过一些案例中,所谓 “更好” 的模型只是有利 validation split 的产物。

关键洞察和实践考虑

Early Stopping 的有效性

Successive Halving 方法成功提前识别出表现差的配置。约 70% 的配置在 low-fidelity evaluation 后被淘汰,节省了大量计算资源,同时保留了所有 top-performing models 进入后续评估。

模型类型表现模式

不同 fidelity levels 之间出现了有趣模式:

  • Gradient boosting models 在不同 fidelity levels 上排名稳定。
  • Neural networks 方差更大,有些配置在额外训练后显著改善。
  • Random forests 很快达到峰值表现,因此非常适合 low-fidelity screening。

资源分配策略

三层 fidelity 方法对这个数据集大小和复杂度来说被证明是最优的。Sarah 尝试了不同资源分配比例,发现每个 fidelity level 上花费大致相等时间,在 exploration 和 exploitation 之间取得最佳平衡。

生产部署考虑

最终模型达到 76% validation AUC,并具备以下特征:

  • Inference time:每次预测小于 50ms。
  • Memory footprint:45 MB。
  • Feature importance:市场波动率和板块轮动信号最具预测性。
  • Model interpretability:SHAP values 为单个 predictions 提供清晰解释。

这个案例研究表明,个人实践者可以在有限计算资源下有效应用 multifidelity optimization 到真实世界问题,并在现实预算约束内取得比传统方法更好的结果。

给个人实践者的经验教训

这个实现带来几个关键结论:

从 low-fidelity screening 开始:快速评估大量配置,通常比对少数配置做穷尽评估更有效地揭示有前景方向。

资源分配很重要:对于这个问题,fidelity levels 之间 40%–40%–20% 的时间分配被证明有效,但尝试不同配比可能进一步提升结果。

模型特定考虑:不同算法家族对 fidelity reductions 的响应不同,因此理解特定领域中的这些模式很有价值。

验证策略对齐:确保 low 和 medium fidelity 的评估流程与 high fidelity 结果有良好相关性,避免被早期评估误导。

什么时候使用 Multifidelity Optimization

Multifidelity optimization 在以下情况下最有价值:

  • High-fidelity evaluations 成本高昂,每次评估数小时到数天。
  • 存在多个自然 fidelity 维度,例如 dataset size、resolution、training time。
  • Fidelity correlations 很强,即 low 和 high fidelity 之间相关性超过 0.7。
  • 你有足够工程资源实现额外复杂度。

可以考虑更简单方法的情况:

  • 评估已经很快,几分钟或更短。
  • 没有自然 fidelity 维度可用。
  • Fidelity correlations 较弱。
  • 你需要快速结果,不想投入额外工程工作。

Multifidelity optimization 代表超参数优化前沿,使实践者能够调优那些原本计算上不可行的模型。随着模型规模和复杂度持续增长,这些技术会越来越成为高效机器学习开发的必需品。

小结

本章中,我们探索了多种超参数优化技术,从基础 grid search 和 random search,到复杂的 multifidelity 方法。我在数百个项目中实现过这些技术,下面分享一些实用指南,帮助你选择合适方法并避开常见陷阱。

超参数优化方法的选择应取决于你的具体约束和需求:

快速实验,预算小于 1 小时:从 random search 开始;使用 Scikit-learn 的 RandomizedSearchCV 或类似内置工具;重点关注最重要的 2 到 3 个超参数。

生产模型,预算 1 到 10 小时:使用 Bayesian optimization,例如 Optuna 或类似工具;纳入 early stopping / pruning;将 20%–30% 预算用于 exploration,70%–80% 用于 refinement。

昂贵模型,预算大于 10 小时:实现 multifidelity optimization;考虑 BOHB,以获得两者优势;可用时使用 distributed computing。

研究项目:完整记录你的搜索过程;使用具备良好可视化能力的工具,例如 Optuna、Weights & Biases;如果需要在多个 metrics 之间取舍,考虑 multi-objective optimization。

对 validation data 过拟合:当进行大规模超参数搜索时,你有可能过拟合 validation set。这个风险会随 trials 数量增加而上升——在小 validation set 上运行 1000 次 HPO iterations,几乎可以保证你会找到利用该 split 噪声的超参数。一些实践者会用单独的 “HPO validation set”,区别于最终 model validation set;或者使用 nested cross-validation 做最终评估。始终保留一个 held-out test set,并且只在最后评估一次。

Inconsistent evaluation:确保所有超参数配置在完全相同条件下评估。使用相同 random seeds、data splits 和 evaluation metrics。

Ignoring statistical significance:performance metrics 中的小差异可能不具备统计显著性。当性能差异很小时,考虑运行多个 seeds 并使用统计检验。

Premature stopping:early stopping 很强大,但不要过于激进地终止有前景配置。在 pruning 开始前留出 warm-up period。

Start small:从少量 trials 开始,验证设置并获得初始结果。等一切正常后,再扩展规模。

Monitor progress:定期检查中间结果。如果许多 trials 后仍看不到改进,可以考虑调整 search space 或方法。

Parallel execution:大多数超参数优化都可以有效并行化。使用所有可用计算资源,但要注意,某些方法,例如 Bayesian optimization,在极高并行度下可能效果较差。

Budget planning:可以先将项目总时间的约 10%–20% 分配给超参数优化,再根据上下文调整。对于你正在推动 state of the art 的新问题或研究场景,30%–40% 可能是合理的。对于已有成熟 baseline 的熟悉问题,例如 CIFAR-10 上的标准图像分类,5%–10% 通常足够,因为你不太可能发现明显优于已发表最佳实践的超参数。调参很容易让人陷入无尽优化,但边际收益会迅速递减。

展望未来,超参数优化已经从人工、直觉驱动的流程,演进为一个拥有原则化算法和稳健工具的成熟领域。本章覆盖的技术,从 random search 到 multifidelity Bayesian optimization,代表当前 state of the art,并足以满足大多数实际应用。

不过,该领域仍在快速演化。新兴趋势包括:

与 neural architecture search 集成:超参数优化和架构搜索之间的边界正在模糊,一些方法会同时优化超参数和架构。

Large language model integration:近期工作探索使用 LLMs 根据问题的自然语言描述建议超参数配置。OptFormer(Chen 等,2022)关于 learned optimization,以及 LLAMBO(Liu 等,2024)关于 LLM-augmented Bayesian optimization,是这一方向的早期工作。Google Vizier 等工具也在实验 LLM-guided suggestions,尽管这一领域仍处早期,结果也参差不齐。

Automated feature engineering:超参数优化正在扩展到优化 feature engineering pipelines,而不只是模型参数。

Green AI considerations:随着环境问题受到更多关注,人们越来越重视能够最小化计算浪费的超参数优化方法。

下一章中,我们将探索 neural architecture search(NAS),它可以被视为超参数优化的逻辑极限:不仅优化固定架构中的参数,还优化架构本身。NAS 是自动化机器学习中最令人兴奋的前沿之一,有望发现人类专家可能永远不会想到的新颖架构。

你在理解超参数优化中获得的能力——平衡 exploration 与 exploitation、重视高效资源分配、善于从过往评估中学习——将在我们进入更复杂的自动化架构设计世界时继续发挥作用。