Funciones de activación: El pulso de las redes neuronales

Introducción

En el amplio universo de la inteligencia artificial, las redes neuronales artificiales han transformado la forma en que enfrentamos problemas complejos en múltiples disciplinas, desde la visión por computadora y el reconocimiento de imágenes, hasta el procesamiento del lenguaje natural y los sistemas de recomendación. Estas redes, inspiradas en el funcionamiento del cerebro humano, son capaces de aprender patrones, tomar decisiones y generalizar a partir de grandes cantidades de datos. Sin embargo, para que estas redes puedan ir más allá de simples relaciones lineales, es necesario incorporar un elemento esencial que permite capturar la complejidad de los datos reales: las funciones de activación. A lo largo de este post se revisarán 5 funciones que aparecen en arquitecturas ampliamente utilizadas, desde redes neuronales densas tradicionales hasta redes convolucionales, recurrentes, LSTM y modelos más recientes como los Transformers.

¿Qué es una función de activación?

Una función de activación es una función matemática que se aplica a una neurona o a un nodo de una red neuronal (NN). Su función principal es determinar la salida de esa neurona en función de sus entradas ponderadas. En otras palabras, decide si una neurona debe “activarse” o “dispararse” y, en caso afirmativo, cuál debe ser la intensidad de su señal al pasar a la siguiente capa. Este mecanismo es crucial para introducir la no linealidad en la red y permitirle aprender patrones y relaciones complejas a partir de los datos.

Una buena función de activación debe cumplir ciertas propiedades que favorecen un entrenamiento eficiente y estable en redes neuronales:
- No linealidad: Permite que la red aprenda relaciones complejas y no se reduzca a una simple transformación lineal.
- Diferenciabilidad: Es esencial para aplicar descenso de gradiente y ajustar los pesos mediante retropropagación.
- Derivada computacionalmente eficiente: Su cálculo debe ser rápido, ya que se evalúa millones de veces durante el entrenamiento.
- Centrada en cero: Favorece actualizaciones de pesos más equilibradas y estables, evitando sesgos en una sola dirección.
- Evitar el gradiente que se desvanece o explota: Una buena función debe mantener los gradientes dentro de un rango razonable para asegurar el aprendizaje en todas las capas.
- Evitar zonas muertas: Debe minimizar regiones donde el gradiente sea nulo, lo que impediría que ciertas neuronas sigan aprendiendo.

Tipos de funciones de activación

Existen muchos tipos de funciones de activación, cada una con propiedades únicas. La elección de la función puede afectar significativamente al rendimiento de un modelo y a la eficacia del entrenamiento.

1. Función Softmax

Dado un vector de entrada \(\mathbf{x} = [x_1, x_2, \dots, x_n]\), la función Softmax se define como:

\[ \text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^{K} e^{x_j}} \quad \text{para } i = 1, 2, \dots, K \]

Dónde:
- \(x_i\): Es el valor de entrada (logit) para la clase \(i\).
- \(e^{x_i}\): Es la función exponencial aplicada a \(x_i\).
- \(K\): Es el número de clases en el clasificador multiclase.
- \(\sum_{j=1}^{K} e^{x_j}\): Sumatoria de la función exponencial estándar del vector de salida.

Componente gráfico

# Definir función softmax
softmax <- function(x) {
  exp_x <- exp(x)
  exp_x / sum(exp_x)
}

# Secuencia de valores para el primer elemento del vector
x <- seq(-6, 6, length.out = 200)

# Evaluar softmax para vectores de la forma (x, 1, 2)
softmax_vals <- sapply(x, function(xi) softmax(c(xi, 1, 2))[1])

# Crear gráfico
plot(x, softmax_vals, type = "l", lwd = 2, col = "orange",
     main = "Función Softmax", ylab = "Softmax(x)", xlab = "x")

# Agregar cuadrícula
grid()

La función Softmax convierte un vector de valores reales en un vector de probabilidades. Es decir, toma un conjunto de puntuaciones arbitrarias (logits) y las transforma en valores entre 0 y 1, asegurándose de que la suma de todas las salidas sea igual a 1. Esto permite interpretar cada salida como la probabilidad de pertenecer a una clase específica.

