Forward Mode (AD)
Propaga derivadas hacia adelante con las entradas. Eficiente para funciones con pocas entradas y muchas salidas.
Capítulo 3
El backpropagation es la regla de la cadena aplicada eficientemente a redes de computación. Dos modos:
Propaga derivadas hacia adelante con las entradas. Eficiente para funciones con pocas entradas y muchas salidas.
El backpropagation. Calcula el gradiente propagando hacia atrás desde la salida. Eficiente para ML: muchas entradas, una salida (pérdida).
Para donde :
import numpy as np
# Forward + Backward manual
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def sigmoid_prime(z):
s = sigmoid(z)
return s * (1 - s)
x, w, b = 2.0, 0.5, 0.1
y_true = 1.0 # etiqueta
# Forward
z = w * x + b
a = sigmoid(z)
L = - (y_true * np.log(a) + (1 - y_true) * np.log(1 - a)) # BCE
# Backward
dL_da = -(y_true / a) + ((1 - y_true) / (1 - a))
da_dz = sigmoid_prime(z)
dz_dw = x
dz_db = 1.0
dL_dz = dL_da * da_dz
dL_dw = dL_dz * dz_dw
dL_db = dL_dz * dz_db
print(f"Pérdida: {L:.4f}")
print(f"∂L/∂w = {dL_dw:.4f}")
print(f"∂L/∂b = {dL_db:.4f}")
# Con autograd (JAX)
# import jax.numpy as jnp
# from jax import gradPara una red con capas ocultas, el gradiente se propaga capa por capa usando la regla de la cadena. Cada capa tiene un paso forward y backward.
import numpy as np
class RedNeuronal:
def __init__(self, n_input, n_hidden, n_output):
self.W1 = np.random.randn(n_input, n_hidden) * 0.01
self.b1 = np.zeros(n_hidden)
self.W2 = np.random.randn(n_hidden, n_output) * 0.01
self.b2 = np.zeros(n_output)
def forward(self, X):
self.z1 = X @ self.W1 + self.b1
self.a1 = np.maximum(0, self.z1) # ReLU
self.z2 = self.a1 @ self.W2 + self.b2
# Softmax implícito para clasificación
exp = np.exp(self.z2 - self.z2.max(axis=1, keepdims=True))
self.a2 = exp / exp.sum(axis=1, keepdims=True)
return self.a2
def backward(self, X, y, lr=0.01):
m = X.shape[0]
# Gradiente capa de salida (cross-entropy + softmax)
dz2 = self.a2 - y
dW2 = self.a1.T @ dz2 / m
db2 = dz2.mean(axis=0)
# Gradiente capa oculta
da1 = dz2 @ self.W2.T
dz1 = da1 * (self.z1 > 0) # derivada de ReLU
dW1 = X.T @ dz1 / m
db1 = dz1.mean(axis=0)
# Actualizar
self.W2 -= lr * dW2
self.b2 -= lr * db2
self.W1 -= lr * dW1
self.b1 -= lr * db1
# Ejemplo: XOR
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([[0],[1],[1],[0]])
y_onehot = np.eye(2)[y.flatten()]
red = RedNeuronal(2, 4, 2)
for i in range(1000):
red.forward(X)
red.backward(X, y_onehot, lr=0.5)
pred = red.forward(X).argmax(axis=1)
print(f"Predicciones: {pred}")
print(f"Reales: {y.flatten()}")devuelve la función gradiente. Composición: para segunda derivada.
computa gradientes vía autograd. Cada tensor guarda su .
graba operaciones para diferenciación automática backwards.