Transformacion de MATLAB a PyTorch y EvoX
Este documento tiene como objetivo guiar a los usuarios de MATLAB en la transicion a PyTorch y EvoX para computacion evolutiva. Destacaremos las diferencias principales entre MATLAB y PyTorch en terminos de sintaxis, estructuras de datos y flujo de trabajo. Luego ilustraremos estas diferencias usando un ejemplo de Optimizacion por Enjambre de Particulas (PSO) tanto en MATLAB como en PyTorch.
Diferencias de Sintaxis
Creacion e Indexacion de Arreglos
MATLAB
- Usa indexacion basada en 1.
- Los vectores y matrices se declaran usando corchetes y punto y coma (por ejemplo,
[1 2 3; 4 5 6]). La inicializacion aleatoria conrand()devuelve valores en el intervalo $[0, 1)$. - El rebanado se realiza usando la sintaxis
(start:end)y utiliza indexacion basada en 1.
PyTorch
- Usa indexacion basada en 0.
- Los arreglos (tensores) tipicamente se crean usando constructores como
torch.rand(),torch.zeros(), o listas de Python convertidas a tensores contorch.tensor(). - El rebanado se hace usando
[start:end]con indices basados en 0.
Computacion Matricial
MATLAB
- Realiza multiplicacion matricial de algebra lineal con
*. - Usa
.*para multiplicar elementos correspondientes de matrices del mismo tamano. /representa la division matricial derecha..^representa la potencia elemento a elemento.- Las dimensiones iniciales y finales de tensores con longitud 1 se ignoran.
- Encuentra automaticamente dimensiones expandibles para operaciones elemento a elemento y realiza extension de dimension implicita.
PyTorch
- Realiza multiplicacion matricial de algebra lineal con
@otorch.matmul(). - Usa directamente
*para multiplicar elementos correspondientes de tensores de la misma forma o formas expandibles. /representa la division elemento a elemento.**representa la potencia elemento a elemento.- Las dimensiones de tensores con longitud 1 se preservan y se tratan como dimensiones de broadcast.
- Previene la mayoria de las extensiones de dimension implicitas, generalmente se requieren dimensiones de broadcast.
Funciones y Definiciones
MATLAB
- Una funcion se define con la palabra clave
function. - Un archivo puede contener multiples funciones, pero tipicamente la funcion principal comparte el nombre del archivo.
- Las funciones anonimas (por ejemplo,
@(x) sum(x.^2)) se usan para calculos cortos en linea.
PyTorch
- Las funciones se definen usando la palabra clave def, tipicamente dentro de un solo archivo
.pyo modulo. - Las clases se usan para encapsular datos y metodos de manera orientada a objetos.
- Las lambdas sirven como funciones anonimas cortas (
lambda x: x.sum()), pero no se permiten lambdas multilinea.
Flujo de Control
MATLAB
- Usa bucles
for i = 1:N…endcon indexacion basada en 1. - Sentencias condicionales como
if,elseifyelse.
PyTorch
- Usa
for i in range(N):con indexacion basada en 0. - La indentacion es significativa para el alcance en bucles y condicionales (sin palabra clave
end).
Impresion y Comentarios
MATLAB
- Usa funciones
fprintf()para salida formateada. - Usa
%para comentarios de una linea.
PyTorch
- Usa
printcon f-strings para salida formateada. - Usa
#para comentarios de una linea.
Codigo Multilinea
MATLAB
- Usa
...al final de una linea para indicar que la siguiente linea debe tratarse como la misma linea.
Python
- Usa
\al final de una linea para indicar que la siguiente linea debe tratarse como la misma linea. - Si multiples lineas estan dentro de parentesis, no se requiere ningun simbolo especial al final.
Como Escribir un Algoritmo de Computacion Evolutiva con EvoX?
MATLAB
Un ejemplo de codigo MATLAB para el algoritmo PSO es el siguiente:
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 funcion init_pso() inicializa el algoritmo, y una funcion separada step_pso() realiza un paso de iteracion y la funcion principal example_pso() orquesta el bucle.
EvoX
En EvoX, puedes construir el algoritmo PSO de la siguiente manera:
Primero, se recomienda importar los modulos y funciones necesarios de EvoX y PyTorch.
import torch
from evox.core import *
from evox.utils import *
from evox.workflows import *
from evox.problems.numerical import Sphere
Luego, puedes transformar el codigo MATLAB al codigo Python correspondiente segun la seccion “Diferencias de Sintaxis”.
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()
Nota: Vale la pena senalar que usamos
[]con;y,en MATLAB para concatenar matrices y vectores a lo largo de una dimension especifica; sin embargo, en EvoX, se debe invocartorch.catcon el argumentodimpara indicar la dimension de concatenacion. Ademas, en PyTorch, los tensores a concatenar deben tener el mismo numero de dimensiones; por lo tanto, se aplicaXXX.unsqueeze(0)adicional para agregar una nueva dimension de longitud 1 antes de la primera dimension.
En EvoX, la logica de PSO esta encapsulada dentro de una clase que hereda de Algorithm. Este diseno orientado a objetos simplifica la gestion del estado y la iteracion, e introduce las siguientes ventajas:
- Metodo
evaluate()heredado Puedes simplemente llamar aself.evaluate(self.population)para calcular los valores de aptitud, en lugar de pasar manualmente tu funcion objetivo en cada iteracion. - Integracion de Flujo de Trabajo Incorporada
Cuando registras tu clase PSO con un flujo de trabajo
StdWorkflow, este maneja las llamadas iterativas astep()en tu nombre.
Al extender Algorithm, __init__() configura todos los componentes principales de PSO (poblacion, velocidad, mejor local/global, etc.) en un constructor de clase Python estandar.