Optimización Numérica en Redes Neuronales: Loss Functions y Stochastic Gradient Descent

Análisis técnico de la minimización del error en modelos supervisados mediante funciones de pérdida. Se aborda la formulación matemática de Cross-Entropy, la mecánica de Gradient Descent y su implementación vectorizada en Python para garantizar convergencia en superficies no convexas.

El entrenamiento de redes neuronales profundas (Deep Learning) se reduce, en última instancia, a un problema de optimización numérica. El objetivo no es que la máquina "aprenda" en un sentido cognitivo, sino encontrar un conjunto de parámetros $\theta$ (pesos y sesgos) que minimice una función escalar, conocida como función de pérdida (Loss Function).

En el estado del arte actual (2016), arquitecturas como ResNet o VGG han demostrado que la profundidad es crítica, pero esto introduce complejidades en la superficie de error. La elección incorrecta de la función de pérdida o del algoritmo de optimización provoca problemas de vanishing gradients o convergencia en mínimos locales subóptimos. Este artículo disecciona la interacción entre la medición del error (Cross-Entropy) y el mecanismo de actualización (Gradient Descent).


Fundamentos matemáticos

Para un modelo parametrizado por $\theta$, dado un dataset de entrada $X$ y etiquetas $Y$, definimos la función de costo $J(\theta)$ como el promedio de la pérdida sobre todo el conjunto de entrenamiento.

Cross-Entropy Loss

En problemas de clasificación multiclase, la Categorical Cross-Entropy es el estándar debido a sus propiedades probabilísticas derivadas de la estimación de máxima verosimilitud. Dada una distribución real $y$ (one-hot encoded) y una predicción $\hat{y}$ (salida de una función Softmax), la pérdida para una muestra $i$ se define como:

$$L(\hat{y}_i, y_i) = - \sum_{k=1}^{K} y_{i,k} \log(\hat{y}_{i,k})$$

Donde $K$ es el número de clases.

Gradient Descent

El algoritmo de Gradient Descent actualiza los parámetros moviéndose en la dirección opuesta al gradiente de la función de costo con respecto a los parámetros. La regla de actualización básica es:

$$\theta_{t+1} = \theta_t - \eta \nabla_\theta J(\theta_t)$$

Donde:

  • $\eta$ es el learning rate (hiperparámetro escalar).
  • $\nabla_\theta J(\theta_t)$ es el vector gradiente que indica la dirección de mayor ascenso del error.

En la práctica, calcular el gradiente sobre todo el dataset es computacionalmente inviable para conjuntos grandes (como ImageNet), por lo que se utiliza Mini-batch Stochastic Gradient Descent (SGD), aproximando el gradiente sobre un subconjunto $B$:

$$\nabla_\theta J(\theta; X, Y) \approx \frac{1}{|B|} \sum_{i \in B} \nabla_\theta L(f(x_i; \theta), y_i)$$


Implementación práctica

A continuación, se presenta una implementación vectorizada utilizando NumPy. Se asume una arquitectura simple donde la salida y_pred proviene de una capa Softmax.

import numpy as np

def categorical_cross_entropy(y_true, y_pred, epsilon=1e-12):
    """
    Calcula la Categorical Cross-Entropy.
    Args:
        y_true: Array NumPy con etiquetas one-hot (N, K).
        y_pred: Array NumPy con probabilidades predichas (N, K).
        epsilon: Pequeño valor para evitar log(0).
    Returns:
        float: Valor promedio de la pérdida.
    """
    # Clip para estabilidad numérica
    y_pred = np.clip(y_pred, epsilon, 1. - epsilon)
    
    # Cálculo de la fórmula
    # N es el número de muestras
    N = y_pred.shape[0]
    
    # Suma sobre clases y promedio sobre batch
    ce_loss = -np.sum(y_true * np.log(y_pred)) / N
    return ce_loss

