En el artículo anterior entrenamos una sola neurona y vimos algo muy importante:
Aunque el código funciona, no puede aprender ciertos patrones.
Eso no es un bug.
Es una limitación matemática.
En este artículo damos el salto clave:
👉 añadir una capa oculta
👉 usar varias neuronas
👉 resolver un problema que antes era imposible
Aquí empieza una red neuronal real.
El problema que una sola neurona no puede resolver: XOR
Recordemos el dataset:
X = np.array([ [0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])y = np.array([[0], [1], [1], [0]])
Este es el famoso problema XOR.
Propiedad clave:
- No es separable linealmente
- No se puede resolver con una sola frontera recta
Por eso una sola neurona falla siempre.
Por qué una capa oculta cambia todo
Al añadir una capa oculta:
- Dejamos de aprender una sola frontera
- Empezamos a combinar múltiples transformaciones
- Introducimos verdadera no linealidad
En términos simples:
Una capa oculta permite “doblar” el espacio.
Arquitectura de la red que vamos a construir
Nuestra red tendrá:
- 2 entradas
- 1 capa oculta con varias neuronas
- 1 neurona de salida
Esquema mental:
X → (W1, b1) → tanh → (W2, b2) → sigmoid → ŷ
Esto ya no es una sola neurona.
Es una red neuronal multicapa (MLP).
Inicializar los parámetros
Supongamos:
- 2 entradas
- 4 neuronas ocultas
- 1 salida
rng = np.random.default_rng(42)W1 = rng.normal(0, 1, size=(2, 4))b1 = np.zeros((1, 4))W2 = rng.normal(0, 1, size=(4, 1))b2 = np.zeros((1, 1))
Dimensiones importantes:
W1: (2 → 4)W2: (4 → 1)
Forward pass con capa oculta
def forward(X, W1, b1, W2, b2): Z1 = X @ W1 + b1 A1 = np.tanh(Z1) Z2 = A1 @ W2 + b2 y_hat = sigmoid(Z2) return Z1, A1, Z2, y_hat
Aquí ocurre la magia:
A1crea representaciones no lineales- La salida ya no depende linealmente de
X
Backpropagation con capa oculta
Ahora el error debe viajar hacia atrás por dos capas.
Paso 1: salida
def backward(X, y, Z1, A1, y_hat, W2): dZ2 = y_hat - y dW2 = A1.T @ dZ2 db2 = dZ2.sum(axis=0, keepdims=True)
Paso 2: capa oculta
La derivada de tanh es:tanh′(x)=1−tanh2(x)
dA1 = dZ2 @ W2.T
dZ1 = dA1 * (1 - A1 ** 2)
dW1 = X.T @ dZ1
db1 = dZ1.sum(axis=0, keepdims=True)
return dW1, db1, dW2, db2
Esto es backpropagation completo, sin librerías externas.
Loop de entrenamiento (red completa)
learning_rate = 0.1epochs = 10000for epoch in range(epochs): Z1, A1, Z2, y_hat = forward(X, W1, b1, W2, b2) loss = binary_cross_entropy_mean(y, y_hat) dW1, db1, dW2, db2 = backward(X, y, Z1, A1, y_hat, W2) W1 -= learning_rate * dW1 b1 -= learning_rate * db1 W2 -= learning_rate * dW2 b2 -= learning_rate * db2 if epoch % 1000 == 0: print(f"Epoch {epoch} | Loss: {loss:.4f}")
Ahora sí:
- La pérdida baja de verdad
- El modelo aprende XOR
Verificando el resultado
_, _, _, y_hat = forward(X, W1, b1, W2, b2)print("Predicciones:")print(y_hat.round(3))print("Clases:")print((y_hat >= 0.5).astype(int))
Resultado esperado:
[0, 1, 1, 0]
🎉 Problema resuelto.
Qué acabas de lograr
Sin frameworks, sin magia, sin atajos:
- Construiste una red neuronal real
- Entendiste por qué una capa oculta es necesaria
- Implementaste backpropagation completo
- Resolviste un problema no lineal
Esto es exactamente lo que ocurre dentro de librerías como Keras o PyTorch.
Qué viene después (opcional, pero poderoso)
En el Artículo #8 podemos ir en varias direcciones:
- Visualizar qué aprende la capa oculta
- Ajustar número de neuronas
- Aprender sobre overfitting
- Reescribir todo en una clase
NeuralNetwork - Comparar con PyTorch (misma red)
Tú decides cómo continuamos.