Redes Completamente Conectadas (Modularidad)
El diseño de redes neuronales modernas se basa en la programación modular. Cada capa se define con dos funciones principales: una para la propagación hacia adelente (forward) y otra para la propagación hacia atrás (backward).
- Forward: Recibe la entrada y devuelve la salida junto con un "cache" que almacena información necesaria para el gradiente.
- Backward: Recibe el gradiente de la capa superior (upstream) y calcula los gradientes respecto a los pesos y a la entrada utilizando la regla de la cadena.
# Ejemplo de una capa ReLU simplificada
def relu_forward(entrada):
salida = np.maximum(0, entrada)
cache = entrada
return salida, cache
def relu_backward(grad_salida, cache):
entrada = cache
# El gradiente es 1 para valores > 0, de lo contrario es 0
grad_entrada = grad_salida * (entrada > 0)
return grad_entrada
Algoritmos de Optimización Avanzados
Más allá del descenso de gradiente estocástico (SGD) básico, existen métodos que adaptan la tasa de aprendizaje o utilizan el momento para acelerar la convergencia.
Momentum SGD
Acumula un promedio de los gradientes pasados para suavizar las actualizaciones y superar mínimos locales o zonas de baja pendiente.
# v representa la velocidad acumulada
v = coeficiente_momento * v - tasa_aprendizaje * grad_pesos
pesos += v
RMSProp
Divide la tasa de aprendizaje por una media móvil de las magnitudes de los gradientes recientes para normalizar el paso de actualización.
cache_grad = decaimiento * cache_grad + (1 - decaimiento) * (grad_pesos**2)
pesos -= tasa_aprendizaje * grad_pesos / (np.sqrt(cache_grad) + eps)
Adam (Adaptive Moment Estimation)
Combina las ideas de Momentum y RMSProp, incluyendo una corrección de sesgo para las estimaciones iniciales.
m = beta1 * m + (1 - beta1) * grad_pesos # Primer momento (media)
v = beta2 * v + (1 - beta2) * (grad_pesos**2) # Segundo momento (varianza)
# Corrección de sesgo
m_corregido = m / (1 - beta1**t)
v_corregido = v / (1 - beta2**t)
actualizacion = tasa_aprendizaje * m_corregido / (np.sqrt(v_corregido) + eps)
pesos -= actualizacion
Funciones de Pérdida: SVM y Softmax
Multiclass SVM (Hinge Loss)
Busca que la puntuación de la clase correcta sea mayor que las incorrectas por un margen determinado.
def svm_loss(puntuaciones, etiquetas):
num_ejemplos = puntuaciones.shape[0]
puntuacion_correcta = puntuaciones[np.arange(num_ejemplos), etiquetas][:, np.newaxis]
margenes = np.maximum(0, puntuaciones - puntuacion_correcta + 1.0)
margenes[np.arange(num_ejemplos), etiquetas] = 0
loss = np.sum(margenes) / num_ejemplos
# Cálculo del gradiente
d_puntuaciones = (margenes > 0).astype(float)
conteo_margen = np.sum(d_puntuaciones, axis=1)
d_puntuaciones[np.arange(num_ejemplos), etiquetas] -= conteo_margen
d_puntuaciones /= num_ejemplos
return loss, d_puntuaciones
Softmax (Entropía Cruzada)
Interpreta las puntuaciones como probabilidades logarítmicas no normalizadas.
def softmax_loss(x, y):
# Truco numérico restando el máximo
logits_estables = x - np.max(x, axis=1, keepdims=True)
exp_sum = np.sum(np.exp(logits_estables), axis=1, keepdims=True)
log_probabilidades = logits_estables - np.log(exp_sum)
probabilidades = np.exp(log_probabilidades)
n = x.shape[0]
loss = -np.sum(log_probabilidades[np.arange(n), y]) / n
dx = probabilidades.copy()
dx[np.arange(n), y] -= 1
dx /= n
return loss, dx
Batch Normalization
Esta técnica mitiga el problema del desvanecimiento del gradiente al normalizar las activaciones de una capa para que tengan media cero y varianza unitaria dentro de un mini-batch. Posteriormente, se escalan y desplazan con parámetros aprendibles ($\gamma$ y $\beta$).
Durante la inferencia, se utilizan promedios móviles calculados durante el entrenamiento para asegurar que la predición sea consistente independientemente del tamaño del batch.
Dropout
Es una técnica de regularización potente. En Inverted Dropout, durante el entrenamiento se desactivan neuronas aleatoriamente con probabilidad $p$ y se escalan las activaciones restantes por $1/(1-p)$ para mantener constante el valor esperado de la salida. Esto elimina la necesidad de modificar nada durante la fase de prueba.
Redes Neuronales Convolucionales (CNN)
A diferencia de las redes densas, las CNN preservan la estructura espacial de los datos. Sus componentes clave son:
- Convolución: Aplica filtros (kernels) que se deslizan sobre la imagen. El tamaño de salida depende del stride (paso) y el padding (relleno).
- Max Pooling: Reduce la resolución espacial seleccionando el valor máximo en ventanas locales, lo que otorga cierta invarianza a traslaciones pequeñas.
- Spatial Batch Normalization: A diferencia de la normalización estándar, aquí se calculan estadísticas por cada canal (C) a través de todas las posiciones espaciales (H, W) y ejemplos (N).
# Cálculo de dimensiones de salida
alto_salida = (alto_entrada + 2 * padding - alto_kernel) // paso + 1
ancho_salida = (ancho_entrada + 2 * padding - ancho_kernel) // paso + 1
Implementación en Frameworks
PyTorch: Estilo Secuencial
PyTorch permite definir modelos de forma imperativa. Es fundamental utilizar model.train() y model.eval() para alternar el comportamiento de capas como Dropout y BatchNorm.
import torch.nn as nn
red_cnn = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.BatchNorm2d(32),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(32 * 16 * 16, 10)
)
TensorFlow (v1): Grafos y Sesiones
En versiones clásicas de TensorFlow, se definen placeholders para los datos y se gestiona la ejecución mediante una sesión (tf.Session).
import tensorflow.compat.v1 as tf
tf.disable_eager_execution()
X = tf.placeholder(tf.float32, [None, 32, 32, 3])
etiquetas = tf.placeholder(tf.int64, [None])
entrenando = tf.placeholder(tf.bool)
def construir_modelo(datos, es_entrenamiento):
capa_conv = tf.layers.conv2d(datos, 64, 3, activation=tf.nn.relu)
bn = tf.layers.batch_normalization(capa_conv, training=es_entrenamiento)
pool = tf.layers.max_pooling2d(bn, 2, 2)
return tf.layers.dense(tf.layers.flatten(pool), 10)
logits = construir_modelo(X, entrenando)
error = tf.losses.sparse_softmax_cross_entropy(labels=etiquetas, logits=logits)
paso_entrenamiento = tf.train.AdamOptimizer(1e-3).minimize(error)