O que é o PSO clássico?
O PSO clássico é a versão original do algoritmo de otimização por enxame de partículas. Ele foi proposto por Kennedy e Eberhart em 1995 sem modificações posteriores. Nesta versão, cada partícula tem posição, velocidade e memória individual. A velocidade é atualizada usando inércia constante, sem coeficientes adaptativos. Os parâmetros w, c₁ e c₂ são fixos durante toda a execução. Não há mecanismos de turbulência ou reinicialização para escapar de ótimos locais. O gbest é a melhor posição encontrada por qualquer partícula até o momento. O pbest é a melhor posição que cada partícula já visitou individualmente. A equação de velocidade é: v = w*v + c₁*r₁*(pbest – x) + c₂*r₂*(gbest – x). Essa formulação simples é a base de todas as variantes modernas do PSO.
Características essenciais do PSO clássico
O PSO clássico tem três características marcantes que o definem. Primeira, ele opera com um enxame de tamanho fixo, tipicamente entre 20 e 50. Segunda, a inércia w é constante (geralmente 0.7 a 0.9) durante todo o processo. Terceira, os coeficientes cognitivo (c₁) e social (c₂) são iguais (≈ 1.5 a 2.0). Não há evaporação, seleção ou crossover como em algoritmos genéticos. A atualização é puramente baseada em equações diferenciais estocásticas. Além disso, o PSO clássico não usa restrições de velocidade máxima (vmax). Contudo, muitas implementações incluem clamping para evitar explosão das partículas. O critério de parada é geralmente um número fixo de iterações ou tolerância. Essa simplicidade torna o PSO clássico fácil de programar e entender.
Vantagens e limitações da versão clássica
A principal vantagem é a convergência rápida em funções unimodais e suaves. Ela também requer poucos parâmetros e nenhum conhecimento prévio do problema. Por outro lado, o PSO clássico sofre com convergência prematura em funções multimodais. Ele tende a estagnar em mínimos locais porque a inércia não é ajustada. Além disso, a falta de diversidade populacional reduz a capacidade de exploração. Para superar isso, variantes com inércia adaptativa ou enxames hierárquicos surgiram. Ainda assim, o PSO clássico é um excelente ponto de partida para iniciantes.
O PSO clássico foi inicialmente testado em funções de referência como Sphere e Rastrigin. Ele mostrou desempenho superior a algoritmos genéticos em problemas contínuos. A simplicidade matemática permitiu análises teóricas de convergência. Estudos mostraram que o enxame converge para o gbest se os parâmetros forem bem escolhidos. A inércia w controla o trade-off entre exploração global e explotação local. Valores altos de w (próximos a 1) incentivam movimentos amplos e diversidade. Valores baixos (próximos a 0) fazem as partículas se concentrarem ao redor do gbest. Os coeficientes c₁ e c₂ determinam a atração para pbest e gbest, respectivamente. Quando c₁ > c₂, as partículas confiam mais em sua própria experiência. Quando c₂ > c₁, elas seguem o enxame de forma mais coletiva. O equilíbrio clássico c₁ = c₂ = 1.5 produz resultados robustos na maioria dos casos. O PSO clássico não tem mecanismo de reinicialização, o que é uma desvantagem. Porém, sua transparência facilita a depuração e o ajuste manual. Assim, o PSO clássico é uma ferramenta didática e funcional para otimização.
Um exemplo clássico é minimizar a função de Rosenbrock em 2 dimensões. Seu vale estreito e curvo desafia algoritmos baseados em gradiente. O PSO clássico encontra o mínimo (1,1) com precisão razoável. A inércia constante permite que as partículas oscilem até se estabilizar.
Enunciado do exemplo clássico
Implemente o PSO clássico para minimizar a função de Rosenbrock em 2D: f(x,y) = (1-x)² + 100*(y-x²)², com x,y ∈ [-2, 2]. Use 25 partículas, 300 iterações, w=0.8, c₁=c₂=1.5, e vmax = 0.5 por dimensão. Armazene o melhor fitness e a posição global a cada iteração. Plote a curva de convergência e a trajetória do gbest no espaço 2D com contornos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
import numpy as np import matplotlib.pyplot as plt # Função de Rosenbrock (minimização) def rosenbrock(x, y): return (1 - x)**2 + 100 * (y - x**2)**2 # Parâmetros PSO clássico num_particulas = 25 iteracoes = 300 w = 0.8 c1 = 1.5 c2 = 1.5 vmax = 0.5 limites = [-2, 2] dim = 2 # Inicialização posicoes = np.random.uniform(limites[0], limites[1], (num_particulas, dim)) velocidades = np.random.uniform(-vmax, vmax, (num_particulas, dim)) pbest_pos = posicoes.copy() pbest_fit = np.array([rosenbrock(p[0], p[1]) for p in posicoes]) gbest_idx = np.argmin(pbest_fit) gbest_pos = pbest_pos[gbest_idx].copy() gbest_fit = pbest_fit[gbest_idx] # Históricos melhor_fit_hist = [gbest_fit] melhor_pos_hist = [gbest_pos.copy()] for it in range(iteracoes): for i in range(num_particulas): r1, r2 = np.random.random(dim), np.random.random(dim) # Atualização clássica velocidades[i] = (w * velocidades[i] + c1 * r1 * (pbest_pos[i] - posicoes[i]) + c2 * r2 * (gbest_pos - posicoes[i])) # Clamping da velocidade velocidades[i] = np.clip(velocidades[i], -vmax, vmax) posicoes[i] += velocidades[i] posicoes[i] = np.clip(posicoes[i], limites[0], limites[1]) # Avaliação fit_atual = np.array([rosenbrock(p[0], p[1]) for p in posicoes]) # Atualizar pbest for i in range(num_particulas): if fit_atual[i] < pbest_fit[i]: pbest_fit[i] = fit_atual[i] pbest_pos[i] = posicoes[i].copy() # Atualizar gbest if np.min(fit_atual) < gbest_fit: gbest_idx = np.argmin(fit_atual) gbest_fit = fit_atual[gbest_idx] gbest_pos = posicoes[gbest_idx].copy() melhor_fit_hist.append(gbest_fit) melhor_pos_hist.append(gbest_pos.copy()) print(f"Melhor fitness: {gbest_fit:.6f}") print(f"Melhor posição: x={gbest_pos[0]:.6f}, y={gbest_pos[1]:.6f}") # Gráficos plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.plot(melhor_fit_hist, 'b-', linewidth=2) plt.title('Convergência - PSO Clássico na Rosenbrock') plt.xlabel('Iteração') plt.ylabel('Melhor fitness') plt.grid(True) plt.subplot(1, 2, 2) # Contornos x_vals = np.linspace(limites[0], limites[1], 200) y_vals = np.linspace(limites[0], limites[1], 200) X, Y = np.meshgrid(x_vals, y_vals) Z = rosenbrock(X, Y) plt.contourf(X, Y, Z, levels=50, cmap='plasma', alpha=0.7) plt.colorbar(label='f(x,y)') # Trajetória do gbest gbest_traj = np.array(melhor_pos_hist) plt.plot(gbest_traj[:,0], gbest_traj[:,1], 'w-', linewidth=1.5, alpha=0.8, label='Trajetória do gbest') plt.scatter(gbest_traj[0,0], gbest_traj[0,1], color='lime', s=80, label='Início') plt.scatter(gbest_traj[-1,0], gbest_traj[-1,1], color='red', s=100, label='Final') plt.scatter(1, 1, color='white', marker='*', s=200, label='Ótimo (1,1)') plt.title('Trajetória do Melhor Global - PSO Clássico') plt.xlabel('x') plt.ylabel('y') plt.legend() plt.grid(True) plt.tight_layout() plt.show() |
Este código implementa o PSO clássico com clamping de velocidade. A curva de convergência mostra uma redução estável do erro ao longo do tempo. A trajetória do gbest revela como o enxame se move pelo vale da Rosenbrock. Mesmo com parâmetros fixos, o algoritmo encontra uma boa aproximação do ótimo. Para iniciantes, este exemplo demonstra a eficácia da formulação original. O PSO clássico é, portanto, um algoritmo fundamental e atemporal.