Problema
XOR (o exclusivo) es el problema clásico que una sola neurona no puede resolver, pero una red con una capa oculta sí. Entrena una red con 2 entradas, 2 neuronas ocultas y 1 salida, todas sigmoides (σ(z) = 1 / (1 + e^(-z))), con la pérdida L = (y - target)².
Los datos de entrenamiento, usados siempre en este orden en cada época:
| entrada | target |
|---|---|
| [0, 0] | 0 |
| [0, 1] | 1 |
| [1, 0] | 1 |
| [1, 1] | 0 |
Para cada ejemplo:
- Hacia adelante: a_j = σ(x₁ × hidden_weights[j][0] + x₂ × hidden_weights[j][1] + hidden_biases[j]), y = σ(a₁ × output_weights[0] + a₂ × output_weights[1] + output_bias)
- delta_out = 2 × (y - target) × y × (1 - y), y delta_j = delta_out × output_weights[j] × a_j × (1 - a_j), usando los pesos antes de la actualización de este ejemplo
- Actualiza cada parámetro con la tasa de aprendizaje lr: output_weights[j] -= lr × delta_out × a_j, output_bias -= lr × delta_out, hidden_weights[j][i] -= lr × delta_j × x_i, hidden_biases[j] -= lr × delta_j
Implementa train_xor(hidden_weights, hidden_biases, output_weights, output_bias, learning_rate, epochs).
Salida
Después del entrenamiento, devuelve las predicciones y de la red para las entradas [0, 0], [0, 1], [1, 0] y [1, 1], en ese orden.
Restricciones
0 ≤ epochs ≤ 5000
0 < learning_rate ≤ 5
-2 ≤ cada peso y sesgo inicial ≤ 2
Ejemplos
Entrada:
epochs = 0
output_bias = 0.0
hidden_biases = [0.0, 0.3]
learning_rate = 1.5
hidden_weights = [
[0.2, 0.8],
[0.9, -0.7]
]
output_weights = [-0.6, 0.4]
Salida esperada:
expected = [0.482451, 0.436972, 0.494378, 0.452729]
Entrada:
long long epochs = 0;
double output_bias = 0.0;
std::vector<double> hidden_biases = {0.0, 0.3};
double learning_rate = 1.5;
std::vector<std::vector<double>> hidden_weights = {
{0.2, 0.8},
{0.9, -0.7}
};
std::vector<double> output_weights = {-0.6, 0.4};
Salida esperada:
std::vector<double> expected = {0.482451, 0.436972, 0.494378, 0.452729};
Entrada:
long epochs = 0;
double output_bias = 0.0;
double[] hidden_biases = new double[] {0.0, 0.3};
double learning_rate = 1.5;
double[][] hidden_weights = new double[][] {
new double[] {0.2, 0.8},
new double[] {0.9, -0.7}
};
double[] output_weights = new double[] {-0.6, 0.4};
Salida esperada:
double[] expected = new double[] {0.482451, 0.436972, 0.494378, 0.452729};
Explicación
Con 0 épocas no se entrena nada, así que el resultado es solo la propagación hacia adelante con los pesos iniciales: aproximadamente [0.482, 0.437, 0.494, 0.453]. La red sin entrenar da casi la misma respuesta a cada entrada.
Entrada:
epochs = 2000
output_bias = 0.0
hidden_biases = [0.0, 0.3]
learning_rate = 1.5
hidden_weights = [
[0.2, 0.8],
[0.9, -0.7]
]
output_weights = [-0.6, 0.4]
Salida esperada:
expected = [0.015727, 0.982119, 0.985515, 0.013682]
Entrada:
long long epochs = 2000;
double output_bias = 0.0;
std::vector<double> hidden_biases = {0.0, 0.3};
double learning_rate = 1.5;
std::vector<std::vector<double>> hidden_weights = {
{0.2, 0.8},
{0.9, -0.7}
};
std::vector<double> output_weights = {-0.6, 0.4};
Salida esperada:
std::vector<double> expected = {0.015727, 0.982119, 0.985515, 0.013682};
Entrada:
long epochs = 2000;
double output_bias = 0.0;
double[] hidden_biases = new double[] {0.0, 0.3};
double learning_rate = 1.5;
double[][] hidden_weights = new double[][] {
new double[] {0.2, 0.8},
new double[] {0.9, -0.7}
};
double[] output_weights = new double[] {-0.6, 0.4};
Salida esperada:
double[] expected = new double[] {0.015727, 0.982119, 0.985515, 0.013682};
Explicación
Tras 2000 épocas, las predicciones son aproximadamente [0.016, 0.982, 0.986, 0.014]: bajas para [0, 0] y [1, 1], altas para [0, 1] y [1, 0]. La red ha aprendido XOR.
Cómo abordarlo
Inténtalo primero por tu cuenta y luego abre las pistas una a una.
Pista 1
Procesa las cuatro muestras XOR en el mismo orden fijo en cada epoch, y actualiza los parámetros inmediatamente después de cada una.
Pista 2
Usa forward propagation y luego retropropaga el error de salida a ambas neuronas ocultas con los pesos actuales antes de aplicar las actualizaciones.
Pista 3
Ojo con la derivada de sigmoid y con que las actualizaciones de la capa oculta dependen de los valores de entrada, así que las entradas cero dejan algunos pesos sin cambios.
Complejidad esperada
tiempo O(epochs), espacio extra O(1)
Referencias
Este reto está adaptado para CodeUtility. Sigue el enunciado y las restricciones indicadas arriba.