Capítulo 2

Gradientes y Derivadas Parciales

2.1 Derivadas Parciales

Para funciones multivariadas f:RnRf: \mathbb{R}^n \to \mathbb{R}, la derivada parcial mide el cambio en una dirección:

fxi=limh0f(x1,,xi+h,,xn)f(x)h\frac{\partial f}{\partial x_i} = \lim_{h \to 0} \frac{f(x_1, \ldots, x_i + h, \ldots, x_n) - f(\mathbf{x})}{h}

2.2 El Gradiente

El gradiente es el vector de todas las derivadas parciales. Apunta en la dirección de máximo crecimiento de f:

f(x)=(fx1fx2fxn)T\nabla f(\mathbf{x}) = \begin{pmatrix} \frac{\partial f}{\partial x_1} & \frac{\partial f}{\partial x_2} & \cdots & \frac{\partial f}{\partial x_n} \end{pmatrix}^T

Descenso de Gradiente (n-D)

xt+1=xtηf(xt)\mathbf{x}_{t+1} = \mathbf{x}_t - \eta \nabla f(\mathbf{x}_t)

Actualización simultánea de todos los parámetros.

Regresión Lineal

Para L(β)=yXβ2L(\beta) = \|y - X\beta\|^2, el gradiente es:L=2XT(yXβ)\nabla L = -2X^T(y - X\beta).

python
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}")

2.3 Matriz Jacobiana

Para f:RnRm\mathbf{f}: \mathbb{R}^n \to \mathbb{R}^m, la Jacobiana J es una matriz m×n con todas las derivadas parciales:

Jij=fixjJ_{ij} = \frac{\partial f_i}{\partial x_j}
python
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)

2.4 Regla de la Cadena Multivariada

Si z=g(x)\mathbf{z} = \mathbf{g}(\mathbf{x}) y y=f(z)y = f(\mathbf{z}), entonces:

yx=yzzx\frac{\partial y}{\partial \mathbf{x}} = \frac{\partial y}{\partial \mathbf{z}} \frac{\partial \mathbf{z}}{\partial \mathbf{x}}

En notación matricial: xy=Jz(x)Tzy\nabla_{\mathbf{x}} y = J_{\mathbf{z}}(\mathbf{x})^T \nabla_{\mathbf{z}} y. Esta es la base del backpropagation.

python
# 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 vectorial

2.5 Gradientes de Matrices

Identidades útiles para ML (todas escalares en función de matrices/vectores):

x(aTx)=a\nabla_{\mathbf{x}} (\mathbf{a}^T \mathbf{x}) = \mathbf{a}
x(xTAx)=(A+AT)x\nabla_{\mathbf{x}} (\mathbf{x}^T A \mathbf{x}) = (A + A^T) \mathbf{x}
xx2=2x\nabla_{\mathbf{x}} \|\mathbf{x}\|^2 = 2\mathbf{x}
XXF2=2X\nabla_X \|X\|_F^2 = 2X
python
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)}")