Programação Probabilística

A programação probabilística é uma abordagem para construir modelos bayesianos em que você escreve o modelo como código — um programa que faz escolhas aleatórias — e então depende de algoritmos de inferência automatizada (como MCMC ou inferência variacional) para calcular distribuições posteriores condicionadas aos dados observados. Ela busca separar:

  • Modelagem: especificar no que você acredita sobre como os dados são gerados
  • Inferência: calcular o que essas crenças implicam depois de ver os dados

Essa separação é poderosa porque permite expressar modelos probabilísticos complexos (hierarquias, variáveis latentes, dados ausentes, modelos de mistura) sem derivar manualmente atualizações de inferência sob medida para cada novo modelo.

O que “programação probabilística” significa

Na programação comum, executar um programa produz uma saída determinística dadas as entradas. Na programação probabilística, executar um programa produz uma distribuição sobre possíveis rastros de execução porque algumas etapas envolvem amostragem aleatória.

Um programa probabilístico normalmente inclui:

  • Variáveis aleatórias: criadas ao amostrar de distribuições
  • Um processo gerativo: uma “história” de como variáveis latentes e observações surgem
  • Condicionamento: afirmar que alguns valores amostrados devem corresponder a dados observados
  • Inferência: calcular a distribuição posterior sobre variáveis não observadas

Isso se conecta diretamente à Inferência Bayesiana: você define um prior e uma verossimilhança, e a inferência calcula o posterior [ p(\mathbf{z}\mid \mathbf{x}) = \frac{p(\mathbf{x}\mid \mathbf{z}),p(\mathbf{z})}{p(\mathbf{x})}, ] onde (\mathbf{z}) são variáveis latentes/parâmetros e (\mathbf{x}) são observações.

Linguagens (PPLs) e bibliotecas de programação probabilística incluem Stan, PyMC, Pyro, NumPyro, Turing, TensorFlow Probability, entre outras. Elas diferem em sintaxe e capacidades de inferência, mas compartilham a mesma ideia central: modelos como programas, inferência como uma ferramenta.

Abstrações centrais

Variáveis aleatórias e distribuições

Um programa probabilístico se apoia nos conceitos de Variáveis Aleatórias e Distribuições: você especifica que alguma quantidade é distribuída como Normal, Bernoulli, Gamma etc.

Em muitas PPLs, você escreverá algo como:

w = sample("w", Normal(0, 1))

Isso não significa “sortear um valor fixo e seguir” em um sentido puramente de Monte Carlo. Em vez disso, define uma escolha aleatória no modelo; a inferência posteriormente raciocinará sobre quais valores de w são plausíveis dado os dados.

Modelos gerativos como distribuições conjuntas executáveis

Um modelo probabilístico geralmente define uma distribuição conjunta sobre variáveis latentes e variáveis observadas: [ p(\mathbf{x}, \mathbf{z}) = p(\mathbf{z}),p(\mathbf{x}\mid \mathbf{z}). ]

Um programa probabilístico é uma forma de especificar essa distribuição conjunta de maneira procedimental. Por exemplo, um modelo de regressão linear bayesiana é uma distribuição conjunta sobre pesos (w), ruído (\sigma) e saídas (y).

Uma vantagem de usar um programa é que ele lida naturalmente com:

  • laços e vetorização (“plates”)
  • lógica condicional (componentes de mistura, ramificações)
  • estrutura modular (submodelos/funções)

Condicionamento (observações)

Condicionamento é a operação que transforma um prior em um posterior ao incorporar dados. Muitas PPLs expõem uma operação observe (ou sample(..., obs=...)):

observe("y", Normal(mu, sigma), y_observed)

Conceitualmente, isso multiplica a densidade conjunta pelo termo de verossimilhança para o(s) valor(es) observado(s), transformando a inferência em “encontrar valores latentes que tornem os dados observados prováveis sob o modelo”.

Consultas ao posterior e distribuições preditivas

Depois de condicionar nos dados, consultas típicas incluem:

  • Posterior sobre parâmetros: (p(\theta \mid \mathcal{D}))
  • Posterior sobre variáveis latentes: (p(\mathbf{z} \mid \mathcal{D}))
  • Posterior preditivo: (p(\tilde{\mathbf{x}} \mid \mathcal{D}) = \int p(\tilde{\mathbf{x}}\mid \theta),p(\theta\mid \mathcal{D}),d\theta)

O posterior preditivo é especialmente importante para checagem de modelo e tomada de decisão, porque ele diz o que o modelo espera em seguida dada a incerteza nos parâmetros.

Por que a inferência é necessária (e por que é difícil)

