Particle Swarm Optimization (PSO): Principles, Examples, and Python Implementation

Particle Swarm Optimization (PSO) : Guide Complet — Principes, Exemples et Implémentation Python

Particle Swarm Optimization (PSO): Complete Guide — Principles, Examples, and Python Implementation

Summary

Particle Swarm Optimization (PSO) is a stochastic optimization algorithm inspired by the collective behavior of biological swarms. Proposed by James Kennedy and Russell Eberhart in 1995, PSO simulates a swarm of particles that jointly explore the search space to identify the minimum (or maximum) of an objective function. Each particle adjusts its trajectory by combining its own individual experience (personal best position, pbest) and the collective experience of the group (global best position, gbest). This individual cognition — social information duality is at the heart of the algorithm. Unlike evolutionary algorithms, PSO uses neither crossover nor mutation: particle movement is based entirely on velocity and position updates. Simple to implement, easy to parameterize, and effective on non-convex continuous problems, PSO has established itself as one of the most popular metaheuristics in optimization and machine learning.


Mathematical Principles

PSO maintains a swarm of N particles in a D-dimensional search space. Each particle i has:

  • A position x_i(t) ∈ ℝ^D at iteration t.
  • A velocity v_i(t) ∈ ℝ^D that determines its movement.
  • A personal best position pbest_i — the position where the particle has achieved the best score since the beginning.
  • A global best position gbest — the best position discovered by the entire swarm.

Update Equations

At each iteration, the velocity and position of each particle are updated according to the following formulas:

Velocity update:

v(t+1) = w · v(t) + c₁ · r₁ · (pbest − x) + c₂ · r₂ · (gbest − x)

Position update:

x(t+1) = x(t) + v(t+1)

Where:

Parameter Role
w (inertia) Controls the fraction of previous motion retained. High inertia favors exploration; low inertia favors exploitation.
c₁ (cognitive coefficient) Weights the attraction to the personal best position. This is the particle’s individual memory.
c₂ (social coefficient) Weights the attraction to the global best position. This is the group’s influence.
r₁, r₂ Random numbers drawn uniformly from [0, 1], recomputed at each step and for each dimension. They introduce the stochastic nature of the algorithm.

In practice, a velocity ceiling |v| ≤ v_max is often applied to prevent particles from abruptly leaving the search space. Similarly, the decreasing inertia weight strategy (inertia weight decay) is widely used: start with a high w (broad exploration) and gradually reduce it toward a low w (local refinement).


Intuition: The School of Fish

To understand PSO without formulas, imagine a school of fish searching for food in a darkened lake. Each fish:

  1. Remembers the spot where it has already found the most food (pbest — personal memory).
  2. Observes the group and identifies the area where the luckiest fish found the most food (gbest — collective information).
  3. Swims by combining these two signals: it tends toward its own favorite zone while being attracted to the group’s best spot. Each advances based on its own memory (where I’ve found food before) and the collective memory (where the group has found the most).

If a fish were guided only by its personal memory (c₂ = 0), it would circle around its own past discoveries — no collective learning. Conversely, if it only followed the group’s best (c₁ = 0), the entire swarm would immediately converge to a single point, at risk of missing better solutions elsewhere. The balance between these two forces — individual cognition and social collaboration — is what makes PSO effective.

The inertia w acts like momentum: a fast fish continues in its direction despite new information (exploration), while a slowing fish finely adjusts its trajectory (exploitation).


Python Implementation

Here is a from-scratch implementation of PSO applied to the Rastrigin function, a classic optimization benchmark:

import numpy as np

def rastrigin(x):
    """Rastrigin function (global minimum = 0 at x = 0)."""
    A = 10
    D = len(x)
    return A * D + np.sum(x**2 - A * np.cos(2 * np.pi * x))

