Volver a la lista

¿Cómo funciona la retropropagación: rastreo causal inverso en una cascada de señales

¿Cómo calcular eficientemente las derivadas parciales de 33,62 millones de parámetros? La retropropagación y la regla de la cadena para comprender hacia atrás las cascadas de señalización celular, junto con los principios de autograd de PyTorch.

Principiante
|
20min
|
Verificado (2026-07)
Progreso0/15 (0%)

¿Cómo funciona la retropropagación? — Rastreo causal inverso en una cascada de señales

Al finalizar este capítulo

Obtendremos la respuesta a la pregunta pospuesta en el capítulo #3: "más adelante trataremos cómo se calculan los gradientes". Cómo calcular las derivadas parciales para cada uno de los 33,62 millones de parámetros dentro de un tiempo práctico. El algoritmo que aparece aquí es la retropropagación (backpropagation), y la matemática subyacente es la regla de la cadena (chain rule).

Este capítulo es el complemento del capítulo #3. Si el capítulo #3 trató "cuánto moverse", este capítulo trata "cómo sabemos en qué dirección". El capítulo #5 sobre Transformers y el capítulo #12 sobre prácticas con PyTorch se basan en esta base.


El fracaso estrepitoso del método ingenuo

En el capítulo #3, definimos la derivada parcial del parámetro θ_i de la siguiente manera:

text
∂L/∂θ_i = lim_{ε → 0} [L(θ_1, ..., θ_i + ε, ..., θ_P) - L(θ)] / ε

Ejecutémoslo tal cual, de manera clásica. Fijemos los parámetros uno por uno, aumentémoslos ligeramente en una cantidad ε para observar cuánto cambia la pérdida y dividamos por ε para obtener la derivada parcial. Esto se denomina método de diferencias finitas.

Problema: Si hay 33,62 millones de parámetros, se requiere una propagación hacia adelante completa de la red neuronal para obtener una sola derivada parcial. Para obtener las 33,62 millones de derivadas parciales, es necesario realizar 33,62 millones de propagaciones hacia adelante en la red neuronal.

Tomemos como ejemplo una red neuronal patológica para tener una idea de la escala. Si una única propagación hacia adelante tarda 1 milisegundo en ejecutarse en una GPU, el cálculo del gradiente para un solo paso tomaría 33,62 millones de milisegundos, es decir, aproximadamente 9,3 horas. Sin embargo, el entrenamiento debe repetir este paso cientos de miles o incluso millones de veces. Con este método, el entrenamiento tardaría cientos de años.

Esto no significa que el método de diferencias finitas sea completamente inútil. Como se mencionará más adelante, se utiliza como herramienta de validación de la implementación de la propagación hacia atrás. Cuando verificamos "si nuestro código de propagación hacia atrás es realmente correcto", calculamos las derivadas parciales de algunos parámetros utilizando el método de diferencias finitas y las comparamos con los valores verdaderos. Esto se denomina gradient check (verificación del gradiente) y es una validación obligatoria para los desarrolladores de bibliotecas de redes neuronales.

Para el entrenamiento en sí, se necesita un método mucho más rápido. Ese método es la propagación hacia atrás.


Reacción anómala en la cascada de señales — ¿Dónde está la causa?

Para comprender la intuición detrás de la propagación hacia atrás, observemos un escenario experimental de laboratorio.

Supongamos que estás realizando un experimento de señalización celular. Al tratar las células con un ligando específico, se debe inducir la expresión génica en la vía descendente. La ruta de señalización es aproximadamente la siguiente:

text
Ligando (estímulo externo)
   ↓
Activación del receptor
   ↓
Segundo mensajero (por ejemplo, cAMP)
   ↓
Cascada de quinasas (RAF → MEK → ERK, etc.)
   ↓
Fosforilación del factor de transcripción
   ↓
Inducción de la expresión génica (resultado medido)

Supongamos que procesaron un ligando y midieron la expresión génica downstream, obteniendo un resultado 50% menor de lo esperado. Su pregunta es: "¿En qué etapa ocurrió el problema?"

