SolveConPython

Añadiendo una capa oculta en Python — construyendo una red neuronal real

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:

  • A1 crea 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)=1tanh2(x)\tanh'(x) = 1 – \tanh^2(x)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.1
epochs = 10000
for 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:

  1. Visualizar qué aprende la capa oculta
  2. Ajustar número de neuronas
  3. Aprender sobre overfitting
  4. Reescribir todo en una clase NeuralNetwork
  5. Comparar con PyTorch (misma red)

Tú decides cómo continuamos.