Descenso de Gradiente (n-D)
Actualización simultánea de todos los parámetros.
Capítulo 2
Para funciones multivariadas , la derivada parcial mide el cambio en una dirección:
El gradiente es el vector de todas las derivadas parciales. Apunta en la dirección de máximo crecimiento de f:
Actualización simultánea de todos los parámetros.
Para , el gradiente es:.
import numpy as np
# Gradiente numérico vs analítico
def f(x, y):
return x**2 + y**2 + x*y
def grad_f(x, y):
return np.array([2*x + y, 2*y + x])
# Gradiente numérico (aproximación)
h = 1e-6
x, y = 1.0, 2.0
grad_num = np.array([
(f(x+h, y) - f(x-h, y))/(2*h),
(f(x, y+h) - f(x, y-h))/(2*h)
])
print(f"Gradiente analítico: {grad_f(x, y)}")
print(f"Gradiente numérico: {grad_num}")
print(f"Error: {np.linalg.norm(grad_f(x, y) - grad_num):.2e}")Para , la Jacobiana J es una matriz m×n con todas las derivadas parciales:
import numpy as np
# Jacobiana de f(x,y) = [x^2 + y, x + y^2]
def f(xy):
x, y = xy
return np.array([x**2 + y, x + y**2])
# Jacobiana analítica: [[2x, 1], [1, 2y]]
def jacobian(xy):
x, y = xy
return np.array([[2*x, 1], [1, 2*y]])
# Jacobiana numérica
from scipy.optimize import approx_fprime
xy = np.array([1.0, 2.0])
J_num = np.array([approx_fprime(xy, lambda p: f(p)[0], 1e-6),
approx_fprime(xy, lambda p: f(p)[1], 1e-6)])
print("Jacobiana analítica:
", jacobian(xy))
print("Jacobiana numérica:
", J_num)Si y , entonces:
En notación matricial: . Esta es la base del backpropagation.
# Regla de la cadena: ejemplo concreto
# y = f(z) = z^2, z = g(x) = 3x
# dy/dx = dy/dz * dz/dx = 2z * 3 = 2(3x) * 3 = 18x
x = 2.0
z = 3 * x
dy_dz = 2 * z
dz_dx = 3
dy_dx = dy_dz * dz_dx
print(f"dy/dx = {dy_dx} (verificación: 18*2 = {18*x})")
# Caso multivariado: softmax + cross-entropy (típico en clasificación)
# Se resuelve con la regla de la cadena vectorialIdentidades útiles para ML (todas escalares en función de matrices/vectores):
import numpy as np
# Verificar: ∇_x (x^T A x) = (A + A^T)x
A = np.array([[2, 1], [1, 3]])
x = np.array([1.0, 2.0])
grad_analitico = (A + A.T) @ x
# Numérico
h = 1e-6
def f(x):
return x @ A @ x
grad_num = np.array([
(f(x + h*np.array([1,0])) - f(x - h*np.array([1,0])))/(2*h),
(f(x + h*np.array([0,1])) - f(x - h*np.array([0,1])))/(2*h),
])
print(f"Analítico: {grad_analitico}")
print(f"Numérico: {grad_num}")
print(f"Coinciden: {np.allclose(grad_analitico, grad_num)}")