粒子群优化算法 (PSO):数学原理、Python 实现与变种解析

目录

1. 引言:从鸟群说起

2. 数学原理

2.1 核心定义

2.2 速度与位置更新公式

3. Python 实现 (向量化版本)

进阶粒子群优化:从理论到自适应变种

1. 变种原理深度解析

A. 策略一:线性递减权重 (LDIW)

B. 策略二:时变加速因子 (TVAC)

C. 策略三:压缩因子 (Constriction Factor)

2. 完整进阶代码实现 (Modular Advanced PSO)

3. 代码与结果分析

运行结果预期

1. 二进制粒子群优化 (Binary PSO, BPSO)

算法步骤

Python 实现代码

2. 量子行为粒子群 (QPSO)

算法步骤

Python 实现代码


1. 引言:从鸟群说起

粒子群优化算法(Particle Swarm Optimization, PSO)是由 Kennedy 和 Eberhart 于 1995 年提出的一种群智能算法。

它的灵感来源于鸟群捕食的行为:一群鸟在随机搜索食物,它们不知道食物在哪里,但它们知道自己当前的位置距离食物有多远,同时也知道整个鸟群中“目前离食物最近的那只鸟”在哪里。

为了找到食物,每只鸟会根据以下三个因素调整自己的飞行速度和方向:

  1. 惯性:保持之前的飞行状态。

  2. 个体认知:飞向自己曾经过的最好位置。

  3. 社会认知:飞向群体目前找到的最好位置。

这种简单的规则涌现出了复杂的群体智能,使得 PSO 成为一种强大的全局优化算法,广泛应用于函数优化、神经网络训练、路径规划等领域。

2. 数学原理

2.1 核心定义

2.2 速度与位置更新公式

3. Python 实现 (向量化版本)

为了提高计算效率,我们不使用循环遍历每个粒子,而是利用 numpy 进行矩阵运算。

import numpy as np

class PSO:
    def __init__(self, func, dim, pop_size=30, max_iter=100, bounds=(-10, 10)):
        """
        :param func: 目标函数 (我们要最小化的函数)
        :param dim: 变量维度
        :param pop_size: 粒子数量
        :param max_iter: 最大迭代次数
        :param bounds: 搜索边界 (min, max)
        """
        self.func = func
        self.dim = dim
        self.pop_size = pop_size
        self.max_iter = max_iter
        self.bounds = bounds
        
        # PSO 参数 (标准建议值)
        self.w = 0.8      # 惯性权重
        self.c1 = 1.5     # 个体学习因子
        self.c2 = 1.5     # 社会学习因子

        # 初始化粒子位置和速度
        self.X = np.random.uniform(bounds[0], bounds[1], (pop_size, dim))
        self.V = np.random.uniform(-1, 1, (pop_size, dim))
        
        # 初始化个体最佳 (pbest)
        self.pbest_X = self.X.copy()
        self.pbest_y = self.func(self.X)
        
        # 初始化全局最佳 (gbest)
        self.gbest_X = np.zeros(dim)
        self.gbest_y = np.inf
        self.update_gbest()
        
        # 记录历代最优值用于绘图
        self.history = []

    def update_gbest(self):
        """更新全局最优解"""
        min_idx = np.argmin(self.pbest_y)
        if self.pbest_y[min_idx] < self.gbest_y:
            self.gbest_y = self.pbest_y[min_idx]
            self.gbest_X = self.pbest_X[min_idx].copy()

    def run(self):
        for t in range(self.max_iter):
            # 生成随机数矩阵
            r1 = np.random.rand(self.pop_size, self.dim)
            r2 = np.random.rand(self.pop_size, self.dim)
            
            # --- 核心公式更新 ---
            # 更新速度
            self.V = (self.w * self.V + 
                      self.c1 * r1 * (self.pbest_X - self.X) + 
                      self.c2 * r2 * (self.gbest_X - self.X))
            
            # 限制速度 (可选,防止粒子飞太快)
            # self.V = np.clip(self.V, -limit, limit)

            # 更新位置
            self.X = self.X + self.V
            
            # 边界处理:防止粒子跑出定义域
            self.X = np.clip(self.X, self.bounds[0], self.bounds[1])
            
            # --- 评估与更新 ---
            # 计算适应度
            y = self.func(self.X)
            
            # 更新个体最佳 pbest
            # 只有当新位置的适应度比原来的小,才更新
            better_mask = y < self.pbest_y
            self.pbest_X[better_mask] = self.X[better_mask]
            self.pbest_y[better_mask] = y[better_mask]
            
            # 更新全局最佳 gbest
            self.update_gbest()
            
            self.history.append(self.gbest_y)
            
        return self.gbest_X, self.gbest_y, self.history

