跳到主内容
@wquguru
精选80MarkTechPost(RSS)技巧与观点

Meta Ax 自适应实验实战:贝叶斯优化与多目标调参指南

Adaptive Experimentation with Meta’s Ax: A Practical Coding Guide

原文
发到 X
推荐理由

做超参调优和 AutoML 的同学值得收藏,这份教程把 Ax 的约束优化、多目标优化和 Pareto 分析串成了可直接照跑的完整流程,代码拿来就能改。

In this tutorial, we explore adaptive experimentation using Meta’s Ax with the modern Client API. We work through a complete workflow where we tune a RandomForest model on a synthetic classification dataset while balancing predictive accuracy against model footprint. We begin by defining a mixed search space with integer, float, log-scaled, and categorical parameters, then use Ax’s ask-tell optimization loop to run constrained Bayesian optimization, multi-objective optimization, and parameter-constrained experimentation. Along the way, we visualize convergence, inspect the Pareto frontier, use Ax’s built-in analysis tools, and persist the experiment for future reuse.

在本教程中,我们使用 Meta 的 Ax 与现代 Client API 探索自适应实验。我们完成一个完整的工作流程,在合成分类数据集上调整 RandomForest 模型,同时平衡预测准确性与模型占用空间。我们首先定义一个包含整数、浮点数、对数缩放和分类参数的混合搜索空间,然后使用 Ax 的 ask-tell 优化循环进行约束贝叶斯优化、多目标优化和参数约束实验。在此过程中,我们可视化收敛情况、检查帕累托前沿、使用 Ax 的内置分析工具,并持久化实验以供将来重用。

代码 · 20
import importlib, subprocess, sys
def _ensure(module, pip_name=None):
   try:
       importlib.import_module(module)
   except ImportError:
       print(f"Installing {pip_name or module} ...")
       subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", pip_name or module])
_ensure("ax", "ax-platform")
_ensure("sklearn", "scikit-learn")
import logging, warnings, time
import numpy as np
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")
logging.getLogger("ax").setLevel(logging.WARNING)
from ax.api.client import Client
from ax.api.configs import RangeParameterConfig, ChoiceParameterConfig
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
np.random.seed(0)
代码 · 20
import importlib, subprocess, sys
def _ensure(module, pip_name=None):
   try:
       importlib.import_module(module)
   except ImportError:
       print(f"Installing {pip_name or module} ...")
       subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", pip_name or module])
_ensure("ax", "ax-platform")
_ensure("sklearn", "scikit-learn")
import logging, warnings, time
import numpy as np
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")
logging.getLogger("ax").setLevel(logging.WARNING)
from ax.api.client import Client
from ax.api.configs import RangeParameterConfig, ChoiceParameterConfig
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
np.random.seed(0)

We begin by preparing the Colab environment and installing the required packages for Ax and scikit-learn. We import the core libraries for optimization, machine learning, plotting, logging, and reproducibility. We also configure warnings and Ax logging to keep the notebook output clean and focused on the experimental results.

我们首先准备 Colab 环境并安装 Ax 和 scikit-learn 所需的包。我们导入用于优化、机器学习、绘图、日志记录和可复现性的核心库。我们还配置警告和 Ax 日志记录,以保持笔记本输出整洁并专注于实验结果。

代码 · 41
X, y = make_classification(
   n_samples=1400, n_features=20, n_informative=8, n_redundant=4,
   n_classes=3, random_state=0,
)
CV = StratifiedKFold(n_splits=3, shuffle=True, random_state=0)
def evaluate(p):
   n_est, depth = int(p["n_estimators"]), int(p["max_depth"])
   clf = RandomForestClassifier(
       n_estimators=n_est,
       max_depth=depth,
       max_features=float(p["max_features"]),
       min_samples_leaf=int(p["min_samples_leaf"]),
       criterion=p["criterion"],
       ccp_alpha=float(p["ccp_alpha"]),
       n_jobs=-1,
       random_state=0,
   )
   accuracy = cross_val_score(clf, X, y, cv=CV, scoring="accuracy").mean()
   model_size = n_est * depth
   return {"accuracy": float(accuracy), "model_size": float(model_size)}
SEARCH_SPACE = [
   RangeParameterConfig(name="n_estimators",    bounds=(50, 300),     parameter_type="int"),
   RangeParameterConfig(name="max_depth",       bounds=(3, 24),       parameter_type="int"),
   RangeParameterConfig(name="max_features",    bounds=(0.2, 1.0),    parameter_type="float"),
   RangeParameterConfig(name="min_samples_leaf",bounds=(1, 12),       parameter_type="int"),
   RangeParameterConfig(name="ccp_alpha",       bounds=(1e-5, 1e-1),  parameter_type="float", scaling="log"),
   ChoiceParameterConfig(name="criterion", values=["gini", "entropy", "log_loss"],
                         parameter_type="str", is_ordered=False),
]
def run_study(client, total_trials, metric_keys, batch=4):
   records = []
   while len(records) < total_trials:
       trials = client.get_next_trials(max_trials=min(batch, total_trials - len(records)))
       if not trials:
           break
       for idx, params in trials.items():
           full = evaluate(params)
           raw = {k: full[k] for k in metric_keys}
           client.complete_trial(trial_index=idx, raw_data=raw)
           records.append({"trial": idx, "params": params, **full})
   return records
