Prima di tutto definiamo i nostri ipoparametri. Come in molti altri algoritmi metaeuristici, queste variabili dovrebbero essere aggiustate strada facendo e non esiste un insieme versatile di valori. Ma restiamo a questi:
POP_SIZE = 10 #population size
MAX_ITER = 30 #the amount of optimization iterations
w = 0.2 #inertia weight
c1 = 1 #personal acceleration factor
c2 = 2 #social acceleration factor
Ora creiamo una funzione che generi una popolazione casuale:
def populate(size):
x1,x2 = -10, 3 #x1, x2 = right and left boundaries of our X axis
pop = rnd.uniform(x1,x2, size) # size = amount of particles in population
return pop
Se lo visualizziamo, otterremo qualcosa del genere:
x1=populate(50)
y1=function(x1)plt.plot(x,y, lw=3, label='Func to optimize')
plt.plot(x1,y1,marker='o', ls='', label='Particles')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
plt.show()
Qui puoi vedere che ho inizializzato casualmente una popolazione di 50 particelle, alcune delle quali sono già vicine alla soluzione.
Ora implementiamo l'algoritmo PSO stesso. Ho commentato ogni riga del codice, ma se hai domande, non esitare a chiedere nei commenti qui sotto.
"""Particle Swarm Optimization (PSO)"""
particles = populate(POP_SIZE) #generating a set of particles
velocities = np.zeros(np.shape(particles)) #velocities of the particles
gains = -np.array(function(particles)) #calculating function values for the populationbest_positions = np.copy(particles) #it's our first iteration, so all positions are the best
swarm_best_position = particles(np.argmax(gains)) #x with with the highest gain
swarm_best_gain = np.max(gains) #highest gain
l = np.empty((MAX_ITER, POP_SIZE)) #array to collect all pops to visualize afterwards
for i in range(MAX_ITER):
l(i) = np.array(np.copy(particles)) #collecting a pop to visualize
r1 = rnd.uniform(0, 1, POP_SIZE) #defining a random coefficient for personal behavior
r2 = rnd.uniform(0, 1, POP_SIZE) #defining a random coefficient for social behavior
velocities = np.array(w * velocities + c1 * r1 * (best_positions - particles) + c2 * r2 * (swarm_best_position - particles)) #calculating velocities
particles+=velocities #updating position by adding the velocity
new_gains = -np.array(function(particles)) #calculating new gains
idx = np.where(new_gains > gains) #getting index of Xs, which have a greater gain now
best_positions(idx) = particles(idx) #updating the best positions with the new particles
gains(idx) = new_gains(idx) #updating gains
if np.max(new_gains) > swarm_best_gain: #if current maxima is greateer than across all previous iters, than assign
swarm_best_position = particles(np.argmax(new_gains)) #assigning the best candidate solution
swarm_best_gain = np.max(new_gains) #assigning the best gain
print(f'Iteration {i+1} \tGain: {swarm_best_gain}')
Dopo 30 iterazioni abbiamo questo:
Come puoi vedere, l'algoritmo è caduto nel minimo locale, che non è quello che volevamo. Ecco perché dobbiamo mettere a punto i nostri ipoparametri e ripartire. Questa volta ho deciso di impostare il peso d'inerzia w=0,8quindi, ora la velocità precedente ha un impatto maggiore sullo stato attuale.
E voilà, abbiamo raggiunto il minimo globale della funzione. Ti incoraggio vivamente a giocare con POP_SIZE, c₁ e c₂. Ti consentirà di comprendere meglio il codice e l'idea alla base di PSO. Se sei interessato puoi complicare il compito e ottimizzare alcune funzioni 3D e realizzare una bella visualizzazione.
===========================================
(1) Shi Y. Ottimizzazione dello sciame di particelle // Connessioni IEEE. — 2004. — Т. 2. — №. 1. — С. 8–13.
===========================================
Tutti i miei articoli su Medium sono gratuiti e ad accesso aperto, ecco perché apprezzerei davvero se mi seguissi qui!
Ps Sono estremamente appassionato di (Geo)Data Science, ML/AI e cambiamento climatico. Quindi, se vuoi lavorare insieme su qualche progetto, contattami LinkedIn.
🛰️Segui per saperne di più🛰️
Fonte: towardsdatascience.com