Podría ser que los receptores se activaran menos, que se generara menos señal secundaria, que la actividad de una quinasa específica fuera baja o que no se produjera la fosforilación del factor de transcripción. Debe cuantificar cuánto "responsabilidad" tiene cada nodo upstream para el error del 50% downstream para identificar la causa.

¿Cómo abordarían este problema? Miden cuantitativamente cada etapa y retroceden a través de las relaciones jerárquicas.

  • La expresión génica es un 50% menor → ¿Cuál fue el nivel de fosforilación del factor de transcripción? Un 30% menor → Contribución del 30% en la etapa del factor de transcripción, y contribución del 20% en la etapa de mapeo (factor de transcripción → expresión)
  • La fosforilación del factor de transcripción es un 30% menor → ¿Cuál fue la actividad upstream de ERK? Un 15% menor → Contribución del 15% en la etapa de ERK
  • La actividad de ERK es un 15% menor → ¿Qué pasa con MEK? Un 8% menor → Contribución del 8% en MEK
  • ... continuar hacia arriba

En cada etapa, calculan "cuánto ha contribuido esta etapa a la anomalía downstream". El método es el siguiente:

Contribución ya conocida downstream × Sensibilidad transmitida desde esta etapa hacia downstream = Contribución de esta etapa

Multiplicando estos dos factores mientras se propaga hacia arriba es exactamente lo que se conoce como retropropagación. La formulación matemática de esto es la regla de la cadena (chain rule).

Observación importante. Antes de realizar este rastreo inverso, deben haber pasado una vez por el sentido directo (ligando → expresión) para conocer los valores normales (línea base) de cada etapa. Es decir, con una medición en sentido directo + un rastreo inverso, obtienen la contribución de todos los nodos upstream. Esta es la razón por la cual la retropropagación es abrumadoramente más rápida que el método de diferencias finitas (que requiere repetir el sentido directo para cada parámetro).


Regla de la cadena — El principio de retroceso del cálculo diferencial

Comprendamos la regla de la cadena, la base de la retropropagación, con el mínimo de matemáticas.

Supongamos que una función está compuesta por combinaciones. Al ingresar x, primero se convierte en y = f(x) y luego en z = g(y). Finalmente, obtenemos z = g(f(x)).

Queremos saber cuánto cambia z si x cambia ligeramente. Es decir, queremos calcular dz/dx.

Podríamos calcularlo directamente, pero la regla de la cadena divide esto en dos pasos.

text
dz/dx = (dz/dy) × (dy/dx)

En términos simples: multiplicar "cuánto cambia x a y" por "cuánto cambia y a z" da como resultado "cuánto cambia x a z".

Volvamos a la analogía de la cascada biológica: "cuánto cambia la expresión génica cuando la concentración del ligando varía ligeramente" es el producto de múltiples etapas.

text
Δ(expresión)/Δ(ligando) =
   Δ(expresión)/Δ(factor de transcripción) × Δ(factor de transcripción)/Δ(ERK) × Δ(ERK)/Δ(MEK) × ... × Δ(receptor)/Δ(ligando)

Por cada flecha se multiplica la "sensibilidad desde esta etapa a la siguiente", y finalmente se obtiene la sensibilidad total desde el ligando hasta la expresión.

La red neuronal es exactamente igual. La función de pérdida L es una función de la salida de la última capa, la cual es una función de la salida de la capa anterior, y así sucesivamente... hasta convertirse en una composición de funciones anidadas que llega hasta los parámetros de la primera capa.

La derivada parcial de L con respecto a los parámetros de la primera capa es el producto de las sensibilidades en cada capa, según la regla de la cadena. Calcular este producto en dirección inversa es la retropropagación.


Estructura del algoritmo de retropropagación

Ahora organicemos el algoritmo. Consideremos que la red neuronal está compuesta por L capas.

Paso 1 — Propagación hacia adelante (Forward Pass)

Calcule y almacene los valores de activación de cada capa haciendo pasar la entrada a través de la red neuronal hasta la salida.

