Redes neuronales artificiales

"No intentes entender las redes neuronales como si fueran el cerebro. Entendélas como álgebra lineal con funciones no lineales apiladas."

Qué vas a aprender en este capítulo

Las redes neuronales son la tecnología detrás de los LLMs (ChatGPT), el reconocimiento de imágenes (Face ID), la traducción automática, y los sistemas de recomendación de Netflix. Este capítulo explica cómo funcionan desde los fundamentos matemáticos hasta la implementación con Keras.


3.1 La neurona artificial

💡 Intuición

Una neurona biológica recibe señales de otras neuronas, las suma, y si la suma supera cierto umbral, dispara una señal hacia adelante.

Una neurona artificial hace lo mismo matemáticamente: toma entradas x1,x2,...,xdx_1, x_2, ..., x_d, las multiplica por pesos w1,w2,...,wdw_1, w_2, ..., w_d, suma, agrega un sesgo bb, y pasa el resultado por una función de activación no lineal.

z=w1x1+w2x2++wdxd+b=wTx+bz = w_1 x_1 + w_2 x_2 + \ldots + w_d x_d + b = \mathbf{w}^T \mathbf{x} + b
a=σ(z)a = \sigma(z)

📐 Fundamento

Funciones de activación:

Función Fórmula Rango Uso típico
Sigmoid 1/(1+ez)1/(1+e^{-z}) (0,1) Clasificación binaria (capa de salida)
Tanh (ezez)/(ez+ez)(e^z - e^{-z})/(e^z + e^{-z}) (-1,1) Capas ocultas (obsoleto)
ReLU max(0,z)\max(0, z) [0,∞) Capas ocultas (estándar actual)
Leaky ReLU max(0.01z,z)\max(0.01z, z) (-∞,∞) Cuando ReLU "muere"
Softmax ezk/jezje^{z_k}/\sum_j e^{z_j} (0,1), suma=1 Clasificación multiclase (salida)

¿Por qué se necesita la no linealidad?

Sin funciones de activación no lineales, apilar capas lineales equivale a una sola transformación lineal — el modelo no puede aprender patrones complejos. ReLU introduce la no linealidad necesaria con una operación muy barata.

El perceptrón (una sola neurona):

import numpy as np

def perceptron(x, w, b, activacion='relu'):
    z = np.dot(w, x) + b
    if activacion == 'relu':
        return max(0, z)
    elif activacion == 'sigmoid':
        return 1 / (1 + np.exp(-z))

# Ejemplo: neurona que predice "¿vale la pena hacer descuento?"
# Features: [gasto_promedio, frecuencia_visitas, cantidad_personas]
w = np.array([0.3, 0.5, 0.2])
b = -0.8
x = np.array([50.0, 8.0, 3.0])  # cliente con buen historial
print(perceptron(x, w, b, 'sigmoid'))  # probabilidad de descuento

3.2 Redes multicapa (MLP)

💡 Intuición

Una sola neurona puede aprender una frontera de decisión lineal. Una red multicapa apila capas de neuronas, y cada capa aprende representaciones más abstractas.

La primera capa puede aprender "detectar bordes" en una imagen. La segunda, "detectar formas". La tercera, "detectar objetos". Sin que nadie le diga qué buscar.

📐 Fundamento

Arquitectura de una red MLP:

Entrada:        Capa oculta 1:    Capa oculta 2:    Salida:
x1 ─────────┐   [neuron 1]       [neuron A]         [ŷ]
x2 ─────────┼─→ [neuron 2] ─────→ [neuron B] ──────→
x3 ─────────┘   [neuron 3]       [neuron C]

Implementación con Keras/TensorFlow:

import tensorflow as tf
from tensorflow import keras

