一、直观概念与算法流程(快速上手版)
- 直觉:把每个解看作“粒子”,粒子在解空间中飞行。每个粒子记住自己遇到过的最好位置(个体最优
pbest),群体也记住到的最好位置(全局最优gbest)。粒子根据自己的经验和群体经验调整速度和位置,逐步逼近最优解。 - 每个粒子包含:位置
x(向量)、速度v(向量)、个体最优位置pbest及对应适应度pbest_val。 - 核心更新公式(标准 PSO):
- 速度更新:
v = w * v + c1 * r1 * (pbest - x) + c2 * r2 * (gbest - x)其中w是惯性权重,c1和c2是学习因子(通常 ~1.5~2.0),r1、r2是 [0,1] 的随机数。 - 位置更新:
x = x + v
- 速度更新:
- 典型参数:
- 惯性权重
w:控制探索(大)与开发(小)。常用 0.4–0.9,或随迭代线性递减。 - 学习因子
c1、c2:常见设为 1.5–2.2。若c1>c2,粒子更信任自己的经验;反之更信任群体。 - 速度限制
v_max:防止粒子飞出太远,常设为位置范围的某分数。
- 惯性权重
- 边界处理:常见策略包括:截断(clamp)到边界、反弹、将速度设为 0、周期(wrap-around)。
- 拓扑变体:
- 全局最优(gBest):所有粒子共享一个
gbest(收敛快,易陷入局部)。 - 局部最优(lBest):每个粒子只与邻居交流,更适合多峰问题。
- 全局最优(gBest):所有粒子共享一个
- 优点:实现简单、参数少、对目标函数连续可导性无要求、计算开销较低。
- 缺点:易陷入局部最优(尤其是 gBest),不保证全局收敛;参数敏感。
二、使用建议与调参小技巧
- 初期用较大的
w(例如 0.9),逐步减小到 0.4,能先大范围探索再细化搜索。 - 若陷入局部最优:降低
c1/ 提高c2(或反之)尝试;或使用重启策略、加入扰动或混合其它算法(如模拟退火、差分进化)。 - 对多峰问题可使用 lBest(环形邻居)或分簇(多群体)策略。
- 维度高时,增加粒子数(经验:粒子数通常与维度线性或超线性增长)。
三、C++ 示例代码(可直接编译)
- 功能:用 PSO 最小化 n 维 Sphere 函数:
f(x) = sum(x_i^2)(全局最优在 0 向量)。 - 说明:把目标函数
objective()替换成你自己的连续函数即可。 - 编译:
g++ -O2 -std=c++17 pso.cpp -o pso,运行./pso。
// pso.cpp
// 简单易用的 PSO 实现(C++17)
// 优化目标:Sphere 函数(可在 objective() 中替换为任意连续目标)
// 编译:g++ -O2 -std=c++17 pso.cpp -o pso
#include <bits/stdc++.h>
using namespace std;
using Vec = vector<double>;
std::mt19937_64 rng((unsigned)chrono::high_resolution_clock::now().time_since_epoch().count());
std::uniform_real_distribution<double> uniform01(0.0, 1.0);
// --------- 可调整的 PSO 超参数 ----------
struct PSOParams {
int dim = 30; // 维度
int swarm_size = 50; // 粒子数量
int max_iters = 1000; // 最大迭代次数
double w_start = 0.9; // 惯性权重起始值
double w_end = 0.4; // 惯性权重结束值(线性递减)
double c1 = 1.5; // 个体学习因子
double c2 = 1.5; // 群体学习因子
double xmin = -5.0; // 每维搜索下界
double xmax = 5.0; // 每维搜索上界
double vmax_ratio = 0.5; // v_max = vmax_ratio * (xmax - xmin)
bool verbose = true;
};
// ---------------------------------------
double rand01() { return uniform01(rng); }
// 用户目标函数:输入向量 x 返回标量值(要**最小化**)
double objective(const Vec &x) {
// Sphere 函数:全局最优在 x = 0
double s = 0.0;
for (double v : x) s += v * v;
return s;
}
struct Particle {
Vec x; // 当前位置
Vec v; // 速度
Vec pbest; // 个体最优位置
double pbest_val = numeric_limits<double>::infinity();
Particle(int dim = 0) {
x.assign(dim, 0.0);
v.assign(dim, 0.0);
pbest.assign(dim, 0.0);
}
};
struct PSOResult {
Vec best_pos;
double best_val;
int iterations;
vector<double> history; // 每次迭代的全局最优值(可选)
};
// 约束工具:夹住位置与速度
inline void clamp_vec(Vec &v, double lo, double hi) {
for (double &x : v) {
if (x < lo) x = lo;
else if (x > hi) x = hi;
}
}
PSOResult run_pso(const PSOParams &par) {
int dim = par.dim;
int n = par.swarm_size;
double v_max = par.vmax_ratio * (par.xmax - par.xmin);
// 初始化粒子群
vector<Particle> swarm;
swarm.reserve(n);
for (int i = 0; i < n; ++i) {
Particle p(dim);
for (int d = 0; d < dim; ++d) {
double r = rand01();
p.x[d] = par.xmin + r * (par.xmax - par.xmin);
// 速度初始为小幅随机值
p.v[d] = (rand01() * 2.0 - 1.0) * v_max;
}
p.pbest = p.x;
p.pbest_val = objective(p.x);
swarm.push_back(move(p));
}
// 初始化全局最优
Vec gbest_pos(dim);
double gbest_val = numeric_limits<double>::infinity();
for (const auto &p : swarm) {
if (p.pbest_val < gbest_val) {
gbest_val = p.pbest_val;
gbest_pos = p.pbest;
}
}
PSOResult result;
result.best_pos = gbest_pos;
result.best_val = gbest_val;
result.iterations = 0;
result.history.reserve(par.max_iters);
// 主循环
for (int iter = 0; iter < par.max_iters; ++iter) {
// 线性递减惯性权重(也可以选用其他策略)
double w = par.w_start + (par.w_end - par.w_start) * (double(iter) / max(1, par.max_iters - 1));
for (auto &p : swarm) {
// 更新速度与位置
for (int d = 0; d < dim; ++d) {
double r1 = rand01();
double r2 = rand01();
double cognitive = par.c1 * r1 * (p.pbest[d] - p.x[d]);
double social = par.c2 * r2 * (gbest_pos[d] - p.x[d]);
p.v[d] = w * p.v[d] + cognitive + social;
// 限制速度
if (p.v[d] > v_max) p.v[d] = v_max;
else if (p.v[d] < -v_max) p.v[d] = -v_max;
// 更新位置
p.x[d] += p.v[d];
}
// 边界处理:简单截断到 [xmin, xmax]
clamp_vec(p.x, par.xmin, par.xmax);
// 评估目标并更新个体最优
double val = objective(p.x);
if (val < p.pbest_val) {
p.pbest_val = val;
p.pbest = p.x;
}
// 更新全局最优
if (val < gbest_val) {
gbest_val = val;
gbest_pos = p.x;
}
} // end for swarm
result.history.push_back(gbest_val);
result.best_val = gbest_val;
result.best_pos = gbest_pos;
result.iterations = iter + 1;
// 可选终止条件:如果足够小就提前结束
if (gbest_val <= 1e-12) break;
if (par.verbose && (iter % max(1, par.max_iters/10) == 0 || iter + 1 == par.max_iters)) {
cout << "Iter " << (iter+1) << "/" << par.max_iters
<< " best = " << scientific << setprecision(5) << gbest_val << endl;
}
} // end main loop
return result;
}
int main() {
PSOParams p;
p.dim = 30; // 维度,可改
p.swarm_size = 60; // 粒子数量
p.max_iters = 1000;
p.w_start = 0.9; p.w_end = 0.4;
p.c1 = 1.7; p.c2 = 1.7;
p.xmin = -5.12; p.xmax = 5.12; // Sphere 常见范围
cout << "Running PSO (dim=" << p.dim << ", swarm=" << p.swarm_size << ", max_iters=" << p.max_iters << ")\n";
PSOResult res = run_pso(p);
cout << "Finished. Iterations: " << res.iterations << "\n";
cout << "Best value: " << setprecision(12) << res.best_val << "\n";
cout << "Best pos (first 10 dims): ";
for (int i = 0; i < min(p.dim, 10); ++i) {
cout << res.best_pos[i] << (i+1==min(p.dim,10) ? "\n" : ", ");
}
return 0;
}
四、如何把代码改成你需要的样子
- 要最小化你的目标函数:把
objective()函数替换为你的函数(保持输入Vec &x)。 - 若希望最大化,把返回值取负号(或改判断方向)。
- 要优化带约束的问题(如等式/不等式),常用方法:
- 惩罚函数(penalty):目标 + 大的罚项;
- 修复操作:把违规粒子映射回可行域;
- 使用特殊的启发式方式来生成可行解。
- 若要并行评估(目标函数贵重),可以把粒子目标评估替换为并行
std::async/ OpenMP /线程池调用。
五、常见问题(FAQ)
- Q:PSO 会不会保证找到全局最优?
A:不能保证(启发式算法)。但对于很多连续优化问题,PSO 在实践中效果不错;必要时与其它方法结合或多次重启改善可信度。 - Q:粒子数选多少合适?
A:经验值:20–100。维度更高时需要更多粒子。常见经验:swarm_size ≈ 10 * dim(只是经验)。 - Q:迭代次数如何设置?
A:看问题复杂度和目标函数评价时间。可以设置简单的早停条件(目标值阈值或若干次无改善后停止)。
1️⃣ 什么叫“维度”?
- 数学里:维度(Dimension)就是“有多少个变量”。
- 一维:(x)
- 二维:((x_1,x_2))
- 三维:((x_1,x_2,x_3))
- 优化里:维度就是你要同时优化的参数的个数。
举例:
Griewank 函数:
[
G(x_1,x_2)=1+\frac{x_1^2+x_2^2}{4000}-\cos x_1\cos\frac{x_2}{\sqrt2}
]
这里 n=2 表示有两个变量 (x_1)、(x_2)。
所以搜索空间就是一个平面(x1,x2),这就是“二维”。
如果 n=3,就是 ((x_1,x_2,x_3)) 三个变量,搜索空间是一个三维空间。
2️⃣ 在粒子群优化(PSO)里“维度”的作用
每一只粒子就是一个“解”,它在搜索空间里有一个位置。
- 位置 position 就是一个向量,长度 = 维度数
- 速度 velocity 也是同样长度的向量
如果 n=2:
struct Particle {
std::vector<double> position; // [x1, x2]
std::vector<double> velocity; // [v1, v2]
std::vector<double> bestPosition; // 个人最好位置
double bestValue; // 个人最好值
};
如果 n=3,就会是 [x1,x2,x3]。
3️⃣ 结合代码看一只“粒子”的运动
比如 n=2 的 Griewank 函数,粒子初始时:
particle.position = {rand(-600,600), rand(-600,600)};
particle.velocity = {rand(-1,1), rand(-1,1)};
然后迭代更新:
for (int d=0; d<dim; d++) {
// 速度更新
particle.velocity[d] = w * particle.velocity[d]
+ c1 * r1 * (particle.bestPosition[d] - particle.position[d])
+ c2 * r2 * (globalBestPosition[d] - particle.position[d]);
// 位置更新
particle.position[d] += particle.velocity[d];
}
d从0到dim-1- 每一维度的速度和位置都单独更新,这样粒子才能在多维空间中移动。
4️⃣ 为什么要这么做?
因为多维空间的每个坐标轴都是一个独立的变量。
粒子在多维空间里移动,本质就是在每个维度单独调整数值,一步步靠近最优解。
如果你只更新一个维度,另一个维度不动,粒子就不能在空间里“自由搜索”。
总结口诀
- 维度 = 变量个数
- 粒子的位置 = 一个解(多维向量)
- 粒子群优化就是在多维空间里“找最优解”
- 每个维度分别更新位置和速度 → 粒子才能搜索整个空间

