Transition de MATLAB vers PyTorch et EvoX
Ce document vise à guider les utilisateurs de MATLAB dans la transition vers PyTorch et EvoX pour le calcul évolutif. Nous mettrons en évidence les différences fondamentales entre MATLAB et PyTorch en termes de syntaxe, de structures de données et de workflow. Nous illustrerons ensuite ces différences en utilisant un exemple d’Optimisation par Essaim de Particules (PSO) en MATLAB et en PyTorch.
Différences de syntaxe
Création et indexation de tableaux
MATLAB
- Utilise l’indexation basée sur 1.
- Les vecteurs et matrices sont déclarés en utilisant des crochets et des points-virgules (par exemple
[1 2 3; 4 5 6]). L’initialisation aléatoire avecrand()retourne des valeurs dans l’intervalle $[0, 1)$. - Le découpage est effectué en utilisant la syntaxe
(start:end)et utilise l’indexation basée sur 1.
PyTorch
- Utilise l’indexation basée sur 0.
- Les tableaux (tenseurs) sont généralement créés en utilisant des constructeurs comme
torch.rand(),torch.zeros(), ou des listes Python converties en tenseurs avectorch.tensor(). - Le découpage est fait en utilisant
[start:end]avec des indices basés sur 0.
Calcul matriciel
MATLAB
- Effectue la multiplication matricielle algébrique linéaire par
*. - Utilise
.*pour multiplier les éléments correspondants de matrices de même taille. /représente la division matricielle à droite..^représente la puissance élément par élément.- Les dimensions de début et de fin des tenseurs de longueur 1 sont ignorées.
- Trouve automatiquement les dimensions diffusables pour les opérations élément par élément et effectue une extension de dimension implicite.
PyTorch
- Effectue la multiplication matricielle algébrique linéaire par
@outorch.matmul(). - Utilise directement
*pour multiplier les éléments correspondants de tenseurs de même forme ou de formes diffusables. /représente la division élément par élément.**représente la puissance élément par élément.- Les dimensions des tenseurs de longueur 1 sont préservées et traitées comme des dimensions de diffusion.
- Empêche la plupart des extensions de dimension implicites, les dimensions de diffusion sont généralement requises.
Fonctions et définitions
MATLAB
- Une fonction est définie par le mot-clé
function. - Un fichier peut contenir plusieurs fonctions, mais généralement la fonction principale partage le nom du fichier.
- Les fonctions anonymes (par exemple
@(x) sum(x.^2)) sont utilisées pour de courts calculs en ligne.
PyTorch
- Les fonctions sont définies en utilisant le mot-clé def, généralement dans un seul fichier
.pyou module. - Les classes sont utilisées pour encapsuler les données et les méthodes de manière orientée objet.
- Les lambdas servent de courtes fonctions anonymes (
lambda x: x.sum()), mais les lambdas multi-lignes ne sont pas autorisées.
Flux de contrôle
MATLAB
- Utilise
for i = 1:N…endavec l’indexation basée sur 1. - Instructions conditionnelles comme
if,elseifetelse.
PyTorch
- Utilise
for i in range(N):avec l’indexation basée sur 0. - L’indentation est significative pour la portée dans les boucles et les conditionnelles (pas de mot-clé
end).
Affichage et commentaires
MATLAB
- Utilise les fonctions
fprintf()pour la sortie formatée. - Utilise
%pour les commentaires sur une seule ligne.
PyTorch
- Utilise
printavec les f-strings pour la sortie formatée. - Utilise
#pour les commentaires sur une seule ligne.
Code multi-lignes
MATLAB
- Utilise
...en fin de ligne pour indiquer que la ligne suivante doit être traitée comme la même ligne.
Python
- Utilise
\en fin de ligne pour indiquer que la ligne suivante doit être traitée comme la même ligne. - Si plusieurs lignes sont à l’intérieur de parenthèses, aucun symbole de fin spécifique n’est requis.
Comment écrire un algorithme de calcul évolutif via EvoX ?
MATLAB
Un exemple de code MATLAB pour l’algorithme PSO est le suivant :
function [] = example_pso()
pso = init_pso(100, [-10, -10], [10, 10], 0.6, 2.5, 0.8);
test_fn = @(x) (sum(x .* x, 2));
for i = 1:20
pso = step_pso(pso, test_fn);
fprintf("Iteration = %d, global best = %f\n", i, pso.global_best_fitness);
end
end
function [self] = init_pso(pop_size, lb, ub, w, phi_p, phi_g)
self = struct();
self.pop_size = pop_size;
self.dim = length(lb);
self.w = w;
self.phi_p = phi_p;
self.phi_g = phi_g;
% setup
range = ub - lb;
population = rand(self.pop_size, self.dim);
population = range .* population + lb;
velocity = rand(self.pop_size, self.dim);
velocity = 2 .* range .* velocity - range;
self.lb = lb;
self.ub = ub;
% mutable
self.population = population;
self.velocity = velocity;
self.local_best_location = population;
self.local_best_fitness = Inf(self.pop_size, 1);
self.global_best_location = population(1, :);
self.global_best_fitness = Inf;
end
function [self] = step_pso(self, evaluate)
% Evaluate
fitness = evaluate(self.population);
% Update the local best
compare = find(self.local_best_fitness > fitness);
self.local_best_location(compare, :) = self.population(compare, :);
self.local_best_fitness(compare) = fitness(compare);
% Update the global best
values = [self.global_best_location; self.population];
keys = [self.global_best_fitness; fitness];
[min_val, min_index] = min(keys);
self.global_best_location = values(min_index, :);
self.global_best_fitness = min_val;
% Update velocity and position
rg = rand(self.pop_size, self.dim);
rp = rand(self.pop_size, self.dim);
velocity = self.w .* self.velocity ...
+ self.phi_p .* rp .* (self.local_best_location - self.population) ...
+ self.phi_g .* rg .* (self.global_best_location - self.population);
population = self.population + velocity;
self.population = min(max(population, self.lb), self.ub);
self.velocity = min(max(velocity, self.lb), self.ub);
end
En MATLAB, la fonction init_pso() initialise l’algorithme, et une fonction séparée step_pso() effectue une étape d’itération et la fonction principale example_pso() orchestre la boucle.
EvoX
Dans EvoX, vous pouvez construire l’algorithme PSO de la manière suivante :
D’abord, il est recommandé d’importer les modules et fonctions nécessaires depuis EvoX et PyTorch.
import torch
from evox.core import *
from evox.utils import *
from evox.workflows import *
from evox.problems.numerical import Sphere
Ensuite, vous pouvez transformer le code MATLAB en code Python correspondant selon la section “Différences de syntaxe”.
def main():
pso = PSO(pop_size=10, lb=torch.tensor([-10.0, -10.0]), ub=torch.tensor([10.0, 10.0]))
wf = StdWorkflow()
wf.setup(algorithm=pso, problem=Sphere())
for i in range(1, 21):
wf.step()
print(f"Iteration = {i}, global best = {wf.algorithm.global_best_fitness}")
@jit_class
class PSO(Algorithm):
def __init__(self, pop_size, lb, ub, w=0.6, phi_p=2.5, phi_g=0.8):
super().__init__()
self.pop_size = pop_size
self.dim = lb.shape[0]
self.w = w
self.phi_p = phi_p
self.phi_g = phi_g
# setup
lb = lb.unsqueeze(0)
ub = ub.unsqueeze(0)
range = ub - lb
population = torch.rand(self.pop_size, self.dim)
population = range * population + lb
velocity = torch.rand(self.pop_size, self.dim)
velocity = 2 * range * velocity - range
self.lb = lb
self.ub = ub
# mutable
self.population = population
self.velocity = velocity
self.local_best_location = population
self.local_best_fitness = torch.full((self.pop_size,), fill_value=torch.inf)
self.global_best_location = population[0, :]
self.global_best_fitness = torch.tensor(torch.inf)
def step(self):
# Evaluate
fitness = self.evaluate(self.population)
# Update the local best
compare = self.local_best_fitness > fitness
self.local_best_location = torch.where(compare.unsqueeze(1), self.population, self.local_best_location)
self.local_best_fitness = torch.where(compare, fitness, self.local_best_fitness)
# Update the global best
values = torch.cat([self.global_best_location.unsqueeze(0), self.population], dim=0)
keys = torch.cat([self.global_best_fitness.unsqueeze(0), fitness], dim=0)
min_index = torch.argmin(keys)
self.global_best_location = values[min_index]
self.global_best_fitness = keys[min_index]
# Update velocity and position
rg = torch.rand(self.pop_size, self.dim)
rp = torch.rand(self.pop_size, self.dim)
velocity = (
self.w * self.velocity
+ self.phi_p * rp * (self.local_best_location - self.population)
+ self.phi_g * rg * (self.global_best_location - self.population)
)
population = self.population + velocity
self.population = clamp(population, self.lb, self.ub)
self.velocity = clamp(velocity, self.lb, self.ub)
# Run the main function
if __name__ == "__main__":
main()
Note : Il convient de noter que nous utilisons
[]avec;et,en MATLAB pour concaténer des matrices et des vecteurs le long d’une dimension spécifique ; cependant, dans EvoX,torch.catdoit être invoqué avec l’argumentdimpour indiquer la dimension de concaténation. De plus, dans PyTorch, les tenseurs à concaténer doivent avoir le même nombre de dimensions ; par conséquent, unXXX.unsqueeze(0)supplémentaire est appliqué pour ajouter une nouvelle dimension de longueur 1 avant la première dimension.
Dans EvoX, la logique PSO est encapsulée dans une classe qui hérite de Algorithm. Cette conception orientée objet simplifie la gestion de l’état et l’itération, et introduit les avantages suivants :
- Méthode
evaluate()héritée Vous pouvez simplement appelerself.evaluate(self.population)pour calculer les valeurs de fitness, plutôt que de passer manuellement votre fonction objectif à chaque itération. - Intégration de workflow intégrée
Lorsque vous enregistrez votre classe PSO avec un workflow
StdWorkflow, il gère les appels itératifs àstep()en votre nom.
En étendant Algorithm, __init__() configure tous les composants majeurs du PSO (population, vélocité, meilleur local/global, etc.) dans un constructeur de classe Python standard.