☰
黄金搜索算法:基于0.618的数值优化方法原理与实战
2026/10/15 16:06:29 网站建设 项目流程

1. 黄金搜索算法到底在搜什么

第一次看到"黄金搜索算法"这个词,很多人会误以为是什么搜索引擎的排序策略,或者某个电商平台的商品推荐机制。其实它跟这些都没关系。黄金搜索算法是一类基于黄金分割比例(约0.618)进行区间收缩的数值优化方法,核心用途是在一个已知的单峰函数区间内,快速逼近极值点。说白了,就是当你面对一个"碗状"或"山峰状"的函数,想知道它的最低点或最高点在哪里,但又不想(或不能)求导、不想暴力遍历时,黄金搜索算法就是一把非常好用的"尺子"。

它的适用场景非常具体:目标函数不可导、求导成本极高、或者根本写不出解析表达式,但你可以在任意点采样得到函数值。比如调参时那个"训练轮数 vs 验证集准确率"的曲线,比如某个物理仿真里"某个结构参数 vs 能耗"的关系,只要它在搜索区间内是单峰的,黄金搜索就能用极少的采样次数把极值点锁定到很高精度。

这篇文章我会从算法原理、手算过程、代码实现、精度与迭代次数的取舍、常见踩坑几个角度,把黄金搜索算法讲透。适合正在学数值优化的大学生、需要做参数寻优的工程师,以及想搞懂"0.618"这个数字到底从哪来的人。读完你不仅能自己写出实现,还能判断什么时候该用它、什么时候该换别的算法。

2. 为什么是0.618而不是0.5

2.1 从"三分法"到黄金分割的进化

要理解黄金搜索,先看一个更朴素的想法:三分搜索。假设区间是 [a, b],我在里面取两个点 x1、x2,把区间分成三段,比较 f(x1) 和 f(x2),然后丢掉不可能包含极值的那一段。这个思路没错,但三分法有个问题:每次迭代要重新计算两个新点,采样成本高。

黄金搜索的聪明之处在于:它让每次迭代只需要新增一个采样点,另一个点可以复用上一轮的结果。这就像你切蛋糕,切一刀之后,其中一块的形状和原来那块是"相似"的,可以继续用同样的比例切下去。这个"相似"的比例,正是黄金分割比。

设区间长度为 L,我们取两个对称的点,把区间分成三段:左边一段长 x,中间一段长 y,右边一段长 x。为了能复用,我们希望"丢掉一段后剩下的区间里,原来的某个点仍然处于相同的相对位置"。这个条件推导下来,会得到一个方程:

x / y = L / x

令 L = x + y,代入整理得到 x² = y(x+y),两边除以 y²,设 t = x/y,则 t² = t + 1,解出 t = (1+√5)/2 ≈ 1.618,取倒数就是 0.618。这就是黄金分割比的来源,也是算法名字的由来。

2.2 复用点带来的效率优势

这个"复用"到底省了多少?我们算一笔账。假设要把区间精度收缩到原来的 1/1000,也就是需要约 10 次有效收缩(因为 0.618 的 10 次方约等于 0.008)。

  • 三分法:每次迭代算 2 个新点,10 次迭代约 20 次函数求值。
  • 黄金搜索:第一次算 2 个点,之后每次只算 1 个新点,10 次迭代约 11 次函数求值。

看起来只差了一倍,但如果你的单次函数求值需要跑一次几分钟的仿真,这个差距就是几十分钟和十几分钟的区别。在工程优化里,函数求值次数往往就是真金白银的成本,这也是黄金搜索至今仍被广泛使用的原因。

注意:黄金搜索的前提是函数在区间内单峰。如果函数有多个局部极值,黄金搜索可能收敛到其中一个局部极值,而不是全局极值。这一点后面会专门讲怎么判断和处理。

2.3 和其他一维搜索方法的对比

