
简介面向优化算法学习者的灰狼优化算法GWOPython实现以经典Rastrigin函数为测试基准完整演示了模拟灰狼社会等级α、β、δ与狩猎行为的优化流程。压缩包共2个文件包含Python主程序与Markdown说明文档整体仅2KB小巧精炼适合快速阅读理解与二次修改。目前已有42人学习下载。运行主程序后可直接显示收敛曲线图并输出最优解位置与适应度值便于对照算法原理验证其在Rastrigin函数上的收敛速度与精度。代码结构简洁、注释清晰既能帮助初学者掌握元启发式优化算法的基本框架也可作为进一步改进或迁移到其他连续函数优化问题的实用起点。对于需要解决多维非线性优化任务的研究者而言这份实现同样提供了直观的参考样例。1. 为什么用 GWO 来优化函数一个启发式搜索的典型场景连续函数求最小值这件事看起来简单可一旦目标函数没有导数、存在大量局部极值、或者计算一次就要跑好几秒的仿真传统的梯度下降就变得不可靠。把搜索空间想象成一片高低起伏的地形你站在某个山坡上梯度的方向只告诉你眼前最陡的下降方向却没法告诉你翻过这座山之后还有没有更深的谷。灰狼优化算法Grey Wolf OptimizerGWO的思路是用一群候选解去覆盖这片地形通过模拟狼群的社会等级和捕食行为让这些候选解一边保持探索新区域的能力、一边逐步收敛到当前找到的最优区域。这种思路不依赖目标函数的梯度也不需要目标函数有解析形式只要能把候选解输入进去、得到适应度值就可以跑优化。GWO 的实现门槛很低核心数学公式不超过十个几十行 Python 代码就能写出一个够用的版本配合 matplotlib 可以把收敛过程和狼群位置变化直接画出来这也是很多人接触群体智能算法的第一个可视化入门项目。对于数据分析、工程设计这类需要反复调参的场景GWO 的价值在于提供了一套可解释、易调试的搜索框架你可以画二维等高线看狼群怎么移动画收敛曲线看算法有没有早熟画不同参数对比看种群规模对结果的影响。这篇文章从算法原理讲起给出一份不带依赖、能直接跑通的最小实现然后把 matplotlib 可视化拆成散点图、等高线图、收敛曲线和子图编排四个部分最后补充几个实际操作中最容易踩的坑。2. GWO 的数学建模与包围机制从灰狼捕猎到参数方程GWO 的灵感来源是灰狼群体的社会等级。狼群被划分为 alpha、beta、delta、omega 四个层级alpha 是头狼负责决定捕猎方向beta 和 delta 协助决策omega 是底层个体服从前三者的指挥。在算法里alpha 对应当前适应度最好的解beta 次之delta 再次之剩下的所有狼都是 omega。每次迭代时omega 层的狼会根据 alpha、beta、delta 三只头狼的位置来更新自己的位置这样既保证了向着最优区域靠拢又因为三只头狼的位置存在差异避免了所有个体挤到同一个点上。2.1 包围猎物的数学表达从位置更新公式说起在 t 次迭代时第 i 匹狼的位置是一个 D 维向量记为 X_i(t)。它到某一头狼 X_p(t) 的距离和下一步位置由下面两个公式决定D_p | C ∘ X_p(t) - X_i(t) | X_i(t1) X_p(t) - A ∘ D_p其中 ∘ 表示逐元素相乘A 和 C 是系数向量A 的计算方式是 A 2a ∘ r1 - aC 2 ∘ r2。注意这里有三个不同的随机源r1 和 r2 是 [0,1) 区间均匀分布的随机向量每次迭代重新生成a 是一个从 2 线性递减到 0 的标量。A 的模长决定了狼的移动策略当 |A| 1 时狼会远离当前的猎物这是全局探索行为当 |A| 1 时狼会靠近猎物这是局部开发行为。C 的作用是给猎物坐标加上随机权重防止狼群过于快速地冲向头狼位置从而丢失搜索的多样性。实际更新时每一匹 omega 狼会分别对 alpha、beta、delta 三头狼做上述计算得到三个候选新位置然后取平均值作为最终位置。这个平均操作用数学语言描述就是X_i(t1) (X_alpha(t) - A1 ∘ D_alpha X_beta(t) - A2 ∘ D_beta X_delta(t) - A3 ∘ D_delta) / 3每匹狼其实是在一个由三头狼围成的三角形区域内移动。alpha 的位置代表当前最优方向如果 alpha 判断失误beta 和 delta 的贡献能把狼群拉回到正确的搜索区域这种冗余设计是 GWO 在简单粒子群算法基础上做的主要改进。2.2 算法流程的三个阶段探索、开发与收敛条件GWO 的完整流程可以拆成三个阶段。第一阶段是初始化确定种群规模 N、最大迭代次数 T、搜索空间的维度 D 以及上下界然后生成 N 个 D 维的随机位置向量计算每个位置的适应度选出前三名记录为 alpha、beta、delta。第二阶段是迭代更新每轮先更新 a 的值通常按 a 2 - 2t/T 线性衰减再对每匹狼计算三头狼的包围参数、更新位置把超出边界的坐标裁剪回可行域重新计算适应度并刷新前三名。第三阶段是终止判断达到最大迭代次数时输出 alpha 的位置和适应度值。三个系数有一个常用的参数配置表新手照着设置基本不会出大问题参数含义常用配置N种群规模30 左右维度高时加大到 50T最大迭代次数500 到 1000看收敛曲线决定a控制探索与开发的平衡2 线性递减至 0r1, r2随机扰动[0, 1) 均匀分布每轮重新生成与遗传算法相比GWO 没有交叉和变异操作代码天然短与粒子群算法相比GWO 不用维护每个粒子的个体历史最优和全局最优两组变量速度向量也省了更新公式更少。代价是 GWO 的局部开发能力偏弱在某些多峰函数上收敛精度可能不如加了惯性权重的 PSO这正好引出一个优化思路可以把 GWO 的结果当作初始种群再切换到一个局部搜索算法做精细化这种混合策略在工程落地中比单独调 GWO 参数有效得多。3. 用 Python 实现 GWO 的最小可运行版本代码与参数逐行拆解先明确目标函数。为了测试优化算法通常会选用一组有解析形式的基准函数比如 Sphere 函数 f(x) Σ x_i²它只有一个全局最小值点 0适合验证算法能不能收敛Rastrigin 函数 f(x) Σ (x_i² - 10cos(2πx_i)) 10D它有大量局部极值适合测试算法会不会陷入局部最优。这一章的示例代码以 Rastrigin 为例因为它的地形更接近真实优化问题的复杂度。3.1 核心代码一个只有六十行的 GWO 优化器下面给出一个完整的可运行版本不依赖任何第三方库以外的工具只用 numpy 和 matplotlibimport numpy as np def rastrigin(X): Rastrigin 函数大量局部极值全局最小值在 x 0 处取得 D X.shape[1] return np.sum(X**2 - 10 * np.cos(2 * np.pi * X) 10, axis1) def gwo(obj_func, dim, lb, ub, n_pop30, max_iter500, seed42): 灰狼优化器 参数: obj_func: 目标函数输入 (N, dim) 输出 (N,) 形式的适应度 dim: 维度整数 lb, ub: 搜索空间的上下界 n_pop: 种群规模 max_iter: 最大迭代次数 返回: alpha_pos: 最优位置 alpha_score: 最优适应度 history: 每次迭代的最优适应度列表 rng np.random.default_rng(seed) lb np.array(lb) ub np.array(ub) # 在 [lb, ub] 范围内随机初始化种群 positions rng.uniform(lb, ub, size(n_pop, dim)) fitness obj_func(positions) # 按适应度排序取前三个作为 alpha, beta, delta sort_idx np.argsort(fitness) alpha_pos positions[sort_idx[0]].copy() beta_pos positions[sort_idx[1]].copy() delta_pos positions[sort_idx[2]].copy() alpha_score fitness[sort_idx[0]] beta_score fitness[sort_idx[1]] delta_score fitness[sort_idx[2]] history [alpha_score] for t in range(max_iter): # a 从 2 线性递减到 0控制探索与开发的比重 a 2.0 * (1.0 - t / max_iter) for i in range(n_pop): # 对当前狼计算到三头狼的距离 dist_alpha np.abs(2.0 * rng.random(dim) * alpha_pos - positions[i]) dist_beta np.abs(2.0 * rng.random(dim) * beta_pos - positions[i]) dist_delta np.abs(2.0 * rng.random(dim) * delta_pos - positions[i]) # 计算向三头狼移动的候选位置 x1 alpha_pos - (2.0 * a * rng.random(dim) - a) * dist_alpha x2 beta_pos - (2.0 * a * rng.random(dim) - a) * dist_beta x3 delta_pos - (2.0 * a * rng.random(dim) - a) * dist_delta # 取三个候选位置的均值作为新位置 positions[i] (x1 x2 x3) / 3.0 # 越界裁剪 positions np.clip(positions, lb, ub) # 重新计算适应度并更新前三名 fitness obj_func(positions) for i in range(n_pop): if fitness[i] alpha_score: alpha_score fitness[i] alpha_pos positions[i].copy() elif fitness[i] beta_score: beta_score fitness[i] beta_pos positions[i].copy() elif fitness[i] delta_score: delta_score fitness[i] delta_pos positions[i].copy() history.append(alpha_score) return alpha_pos, alpha_score, history if __name__ __main__: best_pos, best_score, history gwo(rastrigin, dim30, lb-100.0, ub100.0) print(f最优位置: {best_pos}) print(f最优适应度: {best_score})整个脚本的核心逻辑在迭代循环内部先算每匹狼到三头狼的距离注意这里 A 和 C 的随机分量直接在计算中生成没有单独声明变量好处是代码短坏处是如果你要可视化每一轮的具体移动轨迹需要改写成返回中间状态的形式。a采用线性衰减公式在迭代初期数值接近 2此时2*a*rng.random(dim) - a的区间大致是 [-2, 2]狼的步长可以超过与猎物的距离表现为向外探索迭代后期a趋近于 0步长缩小狼群围拢到小范围精确搜索。排序取前三名的写法只在初始化时用了一次之后每轮用三个if判断逐个比较这种选择方式比每轮重新排序效率高且能保证一旦 alpha 被更优解替代原 alpha 顺延为 beta、原 beta 顺延为 delta维持等级结构的连续性。注意赋值时使用了.copy()否则会把引用而不是副本存储起来后续positions[i]的修改会意外改变 alpha_pos 的值——这是新手最容易踩的 alias 陷阱。history数组单独记录每轮的 alpha_score方便后续画收敛曲线。运行上面的代码在 30 维 Rastrigin 函数上500 次迭代后最优适应度一般能降到 1 以下如果多次运行结果波动很大可以把n_pop增大到 50 试试。3.2 参数怎么调种群规模、迭代次数与边界裁剪的关系参数调优的几个原则值得单独说明。种群规模影响的是搜索的覆盖面N 太小狼群容易被 Rastrigin 这类多峰函数的局部极值捕获N 太大每轮的计算量线性增长收益递减实际中 30 是一个覆盖了效率和效果平衡点的经验值。维度是另一个决定性因素30 维函数的搜索空间体积远大于 2 维同样的种群规模在低维够用高维就可能稀疏所以维度升高时优先增加种群规模而不是迭代次数。迭代次数的设置取决于你观察到的收敛曲线形态。常见做法是先跑一次 500 次迭代的试验看history列表最后几十轮的下降幅度如果最后 50 轮的改进小于 1e-6说明已经收敛可以提前终止如果曲线还在明显下降说明还没到极限需要加大迭代次数。不要盲目把迭代次数设到 5000因为后期a已经接近 0狼群步长很小单轮带来的改进极为有限多跑几千轮往往只是浪费算力。边界裁剪是一个容易被忽略的细节。上面的代码在每轮更新后统一做了一次np.clip把越界坐标拉回到边界上。有些实现会随机重置越界个体的位置那样会破坏已经积累的搜索方向信息导致收敛变慢。折中的做法是把越界的坐标设置为靠近边界的随机值保留一部分多样性但在大多数基准函数上简单裁剪的效果已经足够好而且代码更容易维护。4. matplotlib 可视化从收敛曲线到多子图对比可视化之于优化算法不只是把结果画出来更是调试工具。没有可视化的情况下你只能看到最终输出的一堆浮点数有了图你一眼就能看出狼群是否在迭代后期还保持着探索能力、是否过早聚集到了某个次优区域、收敛速度是否如预期那样呈指数下降。matplotlib 在这里承担的核心任务有三个展示目标函数的形状、展示狼群的运动轨迹、展示收敛过程的数值变化。4.1 二维适应度地形与散点图的叠加直观看到狼群如何移动对二维目标函数最直观的可视化是等高线图加上狼群位置散点图。等高线用np.meshgrid生成网格坐标然后计算每个点的函数值再用plt.contour或plt.contourf绘制。散点图则在每次迭代后将positions数组画到同一张图上。下面这段代码展示了具体做法import numpy as np import matplotlib.pyplot as plt def plot_2d_gwo(obj_func, lb, ub, positions_list, best_idx_list): 绘制 GWO 运行过程等高线 狼群位置散点 positions_list: 每次迭代后的种群位置列表 best_idx_list: 每次迭代的最优个体索引列表 x np.linspace(lb, ub, 200) y np.linspace(lb, ub, 200) X, Y np.meshgrid(x, y) Z obj_func(np.column_stack([X.ravel(), Y.ravel()])).reshape(X.shape) fig, ax plt.subplots(figsize(8, 6)) cf ax.contourf(X, Y, Z, levels20, cmapviridis, alpha0.8) plt.colorbar(cf, axax, labelfitness) for i, positions in enumerate(positions_list[::20]): # 每 20 轮画一次 ax.scatter(positions[:, 0], positions[:, 1], s8, alpha0.5, labelfiter {i*20}) ax.set_xlabel(x1) ax.set_ylabel(x2) ax.set_title(GWO 狼群位置演变) ax.legend()Z的计算直接把二维坐标展平成一维向量批量送进目标函数再还原形状这样能利用 numpy 的向量化能力避免用 Python 循环逐点计算。positions_list每 20 轮采样一次是因为 500 轮全画上去会糊成一片看不出移动趋势。画图时有一个常见的坑如果你用plt.scatter连续画多帧而不是保存列表一次性绘制那需要配合plt.pause做动画但动画的录制和交互调试成本更高先画一张静态叠图往往能更快发现问题。4.2 收敛曲线的三种画法线性坐标、对数坐标与多函数对比收敛曲线是评估算法性能最直接的证据。最简单的画法是把history列表直接plt.plot(history)但这样画有一个问题如果最优适应度从 1000 降到 0.001跨越了六个数量级线性坐标下前半段曲线会被压低成一条直线后半段的细节完全看不见。解决方案是把纵轴改成对数刻度plt.yscale(log)这样下降过程在图上呈现为近似直线能清楚看到每个阶段的收敛速度。更实用的画法是把同一个 GWO 在不同参数下的收敛曲线放在同一张图里对比。下面的代码是一个完整的对比脚本import matplotlib.pyplot as plt import numpy as np def compare_convergence(): 对比不同种群规模下 GWO 的收敛行为 configs [{n_pop: 10}, {n_pop: 30}, {n_pop: 50}] results [] for cfg in configs: _, _, history gwo(rastrigin, dim30, lb-100.0, ub100.0, **cfg) results.append(history) fig, axes plt.subplots(1, 2, figsize(12, 4)) for i, history in enumerate(results): axes[0].plot(history, labelfN{configs[i][n_pop]}) axes[0].set_xlabel(迭代次数) axes[0].set_ylabel(最优适应度) axes[0].set_yscale(log) axes[0].legend() axes[0].set_title(线性线性对比) for i, history in enumerate(results): axes[1].plot(np.clip(history, 1e-10, None), labelfN{configs[i][n_pop]}) axes[1].set_xscale(log) axes[1].set_xlabel(迭代次数对数) axes[1].set_ylabel(最优适应度对数) axes[1].legend() axes[1].set_title(双对数坐标下的收敛段对比) plt.tight_layout()左边子图用普通横轴加对数纵轴适合看整体趋势右边子图把横轴也改成对数坐标能透视出迭代早期每轮改进的幅度差异。写这段代码时我通常用np.clip(history, 1e-10, None)处理历史中可能出现的 0 值因为log(0)会报错。如果你对比的是多个版本的算法不只是参数不同这个脚本框架可以直接套用把gwo替换成其他实现即可。4.3 标题、坐标轴名称和图例的设置规范让图表具有可解释性可视化最容易犯的错误是图表信息不完整。一张图交给同事看对方不知道横轴是什么、纵轴是什么、每个颜色代表什么那张图的信息传递效率就接近于零。matplotlib 里对应的原子操作就是set_xlabel、set_ylabel、set_title、legend。字体设置也有讲究matplotlib 默认字体是英文中文标注会出现方框常见的解决方案是按下述代码全局设置字体import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False # 修正负号显示第一行把无衬线字体替换为支持中文的字体第二行关闭 Unicode 负号否则坐标轴上的负号会显示成方块。上面提到需要标注的图例名注意不要在label里混入不必要的字符图例数量超过 5 个时颜色区分度会下降考虑改用线型或标记形状来区分。5. 种群规模、维度与绘图的三处细节经验这一章节聚焦三个具体技巧怎么处理高维 GWO 的可视化限制、怎么让收敛曲线和位置动画配合分析、以及 matplotlib 在批量实验时的编排技巧。5.1 高维问题如何可视化的两种变通方案当维度超过 3 时无法直接画出狼群位置分布图。变通方案有两个。第一个是选两个最有代表性的维度画投影图代表维度的选取依据是 alpha 位置中绝对值最大的两个分量因为它们对适应度贡献最大。实现方式是在每次迭代结束时记录alpha_pos然后只取这两个维度画散点图代码如下def alpha_trajectory_2d(alpha_history, dim_a, dim_b): alpha 前两维投影轨迹把每次迭代的最优点连成线 alpha_history np.array(alpha_history) plt.plot(alpha_history[:, dim_a], alpha_history[:, dim_b], markero, markersize2, linestyle-, alpha0.6)第二个方案是转做面向目标的聚合可视化比如画每轮狼群在某一维度上的分布直方图或者画种群位置到 alpha 距离的箱线图观察离散程度随迭代的变化。聚合可视化牺牲了空间直觉但能在二维图上表达出高维搜索过程的多样性演进趋势。5.2 收敛曲线和位置动画配合分析的共同调参技巧只看收敛曲线很容易被误导曲线平滑下降不代表搜索过程健康可能只是所有狼都挤在同一个局部极值附近适应度不再改善而已。所以分析时要结合位置分布来看。先画收敛曲线如果曲线前期快速下降、后期变成平台再看位置分布图分布仍然分散说明算法还在探索分布高度聚集但不在全局最优点附近说明发生了早熟收敛——这时应增大种群规模或调大a的初值来增加探索强度。制作位置动画时可以用matplotlib.animation.FuncAnimation但第一次跑通建议先保存成 MP4 或 GIF 而不是实时播放因为交互界面卡顿会影响观察节奏。注意动画的帧间隔与迭代次数的换算不要每帧绘制一轮迭代取每 5 或 10 轮一帧帧间隔 200 毫秒左右同时在图上叠加显示当前帧的适应度这样动画本身就成了一页可回放的收敛报告。5.3 Python 环境配置与 matplotlib 安装的一个提示如果本机还没装 matplotlib在终端执行pip install matplotlib即可。装完后在交互式环境中运行import matplotlib.pyplot as plt如果报错提示后端问题通常是缺少 GUI 依赖一个稳妥的做法是改用export MPLBACKENDAgg先保存图片验证代码逻辑确认无误后再切回交互式后端。这条建议在服务器上用 Jupyter 跑实验时尤其实用能避免长期挂着一个不可交互的绘图进程浪费内存。GWO 的真正价值不在于它比别的算法好多少而在于它提供了一个结构足够简单、行为足够可解释的搜索框架——你总能通过一两张图说清楚当前的搜索状态这一点在调试新问题的时候比盲目的参数搜索有意义得多。本文还有配套的精品资源点击获取