Para a maioria dos modelos interessantes, o posterior não pode ser computado em forma fechada porque requer integrar sobre variáveis latentes: [ p(\mathbf{z}\mid \mathbf{x}) \propto p(\mathbf{x}\mid \mathbf{z})p(\mathbf{z}), \quad p(\mathbf{x})=\int p(\mathbf{x}\mid \mathbf{z})p(\mathbf{z}),d\mathbf{z}. ]

Essa integral frequentemente é intratável em alta dimensão ou com verossimilhanças não conjugadas. Sistemas de programação probabilística, portanto, oferecem métodos de inferência aproximada.

Métodos de inferência automatizada

Monte Carlo via Cadeias de Markov (MCMC) (Markov Chain Monte Carlo)

MCMC produz amostras do posterior. Se você consegue obter amostras ({\mathbf{z}^{(s)}}\sim p(\mathbf{z}\mid \mathbf{x})), então expectativas viram médias de Monte Carlo.

Famílias comuns de MCMC:

  • Metropolis–Hastings: propósito geral, mas pode misturar lentamente em alta dimensão
  • Amostragem de Gibbs: eficiente quando distribuições condicionais são tratáveis
  • Monte Carlo Hamiltoniano (HMC) (Hamiltonian Monte Carlo) e NUTS: amostragem baseada em gradientes que escala bem para modelos contínuos e diferenciáveis (amplamente usada em Stan e muitos sistemas modernos)

Notas práticas:

  • MCMC frequentemente é o “padrão-ouro” em precisão, mas pode ser computacionalmente caro.
  • Diagnósticos importam: tamanho efetivo de amostra (ESS) (effective sample size), (\hat{R}) (Gelman–Rubin), gráficos de traço, autocorrelação, transições divergentes (HMC/NUTS).

Se você pode pagar o custo e o modelo é adequado (latentes contínuos, densidade log diferenciável), MCMC é um forte padrão.

Inferência variacional (VI) (Variational Inference)

A Inferência Variacional transforma a inferência em um problema de otimização. Você escolhe uma família de distribuições aproximadoras (q_\phi(\mathbf{z})) e minimiza (D_{\mathrm{KL}}(q_\phi(\mathbf{z});|;p(\mathbf{z}\mid \mathbf{x}))), geralmente maximizando o ELBO: [ \mathcal{L}(\phi) = \mathbb{E}{q\phi}[\log p(\mathbf{x},\mathbf{z}) - \log q_\phi(\mathbf{z})]. ]

VI normalmente é mais rápida do que MCMC e funciona bem em escala, mas introduz viés de aproximação dependendo de quão flexível (q_\phi) é.

Variantes comuns de VI:

  • VI de campo médio (mean-field VI): assume independência entre dimensões latentes (rápida, às vezes restritiva demais)
  • VI estruturada (structured VI): mantém algumas dependências
  • Gradientes por reparametrização (reparameterization gradients): gradientes de baixa variância para latentes contínuos (importante para PPLs diferenciáveis)
  • Fluxos normalizantes (normalizing flows): famílias variacionais mais ricas a um custo maior
  • Inferência amortizada (amortized inference): uma “rede de inferência” prediz parâmetros variacionais a partir dos dados (comum em modelos gerativos profundos)

VI frequentemente depende de otimizadores como os usados em Descida do Gradiente.

Outras abordagens de inferência

Dependendo da PPL, você também pode ver:

  • Amostragem por importância (importance sampling) e SMC (Monte Carlo Sequencial) (Sequential Monte Carlo): úteis para séries temporais/modelos de espaço de estados e dados em streaming
  • Inferência exata (exact inference) para pequenos modelos discretos (enumeração, eliminação de variáveis)
  • Métodos híbridos (por exemplo, MCMC dentro de VI, particle MCMC)

Fluxo de trabalho típico em programação probabilística

Um fluxo de trabalho prático se parece com:

  1. Especificar o modelo

    • Escolha priors, verossimilhança e estrutura latente.
    • Use conhecimento de domínio para definir priors razoáveis (muitas vezes priors levemente informativos ajudam muito).
  2. Rodar checagens preditivas do prior

    • Amostre do modelo antes de ver dados.
    • Pergunte: “Os dados simulados parecem plausíveis?” Isso detecta cedo erros de escala e priors irreais.
  3. Condicionar nos dados e rodar a inferência

    • Escolha MCMC (precisão) ou VI (velocidade/escala), conforme a necessidade.
  4. Diagnosticar a inferência

    • Para MCMC: (\hat{R}), ESS, divergências, gráficos de traço.
    • Para VI: estabilidade do ELBO, sensibilidade à inicialização, checagens preditivas posteriores.
  5. Checagens preditivas posteriores

    • Simule conjuntos de dados replicados a partir do posterior preditivo e compare com dados reais.
    • Isso testa o ajuste do modelo de forma bayesiana.
  6. Iterar

    • Refine priors, verossimilhança ou estrutura latente.
    • Considere modelos alternativos; use ferramentas como Critérios de Informação com cautela (eles aproximam desempenho preditivo, mas não substituem checagens substantivas).
  7. Usar o posterior

    • Faça previsões probabilísticas e quantifique incerteza.
    • Avalie previsões probabilísticas com Regras de Pontuação Próprias (por exemplo, log score).

