Descenso de gradiente

Descenso de gradiente: el algoritmo de optimización fundamental en machine learning, explicado con intuición geométrica y código.
Autor/a

Santi López Pavón

Palabras clave

machine learning, aprendizaje automatico, Python, algebra lineal, optimizacion, regresion lineal, clasificacion, estadistica

1 Vector gradiente

El gradiente (\(\nabla\)) es un vector que agrupa todas las derivadas parciales de una función. Si una función tiene dos variables, su gradiente tendrá dos componentes, cada una encargada de medir la pendiente en la dirección de su respectivo eje coordenado.

\[f(x, y) = x^2 + y^2\]

El vector gradiente se construye uniendo ambas pendientes:

\[\nabla f(x, y) = \begin{bmatrix} 2x \\ 2y \end{bmatrix}\]

Si queremos evaluar el gradiente en un punto específico, por ejemplo en \((x=2, y=3)\), simplemente sustituimos los valores en el vector:

\[\nabla f(2, 3) = \begin{bmatrix} 2(2) \\ 2(3) \end{bmatrix} = \begin{bmatrix} 4 \\ 6 \end{bmatrix}\]

Geométricamente, este vector nos da la clave del plano tangente. Al contener las pendientes de las dos líneas principales que cruzan el punto, el gradiente define por completo la inclinación y orientación de ese plano en el espacio tridimensional.

Para optimizar y encontrar los puntos mínimos (o máximos) de una función, la regla de oro sigue siendo la misma tanto en una como en múltiples dimensiones: debemos buscar el lugar donde la pendiente sea cero.

Para que un punto sea el mínimo absoluto, no basta con que la función deje de cambiar en una sola dirección; debe dejar de cambiar en todas las variables a la vez.Geométricamente, esto significa que el plano tangente en el punto mínimo es perfectamente plano y paralelo al suelo (al plano XY). Para que un plano sea completamente horizontal, las pendientes de las líneas que lo sostienen deben ser cero simultáneamente.

Por lo tanto, el punto mínimo es aquel donde todas las derivadas parciales valen cero, lo que hace que el vector gradiente sea el vector nulo:

\[\nabla f(x, y) = \begin{bmatrix} \frac{\partial f}{\partial x} \\ \frac{\partial f}{\partial y} \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \end{bmatrix}\]

import numpy as np
import matplotlib.pyplot as plt

# 1. Definir la función
def f(X, Y):
    return X**2 + Y**2

# Crear la malla para la superficie
x = np.linspace(-2, 2, 50)
y = np.linspace(-2, 2, 50)
X, Y = np.meshgrid(x, y)
Z_superficie = f(X, Y)

# 2. Definir el plano tangente en el mínimo (0,0,0)
# Como las derivadas parciales en (0,0) son cero, la ecuación del plano es z = 0
Z_plano_minimo = np.zeros_like(X)

# 3. Graficar en 3D
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')

# Dibujar el paraboloide (azul translúcido)
ax.plot_surface(X, Y, Z_superficie, cmap='Blues', alpha=0.6, edgecolor='none')

# Dibujar el plano tangente horizontal en el fondo (verde translúcido)
# ¡Corregido! Quitamos el argumento 'name' que causaba el AttributeError
ax.plot_surface(X, Y, Z_plano_minimo, color='green', alpha=0.4)

# Marcar el punto mínimo (esfera roja)
ax.scatter([0], [0], [0], color='red', s=150, label='Mínimo Absoluto (Gradiente = [0, 0])', zorder=5)

2 Resolución análitica

Tenemos tres puntos de datos: \((1, 5)\), \((2, 7)\) y \((3, 8)\).La función del modelo es una línea recta:

\[f(x) = mx + b\]

Queremos encontrar los valores óptimos de \(m\) (la pendiente) y \(b\) (la intersección) que minimicen la distancia cuadrática a estos tres puntos.

\[E(m, b) = (m + b - 5)^2 + (2m + b - 7)^2 + (3m + b - 8)^2\]

Como tiene dos variables (\(m\) y \(b\)), el error forma un paraboloide en 3D. El mínimo absoluto estará donde ambas derivadas parciales sean cero.

2.0.1 Derivada Parcial respecto a \(m\) (\(\frac{\partial E}{\partial m}\))