方法是否需导数每次迭代新增求值收敛速度适用场景
黄金搜索否1 次线性,比率0.618单峰、不可导、求值贵
三分搜索否2 次线性,比率2/3单峰、实现简单
斐波那契搜索否1 次线性,理论最优已知迭代次数上限
牛顿法需要二阶导1 次二次收敛函数光滑、可导
梯度下降需要一阶导1 次线性/次线性高维、可导

从表里能看出来,黄金搜索的定位很清晰:不需要导数、每次只加一个点、实现简单。斐波那契搜索在理论上比黄金搜索更优(在固定迭代次数下区间收缩最快),但黄金搜索不需要预先知道迭代次数,比例固定,写起来更省心,所以实际工程里黄金搜索的出场率反而更高。

3. 手把手推一遍迭代过程

3.1 初始化:两个内点的位置怎么算

假设我们要在区间 [a, b] = [0, 10] 上找函数 f(x) = (x-3)² + 1 的最小值。这个函数的最小值显然在 x=3,我们用它来验证算法。

黄金分割比取 φ = 0.618(更精确地说是 (√5-1)/2 ≈ 0.6180339887)。两个内点的位置:

x1 = b - φ * (b - a) = 10 - 0.618 * 10 = 3.82 x2 = a + φ * (b - a) = 0 + 0.618 * 10 = 6.18

计算函数值:

f(3.82) = (3.82-3)² + 1 = 0.6724 + 1 = 1.6724 f(6.18) = (6.18-3)² + 1 = 10.1124 + 1 = 11.1124

因为 f(x1) < f(x2),说明最小值在 [a, x2] 这一侧,所以新区间变成 [0, 6.18],同时原来的 x1=3.82 可以复用为新的 x2。

3.2 第二轮:复用点如何更新

新区间 [0, 6.18],长度 6.18。原来的 x1=3.82 现在应该作为新的 x2 吗?我们验证一下:新 x2 按公式应该是 a + φ*(b-a) = 0 + 0.618*6.18 = 3.819,和 3.82 几乎一致(差异来自四舍五入)。所以确实可以复用。

新的 x1 = b - φ*(b-a) = 6.18 - 0.618*6.18 = 2.36。

计算 f(2.36) = (2.36-3)² + 1 = 0.4096 + 1 = 1.4096。

比较 f(2.36)=1.4096 和 f(3.82)=1.6724,前者更小,所以最小值在 [0, 3.82],新区间 [0, 3.82],复用 x1=2.36 作为新 x2。

3.3 完整迭代表格

继续迭代,我整理成表格方便对照:

迭代区间 [a,b]x1x2f(x1)f(x2)保留区间
1[0, 10]3.826.181.67211.112[0, 6.18]
2[0, 6.18]2.363.821.4101.672[0, 3.82]
3[0, 3.82]1.462.361.3721.410[1.46, 3.82]
4[1.46, 3.82]2.362.921.4101.006[2.36, 3.82]
5[2.36, 3.82]2.923.261.0061.068[2.36, 3.26]
6[2.36, 3.26]2.702.921.0901.006[2.70, 3.26]

到第 6 轮,区间已经收缩到 [2.70, 3.26],长度 0.56,中点约 2.98,离真实最小值 3 已经很近了。可以看到每轮区间长度乘以 0.618,收缩非常稳定。

提示:手算时建议保留 4 位小数,因为黄金分割比本身是无理数,过早四舍五入会累积误差,导致复用点位置对不上。

4. 代码实现与工程化细节

4.1 最小可运行版本

先用 Python 写一个最朴素的版本,把逻辑跑通:

import math def golden_section_search(f, a, b, tol=1e-6, max_iter=1000): phi = (math.sqrt(5) - 1) / 2 # 0.6180339887... x1 = b - phi * (b - a) x2 = a + phi * (b - a) f1, f2 = f(x1), f(x2) for i in range(max_iter): if abs(b - a) < tol: break if f1 < f2: b = x2 x2, f2 = x1, f1 x1 = b - phi * (b - a) f1 = f(x1) else: a = x1 x1, f1 = x2, f2 x2 = a + phi * (b - a) f2 = f(x2) return (a + b) / 2, (a + b) / 2, i + 1

