Phase 03 - Lesson 02

Redes Multicapa y Pasada Hacia Adelante

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

Una neurona dibuja una recta. Apílalas y podrás dibujar cualquier cosa.

Tipo: Build Lenguajes: Python Prerrequisitos: Fase 01 (Fundamentos de Matemáticas), Lección 03.01 (El Perceptrón) Tiempo: ~90 minutos

Objetivos de Aprendizaje

  • Construir una red multicapa desde cero con clases Layer y Network que realicen una pasada hacia adelante completa
  • Rastrear las dimensiones de las matrices a través de cada capa de una red e identificar incompatibilidades de forma
  • Explicar cómo apilar activaciones no lineales permite que una red aprenda fronteras de decisión curvas
  • Resolver el problema XOR usando una arquitectura 2-2-1 con pesos sigmoides ajustados a mano

El Problema

Una sola neurona es una dibujante de rectas. Eso es todo. Una recta a través de tus datos. Todo problema real en IA -- reconocimiento de imágenes, comprensión del lenguaje, jugar Go -- requiere curvas. Apilar neuronas en capas es como obtienes curvas.

En 1969, Minsky y Papert demostraron que esta limitación era fatal: una red de una sola capa no puede aprender XOR. No es que "le cueste aprender" -- matemáticamente no puede. La tabla de verdad de XOR coloca [0,1] y [1,0] de un lado, y [0,0] y [1,1] del otro. Ninguna recta única los separa.

Esto mató el financiamiento de las redes neuronales por más de una década. La solución era obvia en retrospectiva: deja de usar una sola capa. Apila neuronas en capas. Deja que la primera capa esculpa el espacio de entrada en nuevas features y deja que la segunda capa combine esas features en decisiones que ninguna recta única podría tomar.

Esa pila es la red multicapa. Es la base de todo modelo de deep learning en producción hoy. La pasada hacia adelante -- datos fluyendo desde la entrada, a través de las capas ocultas, hasta la salida -- es lo primero que necesitas construir antes de que cualquier otra cosa funcione.

El Concepto

Capas: Entrada, Oculta, Salida

Una red multicapa tiene tres tipos de capas:

Capa de entrada -- en realidad no es una capa. Almacena tus datos crudos. Dos features significan dos nodos de entrada. Aquí no ocurre ningún cálculo.

Capas ocultas -- donde ocurre el trabajo. Cada neurona toma todas las salidas de la capa anterior, aplica pesos y un sesgo, y luego pasa el resultado por una función de activación. "Ocultas" porque nunca ves estos valores directamente en los datos de entrenamiento.

Capa de salida -- la respuesta final. Para clasificación binaria, una neurona con sigmoide. Para multiclase, una neurona por clase.

graph LR
    subgraph Input["Capa de Entrada"]
        x1["x1"]
        x2["x2"]
    end
    subgraph Hidden["Capa Oculta (3 neuronas)"]
        h1["h1"]
        h2["h2"]
        h3["h3"]
    end
    subgraph Output["Capa de Salida"]
        y["y"]
    end
    x1 --> h1
    x1 --> h2
    x1 --> h3
    x2 --> h1
    x2 --> h2
    x2 --> h3
    h1 --> y
    h2 --> y
    h3 --> y

Esta es una red 2-3-1. Dos entradas, tres neuronas ocultas, una salida. Cada conexión lleva un peso. Cada neurona (excepto las de entrada) lleva un sesgo.

Cada capa produce un vector de números llamado estado oculto. Para texto, los estados ocultos aumentan la dimensionalidad -- codificando una palabra como 768 números para capturar significado semántico. Para imágenes, reducen la dimensionalidad -- comprimiendo millones de píxeles en una representación manejable. El estado oculto es donde vive el aprendizaje.

Neuronas y Activaciones

Cada neurona hace tres cosas:

  1. Multiplica cada entrada por su peso correspondiente
  2. Suma todos los productos y agrega un sesgo
  3. Pasa la suma por una función de activación

Por ahora, la activación es la sigmoide:

sigmoid(z) = 1 / (1 + e^(-z))

La sigmoide comprime cualquier número al rango (0, 1). Las entradas grandes y positivas empujan hacia 1. Las entradas grandes y negativas empujan hacia 0. El cero se mapea a 0.5. Esta curva suave es lo que hace posible el aprendizaje -- a diferencia del escalón abrupto del perceptrón, la sigmoide tiene gradiente en todos los puntos.

Pasada Hacia Adelante: Cómo Fluyen los Datos

