A continuacion explico que hace cada linea en mi archivo funcional.
import torch # importamos la libreria principal de PyTorch
import torch.nn as nn # importamos el modulo de redes neuronales de PyTorch para definir la arquitectura de la red neuronal y las funciones de activacion
import torch.optim as optim # importamos el modulo de optimizadores de PyTorch para definir el algoritmo de optimizacion que se utilizara para actualizar los pesos de la red neuronal durante el entrenamiento
import torch.nn.functional as F # importamos el modulo de funciones de activacion de PyTorch para utilizar funciones como ReLU o softmax en la definicion de la arquitectura de la red neuronal y en la propagacion hacia adelante
import time # importamos la libreria time para medir el tiempo de entrenamiento de la red neuronal
# Definimos la arquitectura de la red neuronal
class MLP(nn.Module): # Multi_layer Perceptron
def __init__(self, input_dim, hidden_dim, output_dim): # input_dim: numero de caracteristicas, hidden_dim: numero de neuronas en la capa oculta, output_dim: numero de clases
super(MLP, self).__init__() # llamamos al constructor de la clase padre nn.Module
self.layer1 = nn.Linear(input_dim, hidden_dim) # capa oculta
self.layer2 = nn.Linear(hidden_dim, output_dim)# capa de salida
def forward(self, x): # definimos la funcion de activacion y la propagacion hacia adelante
x = F.relu(self.layer1(x)) # aplicamos la funcion de activacion ReLu para la capa oculta
x = self.layer2(x) # aplicamos la capa de salida sin funcion de activacion debido a que la funcion de perdida crossenropy ya incluye la funcion de activacion softmax
return x # retornamos la salida de la red neuronal
# Creamos una instancia de la red neuronal
input_dim = 10
hidden_dim = 5
output_dim = 2
model = MLP(input_dim, hidden_dim, output_dim) # creamos una instancia de la clase MLP con los parametros de entrada, capa oculta y salida
# Definimos la función de pérdida y el optimizador
criterion = nn.CrossEntropyLoss() # funcion de perdida para problemas de clasificacion, ya que la salida de la red neuronal es un vector de probabilidades para cada clase
optimizer = optim.Adam(model.parameters(), lr=0.001) # optimizador Adam para actualizar los pesos de la red neuronal durante el entrenamiento, lr: learning rate o tasa de aprendizaje que controla la velocidad de actualizacion de los pesos
# Generamos datos de entrenamiento aleatorios
num_samples = 100 # numero de muestras de entrenamiento, cada muestra es un vector de 10 caracteristicas y una etiqueta de clase (0 o 1)
X_train = torch.randn(num_samples, input_dim) # generamos un tensor de tamaño (100, 10) con valores aleatorios siguiendo una distribucion normal, cada fila representa una muestra de entrenamiento con 10 caracteristicas
y_train = torch.randint(0, output_dim, (num_samples,)) # generamos un tensor de tamaño (100,) con valores aleatorios entre 0 y 1, cada valor representa la etiqueta de clase para cada muestra de entrenamiento
# Entrenamos la red neuronal
num_epochs = 20 # numero de epocas de entrenamiento, cada epoca representa una iteracion completa sobre todo el conjunto de datos de entrenamiento
for epoch in range(num_epochs):# iteramos sobre el numero de epocas para entrenar la red neuronal
start_time = time.time() # registramos el tiempo de inicio de cada epoca para medir el tiempo de entrenamiento
model.train() # ponemos el modelo en modo de entrenamiento para activar funciones como dropout o batch normalization si estuvieran presentes
optimizer.zero_grad() # limpiamos los gradientes acumulados de las iteraciones anteriores para evitar que se sumen a los nuevos gradientes calculados en esta iteracion
outputs = model(X_train) # pasamos los datos de entrenamiento a traves del modelo para obtener las predicciones de la red neuronal, outputs es un tensor de tamaño (100, 2) con las probabilidades para cada clase
loss = criterion(outputs, y_train) # calculamos la perdida entre las predicciones de la red neuronal y las etiquetas reales utilizando la funcion de perdida definida anteriormente, loss es un tensor escalar que representa el valor de la perdida para esta iteracion
loss.backward()# calculamos los gradientes de la perdida con respecto a los pesos de la red neuronal utilizando el metodo backward(), esto permite que el optimizador pueda actualizar los pesos en la direccion correcta para minimizar la perdida
optimizer.step() # actualizamos los pesos de la red neuronal utilizando el optimizador definido anteriormente, esto aplica la regla de actualizacion de Adam para ajustar los pesos en funcion de los gradientes calculados y la tasa de aprendizaje
end_time = time.time() # registramos el tiempo de fin de cada epoca para medir el tiempo de entrenamiento
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}, Time: {end_time - start_time:.2f} seconds') # imprimimos el numero de epoca, el valor de la perdida y el tiempo de entrenamiento para cada epoca, loss.item() convierte el tensor de perdida a un valor escalar para facilitar su visualizacion