Três regiões com MLP
Clique no gráfico para adicionar um ponto à classe selecionada.
Grupo AGrupo BGrupo CLimites
Épocas0
Acertos—
Entropia—
LABORATÓRIO 04 · MULTICLASSE
Adicione dados A, B e C, treine o modelo e observe o plano ser dividido em três regiões de decisão.
Clique no gráfico para adicionar um ponto à classe selecionada.
As três saídas representam as probabilidades dos grupos A, B e C.
Potenciais, Softmax, entropia e gradientes da última amostra.
Treine uma época para visualizar as contas.
Compare a entropia média com a taxa de classificações corretas.
A camada de saída possui três neurônios. A função Softmax transforma seus valores em probabilidades para A, B e C; a maior delas define a classe prevista.
SAÍDA MULTICLASSE
CÓDIGO DIDÁTICO
Esta versão usa quatro neurônios ocultos e três saídas. Cada etapa foi isolada para mostrar onde entram Tanh, Softmax e backpropagation.
// As posições do vetor representam as classes A, B e C.
const classes = ["A", "B", "C"];
// Tanh produz ativações ocultas entre -1 e 1.
function tanh(u) {
return Math.tanh(u);
}
// A derivada de Tanh usa a ativação calculada no forward pass.
function derivadaTanh(ativacao) {
return 1 - ativacao * ativacao;
}
// Softmax transforma três potenciais em probabilidades que somam 1.
function softmax(potenciais) {
// Subtrair o maior valor evita estouro numérico no Math.exp.
const maior = Math.max(...potenciais);
const exponenciais = potenciais.map(u => Math.exp(u - maior));
const total = exponenciais.reduce((soma, valor) => soma + valor, 0);
return exponenciais.map(valor => valor / total);
}
// Cria valores iniciais pequenos para quebrar a simetria da rede.
function pesoAleatorio() {
return Math.random() * 1.2 - 0.6;
}
// Cria uma MLP 2 → 4 → 3.
function criarRede() {
const oculta = Array.from({ length: 4 }, () => ({
pesos: [pesoAleatorio(), pesoAleatorio()],
bias: pesoAleatorio()
}));
const saida = Array.from({ length: 3 }, () => ({
pesos: Array.from({ length: 4 }, pesoAleatorio),
bias: pesoAleatorio()
}));
return { oculta, saida };
}
// Calcula u = Σ(wᵢxᵢ) + b para qualquer neurônio.
function potencial(entradas, pesos, bias) {
return entradas.reduce(
(soma, entrada, indice) => soma + entrada * pesos[indice],
bias
);
}
// Propaga uma entrada pelas camadas sem modificar os parâmetros.
function forward(entradas, rede) {
const oculta = rede.oculta.map(neuronio => {
const u = potencial(entradas, neuronio.pesos, neuronio.bias);
const a = tanh(u);
return { u, a };
});
const ativacoesOcultas = oculta.map(neuronio => neuronio.a);
const potenciaisSaida = rede.saida.map(neuronio =>
potencial(ativacoesOcultas, neuronio.pesos, neuronio.bias)
);
const probabilidades = softmax(potenciaisSaida);
return { oculta, potenciaisSaida, probabilidades };
}
// Converte a classe correta em um vetor alvo.
// Exemplo: B torna-se [0, 1, 0].
function oneHot(classeCorreta) {
return classes.map(classe => classe === classeCorreta ? 1 : 0);
}
// Calcula os deltas da saída e da camada oculta.
function backpropagation(entradas, classeCorreta, rede, resultado) {
const alvo = oneHot(classeCorreta);
// Com Softmax + entropia cruzada, delta = probabilidade - alvo.
const deltasSaida = resultado.probabilidades.map(
(probabilidade, indice) => probabilidade - alvo[indice]
);
// Soma a responsabilidade das três saídas para cada neurônio oculto.
const deltasOcultos = resultado.oculta.map((neuronio, indiceOculto) => {
const erroRecebido = deltasSaida.reduce(
(soma, delta, indiceClasse) =>
soma + delta * rede.saida[indiceClasse].pesos[indiceOculto],
0
);
return erroRecebido * derivadaTanh(neuronio.a);
});
return {
entradas,
ativacoesOcultas: resultado.oculta.map(neuronio => neuronio.a),
deltasSaida,
deltasOcultos
};
}
// Atualiza todos os pesos usando os gradientes calculados.
function atualizarPesos(rede, gradientes, taxa) {
// Primeiro atualiza os três neurônios da saída.
rede.saida.forEach((neuronio, indiceClasse) => {
neuronio.pesos = neuronio.pesos.map(
(peso, indiceOculto) =>
peso -
taxa *
gradientes.deltasSaida[indiceClasse] *
gradientes.ativacoesOcultas[indiceOculto]
);
neuronio.bias -= taxa * gradientes.deltasSaida[indiceClasse];
});
// Depois atualiza os quatro neurônios ocultos.
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];
});
}
// Treina uma amostra completa.
function treinarAmostra(amostra, rede, taxa) {
const resultado = forward(amostra.x, rede);
const gradientes = backpropagation(
amostra.x,
amostra.classe,
rede,
resultado
);
atualizarPesos(rede, gradientes, taxa);
}
// Repete o conjunto de dados durante 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;
}
// Escolhe a classe que recebeu a maior probabilidade.
function prever(entradas, rede) {
const probabilidades = forward(entradas, rede).probabilidades;
const maior = Math.max(...probabilidades);
const indice = probabilidades.indexOf(maior);
return {
classe: classes[indice],
probabilidades
};
}
// Pequeno conjunto de exemplo com três grupos.
const dados = [
{ x: [-0.8, 0.6], classe: "A" },
{ x: [0.7, 0.6], classe: "B" },
{ x: [0.0, -0.7], classe: "C" }
];
const redeTreinada = treinar(dados, 3000, 0.1);
console.log(prever([0.1, -0.6], redeTreinada));