def sgd_update(weights, gradients, learning_rate):
    """
    Aplica una actualización de Stochastic Gradient Descent.
    Args:
        weights: Parámetros actuales del modelo (W).
        gradients: Gradiente de la Loss respecto a W (dL/dW).
        learning_rate: Tasa de aprendizaje (eta).
    Returns:
        weights_new: Parámetros actualizados.
    """
    weights_new = weights - learning_rate * gradients
    return weights_new

# Ejemplo de uso simulado
# Batch de 3 muestras, 2 clases
y_real = np.array([[1, 0], [0, 1], [1, 0]])
# Predicciones de la red (output de Softmax)
y_hat = np.array([[0.9, 0.1], [0.3, 0.7], [0.8, 0.2]])

# 1. Calcular Loss
loss = categorical_cross_entropy(y_real, y_hat)
print("Loss actual: {:.4f}".format(loss))

# 2. Simulación de gradiente (en práctica se obtiene via backprop)
# Supongamos un gradiente calculado
dummy_grads = np.array([[0.01, -0.01], [-0.02, 0.02]])
weights = np.random.randn(2, 2)

# 3. Actualizar pesos
new_weights = sgd_update(weights, dummy_grads, learning_rate=0.01)


Análisis de comportamiento

Estabilidad Numérica

El uso de logaritmos en Cross-Entropy es susceptible a errores si la red predice una probabilidad de 0 exacto para la clase correcta ($\log(0) = -\infty$). Es mandatorio implementar clipping (como se ve en el código, usando epsilon) o utilizar formulaciones que combinen Softmax y Cross-Entropy en una sola operación (como softmax_cross_entropy_with_logits en TensorFlow) para aprovechar la estabilidad del LogSumExp.

Dinámica del Gradiente

En redes profundas, la magnitud del gradiente puede variar drásticamente entre capas.

  • Vanishing Gradient: Si se usan activaciones como Sigmoid o Tanh, los gradientes tienden a cero en las capas inferiores, impidiendo el aprendizaje. ReLU mitiga esto significativamente.
  • Saddle Points: En espacios de alta dimensionalidad, es más común encontrar puntos de silla que mínimos locales puros. SGD con Momentum ayuda a escapar de estas zonas donde el gradiente es cero pero no es un óptimo.

Comparativas o referencias técnicas

MSE vs Cross-Entropy para Clasificación

Aunque el Error Cuadrático Medio (MSE) es estándar para regresión, su uso en clasificación con salidas de probabilidad es ineficiente.

Métrica Comportamiento en Clasificación Convexidad
MSE Penaliza errores de forma cuadrática. Gradientes pequeños cuando la predicción está muy equivocada (saturación). No convexa (con NN).
Cross-Entropy Penaliza logarítmicamente. Gradiente fuerte cuando la predicción es incorrecta y confiada. Convexa (con respecto a los logits en Softmax).

Empíricamente, Cross-Entropy converge más rápido en tareas de clasificación (ImageNet, CIFAR-10) debido a que contrarresta la saturación de las unidades de salida.


Limitaciones y casos donde no conviene usarlo

  • Learning Rate Fijo: El SGD puro es muy sensible al valor de $\eta$. Si es muy alto, el modelo diverge; si es muy bajo, el entrenamiento es excesivamente lento. Optimizadores adaptativos como Adam (2014) o RMSprop suelen ser preferibles en fases iniciales de experimentación, aunque SGD con Momentum sigue logrando mejores generalizaciones finales en muchos papers recientes.
  • Ruido en el Batch: Al estimar el gradiente sobre un mini-batch pequeño, la dirección de actualización es ruidosa. Esto funciona como regularización implícita, pero impide una convergencia precisa hacia el mínimo exacto a menos que se use un learning rate decay.
  • Outliers: Cross-Entropy penaliza fuertemente las muestras mal clasificadas con alta confianza. Si el dataset contiene etiquetas incorrectas (noisy labels), el modelo intentará ajustar esos outliers, degradando la generalización.