A Probabilidade da Linguagem

por Frank de Alcantara em 12/07/2026

A Probabilidade da Linguagem

Um modelo de linguagem pode completar uma frase, traduzir um parágrafo, responder a uma pergunta ou produzir uma quantidade industrial de texto sobre assunto nenhum. Antes de qualquer uma dessas façanhas, porém, ele faz algo muito mais modesto e muito mais preciso: recebe um contexto e distribui probabilidade entre as continuações possíveis. Todo o restante começa aí.

Nos artigos anteriores, transformamos palavras em vetores e vimos que a matemática oferece uma linguagem capaz de representar objetos linguísticos. Falta agora definir o que significa uma continuação ser mais provável que outra, como aprender essas probabilidades a partir de um corpus e como medir o preço de uma previsão ruim. É desse caminho que nascem expressões repetidas em aprendizado de máquina — máxima verossimilhança, NLL, entropia cruzada, divergência KL e perplexidade — frequentemente apresentadas como cinco ideias diferentes. Não são. São cinco vistas da mesma conta.

Nosso exemplo acompanhará o artigo inteiro. Dado o contexto “o animal é um”, observamos oito continuações em um corpus: quatro vezes gato, duas vezes cão, uma vez pássaro e uma vez peixe. Dessas oito contagens deduziremos uma distribuição, avaliaremos um modelo concorrente, calcularemos sua perda e chegaremos à perplexidade. A mesma aritmética aparecerá no texto, no programa C++23 e no laboratório interativo. Trocar de exemplo a cada fórmula é uma forma elegante de impedir que a leitora confira qualquer coisa; não faremos isso.

Um modelo de linguagem não guarda uma frase provável. Ele distribui uma unidade inteira de probabilidade entre todas as próximas frases que ainda podem acontecer.

1. Probabilidade não é confiança decorativa

Comecemos pelo conjunto das respostas possíveis. Um espaço amostral é o conjunto de todos os resultados que consideraremos em um experimento. No nosso primeiro experimento, o contexto já está fixado e apenas a próxima palavra varia. O espaço amostral, que chamaremos de vocabulário, é

\[\mathcal{V} =\{\text{gato},\text{cão},\text{pássaro},\text{peixe}\}.\]
Seu tamanho é $V= \mathcal{V} =4$. Uma variável aleatória $X$ associa o resultado do experimento a um elemento desse conjunto. Escrever
\[P(X=\text{gato})=0{,}5\]

significa que a distribuição $P$ atribui massa $0{,}5$ ao resultado gato. Em uma distribuição discreta sobre $\mathcal{V}$, cada massa precisa ser não negativa e a soma de todas elas precisa ser um:

\[P(X=x)\geq0, \qquad \sum_{x\in\mathcal{V}}P(X=x)=1.\]

Essas duas condições não são detalhes administrativos. Se a soma fosse menor que um, parte da possibilidade teria desaparecido; se fosse maior, estaríamos prometendo mais realidade do que o experimento consegue entregar. A distribuição é uma partilha completa da incerteza.

Uma frase como “o modelo está $40\%$ confiante em gato” esconde o objeto principal: confiante dado o quê? O enunciado matemático completo é

\[Q_\theta(X=\text{gato}\mid C=\text{“o animal é um”})=0{,}4,\]

no qual $C$ é a variável que representa o contexto, $Q_\theta$ é o modelo parametrizado por $\theta$ e a barra vertical significa “condicionado a”. A probabilidade de gato depois de “o animal é um” não precisa ter qualquer relação com sua probabilidade depois de “o animal perseguiu o”. O contexto não é uma legenda ao lado da distribuição; ele escolhe a distribuição que está valendo.

2. Condicionar é mudar o universo relevante

Sejam $A$ e $B$ dois eventos e suponhamos que $P(B)>0$. A probabilidade condicional de $A$ dado $B$ é definida por

\[P(A\mid B)=\frac{P(A\cap B)}{P(B)},\]

na qual $A\cap B$ é o evento em que $A$ e $B$ ocorrem juntos. Dividimos pela massa de $B$ porque, depois de saber que $B$ aconteceu, todo resultado fora de $B$ deixou de ser candidato. Renormalizamos o pedaço de $A$ que sobrevive dentro de $B$ para que o novo universo volte a somar um.

Multiplicando os dois lados por $P(B)$, obtemos a regra do produto:

\[P(A\cap B)=P(A\mid B)P(B).\]

Aplicada a duas palavras, ela diz

