Arquitecturas Densas (MLP): Fundamentos Matemáticos y Propagación

Análisis de la arquitectura del Perceptrón Multicapa (MLP) como aproximador universal de funciones. Se aborda la formulación matricial del forward pass, el rol crítico de las funciones de activación no lineales y la dinámica de optimización.

Contexto técnico

El Perceptrón Multicapa (MLP) representa la arquitectura fundacional del aprendizaje profundo. Aunque en el estado del arte actual las redes convolucionales (CNN) dominan en visión por computador (AlexNet, VGG, ResNet) y las redes recurrentes (LSTM) en procesamiento de secuencias, el MLP sigue siendo el bloque terminal esencial para la clasificación y regresión.

El problema que aborda es la no linealidad en la separación de datos. A diferencia de un perceptrón simple o una regresión logística, capaz de resolver solo problemas linealmente separables, el MLP introduce capas ocultas y funciones de activación no lineales. Esto permite transformar el espacio de entrada en una representación donde las clases sean separables.

En el ecosistema actual, frameworks como Theano, Caffe y el reciente TensorFlow utilizan estas estructuras densas principalmente en las capas finales ("fully connected layers") para mapear las características extraídas hacia las probabilidades de clase.


Fundamentos matemáticos

Un MLP se define como un grafo acíclico dirigido compuesto por capas de neuronas. Matemáticamente, es una composición de funciones lineales ponderadas seguidas de no linealidades puntuales.

Para una capa $l$, la pre-activación $z^{(l)}$ se calcula como:

$$z^{(l)} = W^{(l)} a^{(l-1)} + b^{(l)}$$

Donde $W^{(l)}$ es la matriz de pesos, $b^{(l)}$ el vector de sesgo (bias) y $a^{(l-1)}$ la salida de la capa anterior. La salida activada $a^{(l)}$ se define mediante una función de activación $\sigma$:

$$a^{(l)} = \sigma(z^{(l)})$$

El entrenamiento se realiza minimizando una función de coste $J(\theta)$, típicamente Cross-Entropy para clasificación. El algoritmo de optimización estándar es el Descenso de Gradiente Estocástico (SGD), que requiere el cálculo de gradientes mediante Backpropagation.

El error en la capa de salida $\delta^{(L)}$ se propaga hacia atrás. Para una capa oculta $l$, el error se formula usando la regla de la cadena:

$$\delta^{(l)} = \left( (W^{(l+1)})^T \delta^{(l+1)} \right) \odot \sigma'(z^{(l)})$$

Donde $\odot$ representa el producto de Hadamard (elemento a elemento). Finalmente, la actualización de los pesos para una tasa de aprendizaje $\eta$ es:

$$W^{(l)} \leftarrow W^{(l)} - \eta \frac{\partial J}{\partial W^{(l)}} = W^{(l)} - \eta (\delta^{(l)} (a^{(l-1)})^T)$$

La propagación del error hacia atrás sigue el siguiente patrón:


Implementación práctica

A continuación, se presenta una implementación vectorizada en Python (NumPy) de un MLP de una sola capa oculta para clasificación. Se evita el uso de frameworks de alto nivel para exponer la mecánica del Backpropagation.

import numpy as np

class SimpleMLP:
    def __init__(self, input_size, hidden_size, output_size):
        # Inicialización de He/Xavier para estabilidad numérica
        self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2. / input_size)
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2. / hidden_size)
        self.b2 = np.zeros((1, output_size))

    def relu(self, z):
        return np.maximum(0, z)

    def softmax(self, z):
        exp_z = np.exp(z - np.max(z, axis=1, keepdims=True)) # Estabilidad numérica
        return exp_z / np.sum(exp_z, axis=1, keepdims=True)

    def forward(self, X):
        self.z1 = np.dot(X, self.W1) + self.b1
        self.a1 = self.relu(self.z1)
        self.z2 = np.dot(self.a1, self.W2) + self.b2
        self.probs = self.softmax(self.z2)
        return self.probs

    def backward(self, X, y, learning_rate=1e-3):
        m = X.shape[0]
        
        # Gradiente en la salida (Cross-Entropy + Softmax)
        dz2 = self.probs
        dz2[range(m), y] -= 1
        dz2 /= m

        # Backprop a W2 y b2
        dW2 = np.dot(self.a1.T, dz2)
        db2 = np.sum(dz2, axis=0, keepdims=True)

        # Backprop a la capa oculta (derivada de ReLU)
        da1 = np.dot(dz2, self.W2.T)
        dz1 = da1 * (self.z1 > 0)

        # Backprop a W1 y b1
        dW1 = np.dot(X.T, dz1)
        db1 = np.sum(dz1, axis=0, keepdims=True)

        # Actualización de parámetros (SGD)
        self.W1 -= learning_rate * dW1
        self.b1 -= learning_rate * db1
        self.W2 -= learning_rate * dW2
        self.b2 -= learning_rate * db2