# --- 测试用例 ---
if __name__ == "__main__":
    # 定义 Sphere 函数: f(x) = x1^2 + x2^2 + ...
    def sphere(x):
        return np.sum(x**2, axis=1)

    pso = PSO(func=sphere, dim=10, pop_size=50, max_iter=100)
    best_x, best_y, history = pso.run()
    
    print(f"最优解: {best_x}")
    print(f"最优适应度: {best_y}")

进阶粒子群优化:从理论到自适应变种

在标准 PSO 中,参数 $w, c_1, c_2$ 是固定的。这导致了一个致命缺陷:算法在初期“飞得不够散”(容易陷入局部最优),在后期“收敛不够稳”(在最优解附近震荡无法精确)。

为了解决这个问题,学术界提出了多种变种。我们将重点实现三种最经典且行之有效的改进策略:

  1. LDIW-PSO (Linear Decreasing Inertia Weight): 线性递减惯性权重。

  2. TVAC-PSO (Time-Varying Acceleration Coefficients): 时变加速因子。

  3. CF-PSO (Constriction Factor): 压缩因子法(基于特征值分析)。

1. 变种原理深度解析

A. 策略一:线性递减权重 (LDIW)

B. 策略二:时变加速因子 (TVAC)

C. 策略三:压缩因子 (Constriction Factor)


2. 完整进阶代码实现 (Modular Advanced PSO)

为了方便实验,我编写了一个模块化的类 AdvancedPSO,你可以在实例化时通过 mode 参数自由切换变种。

此外,为了验证变种的有效性,我们使用 Rastrigin 函数。这是一个高度非凸的函数,充满了成千上万个局部陷阱,标准 PSO 很容易挂在这里,而改进版表现会好很多。

Python

import numpy as np
import matplotlib.pyplot as plt

# ==========================================
# 1. 测试函数:Rastrigin Function
# (非常难的函数,有大量局部最优,全局最优在 0)
# ==========================================
def rastrigin(X):
    # f(x) = 10*d + sum(x_i^2 - 10*cos(2*pi*x_i))
    A = 10
    d = X.shape[1]
    return A * d + np.sum(X**2 - A * np.cos(2 * np.pi * X), axis=1)

