Un reseau bayesien represente des relations probabilistes entre variables :
[Maladie] ----> [Symptome]
\--------> [Test]
Extension : Reseaux de Decision (Influence Diagrams)
Un reseau de decision ajoute :
Noeuds de decision : choix de l’agent
Noeuds d’utilite : consequences des choix
Arcs informationnels : ce que l’agent sait au moment de decider
[Maladie] ----> [Test Result]
| |
v v
<Traitement> ----> ((Utilite))
Configuration de l’environnement
Nous chargeons les bibliotheques necessaires : PyMC pour la modelisation probabiliste, ArviZ pour l’analyse des résultats, et numpy/scipy pour les calculs numériques.
import numpy as npimport pymc as pmimport arviz as azfrom scipy import statsfrom dataclasses import dataclass, fieldfrom enum import Enumfrom typing import Optionalprint(f"PyMC version: {pm.__version__}")print(f"ArviZ version: {az.__version__}")print("Environnement pret !")
PyMC version: 5.28.5
ArviZ version: 0.23.4
Environnement pret !
2. Types de Noeuds
Les reseaux de decision utilisent une notation graphique standardisee (Howard & Matheson, 1984) qui distingue clairement les différents types de noeuds. Cette convention visuelle permet de lire immediatement la structure du problème decisionnel.
Convention graphique
Type
Forme
Symbole
Description
Exemple
Chance
Ovale/Cercle
[X]
Variable aleatoire non controlee
Maladie, Marche, Meteo
Decision
Rectangle
Choix contrôle par l’agent
Traiter, Investir, Acheter
Utilite
Losange/Hexagone
((U))
Fonction de valeur/payoff
Profit, Sante, Satisfaction
Arcs et leur signification
Chaque type d’arc encode une relation spécifique dans le problème :
Type d’arc
Signification
Interpretation
Chance -> Chance
Dépendance probabiliste
P(B|A) - A influence B
Chance -> Decision
Arc informationnel
L’agent observe A avant de choisir D
Chance -> Utilite
La variable affecte l’utilite
U depend de la valeur de A
Decision -> Chance
La decision influence le résultat
L’action D modifie P(B)
Decision -> Utilite
Cout/benefice direct de la decision
U inclut le cout de D
Ordre temporel implicite
Dans un reseau de decision bien forme : 1. Les noeuds de decision sont totalement ordonnes (D1 avant D2 avant D3…) 2. Un arc informationnel vers Di signifie que la variable est connue avant Di 3. Pas de cycles impliquant des noeuds de decision
La cellule suivante définit une structure de données simple pour representer les noeuds d’un reseau de decision et illustre la notation graphique sur un exemple medical.
Reseau de decision : Diagnostic Medical
[Maladie]
[Test] <- Maladie
<Traiter> <- Test
[Sante] <- Maladie, Traiter
((Utilite)) <- Sante, Traiter
Interpretation de la structure
Le reseau ci-dessus represente un problème de diagnostic medical classique :
Noeud
Type
Rôle
[Maladie]
Chance
Etat du patient (inconnu)
[Test]
Chance
Résultat observable, depend de Maladie
<Traiter>
Decision
Choix du medecin, base sur le résultat du test
[Sante]
Chance
Etat futur, depend de Maladie ET de la decision
((Utilite))
Utilite
Valeur finale, depend de Sante et du cout du traitement
Point cle : L’arc Test -> Traiter est un arc informationnel. Il indique que le medecin connait le résultat du test avant de decider. Sans cet arc, le medecin devrait decider “en aveugle”.
3. Arcs Informationnels
Les arcs informationnels sont fondamentaux pour modeliser ce que l’agent sait au moment de prendre une decision. Ils capturent l’asymetrie d’information qui rend les problemes de decision interessants.
Concept cle
Un arc informationnel vers un noeud de decision indique ce que l’agent sait au moment de prendre cette decision. C’est la différence entre agir en aveugle et agir en connaissance de cause.
Impact sur la politique
Situation
Politique
Complexite
Aucune information
Action unique
1 decision
Test observe
Action conditionnelle au test
2^k decisions (k = bits d’information)
Information parfaite
Action conditionnelle a l’etat
Action optimale par etat
Valeur de l’information
L’ajout d’un arc informationnel peut augmenter l’utilite esperee. La différence est la valeur de l’information que nous etudierons dans le notebook suivant (DecPyMC-5).
# Impact des arcs informationnelsprint("Scenarios d'information :\n")# Prior sur la maladiep_maladie =0.1sensibilite =0.95# P(test+|malade)specificite =0.90# P(test-|sain)# UtilitesU_traiter_malade =90# GuerisonU_traiter_sain =70# Effets secondaires inutilesU_pas_traiter_malade =10# Maladie non traiteeU_pas_traiter_sain =100# Parfait# Scenario 1 : Pas d'information (decision a priori)EU_traiter = p_maladie * U_traiter_malade + (1- p_maladie) * U_traiter_sainEU_pas_traiter = p_maladie * U_pas_traiter_malade + (1- p_maladie) * U_pas_traiter_sainprint("Scenario 1 : Decision SANS information (pas de test)")print(f" P(malade) = {p_maladie:.0%}")print(f" E[U(traiter)] = {EU_traiter:.1f}")print(f" E[U(pas traiter)] = {EU_pas_traiter:.1f}")print(f" => Decision : {'TRAITER'if EU_traiter > EU_pas_traiter else'PAS TRAITER'}\n")# Scenario 2 : Decision AVEC information (apres test)# Calculer P(malade|test+) et P(malade|test-) par Bayesp_test_pos = p_maladie * sensibilite + (1- p_maladie) * (1- specificite)p_malade_test_pos = (p_maladie * sensibilite) / p_test_posp_malade_test_neg = (p_maladie * (1- sensibilite)) / (1- p_test_pos)print("Scenario 2 : Decision AVEC information (apres test)")print(f" P(test+) = {p_test_pos:.1%}")print(f" P(malade|test+) = {p_malade_test_pos:.1%}")print(f" P(malade|test-) = {p_malade_test_neg:.1%}")# Decision si test positifEU_traiter_tp = p_malade_test_pos * U_traiter_malade + (1- p_malade_test_pos) * U_traiter_sainEU_pas_traiter_tp = p_malade_test_pos * U_pas_traiter_malade + (1- p_malade_test_pos) * U_pas_traiter_sainprint(f" Si test+ : traiter={EU_traiter_tp:.1f}, pas traiter={EU_pas_traiter_tp:.1f} "f"=> {'TRAITER'if EU_traiter_tp > EU_pas_traiter_tp else'PAS TRAITER'}")# Decision si test negatifEU_traiter_tn = p_malade_test_neg * U_traiter_malade + (1- p_malade_test_neg) * U_traiter_sainEU_pas_traiter_tn = p_malade_test_neg * U_pas_traiter_malade + (1- p_malade_test_neg) * U_pas_traiter_sainprint(f" Si test- : traiter={EU_traiter_tn:.1f}, pas traiter={EU_pas_traiter_tn:.1f} "f"=> {'TRAITER'if EU_traiter_tn > EU_pas_traiter_tn else'PAS TRAITER'}")
Scenarios d'information :
Scenario 1 : Decision SANS information (pas de test)
P(malade) = 10%
E[U(traiter)] = 72.0
E[U(pas traiter)] = 91.0
=> Decision : PAS TRAITER
Scenario 2 : Decision AVEC information (apres test)
P(test+) = 18.5%
P(malade|test+) = 51.4%
P(malade|test-) = 0.6%
Si test+ : traiter=80.3, pas traiter=53.8 => TRAITER
Si test- : traiter=70.1, pas traiter=99.4 => PAS TRAITER
Exercice : Decision de recrutement avec lettre de reference
Vous etes responsable des recrutements. Un candidat se presente, et vous devez decider de l’embaucher ou non. Avant de decider, vous pouvez demander une lettre de reference qui vous renseignera (imparfaitement) sur la qualite du candidat.
Scénario : - Variable aleatoire : bon_candidat (Oui/Non), avec prior p_bon - Test : lettre de reference, avec sensibilite (recommande un bon candidat) et specificite (deconseille un mauvais candidat) - Decision : embaucher ou refuser - Utilites : productivite d’un bon employe, cout d’un mauvais recrutement, neutralite si pas d’embauche
Objectif : Comparer la decision avec et sans lettre de reference. L’information supplementaire change-t-elle la decision optimale ?
Indices : - Étape 1 : Définir les probabilites a priori et la qualite de la lettre de reference - Étape 2 : Calculer E[U] sans lettre de reference (decision a priori) - Étape 3 : Appliquer le theoreme de Bayes pour obtenir les posteriors P(bon|lettre+) et P(bon|lettre-) - Étape 4 : Calculer E[U] avec lettre de reference et comparer les deux scénarios
# Exercice : Decision de recrutement avec lettre de reference# Etape 1 : Parametres du problemep_bon =None# TODO etudiant : P(bon candidat), essayez 0.3sens_lettre =None# TODO etudiant : sensibilite (lettre favorable si bon), essayez 0.80spec_lettre =None# TODO etudiant : specificite (lettre defavorable si mauvais), essayez 0.75# UtilitesU_embaucher_bon =None# TODO etudiant : gain si bon recrutement, essayez 100U_embaucher_mauvais =None# TODO etudiant : cout si mauvais recrutement, essayez -60U_pas_embaucher =0# Neutre# Etape 2 : Decision sans lettre de reference (a priori)# TODO etudiant : calculer E[U(embaucher)] et E[U(pas embaucher)]eu_embaucher_sans =None# TODO etudianteu_pas_embaucher_sans =None# TODO etudiant# Etape 3 : Posterior apres lecture de la lettre (theoreme de Bayes)# TODO etudiant : calculer P(lettre+), P(bon|lettre+), P(bon|lettre-)p_lettre_pos =None# TODO etudiantp_bon_lpos =None# TODO etudiant : P(bon candidat|lettre favorable)p_bon_lneg =None# TODO etudiant : P(bon candidat|lettre defavorable)# Etape 4 : Decision avec lettre de reference# TODO etudiant : pour chaque resultat de lettre, calculer E[U(embaucher)] et E[U(pas embaucher)]# puis calculer E[U] totale avec informationeu_avec_lettre =None# TODO etudiant# Resultat : comparer les deux scenariosresult =None# TODO etudiant : eu_avec_lettre - max(eu_embaucher_sans, eu_pas_embaucher_sans)print("Exercice a completer : decision de recrutement avec/sans lettre de reference")
Exercice a completer : decision de recrutement avec/sans lettre de reference
Analyse des résultats : Impact de l’information
Les résultats ci-dessus illustrent le theoreme fondamental de la valeur de l’information :
Scénario
Decision
E[U]
Commentaire
Sans test
Pas traiter
91.0
Prior faible (10%), risque acceptable
Avec test+
Traiter
80.3
Posterior eleve (51.4%), traitement justifie
Avec test-
Pas traiter
99.4
Posterior très faible (0.6%), rassurant
Pourquoi la decision change-t-elle ?
A priori : P(malade) = 10% est faible. Le cout des effets secondaires (U=70 vs 100) domine.
Test positif : Le theoreme de Bayes concentre la probabilite : \[P(\text{malade}|\text{test}+) = \frac{0.10 \times 0.95}{0.185} = 51.4\%\] Le risque devient majoritaire, justifiant le traitement.
Test negatif : La probabilite residuelle (0.6%) est negligeable.
Lecon : L’information ne change pas toujours la decision (si le prior etait de 50%, on traiterait dans tous les cas). La valeur de l’information depend de la distance entre le prior et les seuils de decision.
4. Calcul de la Politique Optimale
Algorithme Backward Induction
L’algorithme standard pour resoudre un reseau de decision est la backward induction (ou programmation dynamique de Bellman, 1957), formalisee pour les diagrammes d’influence par Shachter (1986). L’idee est de resoudre le problème de la fin vers le debut.
Algorithme pas a pas
Étape 1 : Dernière decision (Dn) - Pour chaque configuration possible des parents informationnels de Dn : - Calculer E[U | parents, action] pour chaque action possible - Sélectionner l’action qui maximise E[U]
Étape 2 : Remonter (Dn-1, Dn-2, …) - Utiliser les valeurs optimales des decisions suivantes
Étape 3 : Première decision (D1) - La dernière itération donne la decision optimale initiale
Remarque : Cette utilite (95.90) est superieure a celle sans test (91.0). La différence (4.90) represente la valeur de l’information apportee par le test.
5. Exemple : Investissement avec Test de Marche
Scénario
Un investisseur considere un projet. Le marche peut etre favorable ou non. Il peut commander une étude de marche avant de decider.
=== Decision d'Investissement ===
P(marche favorable) = 40%
Cout de l'etude = 20,000 EUR
Option 1 : Decision directe (sans etude)
E[Profit(investir)] = 80,000 EUR
E[Profit(pas investir)] = 0 EUR
=> Decision : INVESTIR
=> E[Profit] = 80,000 EUR
Option 2 : Avec etude de marche
P(etude positive) = 50.0%
Si E+ : P(favorable|E+) = 64.0% => INVESTIR
Si E- : P(favorable|E-) = 16.0% => NE PAS INVESTIR
=> E[Profit avec etude] = 104,000 EUR
=== Comparaison ===
Sans etude : 80,000 EUR
Avec etude : 104,000 EUR
=> Valeur de l'etude : 24,000 EUR
=> Decision : COMMANDER L'ETUDE
Exercice : Achat immobilier et expertise structurelle
Vous envisagez d’acheter une maison. Avant de signer, vous pouvez commander une expertise structurelle pour detecter d’eventuels problemes (fissures, fondations, humidite). L’expertise a un cout, mais peut vous eviter un achat desastreux.
Scénario : - Variable aleatoire : problemes_structurels (Oui/Non), prior p_problemes - Test : expertise structurelle avec sensibilite sens_expert et specificite spec_expert - Decision : acheter ou ne pas acheter - Utilites : gain/perte selon presence de problemes
Objectif : Calculer l’esperance d’utilite avec et sans expertise, en deduire la valeur de l’information (EVPI/EVSI).
Indices : - Étape 1 : Définir les probabilites et utilites du problème - Étape 2 : Calculer E[U] sans expertise (decision a priori) - Étape 3 : Calculer les posteriors P(problemes|expertise+) et P(problemes|expertise-) via Bayes - Étape 4 : Calculer E[U] avec expertise et la valeur nette de l’information
# Exercice : Achat immobilier et expertise structurelle# Etape 1 : Parametres du problemep_problemes =None# TODO etudiant : P(problemes structurels), essayez 0.15sens_expert =None# TODO etudiant : sensibilite de l'expertise, essayez 0.85spec_expert =None# TODO etudiant : specificite de l'expertise, essayez 0.90cout_expertise =None# TODO etudiant : cout de l'expertise en kEUR, essayez 3# Utilites (en kEUR)U_acheter_sain =None# TODO etudiant : gain si achat sans problemes, essayez 50U_acheter_problemes =None# TODO etudiant : perte si achat avec problemes, essayez -80U_pas_acheter =0# Ne pas acheter = pas de gain ni perte# Etape 2 : Decision sans expertise (a priori)# TODO etudiant : calculer E[U(acheter)] et E[U(pas acheter)] sans informationeu_acheter_sans =None# TODO etudianteu_pas_acheter_sans =None# TODO etudiant# Etape 3 : Posterior apres expertise (theoreme de Bayes)# TODO etudiant : calculer P(expertise+), P(problemes|expertise+), P(problemes|expertise-)p_expert_pos =None# TODO etudiantp_prob_epos =None# TODO etudiant : P(problemes|expertise+)p_prob_eneg =None# TODO etudiant : P(problemes|expertise-)# Etape 4 : Decision avec expertise et valeur de l'information# TODO etudiant : calculer E[U] optimal si expertise+, si expertise-# puis E[U] totale avec expertise (moins le cout)eu_avec_expertise =None# TODO etudiant# Resultat : valeur nette de l'informationresult =None# TODO etudiant : eu_avec_expertise - max(eu_acheter_sans, eu_pas_acheter_sans)print("Exercice a completer : valeur de l'information pour l'expertise structurelle")
Exercice a completer : valeur de l'information pour l'expertise structurelle
Analyse de la decision d’investissement
Stratégie
Decision
E[Profit]
Commentaire
Sans étude
Investir directement
80 000 EUR
Prior P(favorable)=40% suffit
Avec étude
Conditionnel au résultat
104 000 EUR
Evite les grosses pertes
La valeur de l’étude (24 000 EUR) se decompose ainsi :
Gain brut d’information : L’étude permet d’eviter d’investir quand le signal est negatif
Cout de l’étude : -20 000 EUR
Valeur nette : 24 000 EUR
Point cle : L’étude est rentable car elle permet de changer de decision dans certains cas. Si P(favorable) etait de 80%, on investirait dans tous les cas (E+ ou E-), et l’étude n’aurait aucune valeur decisionnelle.
6. Decisions Séquentielles
Quand il y a plusieurs noeuds de decision, leur ordre temporel doit etre specifie.
La maladie reste non observée avant les deux décisions : l’arc Maladie -> Utilite indique son effet sur le gain, pas une information disponible pour Traiter. Le résultat dépend de la maladie ; il n’est observé que si le test est fait. Le schéma omet cette dépendance probabiliste et l’arc de coût Faire Test? -> Utilite.
Deux decisions séquentielles : 1. D’abord : Faire le test ou non ? 2. Ensuite : Traiter ou non ? (en connaissant le résultat si test fait ; sinon, selon la probabilité a priori)
# Decision sequentielle : Faire test puis traiter# Parametresp_maladie_seq =0.2sensibilite_seq =0.90specificite_seq =0.85cout_test =50# Utilites (sans cout test)U_traiter_malade =100U_traiter_sain =60U_pas_traiter_malade =20U_pas_traiter_sain =100# Resolution par backward inductionprint("=== Decision Sequentielle : Test puis Traitement ===\n")# Politique pour D2 (traiter) sachant test faitp_tpos_seq = p_maladie_seq * sensibilite_seq + (1- p_maladie_seq) * (1- specificite_seq)p_m_tpos = (p_maladie_seq * sensibilite_seq) / p_tpos_seqeu_traiter_tp = p_m_tpos * U_traiter_malade + (1- p_m_tpos) * U_traiter_saineu_pas_traiter_tp = p_m_tpos * U_pas_traiter_malade + (1- p_m_tpos) * U_pas_traiter_sainbest_u_tp =max(eu_traiter_tp, eu_pas_traiter_tp) - cout_testp_m_tneg = (p_maladie_seq * (1- sensibilite_seq)) / (1- p_tpos_seq)eu_traiter_tn = p_m_tneg * U_traiter_malade + (1- p_m_tneg) * U_traiter_saineu_pas_traiter_tn = p_m_tneg * U_pas_traiter_malade + (1- p_m_tneg) * U_pas_traiter_sainbest_u_tn =max(eu_traiter_tn, eu_pas_traiter_tn) - cout_testeu_faire_test = p_tpos_seq * best_u_tp + (1- p_tpos_seq) * best_u_tn# Si pas de test : decision a priorieu_traiter_ap = p_maladie_seq * U_traiter_malade + (1- p_maladie_seq) * U_traiter_saineu_pas_traiter_ap = p_maladie_seq * U_pas_traiter_malade + (1- p_maladie_seq) * U_pas_traiter_saineu_pas_test =max(eu_traiter_ap, eu_pas_traiter_ap)print("Etape 2 : Politique de traitement si test fait")print(f" Si T+ : P(M|T+)={p_m_tpos:.1%} => {'Traiter'if eu_traiter_tp > eu_pas_traiter_tp else'Pas traiter'}")print(f" Si T- : P(M|T-)={p_m_tneg:.1%} => {'Traiter'if eu_traiter_tn > eu_pas_traiter_tn else'Pas traiter'}\n")print("Etape 1 : Decision de faire le test")print(f" E[U | faire test] = {eu_faire_test:.1f}")print(f" E[U | pas test] = {eu_pas_test:.1f} ({'traiter'if eu_traiter_ap > eu_pas_traiter_ap else'pas traiter'})\n")print("=== Politique Optimale Globale ===")print(f"D1 : {'FAIRE LE TEST'if eu_faire_test > eu_pas_test else'NE PAS FAIRE LE TEST'}")if eu_faire_test > eu_pas_test:print(f"D2 : Si T+ => {'TRAITER'if eu_traiter_tp > eu_pas_traiter_tp else'PAS TRAITER'}")print(f" Si T- => {'TRAITER'if eu_traiter_tn > eu_pas_traiter_tn else'PAS TRAITER'}")else:print(f"D2 : {'TRAITER'if eu_traiter_ap > eu_pas_traiter_ap else'PAS TRAITER'} (sans test)")
=== Decision Sequentielle : Test puis Traitement ===
Etape 2 : Politique de traitement si test fait
Si T+ : P(M|T+)=60.0% => Traiter
Si T- : P(M|T-)=2.9% => Pas traiter
Etape 1 : Decision de faire le test
E[U | faire test] = 43.6
E[U | pas test] = 84.0 (pas traiter)
=== Politique Optimale Globale ===
D1 : NE PAS FAIRE LE TEST
D2 : PAS TRAITER (sans test)
Analyse de la decision séquentielle
Ce résultat est contre-intuitif : malgre un test informatif (sensibilite 90%, specificite 85%), la politique optimale est de ne pas faire le test.
Le cout du test (50 points) est applique dans les deux branches (T+ et T-), ce qui reduit significativement l’utilite esperee.
Lecon importante : Dans les decisions séquentielles, le timing des couts est crucial. Un cout fixe en amont peut rendre toute la branche sous-optimale, même si l’information obtenue est de haute qualite.
7. Implementation avec PyMC
PyMC (Salvatier et al., 2016) n’a pas de support natif pour les noeuds de decision, mais nous pouvons :
Modeliser les variables de chance avec pm.Bernoulli
Enumerer les decisions manuellement
Calculer E[U] pour chaque configuration en utilisant l’inference bayesienne
L’avantage de PyMC est de pouvoir effectuer l’inference exacte du posterior P(maladie|test) et de propager l’incertitude de maniere rigoureuse.
# Reseau de decision avec PyMC# Probleme : Diagnostic et traitement# Parametresp_maladie_prior =0.15sensibilite =0.92# P(test+|malade)specificite_pymc =0.88# P(test-|sain)# Utilitesu_traiter_malade =85u_traiter_sain =65u_pas_traiter_malade =15u_pas_traiter_sain =100print("=== Reseau de Decision avec PyMC ===\n")for obs_test in [True, False]:with pm.Model() as diag_model:# Variable de chance : Maladie maladie = pm.Bernoulli("maladie", p=p_maladie_prior)# Variable de chance : Test | Maladie p_test = pm.math.switch( maladie, sensibilite, # P(test+|malade)1- specificite_pymc # P(test+|sain) ) test_positif = pm.Bernoulli("test_positif", p=p_test, observed=int(obs_test))# Inference trace = pm.sample(5000, return_inferencedata=True, progressbar=False, random_seed=42, chains=4)# Extraire P(maladie|test) p_m =float(trace.posterior["maladie"].mean())# Calculer E[U] pour chaque decision eu_traiter = p_m * u_traiter_malade + (1- p_m) * u_traiter_sain eu_pas_traiter = p_m * u_pas_traiter_malade + (1- p_m) * u_pas_traiter_sain decision ="TRAITER"if eu_traiter > eu_pas_traiter else"PAS TRAITER"print(f"Observation : test = {'POSITIF'if obs_test else'NEGATIF'}")print(f" P(maladie|test) = {p_m:.1%}")print(f" E[U(traiter)] = {eu_traiter:.1f}")print(f" E[U(pas traiter)] = {eu_pas_traiter:.1f}")print(f" => Decision optimale : {decision}\n")
Note : Pour ce problème simple (2 variables binaires), le calcul analytique de Bayes est exact et plus rapide. L’approche PyMC prend tout son sens pour des modèles plus complexes.
Au-dela du cas simple : prevalence hiérarchique avec test imparfait
La cellule précédente modelisait une seule variable binaire (maladie d’un patient) et laissait PyMC l’echantillonner par BinaryGibbsMetropolis – c’est-a-dire compter des 0 et des 1. Pour un problème a 2 variables binaires, le calcul analytique de Bayes donne la même reponse plus rapidement (cf. note ci-dessus) : le moteur MCMC n’etait pas reellement mis a contribution.
Pour faire valoir l’inference bayesienne par MCMC, considerons un problème non conjugue ou aucune forme close n’existe. Scénario : un reseau de cliniques applique le même test diagnostique (imparfait : sensibilite 0.92, specificite 0.88). On observe, par clinique, le nombre de patients testes \(n_i\) et de tests positifs \(k_i\) – mais jamais le vrai etat maladie (latent). Objectif : inferer la vraie prevalence de chaque clinique.
Piege du taux apparent : le taux de test positif naif \(k_i/n_i\)surestime la vraie prevalence, car il melange les vrais positifs (malades bien detectes) et les faux positifs (patients sains malgre tout testes positifs, \(1-\text{specificite}=0{,}12\)). Le taux apparent verifie \(p_{\text{app}} = p\cdot\text{se} + (1-p)\cdot(1-\text{sp})\).
Le prior logit-Normal hiérarchique + le lien non-lineaire entre vraie prevalence et taux apparent cassent toute conjugaison : MCMC est requis, et aucun calcul analytique par clinique (Beta-Binomial indépendant) ne peut produire cette correction du test imparfait ni le shrinkage entre cliniques.
# Prevalence hierarchique avec test imparfait (cas NON-CONJUGUE : MCMC requis)# K cliniques, vraie prevalence inconnue, test imparfait connu (se, sp).# On n'observe que les comptes de tests positifs k_i : l'etat maladie est LATENT.K =6n_patients = np.array([60, 90, 40, 200, 70, 130])prevalences_vraies = np.array([0.12, 0.25, 0.08, 0.40, 0.15, 0.50]) # inconnues de l'inferencese, sp =0.92, 0.88# sensibilite / specificite du test (specs constructeur, cf cellule precedente)# Taux apparent reel (simule) = vraie prevalence corrigee par le test imparfaitp_apparent_vrai = prevalences_vraies * se + (1- prevalences_vraies) * (1- sp)rng_obs = np.random.default_rng(7)k_positifs = rng_obs.binomial(n_patients, p_apparent_vrai)taux_apparent = k_positifs / n_patientsprint("Taux de test positif OBSERVES (apparent, naif k/n) par clinique :", taux_apparent.round(3))print("(ces taux apparents SURESTIMENT la vraie prevalence a cause des faux positifs)\n")with pm.Model() as model_prevalence:# Niveau population (parametrisation NON-CENTREE : evite le funnel, recette #3801) mu_pop = pm.Normal("mu_pop", mu=-1.5, sigma=1.5) # logit-prevalence moyenne tau = pm.HalfNormal("tau", sigma=1.0) # dispersion entre cliniques z = pm.Normal("z", 0.0, 1.0, shape=K) p_vraie = pm.Deterministic("p_vraie", pm.math.sigmoid(mu_pop + tau * z))# Lien NON-LINEAIRE vraie prevalence -> taux apparent (casse la conjugaison) p_apparent = pm.Deterministic("p_apparent", p_vraie * se + (1- p_vraie) * (1- sp) )# Vraisemblance binomiale sur les comptes de tests positifs observes k = pm.Binomial("k", n=n_patients, p=p_apparent, observed=k_positifs) trace_prev = pm.sample( draws=2000, tune=1500, chains=4, target_accept=0.99, random_seed=42, progressbar=False, return_inferencedata=True )# Diagnostics MCMC (honnetete des sorties)div =int(trace_prev.sample_stats["diverging"].sum())resume = az.summary(trace_prev, var_names=["mu_pop", "tau", "p_vraie"])print("Inference hierarchique de la vraie prevalence (MCMC, test imparfait non-conjugue)")print("="*64)print(f"Divergences NUTS : {div} (cible = 0)")print(f"R-hat max : {resume['r_hat'].max():.3f} (cible < 1.01)")print(f"ESS bulk min : {int(resume['ess_bulk'].min())}")print()print("Comparaison : taux apparent (naif k/n) vs vraie prevalence inferiee (corrigee)")print(f"{'clinique':>9}{'n':>5}{'vrai':>6}{'apparent':>9}{'vraie inferiee':>16} correction")print("-"*64)p_post = resume.loc[[f"p_vraie[{i}]"for i inrange(K)], "mean"].valuesfor i inrange(K): corr =abs(taux_apparent[i] - p_post[i]) marqueur =" <-- corrige (faux positifs retires)"if corr >0.02else""print(f"{i:>9}{n_patients[i]:>5}{prevalences_vraies[i]:>6.2f}{taux_apparent[i]:>9.3f}{p_post[i]:>16.3f}{marqueur}")print()print("Le moteur MCMC a inferer la VRAIE prevalence (etat maladie latent) en inversant")print("le test imparfait, avec shrinkage entre cliniques -- ce qu'aucun calcul analytique")print("(Beta-Binomial independant par clinique) ne peut produire : il donnerait seulement")print("la distribution du taux apparent, sans correction ni emprunt d'information.)")
Taux de test positif OBSERVES (apparent, naif k/n) par clinique : [0.233 0.378 0.225 0.45 0.3 0.592]
(ces taux apparents SURESTIMENT la vraie prevalence a cause des faux positifs)
Inference hierarchique de la vraie prevalence (MCMC, test imparfait non-conjugue)
================================================================
Divergences NUTS : 0 (cible = 0)
R-hat max : 1.000 (cible < 1.01)
ESS bulk min : 1791
Comparaison : taux apparent (naif k/n) vs vraie prevalence inferiee (corrigee)
clinique n vrai apparent vraie inferiee correction
----------------------------------------------------------------
0 60 0.12 0.233 0.169 <-- corrige (faux positifs retires)
1 90 0.25 0.378 0.317 <-- corrige (faux positifs retires)
2 40 0.08 0.225 0.174 <-- corrige (faux positifs retires)
3 200 0.40 0.450 0.405 <-- corrige (faux positifs retires)
4 70 0.15 0.300 0.230 <-- corrige (faux positifs retires)
5 130 0.50 0.592 0.568 <-- corrige (faux positifs retires)
Le moteur MCMC a inferer la VRAIE prevalence (etat maladie latent) en inversant
le test imparfait, avec shrinkage entre cliniques -- ce qu'aucun calcul analytique
(Beta-Binomial independant par clinique) ne peut produire : il donnerait seulement
la distribution du taux apparent, sans correction ni emprunt d'information.)
7.1 Visualisation du DAG
Contrairement a Infer.NET qui genere des factor graphs, PyMC permet de visualiser le DAG (Directed Acyclic Graph) du modèle. Cette representation montre les dependances entre variables.
Le diagramme ci-dessous represente le reseau de decision medical (exemple fil rouge) avec la notation standardisee de Howard & Matheson (1984) rappelee en section 2 : les cercles sont des noeuds de chance (variables aleatoires), le rectangle est le noeud de decision, et l’hexagone est le noeud d’utilite. L’arc en pointilles Test -> Traiter est l’arc informationnel : le medecin observe le résultat du test avant de choisir le traitement.
flowchart TD
Mal(("Maladie"))
Test(("Test"))
Treat["Traiter"]
Sante(("Sante"))
Util{{"Utilite"}}
Mal --> Test
Mal --> Sante
Test -.-> Treat
Treat --> Sante
Sante --> Util
Treat -.-> Util
classDef chance fill:#e8f0fe,stroke:#1a73e8,stroke-width:2px,color:#174ea6
classDef decision fill:#fef7e0,stroke:#f9ab00,stroke-width:2px,color:#b06000
classDef utility fill:#e6f4ea,stroke:#188038,stroke-width:2px,color:#0d652d
class Mal,Test,Sante chance
class Treat decision
class Util utility
Lecture.Maladie (incertaine) influence Test (observable) et Sante (future) ; Traiter est decide en connaissant Test (arc en pointilles) ; Utilite combine la Sante obtenue et le cout du Traiter. La cellule de code qui suit montre comment PyMC n’encode que la partie probabiliste (chance) de ce DAG – les decisions et l’utilite restent specifiees hors modèle, comme enumere dans la classe DecisionNode plus haut.
# Visualisation du DAG du modele de diagnosticimport matplotlib.pyplot as plt# Creer le modele pour la visualisationwith pm.Model() as diag_viz: maladie = pm.Bernoulli("maladie", p=0.15) p_test = pm.math.switch(maladie, 0.92, 0.12) test_positif = pm.Bernoulli("test_positif", p=p_test)# Afficher la structure du modeleprint("Structure du DAG du modele de diagnostic :")print(diag_viz)print()print("Variables :")for rv in diag_viz.free_RVs:print(f" {rv.name} ~ {rv.owner.op.name}")print()print("Structure du reseau de decision complet :")print(" [Maladie] -----> [TestResult]")print(" | |")# v vprint(" <Traiter> ------> ((Utilite))")print()print("Dans le DAG PyMC :")print(" - Noeuds = Variables aleatoires (maladie, test_positif)")print(" - Arcs = Dependances probabilistes (maladie -> test_positif)")print(" - Decisions = Enumerees manuellement hors du modele")
Structure du DAG du modele de diagnostic :
<pymc.model.core.Model object at 0x000002902AE5EA50>
Variables :
maladie ~ bernoulli
test_positif ~ bernoulli
Structure du reseau de decision complet :
[Maladie] -----> [TestResult]
| |
<Traiter> ------> ((Utilite))
Dans le DAG PyMC :
- Noeuds = Variables aleatoires (maladie, test_positif)
- Arcs = Dependances probabilistes (maladie -> test_positif)
- Decisions = Enumerees manuellement hors du modele
8. Exercice : Reseau de Decision Personnalise
Enonce
Modelisez le reseau de decision suivant :
Un etudiant doit decider s’il revise ou pas pour un examen. - Variable aleatoire : Difficulte de l’examen (Facile/Difficile) - Decision : Reviser (cout en temps) ou pas - Utilite : Note obtenue moins cout de revision
Indications
Rappel de la formule :
\[E[U(a)] = \sum_{s} P(s) \times U(a, s)\]
ou \(s \in \{\text{facile}, \text{difficile}\}\) et \(a \in \{\text{reviser}, \text{pas reviser}\}\).
# Exercice : Reseau de decision Etudiant# Parametresp_difficile =0.4# P(examen difficile)# Notes espereesnote_revise_facile =18note_revise_difficile =14note_pas_revise_facile =14note_pas_revise_difficile =8# Cout de revision (en points d'utilite)cout_revision =2# A completer 1 : Calculer l'utilite esperee de "reviser"# Formule : E[U(reviser)] = P(facile) * (note_revise_facile - coutRevision) + P(difficile) * (note_revise_difficile - coutRevision)# A completer 2 : Calculer l'utilite esperee de "ne pas reviser"# Formule : E[U(pas reviser)] = P(facile) * note_pas_revise_facile + P(difficile) * note_pas_revise_difficile# A completer 3 : Comparer et afficher la decision optimale# Indice : utilisez un if/else sur les deux utilites esperees# A completer 4 : Calculer le cout seuil (cout de revision au-dela duquel il ne faut plus reviser)# Indice : resolvez E[U(reviser)] = E[U(pas reviser)] pour coutRevisionprint("Exercice a completer")
Exercice a completer
Analyse de vos résultats
Après avoir implemente le code, verifiez :
Quelle action a la plus grande utilite esperee ?
Le cout seuil est-il coherent avec les paramètres du problème ?
Que se passe-t-il si P(difficile) augmente a 0.7 ?
8bis. Application MAUT : Choix de Site d’Aeroport
Contexte
Un comite doit choisir l’emplacement d’un nouvel aeroport parmi 3 sites candidats. Les critères d’evaluation sont incertains car bases sur des études preliminaires :
Critere
Description
Incertitude
Couts
Construction, infrastructure
Depend du terrain, geologie
Securite
Trafic aerien, conditions meteo
Études locales necessaires
Nuisances
Bruit pour riverains, pollution
Impact environnemental
Cet exemple combine Decision Networks et MAUT (Keeney & Raiffa, 1976) avec des attributs incertains.
# Reseau de Decision Multi-Attribut : Site d'Aeroport# 3 sites candidats, 3 attributs incertains modelises avec scipyn_sites =3sites = ["Site A (cotier)", "Site B (rural)", "Site C (periurbain)"]# Priors sur les attributs bases sur etudes preliminaires# Couts (millions EUR) - Gaussiancout_mean = [500, 300, 400]cout_std = [100, 71, 89] # sqrt(variance)# Securite (score 0-1) - Betasec_alpha = [8, 5, 6]sec_beta = [2, 5, 4]# Nuisances (score 1-10) - Gaussiannuis_mean = [3.0, 1.0, 5.0]nuis_std = [1.0, 0.7, 1.4]# Poids MAUTw_cout =0.35w_securite =0.40w_nuisances =0.25print("=== Decision Multi-Attribut : Site d'Aeroport ===")print(f"Poids : Cout={w_cout:.0%}, Securite={w_securite:.0%}, Nuisances={w_nuisances:.0%}\n")# Calculer les esperances des distributionsprint("Distributions des attributs par site :\n")print(f"{'Site':<22} | {'Cout (MEUR)':>14} | {'Securite':>10} | {'Nuisances':>10}")print("-"*70)eu = np.zeros(n_sites)np.random.seed(42)for s inrange(n_sites):# Esperances des distributions e_cout = cout_mean[s] e_sec = sec_alpha[s] / (sec_alpha[s] + sec_beta[s]) # Esperance Beta e_nuis = nuis_mean[s]print(f"{sites[s]:<22} | {e_cout:>7.0f} +/- {cout_std[s]:>4.0f} | {e_sec:>8.1%} | {e_nuis:>6.1f} +/- {nuis_std[s]:.1f}")# Normalisation (0-1) norm_cout =1- (e_cout -300) /200# 300-500 MEUR -> 1-0 norm_sec = e_sec # Deja 0-1 norm_nuis =1- (e_nuis -1) /9# 1-10 -> 1-0 eu[s] = w_cout * norm_cout + w_securite * norm_sec + w_nuisances * norm_nuisprint("\n=== Calcul de l'Utilite Esperee par Site ===\n")for s inrange(n_sites): norm_cout =1- (cout_mean[s] -300) /200 norm_sec = sec_alpha[s] / (sec_alpha[s] + sec_beta[s]) norm_nuis =1- (nuis_mean[s] -1) /9print(f"{sites[s]:<22} :")print(f" v_cout={norm_cout:.3f}, v_sec={norm_sec:.3f}, v_nuis={norm_nuis:.3f}")print(f" => EU = {eu[s]:.3f}")# Decision optimalebest_idx = np.argmax(eu)print(f"\n=== Decision Optimale : {sites[best_idx]} (EU = {eu[best_idx]:.3f}) ===")
=== Decision Multi-Attribut : Site d'Aeroport ===
Poids : Cout=35%, Securite=40%, Nuisances=25%
Distributions des attributs par site :
Site | Cout (MEUR) | Securite | Nuisances
----------------------------------------------------------------------
Site A (cotier) | 500 +/- 100 | 80.0% | 3.0 +/- 1.0
Site B (rural) | 300 +/- 71 | 50.0% | 1.0 +/- 0.7
Site C (periurbain) | 400 +/- 89 | 60.0% | 5.0 +/- 1.4
=== Calcul de l'Utilite Esperee par Site ===
Site A (cotier) :
v_cout=0.000, v_sec=0.800, v_nuis=0.778
=> EU = 0.514
Site B (rural) :
v_cout=1.000, v_sec=0.500, v_nuis=1.000
=> EU = 0.800
Site C (periurbain) :
v_cout=0.500, v_sec=0.600, v_nuis=0.556
=> EU = 0.554
=== Decision Optimale : Site B (rural) (EU = 0.800) ===
Simulation Monte Carlo pour quantifier l’incertitude
Plutot que d’utiliser uniquement les esperances, nous pouvons propager l’incertitude complete via Monte Carlo pour obtenir la distribution de l’utilite de chaque site.
# Simulation Monte Carlo pour propager l'incertituden_samples =10_000np.random.seed(42)print("=== Simulation Monte Carlo (10 000 echantillons) ===\n")eu_samples = np.zeros((n_sites, n_samples))for s inrange(n_sites):# Echantillonner depuis les distributions cout_samples = np.random.normal(cout_mean[s], cout_std[s], n_samples) sec_samples = np.random.beta(sec_alpha[s], sec_beta[s], n_samples) nuis_samples = np.random.normal(nuis_mean[s], nuis_std[s], n_samples)# Normaliser norm_cout = np.clip(1- (cout_samples -300) /200, 0, 1) norm_sec = np.clip(sec_samples, 0, 1) norm_nuis = np.clip(1- (nuis_samples -1) /9, 0, 1) eu_samples[s] = w_cout * norm_cout + w_securite * norm_sec + w_nuisances * norm_nuis# Probabilite que chaque site soit le meilleurbest_per_sample = np.argmax(eu_samples, axis=0)p_best = [np.mean(best_per_sample == s) for s inrange(n_sites)]print("Probabilite d'etre le meilleur site :")for s inrange(n_sites): mean_eu = eu_samples[s].mean() std_eu = eu_samples[s].std()print(f" {sites[s]:<22} : P(meilleur) = {p_best[s]:.1%}, EU = {mean_eu:.3f} +/- {std_eu:.3f}")print(f"\nSite dominant : {sites[np.argmax(p_best)]} (P = {max(p_best):.1%})")
=== Simulation Monte Carlo (10 000 echantillons) ===
Probabilite d'etre le meilleur site :
Site A (cotier) : P(meilleur) = 12.5%, EU = 0.584 +/- 0.112
Site B (rural) : P(meilleur) = 76.0%, EU = 0.742 +/- 0.095
Site C (periurbain) : P(meilleur) = 11.5%, EU = 0.554 +/- 0.138
Site dominant : Site B (rural) (P = 76.0%)
Interpretation de l’analyse multi-attribut
Site
Cout (normalise)
Securite
Nuisances
EU
A (cotier)
0.0 (500M = max)
0.80
0.78
~0.55
B (rural)
1.0 (300M = min)
0.50
1.00
~0.78
C (periurbain)
0.5
0.60
0.56
~0.55
La simulation Monte Carlo revelle que Site B a environ 76% de chances d’etre le meilleur choix, confirmant la robustesse de la decision face a l’incertitude des attributs.
Extensions possibles
Extension
Description
Analyse de sensibilite
Varier les poids pour voir si la decision change
Dominance stochastique
Comparer les distributions completes
Information supplementaire
Modeliser une étude de terrain comme source d’information
Recapitulatif des concepts utilises
Distributions utilisees
Distribution
Usage
Equivalent PyMC
Bernoulli
Variables binaires (maladie, test)
pm.Bernoulli
Gaussian
Attributs continus incertains (couts)
np.random.normal / pm.Normal
Beta
Scores bornes [0,1] (securite)
np.random.beta / pm.Beta
Patterns d’implementation
Pattern
Description
Section
Modèle conditionnel
pm.math.switch pour CPT
7
Observation
observed= pour conditionner
7
Enumeration
Boucle sur les observations possibles
7
Monte Carlo
Propagation d’incertitude
8bis
Concepts de decision illustres
Concept
Description
Section
Arcs informationnels
Ce que l’agent sait avant de decider
3
Backward induction
Resolution de la fin vers le debut
4
Valeur de l’information
Gain d’utilite grace a l’observation
5
Decisions séquentielles
Plusieurs decisions dans le temps
6
MAUT probabiliste
Multi-attributs avec incertitude
8bis
9. Resume
Concept
Description
Reseau de decision
Extension des reseaux bayesiens avec decisions et utilites
Noeuds de decision
Choix contrôles par l’agent
Noeuds d’utilite
Consequences des choix
Arcs informationnels
Ce que l’agent sait au moment de decider
Politique
Fonction observations -> decisions
Backward induction
Resolution du dernier au premier noeud de decision
Des applications : droits de forage, chasse au tresor
References
Bellman, R. (1957). Dynamic Programming. Princeton University Press.
Howard, R. A., & Matheson, J. E. (1984). Influence diagrams. In Readings on the Principles and Applications of Decision Analysis, Strategic Decisions Group.
Shachter, R. D. (1986). Evaluating influence diagrams. Opérations Research, 34(6), 871-882.
Keeney, R. L., & Raiffa, H. (1976). Decisions with Multiple Objectives: Préférences and Value Tradeoffs. Wiley.
Salvatier, J., Wiecki, T. V., & Fonnesbeck, C. (2016). Probabilistic programming in Python using PyMC3. PeerJ Computer Science, 2:e55.
Kumar, R., Carroll, C., Hartikainen, A., & Martin, O. (2019). ArviZ: a unified library for exploratory analysis of Bayesian models in Python. Journal of Open Source Software, 4(33), 1143.
Russell, S., & Norvig, P. Artificial Intelligence: A Modern Approach, Chapter 16.5.
Conclusion : Ce notebook a presente les reseaux de decision (diagrammes d’influence), les arcs informationnels, la resolution par backward induction, et les decisions séquentielles avec PyMC.