16. Construcción de una red neuronal simple

16.1 Introducción

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.

16.2 Qué significa construir una red neuronal

Construir una red neuronal significa definir su arquitectura en código.

Cuando hablamos de arquitectura, nos referimos a decisiones como estas:

  • Cuántas entradas recibirá el modelo.
  • Cuántas capas tendrá.
  • Cuántas neuronas habrá en cada capa.
  • Qué funciones de activación se usarán.
  • Qué forma tendrá la salida.

En otras palabras, construir la red es darle una forma concreta al modelo que luego será entrenado.

16.3 Relación con la teoría ya vista

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:

  • Las neuronas artificiales se convierten en operaciones sobre tensores.
  • Los pesos y bias se convierten en parámetros del modelo.
  • Las funciones de activación se convierten en capas o funciones de PyTorch.
  • La arquitectura se traduce en clases y módulos.

Esto significa que programar una red no es algo separado de la teoría: es la manera de llevarla al código.

16.4 Una red neuronal simple

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:

  • Una capa de entrada.
  • Una capa oculta.
  • Una capa de salida.

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.

16.5 Ejemplo conceptual de arquitectura

Imaginemos una red con esta forma:

2 - 4 - 1

Esto podría significar:

  • 2 valores de entrada.
  • 4 neuronas en una capa oculta.
  • 1 neurona de salida.

Es una red muy pequeña, pero suficiente para entender cómo se traduce una arquitectura al código.

16.6 Qué piezas necesitamos en PyTorch

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.
  • Funciones de activación como ReLU o Sigmoid.

Con estas piezas ya podemos construir muchos modelos iniciales.

16.7 El papel de nn.Module

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”.

16.8 La idea de una capa lineal

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:

y = xW + b

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.

16.9 Por qué una sola capa lineal no siempre alcanza

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:

  • Capa lineal.
  • Función de activación.
  • Nueva capa lineal.

Esta combinación ya produce un modelo mucho más expresivo que una transformación lineal sola.

16.10 La función forward

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:

Dada una entrada, ¿cómo calcula el modelo la salida?

Allí escribimos el orden en que se aplican las capas y activaciones.

16.11 Estructura general de una red simple

Una red neuronal simple en PyTorch suele tener una estructura como esta:

import torch
import torch.nn as nn

class RedSimple(nn.Module):
    def __init__(self):
        super().__init__()
        ...

    def forward(self, x):
        ...
        return x

Aunque todavía haya puntos suspensivos, aquí ya aparecen las piezas fundamentales:

  • Una clase que representa el modelo.
  • Un constructor para definir capas.
  • Un método forward para definir el flujo de datos.

16.12 El constructor __init__

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:

  • Una capa lineal que vaya de 2 entradas a 4 neuronas.
  • Otra capa lineal que vaya de 4 neuronas a 1 salida.

Es decir, el constructor es el lugar donde declaramos los componentes permanentes del modelo.

16.13 Primer ejemplo concreto de definición

Veamos una versión concreta y sencilla:

import torch
import torch.nn as nn

class RedSimple(nn.Module):
    def __init__(self):
        super().__init__()
        self.capa1 = nn.Linear(2, 4)
        self.capa2 = nn.Linear(4, 1)

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.

16.14 Agregar una activación

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:

import torch
import torch.nn as nn

class RedSimple(nn.Module):
    def __init__(self):
        super().__init__()
        self.capa1 = nn.Linear(2, 4)
        self.activacion = nn.ReLU()
        self.capa2 = nn.Linear(4, 1)

Ahora la red ya incluye una no linealidad, lo que le da más capacidad de modelado.

16.15 Definir el forward

Una vez declaradas las capas, necesitamos indicar cómo se usarán. Eso se hace en forward.

def forward(self, x):
    x = self.capa1(x)
    x = self.activacion(x)
    x = self.capa2(x)
    return x

Este método expresa con claridad el recorrido de la información:

  • La entrada pasa por la primera capa.
  • Luego se aplica la activación.
  • Después se pasa por la capa de salida.

16.16 Modelo completo

Si reunimos todo, obtenemos una red neuronal simple y funcional:

import torch
import torch.nn as nn

class RedSimple(nn.Module):
    def __init__(self):
        super().__init__()
        self.capa1 = nn.Linear(2, 4)
        self.activacion = nn.ReLU()
        self.capa2 = nn.Linear(4, 1)

    def forward(self, x):
        x = self.capa1(x)
        x = self.activacion(x)
        x = self.capa2(x)
        return x