La pasada hacia adelante empuja los datos de entrada por la red, capa por capa, hasta que alcanza la salida. No ocurre ningún aprendizaje durante la pasada hacia adelante. Es puro cálculo: multiplicar, sumar, activar, repetir.

graph TD
    X["Entrada: [x1, x2]"] --> WH["Multiplicar por Matriz de Pesos W1 (2x3)"]
    WH --> BH["Agregar Vector de Sesgo b1 (3,)"]
    BH --> AH["Aplicar sigmoide a cada elemento"]
    AH --> H["Salida Oculta: [h1, h2, h3]"]
    H --> WO["Multiplicar por Matriz de Pesos W2 (3x1)"]
    WO --> BO["Agregar Vector de Sesgo b2 (1,)"]
    BO --> AO["Aplicar sigmoide"]
    AO --> Y["Salida: y"]

En cada capa, tres operaciones ocurren en secuencia:

z = W * input + b       (linear transformation)
a = sigmoid(z)           (activation)

La salida de una capa se convierte en la entrada de la siguiente. Esa es la pasada hacia adelante completa.

Dimensiones de Matriz

Rastrear dimensiones es la habilidad de depuración más importante en deep learning. Aquí está la red 2-3-1:

Paso Operación Dimensiones Forma del Resultado
Entrada x -- (2,)
Lineal oculta W1 * x + b1 W1: (3, 2), b1: (3,) (3,)
Activación oculta sigmoid(z1) -- (3,)
Lineal de salida W2 * h + b2 W2: (1, 3), b2: (1,) (1,)
Activación de salida sigmoid(z2) -- (1,)

La regla: la matriz de pesos W en la capa k tiene forma (neurons_in_layer_k, neurons_in_layer_k_minus_1). Las filas corresponden a la capa actual. Las columnas corresponden a la capa anterior. Si las formas no encajan, tienes un bug.

Teorema de Aproximación Universal

En 1989, George Cybenko demostró algo notable: una red neuronal con una sola capa oculta y suficientes neuronas puede aproximar cualquier función continua con cualquier precisión deseada.

Esto no significa que una capa oculta sea siempre lo mejor. Significa que la arquitectura es teóricamente capaz. En la práctica, las redes más profundas (más capas, menos neuronas por capa) aprenden las mismas funciones con muchos menos parámetros totales que las redes superficiales y anchas. Por eso funciona el deep learning.

La intuición: cada neurona en la capa oculta aprende una "protuberancia" o feature. Suficientes protuberancias ubicadas en los lugares correctos pueden aproximar cualquier curva suave. Más neuronas, más protuberancias, mejor aproximación.

graph LR
    subgraph FewNeurons["4 Neuronas Ocultas"]
        A["Aproximación tosca"]
    end
    subgraph MoreNeurons["16 Neuronas Ocultas"]
        B["Aproximación cercana"]
    end
    subgraph ManyNeurons["64 Neuronas Ocultas"]
        C["Ajuste casi perfecto"]
    end
    FewNeurons --> MoreNeurons --> ManyNeurons

Componibilidad

Las redes neuronales son componibles. Puedes apilarlas, encadenarlas, ejecutarlas en paralelo. Un modelo Whisper usa una red codificadora para procesar audio y una red decodificadora separada para generar texto. Los LLM modernos son decoder-only. BERT es encoder-only. T5 es encoder-decoder. La elección de la arquitectura define lo que el modelo puede hacer.

Constrúyelo

Python puro. Sin numpy. Cada operación de matriz escrita desde cero.

Paso 1: Activación Sigmoide

import math

def sigmoid(x):
    x = max(-500.0, min(500.0, x))
    return 1.0 / (1.0 + math.exp(-x))

El recorte a [-500, 500] previene el desbordamiento. math.exp(500) es grande pero finito. math.exp(1000) es infinito.

Paso 2: Clase Layer

La operación más importante en todo el deep learning es la multiplicación de matrices. Cada capa, cada cabeza de atención, cada pasada hacia adelante -- son matmuls hasta el fondo. Una capa lineal toma un vector de entrada, lo multiplica por una matriz de pesos y agrega un vector de sesgo: y = Wx + b. Esa única ecuación es el 90% del cálculo en una red neuronal.

Una capa almacena una matriz de pesos y un vector de sesgo. Su método forward toma un vector de entrada y devuelve la salida activada.

