LABORATÓRIO · LEARNING VECTOR QUANTIZATION

Observe a competição moldar as fronteiras.

Pontos rotulados ensinam quatro protótipos. O vencedor aproxima-se quando acerta e afasta-se quando erra; no LVQ2.1, dois protótipos corrigem juntos uma fronteira.

vencedor correto → atrairvencedor incorreto → repelirjanela LVQ2.1 → ajuste duplo
ÉPOCAS0
ATUALIZAÇÕES0
ERRO DE CLASSIFICAÇÃO
COMPACTAÇÃO

Fronteiras do LVQ1

As cores de fundo mostram a classe do protótipo mais próximo.

Modelo novo
amostras Aamostras Bprotótiposfronteira

Última etapa matemática

Distâncias, decisão, condição da janela e atualização.

Treine uma amostra para visualizar as contas.

Erro × resposta

O erro diminui quando mais amostras recebem a classe correta.

erroacurácia

Protótipos aprendidos

A tabela mostra os poucos vetores usados para classificar todo o plano.

CÓDIGO COMPLETO E EXECUTÁVEL

Treine, avalie e use LVQ1 ou LVQ2.1

Além das regras de movimento, o exemplo agora contém dataset, laço de épocas, redução da taxa, classificação, avaliação e uma chamada final.

iniciar()treinar()classificar()avaliar()
lvq-completo.jsexecute do início ao fim
// Um protótipo é [x1, x2, classe].
var treino = [
  [-3,  3, 0], [-2,  2, 0], [-3, -2, 0], [-1, -3, 0],
  [ 3,  3, 1], [ 2,  1, 1], [ 3, -2, 1], [ 1, -3, 1]
];

var teste = [
  [-2.5, 1.5, 0], [-2, -2.5, 0],
  [ 2.5, 1.5, 1], [ 2, -2.5, 1]
];

var prototipos = [
  [-2,  2, 0],
  [-1, -2, 0],
  [ 2,  2, 1],
  [ 1, -2, 1]
];

// Calcula a distância euclidiana entre dois vetores 2D.
function distancia(x, prototipo) {
  var dx1 = x[0] - prototipo[0];
  var dx2 = x[1] - prototipo[1];
  return Math.sqrt(dx1 * dx1 + dx2 * dx2);
}

// Move um protótipo para perto (+1) ou para longe (-1) da amostra.
function mover(prototipo, x, taxa, direcao) {
  prototipo[0] = prototipo[0] + direcao * taxa * (x[0] - prototipo[0]);
  prototipo[1] = prototipo[1] + direcao * taxa * (x[1] - prototipo[1]);
}

// Encontra os índices dos dois protótipos mais próximos.
function doisMaisProximos(x) {
  var primeiro = -1;
  var segundo = -1;

  for (var i = 0; i < prototipos.length; i++) {
    if (primeiro === -1 || distancia(x, prototipos[i]) < distancia(x, prototipos[primeiro])) {
      segundo = primeiro;
      primeiro = i;
    } else if (segundo === -1 || distancia(x, prototipos[i]) < distancia(x, prototipos[segundo])) {
      segundo = i;
    }
  }
  return [primeiro, segundo];
}

// LVQ1 altera somente o protótipo vencedor.
function treinarLVQ1(x, classeCorreta, taxa) {
  var proximos = doisMaisProximos(x);
  var vencedor = prototipos[proximos[0]];

  if (vencedor[2] === classeCorreta) {
    mover(vencedor, x, taxa, +1); // atrair
  } else {
    mover(vencedor, x, taxa, -1); // repelir
  }
}

// Testa se as duas distâncias são parecidas o suficiente.
function estaNaJanela(d1, d2, largura) {
  var limite = (1 - largura) / (1 + largura);
  var razao1 = d1 / d2;
  var razao2 = d2 / d1;
  return Math.min(razao1, razao2) > limite;
}

// LVQ2.1 ajusta um protótipo correto e um incorreto perto da fronteira.
function treinarLVQ21(x, classeCorreta, taxa, largura) {
  var proximos = doisMaisProximos(x);
  var p1 = prototipos[proximos[0]];
  var p2 = prototipos[proximos[1]];
  var classesDiferentes = p1[2] !== p2[2];
  var umCorreto = (p1[2] === classeCorreta) !== (p2[2] === classeCorreta);

  if (classesDiferentes && umCorreto && estaNaJanela(distancia(x, p1), distancia(x, p2), largura)) {
    if (p1[2] === classeCorreta) {
      mover(p1, x, taxa, +1);
      mover(p2, x, taxa, -1);
    } else {
      mover(p1, x, taxa, -1);
      mover(p2, x, taxa, +1);
    }
  }
}

// A classe do protótipo mais próximo é a previsão do LVQ.
function classificar(x) {
  var proximos = doisMaisProximos(x);
  return prototipos[proximos[0]][2];
}

// Apresenta todas as amostras durante várias épocas.
function treinar(dados, algoritmo, epocas, taxaInicial, largura) {
  for (var epoca = 0; epoca < epocas; epoca++) {
    var taxa = taxaInicial * (1 - epoca / epocas); // diminui gradualmente

    for (var i = 0; i < dados.length; i++) {
      var x = [dados[i][0], dados[i][1]];
      var classe = dados[i][2];

      if (algoritmo === 'LVQ2.1') {
        // O LVQ1 faz o ajuste geral; o LVQ2.1 refina a fronteira.
        treinarLVQ1(x, classe, taxa);
        treinarLVQ21(x, classe, taxa, largura);
      } else {
        treinarLVQ1(x, classe, taxa);
      }
    }
  }
}

function avaliar(dados) {
  var acertos = 0;
  for (var i = 0; i < dados.length; i++) {
    if (classificar(dados[i]) === dados[i][2]) acertos++;
  }
  return acertos / dados.length;
}

// ----------------------- USO DO MODELO -----------------------
treinar(treino, 'LVQ2.1', 80, 0.15, 0.30);
console.log('Protótipos aprendidos:', prototipos);
console.log('Acurácia de teste:', avaliar(teste));
console.log('Previsão para [2.2, 0.5]:', classificar([2.2, 0.5]));