Exemplos práticos

Os exemplos a seguir usam “pseudocódigo genérico de PPL” para ilustrar conceitos; a sintaxe real varia entre Stan/PyMC/Pyro/NumPyro/Turing.

Exemplo 1: Lançamentos de moeda (Beta–Binomial)

Suponha que observamos (n) lançamentos de moeda com (k) caras. Queremos o posterior sobre o viés da moeda (p).

Modelo:

  • Prior: (p \sim \mathrm{Beta}(\alpha,\beta))
  • Verossimilhança: (k \sim \mathrm{Binomial}(n, p))

Programa probabilístico:

def model(k, n):
    p = sample("p", Beta(alpha=2, beta=2))      # prior belief: near fair
    observe("k", Binomial(n=n, p=p), k)         # condition on observed heads
    return p

A inferência retorna uma distribuição posterior sobre p. Neste caso conjugado, o posterior é analítico: [ p\mid k,n \sim \mathrm{Beta}(\alpha+k,\beta+n-k). ] Uma PPL ainda funcionará, mas este exemplo mostra a mecânica central: sample + observe ⇒ posterior.

Pergunta de posterior preditivo: “Qual é a probabilidade de o próximo lançamento dar cara?” [ P(\tilde{x}=\text{heads}\mid \mathcal{D}) = \mathbb{E}[p\mid \mathcal{D}]. ]

Exemplo 2: Regressão linear bayesiana

Modelamos (y) como uma função linear ruidosa de atributos (X):

[ y_i \sim \mathcal{N}(X_i w, \sigma) ]

Programa probabilístico:

def model(X, y):
    D = X.shape[1]
    w = sample("w", Normal(0, 1).expand(D))          # weights
    sigma = sample("sigma", HalfNormal(1.0))         # noise std
    mu = X @ w
    observe("y", Normal(mu, sigma), y)
    return w, sigma

Notas da prática:

  • Padronize os atributos; caso contrário, priors como Normal(0,1) podem ser inadequados.
  • O posterior sobre os pesos captura incerteza: você pode obter intervalos credíveis, coeficientes correlacionados e previsões com consciência de incerteza.
  • Se você comparar isso com Estimativa de Máxima Verossimilhança (MLE) (Maximum Likelihood Estimation (MLE)), a regressão bayesiana adiciona um prior e retorna uma distribuição em vez de uma estimativa pontual.

Este é um ponto de entrada comum na programação probabilística porque:

  • o modelo é simples,
  • a inferência (MCMC/VI) é estável,
  • a quantificação de incerteza é imediatamente útil.

Exemplo 3: Modelagem hierárquica (multinível) para efeitos de grupo

Modelos hierárquicos são um “ponto doce” para programação probabilística: eles são fáceis de escrever como código e dolorosos de ajustar corretamente à mão para cada variação.

Suponha que você observe resultados (y_{i}) com um rótulo de grupo (g_i \in {1,\dots,G}). Você quer médias por grupo, mas também quer pooling parcial (partial pooling) para que grupos pequenos “tomem emprestada” força da população geral.

Modelo:

  • Média global (\mu_0)
  • Desvios por grupo (a_g) extraídos de uma distribuição compartilhada (hierarquia)
  • Observações em torno de (\mu_0 + a_{g_i})
def model(group, y, G):
    mu0 = sample("mu0", Normal(0, 2))
    tau = sample("tau", HalfNormal(1.0))             # group spread
    a = sample("a", Normal(0, tau).expand(G))        # group offsets
    sigma = sample("sigma", HalfNormal(1.0))
    mu = mu0 + a[group]
    observe("y", Normal(mu, sigma), y)
    return mu0, a, tau, sigma

Por que isso importa:

  • Se você ajustar cada grupo separadamente, estimativas para grupos pequenos ficam extremamente ruidosas.
  • Se você fizer pooling total (uma média para todos), você perde diferenças reais entre grupos.
  • Bayes hierárquico interpola automaticamente entre esses extremos.

Esse padrão aparece em testes A/B com muitas variantes, efeitos de usuário/item em recomendação e estudos experimentais multi-sítio.

Quando programação probabilística é útil vs. inferência derivada à mão

Programação probabilística nem sempre é a melhor ferramenta. Ela se destaca quando a complexidade do modelo ou a velocidade de iteração domina.