\[P(x_0,x_1)=P(x_1\mid x_0)P(x_0).\]

Nada nessa igualdade supõe independência. Ao contrário: o termo $P(x_1\mid x_0)$ existe justamente para permitir que a distribuição da segunda palavra dependa da primeira. Se as duas fossem independentes, teríamos o caso particular $P(x_1\mid x_0)=P(x_1)$, e a expressão se reduziria a $P(x_0,x_1)=P(x_0)P(x_1)$. Modelar linguagem começa por recusar essa simplificação: palavras seguintes dependem das anteriores com uma insistência quase gramatical.

3. A regra da cadeia transforma contexto em sequência

Para três palavras, podemos tratar $(x_0,x_1)$ como um único evento e aplicar a regra do produto outra vez:

\[\begin{aligned} P(x_0,x_1,x_2) &=P(x_2\mid x_0,x_1)P(x_0,x_1)\\ &=P(x_2\mid x_0,x_1)P(x_1\mid x_0)P(x_0). \end{aligned}\]

Repetir o mesmo passo para uma sequência $x_{0:T}=(x_0,x_1,\ldots,x_T)$ produz a regra da cadeia:

\[P(x_{0:T}) =P(x_0)\prod_{t=1}^{T}P(x_t\mid x_{<t}),\]

na qual $x_{<t}=(x_0,\ldots,x_{t-1})$ é todo o prefixo anterior à posição $t$. A notação compacta não inventa uma hipótese: é apenas a regra do produto aplicada $T$ vezes.

Na implementação, costuma-se acrescentar um token especial <bos>, de beginning of sequence (início de sequência), antes do primeiro token, e <eos>, de end of sequence (fim de sequência), depois do último. Assim, até a primeira palavra possui contexto e o modelo pode atribuir probabilidade ao ato de encerrar:

\[P(x_{0:T},\texttt{<eos>}\mid\texttt{<bos>}) =\prod_{t=0}^{T+1}P(x_t\mid x_{<t}),\]

entendendo $x_{-1}=\texttt{}$ e $x_{T+1}=\texttt{}$. A convenção exata varia entre sistemas; o que não pode variar no meio de uma comparação é quais posições entram na conta. Neste artigo, o exemplo de oito continuações conta somente o próximo *token* lexical. Quando avaliarmos sequências completas, declararemos explicitamente a inclusão de *tokens* especiais.

4. O objeto chamado modelo de linguagem

Um modelo de linguagem parametrizado por $\theta$ é uma família de distribuições condicionais

\[Q_\theta(x_t\mid x_{<t})\]

que atribui uma probabilidade a cada próximo token $x_t\in\mathcal{V}$ para um prefixo $x_{<t}$. Para todo contexto fixo, as $V$ probabilidades precisam ser não negativas e somar um.

Essa definição separa duas perguntas que convém não misturar. A primeira é qual objeto desejamos obter? Uma distribuição condicional. A segunda é como calcularemos seus números? Um N-grama usa contagens de contextos curtos; uma RNN comprime o passado em um estado recorrente; um Transformer constrói representações contextuais com atenção. As arquiteturas discordam sobre como ler o contexto, mas entregam a mesma espécie de objeto no final: uma distribuição sobre o próximo token.

No nosso contexto “o animal é um”, suponhamos que um modelo $Q$ devolva, na ordem do vocabulário,

\[Q=(0{,}4,0{,}3,0{,}2,0{,}1).\]

A soma é um e nenhuma entrada é negativa, portanto $Q$ é uma distribuição válida. Ainda não sabemos se é uma boa distribuição. Para decidir, precisamos confrontá-la com dados.

5. Probabilidade dos dados, verossimilhança do modelo

Em oito ocorrências do mesmo contexto no corpus, observamos

\[c=(4,2,1,1), \qquad N=\sum_{i=0}^{V-1}c_i=8,\]

nas quais $c_i$ é a contagem do item $i$ do vocabulário. Se cada ocorrência for modelada como uma retirada categórica com a mesma distribuição $q=(q_0,\ldots,q_{V-1})$, a probabilidade dos dados, ignorando por enquanto a ordem em que apareceram, é proporcional a

\[\mathcal{L}(q;c)=\prod_{i=0}^{V-1}q_i^{c_i}.\]

Se tratarmos $c$ como um vetor de contagens sem ordem, a probabilidade completa inclui o coeficiente multinomial $N!/\prod_i c_i!$. Esse fator depende apenas dos dados já fixados, não de $q$, e por isso não altera qual distribuição maximiza a verossimilhança.

