LABORATÓRIO 04 · MULTICLASSE

Uma decisão, três grupos.

Adicione dados A, B e C, treine o modelo e observe o plano ser dividido em três regiões de decisão.

Três regiões com MLP

Clique no gráfico para adicionar um ponto à classe selecionada.

Pronto
Grupo AGrupo BGrupo CLimites
Épocas0
Acertos
Entropia

Neurônios da MLP · 2 → 8 → 3

As três saídas representam as probabilidades dos grupos A, B e C.

Teste um ponto para acompanhar os potenciais e as probabilidades.
1 · Potencialu = Σ(wᵢxᵢ) + b
2 · Ativaçãoa = g(u)
3 · Forwardentrada → oculta → saídas
4 · SoftmaxuA, uB, uC → probabilidades
5 · Backpropo erro retorna às camadas
6 · Atualizaçãow ← w − η∇w

Passo a passo matemático

Potenciais, Softmax, entropia e gradientes da última amostra.

Treine uma época para visualizar as contas.

Erro × resposta

Compare a entropia média com a taxa de classificações corretas.

Entropia médiaTaxa de acerto

Como escolher entre três respostas?

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

[uA, uB, uC] → Softmax → [P(A), P(B), P(C)]
ŷ = classe com maior probabilidade

CÓDIGO DIDÁTICO

MLP multiclasse com Softmax

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.

tanh()softmax()forward()oneHot()backpropagation()atualizarPesos()
mlp-tres-grupos.jsexemplo simplificado
// 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));