class Layer:
    def __init__(self, n_inputs, n_neurons, weights=None, biases=None):
        if weights is not None:
            self.weights = weights
        else:
            import random
            self.weights = [
                [random.uniform(-1, 1) for _ in range(n_inputs)]
                for _ in range(n_neurons)
            ]
        if biases is not None:
            self.biases = biases
        else:
            self.biases = [0.0] * n_neurons

    def forward(self, inputs):
        self.last_input = inputs
        self.last_output = []
        for neuron_idx in range(len(self.weights)):
            z = sum(
                w * x for w, x in zip(self.weights[neuron_idx], inputs)
            )
            z += self.biases[neuron_idx]
            self.last_output.append(sigmoid(z))
        return self.last_output

La matriz de pesos tiene forma (n_neurons, n_inputs). Cada fila son los pesos de una neurona a través de todas las entradas. El método forward recorre las neuronas, calcula la suma ponderada más el sesgo, aplica la sigmoide y recolecta los resultados.

Paso 3: Clase Network

Una red es una lista de capas. La pasada hacia adelante las encadena: la salida de la capa k alimenta la capa k+1.

class Network:
    def __init__(self, layers):
        self.layers = layers

    def forward(self, inputs):
        current = inputs
        for layer in self.layers:
            current = layer.forward(current)
        return current

Esa es la pasada hacia adelante completa. Cuatro líneas de lógica. Los datos entran, fluyen por todas las capas y salen por el otro lado.

Paso 4: XOR con Pesos Ajustados a Mano

En la Lección 01, resolvimos XOR combinando perceptrones OR, NAND y AND. Ahora haz lo mismo con nuestras clases Layer y Network. La arquitectura 2-2-1: dos entradas, dos neuronas ocultas, una salida.

hidden = Layer(
    n_inputs=2,
    n_neurons=2,
    weights=[[20.0, 20.0], [-20.0, -20.0]],
    biases=[-10.0, 30.0],
)

output = Layer(
    n_inputs=2,
    n_neurons=1,
    weights=[[20.0, 20.0]],
    biases=[-30.0],
)

xor_net = Network([hidden, output])

xor_data = [
    ([0, 0], 0),
    ([0, 1], 1),
    ([1, 0], 1),
    ([1, 1], 0),
]

for inputs, expected in xor_data:
    result = xor_net.forward(inputs)
    predicted = 1 if result[0] >= 0.5 else 0
    print(f"  {inputs} -> {result[0]:.6f} (rounded: {predicted}, expected: {expected})")

Los pesos grandes (20, -20) hacen que la sigmoide actúe como una función escalón. La primera neurona oculta aproxima OR. La segunda aproxima NAND. La neurona de salida las combina en AND, que es XOR.

Paso 5: Clasificación de Círculo

Un problema más difícil: clasificar puntos 2D como dentro o fuera de un círculo de radio 0.5 centrado en el origen. Esto requiere una frontera de decisión curva -- imposible para un solo perceptrón.

import random
import math

random.seed(42)

data = []
for _ in range(200):
    x = random.uniform(-1, 1)
    y = random.uniform(-1, 1)
    label = 1 if (x * x + y * y) < 0.25 else 0
    data.append(([x, y], label))

circle_net = Network([
    Layer(n_inputs=2, n_neurons=8),
    Layer(n_inputs=8, n_neurons=1),
])

Con pesos aleatorios, la red no clasificará bien. Pero la pasada hacia adelante igual se ejecuta. Ese es el punto -- la pasada hacia adelante es solo cálculo. Aprender los pesos correctos es la retropropagación, que viene en la Lección 03.

correct = 0
for inputs, expected in data:
    result = circle_net.forward(inputs)
    predicted = 1 if result[0] >= 0.5 else 0
    if predicted == expected:
        correct += 1

print(f"Accuracy with random weights: {correct}/{len(data)} ({100*correct/len(data):.1f}%)")

Los pesos aleatorios dan una precisión pobre -- a menudo peor que adivinar la clase mayoritaria. Después del entrenamiento (Lección 03), esta misma arquitectura con 8 neuronas ocultas dibujará una frontera curva que separa el adentro del afuera.

Úsalo

PyTorch hace todo lo anterior en cuatro líneas:

import torch
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(2, 8),
    nn.Sigmoid(),
    nn.Linear(8, 1),
    nn.Sigmoid(),
)

x = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
output = model(x)
print(output)

nn.Linear(2, 8) es tu clase Layer: matriz de pesos de forma (8, 2), vector de sesgo de forma (8,). nn.Sigmoid() es tu función sigmoide aplicada elemento por elemento. nn.Sequential es tu clase Network: encadena capas en orden.