class ParticleSwarmOptimizer:
    def __init__(self, n_particles, dim, bounds, w=0.7, c1=1.5, c2=1.5,
                 max_iter=200, v_max=None):
        self.n_particles = n_particles
        self.dim = dim
        self.bounds = bounds
        self.w = w
        self.c1 = c1
        self.c2 = c2
        self.max_iter = max_iter
        self.v_max = v_max if v_max else (bounds[0][1] - bounds[0][0]) * 0.2

        # Random initialization
        lows = np.array([b[0] for b in bounds])
        highs = np.array([b[1] for b in bounds])
        self.positions = np.random.uniform(lows, highs, (n_particles, dim))
        self.velocities = np.random.uniform(-self.v_max, self.v_max,
                                            (n_particles, dim))
        self.pbest_pos = self.positions.copy()
        self.pbest_val = np.array([self._evaluate(p) for p in self.positions])

        gbest_idx = np.argmin(self.pbest_val)
        self.gbest_pos = self.pbest_pos[gbest_idx].copy()
        self.gbest_val = self.pbest_val[gbest_idx]

        self.convergence_history = []

    def _evaluate(self, x):
        return rastrigin(x)

    def optimize(self):
        for t in range(self.max_iter):
            # Linear decreasing inertia
            w_current = self.w - (self.w - 0.3) * (t / self.max_iter)

            for i in range(self.n_particles):
                r1 = np.random.rand(self.dim)
                r2 = np.random.rand(self.dim)

                # Velocity update
                cognitive = self.c1 * r1 * (self.pbest_pos[i] - self.positions[i])
                social = self.c2 * r2 * (self.gbest_pos - self.positions[i])
                self.velocities[i] = (w_current * self.velocities[i]
                                      + cognitive + social)

                # Velocity ceiling
                self.velocities[i] = np.clip(self.velocities[i],
                                             -self.v_max, self.v_max)

                # Position update
                self.positions[i] += self.velocities[i]

                # Boundary enforcement
                for d in range(self.dim):
                    self.positions[i, d] = np.clip(
                        self.positions[i, d], self.bounds[d][0], self.bounds[d][1])

                # Evaluation
                val = self._evaluate(self.positions[i])

                # Update pbest
                if val < self.pbest_val[i]:
                    self.pbest_val[i] = val
                    self.pbest_pos[i] = self.positions[i].copy()

                    # Update gbest
                    if val < self.gbest_val:
                        self.gbest_val = val
                        self.gbest_pos = self.positions[i].copy()

            self.convergence_history.append(self.gbest_val)

        return self.gbest_pos, self.gbest_val

# Execution
if __name__ == "__main__":
    dim = 2
    bounds = [(-5.12, 5.12)] * dim

    pso = ParticleSwarmOptimizer(
        n_particles=30, dim=dim, bounds=bounds,
        w=0.9, c1=2.0, c2=2.0, max_iter=100
    )

    best_pos, best_val = pso.optimize()

    print(f"Best position: {best_pos}")
    print(f"Best value    : {best_val:.6f}")
    print(f"Theoretical value : 0.000000 (at x = 0)")
    print(f"History (last 5)  : {pso.convergence_history[-5:]}")

Trajectory and Convergence Visualization

To visualize swarm convergence, plot the gbest history over iterations:

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 5))
plt.plot(pso.convergence_history, linewidth=2, color="steelblue")
plt.xlabel("Iteration")
plt.ylabel("Best value (gbest)")
plt.title("PSO Convergence on the Rastrigin Function")
plt.grid(True, alpha=0.3)
plt.yscale("log")
plt.show()

On the 2D Rastrigin function, fast convergence is generally observed in a few dozen iterations, with gbest approaching the theoretical value of 0 with precision from 10⁻³ to 10⁻⁵ depending on the chosen parameters. To visualize the individual particle trajectory, the paths of a few representative particles can be overlaid on the Rastrigin function contour, revealing how the swarm progressively densifies around the global optimum.


Hyperparameters

PSO is remarkably economical in terms of parameters. Here are the six main hyperparameters and their typical values:

Parameter Description Typical Values Impact
n_particles Number of particles in the swarm 20 – 50 The larger the swarm, the better the space coverage, but computational cost increases linearly.
w (inertia) Weight of previous velocity 0.4 – 0.9 High inertia favors exploration; low inertia favors local exploitation. Decreasing inertia is the recommended strategy.
c₁ (cognitive) Attraction to pbest 1.5 – 2.5 Reinforces each particle’s autonomy. A high c₁ increases swarm diversity.
c₂ (social) Attraction to gbest 1.5 – 2.5 Accelerates collective convergence. A too-high c₂ can cause premature convergence.
max_iter Maximum number of iterations 50 – 500 Determines the computational budget. A stopping criterion based on gbest stagnation is often preferable.
bounds Search space boundaries Problem-specific Define the exploration window. Bounds too wide dilute the search; too narrow, they risk excluding the optimum.

Reference Values

The values proposed by Shi and Eberhart (1998) — w = 0.729, c₁ = c₂ = 1.494 — are considered a robust starting point for most problems. The number of particles is often set to 30 as a default compromise. A common empirical rule for the number of particles is: n_particles ≈ 10 + 2 × √D, where D is the problem dimensionality.


Advantages and Limitations

Advantages

  • Implementation simplicity: a few dozen lines of code suffice. No complex operators (no crossover, no mutation, no selection).
  • Few parameters: only 5 to 6 hyperparameters to tune, compared to dozens for some evolutionary algorithms.
  • Fast convergence: on continuous, smooth functions, PSO generally converges faster than a classical genetic algorithm.
  • Natural parallelization: each particle’s evaluation is independent, enabling massive speedup on GPU or distributed clusters.
  • Flexibility: adaptable to discrete problems (binary PSO), constrained spaces, and non-differentiable objective functions.

Limitations

  • Premature convergence: on multimodal functions with many local minima (such as Rastrigin in high dimensions), the swarm can get trapped in a local optimum if gbest converges too quickly.
  • Parameter sensitivity: although few in number, hyperparameters strongly influence performance. Poor choices lead to stagnation or divergence.
  • Dimensionality: performance degrades beyond ~50 dimensions (the curse of dimensionality).
  • No theoretical guarantee: unlike some convex optimization methods, PSO offers no guarantee of convergence to the global optimum.
  • Discrete problems: the original version is designed for continuous space. Binary or combinatorial adaptations require significant modifications.

4 Concrete Use Cases

1. Neural Network Training

PSO can directly optimize the weights of a neural network by treating the entire set of weights as a particle’s position. Although gradient backpropagation remains dominant for training, PSO is valuable for initializing weights before fine-tuning via gradient descent, or for optimizing non-differentiable networks. Research has shown that PSO can outperform backpropagation on irregular or discontinuous activation functions.

2. Model Hyperparameter Optimization

Just like Bayesian Optimization, PSO can search for the best hyperparameters of a machine learning model (learning rate, number of layers, regularization, activation functions, etc.). Each particle encodes a combination of hyperparameters and its fitness corresponds to the cross-validation of the model trained with those parameters. This approach is particularly effective when the number of hyperparameters is high and evaluations are fast.

3. Engineering and Structural Design

In civil and aerospace engineering, PSO is used to optimize the shape of mechanical parts, the distribution of composite materials, or flight control parameters. Its ability to handle expensive-to-evaluate objective functions (finite element simulations, virtual wind tunnel tests) makes it a relevant choice. For example, the design of an aircraft wing can be formulated as minimizing drag subject to lift and structural strength constraints — an ideal problem for PSO.

4. Path Planning and Robotics

In mobile robotics, PSO plans optimal trajectories while avoiding obstacles. Each particle represents a candidate trajectory, encoded as a sequence of waypoints. The objective function penalizes path length, potential collisions, and abrupt accelerations. This approach is particularly effective for autonomous robots in dynamic environments, where the swarm can be reinitialized at each environmental configuration change to recalculate the optimal trajectory in real time.


See Also