# ==========================================
# 2. 进阶 PSO 框架
# ==========================================
class AdvancedPSO:
    def __init__(self, func, dim, pop_size=50, max_iter=200, bounds=(-5.12, 5.12), mode='tvac'):
        """
        :param mode: 算法模式 
                     'standard' - 标准 PSO
                     'ldiw'     - 线性递减权重
                     'tvac'     - 时变加速因子 (推荐)
                     'constriction' - 压缩因子法 (Clerc Type 1)
        """
        self.func = func
        self.dim = dim
        self.pop_size = pop_size
        self.max_iter = max_iter
        self.bounds = bounds
        self.mode = mode
        
        # --- 初始化粒子 ---
        self.X = np.random.uniform(bounds[0], bounds[1], (pop_size, dim))
        # 速度初始化通常为搜索范围的 10%-20%
        v_limit = (bounds[1] - bounds[0]) * 0.2
        self.V = np.random.uniform(-v_limit, v_limit, (pop_size, dim))
        
        # --- 历史最佳 ---
        self.pbest_X = self.X.copy()
        self.pbest_y = self.func(self.X)
        
        self.gbest_X = np.zeros(dim)
        self.gbest_y = np.inf
        self.update_gbest()
        
        self.history = [] # 记录收敛曲线

    def update_gbest(self):
        min_idx = np.argmin(self.pbest_y)
        if self.pbest_y[min_idx] < self.gbest_y:
            self.gbest_y = self.pbest_y[min_idx]
            self.gbest_X = self.pbest_X[min_idx].copy()

    def get_parameters(self, t):
        """
        根据当前迭代次数 t 和选择的 mode,动态计算 w, c1, c2
        """
        # 进度比率 (0 -> 1)
        ratio = t / self.max_iter
        
        if self.mode == 'standard':
            return 0.7, 1.5, 1.5
            
        elif self.mode == 'ldiw':
            # w 线性递减: 0.9 -> 0.4
            w = 0.9 - 0.5 * ratio
            return w, 1.5, 1.5
            
        elif self.mode == 'tvac':
            # w 线性递减
            w = 0.9 - 0.5 * ratio
            # c1 (自我) 递减: 2.5 -> 0.5 (初期多探索自己)
            c1 = 2.5 - 2.0 * ratio
            # c2 (社会) 递增: 0.5 -> 2.5 (后期多跟从群体)
            c2 = 0.5 + 2.0 * ratio
            return w, c1, c2
            
        elif self.mode == 'constriction':
            # 压缩因子模式:w 实际上就是 chi (压缩因子)
            # 理论值: phi = 4.1, chi = 0.729
            phi = 2.05 + 2.05
            chi = 2 / np.abs(2 - phi - np.sqrt(phi**2 - 4*phi))
            return chi, 2.05, 2.05
            
        return 0.7, 1.5, 1.5

    def run(self):
        print(f"Starting PSO [Mode: {self.mode}]")
        
        for t in range(self.max_iter):
            # 1. 获取动态参数
            w, c1, c2 = self.get_parameters(t)
            
            # 2. 生成随机数
            r1 = np.random.rand(self.pop_size, self.dim)
            r2 = np.random.rand(self.pop_size, self.dim)
            
            # 3. 更新速度
            # 如果是压缩因子模式,公式略有不同 (乘在最外面)
            if self.mode == 'constriction':
                self.V = w * (self.V + 
                              c1 * r1 * (self.pbest_X - self.X) + 
                              c2 * r2 * (self.gbest_X - self.X))
            else:
                self.V = (w * self.V + 
                          c1 * r1 * (self.pbest_X - self.X) + 
                          c2 * r2 * (self.gbest_X - self.X))
            
            # 4. 更新位置
            self.X = self.X + self.V
            
            # 边界处理 (Clip)
            self.X = np.clip(self.X, self.bounds[0], self.bounds[1])
            
            # 5. 计算适应度
            y = self.func(self.X)
            
            # 6. 更新 Pbest
            better_mask = y < self.pbest_y
            self.pbest_X[better_mask] = self.X[better_mask]
            self.pbest_y[better_mask] = y[better_mask]
            
            # 7. 更新 Gbest
            self.update_gbest()
            self.history.append(self.gbest_y)
            
        return self.gbest_y, self.history

