← início
Formulário

A matemática do Mini GPT

Um verbete por conceito, em ordem de dependência: cada um só usa o que os anteriores já definiram. Toda fórmula vem com a legenda de todos os símbolos e com o arquivo onde ela é implementada. Uma equação sem legenda é uma parede, e a página existe justamente para quem ainda não reconhece os símbolos.

Notação e formas

Metade dos erros em redes neurais são erros de forma, não de cálculo.

Todo o projeto trabalha com matrizes double[][] na convenção linha = exemplo, coluna = característica. As letras são estáveis do começo ao fim:

V  vocabulário      T  contexto      E  embedding
H  camada oculta    B  lote          d  dimensão por cabeça
V
quantos caracteres distintos o corpus tem. Um corpus em português costuma dar entre 70 e 110
T
quantos caracteres o modelo enxerga: 1 no bigrama, 8 no MLP, 64 no Transformer
B
quantos exemplos entram num passo de treino: 32 no MLP, 16 no Transformer

A regra que salva tempo: o gradiente de um tensor tem a mesma forma que o tensor. Se W₂ é (H × V), então dW2 é (H × V), e isso já determina de que lado cada transposta entra.

Softmax

Como transformar números quaisquer em probabilidades, sem estourar o double.

A saída de qualquer modelo aqui é um vetor de logits: um número por caractere, sem restrição de sinal nem de soma. O softmax os transforma numa distribuição, preservando a ordem.

softmax(z)ᵢ  =  e^{zᵢ − max z}  /  Σⱼ e^{zⱼ − max z}
zᵢ
o logit do caractere i
max z
o maior logit, subtraído de todos antes de exponenciar

Subtrair o máximo não muda o resultado (a constante cancela entre numerador e denominador) e é o que impede e^{800} de virar infinito. Sem esse cuidado, o treino produz NaN assim que os logits crescem. Está em Matrix.softmax, e há um teste só para essa estabilidade.

Entropia cruzada

A perda que todos os três níveis minimizam, e a única métrica comparável entre eles.

L  =  −(1/N)  Σ_t  ln p(x_t real)
p(x_t real)
a probabilidade que o modelo deu ao caractere que de fato apareceu
N
quantas posições entraram na média
ln
logaritmo natural: a unidade é o nat. Com log₂ seria o bit

É a surpresa média do modelo. Zero seria certeza perfeita; ln V é o chute uniforme. Como a média é por caractere, a perda de um bigrama e a de um Transformer são diretamente comparáveis, que é justamente o que permite dizer se um nível se pagou.

O gradiente que vem de graçaDerivando a entropia cruzada e o softmax juntos em relação aos logits, tudo cancela e sobra P − onehot(y): a probabilidade prevista menos a desejada. É por isso que os dois nunca são implementados separadamente.

Perplexidade

A mesma informação da perda, numa escala que dá para explicar em voz alta.

PP  =  e^L
L
a entropia cruzada em nats

Leia como "entre quantas opções igualmente prováveis o modelo está efetivamente escolhendo". Perda 4,56 com V = 96 dá perplexidade 96, que é o chute puro. Perda 1,7 dá cerca de 5,5: o modelo reduziu 96 candidatos a menos de seis.

Regra da cadeia e backpropagation

Backpropagation não é um algoritmo novo; é a regra da cadeia aplicada na ordem eficiente.

∂L/∂x  =  (∂L/∂y) · (∂y/∂x)
∂L/∂y
o gradiente que chegou da camada de cima: o que o resto da rede pede
∂y/∂x
a derivada local desta operação, a única coisa que ela precisa saber

Cada camada faz uma coisa só: recebe o gradiente da saída e devolve o da entrada. É o que torna o backward modular, e é literalmente a estrutura do Tensor, onde cada operação registra a própria derivada local e mais nada.

A ordem importa por custo: como a perda é um escalar, ir de trás para frente (modo reverso) calcula todos os gradientes numa única passada. Ir de frente para trás exigiria uma passada por parâmetro.

Verificação por diferenças finitas

O único jeito de saber que o backward está certo sem confiar em quem o escreveu.

∂f/∂θ  ≈  ( f(θ+ε) − f(θ−ε) ) / (2ε)
ε
um passo minúsculo, aqui da ordem de 1e-5
diferença central
mais precisa que a lateral: o erro cai com ε² em vez de ε

Lento demais para treinar, independente o bastante para testar. É o critério de aceite do projeto: erro abaixo de 1e-5 em todos os gradientes, verificado por mvn test.

Embeddings

