halo 的技术博客

返回

先说结论:如果你还在用网格搜索调策略参数,你正在为暴力枚举付出指数级的算力税。 每跑一次回测都是一次昂贵的采样,贝叶斯优化的核心思想是——别浪费任何一次采样。它用已跑过的点建一个”代理模型”预测哪里最可能有好参数,再把下一次回测精准投放到那里。实测下,逼近同样的全局最优,贝叶斯优化花的回测次数是网格搜索的零头。

但文章最后我要泼一盆冷水:在验证集上优化超参,本身就是一种过拟合。你优化得越猛,越可能榨干样本外的收益。这是量化调参最隐蔽的死穴。

为什么网格和随机搜索都不够聪明#

先摆清楚三种方法的本质区别:

  • 网格搜索:把每个参数切成若干档,笛卡尔积全跑一遍。3 个参数各 10 档就是 1000 次回测,4 个参数就是 10000 次——维度诅咒,指数爆炸。
  • 随机搜索:在参数空间里随机撒点。比网格高效(Bergstra & Bengio 证明过),但它不看历史——第 50 次采样和第 1 次一样盲目,跑过的信息全浪费了。
  • 贝叶斯优化:每次采样后更新一个”目标长什么样”的信念,用它指导下一次去哪采。它会学习。

关键洞察是:策略回测是昂贵的黑箱函数。你不知道它的解析形式,只能”输入参数→跑回测→得到 Sharpe”,而且一次几秒到几分钟。当采样这么贵时,“每一次都花在刀刃上”就值回票价。

高斯过程:给黑箱建一个带不确定度的代理#

贝叶斯优化的引擎是高斯过程(Gaussian Process, GP)。它不只给出”这个参数大概值多少 Sharpe”的预测,还给出”我对这个预测有多确定”——这个不确定度是整个方法的灵魂。

GP 的核心是核函数,它编码了”相近的参数应该有相近的结果”这个先验。最常用的是 RBF 核:

用 Cholesky 分解而不是 np.linalg.inv 是刻意的:协方差矩阵求逆在病态时会数值爆炸,Cholesky 更稳。

GP 代理模型在 4 次评估后的后验:均值曲线逼近真实目标,未探索区不确定带变宽

上图是只跑了 4 次真实回测(红点)后 GP 拟合出的代理曲面。蓝线是后验均值(对真实目标的猜测),阴影是 ±2σ 不确定带。注意:离已知点越远,不确定带越宽——GP 老实承认”这片我没探过”。这正是下一步决策的依据。

采集函数:探索与利用的数学权衡#

有了代理曲面,下一个问题是:下一次回测该跑哪组参数? 两种诱惑在拉扯:

  • 利用(exploitation):去当前预测最好的地方深挖。
  • 探索(exploration):去不确定度最大的地方碰运气,可能藏着更好的峰。

采集函数把这个权衡量化。最经典的是期望改进(Expected Improvement, EI):它计算”在某点采样,相比当前最优能期望改进多少”,自动同时奖励”预测高”和”不确定大”:

from scipy.stats import norm

def expected_improvement(mu, sd, best, xi=0.01):
    """EI 采集函数:均衡探索与利用"""
    imp = mu - best - xi            # 超过当前最优的幅度
    Z = np.where(sd > 1e-9, imp / sd, 0.0)
    ei = np.where(sd > 1e-9,
                  imp * norm.cdf(Z) + sd * norm.pdf(Z),  # 均值项 + 不确定项
                  0.0)
    return ei
python

imp * norm.cdf(Z) 是”利用”项(预测越高越大),sd * norm.pdf(Z) 是”探索”项(不确定越大越大)。EI 最大的点就是下一个最值得跑的参数。xi 是探索强度旋钮,调大更爱探索。

采集函数 EI 指向下一个最值得评估的参数点

上图下半部分是 EI 曲线,红色竖线标出它的峰值——那就是算法建议的下一次回测参数。它没有选当前预测最高的点,而是选了一个”预测不错 + 还没充分探索”的位置。这就是贝叶斯优化的聪明之处。

完整循环:把它们串起来#

贝叶斯优化的主循环极简:拟合 GP → 最大化 EI 找下一点 → 跑真实回测 → 加入数据集 → 重复。

注意 objective(x_next) 是整个循环里唯一昂贵的一步——一次真实回测。贝叶斯优化的全部智慧,就是为了让这一步尽可能少调用。

三种方法收敛速度对比#

现在把三种方法放在同一个多峰目标上赛跑,横轴是”已花费的回测次数”,纵轴是”目前找到的最优 Sharpe”:

收敛对比:贝叶斯优化用更少回测次数逼近全局最优,网格和随机明显落后

结论很清楚:贝叶斯优化(蓝线)在 10 次左右就逼近全局最优,而随机搜索和网格搜索要花 30~40 次才追上。 当每次回测要几分钟、参数有 4~5 维时,这个差距就是”下午茶前跑完”和”跑一整夜”的区别。