Para derivar cada bloque respecto a \(m\), aplicamos la Regla de la Cadena (bajamos el exponente \(2\), dejamos lo de adentro igual, y multiplicamos por la derivada de lo de adentro respecto a \(m\)):

\[\frac{\partial E}{\partial m} = 2(m + b - 5)\cdot(1) + 2(2m + b - 7)\cdot(2) + 2(3m + b - 8)\cdot(3)\]

Ahora, desarrollamos multiplicando los coeficientes de afuera hacia adentro:

\[\frac{\partial E}{\partial m} = (2m + 2b - 10) + 4(2m + b - 7) + 6(3m + b - 8)\] \[\frac{\partial E}{\partial m} = (2m + 2b - 10) + (8m + 4b - 28) + (18m + 6b - 48)\]

Agrupamos los términos semejantes de \(m\), \(b\) y los números sueltos:

  • Términos de \(m\): \(2m + 8m + 18m = \mathbf{28m}\)
  • Términos de \(b\): \(2b + 4b + 6b = \mathbf{12b}\)
  • Números: \(-10 - 28 - 48 = \mathbf{-86}\)

\[\frac{\partial E}{\partial m} = 28m + 12b - 86\]

2.0.2 Derivada Parcial respecto a \(b\) (\(\frac{\partial E}{\partial b}\))

\[\frac{\partial E}{\partial b} = 2(m + b - 5)\cdot(1) + 2(2m + b - 7)\cdot(1) + 2(3m + b - 8)\cdot(1)\] \[\frac{\partial E}{\partial b} = (2m + 2b - 10) + (4m + 2b - 14) + (6m + 2b - 16)\]

  • Términos de \(m\): \(2m + 4m + 6m = \mathbf{12m}\)
  • Términos de \(b\): \(2b + 2b + 2b = \mathbf{6b}\)
  • Números: \(-10 - 14 - 16 = \mathbf{-40}\)

\[\frac{\partial E}{\partial b} = 12m + 6b - 40\]

2.0.3 Sistema de Ecuaciones (Gradiente = \([0, 0]\))

Para encontrar el mínimo, igualamos ambas derivadas parciales a cero. Esto nos deja un sistema de dos ecuaciones con dos incógnitas:

  • \(28m + 12b = 86\)
  • \(12m + 6b = 40\)

\[m = \frac{6}{4} = \mathbf{1.5}\] \[b = \frac{22}{6} = \frac{11}{3} \approx \mathbf{3.67}\]

La función del modelo que minimiza el error cuadrático para los tres puntos es:

\[f(x) = 1.5x + 3.67\]

  • Para \(x = 1\): El modelo predice \(1.5(1) + 3.67 = \mathbf{5.17}\). El valor real era \(5\). El error es de \(+0.17\)
  • Para \(x = 2\): El modelo predice \(1.5(2) + 3.67 = \mathbf{6.67}\). El valor real era \(7\). El error es de \(-0.33\)
  • Para \(x = 3\): El modelo predice \(1.5(3) + 3.67 = \mathbf{8.17}\). El valor real era \(8\). El error es de \(+0.17\)

Si elevas esos tres errores al cuadrado y los sumas, obtienes el valor más bajo posible para este conjunto de puntos (\(0.17^2 + (-0.33)^2 + 0.17^2 \approx \mathbf{0.166}\)).

¿Cómo puede ser el gradiente \([0,0]\) (lo que significa que la pendiente es cero y ya no hay cambios) si el modelo se sigue equivocando en los puntos reales?. El gradiente igual a \([0,0]\) ocurre en un gráfico completamente diferente al del modelo: el gráfico de la función de error \(E(m, b)\).

3 Descenso del gradiente

Cuando los modelos tienen millones de datos y parámetros, resolver el sistema de ecuaciones analíticamente es imposible para un ordenador porque requiere demasiada memoria. El descenso de gradiente es el método iterativo (paso a paso) que se usa en su lugar: empezamos en un punto aleatorio y vamos por la gráfica del error poco a poco.

Para ejecutar el descenso de gradiente necesitamos definir tres cosas:

  1. El punto de partida (Inicialización): Como no sabemos la respuesta, empezamos con valores totalmente aleatorios. Vamos a suponer que el modelo empieza con una pendiente \(m_0 = 0\) y una intersección \(b_0 = 0\).
  2. Las fórmulas del Gradiente: Ya las calculamos en el paso anterior usando derivadas parciales:

\[\frac{\partial E}{\partial m} = 28m + 12b - 86\] \[\frac{\partial E}{\partial b} = 12m + 6b - 40\]

  1. El Learning Rate (Tasa de Aprendizaje, \(\alpha\)): Es el tamaño del paso que daremos hacia abajo en cada iteración. Si es muy grande, podemos pasarnos de largo el mínimo; si es muy pequeño, tardaremos mucho en llegar al mínimo. Vamos a elegir un valor prudente: \(\alpha = 0.01\).

La regla de actualización para dar un paso hacia el mínimo es:

\[m_{\text{nuevo}} = m_{\text{actual}} - \alpha \cdot \frac{\partial E}{\partial m}\] \[b_{\text{nuevo}} = b_{\text{actual}} - \alpha \cdot \frac{\partial E}{\partial b}\]

Restamos el gradiente porque queremos ir en la dirección opuesta a la subida, es decir, queremos bajar.

3.0.1 Iteración 1: El Primer Paso hacia el Valle

Sustituimos \(m=0\) y \(b=0\) en las derivadas parciales para saber hacia dónde está la pendiente en nuestro punto de partida:

  • \(\frac{\partial E}{\partial m} = 28(0) + 12(0) - 86 = \mathbf{-86}\)
  • \(\frac{\partial E}{\partial b} = 12(0) + 6(0) - 40 = \mathbf{-40}\)

El vector gradiente en este momento es \(\nabla E = \begin{bmatrix} -86 \\ -40 \end{bmatrix}\). Como ambos números son negativos, nos están diciendo que si aumentamos \(m\) y \(b\), el error va a bajar.

Multiplicamos el gradiente por nuestro learning rate (\(0.01\)) y lo restamos a los valores actuales:

\[m_1 = 0 - 0.01 \cdot (-86) = 0 + 0.86 = \mathbf{0.86}\] \[b_1 = 0 - 0.01 \cdot (-40) = 0 + 0.40 = \mathbf{0.40}\]

Al acabar la primera iteración, nuestro modelo ha cambiado, Pasó de ser \(y = 0x + 0\) a ser \(y = 0.86x + 0.40\). El error total ha disminuido porque nos hemos movido hacia el mínimo de la función de error.

3.0.2 Iteración 2: Ajustando la Dirección

Ahora nuestros parámetros actuales son \(m = 0.86\) y \(b = 0.40\). Repetimos exactamente el mismo proceso mecánico.

Sustituimos los nuevos valores en las derivadas parciales:

  • \(\frac{\partial E}{\partial m} = 28(0.86) + 12(0.40) - 86 = 24.08 + 4.8 - 86 = \mathbf{-57.12}\)
  • \(\frac{\partial E}{\partial b} = 12(0.86) + 6(0.40) - 40 = 10.32 + 2.4 - 40 = \mathbf{-27.28}\)

Las pendientes numéricas (\(-57.12\) y \(-27.28\)) son más pequeñas que en la primera iteración (\(-86\) y \(-40\)). Esto significa geométricamente que la ladera se está volviendo menos empinada; nos estamos aproximando al fondo del valle.

Actualizamos los parámetros otra vez:

\[m_2 = 0.86 - 0.01 \cdot (-57.12) = 0.86 + 0.5712 = \mathbf{1.4312}\] \[b_2 = 0.40 - 0.01 \cdot (-27.28) = 0.40 + 0.2728 = \mathbf{0.6728}\]

Al terminar la segunda iteración, nuestra recta es \(y = 1.43x + 0.67\).

Si continuáramos haciendo esto durante unas 50 o 100 iteraciones más:

  • El valor de \(m\) se irá acercando cada vez más despacio a \(1.5\).
  • El valor de \(b\) seguirá subiendo gradualmente hacia \(3.67\).
  • A medida que nos acerquemos a esos números ideales, las derivadas parciales se irán aproximando a \(0\). Al valer casi cero, el término \(\alpha \cdot \text{gradiente}\) se volverá tan infinitamente pequeño que los parámetros dejarán de cambiar.