Softmax no se aplica a valores individuales, sino a vectores completos, por lo que es comúnmente utilizada en la capa de salida de redes neuronales para clasificación multiclase. A diferencia de funciones como la sigmoide o la ReLU, que se aplican elemento por elemento, Softmax actúa globalmente sobre todos los elementos de la capa.

Características

  • Transforma salidas en probabilidades: todas entre 0 y 1, y su suma es 1.
  • Multiclase: ideal para clasificación con más de dos clases.
  • Sensibilidad relativa: amplifica diferencias entre entradas.
  • Dependencia global: el valor de una salida depende de todas las demás entradas.
  • No centrada en cero: todas las salidas son positivas.
  • Computacionalmente costosa: requiere exponenciales y sumas a nivel de vector.

Aplicaciones

Softmax es la elección estándar cuando nuestro modelo necesita clasificar entradas en una de varias categorías mutuamente excluyentes. Algunos ejemplos comunes son:
- Clasificación de imágenes (identificar objetos en fotos)
- Categorización de documentos (asignar temas al texto)
- Reconocimiento de entidades con nombre (identificación de nombres de personas, lugares, etc.)
- Reconocimiento de voz (mapeo de audio a texto)

Derivada

La derivada de la función softmax no es una derivada escalar, sino una derivada matricial (jacobiana), ya que la softmax transforma un vector en otro vector. La derivada parcial de la componente \(i\)-ésima de la softmax con respecto a la entrada \(x_j\) está dada por:

\[ \frac{\partial\ \mathrm{softmax}(x_i)}{\partial x_j} = \begin{cases}\mathrm{softmax}(x_i) \cdot (1 - \mathrm{softmax}(x_i)) & \text{si } i = j \\-\mathrm{softmax}(x_i) \cdot \mathrm{softmax}(x_j) & \text{si } i \neq j\end{cases} \]

Para un vector \(\mathbf{z} \in \mathbb{R}^n\), la derivada de la función Softmax es una matriz Jacobiana de dimensión \(n \times n\), con componentes:

Donde \(s_i = \text{Softmax}(z_i)\). - Si \(i = j\): representa la derivada de la componente respecto a sí misma. - Si \(i \neq j\): muestra cómo cambia la componente \(i\) cuando cambia la entrada \(j\) (por la dependencia global).

# Función softmax comparando x contra 0
softmax <- function(x) {
  exp(x) / (exp(x) + exp(0))
}

# Derivada cuando i = j
softmax_deriv_diag <- function(x) {
  s <- softmax(x)
  s * (1 - s)
}

# Derivada cuando i ≠ j
softmax_deriv_offdiag <- function(x) {
  s_i <- softmax(x)
  s_j <- softmax(0)  # comparando con 0
  -s_i * s_j
}

# Vector de entrada
x_vals <- seq(-6, 6, length.out = 300)

# Evaluamos derivadas
diag_vals <- softmax_deriv_diag(x_vals)
offdiag_vals <- softmax_deriv_offdiag(x_vals)

# Graficar
plot(x_vals, diag_vals, type = "l", col = "blue", lwd = 2,
     ylim = c(min(offdiag_vals), max(diag_vals)),
     xlab = "x", ylab = "Derivada parcial",
     main = "Derivada de la Función Softmax")

lines(x_vals, offdiag_vals, col = "red", lwd = 2, lty = 2)

legend("topright", legend = c("i = j",
                              "i ≠ j"),
       col = c("blue", "red"), lty = c(1, 2), lwd = 2)

grid()

2. Función Swish

La función Swish, propuesta por investigadores de Google, se ha convertido en una alternativa prometedora a funciones clásicas como ReLU. Está definida como:

\[ \text{Swish}(x) = x \cdot \sigma(x) = x \cdot \frac{1}{1 + e^{-x}} \]

donde \(\sigma(x)\) es la función sigmoide.

Esta función de activación es relativamente nueva (2017), y supera a ReLU para redes CNN más profundas. La ecuación que define esta función describe una Sigmoid(x), pero no tiene el problema de desvanecimiento del gradiente, el factor x hace que esta función sea importante nuevamente. Con respecto a su comparativa con ReLU, ReLU tenía el problema de producir salida 0 para valores negativos que no pueden añadirse a backpropagation, Swish maneja este problema parcialmente; sin embargo, el costo computacional de Swish es mayor y es un tema que debe evaluarse para su decisión ya que este costo será llevado al proceso de backpropagation.

