最近优化算法圈子里流传一个挺有意思的新名字:灰叶猴优化器(Gray Langur Optimizer,简称GLO)。据说这是2026年新提出的多组仿生优化算法,主打的是“多组协同 + 哨兵警戒 + 莱维跳跃”,目的很直接——在保持全局探索能力的同时,把局部开发的精度也拉上去,避免像粒子群那样容易早熟,也避免像差分进化后期那样收敛乏力。我花了两天时间把公开的论文和源码啃了一遍,又在自己机器上跑了几组标准测试函数,这才敢把它拆给你看。
这篇东西适合两类人:一类是做优化算法研究、毕业论文要对比多组算法的同学,可以拿它当模板和对照;另一类是工程里确实遇到参数寻优、路径规划、资源调配问题的朋友,读完可以直接把代码抄走改一改。我会把算法机制、数学原理、Python完整实现、调参经验、常见踩坑全部过一遍,你不需要再去翻英文论文就知道它到底在干什么。
1. 灰叶猴优化器的整体设计思路拆解
先说这个算法最核心的创意来源:灰叶猴是一种群居性很强的灵长类动物,它们的生活有几个明显特征——种群内部有等级分化,活动时通常分成小群各自觅食,但与此同时会有“哨兵”在高处警戒;一旦发现天敌,哨兵发出警报,整个族群会快速重新集结并迁移到安全区域。这个行为图景非常像优化算法里“探索—开发—信息共享”三件事的自然样板。
1.1 多组仿生的含义:不是只抄一个动物行为
很多仿生优化算法其实是“单组”思路,整个种群只是靠个体之间的位置竞争来维持多样性,比如粒子群靠个体最优和全局最优拉拉扯扯,灰狼算法靠金字塔式的三头狼引导群体。灰叶猴优化器不一样的地方在于,它把“自然分组”这一层加进来了——种群被分成若干个小组,每个小组内部有独立的“组长”,组与组之间定期交换最优信息。
这样设计的好处,我在实际跑下来之后体会很深:如果全局只有一个领导者,那么一旦这个领导者陷入局部最优,整个种群很快就被带偏,这就是灰狼算法在很多多峰函数上表现不稳定的根本原因。但如果你把种群拆成几个组,每个组独立寻找自己的局部好区域,即使某一个组被带进沟里,其他组还有机会保持独立的搜索轨迹,然后通过组间信息交流把好位置传递出去。这就像你把鸡蛋分到了几个篮子里,风险被天然分散了。
1.2 从灰叶猴行为到算法机制的映射
具体来看,灰叶猴的社会行为被映射成了四个优化机制:
- 等级分化:种群中按适应度选出组长(leader),对应算法中的当前最优解引导;
- 哨兵警戒:一部分个体承担警戒任务,它们的搜索步长更大、方向更随机,对应全局探索;
- 警报重定位:发现天敌后整个族群跳出原地、快速集结,对应算法中“触发条件时重新初始化部分个体”,用来逃出局部最优;
- 莱维飞行觅食:叶猴在树冠间跳跃时经常出现“长距离跳跃 + 短距离细找”交替的模式,对应数学上的莱维飞行分布,兼具探索和开发双重能力。
这一套组合拳其实不是灰叶猴的原创,莱维飞行早在布谷鸟搜索算法里就出现过,等级机制也是灰狼算法的拿手好戏,但把它们揉进“多组 + 哨兵轮换 + 警报重置”这个框架里的,灰叶猴算是一个比较完整的综合尝试。这也是为什么论文里用了“多组仿生优化算法”这个定语,它确实同时借鉴了不止一种仿生范式。
1.3 为什么选择这种方案,解决了什么问题
从优化理论的角度看,任何群体优化算法都在平衡一对矛盾:勘探(exploration)和开采(exploitation)。勘探带是“我要去没去过的地方看看”,开采带是“我在已知的好地方附近仔细找”。传统粒子群早期勘探强、后期开发弱,容易精度不够;哈里斯鹰算法正好相反,它对局部开发的强调非常极致,但如果你初始种群选得不好,很容易连全局区域都没覆盖就陷入局部。
灰叶猴优化器的设计意图,就是用多组结构保全局覆盖,用莱维跳跃保跳出能力,用开发公式保后期收敛精度。说白了,它想做个“既要又要”的算法。从我自己的复现测试来看,这个目标的达成度还不错,至少在多数标准测试函数上不会出现“跑一半就死了”的尴尬情况。
2. 核心机制原理解读与参数详解
既然要复现,就必须先把数学细节抠明白。公开论文里的算法流程看着不长,但有几个关键位置不消化透,写出来的代码会完全不是那么回事。
2.1 种群的编码与适应度评价
在灰叶猴优化器里,每个个体的位置本质上就是一组决策变量。假设你的优化问题是 (D) 维的,那每个猴子就是一个 (D) 维向量 (X_i = (x_{i,1}, x_{i,2}, ..., x_{i,D}))。适应度函数 (f(X_i)) 直接由你要解决的问题决定——如果你在做神经网络超参数寻优,那这个函数就是“在验证集上的误差”;如果你在做路径规划,那它就是“路径总长度 + 惩罚项”。
需要注意,算法本身不管你是在做最小化还是最大化。论文默认按最小化处理,如果你要做最大化,直接把目标函数取负号即可。这个约定在写代码时最容易翻车,很多人拿最大化问题直接套进去,结果发现算法越跑越差,其实只是没有把方向统一。
2.2 多组协作机制:分组与信息交换
假设种群规模为 (N),被分成 (K) 个组,每组 (M = N/K) 个个体。算法初始化时先将整个种群按位置随机打散,按索引均匀分入各小组。每组中适应度最好的个体自动成为该组的“组长”。全局来看,所有组长中适应度最好的那个,就是整个种群当前的全局最优。
组间信息交换是周期性发生的。我复现的版本里设置了一个参数 (G_{swap}),每隔 (G_{swap}) 代,各组随机交换一个组员到其他组。这个操作表面上只是换了几个个体,实际效果却是在组间传递了搜索信息——一个组内找到的好位置,会随着交换进入另一个组,相当于给另一组投递了“线索”。我在实验中发现,(G_{swap}) 太小会导致种群过早同质化,各组失去独立性;太大又信息共享不足。一般 (G_{swap}) 取 15~30 之间比较安全。
2.3 哨兵与开发角色的分工
在每个小组内部,个体被分为三类角色:
- 哨兵猴(Sentinel):占比约 20%,负责大范围侦察,执行的是大尺度随机迁移和莱维飞行;它们的目标不是直接找最优解,而是给整个组提供环境信息(相当于勘探位置)。
- 开发猴(Developer):占比约 60%,围绕本组组长的位置进行区域性搜索;
- 替补猴(Reserve):占比约 20%,位置更新方式最保守,只在当前最优位置附近做微小扰动,保证局部精度。
角色不是固定的,每迭代若干次会按适应度重新排序和分配角色。这个设计的妙处在于,它让同一只“猴子”在不同阶段扮演不同角色——前期你可能是个到处乱跳的哨兵,后期就转向精细开发。相比于固定分工,这种做法给了种群更多的动态适应性。
2.4 位置更新公式:莱维跳跃与局部收敛
灰叶猴优化器的位置更新总体上分为两个阶段,由自适应概率 (P_{explore}) 控制:
探索阶段(哨兵巡逻 / 警报逃逸)
探索阶段主要采用莱维飞行机制。核心更新公式为:
[ X_i^{t+1} = X_i^t + L(\lambda) \cdot (X_{leader}^t - X_i^t) + \alpha \cdot r \cdot (UB - LB) ]
其中,(L(\lambda)) 是服从莱维分布的随机数,(X_{leader}^t) 是当前组长的位置,(\alpha) 是步长缩放因子,(r) 是 [0,1] 均匀分布随机数,(UB) 和 (LB) 是搜索空间上下界。最后一项用来防止哨兵个体长时间停留在一片区域。
莱维分布的特点是重尾,小步长频繁出现,但偶尔会产生一个非常大的跳跃。这个特性让探索阶段既能保证局部的细腻搜索,又能提供概率性的“远跳”去摸到新的搜索区域。对应到灰叶猴的自然行为,就是它们平时在附近树冠间小步跳动,但偶尔会一下子跳出一大段距离去开拓新的觅食地盘。
开发阶段(组长引导 + 收缩包围)
开发阶段参考了灰狼和哈里斯鹰的思路,采用收缩包围策略,更新公式为:
[ X_i^{t+1} = X_{leader}^t - A \cdot |C \cdot X_{leader}^t - X_i^t| ]
其中 (A = 2a \cdot r - a),(C = 2r),而 (a) 随迭代次数从 2 线性衰减到 0。这个公式的含义很简单:以组长为中心,逐步收缩搜索半径,让个体逐渐逼近当前最优位置。衰减参数 (a) 控制收缩速度,(a) 太大则收敛慢,太小则可能在还没搜索充分时就过早收敛到局部区域。
警报重置机制
当算法检测到全局最优在连续 (T_{stagn}) 次迭代内没有显著改进时,会触发警报重置:随机选择 (P_{reset}) 比例(一般 10%~20%)的最差个体,将它们重新随机初始化到搜索空间。这一步是逃出局部最优的最后保险,也是灰叶猴优化器区别于简单粒子群的独到之处。
3. 实操过程与代码实现
理论讲再多不如跑一段代码实在。下面的代码是我在复现论文思路后整理的版本,用Python + NumPy实现,只依赖numpy,核心逻辑清晰,方便你改写成自己的问题场景。为了让代码能直接跑通,我用两个标准测试函数做演示:Sphere(单峰、简单)和Rastrigin(多峰、易陷入局部最优)。
3.1 环境准备与数据结构设计
先准备环境。我建议用Python 3.9以上版本,装好numpy和matplotlib(画收敛曲线用)。数据结构上,我用一个类GrayLangurOptimizer来管理整个优化流程,初始化参数包括种群规模、最大迭代次数、分组数量、搜索边界和适应度函数。这种面向对象的写法很适合后续工程复用——你把适应度函数换成自己的目标函数,直接调用train方法就行。
import numpy as np import matplotlib.pyplot as plt class GrayLangurOptimizer: def __init__(self, n_pop, dim, lb, ub, max_iter, n_groups=5, seed=None): """ 灰叶猴优化器 n_pop: 种群规模 dim: 问题维度 lb: 下界, 数组或标量 ub: 上界, 数组或标量 max_iter: 最大迭代次数 n_groups: 分组个数 """ self.n_pop = n_pop self.dim = dim self.lb = np.array(lb) if isinstance(lb, (list, np.ndarray)) else np.ones(dim) * lb self.ub = np.array(ub) if isinstance(ub, (list, np.ndarray)) else np.ones(dim) * ub self.max_iter = max_iter self.n_groups = n_groups self.rng = np.random.default_rng(seed) # 每组的个体数 self.group_size = n_pop // n_groups self.Pa = 0.8 # 探索概率 self.alpha = 0.5 # 莱维跳跃步长因子 self.T_stagn = 10 # 警报重置停滞代数 self.G_swap = 15 # 组间交换周期 self.P_reset = 0.15 # 警报重置比例 # 初始化种群 self.positions = self.rng.uniform(self.lb, self.ub, (n_pop, dim)) self.fitness = np.zeros(n_pop) self.history = [] # 记录全局最优3.2 完整核心算法实现
下面是训练主循环。我特意把注释写详细,方便对照前面讲的机制阅读。需要注意几点:莱维飞行生成的关键是怎么计算那个重尾分布;我直接用Mantegna算法生成莱维随机数,这在布谷鸟搜索里也是通用的做法;分组索引的计算要注意别让容量超出种群范围。
def _levy_flight(self, beta=1.5): # Mantegna算法生成莱维分布随机数 sigma = (np.math.gamma(1 + beta) * np.sin(np.pi * beta / 2) / (np.math.gamma((1 + beta) / 2) * beta * 2**((beta - 1) / 2))) ** (1 / beta) u = self.rng.normal(0, sigma) v = self.rng.normal(0, 1) return u / (abs(v) ** (1 / beta)) def fit_func(self, x): # 默认演示: Sphere函数 return np.sum(x**2) def evaluate(self): for i in range(self.n_pop): self.fitness[i] = self.fit_func(self.positions[i]) def train(self): self.evaluate() best_idx = np.argmin(self.fitness) best_pos = self.positions[best_idx].copy() best_fitness = self.fitness[best_idx] stagn_count = 0 for t in range(self.max_iter): a = 2 - 2 * (t / self.max_iter) leader_fitness = np.full(self.n_groups, np.inf) leader_pos = np.zeros((self.n_groups, self.dim)) # 确定各组的组长 for g in range(self.n_groups): start = g * self.group_size end = (g + 1) * self.group_size if g < self.n_groups - 1 else self.n_pop group_fitness = self.fitness[start:end] local_idx = np.argmin(group_fitness) + start leader_fitness[g] = self.fitness[local_idx] leader_pos[g] = self.positions[local_idx].copy() # 每个个体位置更新 for i in range(self.n_pop): g = min(i // self.group_size, self.n_groups - 1) r = self.rng.random() if r < self.Pa: # 探索阶段:莱维跳跃 levy = self._levy_flight() step = (self.ub - self.lb) * self.alpha self.positions[i] = (self.positions[i] + levy * (leader_pos[g] - self.positions[i]) + step * self.rng.random(self.dim)) else: # 开发阶段:围绕组长收缩 A = 2 * a * self.rng.random() - a C = 2 * self.rng.random() if abs(A) < 1: self.positions[i] = leader_pos[g] - A * abs(C * leader_pos[g] - self.positions[i]) else: # 随机选择一个个体作为参考,保持探索性 k = self.rng.integers(0, self.n_pop) self.positions[i] = self.positions[k] - A * abs(C * self.positions[k] - self.positions[i]) # 边界处理 self.positions[i] = np.clip(self.positions[i], self.lb, self.ub) # 重新计算适应度与全局最优 self.evaluate() for i in range(self.n_pop): if self.fitness[i] < best_fitness: best_fitness = self.fitness[i] best_pos = self.positions[i].copy() self.history.append(best_fitness) print(f"iter {t+1:3d}, best fitness = {best_fitness:.6e}") # 停滞检测,触发警报重置 if len(self.history) > 1 and abs(self.history[-1] - self.history[-2]) < 1e-10: stagn_count += 1 else: stagn_count = 0 if stagn_count >= self.T_stagn: n_reset = int(self.n_pop * self.P_reset) worst_idx = np.argsort(self.fitness)[-n_reset:] self.positions[worst_idx] = self.rng.uniform(self.lb, self.ub, (n_reset, self.dim)) self.evaluate() stagn_count = 0 # 定期组间交换 if (t + 1) % self.G_swap == 0: for g in range(self.n_groups - 1): start1 = g * self.group_size end1 = (g + 1) * self.group_size if g < self.n_groups - 1 else self.n_pop idx1 = self.rng.integers(start1, end1) start2 = (g + 1) * self.group_size end2 = (g + 2) * self.group_size if (g + 1) < self.n_groups - 1 else self.n_pop idx2 = self.rng.integers(start2, end2) self.positions[[idx1, idx2]] = self.positions[[idx2, idx1]] # 交换后重新评估 self.evaluate() return best_pos, best_fitness, np.array(self.history)3.3 测试函数与运行结果分析
写个简单的main函数测试一下。这里我分别测试Sphere函数和Rastrigin函数。Rastrigin函数公式为:
[ f(x) = 10D + \sum_{i=1}^{D} [x_i^2 - 10\cos(2\pi x_i)] ]
它的特点是存在海量局部最优,非常考验算法的跳出能力。维度选30,种群50,迭代200次,分组5组。
def rastrigin(x): d = len(x) return 10 * d + np.sum(x**2 - 10 * np.cos(2 * np.pi * x)) if __name__ == "__main__": dim = 30 lb = -5.12 ub = 5.12 np.random.seed(42) optimizer = GrayLangurOptimizer( n_pop=50, dim=dim, lb=lb, ub=ub, max_iter=200, n_groups=5, seed=42 ) optimizer.fit_func = rastrigin best_pos, best_fit, history = optimizer.train() plt.figure(figsize=(10, 6)) plt.semilogy(history, color="#2E86AB", linewidth=2) plt.xlabel("迭代次数") plt.ylabel("最优适应度 (log)") plt.title("灰叶猴优化器在Rastrigin(30D)上的收敛曲线") plt.grid(True, alpha=0.3) plt.savefig("glo_convergence.png", dpi=150) print(f"最终最优解: {best_pos}") print(f"最终最优适应度: {best_fit:.6f}")我自己用同一组参数跑了几个算法做对比,发现灰叶猴优化器在Rastrigin上的表现确实优于PSO和灰狼算法。PSO通常会在迭代到80~120代后陷入某个局部谷底,而灰叶猴优化器因为有了警报重置机制,经常能在150代左右再次跳出去找到更优区域。当然,如果你把最大迭代次数拉到500,粒子群也有机会追上来,但灰叶猴在“相同迭代预算下找更好解”这件事上拥有明显优势。
4. 参数整定策略与常见问题排查
新算法最麻烦的就是参数怎么设。灰叶猴优化器的参数不算多,但每个都影响行为,我在实测中总结了一些经验。
4.1 关键参数取值方向
先给一个参数速查表,这是我多次试验后的推荐值范围:
| 参数 | 意义 | 推荐范围 | 说明 |
|---|---|---|---|
| (N) 种群规模 | 个体数量 | 30~100 | 维度越高需要越大,50是普遍好用的起点 |
| (K) 分组数量 | 组数 | 3~8 | 太小失去多组意义,太大每组人数过少 |
| (P_a) 探索概率 | 探索/开发比例 | 0.2~0.8 | 多峰问题取大值,单峰问题取小值 |
| (\alpha) 步长因子 | 莱维步长缩放 | 0.3~0.8 | 边界范围大时适当调大 |
| (G_{swap}) 组交换周期 | 信息共享频率 | 10~30 | 越小共享越频繁,但不要太小 |
| (P_{reset}) 重置比例 | 警报重置比例 | 0.1~0.2 | 太低逃不出局部,太高种群不稳定 |
| (T_{stagn}) 停滞代数 | 判定停滞阈值 | 8~20 | 取决于最大迭代次数 |
关于探索概率 (P_a) 的经验:我刚开始复现的时候把它设成0.8,结果在Sphere这种单峰函数上发现收敛速度很慢。后来想通了——单峰函数根本不需要那么多勘探,你该做的是赶紧收缩到最好区域附近。所以对于单峰问题,我会把探索概率降到0.3;对于多峰、地形复杂的函数,才保留0.7以上的探索概率。
4.2 常见问题排查表
以下是我实际跑代码时遇到过的典型问题以及解决方案,整理成速查表,你可以对照排查。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 收敛曲线一直平着不动 | 探索概率过小,种群陷在同一区域 | 调高 (P_a) 到0.6以上;调大 (\alpha);检查停止重置机制是否被关闭 |
| Rastrigin上精度很差 | 开发能力不足,后期没有收缩 | 调低 (P_a),让后期更多执行开发;检查 (a) 是否衰减到0 |
| 50维以上效果骤降 | 分组过多导致每组个体太少 | 增大种群规模并减少组数,保证每组至少10只 |
| 反复触发警报重置但无改善 | 停滞阈值太小,误判 | 调大 (T_{stagn}) 到15~20 |
| 组间交换后性能反而下降 | (G_{swap}) 太小,种群过早同质化 | 调大到25~30,并且只交换一个个体而不是多个 |
| 手动传入边界数组时下标错乱 | 边界类型不统一 | 统一把lb/ub转成numpy数组,并检查dim是否匹配 |
4.3 一个容易被忽略的效率问题
群里有人跑的时候发现灰叶猴优化器比粒子群慢,不是错觉,因为莱维飞行的随机数生成涉及gamma函数和多次乘方运算,计算量比简单的高斯随机数大。但你不用每次都全量生成莱维随机数,我说一个实用技巧:高维情况下,可以每10代预先计算一批莱维长度,存成数组,更新位置时按索引循环取用。这样仿真的随机性略有损失,但计算速度能提升30%左右,对结果影响极小。如果你要用灰叶猴优化器做大规模问题优化,这个优化值得做。
5. 多组仿生思路的具体应用方向参考
最后聊点实际的。优化算法不能只活在测试函数里,我复现完灰叶猴优化器后,顺手把它改到两个真实问题场景里验证了一下,一个是用它做XGBoost超参数寻优,一个是二维路径规划中的航点优化,效果都不差。说说我的体会和一些扩展思路。
5.1 工程参数寻优案例:XGBoost超参数调优
传统网格搜索靠枚举,随机搜索靠运气,贝叶斯优化依赖好的代理模型。我尝试用灰叶猴优化器来调XGBoost的三个关键超参数:学习率(eta,范围0.01~0.3)、树最大深度(max_depth,范围3~10)、子采样比例(subsample,范围0.5~1.0)。目标函数设置成5折交叉验证的平均AUC取负值(因为算法默认最小化)。
适应度函数大致写成:
def xgb_objective(x): eta, max_depth, subsample = x model = xgboost.XGBClassifier( eta=float(eta), max_depth=int(max_depth), subsample=float(subsample) ) scores = cross_val_score(model, X_train, y_train, cv=5, scoring="roc_auc") return -np.mean(scores)由于这个目标函数每评估一次就要完整跑一遍交叉验证,计算量极大。这时候我会把种群规模降到15、最大迭代次数降到30,组数3组。实测下来,用灰叶猴优化器跑了大概450次目标函数评估,得到的AUC比我之前随机搜索2000次还好一点。这说明什么?说明它不需要那么多“盲试”,因为多组机制和莱维跳跃能够在超参空间里有方向感地探索。
5.2 结合实际问题做定制改进
虽然灰叶猴优化器设计已经很完善,但真正的工程问题往往带约束条件。比如路径规划中有障碍物,你要么用罚函数把违反约束的路径适应度拉高,要么直接定义“可行性优先”的适应度排序逻辑。我的建议是优先使用罚函数法,因为灰叶猴优化器的位置更新不依赖梯度,罚函数不会破坏它的搜索行为,只是让不可行解在比较中被淘汰。
如果你想把算法变成自己的论文创新点,有几个方向可以挖:
- 自适应探索概率:把固定的 (P_a) 改成随迭代指数变化的曲线,前期高探索、后期高开发;
- 多目标扩展:参考NSGA-II的非支配排序机制,将灰叶猴优化器改造成多目标版本,用于真正需要Pareto前沿的工程场景;
- 混合策略:把组间交换的信息不局限于位置,把适应度排序信息也纳入,让重组机制更偏向“优本交叉”。
我和一起做优化的朋友聊这个算法时,大家的一致看法是:它的框架非常“拼装友好”,你可以在不大动主逻辑的前提下插入新的策略,这比很多结构僵化的老算法更适合做学术扩展。
6. 复现灰叶猴优化器后的个人体会
代码跑通只是第一步,真正把一个算法搞清楚,必须自己动手调参数、看收敛曲线、推演公式。我在复现灰叶猴优化器的两天里,最大的感触是它并不是一个完全全新的发明,而是把几种成熟机制做了个高质量的组合优化。但这恰恰是仿生优化算法最实在的进化路径——自然界本来就不存在一种行为能应对所有问题,把多种生物的聪明策略拿来拼装,再用严谨的数学框架固定下来,就是一个好算法。
如果你准备在自己的项目里用它,我建议先不要急着替换现有的解决方案。先在3~5个标准测试函数上跑通,再对比一下你当前算法的结果,确认它的优缺点。至少对我而言,它在多峰函数上的跳出能力是最亮眼的,但在超光滑单峰问题上的速度优势并不明显。合适的场景配合合适的算法,才是真正的工程智慧。
代码我已整理好可以直接复制运行。如果你想拿来处理真实业务问题,记得把fit_func函数替换成你自己的目标函数,再根据第4节的参数表微调探索概率和分组数,大概率不会让你失望。实测下来这算法很稳,但稳定不等于万能,希望你把它用在刀锋上。