这段代码里有个关键点:复用点的时候,函数值也要一起搬过去,不能只搬坐标。我见过有人只更新了 x1、x2 却忘了 f1、f2,结果每轮都多算一次函数,白白浪费了黄金搜索最大的优势。

4.2 收敛判据怎么选

上面用的是区间长度abs(b-a) < tol作为停止条件。这是最常用的判据,但工程里还有几种选择:

  • 区间长度判据:b - a < tol,简单直观,适合对自变量精度有要求的场景。
  • 函数值变化判据:abs(f1 - f2) < tol,适合关心目标值精度、对自变量精度不敏感的场景。
  • 相对精度判据:(b - a) / max(abs(a), abs(b)) < tol,适合区间量级差异大的情况。

我个人的经验是:如果函数值量级很大(比如上百万),用绝对区间长度判据更稳;如果自变量本身很小(比如 1e-3 量级),用相对判据更合理。选错判据可能导致要么迭代次数爆炸,要么精度不够。

4.3 迭代次数与精度的定量关系

黄金搜索每轮区间收缩比固定为 0.618,所以迭代 n 次后区间长度是初始长度的 0.618ⁿ 倍。反过来,如果要求精度达到 ε,需要的迭代次数是:

n = ceil( log(ε / L0) / log(0.618) )

举个例子,初始区间长度 L0 = 10,要求精度 ε = 1e-6:

n = ceil( log(1e-7) / log(0.618) ) = ceil( -16.118 / -0.481 ) = ceil(33.5) = 34

也就是约 34 次迭代,总共约 35 次函数求值。这个数字可以提前算出来,方便你评估"这个优化跑一次要多久"。如果单次求值要 1 秒,那就是 35 秒,可以接受;如果要 1 小时,那就要考虑换更高效的方法或者降低精度要求。

目标精度初始区间10初始区间1初始区间100
1e-318 次14 次22 次
1e-634 次29 次38 次
1e-949 次44 次53 次

从表里能看出一个规律:精度每提高 1000 倍,迭代次数大约增加 15 次。这个增长是对数级的,非常温和,所以黄金搜索在追求高精度时也不会突然变得不可用。

5. 单峰假设被打破时会发生什么

5.1 多峰函数的典型翻车现场

黄金搜索最容易被忽视的前提就是单峰性。我拿一个双峰函数试过:f(x) = sin(x) + 0.3*sin(3x),在 [0, 6] 上有多个局部极值。用黄金搜索跑,结果完全取决于初始区间和函数形状,有时候收敛到第一个峰,有时候收敛到第二个峰,而且算法本身不会报错,它会安安静静地给你一个"局部最优解",让你误以为找到了全局最优。

这是最危险的地方:算法不报错,但答案是错的。在调参场景里,这意味着你可能错过了一个更好的参数组合,却浑然不知。

5.2 怎么判断函数是否单峰

实际工程里,判断单峰性有几个实用手段:

  1. 先粗扫一遍:在区间内均匀取 20~50 个点,画出函数值曲线,肉眼看是否有多个峰谷。这一步成本很低,但能避免大部分翻车。
  2. 多起点跑多次:把区间分成几段,每段分别跑黄金搜索,比较结果。如果不同段给出不同的极值点,说明函数多峰。
  3. 结合先验知识:如果目标函数来自某个物理过程,通常能从机理上判断它是否单峰。比如"误差平方和"类目标函数,在参数空间里往往是单峰的。

注意:如果确认是多峰函数,黄金搜索只能作为局部搜索使用,需要配合网格搜索、随机重启、或者模拟退火等全局策略。不要指望黄金搜索自己跳出局部最优。

5.3 区间端点处极值的处理

还有一种边界情况:极值恰好落在区间端点上。比如 f(x) = x² 在 [0, 10] 上,最小值在 x=0。黄金搜索的内点永远在区间内部,它会把区间不断向 0 收缩,但永远取不到 0,最后返回一个非常接近 0 的小正数。

