Decisão da MLP para XOR
Cada ponto representa uma combinação da tabela-verdade.
LABORATÓRIO 03 · XOR
Compare um Perceptron de uma camada com uma MLP. As regiões coloridas mostram, ao vivo, a decisão aprendida por cada arquitetura.
Cada ponto representa uma combinação da tabela-verdade.
Os valores dentro dos círculos são as ativações do forward pass.
Forward, deltas e pesos da última amostra.
Treine uma época para visualizar as contas.
Compare a queda do erro com o aumento da taxa de acertos.
Os resultados 1 ocupam cantos opostos. Uma única reta sempre deixa pelo menos um ponto do lado errado. A camada oculta permite combinar várias retas e formar uma decisão não linear.
LEITURA DO MODELO
Calcula u = w₁x₁ + w₂x₂ + b e produz ŷ = Step(u). Há apenas uma fronteira: u = 0.
Cada neurônio oculto calcula seu próprio potencial u. A combinação dessas ativações cria as duas regiões exigidas pelo XOR.
CÓDIGO DIDÁTICO
A camada oculta possui dois neurônios. O código separa forward pass, cálculo dos gradientes, backpropagation e atualização dos pesos.
// Tabela-verdade completa do XOR.
const dadosXOR = [
{ x: [0, 0], y: 0 },
{ x: [0, 1], y: 1 },
{ x: [1, 0], y: 1 },
{ x: [1, 1], y: 0 }
];
// Sigmoid comprime qualquer potencial u para o intervalo de 0 a 1.
function sigmoid(u) {
return 1 / (1 + Math.exp(-u));
}
// Calcula a derivada usando a ativação já obtida no forward pass.
function derivadaSigmoid(ativacao) {
return ativacao * (1 - ativacao);
}
// A semente fixa torna o exemplo reproduzível em qualquer execução.
let semente = 1;
// Gera um peso pseudoaleatório pequeno a partir da semente.
function pesoAleatorio() {
semente = (semente * 16807) % 2147483647;
return (semente / 2147483647) * 2 - 1;
}
// Cria uma arquitetura 2 → 2 → 1.
function criarRede() {
return {
oculta: [
{ pesos: [pesoAleatorio(), pesoAleatorio()], bias: pesoAleatorio() },
{ pesos: [pesoAleatorio(), pesoAleatorio()], bias: pesoAleatorio() }
],
saida: {
pesos: [pesoAleatorio(), pesoAleatorio()],
bias: pesoAleatorio()
}
};
}
// Multiplica entradas por pesos e acrescenta o bias.
function potencial(entradas, pesos, bias) {
return entradas.reduce(
(soma, entrada, indice) => soma + entrada * pesos[indice],
bias
);
}
// Executa somente o forward pass; nenhum peso é alterado aqui.
function forward(entradas, rede) {
const oculta = rede.oculta.map(neuronio => {
const u = potencial(entradas, neuronio.pesos, neuronio.bias);
const a = sigmoid(u);
return { u, a };
});
const uSaida = potencial(
oculta.map(neuronio => neuronio.a),
rede.saida.pesos,
rede.saida.bias
);
const probabilidade = sigmoid(uSaida);
return { oculta, uSaida, probabilidade };
}
// Usa o erro da saída para calcular os deltas de todas as camadas.
function backpropagation(entradas, y, rede, resultado) {
// Com Sigmoid + entropia cruzada, o delta da saída é ŷ - y.
const deltaSaida = resultado.probabilidade - y;
// Cada neurônio oculto recebe uma parte do erro da saída.
const deltasOcultos = resultado.oculta.map((neuronio, indice) =>
deltaSaida *
rede.saida.pesos[indice] *
derivadaSigmoid(neuronio.a)
);
return { entradas, deltaSaida, deltasOcultos, ativacoes: resultado.oculta };
}
// Aplica descida do gradiente depois que todos os deltas foram calculados.
function atualizarPesos(rede, gradientes, taxa) {
rede.saida.pesos = rede.saida.pesos.map(
(peso, indice) =>
peso - taxa * gradientes.deltaSaida * gradientes.ativacoes[indice].a
);
rede.saida.bias -= taxa * gradientes.deltaSaida;
rede.oculta.forEach((neuronio, indiceOculto) => {
neuronio.pesos = neuronio.pesos.map(
(peso, indiceEntrada) =>
peso -
taxa *
gradientes.deltasOcultos[indiceOculto] *
gradientes.entradas[indiceEntrada]
);
neuronio.bias -= taxa * gradientes.deltasOcultos[indiceOculto];
});
}
// Reúne forward, erro, backpropagation e atualização para uma amostra.
function treinarAmostra(amostra, rede, taxa) {
const resultado = forward(amostra.x, rede);
const gradientes = backpropagation(amostra.x, amostra.y, rede, resultado);
atualizarPesos(rede, gradientes, taxa);
}
// Repete todas as amostras por várias épocas.
function treinar(dados, epocas, taxa) {
const rede = criarRede();
Array.from({ length: epocas }).forEach(() => {
dados.forEach(amostra => treinarAmostra(amostra, rede, taxa));
});
return rede;
}
// A decisão final usa limiar de probabilidade igual a 0,5.
function prever(entradas, rede) {
return forward(entradas, rede).probabilidade >= 0.5 ? 1 : 0;
}
const redeTreinada = treinar(dadosXOR, 10000, 0.7);
dadosXOR.forEach(amostra => {
console.log(amostra.x, prever(amostra.x, redeTreinada));
});