text
a^(0) = entrada
a^(1) = σ(W^(1) · a^(0) + b^(1))
a^(2) = σ(W^(2) · a^(1) + b^(2))
...
a^(L) = σ(W^(L) · a^(L-1) + b^(L))

a^(l) es el vector de activación de la capa l, W^(l) y b^(l) son la matriz de pesos y el vector de sesgo de la capa l, y σ es la función de activación. Se trata de la figura que se analizó en el apartado 2.

Aquí es importante destacar: se guardan en la memoria los valores de a^(l) de cada capa. Esto es necesario para la retropropagación. Esta es la razón por la que el entrenamiento requiere mucha más memoria de la GPU que la inferencia. La inferencia solo requiere la propagación hacia adelante, pero el entrenamiento requiere que se mantengan los valores de activación de todas las capas hasta la retropropagación.

Paso 2: cálculo de la pérdida

Se calcula la pérdida a partir de la salida a^(L) de la última capa y la respuesta correcta y.

text
L = CrossEntropy(a^(L), y)

Lo que se obtiene aquí es la derivada parcial de la pérdida con respecto a las activaciones de la última capa δ^(L) = ∂L/∂a^(L).

En el caso de la combinación Softmax + Entropía Cruzada, esta derivada parcial es sorprendentemente elegante.

text
δ^(L) = a^(L) - y

Es decir, "distribución de probabilidad predicha menos el vector one-hot de la respuesta correcta". Esta elegancia es la razón por la que SoftMax y CE se utilizan en conjunto. Se deriva en el Apéndice A.4.

Paso 3 — Retropropagación (Backward Pass)

Comenzando desde δ^(L), se recorre hacia abajo capa por capa, calculando el δ^(l) de cada capa.

text
δ^(l) = (W^(l+1))^T · δ^(l+1) ⊙ σ'(z^(l))

es el producto por elemento, y z^(l) = W^(l) · a^(l-1) + b^(l) es el valor antes de pasar por la función de activación. σ' es la derivada de la función de activación.

¿Qué hace esta ecuación:

  • (W^(l+1))^T · δ^(l+1): Devuelve la señal de error de la siguiente capa al espacio de activaciones de la capa actual. Se pasa a través del peso utilizado para enviar la señal a la siguiente capa, pero en dirección inversa.
  • ⊙ σ'(z^(l)): Multiplica por la pendiente local de la función de activación de esta capa. En los puntos donde la función de activación está "muerta" (por ejemplo, neuronas con z < 0 en ReLU), σ' es 0, lo que bloquea el error aquí.

Se repite esto desde la capa L hasta la capa 1 para obtener el δ^(l) de todas las capas.

Paso 4 — Cálculo de las derivadas parciales de los parámetros

Una vez que se obtiene el δ^(l) de cada capa, las derivadas parciales con respecto a los pesos y sesgos de esa capa se calculan inmediatamente.

text
∂L/∂W^(l) = δ^(l) · (a^(l-1))^T
∂L/∂b^(l) = δ^(l)

Así obtenidas, las derivadas parciales son los valores reales del gradiente mencionados en la Parte 3. Ahora, el optimizador de la Parte 3 (Adam, etc.) actualiza los parámetros utilizando este gradiente.


¿Por qué es tan rápido?

Lo sorprendente de la retropropagación es que las derivadas parciales de todos los parámetros se obtienen con una sola pasada hacia adelante y una sola pasada hacia atrás. Esto contrasta con el método de las diferencias finitas, que requiere repetir la pasada hacia adelante tantas veces como parámetros haya.

Reducción del costo computacional: el costo de la retropropagación es aproximadamente 2~3 veces el de la pasada hacia adelante. Es decir, un paso de entrenamiento de una red neuronal con 33,62 millones de parámetros toma aproximadamente el tiempo de realizar 3~4 pasadas hacia adelante. En comparación con los 33,62 millones de pasadas del método de las diferencias finitas, esto es aproximadamente 8 millones de veces más rápido.