代码 · 41
X, y = make_classification(
   n_samples=1400, n_features=20, n_informative=8, n_redundant=4,
   n_classes=3, random_state=0,
)
CV = StratifiedKFold(n_splits=3, shuffle=True, random_state=0)
def evaluate(p):
   n_est, depth = int(p["n_estimators"]), int(p["max_depth"])
   clf = RandomForestClassifier(
       n_estimators=n_est,
       max_depth=depth,
       max_features=float(p["max_features"]),
       min_samples_leaf=int(p["min_samples_leaf"]),
       criterion=p["criterion"],
       ccp_alpha=float(p["ccp_alpha"]),
       n_jobs=-1,
       random_state=0,
   )
   accuracy = cross_val_score(clf, X, y, cv=CV, scoring="accuracy").mean()
   model_size = n_est * depth
   return {"accuracy": float(accuracy), "model_size": float(model_size)}
SEARCH_SPACE = [
   RangeParameterConfig(name="n_estimators",    bounds=(50, 300),     parameter_type="int"),
   RangeParameterConfig(name="max_depth",       bounds=(3, 24),       parameter_type="int"),
   RangeParameterConfig(name="max_features",    bounds=(0.2, 1.0),    parameter_type="float"),
   RangeParameterConfig(name="min_samples_leaf",bounds=(1, 12),       parameter_type="int"),
   RangeParameterConfig(name="ccp_alpha",       bounds=(1e-5, 1e-1),  parameter_type="float", scaling="log"),
   ChoiceParameterConfig(name="criterion", values=["gini", "entropy", "log_loss"],
                         parameter_type="str", is_ordered=False),
]
def run_study(client, total_trials, metric_keys, batch=4):
   records = []
   while len(records) < total_trials:
       trials = client.get_next_trials(max_trials=min(batch, total_trials - len(records)))
       if not trials:
           break
       for idx, params in trials.items():
           full = evaluate(params)
           raw = {k: full[k] for k in metric_keys}
           client.complete_trial(trial_index=idx, raw_data=raw)
           records.append({"trial": idx, "params": params, **full})
   return records

We create a synthetic multi-class classification dataset and define a cross-validation strategy to evaluate Random Forest models. We build an evaluation function that returns both accuracy and model size, allowing us to measure performance and cost together. We then define a mixed search space with integer, float, log-scaled, and categorical parameters, along with a reusable ask-tell study runner.

我们创建一个合成多类分类数据集,并定义交叉验证策略来评估随机森林模型。我们构建一个返回准确性和模型大小的评估函数,使我们能够同时衡量性能和成本。然后,我们定义一个包含整数、浮点数、对数缩放和分类参数的混合搜索空间,以及一个可重用的 ask-tell 研究运行器。

代码 · 20
print("\n=== Study 1: constrained single-objective Bayesian optimization ===")
c1 = Client()
c1.configure_experiment(parameters=SEARCH_SPACE, name="rf_constrained")
c1.configure_optimization(objective="accuracy",
                         outcome_constraints=["model_size <= 2500"])
rec1 = run_study(c1, total_trials=24, metric_keys=["accuracy", "model_size"])
best_params, prediction, best_idx, best_arm = c1.get_best_parameterization()
print("\nBest feasible configuration found:")
for k, v in best_params.items():
   print(f"   {k:>16}: {v}")
print("   predicted:", prediction)
feasible = [(r["trial"], r["accuracy"]) for r in rec1 if r["model_size"] <= 2500]
best_so_far, cur = [], -np.inf
for _, acc in feasible:
   cur = max(cur, acc); best_so_far.append(cur)
plt.figure(figsize=(7, 4))
plt.plot(range(1, len(best_so_far) + 1), best_so_far, "o-")
plt.xlabel("feasible trial #"); plt.ylabel("best accuracy so far")
plt.title("Study 1 — convergence (subject to model_size <= 2500)")
plt.grid(alpha=0.3); plt.tight_layout(); plt.show()
代码 · 20
print("\n=== Study 1: constrained single-objective Bayesian optimization ===")
c1 = Client()
c1.configure_experiment(parameters=SEARCH_SPACE, name="rf_constrained")
c1.configure_optimization(objective="accuracy",
                         outcome_constraints=["model_size <= 2500"])
rec1 = run_study(c1, total_trials=24, metric_keys=["accuracy", "model_size"])
best_params, prediction, best_idx, best_arm = c1.get_best_parameterization()
print("\nBest feasible configuration found:")
for k, v in best_params.items():
   print(f"   {k:>16}: {v}")
