¿Por Qué la No Linealidad?
Sin funciones de activación, una red neuronal — sin importar cuántas capas tenga — es simplemente una única transformación lineal. Apilar capas lineales $y = W_2(W_1 x + b_1) + b_2$ se simplifica a $y = W'x + b'$ — la profundidad no añade expresividad. Las funciones de activación rompen esta linealidad, permitiendo a las redes aprender fronteras de decisión curvas, umbrales y patrones complejos.
Para ver por qué el apilamiento colapsa, expandamos la expresión de dos capas:
donde $W' = W_2 W_1$ y $b' = W_2 b_1 + b_2$. Esto colapsa a una sola capa porque la multiplicación de matrices es asociativa y lineal — multiplicar dos matrices simplemente produce otra matriz. No importa cuántas capas apiles, el resultado siempre es algún $W'x + b'$, una única transformación afín. Podrías reemplazar toda la red con una sola capa y obtener el mismo mapeo entrada-salida.
Una función de activación $\sigma$ entre capas rompe este colapso. Consideremos qué sucede cuando insertamos una:
La función $\sigma(W_1 x + b_1)$ no es lineal, por lo que la composición $W_2 \, \sigma(W_1 x + b_1) + b_2$ no puede reducirse a una única multiplicación de matrices. La no linealidad crea un espacio de funciones fundamentalmente más rico — cada capa puede ahora curvar y remodelar su entrada de maneras que ninguna capa lineal individual puede replicar. Esto es lo que da a las redes profundas su poder: no la profundidad en sí, sino las transformaciones no lineales entre capas.
ReLU: La Opción por Defecto
La Unidad Lineal Rectificada (ReLU) es la función de activación de uso general más simple:
El comportamiento es directo:
- Cuando $x > 0$: la salida es simplemente $x$ (la identidad). El gradiente es exactamente 1, así que la señal pasa sin atenuación. Esto evita el problema del gradiente desvaneciente que afectaba a las activaciones anteriores.
- Cuando $x \leq 0$: la salida es 0. El gradiente también es 0 — la neurona está "muerta" para esta entrada. Esta es la no linealidad: ReLU anula todas las pre-activaciones negativas, seleccionando efectivamente qué neuronas se activan y cuáles no.
¿Por qué esta forma? ReLU es posiblemente la no linealidad más simple posible. Requiere solo una comparación ($x > 0$?), lo que la hace extremadamente rápida de calcular. Su gradiente es 0 o 1 — nunca una fracción pequeña — por lo que las señales positivas se propagan hacia atrás sin reducirse. Estas propiedades hicieron de ReLU la activación que finalmente permitió entrenar redes profundas (Nair & Hinton, 2010; Glorot et al., 2011).
Sin embargo, existe un modo de fallo bien conocido: el problema de la ReLU muerta . Si los pesos de una neurona derivan de tal manera que su pre-activación $W x + b$ es siempre negativa para todas las entradas del conjunto de entrenamiento, entonces la salida de la neurona es permanentemente 0. Como el gradiente también es 0 en esta región, los pesos nunca reciben una señal de actualización — la neurona está efectivamente muerta. Una vez muerta, permanece muerta. Esto tiende a ocurrir más frecuentemente con tasas de aprendizaje altas o mala inicialización, y puede reducir silenciosamente la capacidad efectiva de una red.
El gráfico a continuación muestra ReLU y su gradiente. Observa la esquina abrupta en $x = 0$ y la región plana en cero para todas las entradas negativas:
import numpy as np
import json
import js
x = np.linspace(-5, 5, 200)
relu = np.maximum(0, x)
relu_grad = (x > 0).astype(float)
plot_data = [{
"title": "ReLU y su Gradiente",
"x_label": "x",
"y_label": "f(x)",
"x_data": x.tolist(),
"lines": [
{"label": "ReLU(x)", "data": relu.tolist(), "color": "#3b82f6"},
{"label": "Gradiente", "data": relu_grad.tolist(), "color": "#ef4444"}
]
}]
js.window.py_plot_data = json.dumps(plot_data)
Leaky ReLU y GELU
Leaky ReLU es una corrección directa al problema de la ReLU muerta. En lugar de producir exactamente 0 para entradas negativas, aplica una pendiente pequeña $\alpha$ (típicamente 0.01):
Cuando $x \leq 0$, el gradiente es $\alpha$ en lugar de 0, por lo que las neuronas en el lado negativo aún pueden recibir actualizaciones de gradiente y recuperarse. La contrapartida es que la salida para entradas negativas ya no es exactamente cero, lo que significa que la red no puede "ignorar" características irrelevantes tan limpiamente como lo hace ReLU. En la práctica, esto tiende a ser una preocupación menor — mantener las neuronas vivas generalmente vale más que una región negativa ligeramente más ruidosa.
GELU (Unidad Lineal con Error Gaussiano) (Hendrycks & Gimpel, 2016) es la activación utilizada en GPT, BERT y la mayoría de los transformers modernos:
donde $\Phi(x)$ es la función de distribución acumulativa (CDF) de la distribución normal estándar — la probabilidad de que una variable aleatoria normal estándar $Z \sim \mathcal{N}(0, 1)$ sea menor que $x$. Esto le da a GELU una elegante interpretación probabilística: multiplica cada entrada por la probabilidad de que la entrada sea "suficientemente grande" para conservarla. El comportamiento en diferentes regímenes:
- Cuando $x \gg 0$: $\Phi(x) \approx 1$, así que $\text{GELU}(x) \approx x$ — se comporta como la identidad, igual que ReLU.
- Cuando $x \ll 0$: $\Phi(x) \approx 0$, así que $\text{GELU}(x) \approx 0$ — suprime las entradas negativas, igual que ReLU.
- Cerca de $x = 0$: hay una transición suave y continua. A diferencia de la esquina abrupta de ReLU en cero, GELU se curva suavemente a través del origen. Este paisaje de gradiente suave tiende a ayudar a la optimización, especialmente en redes profundas donde las discontinuidades agudas en el gradiente pueden causar inestabilidad.
Puedes pensar en GELU como una versión suave y probabilística de ReLU: "incluir $x$ con probabilidad $\Phi(x)$". Cuando la entrada es claramente positiva, casi con certeza se incluye. Cuando es claramente negativa, casi con certeza se anula. En la región ambigua cerca de cero, la función interpola suavemente.
En la práctica, evaluar la CDF gaussiana exacta es costoso, así que las implementaciones usan una aproximación basada en tanh:
El gráfico a continuación compara las tres activaciones. Observa cómo tanto GELU como Leaky ReLU permiten pasar algo de señal para entradas negativas, a diferencia del corte abrupto de la ReLU estándar:
import numpy as np
import json
import js
from math import sqrt, pi
x = np.linspace(-3, 3, 300)
relu = np.maximum(0, x)
leaky_relu = np.where(x > 0, x, 0.01 * x)
# GELU approximation
gelu = 0.5 * x * (1 + np.tanh(sqrt(2/pi) * (x + 0.044715 * x**3)))
plot_data = [{
"title": "ReLU vs Leaky ReLU vs GELU",
"x_label": "x",
"y_label": "f(x)",
"x_data": x.tolist(),
"lines": [
{"label": "ReLU", "data": relu.tolist(), "color": "#3b82f6"},
{"label": "Leaky ReLU (α=0.01)", "data": leaky_relu.tolist(), "color": "#f59e0b"},
{"label": "GELU", "data": gelu.tolist(), "color": "#10b981"}
]
}]
js.window.py_plot_data = json.dumps(plot_data)
Sigmoid y Tanh
Sigmoid y tanh son las funciones de activación clásicas — dominaron las redes neuronales antes de ReLU y siguen siendo importantes en roles específicos hoy en día, aunque han sido ampliamente reemplazadas por activaciones de la familia ReLU en las capas ocultas.
Sigmoid comprime cualquier número real en el rango $(0, 1)$:
Recorramos el comportamiento:
- Cuando $x \gg 0$: $e^{-x} \to 0$, así que el denominador se acerca a 1, y $\sigma(x) \to 1$.
- Cuando $x \ll 0$: $e^{-x} \to \infty$, así que el denominador se dispara, y $\sigma(x) \to 0$.
- Cuando $x = 0$: $e^{0} = 1$, así que $\sigma(0) = \frac{1}{2}$ — el punto medio del rango de salida.
La salida siempre es positiva y está acotada entre 0 y 1, que es exactamente por qué sigmoid se usa cuando necesitas una probabilidad: capas de salida para clasificación binaria, puertas en LSTMs y GRUs, y mecanismos de atención. Sin embargo, usar sigmoid en capas ocultas de redes profundas es problemático por su gradiente:
Este gradiente alcanza su máximo en $x = 0$, donde $\sigma'(0) = 0.5 \times 0.5 = 0.25$. Para $|x|$ grande, el gradiente se desvanece hacia cero. Este es el problema del gradiente desvaneciente : en una red con $n$ capas sigmoid, los gradientes se multiplican a través de la regla de la cadena, y como cada sigmoid contribuye como máximo un factor de 0.25, el gradiente que llega a las primeras capas decae aproximadamente como $0.25^n$. Con 10 capas, eso es un factor de aproximadamente $10^{-6}$ — las primeras capas esencialmente dejan de aprender.
Tanh está estrechamente relacionada con sigmoid pero produce valores en $(-1, 1)$:
La segunda forma muestra que tanh es simplemente una sigmoid reescalada y desplazada. Las diferencias clave:
- Centrada en cero: las salidas de tanh van de $-1$ a $1$, centradas alrededor de cero. Esto es mejor para capas ocultas porque las activaciones centradas en cero no sesgan sistemáticamente la dirección del gradiente. Con sigmoid (salidas siempre positivas), los gradientes sobre los pesos tienen todos el mismo signo dentro de una capa, lo que obliga a la optimización a zigzaguear.
- Gradiente más fuerte: la derivada es $\tanh'(x) = 1 - \tanh^2(x)$, que alcanza un máximo de 1.0 en $x = 0$ (comparado con 0.25 de sigmoid). Tanh aún sufre de gradientes desvanecientes para $|x|$ grande, pero el problema es considerablemente menos severo — los gradientes decaen como $1.0^n$ cerca del origen en lugar de $0.25^n$.
Los gráficos a continuación muestran ambas funciones y sus gradientes lado a lado. Observa cómo el pico del gradiente de tanh es cuatro veces mayor que el de sigmoid:
import numpy as np
import json
import js
x = np.linspace(-5, 5, 200)
sigmoid = 1 / (1 + np.exp(-x))
sigmoid_grad = sigmoid * (1 - sigmoid)
tanh = np.tanh(x)
tanh_grad = 1 - tanh**2
plot_data = [
{
"title": "Sigmoid y Tanh",
"x_label": "x",
"y_label": "f(x)",
"x_data": x.tolist(),
"lines": [
{"label": "Sigmoid", "data": sigmoid.tolist(), "color": "#8b5cf6"},
{"label": "Tanh", "data": tanh.tolist(), "color": "#ec4899"}
]
},
{
"title": "Gradientes: Sigmoid vs Tanh",
"x_label": "x",
"y_label": "f'(x)",
"x_data": x.tolist(),
"lines": [
{"label": "Gradiente Sigmoid (máx 0.25)", "data": sigmoid_grad.tolist(), "color": "#8b5cf6"},
{"label": "Gradiente Tanh (máx 1.0)", "data": tanh_grad.tolist(), "color": "#ec4899"}
]
}
]
js.window.py_plot_data = json.dumps(plot_data)
Swish / SiLU
Swish (también llamada SiLU — Unidad Lineal Sigmoid) fue descubierta mediante búsqueda automatizada de arquitecturas neuronales (Ramachandran et al., 2017) :
La estructura es similar a GELU — ambas multiplican $x$ por una función de compuerta — pero Swish usa la sigmoid $\sigma(x)$ como compuerta en lugar de la CDF gaussiana $\Phi(x)$. El comportamiento sigue el mismo patrón:
- Cuando $x \gg 0$: $\sigma(x) \approx 1$, así que $\text{Swish}(x) \approx x$ — la identidad, como ReLU.
- Cuando $x \ll 0$: $\sigma(x) \approx 0$, así que $\text{Swish}(x) \approx 0$ — la entrada se suprime, como ReLU.
- Cerca de $x \approx -1.28$: Swish desciende ligeramente por debajo de cero (alcanzando un mínimo de aproximadamente $-0.28$). Esta pequeña región negativa es una característica distintiva — a diferencia de ReLU, que es estrictamente no negativa, Swish permite un pequeño "undershoot" que parece ayudar a la optimización al proporcionar una señal de gradiente más rica.
Swish es suave y no monótona (por esa pequeña caída bajo cero). El gradiente suave en todas partes evita la discontinuidad abrupta de ReLU en el origen, y la no monotonicidad proporciona una forma de regularización implícita — las entradas cercanas a cero se tratan de manera diferente a las fuertemente positivas o negativas.
Cuándo Usar Cuál
Con tantas activaciones disponibles, aquí tienes una guía práctica para elegir una:
- ReLU: la opción por defecto para la mayoría de las capas ocultas. Simple, rápida y funciona bien. Úsala a menos que tengas una razón específica para no hacerlo.
- GELU: el estándar en transformers (GPT, BERT, LLaMA). Sus gradientes suaves ayudan con la optimización en modelos muy profundos, y se ha convertido en la opción de facto para arquitecturas basadas en atención.
- Swish / SiLU: común en modelos de visión (EfficientNet) y algunas arquitecturas de LLM (LLaMA usa SiLU en su red feed-forward). Muy similar a GELU en la práctica.
- Sigmoid: capa de salida para clasificación binaria (produce una probabilidad en $(0, 1)$). También se usa como compuerta en arquitecturas recurrentes (LSTM, GRU) y en mecanismos de atención.
- Tanh: capa de salida cuando necesitas valores en $(-1, 1)$. Se usa en algunos esquemas de normalización y arquitecturas RNN más antiguas. Preferida sobre sigmoid para capas ocultas cuando se necesita una activación saturante, gracias a su salida centrada en cero.
- Leaky ReLU: útil cuando la ReLU muerta es un problema — típicamente en redes más pequeñas, con tasas de aprendizaje agresivas, o con entradas predominantemente negativas.
Quiz
Pon a prueba tu comprensión de las funciones de activación y su papel en las redes neuronales.
¿Por qué las redes neuronales necesitan funciones de activación?
¿Qué es el problema de la 'ReLU muerta'?
¿Por qué sigmoid es problemática para capas ocultas en redes profundas?
¿Qué hace a GELU adecuada para transformers?