Como um caractere vira algo que tem 'proximidade' com outro caractere.

Um id sozinho é arbitrário: nada faz o 41 ser mais parecido com o 42 do que com o 7. Um embedding é uma tabela C de forma (V × E) em que cada caractere ocupa uma linha: um vetor que o treino ajusta livremente.

embed(id)  =  C[id]        (uma linha, E números)
E
a dimensão do vetor: 24 no MLP, 128 no Transformer

No backward, o gradiente volta apenas para as linhas que foram usadas, e volta somado, porque o mesmo caractere pode aparecer várias vezes na mesma janela (o scatter-add). Ao fim do treino, vogais tendem a ficar próximas de vogais: ninguém programou isso, foi a perda que empurrou.

Adam

Por que ninguém usa descida de gradiente pura.

m ← β₁·m + (1−β₁)·g            v ← β₂·v + (1−β₂)·g²
m̂ = m / (1 − β₁ᵗ)              v̂ = v / (1 − β₂ᵗ)
θ ← θ − lr · m̂ / (√v̂ + ε)
g
o gradiente do passo atual
m
o primeiro momento: a média móvel dos gradientes. Suaviza a direção, como inércia
v
o segundo momento: a média dos gradientes ao quadrado. Estima a escala de cada coordenada
β₁, β₂
os decaimentos, 0,9 e 0,999: os valores usuais, e os do projeto
t
o número do passo, usado só na correção de viés

Dividir por √v̂ dá passo efetivo maior onde os gradientes são pequenos e menor onde são grandes, o que resolve o problema de uma única taxa de aprendizado servir para parâmetros de escalas muito diferentes. A correção de viés existe porque m e v começam em zero: sem ela, os primeiros passos seriam pequenos demais.

O projeto aplica ainda weight decay desacoplado (estilo AdamW): θ ← θ − lr·wd·θ, uma regularização que puxa os pesos para zero independentemente do gradiente. Padrão 1e-4; zero desliga.

LayerNorm

O que mantém os números numa faixa saudável enquanto a rede fica profunda.

y  =  γ · (x − μ) / √(σ² + ε)  +  β
μ, σ²
média e variância calculadas dentro de cada posição, sobre as E dimensões, e nunca sobre o lote
γ, β
escala e deslocamento aprendidos, um par por dimensão: a rede pode desfazer a normalização se lhe convier
ε
1e-5, para não dividir por zero

Normalizar por posição, e não pelo lote, é o que faz o LayerNorm funcionar igual com lote de 16 ou de 1, o que importa porque a geração processa uma sequência de cada vez.

Atenção

O mecanismo que define o Transformer, em uma linha.

atenção(Q, K, V)  =  softmax( Q·Kᵀ / √d  +  máscara ) · V
Q
o que a posição atual procura
K
o que cada posição oferece
V
o que cada posição entrega quando escolhida
√d
a escala que impede o softmax de saturar
máscara
−∞ onde j > i: o futuro não pode ser olhado

A saída de cada posição é a média ponderada dos V das posições que ela decidiu olhar, e essa decisão é recalculada a cada passo, a partir do conteúdo.

Temperatura e top-k

Dois botões que mudam o texto sem mudar o modelo.

p'ᵢ  ∝  pᵢ^{1/τ}
τ = 1
a distribuição original, intocada
τ < 1
esfria: concentra massa no mais provável. Texto conservador, tendendo a repetir
τ > 1
esquenta: achata a distribuição. Texto criativo, tendendo a ruído

No limite τ → 0 vira argmax. A conta é feita em log (ln p'ᵢ = (1/τ)·ln pᵢ, seguido de softmax) por estabilidade. O top-k é ortogonal: mantém os k mais prováveis, zera o resto e renormaliza, o que corta a cauda de opções individualmente improváveis que, somadas, ainda ganham sorteios.

Nenhum dos dois melhora a perdaPerda é medida sobre a distribuição do modelo, não sobre o sorteio. Temperatura e top-k mudam apenas o texto que sai: são decisões de apresentação, e é útil não confundi-las com qualidade de modelo.

Mini-lotes

Por que o gradiente é estimado em 16 exemplos e não no corpus inteiro.

O gradiente exato da perda média exigiria uma passada por todo o corpus a cada passo. Um lote aleatório de B exemplos dá uma estimativa ruidosa e barata do mesmo gradiente, e o ruído até ajuda, empurrando o otimizador para fora de mínimos ruins.

É por isso que a perda impressa durante o treino oscila mesmo quando tudo vai bem: cada linha é medida num lote diferente. A tendência importa; o ponto isolado, não.