La diferencia es velocidad y escala. PyTorch corre en GPUs, maneja lotes de millones de muestras y calcula automáticamente gradientes para la retropropagación. Pero la lógica de la pasada hacia adelante es idéntica a la que acabas de construir desde cero.

Entrégalo

Esta lección produce un prompt reutilizable para diseñar arquitecturas de red:

  • outputs/prompt-network-architect.md

Úsalo cuando necesites decidir cuántas capas, cuántas neuronas por capa y qué funciones de activación usar para un problema dado.

Ejercicios

  1. Construye una red 2-4-2-1 (dos capas ocultas) y ejecuta la pasada hacia adelante sobre los datos de XOR con pesos aleatorios. Imprime las salidas intermedias de las capas ocultas para ver cómo se transforma la representación en cada capa.

  2. Cambia el tamaño de la capa oculta en el clasificador de círculo de 8 a 2, luego a 32. Ejecuta la pasada hacia adelante con pesos aleatorios cada vez. ¿El número de neuronas ocultas cambia el rango o la distribución de la salida? ¿Por qué?

  3. Implementa un método count_parameters en la clase Network que devuelva el número total de pesos y sesgos entrenables. Pruébalo en una red 784-256-128-10 (la clásica arquitectura de MNIST). ¿Cuántos parámetros tiene?

  4. Construye una pasada hacia adelante para una red 3-4-4-2. Aliméntala con valores de color RGB (normalizados a 0-1) y observa las dos salidas. Esta es la arquitectura de un clasificador de colores simple con dos clases.

  5. Reemplaza la sigmoide por una función de "escalón con fuga": devuelve 0.01 * z si z < 0, de lo contrario 1.0. Ejecuta la pasada hacia adelante sobre XOR con los mismos pesos ajustados a mano del Paso 4. ¿Todavía funciona? ¿Por qué se prefiere la sigmoide suave sobre los cortes abruptos?

Términos Clave

Término Lo que dice la gente Lo que realmente significa
Pasada hacia adelante "Ejecutar el modelo" Empujar la entrada por todas las capas -- multiplicar por pesos, agregar sesgo, activar -- para producir una salida
Capa oculta "La parte del medio" Cualquier capa entre la entrada y la salida cuyos valores no se observan directamente en los datos
Red multicapa "Una red neuronal profunda" Capas de neuronas apiladas secuencialmente, donde la salida de cada capa alimenta la entrada de la capa siguiente
Función de activación "La no linealidad" Una función aplicada después de la transformación lineal que introduce curvas en la frontera de decisión
Sigmoide "La curva en S" sigma(z) = 1/(1+e^(-z)), comprime cualquier número real a (0,1), suave y diferenciable en todos los puntos
Matriz de pesos "Los parámetros" Una matriz W de forma (current_layer_neurons, previous_layer_neurons) que contiene fuerzas de conexión aprendibles
Vector de sesgo "El desplazamiento" Un vector agregado después de la multiplicación de matrices que permite que las neuronas se activen incluso cuando todas las entradas son cero
Aproximación universal "Las redes neuronales pueden aprender cualquier cosa" Una sola capa oculta con suficientes neuronas puede aproximar cualquier función continua -- pero "suficientes" puede significar miles de millones
Transformación lineal "El paso de la multiplicación de matrices" z = W * x + b, el cálculo antes de la activación, que mapea entradas a un nuevo espacio
Frontera de decisión "Donde el clasificador cambia" La superficie en el espacio de entrada donde la salida de la red cruza el umbral de clasificación

Lecturas Adicionales

  • Michael Nielsen, "Neural Networks and Deep Learning", Capítulos 1-2 (http://neuralnetworksanddeeplearning.com/) -- la explicación gratuita más clara de las pasadas hacia adelante y la estructura de red, con visualizaciones interactivas
  • Cybenko, "Approximation by Superpositions of a Sigmoidal Function" (1989) -- el artículo original del teorema de aproximación universal, sorprendentemente legible
  • 3Blue1Brown, "But what is a neural network?" (https://www.youtube.com/watch?v=aircAruvnKk) -- recorrido visual de 20 minutos sobre capas, pesos y pasadas hacia adelante que construye el modelo mental correcto
  • Goodfellow, Bengio, Courville, "Deep Learning", Capítulo 6 (https://www.deeplearningbook.org/) -- la referencia estándar para redes multicapa, gratuita en línea
0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).