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 , las multiplica por pesos , suma, agrega un sesgo , y pasa el resultado por una función de activación no lineal.
📐 Fundamento
Funciones de activación:
| Función | Fórmula | Rango | Uso típico |
|---|---|---|---|
| Sigmoid | (0,1) | Clasificación binaria (capa de salida) | |
| Tanh | (-1,1) | Capas ocultas (obsoleto) | |
| ReLU | [0,∞) | Capas ocultas (estándar actual) | |
| Leaky ReLU | (-∞,∞) | Cuando ReLU "muere" | |
| Softmax | (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:
- Forward pass: Pasar los datos hacia adelante, obtener la predicción y calcular el error.
- 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:
(Binary Crossentropy para clasificación binaria)
Descenso de gradiente:
donde es el learning rate y 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.
Solución
a. Texto → binario: Requiere procesamiento de texto (embedding + LSTM o Transformer). Para este curso, simplificado: vectorizar texto (TF-IDF, 500 features) → Dense(64, relu) → Dense(32, relu) → Dense(1, sigmoid). Loss: binary_crossentropy.
b. 8 números → 1 número: Dense(32, relu, input=8) → Dense(16, relu) → Dense(1, sin activación). Loss: mean_squared_error. Arquitectura pequeña porque el problema es simple.
c. Imagen 32×32 → 26 clases: Conv2D(32, 3×3, relu) → MaxPool(2×2) → Conv2D(64, 3×3, relu) → MaxPool(2×2) → Flatten → Dense(128, relu) → Dense(26, softmax). Loss: sparse_categorical_crossentropy.
✏️ 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.
Solución
a. Overfitting. El modelo aprende el training set muy bien (loss 0.05) pero empieza a "olvidar" cómo generalizar alrededor de epoch 50 (validación empieza a subir).
b. Alrededor de la epoch 50 — cuando la validación loss era mínima (~0.35). Esto es exactamente lo que hace EarlyStopping(patience=10, restore_best_weights=True).
c. Técnicas para mejorar generalización:
- Dropout (
layers.Dropout(0.3)) — apaga aleatoriamente neuronas durante el entrenamiento. - Regularización L2 (
kernel_regularizer=keras.regularizers.l2(0.01)) — penaliza pesos grandes. - Data augmentation — si es problema de imágenes, generar variantes (rotaciones, flips). Para datos tabulares: oversampling (SMOTE) si hay desbalance de clases.
3.6 Para profundizar
- Géron, Hands-On Machine Learning, cap. 10-14 — redes neuronales y CNNs con Keras.
- Nielsen, Neural Networks and Deep Learning (neuralnetworksanddeeplearning.com) — gratis online, matemática clara.
- fast.ai — cursos prácticos de deep learning.
- Siguiente: Aprendizaje no supervisado.
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.