# Arquitectura
modelo = keras.Sequential([
    keras.layers.Dense(64, activation='relu', input_shape=(4,)),  # 4 features
    keras.layers.Dense(32, activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')   # salida binaria
])

# Compilar: elegir optimizador, función de pérdida, métricas
modelo.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

# Ver la arquitectura
modelo.summary()
# Total params: 64*4+64 + 32*64+32 + 1*32+1 = 2,369 parámetros

# Entrenar
historial = modelo.fit(
    X_train, y_train,
    epochs=50,
    batch_size=32,
    validation_split=0.2,
    verbose=1
)

# Evaluar
loss, acc = modelo.evaluate(X_test, y_test)
print(f"Test accuracy: {acc:.2%}")

Hiperparámetros clave:

Hiperparámetro Qué controla Valores típicos
epochs Cuántas veces ve el dataset completo 10-1000
batch_size Cuántos ejemplos por actualización de pesos 32, 64, 128
learning_rate Tamaño del paso del gradiente 0.001 (Adam default)
Neuronas por capa Capacidad del modelo 32, 64, 128, 256...
Número de capas Profundidad 1-10 para tabular data

3.3 Backpropagation y descenso de gradiente

💡 Intuición

El entrenamiento tiene dos fases:

  1. Forward pass: Pasar los datos hacia adelante, obtener la predicción y calcular el error.
  2. Backward pass (backpropagation): Propagar el error hacia atrás, calculando cuánto contribuyó cada peso al error, y ajustar los pesos para reducirlo.

Es como ajustar una receta: probás el plato (forward pass), notás que está muy salado, y ajustás la cantidad de sal para la próxima vez (backward pass).

📐 Fundamento

Función de pérdida (loss function):

Mide qué tan equivocada está la predicción:

J(θ)=1ni=1n[yilogy^i+(1yi)log(1y^i)]J(\theta) = -\frac{1}{n}\sum_{i=1}^n [y_i \log \hat{y}_i + (1-y_i)\log(1-\hat{y}_i)]

(Binary Crossentropy para clasificación binaria)

Descenso de gradiente:

θθαθJ(θ)\theta \leftarrow \theta - \alpha \nabla_\theta J(\theta)

donde α\alpha es el learning rate y θJ\nabla_\theta J es el gradiente de la pérdida respecto a los parámetros.

Variantes del descenso de gradiente:

Variante Batch size Pros Cons
Batch GD Todo el dataset Gradiente preciso Lento, mucha memoria
Stochastic GD (SGD) 1 ejemplo Rápido, puede escapar mínimos locales Muy ruidoso
Mini-batch GD 32-512 Balance entre los dos Requiere ajustar batch_size

Optimizadores modernos:

# Adam: adapta el learning rate por parámetro — el más usado
optimizer = keras.optimizers.Adam(learning_rate=0.001)

# SGD con momentum
optimizer = keras.optimizers.SGD(learning_rate=0.01, momentum=0.9)

# RMSprop: bueno para RNNs
optimizer = keras.optimizers.RMSprop(learning_rate=0.001)

El problema del gradiente que desaparece (vanishing gradient):

En redes muy profundas con sigmoid/tanh, los gradientes se hacen exponencialmente pequeños al propagarse hacia atrás — las primeras capas aprenden muy lento.

Soluciones:

  • Usar ReLU (gradiente = 1 para valores positivos, no desaparece).
  • Batch Normalization.
  • Arquitecturas residuales (ResNet).

3.4 Redes Convolucionales (CNN) — conceptual

💡 Intuición

Una MLP normal trata una imagen de 28×28 píxeles como 784 números independientes — pierde la estructura espacial. Un borde horizontal en la esquina inferior izquierda es el mismo patrón que en la esquina superior derecha, pero la MLP tiene que aprender ambos por separado.

Las CNNs aplican el mismo filtro (kernel) a toda la imagen — detectan el patrón en cualquier posición. Son al procesamiento de imágenes lo que los índices son a las bases de datos.

📐 Fundamento

Capas de una CNN:

Imagen → [Conv + ReLU] → [Pooling] → [Conv + ReLU] → [Pooling] → [Flatten] → [Dense] → Salida

Convolución: aplica un filtro (ej: 3×3) deslizándolo sobre la imagen:

# Ejemplo conceptual de una convolución 1D
señal = [1, 2, 3, 4, 5, 6, 7]
filtro = [1, 0, -1]  # detector de bordes

resultado = []
for i in range(len(señal) - len(filtro) + 1):
    conv = sum(señal[i+j] * filtro[j] for j in range(len(filtro)))
    resultado.append(conv)
# resultado: [-2, -2, -2, -2, -2]

Pooling: reduce la dimensión tomando el máximo (MaxPooling) o promedio de cada región:

Antes de pooling (4×4):     Después de MaxPool 2×2:
1 3 2 4                     3 4
5 6 1 2    ─MaxPool──→      6 3
8 2 3 1    (2×2)
4 1 0 3

CNN simple en Keras:

from tensorflow.keras import layers, models

cnn = models.Sequential([
    # Capa convolucional: 32 filtros 3×3
    layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)),
    layers.MaxPooling2D((2,2)),
    
    layers.Conv2D(64, (3,3), activation='relu'),
    layers.MaxPooling2D((2,2)),
    
    layers.Flatten(),          # convertir a vector 1D
    layers.Dense(64, activation='relu'),
    layers.Dense(10, activation='softmax')  # 10 clases
])

