Hasta aquí ya hemos recorrido una parte importante del camino. Primero entendimos los conceptos fundamentales de redes neuronales y del entrenamiento. Luego conocimos PyTorch, estudiamos los tensores y aprendimos a realizar operaciones básicas con ellos.
Ahora llega un momento clave: empezar a construir una red neuronal real con PyTorch.
En este tema no nos concentraremos todavía en el entrenamiento completo, sino en la estructura del modelo. Es decir, aprenderemos cómo definir una red neuronal simple, cómo organizar sus capas y cómo hacer que reciba una entrada y produzca una salida.
Construir una red neuronal significa definir su arquitectura en código.
Cuando hablamos de arquitectura, nos referimos a decisiones como estas:
En otras palabras, construir la red es darle una forma concreta al modelo que luego será entrenado.
Es importante ver este tema como una continuación natural de lo que ya estudiamos. La teoría no desaparece cuando empezamos a programar; simplemente toma una forma práctica.
Por ejemplo:
Esto significa que programar una red no es algo separado de la teoría: es la manera de llevarla al código.
Para comenzar, lo más razonable es trabajar con una red pequeña y fácil de entender.
Una red neuronal simple podría tener:
Esta estructura ya es suficiente para introducir los conceptos esenciales de construcción del modelo.
No hace falta empezar con redes profundas o complejas. Para aprender, una red pequeña es mucho más valiosa.
Imaginemos una red con esta forma:
Esto podría significar:
Es una red muy pequeña, pero suficiente para entender cómo se traduce una arquitectura al código.
Para construir una red simple en PyTorch, normalmente usamos varias herramientas del módulo torch.nn.
Las más importantes en este punto son:
nn.Module: base para definir modelos.nn.Linear: capa lineal o totalmente conectada.ReLU o Sigmoid.Con estas piezas ya podemos construir muchos modelos iniciales.
En PyTorch, los modelos suelen definirse como clases que heredan de nn.Module.
Esto permite que la red neuronal sea tratada como un objeto con una estructura clara: contiene capas, parámetros y una forma de procesar datos.
Puede parecer un detalle técnico, pero en realidad es una decisión muy útil porque organiza el código y facilita el entrenamiento posterior.
En términos simples, heredar de nn.Module significa decirle a PyTorch: “esto que estoy definiendo es un modelo”.
Una de las capas más importantes y más fáciles de entender es la capa lineal, representada en PyTorch con nn.Linear.
Esta capa implementa una transformación del tipo:
donde:
x representa la entrada.W representa los pesos.b representa el bias.Es decir, la capa lineal traduce directamente la matemática básica de una neurona o de una capa totalmente conectada.
Si una red usara solo transformaciones lineales, su capacidad de representación sería limitada. Por eso, normalmente se agregan funciones de activación entre capas.
La activación introduce no linealidad, lo que permite que la red modele relaciones más complejas.
Por ejemplo, en una red simple es muy común usar una secuencia como esta:
Esta combinación ya produce un modelo mucho más expresivo que una transformación lineal sola.
Cuando definimos un modelo como clase en PyTorch, uno de los elementos más importantes es el método forward.
Ese método describe cómo viajan los datos a través de la red, es decir, define la propagación hacia adelante.
En otras palabras, forward responde a esta pregunta:
Allí escribimos el orden en que se aplican las capas y activaciones.
Una red neuronal simple en PyTorch suele tener una estructura como esta:
Aunque todavía haya puntos suspensivos, aquí ya aparecen las piezas fundamentales:
forward para definir el flujo de datos.Dentro del método __init__ definimos las capas que tendrá la red.
Por ejemplo, si queremos una red de arquitectura 2 - 4 - 1, podríamos tener:
Es decir, el constructor es el lugar donde declaramos los componentes permanentes del modelo.
Veamos una versión concreta y sencilla:
Aquí hemos definido dos capas lineales:
self.capa1 transforma una entrada de tamaño 2 en una salida de tamaño 4.self.capa2 transforma una entrada de tamaño 4 en una salida de tamaño 1.Si usamos solamente dos capas lineales una detrás de otra, la red sigue siendo demasiado limitada. Por eso agregamos una activación entre ambas.
Un ejemplo habitual es ReLU:
Ahora la red ya incluye una no linealidad, lo que le da más capacidad de modelado.
Una vez declaradas las capas, necesitamos indicar cómo se usarán. Eso se hace en forward.
Este método expresa con claridad el recorrido de la información:
Si reunimos todo, obtenemos una red neuronal simple y funcional:
Este código ya define una red sencilla lista para ser usada con entradas reales.
Definir la clase no alcanza. Luego necesitamos crear una instancia del modelo, del mismo modo que hacemos con cualquier clase de Python.
Desde ese momento, modelo representa una red neuronal concreta con sus capas y parámetros.
Esos parámetros se inicializan automáticamente cuando se crea la instancia.
Cuando usamos capas como nn.Linear, PyTorch crea automáticamente los pesos y bias asociados a esa capa.
No hace falta que escribamos manualmente cada valor numérico. La biblioteca los inicializa con un criterio razonable para comenzar el entrenamiento.
Esto simplifica mucho el trabajo, porque nos permite concentrarnos primero en la arquitectura del modelo.
Una forma útil de inspeccionar la red es imprimirla:
La salida mostrará las capas que contiene el modelo y su organización general.
Esto ayuda a verificar rápidamente si la estructura definida coincide con la que queríamos construir.
Para probar la red, necesitamos darle una entrada. Como nuestra arquitectura espera 2 valores de entrada, podemos crear un tensor con dos características.
Observa que la entrada se representa como una estructura de una fila y dos columnas.
La fila representa un ejemplo, y las dos columnas representan las dos variables de entrada.
Aunque estemos usando un solo ejemplo, muchas veces PyTorch espera que la entrada tenga forma de lote, es decir, que la primera dimensión represente cuántos ejemplos estamos enviando juntos.
Por eso:
[0.5, 1.2] representa solo una secuencia de dos valores.[[0.5, 1.2]] representa un lote de tamaño 1 con dos características.Esta diferencia es muy importante y suele generar errores al comenzar.
Una vez creado el modelo y preparada la entrada, podemos obtener una salida simplemente llamando al modelo como si fuera una función:
Esto ejecuta automáticamente el método forward.
En otras palabras, PyTorch toma la entrada, la hace pasar por las capas definidas y devuelve el resultado.
La salida del modelo es un tensor. En este ejemplo, como la última capa produce un solo valor, la salida tendrá una dimensión final de tamaño 1.
Ese valor puede interpretarse de distintas maneras según el problema:
Lo importante aquí es entender que la red ya es capaz de transformar datos de entrada en una salida coherente con su arquitectura.
Una red neuronal simple no es solo una secuencia de capas: también contiene parámetros que serán aprendidos durante el entrenamiento.
En nuestro ejemplo, esos parámetros son:
PyTorch los registra automáticamente porque forman parte de un modelo derivado de nn.Module.
Si queremos ver los parámetros del modelo, podemos recorrerlos:
Esto imprimirá tensores que representan pesos y bias.
No es necesario analizar todavía cada valor numérico, pero sí entender que esos son los elementos que luego se ajustarán durante el entrenamiento.
Un punto muy importante es no confundir definir una red con entrenarla.
En este tema estamos construyendo la estructura del modelo. Eso significa:
Entrenar, en cambio, implicará calcular pérdida, hacer backpropagation y actualizar parámetros. Eso lo veremos con más profundidad en el tema siguiente.
Para modelos muy simples, PyTorch ofrece una alternativa llamada nn.Sequential.
Permite definir una secuencia de capas de manera compacta:
Esta forma es cómoda cuando la red es lineal y no necesita una lógica especial en el método forward.
Aunque nn.Sequential es útil, en cursos y proyectos reales suele ser muy importante aprender a definir modelos como clases propias.
Esto da más flexibilidad para:
Por eso, aunque veremos ambas opciones, la definición con nn.Module es la que más conviene dominar.
Al empezar, hay varios errores que suelen repetirse:
super().__init__().forward en lugar de definirlas en __init__.Estos errores son completamente normales. Lo importante es revisar con calma la forma de la entrada, la arquitectura declarada y el recorrido definido en forward.
Al construir tus primeras redes, suelen ser útiles estas recomendaciones:
Estas costumbres reducen errores y ayudan mucho a comprender el comportamiento real del modelo.
En este tema ya conseguimos algo muy importante: construir una red neuronal simple y hacer que produzca una salida.
Pero todavía falta la parte esencial del aprendizaje automático: lograr que esa red aprenda a partir de datos.
Para eso necesitaremos definir una función de pérdida, un optimizador y un ciclo de entrenamiento. Ese será el paso natural del próximo tema.
nn.Module.__init__.forward define cómo viajan los datos por la red.nn.Linear representa capas totalmente conectadas.ReLU permiten introducir no linealidad.Construir una red neuronal simple en PyTorch es uno de los primeros grandes pasos de la parte práctica del curso. A partir de este momento ya no estamos solo estudiando conceptos teóricos: estamos definiendo modelos concretos que pueden procesar datos y producir resultados.
Entender esta etapa significa comprender cómo se traduce una arquitectura de red al lenguaje de PyTorch y cómo se organiza un modelo real.
En el próximo tema daremos el siguiente paso lógico: aprenderemos el entrenamiento de un modelo en PyTorch.