Problema
O XOR (ou exclusivo) é o problema clássico que um único neurônio não consegue resolver, mas uma rede com uma camada oculta consegue. Treine uma rede com 2 entradas, 2 neurônios ocultos e 1 saída, todos sigmoides (σ(z) = 1 / (1 + e^(-z))), usando a perda L = (y - target)².
Os dados de treinamento, sempre usados nesta ordem em cada época:
| entrada | target |
|---|---|
| [0, 0] | 0 |
| [0, 1] | 1 |
| [1, 0] | 1 |
| [1, 1] | 0 |
Para cada exemplo:
- Propagação direta: 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), e delta_j = delta_out × output_weights[j] × a_j × (1 - a_j), usando os pesos antes da atualização deste exemplo
- Atualize cada parâmetro com a taxa de aprendizado 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
Implemente train_xor(hidden_weights, hidden_biases, output_weights, output_bias, learning_rate, epochs).
Saída
Depois do treinamento, retorne as previsões y da rede para as entradas [0, 0], [0, 1], [1, 0] e [1, 1], nessa ordem.
Restrições
0 ≤ epochs ≤ 5.000
0 < learning_rate ≤ 5
-2 ≤ cada peso e viés inicial ≤ 2
Exemplos
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]
Saída 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};
Saída 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};
Saída esperada:
double[] expected = new double[] {0.482451, 0.436972, 0.494378, 0.452729};
Explicação
Com 0 épocas nada é treinado, então o resultado é só a propagação direta com os pesos iniciais: cerca de [0.482, 0.437, 0.494, 0.453]. A rede não treinada dá quase a mesma resposta para toda 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]
Saída 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};
Saída 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};
Saída esperada:
double[] expected = new double[] {0.015727, 0.982119, 0.985515, 0.013682};
Explicação
Depois de 2.000 épocas, as previsões ficam em torno de [0.016, 0.982, 0.986, 0.014]: baixas para [0, 0] e [1, 1], altas para [0, 1] e [1, 0]. A rede aprendeu o XOR.
Como abordar
Tente sozinho primeiro e depois abra uma dica de cada vez.
Dica 1
Processe as quatro amostras de XOR na mesma ordem fixa em toda época e atualize os parâmetros imediatamente após cada uma.
Dica 2
Use forward propagation, depois faça backpropagation do erro da saída para os dois neurônios ocultos com os pesos atuais antes de aplicar as atualizações.
Dica 3
Fique atento à derivada da sigmoid e ao fato de que as atualizações da camada oculta dependem dos valores de entrada, então entradas zero deixam alguns pesos inalterados.
Complexidade esperada
tempo O(epochs), espaço extra O(1)
Referências
Este desafio foi adaptado para o CodeUtility. Siga o enunciado e as restrições acima.