参数维度越高、单次回测越贵,贝叶斯优化的相对优势越大——这正是它在量化调参里最有价值的场景。

最致命的坑:调参本身就是过拟合#

前面全是优点,现在讲那盆冷水。这是量化调参最隐蔽、也最致命的问题。

贝叶斯优化优化的是”验证集上的 Sharpe”。但验证集是有限样本,它的 Sharpe = 真实 Sharpe + 噪声。当你用一个如此高效的优化器去榨取验证集表现时,它会同时榨取信号和噪声——它找到的”最优参数”很可能只是恰好拟合了验证集那段特定噪声的参数。

# 危险的做法:把全部历史当验证集,优化到底
best_param, best_sharpe, _, _ = bayesian_optimize(
    objective=lambda p: backtest_sharpe(prices_ALL, p),  # ← 在全样本上优化
    bounds=(1, 50), n_iter=50
)
# best_sharpe 会很漂亮,但它是骗你的
python

过拟合警告:验证集 Sharpe 持续上升,测试集 Sharpe 过峰后掉头向下

上图是铁证:蓝线(验证集,优化目标)一路上扬,看起来越优化越好;但红线(测试集,真实泛化)在某个点触顶后掉头向下你优化验证集越狠,测试集越惨。 两条线的背离点,就是你该停手的地方。

防御手段有三层:

  1. 样本外隔离:优化只在训练+验证段做,最终评估用一段从未参与优化的测试集,且只看一次。
  2. 限制迭代次数:不要把 EI 榨到干。迭代太多 = 过拟合验证噪声。宁可欠优化。
  3. Walk-forward 验证:滚动地在多个时间窗上重复”优化→样本外测试”,看最优参数是否稳定。如果每个窗口选出的最优参数满天飞,说明你优化的是噪声不是信号。
def walk_forward_bo(prices, n_splits=5, opt_iter=15):
    """Walk-forward:每段独立优化并在下一段样本外检验"""
    L = len(prices) // (n_splits + 1)
    oos_results, chosen_params = [], []
    for k in range(n_splits):
        train = prices[:L * (k + 1)]
        test = prices[L * (k + 1): L * (k + 2)]
        p, _, _, _ = bayesian_optimize(
            lambda x: backtest_sharpe(train, x), bounds=(1, 50), n_iter=opt_iter)
        oos_results.append(backtest_sharpe(test, p))  # 样本外真实表现
        chosen_params.append(p)
    # 参数抖动大 = 过拟合信号
    return np.mean(oos_results), np.std(chosen_params)
python

A. 实现细节#

  • 代理模型:RBF 核 GP,Cholesky 分解求解后验,返回预测均值 + 标准差。
  • 采集函数:期望改进(EI),显式拆成利用项 imp·Φ(Z) 与探索项 sd·φ(Z)xi 控制探索强度。
  • 优化目标:策略年化 Sharpe(黑箱,每次调用 = 一次完整回测)。
  • 主循环n_init 个随机冷启动点,之后每轮”拟合 GP → argmax EI → 真实回测 → 追加数据”。
  • 候选搜索:EI 在 500 点网格上找最大值(低维够用;高维需换 L-BFGS 多起点)。

B. 已知偏差#

  • GP 核与超参未自适应:本文固定了 RBF 的 length/scale,实战应对核超参做边际似然优化,否则代理曲面可能失真。
  • 噪声假设过简:回测 Sharpe 的噪声不是同方差高斯——不同参数区的估计误差差异很大,固定 noise 会低估某些区域的不确定度。
  • 合成目标 vs 真实曲面:文中演示用的多峰目标是构造的,真实策略参数曲面可能有平台、断崖、离散跳变,GP 的平滑先验在断崖处会拟合不良。

C. 结果解读#

  • 效率优势的来源:贝叶斯优化省的不是计算量本身,而是回测次数。当单次回测昂贵(长历史、多标的、复杂逻辑)时,把 40 次砍到 10 次就是数倍的墙钟时间节省。
  • 探索/利用的平衡是关键:EI 的价值在于它不会过早陷进局部最优(纯利用会),也不会一直瞎撞(纯探索会)。xi 调太小容易早熟,调太大退化成随机搜索。
  • 过拟合是头号敌人,不是效率:讽刺的是,贝叶斯优化”太高效”反而放大了调参过拟合——它比随机搜索更擅长找到那个恰好拟合验证集噪声的参数。方法越强,越需要样本外纪律。
  • 何时该用它:参数 ≥3 维、单次回测 ≥数秒、目标曲面相对平滑时,贝叶斯优化收益最大。反过来,参数只有 1~2 个、回测极快,直接网格搜索更省心也更透明——别为了用高级方法而用高级方法。
  • 真正的成功标准不是最高 Sharpe:是 walk-forward 里选出的最优参数稳不稳定。参数满天飞的漂亮回测,不如参数稳定的平庸回测。
贝叶斯优化超参:用高斯过程代理把调参成本砍到最低
https://blog.halo26812.eu.org/blog/bayesian-optimization-hyperparam
Author halo
Published at 2026年7月25日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