对于这个问题,使用粒子群优化算法,自需要替换目标函数即可:
//*目标函数
double objective(const Vec &x){
double sum = 0.0;
double prod = 1.0;
for (size_t i = 0; i < x.size(); ++i) {
double xi = x[i];
sum += xi * xi;
prod *= cos(xi / sqrt(double(i+1)));
}
return 1.0 + sum / 4000.0 - prod;
}
粒子群优化(PSO)代码的逐步详解
1) PSO 的总体流程(映射到代码)
把算法按步骤拆开,和代码位置一一对应。
- 初始化粒子位置与速度(代码里
for (i in swarm)那段)
代码片段(简化):p.x[d] = xmin + rand01() * (xmax - xmin); p.v[d] = (rand01()*2.0 - 1.0) * v_max; p.pbest = p.x; p.pbest_val = objective(p.x);目的/直觉:在解空间内随机散布粒子以覆盖不同区域;速度也随机以产生初始“动量”。把当前点设为个体最优(pbest),以后根据探索结果更新。 - 评估初始 pbest 与 gbest
找出当前群体中适应度(目标值)最好的粒子,作为全局最优 gbest。 - 主循环:每代做两件主要事
- 更新速度(核心公式)
- 更新位置(
x = x + v),然后评估并更新pbest与gbest。
p.v[d] = w * p.v[d] + c1 * r1 * (p.pbest[d] - p.x[d]) + c2 * r2 * (gbest_pos[d] - p.x[d]); p.x[d] += p.v[d];这里w、c1、c2、r1、r2是关键参数/随机量(下文详细解释)。 - 边界与速度限制
if (p.v[d] > v_max) p.v[d] = v_max; clamp_vec(p.x, xmin, xmax);防止粒子一步飞出过远(数值稳定性、避免无限振荡或越界)。 - 终止条件:到达最大迭代或满足精度阈值(
gbest_val <= tol)或无改善若干代。
2) 核心公式逐项拆解(为什么要有这 3 项?)
速度更新公式(一次写完整):
[
v \leftarrow w v + c_1 r_1 (pbest - x) + c_2 r_2 (gbest - x)
]
w * v— 惯性项(momentum)
作用:保留之前速度的“惯性”,使粒子不会只做贪心跳转,而是有平滑的运动轨迹。
直觉:有助于继续向有利方向探索,避免被短暂噪声迷惑。c1 * r1 * (pbest - x)— 认知(个体)项
作用:粒子被自己历史上最好的位置吸引(“我知道哪儿对我最好”)。
直觉:保持个体探索能力,如果一个粒子曾在某位置找到好解,会尝试回去或靠近它进一步局部搜寻。c2 * r2 * (gbest - x)— 社会(群体)项
作用:粒子被群体最好位置吸引(“大家的经验很重要”)。
直觉:把群体中表现好的信息共享,带动整个群体向优良区域聚集,加速收敛。r1, r2— 两个随机数(均匀 [0,1])
作用:在每一维、每次更新加入随机缩放,防止完全确定性更新,使得粒子表现出随机扰动,增强探索多样性。
直觉:即便 pbest 和 gbest 的差向量方向相同,随机缩放能让不同粒子采取不同步幅,避免同步陷入同一局部。
3) 为什么要把速度和位置分开(而不是直接用 (x \leftarrow x + \alpha(pbest-x) + \beta(gbest-x)))?
两个原因:
- 动量和惯性效应:速度累积能够产生连贯的搜索轨迹,能“越过”浅局部、避免在狭小谷底来回抖动。直接移动没有记忆,可能太贪心、步长忽大忽小。
- 控制步长:通过
v_max可以限制每一步的最大移动距离,利于数值稳定和更可控的搜索节奏。
4) 参数(w, c1, c2, v_max)对行为的影响(直观建议)
- w(惯性权重)
- 大
w(例如 0.8–1.0):更强调惯性→更广泛探索(跳得远)。 - 小
w(例如 0.2–0.4):更强调当前吸引项→局部搜索(收敛)。 - 常见做法:线性递减(代码用的
w_start→w_end)——先探索、后收敛。
- 大
- c1(个体学习因子) vs c2(群体学习因子)
c1大:粒子更相信自己的经验→多样化(可避免过早被群体拉偏)。c2大:粒子更容易向 gbest 聚集→收敛快但易陷入局部。- 常见:
c1 ≈ c2 ≈ 1.5~2.0,也可做不对称设置根据问题调整。
- v_max(速度上限)
- 太大:粒子可能越界并错过细粒度最优,数值不稳定。
- 太小:搜索变慢、可能被局部困住。
- 经验:
v_max = ratio * (xmax - xmin),ratio 在0.1–0.5之间常见。
5) 为什么要随机初始化?为什么用均匀分布?
随机初始化保证覆盖不同区域,避免全部粒子集中在一个区域开始搜索。均匀分布是最简单且通常有效的选择。如果已知先验信息(某区间更可能有好解),可以用非均匀分布(正态分布、聚类初始化)来加速。
6) 关于 pbest、gbest 的更新(为什么这样做)
pbest:每个粒子保存自己见过的最好的位置。这样粒子有“记忆”并能把本地调优的成果保留下来。gbest:群体共享的最好位置。它是群体合作的信息汇总,能快速把整个群体导向优良区域。
把 pbest 与 gbest 同时保留,是平衡个体探索与群体协作的关键。
7) 常见陷阱与解决策略(尤其针对 Griewank 这样的多峰函数)
Griewank 有很多局部极小值(很多“坑”),所以 PSO 容易被卡住。对策:
- 增大探索期:增大
w_start,或延长线性递减的时间窗口(让群体更长时间探索)。 - 增大粒子数:二维问题可以用 50–100 个粒子覆盖更多区域。
- 增加随机扰动 / 重启:当若干代没有改进时,随机重置部分粒子位置。
- 局部拓扑(lBest):改为每个粒子只被邻居最优影响,能在复杂多峰上保留多样化解;对多峰函数通常更稳。
- 混合方法:先用 PSO 做全局粗搜索,再对找到的若干候选用局部搜索(如梯度法或 Nelder–Mead)精炼。
8) 边界处理有哪些选择?代码里用了 clamp,还有其它吗?
- 截断(clamp)(代码里用):简单可靠,把越界位置设回边界。优点易实现;缺点可能让粒子卡在边界。
- 反弹(reflect):把超出部分反向映射,相当于在边界弹回。
- 周期(wrap):把超出部分从另一侧进入(类似圆周)。
- 重新随机化:超出则随机放回可行域内(增加多样性)。
选择取决于问题语义与边界含义。
9) 如何把你的代码改成更稳健 / 更适合 Griewank?
- 使用更大的
swarm_size(例如 80–150)来覆盖多峰。 - 把
vmax_ratio设大一点(例如 0.3–0.5)以便粒子能跳出局部坑,再在后期减小w以精细搜索。 - 实现早停后“局部重启”:若
gbest在 200 代内没有显著改进,就随机重启 20% 的粒子。 - 尝试 lBest:把
gbest改为每个粒子邻居的最好(例如环形邻居 4 个),保留多峰多簇解。
10) 代码和调参的快速对照指南(recipes)
- 想要更快收敛(但更易陷入局部):减小
w、增大c2、减小v_max。 - 想要更强全局探索:增大
w_start、增大v_max、增大swarm_size、适当增大c1。 - 想得到稳定而靠谱的解:多次独立运行 PSO,统计最优值的均值和方差(即做多次随机试验)。
11) 与你代码相关的几个实现细节提示
objective()(例如griewank())应尽量高效:目标函数是瓶颈时,把评估并行化(std::async或 OpenMP),每代同时评估多个粒子。- 存
history.csv很好:画出gbest随代数变化,通常看到快速下降阶段 + 平台期。平台期说明算法已收敛或被卡住。 - 若要最大化目标,直接返回
-objective(x)或把比较方向反过来。
12) 小结(一句话版)
- PSO 用“个体记忆 + 群体信息 + 随机性 + 惯性”来在解空间里平衡探索与开发。
- 代码里的每一项(初始化、速度三个项、随机数、速度限制、边界处理、惯性权重的衰减)都是为了解决:覆盖、稳定、避免局部、以及收敛速度之间的折衷。



Comments NOTHING