Chamamos essa expressão de função de verossimilhança. A fórmula numérica pode ser a mesma de uma probabilidade, mas a pergunta mudou. Quando $q$ está fixo e imaginamos diferentes dados, $Q(c\mid q)$ é uma probabilidade sobre dados. Quando os dados $c$ estão fixos e variamos $q$, $\mathcal{L}(q;c)$ é uma função do parâmetro. Verossimilhança não é uma distribuição de probabilidade sobre $q$; sua soma ou integral sobre parâmetros não precisa valer um.

Para o modelo $Q$ do exemplo,

\[\begin{aligned} \mathcal{L}(Q;c) &=0{,}4^4\cdot0{,}3^2\cdot0{,}2\cdot0{,}1\\ &=0{,}00004608. \end{aligned}\]

O número é pequeno, mas isso sozinho não condena o modelo. Produtos de muitas probabilidades ficam pequenos mesmo quando cada fator é razoável. Uma sequência de mil eventos com probabilidade $0{,}9$ já recebe probabilidade $0{,}9^{1000}\approx1{,}75\times10^{-46}$. O remédio não será fingir que o número deveria ser grande; será encontrar uma representação em que possamos compará-lo sem perdê-lo.

6. Máxima verossimilhança nasce das contagens

Queremos escolher uma distribuição $q$ que maximize $\mathcal{L}(q;c)$ sob as restrições

\[q_i\geq0, \qquad \sum_{i=0}^{V-1}q_i=1.\]

Como o logaritmo é estritamente crescente, o mesmo $q$ maximiza a log-verossimilhança

\[\ell(q;c) =\log\mathcal{L}(q;c) =\sum_{i=0}^{V-1}c_i\log q_i.\]

Para incorporar a restrição da soma, introduzimos um multiplicador de Lagrange $\lambda$:

\[\Phi(q,\lambda) =\sum_{i=0}^{V-1}c_i\log q_i +\lambda\left(1-\sum_{i=0}^{V-1}q_i\right).\]

Derivando em relação a cada $q_i$ e igualando a zero,

\[\frac{\partial\Phi}{\partial q_i} =\frac{c_i}{q_i}-\lambda=0,\]

portanto

\[q_i=\frac{c_i}{\lambda}.\]

Somemos essa igualdade sobre todas as categorias:

\[\sum_iq_i =\frac{\sum_i c_i}{\lambda} =\frac{N}{\lambda} =1.\]

Logo, $\lambda=N$ e a estimativa de máxima verossimilhança, ou MLE, de maximum likelihood estimation (estimação por máxima verossimilhança), é

\[\hat p_i=\frac{c_i}{N}.\]

No nosso corpus,

\[P=\hat p =\left(\frac12,\frac14,\frac18,\frac18\right) =(0{,}5,0{,}25,0{,}125,0{,}125).\]

Sua verossimilhança é

\[\mathcal{L}(P;c) =0{,}5^4\cdot0{,}25^2\cdot0{,}125^2 =0{,}00006103515625,\]

maior que os $0{,}00004608$ de $Q$. Não precisávamos torcer por esse resultado: acabamos de provar que nenhuma outra distribuição categórica produz verossimilhança maior para essas contagens. A MLE reproduz as frequências empíricas porque, nesse modelo, as contagens são toda a informação disponível.

Se alguma categoria não aparecer, sua MLE será zero. Isso descreverá perfeitamente o corpus de treinamento e poderá destruir a avaliação de qualquer texto que contenha o evento ausente. Não corrigiremos o problema com um gesto vago chamado “suavização”; o artigo sobre N-gramas deduzirá por que e como redistribuir massa para eventos não observados.

7. O logaritmo troca produtos por somas

A regra da cadeia transforma a probabilidade de uma sequência em produto. O logaritmo transforma esse produto em soma:

\[\log Q_\theta(x_{0:T}) =\sum_{t=0}^{T}\log Q_\theta(x_t\mid x_{<t}).\]

Essa transformação resolve dois problemas de uma vez. Matematicamente, como $\log$ é estritamente crescente, a posição do máximo não muda:

\[\arg\max_\theta Q_\theta(D) =\arg\max_\theta\log Q_\theta(D).\]

Computacionalmente, a soma de logaritmos evita que uma longa cadeia de probabilidades positivas desapareça por underflow, o arredondamento para zero de um número menor que o formato consegue representar. T04 tratará os limites numéricos; aqui basta perceber que $-105{,}36$ ainda é um número representável quando $1{,}75\times10^{-46}$ pode já ter virado zero em baixa precisão.

