O modelo mais simples que ainda merece o nome. Ele estabelece a linha de base: qualquer modelo mais sofisticado precisa bater a perda do bigrama para justificar a própria complexidade.
$ java -jar target/mini-gpt-java.jar train --model bigramA regra do produto diz que a probabilidade de um texto é o produto de p(x_t | x₁ … x_{t−1}) sobre todas as posições. O problema é que esse condicional depende de um passado que cresce sem parar: não há como tabelá-lo. O bigrama resolve isso com a hipótese mais agressiva possível.
p(x_t | x₁ … x_{t−1}) ≈ p(x_t | x_{t−1})Com essa hipótese o condicional vira uma tabela V × V: uma linha por caractere de contexto, uma coluna por caractere possível. Com V = 96, são 9.216 números, que cabem na memória e podem ser contados numa passada.
Como estimar p(b | a) a partir de um corpus? Contando. Se a apareceu 1.000 vezes e em 300 delas foi seguido de b, a estimativa é 0,3. Isso tem nome: estimativa de máxima verossimilhança. É, demonstravelmente, a tabela que torna o corpus observado o mais provável possível.
N[a][b] = quantas vezes 'b' apareceu logo depois de 'a' p(b | a) = ( N[a][b] + α ) / ( Σ_c N[a][c] + α·V )
a apareceu no total: o denominador sem suavizaçãoα = 1)α·V é a massa total que a suavização acrescenta à linhaO treino inteiro do nível 1 são estas cinco linhas, copiadas sem corte do corpo de BigramModel.fit().
for (int i = 0; i + 1 < ids.length; i++) {int a = ids[i];int b = ids[i + 1];counts[a][b] += 1.0;rowSums[a] += 1.0;}
Complexidade O(N) no tamanho do corpus, uma passada, sem gradiente. Um megabyte de texto é contado antes de você tirar a mão do teclado.
Suponha que o par "zq" nunca apareceu no corpus de treino. Sem suavização, p(q | z) = 0. Agora esse par aparece uma única vez na validação, e a perda daquela posição é −ln 0 = +∞. A média de qualquer coisa com um infinito dentro é infinito: uma ocorrência isolada destrói a métrica inteira.
p = 0 é afirmar que aquilo é impossível, não apenas raro. Nenhum corpus finito autoriza essa conclusão. A suavização de Laplace (add-α) soma α a toda contagem, o que dá massa mínima a todo par e mantém a perda finita, ao custo de tirar um pouco de probabilidade dos pares que de fato ocorreram.É por isso que o construtor recusa α ≤ 0 com uma exceção, em vez de aceitar e falhar de forma obscura mais tarde. Na demonstração da página inicial você pode arrastar o α e ver a perda subir dos dois lados: pouco demais deixa a cauda perigosa, muito demais afoga as contagens reais em ruído uniforme.
L = −(1/N) Σ ln p(x_t | x_{t−1})
L_uniforme = ln V ≈ 4,56 (para V = 96)Um bigrama num corpus real de português chega a algo entre 2 e 3 nats. Isso significa que, sabendo apenas o caractere anterior, a incerteza sobre o próximo caiu de 96 opções equivalentes para o equivalente a cerca de e^2,4 ≈ 11.
Se você preferir pensar em perplexidade, é só exponenciar: PP = e^L. Ela responde à pergunta "entre quantas opções igualmente prováveis o modelo está efetivamente escolhendo?", com a mesma informação numa escala mais intuitiva.
de, a, que.menin, o modelo já esqueceu que estava escrevendo uma palavra.Nenhum destes exercícios pede uma biblioteca nova, e todos cabem numa sessão. O terceiro e o quarto de cada nível são os que mais ensinam, porque quebram alguma coisa de propósito.
Rode train --model bigram e anote a perda de validação. Compare com ln V (o V aparece no cabeçalho da saída). Quanto do caminho até a certeza um único caractere de contexto já percorreu?
Em App.java, troque o 1.0 passado ao construtor do BigramModel por 1e-9 e depois por 50. Explique, sem rodar, o que cada extremo faz com a perda, e depois confira.
Escreva um laço que conte quantas das V² células de counts continuam em zero depois do fit(). Essa fração é exatamente a superfície que a suavização está cobrindo.
Gere com --temp 0.4, 1.0 e 1.6. Existe uma temperatura em que o bigrama parece mais inteligente do que é? Por que ela não melhora a perda?
model/BigramModel.java. O Javadoc da classe traz a mesma matemática, ao lado da linha que a implementa.