Características

  • Es diferenciable en todo su dominio, a diferencia de ReLU que no lo es en \(x=0\).
  • Es no monótona, lo cual ha demostrado ser beneficioso en tareas de aprendizaje profundo.
  • Permite valores negativos, pero de forma controlada, lo que evita que neuronas queden completamente inactivas.
  • Mejora el flujo del gradiente, reduciendo el problema del desvanecimiento.

Swish tiene un comportamiento similar a ReLU cuando \(x\) es muy grande, pero suaviza la transición en valores cercanos a 0.

Aplicaciones

  • Usada en arquitecturas modernas como EfficientNet y MobileNetV3.
  • Visión por ordenador: Muy utilizada en redes neuronales convolucionales (CNN) para tareas como la clasificación de imágenes y la detección de objetos.
  • Procesamiento del lenguaje natural (PLN): útil en modelos basados en transformadores en los que es crucial captar relaciones complejas.
  • Aprendizaje por refuerzo: Swish es eficaz en entornos que requieren una toma de decisiones compleja.

Componente gráfico

# Función sigmoide
sigmoid <- function(x) {
  1 / (1 + exp(-x))
}

# Función Swish
swish <- function(x) {
  x * sigmoid(x)
}

# Valores de entrada
x_vals <- seq(-6, 6, length.out = 300)

# Evaluación
y_swish <- swish(x_vals)

# Gráfica de Swish
plot(x_vals, y_swish, type = "l", lwd = 2, col = "orange",
     main = "Función de Activación Swish", xlab = "x", ylab = "Swish(x)")
abline(h = 0, v = 0, col = "gray", lty = 2)
grid()

Derivada

La derivada de la función Swish con respecto a \(x\) es:

\[ \frac{d}{dx} \mathrm{swish}(x) = \sigma(x) + x \cdot \sigma(x) \cdot (1 - \sigma(x)) \]

Esta derivada puede descomponerse como la suma de:
- el valor de la sigmoide \(\sigma(x)\),
- más un término de corrección que incluye la pendiente de la sigmoide y el valor de \(x\).

# Derivada de Swish
swish_deriv <- function(x) {
  s <- sigmoid(x)
  s + x * s * (1 - s)
}

# Valores de entrada
x_vals <- seq(-6, 6, length.out = 300)

# Evaluación
y_deriv <- swish_deriv(x_vals)

# Gráfica de la derivada
plot(x_vals, y_deriv, type = "l", lwd = 2, col = "blue",
     main = "Derivada de la Función Swish", xlab = "x", ylab = "Swish'(x)")
abline(h = 0, v = 0, col = "gray", lty = 2)
grid()

3. Función ReLU(Unidad lineal rectificada)

La función ReLU simplemente rectifica los datos (x) negativos y los vuelve cero a la salida. Las entradas con valores positivos no sufren modificación alguna a la salida. Se define matemáticamente como:

\[ ReLU(x) = max(0, x) \] Es una función por partes:
\[ ReLU(x) = \begin{cases} x & \text{si } x > 0 \\ 0 & \text{si } x \leq 0 \end{cases} \] La función de activación ReLU se ha convertido en la más usada en los modelos Deep Learning durante los últimos años, lo cual se debe principalmente a:
- La no existencia de saturación, como sí ocurre en las funciones sigmoidal y tanh. Lo anterior hace que el algoritmo del gradiente descendente converja mucho más rápidamente, facilitando así el entrenamiento.
- Es más fácil de implementar computacionalmente en comparación con las otras dos funciones, que requieren el cálculo de funciones matemáticas más complejas como la exponencial.

Características

  • No lineal, pero computacionalmente eficiente.
  • No acota los valores positivos, pero bloquea los negativos.
  • Su derivada es constante (1) para x > 0 y nula (0) para x < 0.
  • Produce sparsity: muchas salidas exactamente en cero.
  • Ayuda a evitar el problema del desvanecimiento del gradiente.