Algoritmos de otimização são apresentados como minimizadores. Multiplicar a log-verossimilhança por $-1$ converte o máximo em mínimo e define a NLL, de negative log-likelihood (log-verossimilhança negativa):

\[\operatorname{NLL}(D;\theta) =-\sum_{t=0}^{T}\log Q_\theta(x_t\mid x_{<t}).\]

Portanto,

\[\arg\max_\theta\mathcal{L}(\theta;D) =\arg\max_\theta\log\mathcal{L}(\theta;D) =\arg\min_\theta\operatorname{NLL}(D;\theta).\]

Não são três objetivos que por coincidência costumam concordar. São a mesma ordenação dos modelos, vista antes do logaritmo, depois do logaritmo e depois de trocar o sinal.

Para nossas oito observações,

\[\begin{aligned} \operatorname{NLL}(c;Q) &=-\sum_{i=0}^{V-1}c_i\ln q_i\\ &=-\left(4\ln0{,}4+2\ln0{,}3+\ln0{,}2+\ln0{,}1\right)\\ &\approx9{,}985131542\ \text{nats}. \end{aligned}\]

Usamos logaritmo natural, por isso a unidade é o nat. Com $\log_2$, a unidade seria o bit. A base muda a escala, mas não muda qual modelo vence, pois

\[\log_2x=\frac{\ln x}{\ln2}.\]

8. Surpresa: cobrar mais pelo improvável

Claude Shannon formalizou a quantidade de informação associada a um evento de probabilidade $P(x)$ como

\[I_P(x)=-\log P(x).\]

Essa surpresa, também chamada autoinformação, obedece ao comportamento que esperamos. Um evento certo, com probabilidade um, produz $I(1)=-\log1=0$: saber que ocorreu não reduz incerteza alguma. Um evento raro produz surpresa grande. E dois eventos independentes têm probabilidades multiplicadas, de modo que suas surpresas se somam:

\[-\log(P(a)P(b))=-\log P(a)-\log P(b).\]

No modelo $Q$, observar gato custa

\[-\ln0{,}4\approx0{,}916291\ \text{nats},\]

enquanto observar peixe custa

\[-\ln0{,}1\approx2{,}302585\ \text{nats}.\]

O segundo evento cobra aproximadamente $2{,}51$ vezes a surpresa do primeiro porque o modelo apostou quatro vezes menos massa nele. A perda logarítmica não pune um rótulo abstrato de “erro”; cobra o preço preciso da probabilidade que o modelo ousou atribuir ao que realmente aconteceu.

9. Entropia é a surpresa média da própria fonte

Se os resultados são gerados por uma distribuição $P$, a surpresa média é a entropia:

\[\begin{aligned} H(P) &=\mathbb{E}_{x\sim P}[I_P(x)]\\ &=-\sum_{x\in\mathcal{V}}P(x)\log P(x). \end{aligned}\]

Quando $P(x)=0$, aparece formalmente $0\log0$. Definimos esse termo pelo limite

\[\lim_{p\to0^+}p\log p=0.\]

Um evento impossível sob $P$ nunca é sorteado por $P$ e, portanto, não contribui para a surpresa média da fonte.

Para a distribuição empírica do exemplo, em bits,

\[\begin{aligned} H_2(P) &=-\left( \frac12\log_2\frac12 +\frac14\log_2\frac14 +2\cdot\frac18\log_2\frac18 \right)\\ &=\frac12+\frac12+2\cdot\frac38\\ &=1{,}75\ \text{bits}. \end{aligned}\]

Em nats,

\[H(P)\approx1{,}213007566.\]

Uma distribuição uniforme sobre quatro resultados teria entropia $\log_2 4=2$ bits. Nossa fonte tem entropia menor porque metade da massa está concentrada em gato; conhecê-la permite códigos médios mais curtos e previsões melhores que um sorteio uniforme. Chamar isso apenas de “menos desordem” perde o mecanismo. Entropia é a conta média cobrada pela surpresa quando usamos as probabilidades verdadeiras da fonte.

10. Entropia cruzada é a surpresa média sob outro modelo

Na prática, os dados vêm de uma distribuição $P$ que não conhecemos e são avaliados por um modelo $Q$. A surpresa cobrada pelo modelo quando ocorre $x$ é $-\log Q(x)$, mas a frequência com que $x$ ocorre é determinada por $P$. A média correta é, portanto, a entropia cruzada:

\[\begin{aligned} H(P,Q) &=\mathbb{E}_{x\sim P}[-\log Q(x)]\\ &=-\sum_{x\in\mathcal{V}}P(x)\log Q(x). \end{aligned}\]

No corpus empírico, $\hat p_i=c_i/N$. Substituindo na fórmula,

\[\begin{aligned} H(\hat P,Q) &=-\sum_i\frac{c_i}{N}\log q_i\\ &=\frac1N\left(-\sum_i c_i\log q_i\right)\\ &=\frac1N\operatorname{NLL}(c;Q). \end{aligned}\]

Esta igualdade é a dobradiça do artigo: a NLL média no corpus é a entropia cruzada entre a distribuição empírica e o modelo. Para nossas oito observações,

\[H(P,Q) =\frac{9{,}985131542}{8} \approx1{,}248141443\ \text{nats}.\]

Em uma tarefa de classificação com alvo one-hot, o vetor-alvo $y$ vale um apenas na categoria correta $k$ e zero nas demais. A entropia cruzada por exemplo colapsa:

\[-\sum_{i=0}^{V-1}y_i\log q_i =-\log q_k.\]

É por isso que o treinamento de um modelo de linguagem cobra somente o logaritmo da probabilidade atribuída ao próximo token observado, embora o modelo produza uma distribuição sobre todo o vocabulário. Os demais termos são multiplicados por zero naquele exemplo; seus efeitos reaparecem indiretamente porque todas as probabilidades disputam a mesma soma unitária.

Se $P(x)>0$ e $Q(x)=0$, então

\[-P(x)\log Q(x)=+\infty.\]

O modelo declarou impossível um evento que a fonte produz. A conta infinita não é dramatização da fórmula; é a consequência inevitável de tentar codificar com zero bits reservados algo que acabou de acontecer.

11. Divergência KL é o preço excedente

A divergência de Kullback–Leibler, ou KL, de $Q$ em relação a $P$ é

\[D_{\mathrm{KL}}(P\|Q) =\sum_{x\in\mathcal{V}}P(x) \log\frac{P(x)}{Q(x)}.\]

Separemos o logaritmo da razão:

\[\begin{aligned} D_{\mathrm{KL}}(P\|Q) &=\sum_xP(x)\left(\log P(x)-\log Q(x)\right)\\ &=\sum_xP(x)\log P(x)-\sum_xP(x)\log Q(x)\\ &=-H(P)+H(P,Q). \end{aligned}\]

Rearranjando,

\[\boxed{H(P,Q)=H(P)+D_{\mathrm{KL}}(P\|Q)}.\]

A identidade diz exatamente o que cada parcela cobra. $H(P)$ é a incerteza irredutível da fonte: mesmo o modelo perfeito paga essa média. $D_{\mathrm{KL}}(P|Q)$ é o custo adicional por usar $Q$ onde deveríamos usar $P$.

No exemplo,

\[D_{\mathrm{KL}}(P\|Q) \approx0{,}035133877\ \text{nats},\]

e a conta fecha:

\[1{,}248141443 \approx1{,}213007566+0{,}035133877.\]

Pela desigualdade $\ln z\leq z-1$, válida para $z>0$, podemos provar a não negatividade. Aplique-a a $z=Q(x)/P(x)$:

\[-\ln\frac{Q(x)}{P(x)} \geq1-\frac{Q(x)}{P(x)}.\]

Multiplicando por $P(x)$ e somando,

\[\begin{aligned} D_{\mathrm{KL}}(P\|Q) &\geq\sum_x\left(P(x)-Q(x)\right)\\ &=1-1=0. \end{aligned}\]

A prova acima cobre o suporte em que $P(x)>0$ e $Q(x)>0$. Se $P(x)>0$ e $Q(x)=0$ para algum $x$, a divergência já é $+\infty$ e a não negatividade é imediata.

A igualdade ocorre quando $P=Q$ nos eventos relevantes. KL, entretanto, não é uma distância: em geral,

\[D_{\mathrm{KL}}(P\|Q) \neq D_{\mathrm{KL}}(Q\|P),\]

e ela não satisfaz a desigualdade triangular. A direção importa. $D_{\mathrm{KL}}(P|Q)$ calcula o preço esperado quando os dados vêm de $P$ e são descritos por $Q$; inverter as distribuições responde outra pergunta.

12. Perplexidade desfaz o logaritmo

A NLL total cresce com o tamanho do texto. Para comparar avaliações com o mesmo protocolo, calculamos a NLL média por token:

\[\overline{\operatorname{NLL}}(D;Q) =-\frac1N\sum_{t=0}^{N-1}\log Q(x_t\mid x_{<t}).\]

A perplexidade é a exponencial dessa média quando usamos logaritmos naturais:

\[\operatorname{PPL}(D;Q) =\exp\!\left(\overline{\operatorname{NLL}}(D;Q)\right).\]

Substituindo a soma de logaritmos e usando $\exp(\log z)=z$,

\[\begin{aligned} \operatorname{PPL}(D;Q) &=\exp\!\left( -\frac1N\sum_{t=0}^{N-1}\log Q(x_t\mid x_{<t}) \right)\\ &=\left( \prod_{t=0}^{N-1}\frac{1}{Q(x_t\mid x_{<t})} \right)^{1/N}. \end{aligned}\]

Perplexidade é, portanto, a média geométrica do inverso das probabilidades atribuídas aos tokens observados. Se o modelo fosse uniforme sobre $k$ opções em cada posição, cada probabilidade seria $1/k$ e

\[\operatorname{PPL} =\left(\prod_{t=0}^{N-1}k\right)^{1/N} =k.\]

Daí a interpretação como fator de ramificação equivalente: uma perplexidade de $k$ produz a mesma surpresa média de uma escolha uniforme entre $k$ alternativas. Isso não significa que o modelo enxergou literalmente $k$ palavras em cada posição, nem que acertou uma fração $1/k$ dos tokens.

Para $Q$,

\[\operatorname{PPL}(P,Q) =\exp(1{,}248141443) \approx3{,}483861980.\]

O modelo uniforme sobre quatro palavras tem entropia cruzada $\ln4$ e perplexidade $4$. O modelo $Q$ melhora esse valor para aproximadamente $3{,}484$. O melhor modelo possível para a distribuição empírica é $P$, cuja perplexidade é

\[\exp(H(P)) \approx3{,}363585661.\]

A diferença restante não é incompetência do modelo perfeito; é a incerteza da própria fonte. Perplexidade um só seria possível se cada contexto determinasse o próximo token com probabilidade um.

13. Quando duas perplexidades não podem ser comparadas

O número parece simples o bastante para caber em uma tabela, e é aí que começa o perigo. A perplexidade é normalizada por token. Se dois modelos usam tokenizações diferentes, o denominador $N$ mede unidades diferentes.

Considere a palavra improvável. Um tokenizador pode representá-la como um único token; outro, como im, prov, ável. Mesmo que os dois modelos atribuam a mesma probabilidade à palavra completa, o segundo distribui essa probabilidade por três fatores e divide a NLL por três posições. Comparar diretamente as duas perplexidades é comparar custo por palavra com custo por pedaço de palavra e esperar que a coluna alinhada da tabela resolva a unidade. Não resolve.

Uma comparação legítima exige o mesmo corpus de avaliação, a mesma tokenização ou uma unidade comum explicitamente convertida, a mesma política para <bos>, <eos> e padding, e o mesmo conjunto de contextos disponíveis. Também exige que nenhum modelo tenha sido treinado sobre o conjunto de teste. T06 — Do Texto ao Token mostrará que tokenização não é um pré-processamento neutro: ela decide quais eventos recebem probabilidade e qual unidade aparece no denominador da perplexidade.

A perplexidade também não mede tudo que queremos de um texto. Um modelo pode melhorar a probabilidade média e ainda produzir fatos falsos, respostas inseguras ou repetições desagradáveis. Ela avalia a capacidade probabilística sobre o corpus escolhido. Pedir que avalie verdade, utilidade e estilo ao mesmo tempo é contratar um termômetro para fazer auditoria contábil.

14. A mesma matemática em C++23

Podemos agora implementar as definições, na mesma ordem em que foram construídas. O programa abaixo é uma referência de correção para o exemplo, não um benchmark. Quatro probabilidades não justificam paralelismo, SIMD ou um festival de templates; justificam tipos claros, políticas explícitas para zeros e resultados conferíveis.

#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <limits>
#include <numeric>
#include <span>
#include <stdexcept>

