O treino começa com um espaço perfeito e o estraga. Quatro instrumentos para ver como — e por que nenhum deles sozinho basta.
No curso anterior, tentamos busca semântica com os embeddings do RoBERTa. Falhou de um jeito específico: todas as similaridades deram acima de +0,99. Frases sobre assuntos completamente diferentes pareciam idênticas.
Na época diagnosticamos como anisotropia e seguimos. Aqui a pergunta virou outra: isso é um defeito do RoBERTa, ou acontece com qualquer modelo? Medimos no nosso.
| Momento | Cosseno médio |
|---|---|
| Na inicialização aleatória | +0,0004 |
| Depois de 700 passos (arquitetura clássica) | +0,3824 |
| Depois de 700 passos (arquitetura moderna) | +0,3775 |
O espaço começa perfeitamente isotrópico e o treino cria o cone. Não é defeito de nenhum modelo específico: é o que acontece quando se treina embedding com softmax e weight tying. RoPE, RMSNorm e SwiGLU não mudam nada — os dois números são praticamente iguais.
Isso tem nome na literatura: representation degeneration problem (Gao et al., 2019).
A causa está no gradiente da entropia cruzada. Para cada posição, um token é o alvo e recebe gradiente positivo; os outros 16 391 recebem gradiente negativo — "não era você".
Um token raro é alvo quase nunca e "não-alvo" milhões de vezes. O empurrão líquido dele é sistematicamente negativo, na direção oposta à média dos estados ocultos. E como todos os tokens raros sofrem o mesmo empurrão, eles são empilhados na mesma região — um cone.
Imagine uma sala onde, a cada rodada, uma pessoa é chamada pelo nome e todas as outras ouvem "não é você, afaste-se". Quem é chamado com frequência fica espalhado pela sala. Quem quase nunca é chamado passa a vida recuando — e todos os que recuam acabam amontoados no mesmo canto, encostados uns nos outros.
Perguntar "esses dois são parecidos?" sobre duas pessoas daquele canto não tem resposta útil: todo mundo ali está colado.
Uma medida só não descreve a saúde de um espaço. Usamos quatro, e o ponto desta seção é que cada uma é cega para uma falha que outra enxerga.
Mede o deslocamento da origem: os vetores apontam todos para o mesmo lado? É a medida mais direta do cone. Cega para: colapso de posto, redundância entre dimensões.
As dimensões carregam informação diferente, ou repetem a mesma? É o que Barlow Twins e o termo de covariância do VICReg atacam. Cega para: deslocamento da origem.
Quantas das 256 dimensões o espaço realmente usa. Entropia dos valores singulares, exponenciada: vai de 1 (tudo numa reta) a 256 (uso pleno). Cega para: colapso para um ponto — porque a medida centra os dados antes, e um ponto único mais ruído vira "posto cheio" depois de centrado.
Projeta em direções aleatórias e compara cada projeção com
Construímos cinco distribuições sintéticas e medimos as quatro:
| Distribuição | aniso | decorr | posto | sigreg | var (VICReg) |
|---|---|---|---|---|---|
| gaussiana isotrópica | −0,001 | 0,015 | 253 | 0,006 | 0,009 |
| a mesma, escalada 10× | +0,001 | 0,015 | 253 | 0,006 | 0,009 |
| cone | +0,991 | 0,015 | 253 | 2,769 | 0,009 |
| posto baixo (rank 8) | +0,003 | 0,291 | 8,0 | 0,068 | 31,80 |
| colapso total | — | 0,015 | 253 ✗ | — | 0,999 |
Um espaço que usa 8 das 256 dimensões é uma patologia grave. A anisotropia acusa +0,003 — indistinguível de um espaço perfeito. Se essa fosse a única medida, o problema passaria despercebido para sempre.
E na linha do colapso total, o posto efetivo diz 253, um valor saudável, para um espaço onde todos os vetores são o mesmo ponto. Só o termo de variância do VICReg acusa (0,999).
A primeira versão do nosso SigReg começava assim:
def sigreg(Z, …): Z = Z - Z.mean(0, keepdim=True) # "normalizar" Z = Z / (Z.std() + 1e-6)
Parecia razoável. O teste sintético mostrou o contrário:
caso sigreg gaussiana isotropica 0.0037 cone (anisotropica) 0.0029 <- MENOR que a gaussiana!
O cone — cosseno médio +0,99, a pior patologia da tabela — pontuava melhor que o espaço perfeito.
A causa: a nossa anisotropia é um deslocamento da origem. O cone sintético é ruído pequeno em torno de um ponto distante do zero. Ao subtrair a média, esse ponto some, e sobra ruído isotrópico. Centrar apagava exatamente o que queríamos medir.
O SigReg do paper não centra, e agora entendemos por quê: o alvo é
cone (anisotropica) 2.7687 <- 440x o piso de ruido
Uma normalização que parece inofensiva pode remover o sinal. E o erro era invisível no treino — a perda descia, o modelo aprendia, nada gritava. Só apareceu porque testamos o instrumento contra distribuições cuja resposta certa já sabíamos.
Instrumento novo se calibra antes de usar.
Somamos à perda um termo que castiga o cosseno médio entre linhas da tabela, amostrando 512 por passo:
def reg_cosseno(W, n=512): i = torch.randint(W.shape[0], (n,)) E = F.normalize(W[i], dim=-1) G = E @ E.T return (G.sum() - G.diagonal().sum()) / (n * (n - 1))
| Rodada | Validação | Anisotropia | Posto |
|---|---|---|---|
| sem regularizador | 5,2616 | 0,3775 | 232,4 |
| com λ = 1,0 | 5,2706 | 0,0497 | 244,8 |
7,6× menos anisotropia por 0,17% de qualidade — uma diferença dentro da oscilação que a validação já tem entre avaliações. E de brinde, o posto sobe de 232 para 245.
Por isso o regularizador está ligado por padrão em train.py, com a evidência escrita no próprio código. Desligar exige --reg-cos 0 explícito.
Tokens nunca vistos no treino ficam com norma 3,15× maior que os treinados (1,52 contra 0,48). Com weight tying, a tabela é também a camada de saída — então um vetor de norma grande deveria produzir logit grande, e tokens-lixo apareceriam nas previsões.
Testamos em seis prompts:
| Medida | Esperado por acaso | Observado |
|---|---|---|
| Tokens mortos no top-50 | 1,44 | 0,00 |
| Posição do melhor token morto | ~8 196 | 10 892 |
Os dados não confirmaram a hipótese. A norma cresce, mas a direção aprendeu a ser contrária aos estados ocultos reais — o modelo empurrou esses tokens para abaixo do aleatório. É um bom sinal sobre o treino, e um lembrete: norma grande não implica logit grande quando a direção está errada.
Três razões, em ordem de concretude:
O diagnóstico feito e o remédio simples funcionando, a apostila 04 — quando a fila de experimentos fechar — percorre a linhagem
que a visão computacional construiu para o mesmo problema, e testa cada elo no nosso.