Aplicaciones

  • Se utiliza ampliamente en redes neuronales convolucionales (CNN) para tareas de procesamiento de imágenes.
  • Se aplica en arquitecturas de aprendizaje profundo en dominios como la visión por computadora y el procesamiento del lenguaje natural (PLN).

Componente gráfico

# Función ReLU
relu <- function(x) {
  pmax(0, x)
}

# Valores de entrada
x_vals <- seq(-6, 6, length.out = 300)

# Evaluación
y_relu <- relu(x_vals)

# Gráfica de ReLU
plot(x_vals, y_relu, type = "l", lwd = 2, col = "orange",
     main = "Función ReLU", xlab = "x", ylab = "ReLU(x)")
abline(h = 0, v = 0, col = "gray", lty = 2)
grid()

Derivada

La derivada de la función ReLU con respecto a \(x\) es:

\[ \frac{d}{dx} ReLU(x) = \begin{cases} 1 & \text{si } x > 0 \\ 0 & \text{si } x < 0 \\ \text{indefinida} & \text{si } x = 0 \end{cases} \]
Esto significa:
- Para entradas positivas (x > 0), la función es lineal y su derivada es 1.
- Para entradas negativas (x < 0), la salida se aplana y la derivada es 0, lo que implica que los gradientes no fluyen y el nodo deja de aprender.
- En x = 0 la derivada es técnicamente indefinida, pero en la práctica,se define como 0 o 1 para simplificar los cálculos.

drelu <- function(x) {
  ifelse(x > 0, 1, 0)  # Definimos 0 en x = 0 por simplicidad
}

x_vals <- seq(-6, 6, length.out = 300)
y_drelu <- drelu(x_vals)

plot(x_vals, y_drelu, type = "l", lwd = 2, col = "blue",
     main = "Derivada de la función ReLU",
     xlab = "x", ylab = "dReLU/dx")
abline(h = 0, v = 0, col = "gray", lty = 2)
grid()

4. Función Softplus

La función Softplus es una versión suavizada de la función ReLU, usada como función de activación en redes neuronales. Se define como:

\[ \mathrm{softplus}(x) = \ln(1 + e^x) \] Donde:
- El término \(\ln(1 + e^x)\) suaviza la operación de activación.

La función Softplus suele considerarse una versión más suave de la función ReLU. Si bien ReLU es simple y eficaz, puede presentar problemas, como provocar la “muerte” de neuronas si siempre generan cero para entradas negativas. Softplus evita este problema proporcionando una salida suave y continua tanto para entradas positivas como negativas. Además, Softplus es una función diferenciable en todo su dominio, a diferencia de ReLU, que presenta una discontinuidad en cero. Asimismo, la naturaleza continua y diferenciable de Softplus facilita el funcionamiento eficaz de los algoritmos de optimización basados en gradientes, garantizando un aprendizaje fluido durante el entrenamiento.

La función Softplus ofrece mayor estabilidad numérica que otras funciones de activación, ya que evita los problemas derivados de valores muy grandes o muy pequeños. Presenta una salida suave y, para valores de entrada muy grandes o muy pequeños, se comporta de forma predecible, lo que reduce el riesgo de desbordamiento o subdesbordamiento en los cálculos.

Características

  • La salida es siempre positiva.
  • La función aumenta suavemente y siempre es diferenciable, a diferencia de ReLU, que tiene un punto culminante en cero.
  • Para entradas negativas, la función se acerca a cero, pero a diferencia de ReLU, nunca llega exactamente a cero, evitando el problema de las “neuronas moribundas”.

Aplicaciones

Softplus es útil cuando:
- Necesita una función de activación suave y continua.
- Quiere evitar el problema de neuronas moribundas que ocurre con ReLU.
- La red maneja entradas tanto positivas como negativas, y desea que la salida permanezca no negativa.
- Prefieres una función diferenciable en toda la red para una optimización basada en gradientes más suave.

Componente gráfico

# Función Softplus
softplus <- function(x) {
  log(1 + exp(x))
}

# Valores de entrada
x_vals <- seq(-6, 6, length.out = 300)

# Evaluación
y_softplus <- softplus(x_vals)

# Gráfica de Softplus
plot(x_vals, y_softplus, type = "l", lwd = 2, col = "orange",
     main = "Función Softplus", xlab = "x", ylab = "Softplus(x)")