这在数学上没问题(精度足够时误差可忽略),但如果你的业务逻辑要求"必须精确取到端点",就需要额外判断:迭代结束后,比较端点值和内点值,取最小的那个。这个细节很多教程不讲,但实际用的时候不加这一步,可能会在边界场景下得到偏差较大的结果。

6. 实战中的参数取舍与踩坑记录

6.1 精度设太高反而出问题

我踩过一个坑:把 tol 设成 1e-12,结果程序跑了很久还没停。原因是浮点数精度极限。当区间长度小到 1e-16 量级时,b - a的计算会因为浮点舍入误差而失去意义,x1 和 x2 可能算出来相等,导致死循环或者结果抖动。

经验值是:双精度浮点数下,tol 不要小于 1e-10。如果确实需要更高精度,应该换用更高精度的数值类型,或者改用解析方法。盲目追求极小 tol 是新手常见误区。

6.2 函数求值有噪声时的应对

如果目标函数带随机噪声(比如每次调用返回的值有波动),黄金搜索的比较步骤f1 < f2会变得不可靠。噪声可能让算法误判方向,把本该保留的区间丢掉。

应对办法有两个:一是对每个点多次求值取平均,降低噪声影响;二是改用容忍度比较,比如f1 < f2 - delta才认为 f1 更小,其中 delta 是噪声量级。第二种方法更省求值次数,但 delta 的选取需要一点经验,一般取噪声标准差的 1~2 倍。

6.3 和"网格搜索"的配合策略

在实际调参里,我很少单独用黄金搜索,更常见的组合是:先用网格搜索粗定位,再用黄金搜索精修。网格搜索负责排除多峰干扰、找到大致的最优区间,黄金搜索负责在这个小区间里快速逼近。这样既避免了黄金搜索陷入局部最优,又避免了网格搜索精度不够的问题。

具体做法:在参数范围里均匀取 10~20 个点,找到函数值最小的那个点,以它为中心、左右各取一个网格间距作为新的搜索区间,然后交给黄金搜索。这个组合策略在我做过的几个参数寻优任务里都很稳。

6.4 一个容易忽略的初始化细节

初始化时 x1 和 x2 的计算公式,不同资料写法略有差异,有的用x1 = a + (1-φ)(b-a),有的用x1 = b - φ(b-a)。这两个式子在数学上等价,但浮点计算下可能产生微小差异。如果代码里初始化和迭代更新用了不一致的公式,复用点就可能对不上,导致每轮多算一次函数。

我的建议是:初始化和迭代更新统一用同一套公式,要么都用b - φ(b-a)和a + φ(b-a),要么都用a + (1-φ)(b-a)和a + φ(b-a),不要混用。

7. 黄金搜索在更大框架里的位置

黄金搜索虽然是个一维算法,但它的思想经常被用在更高维的优化里。比如坐标下降法,就是每次固定其他维度、只对一个维度做一维搜索,而这一维搜索就可以用黄金搜索来完成。再比如线性搜索,在梯度下降的每一步确定步长时,黄金搜索也是一个可选方案(虽然实践中更常用 Armijo 等基于导数的准则)。

它的局限也很明确:只适合一维、单峰、求值成本可接受的场景。维度一高,黄金搜索就无能为力了,这时候要考虑 Nelder-Mead、贝叶斯优化等方法。但反过来说,正因为它的适用面窄,它在自己擅长的场景里表现得非常可靠——实现简单、不需要导数、收敛稳定、迭代次数可预测,这些优点让它在工程实践中一直有一席之地。

我自己在做仿真参数标定时,如果确认目标函数是单峰的,第一反应就是上黄金搜索,通常几十次仿真就能把参数精度做到 1e-4 以内,比手动试凑快太多了。真正需要警惕的不是算法本身,而是用之前有没有确认单峰性、用之后有没有检查结果是否落在合理范围内。把这两步做到位,黄金搜索就是一个非常省心的工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询