Metodi di sintesi della distribuzione a posteriori#
L’obiettivo di questo capitolo è analizzare in dettaglio il flusso di lavoro all’interno del contesto dell’inferenza bayesiana, una volta che sia stata ottenuta la stima della distribuzione a posteriori mediante l’utilizzo della tecnica MCMC.
Preparazione del Notebook#
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import pymc as pm
import pymc.sampling_jax
import arviz as az
import scipy.stats as stats
import warnings
warnings.filterwarnings("ignore", category=UserWarning)
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category=Warning)
/Users/corrado/opt/anaconda3/envs/pymc_env/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
%config InlineBackend.figure_format = 'retina'
RANDOM_SEED = 42
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-darkgrid")
sns.set_theme(palette="colorblind")
Sintesi della Distribuzione a Posteriori#
Uno dei fattori principali che ha contribuito alla crescente popolarità dei metodi bayesiani nelle scienze sociali è stata la (ri)scoperta di algoritmi numerici capaci di stimare le distribuzioni a posteriori dei parametri del modello a partire dai dati osservati. Prima di tali sviluppi, era praticamente impossibile ottenere misure riassuntive delle distribuzioni a posteriori, specialmente per modelli complessi con un elevato numero di parametri.
Gli algoritmi numerici che esamineremo in questo capitolo fanno uso dell’integrazione Monte Carlo attraverso catene di Markov, meglio conosciuta come campionamento Markov chain Monte Carlo (MCMC). Questa tecnica ha rivoluzionato la capacità di effettuare inferenze bayesiane, rendendo più accessibile e gestibile l’analisi di modelli complessi.
Campionamento con PyMC#
A titolo di esempio, useremo ancora una volta il set di dati “moma_sample”, che è stato analizzato nel capitolo precedente. Iniziamo replicando il processo di campionamento PyMC descritto in precedenza.
y = 14
ntrials = 100
alpha_prior = 4
beta_prior = 6
with pm.Model() as bb_model:
theta = pm.Beta("theta", alpha=alpha_prior, beta=beta_prior)
obs = pm.Binomial("obs", p=theta, n=ntrials, observed=y)
with bb_model:
idata = pm.sampling_jax.sample_numpyro_nuts()
Show code cell output
Compiling...
Compilation time = 0:00:00.531103
Sampling...
0%| | 0/2000 [00:00<?, ?it/s]
Compiling.. : 0%| | 0/2000 [00:00<?, ?it/s]
0%| | 0/2000 [00:00<?, ?it/s]
Compiling.. : 0%| | 0/2000 [00:00<?, ?it/s]
0%| | 0/2000 [00:00<?, ?it/s]
Compiling.. : 0%| | 0/2000 [00:00<?, ?it/s]
0%| | 0/2000 [00:00<?, ?it/s]
Compiling.. : 0%| | 0/2000 [00:00<?, ?it/s]
Running chain 0: 0%| | 0/2000 [00:01<?, ?it/s]
Running chain 3: 0%| | 0/2000 [00:01<?, ?it/s]
Running chain 2: 0%| | 0/2000 [00:01<?, ?it/s]
Running chain 1: 0%| | 0/2000 [00:01<?, ?it/s]
Running chain 0: 100%|████████████████████████████████████████████████████| 2000/2000 [00:01<00:00, 1858.52it/s]
Running chain 1: 100%|████████████████████████████████████████████████████| 2000/2000 [00:01<00:00, 1860.85it/s]
Running chain 2: 100%|████████████████████████████████████████████████████| 2000/2000 [00:01<00:00, 1863.13it/s]
Running chain 3: 100%|████████████████████████████████████████████████████| 2000/2000 [00:01<00:00, 1865.08it/s]
Sampling time = 0:00:01.293610
Transforming variables...
Transformation time = 0:00:00.053729
La figura seguente rappresenta la distribuzione a posteriori di \(\theta\) ottenuta mediante il metodo MCMC e la traccia delle catene di Markov.
az.plot_trace(idata, combined=True)
plt.show()
![../_images/4dc2a2b2a544f6d55f0c0baf02d15bdc63f19da04c9932699302f62a1594e4f8.png](../_images/4dc2a2b2a544f6d55f0c0baf02d15bdc63f19da04c9932699302f62a1594e4f8.png)
Estrazione degli attributi da InferenceData
#
Ora che abbiamo ottenuto la distribuzione a posteriori, possiamo effettuare varie operazioni con essa:
Stima puntuale
Intervalli di credibilità
Test di ipotesi
Predizione
Stima puntuale#
Procediamo all’esame dell’oggetto idata
.
idata
-
<xarray.Dataset> Dimensions: (chain: 4, draw: 1000) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 6 7 8 ... 992 993 994 995 996 997 998 999 Data variables: theta (chain, draw) float64 0.1733 0.1732 0.2063 ... 0.1187 0.1444 0.1424 Attributes: created_at: 2024-01-23T07:45:24.000626 arviz_version: 0.17.0
-
<xarray.Dataset> Dimensions: (chain: 4, draw: 1000) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 6 ... 993 994 995 996 997 998 999 Data variables: acceptance_rate (chain, draw) float64 0.9147 0.7716 ... 0.6239 0.9669 step_size (chain, draw) float64 1.183 1.183 1.183 ... 0.9726 0.9726 diverging (chain, draw) bool False False False ... False False False energy (chain, draw) float64 5.58 5.612 5.119 ... 5.44 9.76 4.973 n_steps (chain, draw) int64 3 3 3 1 3 1 3 1 3 ... 3 3 3 3 3 3 1 3 3 tree_depth (chain, draw) int64 2 2 2 1 2 1 2 1 2 ... 2 2 2 2 2 2 1 2 2 lp (chain, draw) float64 4.508 4.507 5.118 ... 4.63 4.667 Attributes: created_at: 2024-01-23T07:45:24.002825 arviz_version: 0.17.0
-
<xarray.Dataset> Dimensions: (obs_dim_0: 1) Coordinates: * obs_dim_0 (obs_dim_0) int64 0 Data variables: obs (obs_dim_0) int64 14 Attributes: created_at: 2024-01-23T07:45:24.004056 arviz_version: 0.17.0 inference_library: numpyro inference_library_version: 0.13.2 sampling_time: 1.29361
I vari attributi dell’oggetto InferenceData
possono essere estratti come si farebbe con un dict
, in cui ciascuna coppia è composta da una chiave e un valore, separati dal simbolo dei due punti. In questa situazione, le chiavi sono i nomi delle variabili, mentre i valori sono rappresentati da array di Numpy. Per esempio, possiamo recuperare la traccia di campionamento dalla variabile latente theta
nel modo seguente.
idata.posterior["theta"]
<xarray.DataArray 'theta' (chain: 4, draw: 1000)> array([[0.17326516, 0.17320269, 0.20629908, ..., 0.12454636, 0.16953616, 0.18556175], [0.20671803, 0.14461032, 0.17306915, ..., 0.17527697, 0.1427207 , 0.15351922], [0.14086973, 0.19343519, 0.19594644, ..., 0.1142225 , 0.17836103, 0.14123355], [0.11920471, 0.14753389, 0.26728206, ..., 0.11865411, 0.14442225, 0.14238198]]) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 6 7 8 ... 992 993 994 995 996 997 998 999
Si noti che l’oggetto ritornato è un array di dimensioni 4 \(\times\) 1000 (sul mio computer):
idata.posterior["theta"].shape
(4, 1000)
Per visualizzare il primi 10 valori della prima catena, ad esempio, usiamo:
idata.posterior["theta"][0, 1:10]
<xarray.DataArray 'theta' (draw: 9)> array([0.17320269, 0.20629908, 0.18193757, 0.13434879, 0.14592237, 0.1342829 , 0.12942379, 0.2265291 , 0.23713072]) Coordinates: chain int64 0 * draw (draw) int64 1 2 3 4 5 6 7 8 9
Per combinare catene e iterazioni, utilizziamo la funzione arviz.extract()
.
post = az.extract(idata)
post
<xarray.Dataset> Dimensions: (sample: 4000) Coordinates: * sample (sample) object MultiIndex * chain (sample) int64 0 0 0 0 0 0 0 0 0 0 0 0 ... 3 3 3 3 3 3 3 3 3 3 3 3 * draw (sample) int64 0 1 2 3 4 5 6 7 ... 992 993 994 995 996 997 998 999 Data variables: theta (sample) float64 0.1733 0.1732 0.2063 ... 0.1187 0.1444 0.1424 Attributes: created_at: 2024-01-23T07:45:24.000626 arviz_version: 0.17.0
La funzione extract
ritorna un oggetto di classe xarray.DataArray il quale può essere trattato come un array NumPy.
post["theta"]
<xarray.DataArray 'theta' (sample: 4000)> array([0.17326516, 0.17320269, 0.20629908, ..., 0.11865411, 0.14442225, 0.14238198]) Coordinates: * sample (sample) object MultiIndex * chain (sample) int64 0 0 0 0 0 0 0 0 0 0 0 0 ... 3 3 3 3 3 3 3 3 3 3 3 3 * draw (sample) int64 0 1 2 3 4 5 6 7 ... 992 993 994 995 996 997 998 999
Si noti che post["theta"]
contiene il numero totale (4000) dei campioni a posteriori della distribuzione di \(\theta\) escluso il burn-in:
post["theta"].shape
(4000,)
Di conseguenza, possiamo calcolare su di esso tutte le misure statistiche descrittive che si possono ottenere da un vettore di dati. Per esempio, possiamo calcolare la media a posteriori.
post.mean()
<xarray.Dataset> Dimensions: () Data variables: theta float64 0.1647
post["theta"].mean()
<xarray.DataArray 'theta' ()> array(0.1647053)
Possiamo calcolare la mediana a posteriori di \(\theta\).
post.median()
<xarray.Dataset> Dimensions: () Data variables: theta float64 0.1626
Oppure la deviazione standard della stima a posteriori di \(\theta\).
np.std(post["theta"])
<xarray.DataArray 'theta' ()> array(0.03581982)
Intervallo di credibilità#
L’inferenza bayesiana tramite l’intervallo di credibilità riguarda invece la stima dell’intervallo che contiene il parametro \(\theta\) ad un dato livello di probabilità soggettiva.
Usando idata
, possiamo ottenere un sommario della distribuzione a posteriori con il metodo az.summary()
.
az.summary(idata, hdi_prob=0.94, round_to=3)
mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
---|---|---|---|---|---|---|---|---|---|
theta | 0.165 | 0.036 | 0.099 | 0.233 | 0.001 | 0.001 | 1586.797 | 1643.117 | 1.001 |
Si ottiene così l’intervallo di credibilità a più alta densità a posteriori (HPD) al 94%. Questo intervallo ci informa sul fatto che, a posteriori, possiamo essere certi al 94%, che il vero valore del parametro \(\theta\) sia contenuto nell’intervallo [0.103, 0.23].
Dato che, nel caso presente, conosciamo la soluziona analitica, possiamo verificare il risultato precedente calcolando i quantili della distribuzione a posteriori Beta(18, 92) di ordine 0.03 e 0.97.
ll = stats.beta.ppf(0.03, 18, 92)
ul = stats.beta.ppf(0.97, 18, 92)
list([ll, ul])
[0.10303527075398666, 0.23457657606771784]
Verifica di ipotesi bayesiana#
Un secondo tipo di inferenza bayesiana riguarda problemi in cui siamo interessati a valutare la plausibilità che il parametro \(\theta\) assuma valori contenuti in un dato intervallo di valori. Per esempio, ci potrebbe interessare l’ipotesi \(\theta > 0.5\). In questo caso, possiamo calcolare la probabilità a posteriori che \(\theta\) cada nell’intervallo di interesse, integrando la distribuzione a posteriori Beta su tale intervallo.
Nel caso dell’esempio degli artisti della Generazione X, supponiamo di essere interessati alle due seguenti ipotesi:
La nostra domanda è la seguente: Date le nostre credenze iniziali e i dati disponibili, quale importanza relativa possiamo attribuire a queste due ipotesi?
Per affrontare questa questione, iniziamo a calcolare la probabilità \(P(\theta < 0.2)\).
print(np.mean(post["theta"] < 0.2))
<xarray.DataArray 'theta' ()>
array(0.83525)
Passiamo ora a calcolare gli odds a posteriori:
post_odds = (np.mean(post["theta"] < 0.2)) / (1 - np.mean(post["theta"] < 0.2))
print(post_odds)
<xarray.DataArray 'theta' ()>
array(5.06980273)
Ciò implica che la probabilità che \(\pi\) sia inferiore al 20% è circa 6 volte superiore rispetto alla probabilità che sia al di sopra del 20%.
Questo risultato si basa solo sulle informazioni relative alla distribuzione a posteriori. Prima di avere osservato i dati del campione, avevamo una distribuzione a priori \(\operatorname{Beta}(6, 4)\), e in quel contesto avevamo una probabilità del 9% che \(H_a\) fosse vera e una probabilità del 91% che fosse falsa.
threshold = 0.2
prior_prob = stats.beta.cdf(threshold, a=alpha_prior, b=beta_prior)
prior_odds = prior_prob / (1 - prior_prob)
print(prior_odds)
0.09366320688790145
Ora possiamo combinare le informazioni degli odds a posteriori e degli odds a priori in una quantità chiamata Bayes Factor, che è semplicemente il rapporto tra le due:
BF = post_odds / prior_odds
print(BF)
<xarray.DataArray 'theta' ()>
array(54.12800714)
In conclusione, dopo aver appreso informazioni riguardo a 14 artisti appartenenti alla generazione X, le probabilità posteriori della nostra ipotesi \(H_a: \; \pi < 0.2\) sono circa 60 volte superiori rispetto alle probabilità a priori.
Questo è un esempio di test di ipotesi bayesiano.
Commenti e considerazioni finali#
In questo capitolo sono state presentate le strategie per la trasformazione della distribuzione a posteriori. Sono state esplorate le diverse modalità per ottenere un intervallo di credibilità. In seguito, è stata affrontata l’analisi delle ipotesi a posteriori. Questo approccio permette di comparare in maniera relativa due ipotesi contrapposte riguardanti il parametro \(\theta\). In alcune circostanze, tale confronto viene tradotto in una misura denominata Fattore di Bayes.
%load_ext watermark
%watermark -n -u -v -iv -w
Last updated: Tue Jan 23 2024
Python implementation: CPython
Python version : 3.11.7
IPython version : 8.19.0
scipy : 1.11.4
seaborn : 0.13.0
matplotlib: 3.8.2
arviz : 0.17.0
numpy : 1.26.2
pandas : 2.1.4
pymc : 5.10.3
Watermark: 2.4.3