abline(h = 0, v = 0, col = "gray", lty = 2)
grid()

Derivada

La derivada de la función Softplus con respecto a \(x\) es:

\[ \frac{d}{dx} \mathrm{softplus}(x) = \frac{1}{1 + e^{-x}} = \sigma(x) \] Se observa que la derivada de la función Softplus es exactamente la función sigmoidea. Esta propiedad hace que Softplus sea útil cuando se desea controlar la suavidad del gradiente, ya que tiene una derivada continua y suave.

Esto significa que:
- Para valores grandes de \(x\), la derivada se aproxima a 1 (como ReLU).
- Para valores negativos, la pendiente disminuye suavemente, pero nunca se vuelve cero (a diferencia de ReLU).

# Función sigmoide (derivada de la Softplus)
sigmoid <- function(x) {
  1 / (1 + exp(-x))
}

# Valores de entrada
x_vals <- seq(-6, 6, length.out = 300)

# Evaluación de la derivada
y_derivada_softplus <- sigmoid(x_vals)

# Gráfica de la derivada de Softplus
plot(x_vals, y_derivada_softplus, type = "l", lwd = 2, col = "blue",
     main = "Derivada de la Función Softplus", xlab = "x", ylab = "d(Softplus)/dx")
abline(h = 0, v = 0, col = "gray", lty = 2)
grid()

5. Función Tahn (Tangente Hiperbólica)

La función de activación Tanh se asemeja a la función Sigmoide, pero proporciona resultados en el rango de -1 a 1 en lugar de 0 a 1. Al igual que la Sigmoide, presenta una curva característica en forma de “S” y es diferenciable, lo que la hace adecuada para redes neuronales. Se define matemáticamente como:

\[ \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} \] Debido a que las salidas de Tanh están centradas en cero, conduce a mejores actualizaciones de peso y una convergencia más rápida en el entrenamiento basado en gradientes en comparación con Sigmoid, que está restringido al rango de 0 a 1 .

Características

  • La función también tiene una curva en forma de S, similar a la función de activación logística.
  • A diferencia de la función de activación sigmoide, esta función está centrada en 0, con un rango de -1 a 1.
  • Similar a la función sigmoide, esta función es diferenciable.
  • Similar a la función sigmoide, esta función es monótona, pero su derivada no lo es.

Aplicaciones

  • Comúnmente se aplica en capas ocultas de redes neuronales para garantizar que los valores negativos, neutrales y positivos se representen de manera efectiva.
  • Útil en tareas de procesamiento del lenguaje natural (PLN) y pronóstico de series de tiempo .

Componente gráfico

# Función tanh
tanh_func <- function(x) {
  tanh(x)
}

# Valores de entrada
x_vals <- seq(-5, 5, length.out = 400)

# Evaluación
y_tanh <- tanh_func(x_vals)

# Gráfica de tanh
plot(x_vals, y_tanh, type = "l", lwd = 2, col = "orange",
     main = "Función tanh(x)", xlab = "x", ylab = "tanh(x)")
abline(h = 0, v = 0, col = "gray", lty = 2)
grid()

Derivada

La derivada de la función Tanh con respecto a \(x\) es:

\[ \frac{d}{dx} ReLU(x) = \begin{cases} 1 & \text{si } x > 0 \\ 0 & \text{si } x < 0 \\ \text{indefinida} & \text{si } x = 0 \end{cases} \]
Esto significa:
- Para entradas positivas (x > 0), la función es lineal y su derivada es 1.
- Para entradas negativas (x < 0), la salida se aplana y la derivada es 0, lo que implica que los gradientes no fluyen y el nodo deja de aprender.
- En x = 0 la derivada es técnicamente indefinida, pero en la práctica,se define como 0 o 1 para simplificar los cálculos.

# Derivada de tanh
dtanh_func <- function(x) {
  1 - tanh(x)^2
}

y_dtanh <- dtanh_func(x_vals)


# Gráfica de su derivada
plot(x_vals, y_dtanh, type = "l", lwd = 2, col = "blue",
     main = "Derivada de la función tanh(x)", xlab = "x", ylab = "d/dx tanh(x)")
abline(h = 0, v = 0, col = "gray", lty = 2)
grid()