- Entrenamiento y Retropropagación
Entrenamiento y Retropropagación
Odisea Algorítmica: De la Regresión al Aprendizaje Profundo — En esta lección desentrañamos el corazón del aprendizaje supervisado: el algoritmo de retropropagación (backpropagation) y su simbiosis con el gradiente descendente. Comprenderás cómo una red neuronal ajusta sus pesos internos para minimizar el error, evitando trampas como el sobreajuste. Al final, implementarás desde cero el entrenamiento de una red simple en Python, aplicada a la predicción de churn.
Objetivo: Ser capaz de explicar y codificar el ciclo completo de entrenamiento: forward pass → cálculo de pérdida → backward pass (gradientes) → actualización de pesos. Dominarás los conceptos de tasa de aprendizaje, regularización y diagnóstico de convergencia.
1. El algoritmo de retropropagación (backpropagation)
La retropropagación es el mecanismo que permite que el error fluya hacia atrás a través de la red, desde la capa de salida hasta la primera capa oculta. Se basa en la regla de la cadena del cálculo diferencial para obtener el gradiente de la función de pérdida respecto a cada peso.
- Forward pass: los datos de entrada viajan hacia adelante, capa por capa, hasta producir una salida.
- Cálculo de pérdida: se compara la salida predicha con la etiqueta real usando una función de pérdida (MSE, entropía cruzada).
- Backward pass: se calcula el gradiente de la pérdida con respecto a cada peso, desde la salida hacia la entrada.
- Actualización: los pesos se ajustan en la dirección opuesta al gradiente (descenso) para reducir la pérdida.
Sin la retropropagación, las redes multicapa serían inentrenables; este algoritmo es el pilar del aprendizaje profundo moderno.
2. Gradiente descendente, tasa de aprendizaje y convergencia
El gradiente descendente (Gradient Descent) es el optimizador que usa la información del gradiente para actualizar los parámetros. La tasa de aprendizaje (learning rate) controla el tamaño del paso:
- Tasa de aprendizaje pequeña: convergencia lenta, riesgo de estancarse en un mínimo local.
- Tasa de aprendizaje grande: puede divergir, oscilar o no converger.
- Convergencia: cuando la pérdida deja de disminuir significativamente (tolerancia) o se alcanza un número máximo de épocas.
En la práctica se usan variantes como SGD, Adam o RMSprop que adaptan la tasa de aprendizaje durante el entrenamiento.
3. Función de pérdida: MSE y entropía cruzada
La función de pérdida cuantifica qué tan lejos está la predicción del valor real. La elección depende del tipo de problema:
| Función | Uso típico | Fórmula conceptual | Propiedad clave |
|---|---|---|---|
| MSE (Error Cuadrático Medio) | Regresión | 1/n Σ (y_pred − y_true)² | Penaliza errores grandes cuadráticamente |
| Entropía Cruzada (Binary/ Categorical) | Clasificación binaria / multiclase | − Σ y_true · log(y_pred) | Ideal para probabilidades (salida softmax/sigmoide) |
En el caso de la red de churn (clasificación binaria), usaremos entropía cruzada binaria como función de pérdida.
4. Overfitting y regularización (dropout, L2)
El sobreajuste ocurre cuando la red memoriza los datos de entrenamiento y falla en generalizar a nuevos datos. Estrategias de regularización:
- Dropout: durante el entrenamiento se “apagan” aleatoriamente un porcentaje de neuronas (ej. 20-50%), forzando a la red a aprender representaciones robustas.
- Regularización L2 (weight decay): añade un término de penalización a la función de pérdida proporcional a la magnitud de los pesos (norma L2).
- Early stopping: detener el entrenamiento cuando la pérdida en validación deja de mejorar.
- Aumento de datos: aplicar transformaciones a los datos de entrada (menos común en tabular, pero posible).
En nuestro ejemplo de churn, incluiremos una capa Dropout y regularización L2 en los pesos para mitigar el sobreajuste.
5. Implementación desde cero en Python
A continuación, un esqueleto funcional de una red neuronal con una capa oculta, entrenada con retropropagación y gradiente descendente. Se incluye dropout y regularización L2.
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def binary_cross_entropy(y_true, y_pred):
# estabilidad numérica
eps = 1e-12
y_pred = np.clip(y_pred, eps, 1 - eps)
return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
class RedSimple:
def __init__(self, input_dim, hidden_dim, lr=0.01, l2_lambda=0.001, dropout_rate=0.2):
self.lr = lr
self.l2_lambda = l2_lambda
self.dropout_rate = dropout_rate
# inicialización He
self.W1 = np.random.randn(input_dim, hidden_dim) * np.sqrt(2.0 / input_dim)
self.b1 = np.zeros((1, hidden_dim))
self.W2 = np.random.randn(hidden_dim, 1) * np.sqrt(2.0 / hidden_dim)
self.b2 = np.zeros((1, 1))
def forward(self, X, training=True):
self.z1 = X @ self.W1 + self.b1
self.a1 = sigmoid(self.z1)
# dropout (solo entrenamiento)
if training and self.dropout_rate > 0:
self.mask = np.random.binomial(1, 1 - self.dropout_rate, size=self.a1.shape)
self.a1 *= self.mask / (1 - self.dropout_rate) # inverted dropout
self.z2 = self.a1 @ self.W2 + self.b2
self.a2 = sigmoid(self.z2)
return self.a2
def backward(self, X, y, output):
m = X.shape[0]
# gradiente capa de salida
dz2 = output - y.reshape(-1, 1)
dW2 = (self.a1.T @ dz2) / m + self.l2_lambda * self.W2 # regularización L2
db2 = np.sum(dz2, axis=0, keepdims=True) / m
# gradiente capa oculta
da1 = dz2 @ self.W2.T
# aplicar máscara de dropout (la misma del forward)
if self.dropout_rate > 0:
da1 *= self.mask / (1 - self.dropout_rate)
dz1 = da1 * (self.a1 * (1 - self.a1)) # derivada sigmoide
dW1 = (X.T @ dz1) / m + self.l2_lambda * self.W1
db1 = np.sum(dz1, axis=0, keepdims=True) / m
# actualización de parámetros
self.W2 -= self.lr * dW2
self.b2 -= self.lr * db2
self.W1 -= self.lr * dW1
self.b1 -= self.lr * db1
def train(self, X, y, epochs=1000, verbose=True):
for epoch in range(epochs):
output = self.forward(X, training=True)
loss = binary_cross_entropy(y, output)
self.backward(X, y, output)
if verbose and epoch % 200 == 0:
print(f'Época {epoch}, pérdida: {loss:.6f}')
# ejemplo de uso (datos sintéticos)
np.random.seed(42)
X = np.random.randn(200, 10) # 200 ejemplos, 10 características
y = (X[:, 0] + X[:, 1] > 0).astype(float) # variable binaria
red = RedSimple(input_dim=10, hidden_dim=8, lr=0.1)
red.train(X, y, epochs=1000, verbose=True)
6. Ejemplo: optimización de la red de churn
Supongamos un conjunto de datos de clientes con características como antigüedad, número de quejas, uso del servicio, etc. La red aprenderá a predecir la probabilidad de cancelación (churn). Los pasos son:
- Preprocesar datos (normalizar, codificar variables categóricas).
- Dividir en entrenamiento (80%) y validación (20%).
- Entrenar la red con los hiperparámetros anteriores, ajustando dropout y L2 según el rendimiento en validación.
- Evaluar: precisión, recall, AUC-ROC.
La implementación de la clase RedSimple es directamente aplicable a este problema, solo se debe adaptar la dimensionalidad de entrada (número de características) y, opcionalmente, agregar más capas ocultas y dropout.
7. Preparación para arquitecturas más complejas
Una vez que domines la retropropagación y la regularización, estarás listo para abordar:
- Redes convolucionales (CNN) para imágenes.
- Redes recurrentes (RNN, LSTM) para secuencias.
- Redes residuales (ResNet) y transformers.
- Optimizadores avanzados (Adam, RMSprop).
- Inicialización de pesos, normalización por lotes (batch normalization).
Todo se sustenta en los fundamentos de esta lección: el gradiente descendente y la retropropagación son el motor universal del aprendizaje profundo.
Lección parte de “Odisea Algorítmica: De la Regresión al Aprendizaje Profundo”. Para afianzar conceptos, implementa la clase con diferentes tasas de aprendizaje y observa la convergencia. El código completo y datasets están disponibles en el repositorio del curso.
No hay comentarios por ahora.
Compartir este contenido
Compartir enlace
Compartir en redes sociales
Compartir por correo electrónico
Please iniciar sesión para compartir esto Artículo por correo electrónico.