%pip install pymc arviz numpy pandas matplotlib ipywidgets
Insea 2025 Statistiques Bayésiennes
TP3: Introduction à PyMC
Author: Hicham Janati
Objectifs:
- Découvrir la librairie PyMC
- Implémenter les premiers modèles bayésiens et faire le diagnostic de convergence
- Interpréter les résultats et comparer avec les statistiques fréquentistes
On importe les librariries et un crée un générateur aléatoire:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pymc as pm
import arviz as az
= 42
seed = np.random.default_rng(seed) rng
Modèle Poisson-Gamma simple
On considère l’ex 1 du TD1. On observe le nombre de sinistres par année \(N | \lambda \sim \mathcal{P}(\lambda)\), où \(\lambda\) suit une loi a priori \(\text{Gamma}(a, b)\). On suppose que les données historiques mènent au choix a = 4 et b = 2. On utilise la définition de Gamma où b correspond au “rate” et non pas au scale (comme en TD), la moyenne de cette Gamma est a/b. (voir wikipedia).
On suppose que les 5 observations sont données par:
= np.array([4, 0, 2, 1, 0])
data = np.random.default_rng(42)
rng = 2
lambda_true = rng.poisson(lambda_true, size=5) data
On définit le modèle bayésien dans un contexte avec pymc ou on précise la distribution, le nom et les paramètres de chaque variable
= 4
a = 2
b with pm.Model() as model:
= pm.Gamma("lambda", a, b) # non observée
lambda_ = pm.Poisson("N_obs", mu=lambda_, observed=data) # observée
N_obs = pm.sample(1000) # on simule une chaine MCMC de la loi a posteriori trace
On voit que pymc a automatiquement choisi NUTS et a simulé 4 chaînes avec 2000 échantillons chacune dont 1000 jetés (tuning / burn-in). Voyons ce que contient l’objet trace:
trace
C’est un objet InferenceData
du package arviz
. On peut obtenir les échantillons simulés dans l’attribut posterior:
"lambda"].data trace.posterior[
"lambda"].data.shape trace.posterior[
On a effectivement généré 4 chaines avec 1000 échantillons chacune. On peut les visualiser:
plt.figure()"lambda"].data.T)
plt.plot(trace.posterior[True)
plt.grid("Trace plot")
plt.title( plt.show()
Ou utiliser la librairie arviz directement qui donne également une estimation de la densité a posteriori:
az.plot_trace(trace)
On fait le diagnostic de convergence:
az.plot_autocorr(trace) plt.show()
az.summary(trace)
az.summary(trace)
- Les tracés sont bien mélangés
- Rhat = 1.0 < 1.01 : pas de différence significative entre les 4 chaines
- ESS très larges
- Autocorrélations diminuent très rapidement
Cette chaîne réussit les diagnostics de convergence.
On peut visualiser la densité a posteriori avec l’intervalle de crédibilité HDI:
=0.94) az.plot_posterior(trace, hdi_prob
Les bornes de cet intervalle sont également présente dans le tableau du summary ci-dessus. On peut calcule un HDI directement:
=0.94).to_pandas() az.hdi(trace, hdi_prob
On peut calculer un ESS relatif (divisé par le nombre d’échantillons):
="bulk", relative=True).to_pandas() az.ess(trace, method
Ainsi, on en déduit que 44% des échantillons “sont efficaces” pour estimer “le centre” (bulk) de la distribution
Question 1: Augmenter le nombre de sinistres observés de 5 à 10 puis 100, comment changent les statistiques du az.summary ?
Question 2: Les métriques de convergence sont-elle très différentes ? Est-ce surprenant ?
Question 3: Comment peut-on interpréter le HDI obtenu ?
Question 4: En utilisant le fait que la loi a priori soit conjuguée, générez des échantillons a posteriori directement (sans pymc) et comparez
La loi a posteriori est \(\text{Gamma}(a + \sum_{i=1}^n N_i, b + n)\)
= rng.gamma(a + data.sum(), 1/(b + len(data)), size=(4, 1000))
lambda_post_samples
= az.plot_posterior(trace)
ax =100, density=True, alpha=0.7)
ax.hist(lambda_post_samples, bins plt.show()
= az.convert_to_inference_data(dict(iid=lambda_post_samples))
trace_iid az.summary(trace_iid)
lambda_post_samples.mean()
az.summary(trace)
Modèle Poisson-Gamma à plusieurs conducteurs
On considère désormais les données de plusieurs individus avec un \(\lambda_i\) différent mais un a priori commun. Chaque conducteur \(i\) a ses données \(N_i^1, \dots, N_i^m\). Le modèle pymc s’adapte facilement en changeant le shape des paramètres:
= np.array([[4, 0, 0],
data 0, 2, 1],
[2, 2, 0],
[1, 3, 0],
[0, 0, 1]])
[
# donnéees de 3 conducteurs
= data.shape[1]
n_drivers
= 4
a = 2
b with pm.Model() as model:
= pm.Gamma("lambda", a, b, shape=n_drivers) # non observée, on précise le nombre de lambda
lambda_ # le shape des données par défaut est n_observation x n_features, pymc associe chaque lambda_i a une colonne de data
= pm.Poisson("N_obs", mu=lambda_, observed=data, ) # observée
N_obs = pm.sample(1000) # on simule une chaine MCMC de la loi a posteriori
trace pm.compute_log_likelihood(trace)
L’objet trace contient désormais plusieurs variables lambda:
print(az.summary(trace))
Question 4: Comparez l’estimation fréquentiste avec l’estimation bayésienne avec (a, b) = (4, 2) puis (a, b) = (10, 1).
0) data.mean(
Question 5: Déterminez un intervalle de confiance fréquentiste de niveau 95% pour chaque \(\lambda_i\).
\([\bar{N} \pm \frac{Q_{97.5}\hat{\sigma}}{\sqrt{n}}]\) avec \(Q_{97.5}\) le quantile de la loi de student à n-1 degrés de liberté.
from scipy.stats import t
= 5
n = t.ppf(0.975, df=n-1)
q975 = data.std(0)
sigma
0) - q975 * sigma / n ** 0.5, data.mean(0) + q975 * sigma / n ** 0.5, data.mean(
az.summary(trace)
Question 6: On reprend à présent une loi a priori Uniforme([0, 5]). Déterminez des HDI de niveau 95% pour chaque \(\lambda_i\). Comment se comparent-ils aux intervalles de confiance fréquentistes ?
= np.array([[4, 0, 0],
data 0, 2, 1],
[2, 2, 0],
[1, 3, 0],
[0, 0, 1]])
[
# donnéees de 3 conducteurs
= data.shape[1]
n_drivers
= 4
a = 2
b with pm.Model() as model:
= pm.Uniform("lambda", 0, 5, shape=n_drivers) # non observée, on précise le nombre de lambda
lambda_ # le shape des données par défaut est n_observation x n_features, pymc associe chaque lambda_i a une colonne de data
= pm.Poisson("N_obs", mu=lambda_, observed=data, ) # observée
N_obs = pm.sample(1000) # on simule une chaine MCMC de la loi a posteriori trace
= np.array([[4, 0, 0],
data 0, 2, 1],
[2, 2, 0],
[1, 3, 0],
[0, 0, 1]])
[
# donnéees de 3 conducteurs
= data.shape[1]
n_drivers
= 4
a = 2
b with pm.Model() as model_unif:
# lambda_ = pm.Gamma("lambda", a, b, shape=n_drivers) # non observée, on précise le nombre de lambda
= pm.Uniform("lambda", 0, 100, shape=n_drivers)
lambda_ # le shape des données par défaut est n_observation x n_features, pymc associe chaque lambda_i a une colonne de data
= pm.Poisson("N_obs", mu=lambda_, observed=data, ) # observée
N_obs = pm.sample(1000) # on simule une chaine MCMC de la loi a posteriori trace_unif
az.summary(trace_unif)
0) - q975 * sigma / n ** 0.5, data.mean(0) + q975 * sigma / n ** 0.5, data.mean(