Sin este algoritmo, el aprendizaje profundo actual no existiría. De hecho, solo después de que se consolidó la forma moderna de la retropropagación en el artículo de Rumelhart-Hinton-Williams de 1986, el entrenamiento de redes neuronales multicapa se volvió práctico y comenzó el auge actual de la IA.

Revisión del escenario biológico. En el análisis de las causas de la cascada de señales, tampoco realizamos experimentos separados para cada nodo aguas arriba. Obtenemos la contribución de cada nodo mediante una sola estimulación normal + una observación de la situación anómala + un seguimiento hacia atrás. Lo equivalente al método de las diferencias finitas sería realizar experimentos de knockout/knockdown individuales para cada nodo, lo cual requiere tantos experimentos como nodos existan. La razón por la que el rastreo causal basado en retropropagación es mucho más eficiente en términos de economía experimental se explica con la misma lógica.


Grafo computacional — Lo que PyTorch realmente hace

Las explicaciones anteriores se limitaban a redes neuronales por capas, pero los marcos de aprendizaje profundo reales (PyTorch·JAX) manejan la retropropagación en una forma mucho más general. Esa forma es el grafo computacional.

El principio es el siguiente: cuando el marco ejecuta el código de la pasada hacia adelante, registra cada operación (suma, multiplicación, producto matricial, softmax, etc.) como un nodo del grafo. Las aristas del grafo representan el flujo de datos.

text
x → (multiplicar por W_1) → z_1 → (ReLU) → a_1 → (multiplicar por W_2) → z_2 → ...

Cada nodo de operación almacena qué tipo de operación realiza y qué entradas recibe. Una vez completado este grafo, se recorre en dirección inversa desde la pérdida L, multiplicando las derivadas parciales locales de cada nodo para obtener las derivadas parciales de todos los parámetros.

A esta retropropagación automatizada se le denomina diferenciación automática (automatic differentiation, autograd). Hoy en día, los desarrolladores de redes neuronales no necesitan escribir manualmente el código de la retropropagación; basta con implementar el código de propagación hacia adelante, ya que el framework genera automáticamente el grafo de cálculo y gestiona la retropropagación.

Así se ve en PyTorch:

python
import torch
x = torch.randn(32, 100) # Tamaño de lote 32, 100 características
W1 = torch.randn(100, 50, requires_grad=True)
W2 = torch.randn(50, 3, requires_grad=True)
y_true = torch.randint(0, 3, (32,))
# Propagación hacia delante: PyTorch construye automáticamente el grafo computacional
z1 = x @ W1
a1 = torch.relu(z1)
logits = a1 @ W2
loss = torch.nn.functional.cross_entropy(logits, y_true)
# Retropropagación: esta línea calcula todas las derivadas parciales
loss.backward()
# W1.grad y W2.grad almacenan las derivadas parciales de cada parámetro
print(W1.grad.shape, W2.grad.shape)

loss.backward() En una sola línea se encuentran las cuatro etapas que hemos descrito anteriormente. Gracias a esta automatización, el desarrollador solo necesita preocuparse por la propagación hacia adelante al experimentar con nuevas arquitecturas.

Perspectiva biológica. Este grafo de cálculo es conceptualmente idéntico a la representación y el concepto de una red regulatoria (regulatory network) en biología de sistemas. Cada nodo representa relaciones de reacción y regulación, y cada arista representa un flujo causal. El método que utiliza un biólogo de sistemas para analizar la contribución de cada gen a un fenotipo downstream específico dentro de una red regulatoria es, de hecho, análogo lógicamente a la diferenciación automática en un grafo de cálculo.


Gradientes que desaparecen y gradientes explosivos: el desastre de la retropropagación

La estructura multiplicativa de la retropropagación puede crear problemas inesperados.

Gradiente que desaparece (Vanishing Gradient). Si el gradiente local en cada capa es menor que 1 (por ejemplo, la derivada de la función de activación Sigmoid tiene un máximo de 0,25), el gradiente se reduce exponencialmente al multiplicarse al pasar por múltiples capas. En una red neuronal de 20 capas, cuando el gradiente llega a la primera capa, es prácticamente cero. Esto provoca que los parámetros de la primera capa no se entrenen.