# ==========================================
# 3. 对比实验与绘图
# ==========================================
if __name__ == "__main__":
    # 实验设置
    DIM = 30           # 30维 Rastrigin 问题 (很难!)
    POP = 50
    ITER = 500
    
    # 运行三种不同模式进行对比
    modes = ['standard', 'ldiw', 'tvac', 'constriction']
    results = {}
    
    plt.figure(figsize=(10, 6))
    
    for mode in modes:
        # 为了公平,每种模式运行 10 次取平均,减少随机性影响
        avg_history = np.zeros(ITER)
        runs = 10
        
        print(f"\nTesting {mode}...")
        for _ in range(runs):
            optimizer = AdvancedPSO(rastrigin, DIM, POP, ITER, bounds=(-5.12, 5.12), mode=mode)
            _, hist = optimizer.run()
            avg_history += np.array(hist)
            
        avg_history /= runs
        results[mode] = avg_history[-1]
        
        # 绘图
        plt.plot(avg_history, label=f"{mode} (Final: {avg_history[-1]:.2e})")

    plt.title(f'Comparison of PSO Variants on {DIM}-D Rastrigin Function')
    plt.xlabel('Iteration')
    plt.ylabel('Fitness (Log Scale)')
    plt.yscale('log')
    plt.legend()
    plt.grid(True, which="both", ls="-", alpha=0.5)
    plt.show()

    # 打印最终对比
    print("\n=== Final Results (Lower is Better) ===")
    for m, res in results.items():
        print(f"{m.ljust(15)}: {res:.10f}")

3. 代码与结果分析

运行结果预期

当你运行这段代码时,你会清楚地看到不同变种的性能差异(特别是在 30 维 Rastrigin 这种复杂函数上):

  1. Standard (标准版):

    • 通常表现最差。曲线下降一段后会变成一条水平线。这说明粒子群**“早熟”**了,它们过早地聚集在了一个局部坑里,再也跳不出来了。

  2. LDIW (线性递减权重):

    • 表现优于标准版。曲线下降得更深,说明它在后期进行了更精细的搜索,但可能仍然无法跳出深层的局部最优。

  3. TVAC (时变加速因子):

  4. Constriction (压缩因子):

    • 表现非常稳定,收敛速度通常很快,属于“性价比”极高的选择,不需要复杂的参数调整。

二进制粒子群优化 (Binary PSO, BPSO)

应用场景:用于离散问题,如特征选择、背包问题、任务分配(解只能是 0 或 1)。

算法步骤

Python 实现代码
import numpy as np

class BinaryPSO:
    def __init__(self, func, dim, pop_size=30, max_iter=100):
        self.func = func
        self.dim = dim
        self.pop_size = pop_size
        self.max_iter = max_iter
        
        # 初始化位置 (0 或 1)
        self.X = np.random.randint(2, size=(pop_size, dim))
        # 初始化速度
        self.V = np.random.uniform(-1, 1, (pop_size, dim))
        
        # 初始化极值
        self.pbest_X = self.X.copy()
        self.pbest_score = self.func(self.X)
        
        self.gbest_X = self.pbest_X[np.argmin(self.pbest_score)].copy()
        self.gbest_score = np.min(self.pbest_score)
        
        # Sigmoid 函数
        self.sigmoid = lambda x: 1 / (1 + np.exp(-x))

    def run(self):
        print("Starting BPSO...")
        for t in range(self.max_iter):
            # 1. 计算速度 (标准公式)
            r1, r2 = np.random.rand(2)
            self.V = (self.V + 
                      1.5 * r1 * (self.pbest_X - self.X) + 
                      1.5 * r2 * (self.gbest_X - self.X))
            
            # 2. 速度映射为概率 (Sigmoid)
            prob = self.sigmoid(self.V)
            
            # 3. 位置更新 (根据概率决定是 0 还是 1)
            # 生成随机矩阵,如果 rand < prob,则设为 1,否则 0
            rand_matrix = np.random.rand(self.pop_size, self.dim)
            self.X = (rand_matrix < prob).astype(int)
            
            # 4. 计算适应度
            scores = self.func(self.X)
            
            # 5. 更新 pbest
            better_mask = scores < self.pbest_score
            self.pbest_X[better_mask] = self.X[better_mask]
            self.pbest_score[better_mask] = scores[better_mask]
            
            # 6. 更新 gbest
            current_best_val = np.min(self.pbest_score)
            if current_best_val < self.gbest_score:
                self.gbest_score = current_best_val
                self.gbest_X = self.pbest_X[np.argmin(self.pbest_score)].copy()
                
            print(f"Iter {t}: Best Score = {self.gbest_score}")