Quando programação probabilística é uma boa escolha

  • Modelos bayesianos personalizados além da conjugação de livros-texto
  • Modelos hierárquicos com muitos grupos e pooling parcial
  • Modelos com variáveis latentes (misturas, modelos fatoriais, mecanismos de dados ausentes)
  • Quantificação de incerteza é central
    • por exemplo, “Qual é a probabilidade de este parâmetro ser positivo?” e não apenas “Qual é sua estimativa?”
  • Iteração rápida de modelos
    • Você pode modificar o programa e rerodar a inferência sem rederivar a matemática.
  • Tomada de decisão probabilística
    • Sistemas a jusante podem usar distribuições posteriores preditivas, não apenas previsões pontuais.

Quando inferência derivada à mão ou especializada pode ser melhor

  • Modelos conjugados com formas fechadas
    • Se o posterior é analítico, implementar a forma fechada é mais rápido e simples.
  • Modelos em escala muito grande que exigem aproximações especializadas
    • Para alguns problemas, atualizações variacionais sob medida ou truques específicos do domínio superam motores gerais de inferência.
  • Restrições de streaming / tempo real
    • Você pode precisar de filtros ou atualizações incrementais (por exemplo, algoritmos no estilo filtro de Kalman) em vez de amostragem completa do posterior.
  • Estruturas latentes discretas e de alta dimensão
    • MCMC genérico pode ter dificuldade quando há muitas variáveis latentes discretas ou forte multimodalidade (embora algumas PPLs ofereçam amostradores especializados).

Um modelo mental útil: programação probabilística troca esforço de derivação por esforço computacional. Você gasta menos tempo derivando inferência e mais tempo deixando o sistema computar aproximações.

Considerações práticas e armadilhas comuns

Especificação incorreta do modelo e priors fracos

Uma PPL pode computar um posterior para um modelo ruim tão facilmente quanto para um bom. Problemas comuns:

  • priors que são involuntariamente amplos demais (levando a previsões irreais)
  • verossimilhança que ignora caudas pesadas/outliers
  • dependências não modeladas (suposições de independência violadas)

Checagens preditivas do prior e checagens preditivas posteriores são essenciais.

Identificabilidade e geometrias de “funil”

Alguns modelos têm parâmetros que não são bem identificados pelos dados, produzindo geometrias posteriores desafiadoras (correlações fortes, funis). Sintomas incluem:

  • transições divergentes em HMC/NUTS
  • mistura extremamente lenta
  • soluções de VI instáveis

Reparametrização (por exemplo, parametrizações não centradas para modelos hierárquicos) frequentemente é crucial.

Multimodalidade e troca de rótulos

Modelos de mistura podem produzir múltiplos modos simétricos (trocando rótulos de componentes). A inferência pode:

  • misturar lentamente entre modos (MCMC)
  • colapsar em um modo (VI)

Você pode precisar de restrições, priors informativos ou pós-processamento.

Diagnósticos fazem parte do trabalho

Inferência automatizada não é “estatística de apertar botão”. Você precisa verificar:

  • as cadeias convergiram (se estiver usando MCMC)
  • a aproximação é adequada (se estiver usando VI)
  • desempenho preditivo e calibração são aceitáveis

Aplicações em IA e AM

Programação probabilística é usada em pesquisa e indústria:

  • Modelagem científica: regressão bayesiana, análises hierárquicas, modelos de erro de medição
  • Séries temporais e modelos de espaço de estados: previsão com incerteza
  • Aprendizado de máquina probabilístico (probabilistic machine learning): modelos lineares generalizados bayesianos, modelos de fatores latentes
  • Aprendizado profundo bayesiano (Bayesian deep learning): combinar componentes neurais com camadas probabilísticas (frequentemente via VI)
  • Modelagem causal: modelos gerativos probabilísticos podem codificar suposições sobre geração de dados e intervenções (frequentemente junto a frameworks causais)

Ela complementa a modelagem “puramente preditiva” (por exemplo, muitas aplicações de Redes Neurais) ao enfatizar suposições estruturadas e incerteza.

Resumo

Programação probabilística permite que você:

  • expresse modelos bayesianos como programas executáveis (variáveis aleatórias + processo gerativo + condicionamento),
  • dependa de inferência automatizada (MCMC, VI, SMC) para aproximar posteriores,
  • itere rapidamente sobre modelos complexos que seriam tediosos de tratar à mão,
  • e produza previsões e decisões conscientes de incerteza.

Ela é mais valiosa quando a estrutura do modelo importa e a incerteza não é opcional — ainda exigindo modelagem cuidadosa, diagnósticos e validação para garantir que os posteriores inferidos reflitam a realidade em vez de artefatos de um modelo mal especificado ou de um algoritmo de inferência com dificuldades.