namespace probability {

void require_same_size(std::span<const double> first,
                       std::span<const double> second) {
    if (first.size() != second.size()) {
        throw std::invalid_argument("distributions must have equal sizes");
    }
}

void validate_distribution(std::span<const double> distribution,
                           double tolerance = 1.0e-12) {
    if (distribution.empty()) {
        throw std::invalid_argument("distribution cannot be empty");
    }

    double total = 0.0;
    for (const double value : distribution) {
        if (!std::isfinite(value) || value < 0.0) {
            throw std::invalid_argument(
                "probabilities must be finite and non-negative");
        }
        total += value;
    }

    if (std::abs(total - 1.0) > tolerance) {
        throw std::invalid_argument("probabilities must sum to one");
    }
}

double entropy(std::span<const double> distribution) {
    validate_distribution(distribution);
    double result = 0.0;
    for (const double probability : distribution) {
        // The continuous limit of -p log(p) is zero when p approaches zero.
        if (probability > 0.0) {
            result -= probability * std::log(probability);
        }
    }
    return result;
}

double cross_entropy(std::span<const double> source,
                     std::span<const double> model) {
    require_same_size(source, model);
    validate_distribution(source);
    validate_distribution(model);

    double result = 0.0;
    for (std::size_t i = 0uz; i < source.size(); ++i) {
        if (source[i] == 0.0) {
            continue;
        }
        // An observed event assigned zero model probability has infinite cost.
        if (model[i] == 0.0) {
            return std::numeric_limits<double>::infinity();
        }
        result -= source[i] * std::log(model[i]);
    }
    return result;
}

double kl_divergence(std::span<const double> source,
                     std::span<const double> model) {
    require_same_size(source, model);
    validate_distribution(source);
    validate_distribution(model);

    double result = 0.0;
    for (std::size_t i = 0uz; i < source.size(); ++i) {
        if (source[i] == 0.0) {
            continue;
        }
        if (model[i] == 0.0) {
            return std::numeric_limits<double>::infinity();
        }
        result += source[i] * std::log(source[i] / model[i]);
    }
    return result;
}

double nll_from_counts(std::span<const std::uint64_t> counts,
                       std::span<const double> model) {
    if (counts.size() != model.size()) {
        throw std::invalid_argument("counts and model must have equal sizes");
    }
    validate_distribution(model);

    double result = 0.0;
    for (std::size_t i = 0uz; i < counts.size(); ++i) {
        if (counts[i] == 0) {
            continue;
        }
        if (model[i] == 0.0) {
            return std::numeric_limits<double>::infinity();
        }
        result -= static_cast<double>(counts[i]) * std::log(model[i]);
    }
    return result;
}

template <std::size_t N>
std::array<double, N> maximum_likelihood(
    const std::array<std::uint64_t, N>& counts) {
    const std::uint64_t total = std::accumulate(
        counts.begin(), counts.end(), std::uint64_t{0});
    if (total == 0) {
        throw std::invalid_argument("at least one observation is required");
    }

    std::array<double, N> estimate{};
    for (std::size_t i = 0uz; i < counts.size(); ++i) {
        estimate[i] = static_cast<double>(counts[i])
                    / static_cast<double>(total);
    }
    return estimate;
}

}  // namespace probability

int main() {
    constexpr std::array<std::uint64_t, 4> counts{4, 2, 1, 1};
    constexpr std::array<double, 4> model{0.4, 0.3, 0.2, 0.1};

    const auto source = probability::maximum_likelihood(counts);
    const double h_source = probability::entropy(source);
    const double h_cross = probability::cross_entropy(source, model);
    const double kl = probability::kl_divergence(source, model);
    const double nll = probability::nll_from_counts(counts, model);
    const double perplexity = std::exp(h_cross);
    const double likelihood = std::exp(-nll);

    assert(std::abs(h_cross - (h_source + kl)) < 1.0e-12);
    assert(std::abs(nll - 8.0 * h_cross) < 1.0e-12);

    std::cout << std::fixed << std::setprecision(12)
              << "H(P)       = " << h_source << " nats\n"
              << "H(P,Q)     = " << h_cross << " nats\n"
              << "KL(P||Q)   = " << kl << " nats\n"
              << "NLL        = " << nll << " nats\n"
              << "likelihood = " << likelihood << '\n'
              << "perplexity = " << perplexity << '\n';
}

Compilamos com C++23 e avisos fortes:

g++ -std=c++23 -O2 -Wall -Wextra -Wpedantic -Wconversion -Wshadow probability_language.cpp -o probability_language

A saída esperada é

H(P)       = 1.213007565980 nats
H(P,Q)     = 1.248141442697 nats
KL(P||Q)   = 0.035133876717 nats
NLL        = 9.985131541577 nats
likelihood = 0.000046080000
perplexity = 3.483861979569