print("   predicted:", prediction)
feasible = [(r["trial"], r["accuracy"]) for r in rec1 if r["model_size"] <= 2500]
best_so_far, cur = [], -np.inf
for _, acc in feasible:
   cur = max(cur, acc); best_so_far.append(cur)
plt.figure(figsize=(7, 4))
plt.plot(range(1, len(best_so_far) + 1), best_so_far, "o-")
plt.xlabel("feasible trial #"); plt.ylabel("best accuracy so far")
plt.title("Study 1 — convergence (subject to model_size <= 2500)")
plt.grid(alpha=0.3); plt.tight_layout(); plt.show()

We run a constrained single-objective Bayesian optimization study where we maximize accuracy while keeping model size below a fixed threshold. We use Ax to suggest hyperparameter configurations, evaluate them, and report both accuracy and model size back to the optimizer. We then extract the best feasible configuration and plot the best accuracy achieved over feasible trials.

我们运行一个受约束的单目标贝叶斯优化研究,在保持模型大小低于固定阈值的同时最大化准确性。我们使用 Ax 来建议超参数配置,评估它们,并将准确性和模型大小报告给优化器。然后,我们提取最佳可行配置,并绘制在可行试验中实现的最佳准确性。

代码 · 25
print("\n=== Study 2: multi-objective (accuracy vs. model_size) ===")
c2 = Client()
c2.configure_experiment(parameters=SEARCH_SPACE, name="rf_multiobjective")
c2.configure_optimization(objective="accuracy, -model_size")
rec2 = run_study(c2, total_trials=28, metric_keys=["accuracy", "model_size"])
try:
   frontier = c2.get_pareto_frontier()
   print(f"Ax identified {len(frontier)} Pareto-optimal configurations.")
except Exception as e:
   frontier = None
   print("get_pareto_frontier unavailable in this version:", e)
acc = np.array([r["accuracy"] for r in rec2])
size = np.array([r["model_size"] for r in rec2])
order = np.argsort(size)
pareto_idx, best_acc = [], -np.inf
for i in order:
   if acc[i] > best_acc:
       best_acc = acc[i]; pareto_idx.append(i)
plt.figure(figsize=(7, 5))
plt.scatter(size, acc, c="lightgray", label="all trials")
plt.scatter(size[pareto_idx], acc[pareto_idx], c="crimson", zorder=3, label="Pareto front")
plt.plot(size[pareto_idx], acc[pareto_idx], "--", c="crimson", alpha=0.6)
plt.xlabel("model_size (lower = cheaper)"); plt.ylabel("accuracy (higher = better)")
plt.title("Study 2 — accuracy vs. model size trade-off")
plt.legend(); plt.grid(alpha=0.3); plt.tight_layout(); plt.show()
代码 · 25
print("\n=== Study 2: multi-objective (accuracy vs. model_size) ===")
c2 = Client()
c2.configure_experiment(parameters=SEARCH_SPACE, name="rf_multiobjective")
c2.configure_optimization(objective="accuracy, -model_size")
rec2 = run_study(c2, total_trials=28, metric_keys=["accuracy", "model_size"])
try:
   frontier = c2.get_pareto_frontier()
   print(f"Ax identified {len(frontier)} Pareto-optimal configurations.")
except Exception as e:
   frontier = None
   print("get_pareto_frontier unavailable in this version:", e)
acc = np.array([r["accuracy"] for r in rec2])
size = np.array([r["model_size"] for r in rec2])
order = np.argsort(size)
pareto_idx, best_acc = [], -np.inf
for i in order:
   if acc[i] > best_acc:
       best_acc = acc[i]; pareto_idx.append(i)
plt.figure(figsize=(7, 5))
plt.scatter(size, acc, c="lightgray", label="all trials")
plt.scatter(size[pareto_idx], acc[pareto_idx], c="crimson", zorder=3, label="Pareto front")
plt.plot(size[pareto_idx], acc[pareto_idx], "--", c="crimson", alpha=0.6)
plt.xlabel("model_size (lower = cheaper)"); plt.ylabel("accuracy (higher = better)")
plt.title("Study 2 — accuracy vs. model size trade-off")
plt.legend(); plt.grid(alpha=0.3); plt.tight_layout(); plt.show()

We move from single-objective optimization to multi-objective optimization by jointly maximizing accuracy and minimizing model size. We use Ax to search for configurations that represent strong trade-offs between predictive performance and computational footprint. We then calculate and visualize the empirical Pareto frontier to understand how accuracy varies with model size.

我们从单目标优化转向多目标优化,同时最大化准确性和最小化模型大小。我们使用 Ax 搜索代表预测性能和计算占用空间之间强权衡的配置。然后,我们计算并可视化经验帕累托前沿,以了解准确性如何随模型大小变化。

更进一步:量化金融体系

看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力

进入量化体系 →

相似阅读

关联信息,但可能不是同一事件