Análisis de comportamiento

Durante el entrenamiento de arquitecturas densas profundas, emergen comportamientos críticos que determinan la convergencia:

  1. Vanishing Gradient (Desvanecimiento del Gradiente): El uso de la función de activación Sigmoide ($\sigma(z) = \frac{1}{1+e^{-z}}$) comprime las salidas entre 0 y 1. En redes profundas, la multiplicación sucesiva de derivadas locales (máximo 0.25) hace que el gradiente tienda a cero en las primeras capas. La adopción de ReLU ($f(x) = \max(0, x)$) en 2012-2015 ha mitigado este problema al mantener un gradiente constante de 1 para valores positivos. Esta comparativa visual muestra por qué ReLU mitiga el problema del desvanecimiento del gradiente:
  1. Sobreajuste (Overfitting): Los MLPs son propensos a memorizar el ruido del set de entrenamiento debido a su gran cantidad de parámetros ($N \times M$ conexiones entre capas). Técnicas como Dropout (Srivastava et al., 2014) son obligatorias en la práctica para regularizar la red, apagando neuronas aleatoriamente durante el entrenamiento.
  2. Sensibilidad a la Inicialización: Iniciar los pesos con una distribución normal estándar sin escalar provoca que las activaciones exploten o se desvanezcan. Métodos como la inicialización de Glorot (Xavier) o He son necesarios para mantener la varianza de las activaciones a través de las capas.

Comparativas o referencias técnicas

Al comparar MLPs con algoritmos clásicos y arquitecturas modernas (al corte de 2016):

Métrica SVM (Kernel RBF) MLP (Denso) CNN (Convolucional)
Feature Engineering Manual / Semi-auto Automático Automático (Espacial)
Parámetros Depende de Support Vectors Alto ($O(N^2)$ por capa) Bajo (Weights sharing)
Inferencia Rápida Media (MatMul intensivo) Lenta (depende de profundidad)
Interpretabilidad Media Caja negra Mapas de activación

En datasets como MNIST, un MLP bien ajustado (2-3 capas ocultas, ReLU, Dropout) alcanza fácilmente un accuracy >98%, superando a los métodos lineales, pero quedando por debajo de las CNNs simples (>99%) que aprovechan la correlación espacial de los píxeles.


Limitaciones y casos donde no conviene usarlo

A pesar de su flexibilidad teórica, las arquitecturas puramente densas presentan limitaciones severas en entornos de alta dimensionalidad:

  • Pérdida de estructura espacial/temporal: Al "aplanar" (flatten) una imagen o serie de tiempo en un vector de entrada, se destruye la topología de los datos. El modelo debe volver a aprender relaciones de proximidad que una convolución asume por defecto.
  • Explosión de parámetros: Para una imagen de entrada pequeña de $200 \times 200$ píxeles (40,000 features) y una primera capa oculta de 1,000 neuronas, la matriz de pesos $W^{(1)}$ contiene 40 millones de parámetros. Esto hace que el entrenamiento sea computacionalmente costoso y aumenta el riesgo de overfitting si no se dispone de un dataset masivo.
  • Ineficiencia en inferencia: Al ser fully connected, cada predicción requiere multiplicar matrices densas completas, desperdiciando cómputo en conexiones que podrían ser irrelevantes (sparsidad).

Para datos no estructurados (imágenes, audio), el MLP debe limitarse a actuar como clasificador final sobre features extraídos por otras arquitecturas (CNN/RNN).