LABORATÓRIO DE FRONTEIRAS

Onde uma reta não consegue chegar.

Monte grupos A e B, treine uma rede e veja como a fronteira de decisão muda entre um Perceptron e uma MLP.

Fronteira de decisão: Perceptron

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

Pronto
Classe AClasse BFronteira de decisão
Épocas0
Acertos
Erro médio

Neurônios da MLP · 2 → 4 → 1

O primeiro ponto do conjunto percorre a rede abaixo.

Os valores serão recalculados durante o treinamento.
1 · Potencialu = Σ(wᵢxᵢ) + b
2 · Ativaçãoa = g(u)
3 · Forwardx₁, x₂ → h → ŷ
4 · Errocompara y com ŷ
5 · Backpropcalcula os gradientes
6 · Atualizaçãow ← w − η∇w

Passo a passo matemático

Última amostra usada no treinamento.

Treine uma época para visualizar as contas.

Erro × resposta

Veja o erro médio diminuir enquanto a taxa de acerto aumenta.

Erro médioTaxa de acerto

Uma função de ativação não curva uma reta

Em um perceptron de camada única, qualquer ativação é aplicada depois da mesma soma linear. Ela muda a forma da saída, mas a fronteira continua sendo uma reta. Para separar este cenário, selecione MLP · camada oculta e uma ativação diferenciável.

CÓDIGO DIDÁTICO

Separação linear em JavaScript

O exemplo abaixo treina um Perceptron com pontos A e B e também devolve a equação da reta aprendida.

normalizarClasse()potencial()ativacaoStep()classificar()ajustarPesos()obterFronteira()
separacao-linear.jsexemplo simplificado
// Pontos com duas coordenadas e uma classe conhecida.
const pontos = [
  { x: [-0.8, 0.6], classe: "B" },
  { x: [-0.4, 0.2], classe: "B" },
  { x: [0.3, 0.5], classe: "A" },
  { x: [0.7, -0.2], classe: "A" }
];

// Converte os nomes A e B nos alvos numéricos usados no treino.
function normalizarClasse(classe) {
  return classe === "A" ? 1 : 0;
}

// Calcula o potencial de ativação u para um ponto do plano.
function calcularPotencial(entradas, modelo) {
  return modelo.w1 * entradas[0] + modelo.w2 * entradas[1] + modelo.bias;
}

// O degrau escolhe o lado da fronteira em que o ponto está.
function ativacaoStep(u) {
  return u >= 0 ? 1 : 0;
}

// Produz a resposta ŷ sem modificar o modelo.
function classificar(entradas, modelo) {
  const u = calcularPotencial(entradas, modelo);
  return { u, yChapeu: ativacaoStep(u) };
}

// Aplica a regra de aprendizagem em somente um ponto.
function ajustarPesos(ponto, modelo, taxa) {
  const y = normalizarClasse(ponto.classe);
  const { yChapeu } = classificar(ponto.x, modelo);
  const erro = y - yChapeu;

  modelo.w1 += taxa * erro * ponto.x[0];
  modelo.w2 += taxa * erro * ponto.x[1];
  modelo.bias += taxa * erro;
  return erro;
}

// Uma época apresenta todos os pontos uma vez.
function treinarEpoca(pontos, modelo, taxa) {
  return pontos.reduce(
    (erros, ponto) => erros + Math.abs(ajustarPesos(ponto, modelo, taxa)),
    0
  );
}

// Repete épocas até não haver erros ou o limite ser atingido.
function treinar(pontos, limiteEpocas, taxa) {
  const modelo = { w1: 0.2, w2: -0.1, bias: 0 };

  for (let epoca = 1; epoca <= limiteEpocas; epoca++) {
    const erros = treinarEpoca(pontos, modelo, taxa);
    if (erros === 0) break;
  }

  return modelo;
}

// Converte u = 0 na forma x₂ = inclinação·x₁ + intercepto.
function obterFronteira(modelo) {
  return {
    inclinacao: -modelo.w1 / modelo.w2,
    intercepto: -modelo.bias / modelo.w2
  };
}

const modelo = treinar(pontos, 100, 0.15);
console.log(modelo, obterFronteira(modelo));

// Limitação importante:
// se A e B não forem separáveis por uma reta, esse treino não converge.
// Nesse caso usamos uma MLP, como no laboratório XOR.