Gradiente explosivo (Exploding Gradient). Por el contrario, si el gradiente local es mayor que 1, este crece exponencialmente. El gradiente de los parámetros de la primera capa adquiere valores astronómicamente grandes, haciendo que los parámetros se comporten de manera errática y provocando que el entrenamiento diverja.

Soluciones (ver sección #2 A.8 Inicialización, y detalles en la sección #12):

  • Familia de funciones de activación ReLU: En la región donde los valores activos son positivos, la derivada es exactamente 1, por lo que no desaparece al multiplicarse.
  • Inicialización de Xavier·He: Ajusta la varianza de las pesos iniciales según el tamaño de la capa para evitar que los gradientes desaparezcan desde el inicio.
  • Normalización por lotes (Batch Normalization) y Normalización por capa (Layer Normalization): Ajustan la distribución de los valores activos de la capa para mantener la escala del gradiente.
  • Conexión residual (sección #5): Crea atajos que saltan capas, evitando que el gradiente se multiplique a través de muchas capas. Es fundamental en ResNet y Transformers.
  • Recorte de gradientes (Gradient Clipping): Corta forzosamente los gradientes explosivos por debajo de un valor específico.

Analogía biológica. Estructura similar a la multiplicación de factores de amplificación y atenuación en cada etapa de una cascada de señales. La naturaleza ha resuelto evolutivamente este problema celular. Existen bucles de retroalimentación negativa y positiva en cada etapa, proteínas andamio de MAPK (que regulan la intensidad de la señal) y el equilibrio entre fosforilación y desfosforilación. Las funciones de las técnicas de inicialización, normalización y residuales en redes neuronales corresponden conceptualmente a estos mecanismos de regulación del sistema de señales celulares.


Escenarios de aplicación biológica

Escenario 1 — Entrenamiento real de un clasificador de tipos celulares

Entrenamiento del clasificador de tipos celulares de scRNA-seq mencionado en la Parte 3. Al implementarlo con PyTorch, solo necesitas escribir el paso de propagación hacia adelante (forward pass).

python
class CellTypeClassifier(torch.nn.Module):
def __init__(self, n_genes, n_types, hidden=512):
super().__init__()
self.encoder = torch.nn.Sequential(
torch.nn.Linear(n_genes, hidden),
torch.nn.ReLU(),
torch.nn.Dropout(0.3),
torch.nn.Linear(hidden, hidden // 2),
torch.nn.ReLU(),
torch.nn.Linear(hidden // 2, n_types),
)
def forward(self, expr_matrix):
return self.encoder(expr_matrix)
model = CellTypeClassifier(n_genes=20000, n_types=25)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)
for cells, labels in train_loader:
logits = model(cells) # Propagación hacia delante
loss = torch.nn.functional.cross_entropy(logits, labels)
optimizer.zero_grad()
loss.backward() # Retropropagación automática
optimizer.step() # Actualización de Adam

loss.backward() realiza automáticamente los cuatro pasos de este algoritmo. El desarrollador se centra únicamente en la estructura de propagación hacia adelante necesaria para la clasificación de tipos celulares y en la selección de la función de pérdida.

Escenario 2 — Entrenamiento de incrustaciones de secuencias de proteínas (ESM)

ESM (Evolutionary Scale Modeling) de Meta AI es un modelo que aprende incrustaciones de secuencias de proteínas. Utiliza cientos de millones de secuencias de proteínas de UniProt como datos de entrenamiento. Este entrenamiento también se basa en la potencia de la retropropagación. El número de parámetros es miles de veces mayor que el tamaño visto en el episodio #2 (del orden de 150 mil millones de parámetros). La razón por la cual es físicamente posible entrenar una red neuronal de este tamaño es la eficiencia computacional de la retropropagación.

Una vez entrenado ESM, es posible representar cada secuencia de proteínas como un vector, y este vector se utiliza ampliamente en AlphaFold, la predicción de funciones de proteínas y el descubrimiento de fármacos.

Escenario 3 — Estimación de parámetros de modelos de biología de sistemas

Algunos laboratorios de biología de sistemas utilizan diferenciación automática para ajustar los parámetros de modelos de ecuaciones diferenciales ordinarias (ODE) de cascadas de señalización celular a datos. Se obtiene el gradiente de la pérdida con respecto a cada parámetro mediante diferenciación automática y se optimizan los parámetros mediante descenso de gradiente. Este es un procedimiento conceptualmente idéntico al entrenamiento de redes neuronales. A esto se le llama programación diferenciable o Machine Learning científico, una nueva área de aplicación de marcos como JAX y PyTorch.


Resumen clave

  • La retropropagación es un algoritmo eficiente que obtiene las derivadas parciales de todos los parámetros en un solo pase hacia adelante y un solo pase hacia atrás.
  • Las matemáticas subyacentes son la regla de la cadena. La derivada de una composición de múltiples funciones es el producto de las derivadas locales de cada paso.
  • El algoritmo consta de 4 pasos: propagación hacia adelante (almacenamiento de activaciones) → cálculo de la pérdida → propagación hacia atrás de la señal de error (cálculo de δ) → cálculo de las derivadas parciales de los parámetros de cada capa.
  • Los marcos actuales (PyTorch, JAX) automatizan completamente este proceso mediante grafos computacionales + diferenciación automática. El desarrollador solo necesita escribir el pase hacia adelante.
  • El problema de los gradientes que desaparecen o explotan se mitiga combinando funciones de activación, inicialización, normalización, conexiones residuales y recorte de gradientes.
  • La diferenciación automática se está utilizando fuera de las redes neuronales en biología de sistemas y cálculo científico como herramienta de optimización de parámetros.

Próximos conceptos

  • Episodio #5 transformer-and-embedding — Por qué las incrustaciones, el codificado de posición y las conexiones residuales del transformador estabilizan el aprendizaje.
  • Episodio #6 attention-mechanism — Por qué los gradientes de los puntajes de atención son la clave para comprender el contexto.
  • Episodio #12 pytorch-basics — Ponga en práctica la teoría de este episodio mediante código.

📐 Apéndice — Fórmulas matemáticas para expertos

Dificultad: Muy difícil (Very Hard) Dirigido a: Lectores con conocimientos de álgebra lineal, cálculo multivariable y teoría de optimización a nivel de posgrado

A.1 Regla de la cadena para escalares

La forma más sencilla. Si z = g(y) y y = f(x) son funciones escalares:

text
dz/dx = (dz/dy) · (dy/dx)

Combinación de múltiples etapas:

text
dz/dx = (dz/dy_k) · (dy_k/dy_{k-1}) · ... · (dy_2/dy_1) · (dy_1/dx)

A.2 Regla de la cadena multivariable

z = g(y_1, y_2, ..., y_m) y cada y_i = f_i(x_1, ..., x_n):

text
∂z/∂x_j = Σ_{i=1}^{m} (∂z/∂y_i) · (∂y_i/∂x_j)

Si lo reescribimos en forma de vector y matriz, será el producto de la matriz jacobiana.

Si y = f(x) y x ∈ ℝ^n, y ∈ ℝ^m, entonces la matriz jacobiana:

text
J_f = ∂y/∂x = [ ∂y_i/∂x_j ]_{i,j}    (matriz m × n)

Jacobiano de z = g(f(x)):

text
J_{g∘f} = J_g · J_f

El retropropagación equivale exactamente a calcular este producto de matrices de derecha a izquierda (en dirección de entrada). Si se calcula de izquierda a derecha, se obtiene la diferenciación automática en modo directo, pero cuando las redes neuronales tienen una gran dimensión de entrada y una salida escalar (pérdida), el modo inverso es abrumadoramente más eficiente.

A.3 Fórmulas capa por capa del retropropagación (forma completa)

LRed neuronal completamente conectada:

Propagación hacia adelante:

text
z^(l) = W^(l) a^(l-1) + b^(l)
a^(l) = σ(z^(l))

Pérdida:

text
L = ℓ(a^(L), y)

Retropropagación — Señal de error en la última capa:

text
δ^(L) = ∂L/∂z^(L) = (∂ℓ/∂a^(L)) ⊙ σ'(z^(L))

Retropropagación — Señal de error en la capa ll (l=L1,L2,,1l = L-1, L-2, \dots, 1):

text
δ^(l) = ((W^(l+1))^T δ^(l+1)) ⊙ σ'(z^(l))

Derivadas parciales de los parámetros por capa:

text
∂L/∂W^(l) = δ^(l) (a^(l-1))^T
∂L/∂b^(l) = δ^(l)

En el caso de los mini-lotes, se calcula el promedio de estas derivadas parciales con respecto a los ejemplos dentro del lote.

A.4 Derivada parcial elegante de Softmax + Entropía Cruzada

Softmax:

text
p_i = exp(z_i) / Σ_k exp(z_k)

Entropía cruzada (cuando la clase correcta es c):

text
L = -log(p_c)

∂L/∂z_i Cálculo.

Caso 1: i = c (clase correcta):

text
∂L/∂z_c = -1 + p_c = p_c - 1

Caso 2: i ≠ c (clase incorrecta):

text
∂L/∂z_i = p_i

Expresar la respuesta correcta como un vector one-hot y integra ambos casos en una sola ecuación:

text
∂L/∂z = p - y

Es decir, la distribución de probabilidad predicha menos la etiqueta one-hot. Sorprendentemente elegante, y la razón por la que Softmax y Cross-Entropy se utilizan en conjunto.

A.5 Derivadas de las funciones de activación

Sigmoid: σ(z) = 1/(1+e^{-z})

text
σ'(z) = σ(z)(1 - σ(z))

Valor máximo de 0.25 (en z=0). La multiplicación de múltiples capas provoca un decaimiento exponencial, lo que causa el problema del gradiente desaparecido.

Tanh: tanh(z) = (e^z - e^{-z})/(e^z + e^{-z})

text
tanh'(z) = 1 - tanh^2(z)

Valor máximo 1. Mejor que la sigmoidea, pero aún así su derivada es 0 en los extremos.

ReLU: ReLU(z) = max(0, z)

text
ReLU'(z) = 1 if z > 0 else 0

En la región positiva, mitiga significativamente el problema del gradiente que desaparece. Sin embargo, presenta el problema de ReLU muerto (las neuronas en la región negativa dejan de entrenarse porque su gradiente es 0).

Leaky ReLU: LeakyReLU(z) = z if z > 0 else αz (α=0.01)

text
LeakyReLU'(z) = 1 if z > 0 else α

Mantener una pequeña pendiente incluso en la región negativa.

GELU: GELU(z) = z · Φ(z) (Φ: CDF normal estándar)

text
GELU'(z) ≈ Φ(z) + z · φ(z)

Estándar de Transformers. Suavizado natural.

A.6 Grafos computacionales y modo reverso de diferenciación automática

Grafo computacional G = (V, E):

  • Nodo V: cada operación elemental (suma, multiplicación, producto matricial, funciones de activación, etc.)
  • Arista E: flujo de datos

Para cada nodo v:

  • Definir previamente una función que pueda calcular la derivada parcial local ∂v/∂parent_i (integrada en el marco de trabajo)

Modo reverso de diferenciación automática:

  1. Comenzar desde el nodo de salida hacia ∂L/∂L = 1.
  2. Recorrer los nodos mediante ordenamiento topológico inverso del grafo.
  3. En cada nodo, propagar hacia los nodos aguas arriba el producto de su derivada parcial propia por la derivada parcial local.
  4. Al llegar al nodo de parámetro, este constituye la derivada parcial final del parámetro.

Complejidad temporal: del mismo orden que el paso hacia adelante (el costo computacional es un factor constante de 2 a 3 veces mayor). Complejidad espacial: requiere memoria del mismo orden que el paso hacia adelante, ya que deben almacenarse todos los valores activos.

A.7 Puntos de control de gradiente (Gradient Checkpointing)

Truco para reducir la memoria durante el entrenamiento. En el paso hacia adelante no se guardan todos los valores activos, sino solo algunos, y en la retropropagación se recalculan reejecutando el paso hacia adelante cuando sea necesario.

  • Tiempo: dos pasos hacia adelante y uno hacia atrás → aproximadamente 1,5 veces más lento
  • Espacio: almacenamiento de valores activos a nivel de √L (para una red neuronal de L capas)

Técnica esencial para el entrenamiento de modelos grandes, mencionada en la sección #14 (entrenamiento práctico de LLM).

A.8 Recorte de gradiente (Gradient Clipping)

Respuesta ante gradientes explosivos. Si la norma L2 del gradiente supera el umbral c, se reduce su escala:

text
if ||∇||_2 > c:
    ∇ ← ∇ · (c / ||∇||_2)

c es generalmente la versión 1.0. Es el estándar de facto en el entrenamiento de Transformers.

A.9 Verificación del gradiente (validación de la implementación)

Comparar si el código de retropropagación es correcto con diferencias finitas:

text
∂L/∂θ_i ≈ [L(θ + ε · e_i) - L(θ - ε · e_i)] / (2ε)

e_i es el vector unitario en la posición i, ε ≈ 1e-5.

Indicador de error relativo:

text
relative_error = |grad_analytic - grad_numeric| / max(|grad_analytic|, |grad_numeric|)

Por lo general, si el valor es menor o igual a 1e-7, se considera preciso; si es menor o igual a 1e-5, está dentro del margen de error numérico; y si supera 1e-3, se sospecha de un error de programación.

A.10 Resumen de la complejidad temporal de la retropropagación

  • Tiempo de propagación hacia adelante: O(F) — Cantidad total de operaciones (FLOPs) de la red neuronal
  • Tiempo de retropropagación: O(F) — Del mismo orden que la propagación hacia adelante (factor constante de 2 a 3)
  • Tiempo para el gradiente completo por diferencias finitas: O(P · F) — Número de parámetros × propagación hacia adelante

La retropropagación obtiene el gradiente completo en un tiempo independiente del número de parámetros. Esta es la razón decisiva que hace posible el entrenamiento de redes neuronales grandes.

Esta elegancia en la complejidad temporal se conoce como el teorema de Baur–Strassen (1983), que constituye la base teórica del cálculo de la diferenciación automática.


Referencias

El contenido, los escenarios, las metáforas y los datos numéricos de esta entrega son desarrollados internamente por BioPlayground; a continuación se presentan referencias externas útiles para el aprendizaje conceptual:

  • Artículo original sobre retropropagación: Rumelhart, Hinton, Williams, "Learning representations by back-propagating errors" (Nature 1986)
  • Teoría del cálculo de la diferenciación automática: Baur & Strassen, "The complexity of partial derivatives" (Theoretical Computer Science 1983)
  • Texto estándar de aprendizaje profundo: Goodfellow et al., "Deep Learning" Capítulo 6 (Retropropagación)
  • Guía práctica de diferenciación automática: Baydin et al., "Automatic Differentiation in Machine Learning: a Survey" (JMLR 2018)
  • Documentación de autograd de PyTorch: pytorch.org autograd tutorial
  • Diferenciación automática de JAX: jax.readthedocs.io — Implementación moderna de diferenciación automática funcional
  • Educación visual sobre aprendizaje profundo: 3Blue1Brown "Deep Learning" Caps. 3 y 4 (YouTube) — Referencia pedagógica
  • Artículo sobre ESM: Rives et al., "Biological structure and function emerge from scaling unsupervised learning to 250 million protein sequences" (PNAS 2021)

Con esta entrega, se han cubrido los dos ejes del entrenamiento de redes neuronales: el método de exploración del paisaje de costos y el método de cálculo de los gradientes. A partir de la entrega #5, se abordará cómo este mecanismo de entrenamiento se reensambla para adaptarse al lenguaje y convertirse en un transformador.

💬 Preguntas y comentarios

0 comentarios

Puedes publicar sin iniciar sesión. Los comentarios de invitados no pueden editarse ni eliminarse después.

0/2000

Cargando...