As funções recebem std::span, uma visão não proprietária sobre memória contígua; nenhum span sobrevive aos vetores ou arranjos que observa. As contagens usam std::uint64_t porque acumulá-las em int seria impor ao corpus um limite que a matemática não pediu. O acumulador de std::accumulate começa com std::uint64_t{0}, pois o valor inicial determina o tipo da redução. E os comentários explicam os dois pontos em que a sintaxe sozinha não revela a matemática: $0\log0=0$ por limite e custo infinito quando um evento observado recebe probabilidade zero.

O território deste programa é CPU, mas não há otimização específica de CPU a medir. Em uma rede real, essas somas percorrem lotes, posições e vocabulários em CPU ou GPU; a ideia matemática é comum às duas. Paralelizar quatro elementos seria uma maneira admiravelmente sofisticada de gastar mais tempo coordenando threads do que calculando entropia.

15. Laboratório: mova a probabilidade, mova a perda

O laboratório abaixo começa com as contagens e o modelo deste artigo. Altere os quatro pesos e observe como NLL, entropia cruzada, KL e perplexidade respondem. Use as predefinições para igualar $Q$ a $P$, tornar $Q$ uniforme ou zerar a probabilidade de peixe. Nesse último caso, o laboratório não impedirá a escolha: mostrará por que um evento observado com probabilidade zero torna o custo infinito. Troque nats por bits e confirme que os valores logarítmicos mudam de unidade, enquanto a perplexidade permanece a mesma.

O controle manipula pesos brutos $w_i\geq0$ e calcula $q_i=w_i/\sum_jw_j$. Isso permite explorar a fronteira da distribuição sem exigir que quatro controles independentes milagrosamente somem um. Se todos os pesos forem zero, nem a normalização existe; em vez de inventar uma distribuição, o laboratório explica a violação.

Conclusão

Partimos de oito contagens e construímos uma cadeia inteira:

\[\text{contagens} \longrightarrow \hat P \longrightarrow \mathcal{L} \longrightarrow \operatorname{NLL} \longrightarrow H(P,Q) \longrightarrow D_{\mathrm{KL}}(P\|Q) \longrightarrow \operatorname{PPL}.\]

A máxima verossimilhança escolhe os parâmetros que tornam os dados observados mais prováveis. O logaritmo troca produtos frágeis por somas e preserva a posição do ótimo. O sinal negativo converte maximização em minimização. A NLL média é a entropia cruzada empírica. A entropia cruzada é a entropia da fonte acrescida do preço KL por usar o modelo errado. A perplexidade apenas desfaz a escala logarítmica e expressa a surpresa média como um fator de ramificação equivalente.

Modelos reais, naturalmente, não recebem quatro probabilidades prontas. Produzem milhões de vetores de logits, para muitos tokens e muitos exemplos ao mesmo tempo, e precisam transformá-los em distribuições sem overflow, underflow ou erros de dimensão. Esse será o objeto do próximo artigo: tensores, formas, lotes, broadcasting, log-sum-exp e estabilidade numérica.

Essa é a ideia que levaremos adiante: um modelo de linguagem não aprende frases; aprende a redistribuir probabilidade sobre continuações possíveis.

Referências

BENGIO, Y.; DUCHARME, R.; VINCENT, P.; JAUVIN, C. A Neural Probabilistic Language Model. Journal of Machine Learning Research, v. 3, p. 1137–1155, 2003. Disponível em: https://www.jmlr.org/papers/v3/bengio03a.html.

FISHER, R. A. On the Mathematical Foundations of Theoretical Statistics. Philosophical Transactions of the Royal Society A, v. 222, p. 309–368, 1922. DOI: https://doi.org/10.1098/rsta.1922.0009.

GOODFELLOW, I.; BENGIO, Y.; COURVILLE, A. Deep Learning. Cambridge: MIT Press, 2016. Cap. 3. Disponível em: https://www.deeplearningbook.org/contents/prob.html.

JELINEK, F. Continuous Speech Recognition by Statistical Methods. Proceedings of the IEEE, v. 64, n. 4, p. 532–556, 1976. Disponível em: https://research.ibm.com/publications/continuous-speech-recognition-by-statistical-methods.

KULLBACK, S.; LEIBLER, R. A. On Information and Sufficiency. The Annals of Mathematical Statistics, v. 22, n. 1, p. 79–86, 1951. DOI: https://doi.org/10.1214/aoms/1177729694.

SHANNON, C. E. A Mathematical Theory of Communication. The Bell System Technical Journal, v. 27, p. 379–423 e 623–656, 1948. Disponível em: https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf.

(Updated: )