Las librerías de alto nivel como TensorFlow y PyTorch ocultan la magia. Aquí construiremos una red neuronal desde cero usando solo NumPy para que entiendas realmente cómo aprende una máquina — sin cajas negras.
¿Qué es una red neuronal?
Una red neuronal artificial es un modelo matemático inspirado en el cerebro humano. Está compuesta por neuronas artificiales organizadas en capas que procesan información de forma encadenada. Cada neurona recibe entradas, las pondera, aplica una función de activación y pasa el resultado a la siguiente capa.
Los tres tipos de capas son:
- Capa de entrada (input layer) — recibe los datos en bruto
- Capas ocultas (hidden layers) — procesan y transforman la información
- Capa de salida (output layer) — produce el resultado final (clasificación, predicción)
El Perceptrón: la neurona más simple
Todo empieza con el perceptrón, una sola neurona que hace una cosa: decidir si una entrada activa una salida o no.
import numpy as np
class Perceptron:
def __init__(self, tasa_aprendizaje=0.01, epocas=1000):
self.lr = tasa_aprendizaje
self.epocas = epocas
self.pesos = None
self.sesgo = None
def activacion(self, x):
return 1 if x >= 0 else 0
def entrenar(self, X, y):
n_muestras, n_features = X.shape
self.pesos = np.zeros(n_features)
self.sesgo = 0
for _ in range(self.epocas):
for xi, yi in zip(X, y):
prediccion = self.activacion(np.dot(xi, self.pesos) + self.sesgo)
error = yi - prediccion
self.pesos += self.lr * error * xi
self.sesgo += self.lr * error
def predecir(self, X):
return [self.activacion(np.dot(x, self.pesos) + self.sesgo) for x in X]
# Probar con compuerta lógica AND
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0, 0, 0, 1])
p = Perceptron()
p.entrenar(X, y)
print(p.predecir(X)) # [0, 0, 0, 1] ✅
Funciones de activación
La función de activación determina si una neurona "se activa" y qué tan fuerte. Las más importantes:
import numpy as np
# Escalón (Perceptrón clásico) — solo 0 o 1
def escalon(x):
return np.where(x >= 0, 1, 0)
# Sigmoide — salida entre 0 y 1, ideal para probabilidades
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# ReLU — la más usada en redes profundas modernas
def relu(x):
return np.maximum(0, x)
# Softmax — para clasificación multiclase (última capa)
def softmax(x):
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
Red Neuronal completa con backpropagation
Aquí la implementación completa de una red de 2 capas desde cero — incluyendo la propagación hacia adelante (forward pass) y el algoritmo de retropropagación (backpropagation) que es donde ocurre el aprendizaje:
import numpy as np
class RedNeuronal:
def __init__(self, capa_entrada, capa_oculta, capa_salida, lr=0.01):
# Inicializar pesos con valores pequeños aleatorios
self.W1 = np.random.randn(capa_entrada, capa_oculta) * 0.01
self.b1 = np.zeros((1, capa_oculta))
self.W2 = np.random.randn(capa_oculta, capa_salida) * 0.01
self.b2 = np.zeros((1, capa_salida))
self.lr = lr
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def sigmoid_deriv(self, x):
s = self.sigmoid(x)
return s * (1 - s)
def forward(self, X):
# Capa 1
self.z1 = np.dot(X, self.W1) + self.b1
self.a1 = self.sigmoid(self.z1)
# Capa 2 (salida)
self.z2 = np.dot(self.a1, self.W2) + self.b2
self.a2 = self.sigmoid(self.z2)
return self.a2
def backward(self, X, y):
m = X.shape[0]
# Gradiente de la capa de salida
dz2 = self.a2 - y
dW2 = np.dot(self.a1.T, dz2) / m
db2 = np.sum(dz2, axis=0, keepdims=True) / m
# Gradiente de la capa oculta
dz1 = np.dot(dz2, self.W2.T) * self.sigmoid_deriv(self.z1)
dW1 = np.dot(X.T, dz1) / m
db1 = np.sum(dz1, axis=0, keepdims=True) / m
# Actualizar pesos (descenso de gradiente)
self.W2 -= self.lr * dW2
self.b2 -= self.lr * db2
self.W1 -= self.lr * dW1
self.b1 -= self.lr * db1
def entrenar(self, X, y, epocas=10000):
for epoca in range(epocas):
self.forward(X)
self.backward(X, y)
if epoca % 1000 == 0:
perdida = np.mean((self.a2 - y) ** 2)
print(f"Época {epoca} — Pérdida: {perdida:.4f}")
def predecir(self, X):
return (self.forward(X) > 0.5).astype(int)
# Ejemplo: aprender la compuerta XOR (imposible con perceptrón simple)
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([[0],[1],[1],[0]])
red = RedNeuronal(capa_entrada=2, capa_oculta=4, capa_salida=1, lr=0.1)
red.entrenar(X, y, epocas=10000)
print(red.predecir(X)) # [[0],[1],[1],[0]] ✅
¿Qué acaba de pasar?
El algoritmo de backpropagation calcula cuánto contribuyó cada peso al error total usando la regla de la cadena del cálculo diferencial. Luego ajusta todos los pesos en la dirección que reduce ese error. Repitiendo este proceso miles de veces, la red "aprende" los patrones en los datos.
* 0.01).
El siguiente paso: usar PyTorch
Una vez que entiendes los fundamentos, las librerías de alto nivel cobran sentido real. El mismo XOR en PyTorch:
import torch
import torch.nn as nn
X = torch.FloatTensor([[0,0],[0,1],[1,0],[1,1]])
y = torch.FloatTensor([[0],[1],[1],[0]])
modelo = nn.Sequential(
nn.Linear(2, 4),
nn.Sigmoid(),
nn.Linear(4, 1),
nn.Sigmoid()
)
optimizer = torch.optim.SGD(modelo.parameters(), lr=0.1)
criterio = nn.MSELoss()
for epoca in range(5000):
prediccion = modelo(X)
perdida = criterio(prediccion, y)
optimizer.zero_grad()
perdida.backward()
optimizer.step()
"Usar TensorFlow sin entender backpropagation es como manejar un auto sin saber qué es un motor. Puedes llegar a donde quieres, pero no sabrás qué hacer cuando algo falle."