尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

灰狼优化算法GWO详解:从原理、Python实现到调参实践

灰狼优化算法GWO详解:从原理、Python实现到调参实践 不知道大家有没有注意到很多看起来“年轻”的技术实际实力并不弱。比如灰狼优化算法Grey Wolf OptimizerGWO它 2014 年才提出在智能优化算法家族里算是“年纪不大”但它的收敛速度、寻优稳定性和工程应用范围却一点也不“小”。有网友用一句话形容它“小狼只是年纪不大其他都大。”放在 GWO 身上意外地贴切。本文我会和大家一起从算法原理、数学公式、Python 实现到常见调参经验把 GWO 完整拆解一遍。如果你正准备学习群体智能算法或者在找一个新的优化工具做课题实验这篇文章可以直接作为入门教程。1. 背景与核心概念1.1 什么是灰狼优化算法灰狼优化算法是一种模拟灰狼群体社会等级和捕猎行为的元启发式优化算法由 Mirjalili 等人在 2014 年提出。它的基本思想并不复杂将一组候选解看成一群灰狼通过模拟狼群在捕食过程中的“包围猎物”“追踪猎物”“攻击猎物”等行为让候选解在解空间中不断向更优的区域移动。灰狼群体内部有着严格的等级制度。头狼 α 负责群体决策是理论上离最优解最近的个体β 是 α 的副手当 α 缺失时可以接替指挥δ 负责侦察、放哨、守卫等任务剩下的 ω 则是群体中的普通个体负责跟随前三个等级的狼移动。GWO 将这套等级关系映射到优化问题中α 对应当前最优解β 对应次优解δ 对应第三优解其余候选解都归为 ω。这种设计的巧妙之处在于算法不需要像遗传算法那样设计选择、交叉、变异算子也不需要像粒子群算法那样维护个体的历史最优位置和全局最优速度。GWO 只需要保存三个当前最优解然后让所有其他个体根据这三条“指引线”更新自己的位置整体的计算逻辑非常简洁。1.2 GWO 能解决什么问题从数学角度来看GWO 解决的是连续优化问题在给定的变量边界内寻找使目标函数最小化或最大化的变量组合。这类问题在工程领域几乎无处不在。例如机器学习模型中的超参数寻优学习率、正则化系数、树模型深度这些参数往往没有一个通用的最优值需要结合具体数据不断搜索再比如 PID 控制器的三个系数 Kp、Ki、Kd 需要整定整定过程本质上就是一个三维连续优化问题。GWO 也常用于路径规划中的路径点搜索、图像分割中的阈值选取、无线传感器网络的节点部署优化、神经网络初始权重选择等场景。它的优势在于参数少、实现简单、对初始值不敏感尤其适合在目标函数没有梯度信息、或者目标函数非凸多峰的情况下做全局搜索。当然GWO 并不是万能的。它在处理超高维问题比如上千维时同样面临群体智能算法常见的“维度灾难”问题对于离散组合优化问题也需要做额外的编码映射才能使用。但它作为入门群体智能算法、建立优化思维的第一个模型性价比非常高。1.3 GWO 与其他群体智能算法的对比为了更直观地理解 GWO 的定位这里把它和两种最常见的群体智能算法做对比。对比项GWO 灰狼优化算法PSO 粒子群算法GA 遗传算法提出时间2014 年1995 年1975 年灵感来源灰狼群体捕猎鸟群觅食自然选择与遗传需要调节的参数很少主要是种群数量和迭代次数惯性权重、个体学习因子、社会学习因子交叉概率、变异概率、选择策略位置更新方式α、β、δ 三只头狼引导个体最优和全局最优引导选择、交叉、变异实现难度较低较低中等局部开发能力较强较强一般全局探索能力较强一般较强需要说明的是“哪个算法更好”没有标准答案。在 benchmark 测试函数上GWO 的收敛速度通常优于 PSO 和 GA但在某些特定结构的问题上其他算法也可能表现更好。实际使用中更重要的是理解每种算法的搜索逻辑然后针对具体问题做测试和选择。2. 算法原理与数学建模2.1 灰狼的社会等级结构GWO 的第一步是抽象灰狼的社会等级。在算法中我们将所有候选解按适应度排序适应度最好的个体命名为 α第二为 β第三为 δ剩余个体全部为 ω。搜索过程主要由 α、β、δ 三个个体来主导ω 个体则跟随这三个位置更新自己。这里需要理解一个关键点α 是当前代数中找到的最优解但它并不是理论上的全局最优解。算法通过 β 和 δ 的参与避免了整个狼群盲目地向 α 一个点收缩。如果只保留 α相当于变成了一个贪心策略很容易陷入局部最优。加入 β 和 δ相当于保留了多个候选方向增加了跳出局部最优的概率。2.2 包围猎物灰狼捕猎的第一步是包围猎物。在数学模型中包围行为可以用下面两个公式来描述$$ D |C \cdot X_p(t) - X(t)| $$$$ X(t1) X_p(t) - A \cdot D $$其中$X_p(t)$ 是当前最优解的位置也就是 α 的位置$X(t)$ 是某一头灰狼当前的位置$D$ 表示灰狼与猎物之间的距离差。$A$ 和 $C$ 是两个重要的系数计算方式如下$$ A 2a \cdot r_1 - a $$$$ C 2 \cdot r_2 $$其中 $r_1$ 和 $r_2$ 是取值范围在 0 到 1 之间的随机数$a$ 则从 2 线性递减到 0随着迭代次数增加不断变小。先解释 $C$ 的作用。$C$ 是 0 到 2 之间的随机数它可以调节灰狼对猎物“感知距离”的放大或缩小。当 $C 1$ 时算法会扩大搜索范围当 $C 1$ 时算法会缩小搜索范围。这种随机性可以让灰狼在搜索过程中表现出不同的行为增强全局探索能力。再解释 $A$。$A$ 的取值会在 $[-a, a]$ 之间变化。当 $|A| 1$ 时灰狼会向猎物方向靠近相当于局部开发当 $|A| 1$ 时灰狼会远离猎物相当于全局探索。$A$ 的符号则决定了灰狼是向猎物的左侧还是右侧移动。这种动态变化是 GWO 能够在探索与开发之间保持平衡的关键。2.3 狩猎与位置更新在真实的灰狼捕猎过程中α、β、δ 对猎物位置有不同的判断。GWO 假设它们对猎物的位置都有一定程度的认知因此让每只 ω 狼分别朝向 α、β、δ 方向更新最终取三个方向结果的平均值。计算过程如下对于每个候选解 $X$需要分别计算它与 α、β、δ 之间的距离$$ D_\alpha |C_1 \cdot X_\alpha - X| $$$$ D_\beta |C_2 \cdot X_\beta - X| $$$$ D_\delta |C_3 \cdot X_\delta - X| $$然后计算向这三个方向移动的候选位置$$ X_1 X_\alpha - A_1 \cdot D_\alpha $$$$ X_2 X_\beta - A_2 \cdot D_\beta $$$$ X_3 X_\delta - A_3 \cdot D_\delta $$最终位置更新为三者的平均值$$ X(t1) \frac{X_1 X_2 X_3}{3} $$这里三个 $A$ 和三个 $C$ 都是独立生成的随机参数所以即使每只灰狼同时参考三个头狼最终每只狼移动的方向和距离也各不相同。这种设计保留了狼群行为的随机性有利于维持种群多样性。2.4 攻击猎物与全局搜索攻击猎物对应算法中的收敛阶段。当 $a$ 从 2 线性递减到 0 时$A$ 的取值范围不断缩小。当 $|A| 1$ 时灰狼会向猎物方向逼近这个过程叫“攻击”本质上是局部精细搜索。当 $|A| 1$ 时灰狼会远离猎物强制它们向其他方向搜索这个过程对应“寻找猎物”也就是全局探索。GWO 通过这种方式让算法在前半段以较大的步长搜索整个解空间在后半段逐渐收缩到最有希望的区域进行精细寻优。唯一需要留意的是 $C$ 在攻击阶段的随机性。即使 $|A| 1$由于 $C$ 的存在灰狼的位置更新仍然带有一定的不确定性可以继续探索未访问过的区域。这个设计能有效降低算法陷入局部最优的概率。2.5 GWO 伪代码与算法流程综合上面内容GWO 的完整流程可以用以下步骤描述初始化灰狼种群设置种群数量、维度、变量边界和最大迭代次数。计算每只灰狼的适应度。找出适应度最好的三只狼分别记为 α、β、δ。进入主循环更新参数 a计算 A 和 C。按公式计算出每只灰狼的新位置并修正边界。重新计算所有灰狼的适应度。更新 α、β、δ 的位置与适应度。判断是否达到最大迭代次数如果未达到则继续循环否则输出 α 的位置和适应度。这个流程结构非常清晰没有任何复杂的算子设计。接下来我们用 Python 把每一步完整实现出来。3. 环境准备与项目结构3.1 运行环境说明本文的示例代码以 Python 为基础核心算法部分只使用 NumPy 处理数组计算可视化部分使用 Matplotlib。你不需要安装任何深度学习框架也不需要 GPU普通的笔记本环境就能直接运行。具体环境方面我建议使用 Python 3.8 及以上版本。NumPy 和 Matplotlib 的版本不需要非常精确使用常见的新版本即可。如果你的环境还没有安装这两个库请先在终端执行安装命令。下面的命令适用于大多数 Python 环境pip install numpy matplotlib如果你使用的是 Anaconda 环境也可以使用conda install numpy matplotlib如果你还需要在 Jupyter Notebook 中查看动态图确保已经安装了 ipykernel 和 matplotlib 的配套支持。3.2 项目目录结构为了让代码结构更清晰我把算法代码、测试函数和入口程序分开存放。这里给出一个建议的项目结构gwo-demo/ ├── benchmark_functions.py # 基准测试函数 ├── gwo_algorithm.py # GWO 核心算法 ├── demo.py # 入口程序运行算法并输出结果 └── visualization.py # 搜索过程可视化脚本你完全可以把所有代码写在一个文件里但拆分文件的好处是后续想替换测试函数、对比不同算法或者扩展改进版本时不需要改动算法主体代码。这个结构也是我在实际工程中的习惯。4. 完整实战Python 实现 GWO4.1 定义基准测试函数为了验证算法效果我们需要用标准测试函数来评估 GWO。这里选用两个经典的 benchmark 函数。第一个是 Sphere 函数它是最简单的单峰函数用来测试算法的基础寻优能力# 文件路径benchmark_functions.py import numpy as np def sphere(x): Sphere 函数全局最小值在 x(0,0,...,0) 处最小值为 0。 return np.sum(x ** 2)第二个是 Rastrigin 函数它是一个典型的非线性多峰函数局部最优点非常多适合测试算法跳出局部最优的能力# 文件路径benchmark_functions.py def rastrigin(x): Rastrigin 函数全局最小值在 x(0,0,...,0) 处最小值为 0。 n len(x) return 10 * n np.sum(x ** 2 - 10 * np.cos(2 * np.pi * x))两个函数的全局最优点都在原点最小值都是 0非常适合用来观察算法收敛后的精度。实际测试时我们通过算法最后输出的最优适应度来判断求解效果。4.2 编写 GWO 核心算法接下来是整篇文章最核心的部分用 Python 实现 GWO。为了保证代码可读性和复用性我将算法封装成一个函数# 文件路径gwo_algorithm.py import numpy as np def gwo(fitness_func, dim, lb, ub, search_agents_no30, max_iter500): GWO 灰狼优化算法 参数 fitness_func: 目标函数输入一个一维数组返回适应度值 dim: 问题维度 lb: 变量下界可以是标量或数组 ub: 变量上界可以是标量或数组 search_agents_no: 灰狼种群数量 max_iter: 最大迭代次数 返回 alpha_pos: 最优位置 alpha_score: 最优适应度 convergence_curve: 收敛曲线每一代的最优适应度 # 初始化种群 positions np.random.uniform(lb, ub, (search_agents_no, dim)) # 初始化 Alpha、Beta、Delta alpha_pos np.zeros(dim) alpha_score float(inf) beta_pos np.zeros(dim) beta_score float(inf) delta_pos np.zeros(dim) delta_score float(inf) convergence_curve np.zeros(max_iter) for t in range(max_iter): # 遍历种群计算适应度并更新头狼 for i in range(search_agents_no): # 边界处理将越界个体拉回搜索空间 positions[i, :] np.clip(positions[i, :], lb, ub) fitness fitness_func(positions[i, :]) if fitness alpha_score: alpha_score fitness alpha_pos positions[i, :].copy() elif fitness beta_score: beta_score fitness beta_pos positions[i, :].copy() elif fitness delta_score: delta_score fitness delta_pos positions[i, :].copy() # a 从 2 线性递减到 0 a 2.0 - 2.0 * t / max_iter # 更新每只灰狼的位置 for i in range(search_agents_no): for j in range(dim): # 利用 Alpha 更新位置 r1, r2 np.random.random(), np.random.random() A1 2.0 * a * r1 - a C1 2.0 * r2 D_alpha abs(C1 * alpha_pos[j] - positions[i, j]) X1 alpha_pos[j] - A1 * D_alpha # 利用 Beta 更新位置 r1, r2 np.random.random(), np.random.random() A2 2.0 * a * r1 - a C2 2.0 * r2 D_beta abs(C2 * beta_pos[j] - positions[i, j]) X2 beta_pos[j] - A2 * D_beta # 利用 Delta 更新位置 r1, r2 np.random.random(), np.random.random() A3 2.0 * a * r1 - a C3 2.0 * r2 D_delta abs(C3 * delta_pos[j] - positions[i, j]) X3 delta_pos[j] - A3 * D_delta # 取三者平均值作为新位置 positions[i, j] (X1 X2 X3) / 3.0 convergence_curve[t] alpha_score return alpha_pos, alpha_score, convergence_curve这段代码可能有几个细节需要解释。第一为什么每次都要检查边界因为灰狼按照公式更新位置后很可能跳出变量的合法范围。如果不做任何处理算法会去计算无意义的目标函数值影响后续的适应度比较。这里使用np.clip把越界位置直接拉回边界是一种简单高效的边界处理方式。第二为什么要在每个个体的每一维上重新生成 A 和 C因为随机性越强狼群的行为就越多样越不容易在早期就早熟收敛。如果所有狼共用同一组参数种群会快速聚集多样性会大幅下降。第三为什么更新顺序要放在头狼更新之后因为位置更新依赖当前代数 α、β、δ 的位置所以必须先计算出本代最优的三个个体再让其他狼朝它们移动。4.3 编写入口程序编写好核心算法后我们再写一个入口脚本。我们先用 Sphere 函数做测试维度设为 30边界设为 [-100, 100]# 文件路径demo.py import numpy as np from benchmark_functions import sphere from gwo_algorithm import gwo if __name__ __main__: # 固定随机种子保证结果可复现 np.random.seed(42) dim 30 lb -100 ub 100 best_pos, best_score, curve gwo( fitness_funcsphere, dimdim, lblb, ubub, search_agents_no50, max_iter500 ) print(最优位置前 5 维, best_pos[:5]) print(最优适应度, best_score) print(收敛曲线最后一轮, curve[-1])这里选择 30 维、边界 [-100, 100] 的 Sphere 函数是智能优化算法论文中最常见的测试配置之一。固定随机种子可以让结果稳定复现也方便我们后续对比不同参数的效果。4.4 运行结果与收敛曲线运行上面的demo.py你会看到类似下面的输出最优位置前 5 维 [-1.38451234e-15 7.62527351e-15 -8.44172276e-16 ...] 最优适应度 1.20384906e-27 收敛曲线最后一轮 1.20384906e-27不同环境下打印的数值会有细微差别但量级应该接近。这意味着 GWO 在 500 次迭代后已经能将 30 维 Sphere 函数的最优值优化到 10^-27 量级说明它对这个简单单峰函数的寻优能力很强。为了直观地观察收敛过程可以绘制收敛曲线。收敛曲线的横轴是迭代次数纵轴是每一代的最优适应度。由于适应度下降速度很快建议使用对数坐标# 文件路径visualization.py import matplotlib.pyplot as plt import numpy as np from benchmark_functions import sphere from gwo_algorithm import gwo np.random.seed(42) best_pos, best_score, curve gwo( fitness_funcsphere, dim30, lb-100, ub100, search_agents_no50, max_iter500 ) plt.figure(figsize(8, 5)) plt.plot(curve, colorsteelblue, linewidth2) plt.yscale(log) plt.xlabel(Iteration) plt.ylabel(Best Fitness) plt.title(GWO Convergence Curve on Sphere Function) plt.grid(True, alpha0.4) plt.show()从图中可以看到适应度在前几十代下降非常快后期趋于平缓这是群体智能算法的典型收敛形态。前期的快速下降来自全局探索后期的平缓来自局部精细开发。5. 进阶案例二维搜索过程可视化收敛曲线只能告诉我们适应度在下降但看不到狼群在解空间中的具体移动轨迹。为了更直观地理解 GWO 的搜索过程我们可以把算法放到二维 Sphere 函数上运行然后把每一代所有灰狼的位置画成散点图。这样能清楚看到狼群是如何从一片散点逐渐向原点最优解聚拢的。5.1 改进算法返回历史位置原来的gwo函数只返回最优解和收敛曲线没有记录每一代的所有位置。为了让可视化更准确我在原函数基础上增加一个history列表每次迭代结束后保存当前种群位置和当前 α 的位置# 文件路径gwo_algorithm.py def gwo_with_history(fitness_func, dim, lb, ub, search_agents_no30, max_iter100): positions np.random.uniform(lb, ub, (search_agents_no, dim)) alpha_pos np.zeros(dim) alpha_score float(inf) beta_pos np.zeros(dim) beta_score float(inf) delta_pos np.zeros(dim) delta_score float(inf) history [] for t in range(max_iter): for i in range(search_agents_no): positions[i, :] np.clip(positions[i, :], lb, ub) fitness fitness_func(positions[i, :]) if fitness alpha_score: alpha_score fitness alpha_pos positions[i, :].copy() elif fitness beta_score: beta_score fitness beta_pos positions[i, :].copy() elif fitness delta_score: delta_score fitness delta_pos positions[i, :].copy() a 2.0 - 2.0 * t / max_iter for i in range(search_agents_no): for j in range(dim): r1, r2 np.random.random(), np.random.random() A1 2.0 * a * r1 - a C1 2.0 * r2 D_alpha abs(C1 * alpha_pos[j] - positions[i, j]) X1 alpha_pos[j] - A1 * D_alpha r1, r2 np.random.random(), np.random.random() A2 2.0 * a * r1 - a C2 2.0 * r2 D_beta abs(C2 * beta_pos[j] - positions[i, j]) X2 beta_pos[j] - A2 * D_beta r1, r2 np.random.random(), np.random.random() A3 2.0 * a * r1 - a C3 2.0 * r2 D_delta abs(C3 * delta_pos[j] - positions[i, j]) X3 delta_pos[j] - A3 * D_delta positions[i, j] (X1 X2 X3) / 3.0 history.append({ positions: positions.copy(), alpha_pos: alpha_pos.copy() }) return alpha_pos, alpha_score, history这里使用字典来保存每一代的快照信息后续绘图时可以直接用history[t][positions]取对应时刻的种群。5.2 绘制狼群演化过程下面这段脚本会在二维搜索空间中运行 GWO然后用一个 2x2 的子图分别展示第 0 代、第 5 代、第 20 代和第 49 代的狼群分布# 文件路径visualization.py import matplotlib.pyplot as plt import numpy as np from benchmark_functions import sphere from gwo_algorithm import gwo_with_history np.random.seed(0) dim 2 lb -10 ub 10 alpha_pos, alpha_score, history gwo_with_history( fitness_funcsphere, dimdim, lblb, ubub, search_agents_no30, max_iter50 ) fig, axes plt.subplots(2, 2, figsize(10, 10)) show_iters [0, 5, 20, 49] for ax, t in zip(axes.flatten(), show_iters): positions history[t][positions] current_alpha history[t][alpha_pos] ax.scatter(positions[:, 0], positions[:, 1], csteelblue, alpha0.7, labelwolves) ax.scatter(current_alpha[0], current_alpha[1], cred, marker*, s220, labelalpha) ax.set_xlim(lb, ub) ax.set_ylim(lb, ub) ax.set_xlabel(x1)
返回列表