LABORATÓRIO · MINI TRANSFORMER
Treine atenção causal para prever a próxima palavra.
Um modelo reduzido transforma tokens em embeddings de três dimensões, calcula uma cabeça de Self-Attention e treina uma saída Softmax sobre um corpus pequeno.
Exemplos criados do texto
Cada prefixo deve prever a palavra seguinte. Clique em uma linha para inspecionar sua atenção.
Embeddings + posição
Cada token recebe três valores de embedding e três valores posicionais.
Matriz de Self-Attention causal
Cada linha é uma consulta. As células mais escuras possuem maior peso αᵢⱼ; o triângulo futuro permanece mascarado.
Distribuição do próximo token
A saída Softmax compara todos os tokens do vocabulário.
Treinamento selecionado, passo a passo
Da frase até a atualização de um peso da camada de saída.
Erro × resposta
A curva vermelha mostra a loss normalizada; a verde mostra a acurácia de próximo token.
CÓDIGO COMPLETO E EXECUTÁVEL
Treine e use um pequeno preditor com autoatenção
Este é um Transformer educacional reduzido: tokeniza o corpus, cria embeddings, calcula atenção causal, treina a camada de saída por entropia cruzada e prevê novos tokens. Os embeddings e a atenção ficam fixos para manter a matemática legível; em um Transformer real, essas matrizes também recebem gradientes.
// ============================================================
// MINI TRANSFORMER DIDÁTICO PARA PREVISÃO DO PRÓXIMO TOKEN
// Pode ser executado inteiro no console do navegador ou no Node.
// ============================================================
var textoTreino = 'redes neurais aprendem padrões ' +
'redes neurais processam dados ' +
'redes neurais fazem previsões';
var dimensao = 4;
var contextoMaximo = 4;
var vocabulario = [];
var tokensTreino = [];
var embeddings = [];
var pesosSaida = [];
var biasSaida = [];
function tokenizar(texto) {
var limpo = texto.toLowerCase().replace(/[^a-záàâãéêíóôõúç0-9]+/g, ' ').trim();
if (limpo === '') return [];
return limpo.split(' ');
}
// Cria o vocabulário e converte palavras em índices inteiros.
function prepararTexto(texto) {
var palavras = tokenizar(texto);
vocabulario = [];
tokensTreino = [];
for (var i = 0; i < palavras.length; i++) {
var id = vocabulario.indexOf(palavras[i]);
if (id === -1) {
vocabulario.push(palavras[i]);
id = vocabulario.length - 1;
}
tokensTreino.push(id);
}
}
// Inicialização determinística: facilita repetir o exemplo em sala.
function iniciarParametros() {
embeddings = [];
pesosSaida = [];
biasSaida = [];
for (var token = 0; token < vocabulario.length; token++) {
embeddings[token] = [];
pesosSaida[token] = [];
biasSaida[token] = 0;
for (var d = 0; d < dimensao; d++) {
embeddings[token][d] = Math.sin((token + 1) * (d + 1)) * 0.5;
pesosSaida[token][d] = 0;
}
}
}
function produtoEscalar(a, b) {
var soma = 0;
for (var i = 0; i < a.length; i++) soma = soma + a[i] * b[i];
return soma;
}
function softmax(logits) {
var maior = logits[0];
for (var i = 1; i < logits.length; i++) {
if (logits[i] > maior) maior = logits[i];
}
var probabilidades = [];
var soma = 0;
for (i = 0; i < logits.length; i++) {
probabilidades[i] = Math.exp(logits[i] - maior);
soma = soma + probabilidades[i];
}
for (i = 0; i < logits.length; i++) probabilidades[i] /= soma;
return probabilidades;
}
// Q, K e V usam os embeddings diretamente nesta versão reduzida.
function atencao(prefixo) {
var consulta = embeddings[prefixo[prefixo.length - 1]];
var escores = [];
for (var i = 0; i < prefixo.length; i++) {
var chave = embeddings[prefixo[i]];
escores[i] = produtoEscalar(consulta, chave) / Math.sqrt(dimensao);
}
var pesosAtencao = softmax(escores);
var contexto = [];
for (var d = 0; d < dimensao; d++) contexto[d] = 0;
for (i = 0; i < prefixo.length; i++) {
var valor = embeddings[prefixo[i]];
for (d = 0; d < dimensao; d++) {
contexto[d] = contexto[d] + pesosAtencao[i] * valor[d];
}
}
return [contexto, pesosAtencao];
}
function forward(prefixo) {
var resultadoAtencao = atencao(prefixo);
var contexto = resultadoAtencao[0];
var logits = [];
for (var token = 0; token < vocabulario.length; token++) {
logits[token] = produtoEscalar(pesosSaida[token], contexto) + biasSaida[token];
}
return [softmax(logits), contexto, resultadoAtencao[1]];
}
// Gradiente da Softmax + entropia cruzada: probabilidade - alvo.
function ajustarSaida(contexto, probabilidades, alvo, taxa) {
for (var token = 0; token < vocabulario.length; token++) {
var esperado = 0;
if (token === alvo) esperado = 1;
var gradiente = probabilidades[token] - esperado;
for (var d = 0; d < dimensao; d++) {
pesosSaida[token][d] -= taxa * gradiente * contexto[d];
}
biasSaida[token] -= taxa * gradiente;
}
}
function treinar(epocas, taxa) {
var perdaMedia = 0;
for (var epoca = 0; epoca < epocas; epoca++) {
var perdaTotal = 0;
for (var posicao = 1; posicao < tokensTreino.length; posicao++) {
var inicio = posicao - contextoMaximo;
if (inicio < 0) inicio = 0;
var prefixo = tokensTreino.slice(inicio, posicao);
var alvo = tokensTreino[posicao];
var resultado = forward(prefixo);
perdaTotal -= Math.log(resultado[0][alvo] + 0.0000001);
ajustarSaida(resultado[1], resultado[0], alvo, taxa);
}
perdaMedia = perdaTotal / (tokensTreino.length - 1);
}
return perdaMedia;
}
function converterPrompt(texto) {
var palavras = tokenizar(texto);
var ids = [];
for (var i = 0; i < palavras.length; i++) {
var id = vocabulario.indexOf(palavras[i]);
if (id !== -1) ids.push(id);
}
if (ids.length > contextoMaximo) ids = ids.slice(ids.length - contextoMaximo);
return ids;
}
function preverProximo(texto) {
var prefixo = converterPrompt(texto);
if (prefixo.length === 0) return '[token desconhecido]';
var probabilidades = forward(prefixo)[0];
var melhor = 0;
for (var i = 1; i < probabilidades.length; i++) {
if (probabilidades[i] > probabilidades[melhor]) melhor = i;
}
return vocabulario[melhor];
}
function gerar(textoInicial, quantidade) {
var texto = textoInicial;
for (var i = 0; i < quantidade; i++) texto += ' ' + preverProximo(texto);
return texto;
}
// ----------------------- USO DO MODELO -----------------------
prepararTexto(textoTreino);
iniciarParametros();
var perdaFinal = treinar(600, 0.08);
console.log('Vocabulário:', vocabulario);
console.log('Perda final:', perdaFinal);
console.log('Próximo token de "redes neurais":', preverProximo('redes neurais'));
console.log('Geração:', gerar('redes neurais', 4));