cnn.compile(optimizer='adam', loss='sparse_categorical_crossentropy', 
            metrics=['accuracy'])

Arquitecturas famosas:

  • LeNet-5 (1998): Primera CNN exitosa — reconocimiento de dígitos.
  • AlexNet (2012): Ganó ImageNet con 15.3% de error. Inicio del deep learning moderno.
  • ResNet (2015): 152 capas con conexiones residuales. Superó a humanos en ImageNet.
  • EfficientNet (2019): Estado del arte en efficiency/accuracy.

Transfer learning:

# Usar ResNet50 preentrenada en ImageNet, reemplazar solo la capa de salida
base_model = keras.applications.ResNet50(
    weights='imagenet', include_top=False, input_shape=(224,224,3)
)
base_model.trainable = False  # congelar pesos preentrenados

x = base_model.output
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(256, activation='relu')(x)
salida = layers.Dense(5, activation='softmax')(x)  # 5 clases propias

modelo = keras.Model(inputs=base_model.input, outputs=salida)

🛠️ En la práctica

La Esquina: red neuronal para predecir demanda horaria

El sistema predice cuántos pedidos habrá en la próxima hora para gestionar el personal.

import numpy as np
from tensorflow import keras

# Features: hora del día, día semana, si es feriado, temperatura, pedidos última hora
X_train = np.random.rand(1000, 5)  # 1000 días de historial
y_train = np.random.randint(5, 50, 1000)  # pedidos por hora

# Red para regresión
modelo = keras.Sequential([
    keras.layers.Dense(64, activation='relu', input_shape=(5,)),
    keras.layers.Dropout(0.2),   # regularización: apaga 20% de neuronas por iter
    keras.layers.Dense(32, activation='relu'),
    keras.layers.Dense(1)         # salida: número de pedidos (sin activación)
])

modelo.compile(
    optimizer='adam',
    loss='mean_squared_error',
    metrics=['mae']  # Mean Absolute Error
)

# Early stopping: parar cuando la validación deje de mejorar
callback = keras.callbacks.EarlyStopping(
    patience=10, restore_best_weights=True
)

modelo.fit(X_train, y_train, epochs=200, validation_split=0.2, 
           callbacks=[callback], verbose=0)
print("Entrenamiento completo")

3.5 Ejercicios

✏️ Ejercicio 3.1 — Arquitectura de red

Diseñá la arquitectura de una red neuronal (número de capas, neuronas por capa, función de activación) para cada problema:

a. Clasificar si una reseña de restaurante es positiva o negativa (texto → binario). b. Predecir el precio de un platillo basándose en 8 ingredientes (8 números → 1 número). c. Reconocer cuál de 26 letras del alfabeto aparece en una imagen de 32×32 píxeles.

✏️ Ejercicio 3.2 — Interpretar curvas de entrenamiento

Una red neuronal muestra estas curvas después de 100 epochs:

  • Loss de entrenamiento: baja de 0.9 a 0.05 suavemente.
  • Loss de validación: baja de 0.9 a 0.35, luego sube a 0.65 en la epoch 50.

a. ¿Qué fenómeno muestra esta curva? b. ¿En qué epoch aproximadamente debería haberse parado el entrenamiento? c. Propone tres técnicas para mejorar la generalización.


3.6 Para profundizar


Definiciones nuevas: neurona artificial, función de activación, ReLU, sigmoid, softmax, MLP, forward pass, backpropagation, descenso de gradiente, batch, learning rate, Adam, vanishing gradient, CNN, convolución, kernel, pooling, transfer learning, dropout, regularización L2, early stopping.