Este código ya define una red sencilla lista para ser usada con entradas reales.

16.17 Crear una instancia del modelo

Definir la clase no alcanza. Luego necesitamos crear una instancia del modelo, del mismo modo que hacemos con cualquier clase de Python.

modelo = RedSimple()

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.

16.18 Qué significa que los parámetros se inicialicen solos

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.

16.19 Ver el modelo en pantalla

Una forma útil de inspeccionar la red es imprimirla:

print(modelo)

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.

16.20 Preparar una entrada de ejemplo

Para probar la red, necesitamos darle una entrada. Como nuestra arquitectura espera 2 valores de entrada, podemos crear un tensor con dos características.

entrada = torch.tensor([[0.5, 1.2]])

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.

16.21 Por qué la entrada suele tener forma de lote

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.

16.22 Realizar una pasada hacia adelante

Una vez creado el modelo y preparada la entrada, podemos obtener una salida simplemente llamando al modelo como si fuera una función:

salida = modelo(entrada)
print(salida)

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.

16.23 Qué representa la salida

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:

  • Una predicción continua en regresión.
  • Un puntaje previo a una activación.
  • Una salida intermedia que luego se transformará.

Lo importante aquí es entender que la red ya es capaz de transformar datos de entrada en una salida coherente con su arquitectura.

16.24 Parámetros del modelo

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:

  • Los pesos y bias de la primera capa lineal.
  • Los pesos y bias de la segunda capa lineal.

PyTorch los registra automáticamente porque forman parte de un modelo derivado de nn.Module.

16.25 Consultar los parámetros

Si queremos ver los parámetros del modelo, podemos recorrerlos:

for parametro in modelo.parameters():
    print(parametro)

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.

16.26 Diferencia entre definir y entrenar

Un punto muy importante es no confundir definir una red con entrenarla.

En este tema estamos construyendo la estructura del modelo. Eso significa:

  • Definir capas.
  • Definir el recorrido de la información.
  • Crear el objeto modelo.
  • Probar una pasada hacia adelante.

Entrenar, en cambio, implicará calcular pérdida, hacer backpropagation y actualizar parámetros. Eso lo veremos con más profundidad en el tema siguiente.

16.27 Otro modo de construir modelos: nn.Sequential

Para modelos muy simples, PyTorch ofrece una alternativa llamada nn.Sequential.

Permite definir una secuencia de capas de manera compacta:

import torch.nn as nn

modelo = nn.Sequential(
    nn.Linear(2, 4),
    nn.ReLU(),
    nn.Linear(4, 1)
)

Esta forma es cómoda cuando la red es lineal y no necesita una lógica especial en el método forward.

16.28 Cuándo conviene usar una clase propia

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:

  • Controlar mejor la arquitectura.
  • Agregar lógica personalizada.
  • Combinar capas de manera no lineal.
  • Hacer el código más expresivo y mantenible.

Por eso, aunque veremos ambas opciones, la definición con nn.Module es la que más conviene dominar.

16.29 Errores comunes al construir una red simple

Al empezar, hay varios errores que suelen repetirse:

  • Olvidar llamar a super().__init__().
  • Definir capas en forward en lugar de definirlas en __init__.
  • Usar un tamaño de entrada que no coincide con la primera capa.
  • No respetar la forma esperada del lote.
  • Confundir la definición de la arquitectura con el entrenamiento.

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.

16.30 Buenas prácticas para estudiantes

Al construir tus primeras redes, suelen ser útiles estas recomendaciones:

  • Comenzar con arquitecturas pequeñas.
  • Usar nombres claros para las capas.
  • Probar el modelo con entradas simples antes de entrenarlo.
  • Imprimir el modelo y revisar su estructura.
  • Verificar siempre la forma de las entradas y salidas.

Estas costumbres reducen errores y ayudan mucho a comprender el comportamiento real del modelo.

16.31 Relación con el siguiente paso

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.

16.32 Qué debes recordar de este tema

  • Construir una red neuronal significa definir su arquitectura en código.
  • En PyTorch, los modelos suelen definirse como clases que heredan de nn.Module.
  • Las capas se declaran normalmente en __init__.
  • El método forward define cómo viajan los datos por la red.
  • nn.Linear representa capas totalmente conectadas.
  • Las funciones de activación como ReLU permiten introducir no linealidad.
  • Una vez creado el modelo, se puede probar con una entrada para obtener una salida.
  • Definir la red no es lo mismo que entrenarla.

16.33 Conclusión

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.