# --- 测试用例 (OneMax 问题: 让所有位都变成 1) ---
# 目标函数:返回 0 的个数 (越少越好)
def objective_function(x):
    # x 是 0/1 矩阵,我们希望 1 越多越好,所以最小化 (dim - sum)
    return x.shape[1] - np.sum(x, axis=1)

if __name__ == "__main__":
    bpso = BinaryPSO(objective_function, dim=20, pop_size=30, max_iter=50)
    bpso.run()

量子行为粒子群 (QPSO)

应用场景:这是一种现代变种。它完全抛弃了“速度”向量,认为粒子具有量子行为,出现在某一点是概率性的。它的参数更少,收敛能力通常强于标准 PSO。

算法步骤
  1. 计算平均最优位置 (mbest):计算群体中所有粒子个体历史最优位置 (pbest) 的平均值。

  2. 计算局部吸引点 (attractor):对于每个粒子,在它的 pbest 和群体的 gbest 之间随机找一个点作为吸引点。

  3. 位置更新:根据量子力学的波函数推导,位置由吸引点、平均最优位置和一个收缩扩张系数 (alpha) 决定。粒子直接“瞬移”到新位置。

Python 实现代码
import numpy as np

class QPSO:
    def __init__(self, func, dim, pop_size=30, max_iter=100, bounds=(-10, 10)):
        self.func = func
        self.dim = dim
        self.pop_size = pop_size
        self.max_iter = max_iter
        self.bounds = bounds
        
        self.X = np.random.uniform(bounds[0], bounds[1], (pop_size, dim))
        
        # QPSO 没有速度 V
        
        self.pbest_X = self.X.copy()
        self.pbest_score = self.func(self.X)
        
        self.gbest_X = self.pbest_X[np.argmin(self.pbest_score)].copy()
        self.gbest_score = np.min(self.pbest_score)

    def run(self):
        print("Starting QPSO...")
        for t in range(self.max_iter):
            # Alpha: 收缩扩张系数,通常从 1.0 线性递减到 0.5
            alpha = 1.0 - 0.5 * (t / self.max_iter)
            
            # 1. 计算 mbest (Mean Best Position)
            # 所有 pbest 的中心点
            mbest = np.mean(self.pbest_X, axis=0)
            
            # 2. 更新每个粒子的位置
            # 生成随机数 phi, u
            phi = np.random.rand(self.pop_size, self.dim)
            u = np.random.rand(self.pop_size, self.dim)
            
            # 计算局部吸引点 p
            # p = phi * pbest + (1-phi) * gbest
            p = phi * self.pbest_X + (1 - phi) * self.gbest_X
            
            # 核心更新公式 (根据 u > 0.5 分正负)
            # X = p +/- alpha * |mbest - X| * ln(1/u)
            sign = np.where(np.random.rand(self.pop_size, self.dim) > 0.5, 1, -1)
            
            self.X = p + sign * alpha * np.abs(mbest - self.X) * np.log(1 / u)
            
            # 边界处理
            self.X = np.clip(self.X, self.bounds[0], self.bounds[1])
            
            # 3. 更新极值 (同标准 PSO)
            scores = self.func(self.X)
            better_mask = scores < self.pbest_score
            self.pbest_X[better_mask] = self.X[better_mask]
            self.pbest_score[better_mask] = scores[better_mask]
            
            min_val = np.min(self.pbest_score)
            if min_val < self.gbest_score:
                self.gbest_score = min_val
                self.gbest_X = self.pbest_X[np.argmin(self.pbest_score)].copy()
                
            if t % 10 == 0:
                print(f"Iter {t}: Best Score = {self.gbest_score:.6f}")

# --- 测试用例 (Sphere 函数) ---
def sphere(x):
    return np.sum(x**2, axis=1)

if __name__ == "__main__":
    qpso = QPSO(sphere, dim=10, pop_size=40, max_iter=100)
    qpso.run()
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值