Infer-4-Bayesian-Networks : Reseaux Bayesiens Classiques

Serie : Programmation Probabiliste avec Infer.NET (4/19)
Duree estimee : 55 minutes
Prerequis : Infer-3-Factor-Graphs


Objectifs

  • Comprendre les reseaux bayesiens et leur structure
  • Implementer le reseau classique Wet Grass / Sprinkler / Rain
  • Maitriser les tables de probabilites conditionnelles (CPT)
  • Distinguer inference causale et observationnelle
  • Comprendre la D-separation et l’indépendance conditionnelle

1. Configuration

Cette section initialise l’environnement Infer.NET avec les packages necessaires pour construire et inferer des reseaux bayesiens. Les reseaux bayesiens sont des modèles graphiques diriges qui representent les relations de dépendance conditionnelle entre variables.

#r "nuget: Microsoft.ML.Probabilistic"
#r "nuget: Microsoft.ML.Probabilistic.Compiler"

using Microsoft.ML.Probabilistic;
using Microsoft.ML.Probabilistic.Distributions;
using Microsoft.ML.Probabilistic.Utilities;
using Microsoft.ML.Probabilistic.Math;
using Microsoft.ML.Probabilistic.Models;
using Microsoft.ML.Probabilistic.Algorithms;
using Microsoft.ML.Probabilistic.Compiler;

Console.WriteLine("Infer.NET pret !");
Installed Packages
  • Microsoft.ML.Probabilistic, 0.4.2504.701
  • Microsoft.ML.Probabilistic.Compiler, 0.4.2504.701
Infer.NET pret !

Chargement du helper de visualisation des graphes de facteurs.

// Chargement du helper pour visualiser les graphes de facteurs
#load "FactorGraphHelper.cs"

Console.WriteLine($"FactorGraphHelper charge. Graphviz disponible: {FactorGraphHelper.IsGraphvizAvailable()}");
FactorGraphHelper charge. Graphviz disponible: True

Environnement pret. Les packages Infer.NET charges incluent : - Microsoft.ML.Probabilistic : moteur d’inference - Microsoft.ML.Probabilistic.Compiler : compilation de modèles vers code C# - Microsoft.ML.Probabilistic.Models : API de construction de modèles

Infer.NET utilise l’algorithme Expectation Propagation (EP) par defaut, particulierement adapte aux reseaux bayesiens avec variables discretes et continues.

2. Introduction aux Reseaux Bayesiens

Definition

Un reseau bayesien est un graphe dirige acyclique (DAG) ou : - Les noeuds representent des variables aleatoires - Les arcs representent des dependances directes - Chaque noeud a une table de probabilite conditionnelle (CPT)

Proprietes

\[P(X_1, ..., X_n) = \prod_{i=1}^{n} P(X_i | \text{Parents}(X_i))\]

Avantages

Avantage Description
Compacite Representation factorisee de la jointe
Interpretabilite Structure causale explicite
Inference efficace Algorithmes de propagation
Apprentissage Structure et paramètres apprenables

3. Le Reseau Wet Grass / Sprinkler / Rain

Structure

         (Cloudy)
         /      \
        v        v
   (Sprinkler)  (Rain)
        \        /
         v      v
        (WetGrass)

sémantique

  • Cloudy : Temps nuageux (cause commune)
  • Sprinkler : Arroseur automatique (s’active moins si nuageux)
  • Rain : Pluie (plus probable si nuageux)
  • WetGrass : Herbe mouillee (effet commun)

Tables de Probabilites Conditionnelles

Variable CPT
Cloudy P(C=T) = 0.5
Sprinkler P(S=T|C=T) = 0.1, P(S=T|C=F) = 0.5
Rain P(R=T|C=T) = 0.8, P(R=T|C=F) = 0.2
WetGrass P(W=T|S,R) - voir table complete

Fidélité à la source : Le réseau Wet Grass / Sprinkler / Rain est l’exemple canonique de la littérature sur les réseaux bayésiens, popularisé par Russell & Norvig (Artificial Intelligence: A Modern Approach, chapitre 14 « Probabilistic Reasoning ») et repris comme running example par Koller & Friedman (Probabilistic Graphical Models). Il illustre de façon économique les trois structures fondamentales (chaîne, fourche, collideur), l’explaining-away (observation d’un effet commun qui rend ses causes dépendantes — démontré numériquement plus bas dans le notebook) et la D-séparation (lecture graphique de l’indépendance conditionnelle, validée empiriquement contre l’inférence Infer.NET plus bas). Le fil narratif Murder Mystery de Mr Black utilisé par le Model-Based Machine Learning (Winn & Bishop, MBML Ch.1/3) vit dans Infer-3-Factor-Graphs ; Infer-4 choisit délibérément le réseau Wet Grass — un exemple également canonique, mieux adapté pour démontrer D-séparation et explaining-away en Infer.NET. Enfin, là où MBML procède par dialogue de dérivation (l’étudiant construit le modèle pas à pas), ce notebook présente le modèle fini puis l’exécute — un choix pédagogique légitime pour un notebook technique orienté implémentation.

// Implementation du reseau Wet Grass

// Variable racine : Cloudy
Variable<bool> cloudy = Variable.Bernoulli(0.5).Named("cloudy");

// Sprinkler conditionne par Cloudy
Variable<bool> sprinkler = Variable.New<bool>().Named("sprinkler");
using (Variable.If(cloudy))
{
    sprinkler.SetTo(Variable.Bernoulli(0.1));  // Peu probable si nuageux
}
using (Variable.IfNot(cloudy))
{
    sprinkler.SetTo(Variable.Bernoulli(0.5));  // Plus probable si ensoleille
}

// Rain conditionne par Cloudy
Variable<bool> rain = Variable.New<bool>().Named("rain");
using (Variable.If(cloudy))
{
    rain.SetTo(Variable.Bernoulli(0.8));  // Tres probable si nuageux
}
using (Variable.IfNot(cloudy))
{
    rain.SetTo(Variable.Bernoulli(0.2));  // Peu probable si ensoleille
}

// WetGrass conditionne par Sprinkler ET Rain
Variable<bool> wetGrass = Variable.New<bool>().Named("wetGrass");

// CPT pour WetGrass
// S=F, R=F -> P(W=T) = 0.0
// S=F, R=T -> P(W=T) = 0.9
// S=T, R=F -> P(W=T) = 0.9
// S=T, R=T -> P(W=T) = 0.99

using (Variable.If(sprinkler))
{
    using (Variable.If(rain))
    {
        wetGrass.SetTo(Variable.Bernoulli(0.99));  // S=T, R=T
    }
    using (Variable.IfNot(rain))
    {
        wetGrass.SetTo(Variable.Bernoulli(0.9));   // S=T, R=F
    }
}
using (Variable.IfNot(sprinkler))
{
    using (Variable.If(rain))
    {
        wetGrass.SetTo(Variable.Bernoulli(0.9));   // S=F, R=T
    }
    using (Variable.IfNot(rain))
    {
        wetGrass.SetTo(Variable.Bernoulli(0.0));   // S=F, R=F
    }
}

Console.WriteLine("Reseau Wet Grass defini.");
Reseau Wet Grass defini.

Analyse de la structure du modèle

Le modèle Wet Grass est maintenant défini. Observons les éléments cles de l’implementation :

élément Infer.NET Utilisation Signification
Variable.Bernoulli(p) Prior marginal Variable racine avec probabilite p
Variable.New<bool>() Variable conditionnelle Declaration sans prior (défini ensuite)
Variable.If(x) / Variable.IfNot(x) Branchement conditionnel Encode les CPT via des blocs imbriques
SetTo() Affectation conditionnelle définit la distribution dans chaque branche

Structure du graphe de facteurs : Infer.NET compile ce code en un graphe de facteurs equivalent : - 4 variables : Cloudy, Sprinkler, Rain, WetGrass - 4 facteurs : 1 prior (Cloudy) + 3 CPT conditionnelles

Point technique : Les blocs Variable.If/Variable.IfNot imbriques implementent naturellement les tables de probabilites conditionnelles. Chaque combinaison de conditions définit une entree de la CPT.


Inference des probabilites marginales

Nous allons maintenant utiliser le moteur d’inference pour calculer les probabilites marginales de chaque variable, c’est-a-dire P(X) sans aucune observation.

L’algorithme Expectation Propagation (EP) propage des messages dans le graphe de facteurs pour calculer ces marginales efficacement, sans enumerer toutes les combinaisons possibles.

// Inference sans observations
InferenceEngine moteur = new InferenceEngine();
moteur.Compiler.CompilerChoice = CompilerChoice.Roslyn;
moteur.ShowFactorGraph = true;  // Genere le graphe de facteurs

Console.WriteLine("=== Probabilites marginales (sans observation) ===");
Console.WriteLine($"P(Cloudy) = {moteur.Infer<Bernoulli>(cloudy).GetProbTrue():F3}");
Console.WriteLine($"P(Sprinkler) = {moteur.Infer<Bernoulli>(sprinkler).GetProbTrue():F3}");
Console.WriteLine($"P(Rain) = {moteur.Infer<Bernoulli>(rain).GetProbTrue():F3}");
Console.WriteLine($"P(WetGrass) = {moteur.Infer<Bernoulli>(wetGrass).GetProbTrue():F3}");
=== Probabilites marginales (sans observation) ===
Compiling model...done.
P(Cloudy) = 0,500
P(Sprinkler) = 0,300
P(Rain) = 0,500
P(WetGrass) = 0,598

Visualisation du graphe de facteurs du reseau Wet Grass.

// Visualisation du graphe de facteurs du reseau Wet Grass
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_02_22_36_21.svg
Model node0 Bernoulli(0,5) node1 Random node0->node1 dist node2 cloudy node1->node2 node5 sprinkler node2->node5 condition node10 rain node2->node10 condition node3 Bernoulli(0,1) node4 Random node3->node4 dist node4->node5 node15 wetGrass node5->node15 condition node6 Bernoulli(0,5) node7 Random node6->node7 dist node7->node5 node8 Bernoulli(0,8) node9 Random node8->node9 dist node9->node10 node10->node15 condition node11 Bernoulli(0,2) node12 Random node11->node12 dist node12->node10 node13 Bernoulli(0,99) node14 Random node13->node14 dist node14->node15 node16 Bernoulli(0,9) node17 Random node16->node17 dist node17->node15 node18 Bernoulli(0,9) node19 Random node18->node19 dist node19->node15 node20 Bernoulli(0) node21 Random node20->node21 dist node21->node15

Lecture du graphe de facteurs

Le graphe ci-dessus represente la structure compilee par Infer.NET :

  • Cercles/Ovales : Variables aleatoires (Cloudy, Sprinkler, Rain, WetGrass)
  • Carres/Rectangles : Facteurs (distributions conditionnelles, CPT)
  • Aretes : Connexions variable-facteur

La structure reflète le DAG bayésien original avec ses 4 noeuds, mais Infer.NET le compile en graphe de facteurs où les CPT deviennent des facteurs explicites. Notez comment les blocs Variable.If/IfNot imbriqués sont représentés.

Analyse des probabilites marginales

Résultats : - P(Cloudy) = 0.500 — le prior direct - P(Sprinkler) = 0.300 — moyenne pondérée : 0.5×0.1 + 0.5×0.5 = 0.30 ✓ - P(Rain) = 0.500 — moyenne pondérée : 0.5×0.8 + 0.5×0.2 = 0.50 ✓ - P(WetGrass) = 0.598 — approximation EP d’Infer.NET (la valeur exacte est 0.647, voir ci-dessous)

Calcul exact de P(WetGrass) par énumération de la jointe

Sprinkler et Rain ne sont pas indépendantes : toutes deux conditionnées par la cause commune Cloudy, leur marginale jointe vérifie \(P(s,r) \neq P(s)\,P(r)\). Le calcul exact somme donc sur la jointe, pas sur le produit des marginales :

\[P(W) = \sum_{s,r} P(W|s,r) \cdot P(s,r), \qquad P(s,r) = \sum_{c} P(c)\,P(s|c)\,P(r|c)\]

S, R P(s,r) P(W|s,r) contribution
T, T 0.090 0.99 0.089
T, F 0.210 0.90 0.189
F, T 0.410 0.90 0.369
F, F 0.290 0.00 0.000

\[P(W{=}T) = 0.089 + 0.189 + 0.369 + 0.000 = \mathbf{0.647}\]

Pourquoi Infer.NET affiche-t-il 0.598 ? Le moteur utilise l’Expectation Propagation (EP), dont le passage de messages factorise ici S et R lors de la marginalisation de W — ce qui revient au calcul indépendant \(\sum_{s,r} P(W|s,r)\,P(s)\,P(r) = 0.598\). L’écart (~8 %) est l’erreur d’approximation d’EP : sur ce réseau à boucle (cause commune \(C{\to}S, C{\to}R\) + collider \(S{\to}W{\leftarrow}R\)), EP perd la corrélation S-R induite par Cloudy. Les cellules 30-36 diagnostiquent la convergence d’EP en détail. Leçon : sur un réseau à boucles, la marginale d’Infer.NET est une approximation — la vérifier par énumération quand la taille le permet (4 nœuds ici).


Vers l’inference conditionnelle

Les marginales nous donnent les probabilites “a priori” (sans observation). Le veritable pouvoir des reseaux bayesiens reside dans leur capacite a mettre a jour ces croyances lorsqu’on observe des evidences.

Que se passe-t-il quand on observe que l’herbe est mouillee ? Les probabilites des causes potentielles (pluie, arroseur) doivent etre revisees selon le theoreme de Bayes.

4. Inference avec Observations

// Nouveau modele pour inference avec observation
Variable<bool> cloudy2 = Variable.Bernoulli(0.5).Named("cloudy2");
Variable<bool> sprinkler2 = Variable.New<bool>().Named("sprinkler2");
Variable<bool> rain2 = Variable.New<bool>().Named("rain2");
Variable<bool> wetGrass2 = Variable.New<bool>().Named("wetGrass2");

// Meme structure que precedemment
using (Variable.If(cloudy2))
{
    sprinkler2.SetTo(Variable.Bernoulli(0.1));
    rain2.SetTo(Variable.Bernoulli(0.8));
}
using (Variable.IfNot(cloudy2))
{
    sprinkler2.SetTo(Variable.Bernoulli(0.5));
    rain2.SetTo(Variable.Bernoulli(0.2));
}

using (Variable.If(sprinkler2))
{
    using (Variable.If(rain2))
        wetGrass2.SetTo(Variable.Bernoulli(0.99));
    using (Variable.IfNot(rain2))
        wetGrass2.SetTo(Variable.Bernoulli(0.9));
}
using (Variable.IfNot(sprinkler2))
{
    using (Variable.If(rain2))
        wetGrass2.SetTo(Variable.Bernoulli(0.9));
    using (Variable.IfNot(rain2))
        wetGrass2.SetTo(Variable.Bernoulli(0.0));
}

// OBSERVATION : L'herbe est mouillee
wetGrass2.ObservedValue = true;

InferenceEngine moteur2 = new InferenceEngine();
moteur2.Compiler.CompilerChoice = CompilerChoice.Roslyn;
moteur2.ShowFactorGraph = true;  // Genere le graphe avec observation

Console.WriteLine("=== Inference : P(X | WetGrass=True) ===");
Console.WriteLine($"P(Rain | WetGrass) = {moteur2.Infer<Bernoulli>(rain2).GetProbTrue():F3}");
Console.WriteLine($"P(Sprinkler | WetGrass) = {moteur2.Infer<Bernoulli>(sprinkler2).GetProbTrue():F3}");
Console.WriteLine($"P(Cloudy | WetGrass) = {moteur2.Infer<Bernoulli>(cloudy2).GetProbTrue():F3}");
=== Inference : P(X | WetGrass=True) ===
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
Iterating: 
.........|.........|.........|.........|.........| 50
P(Rain | WetGrass) = 0,784
P(Sprinkler | WetGrass) = 0,404
P(Cloudy | WetGrass) = 0,604

Visualisation du graphe avec l’observation WetGrass=True.

// Visualisation du graphe avec observation WetGrass=True
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_02_22_39_09.svg
Model node0 Bernoulli(0,8) node1 Random node0->node1 dist node2 rain2 node1->node2 node8 True node2->node8 condition node3 cloudy2 node3->node2 condition node9 sprinkler2 node3->node9 condition node4 Bernoulli(0,2) node5 Random node4->node5 dist node5->node2 node6 Bernoulli(0,99) node7 Random node6->node7 dist node7->node8 node9->node8 condition node10 Bernoulli(0,9) node11 Random node10->node11 dist node11->node8 node12 Bernoulli(0,9) node13 Random node12->node13 dist node13->node8 node14 Bernoulli(0) node15 Random node14->node15 dist node15->node8 node16 Bernoulli(0,1) node17 Random node16->node17 dist node17->node9 node18 Bernoulli(0,5) node19 Random node18->node19 dist node19->node9 node20 Bernoulli(0,5) node21 Random node20->node21 dist node21->node3

Impact de l’observation sur le graphe

Comparez ce graphe avec le précédent. L’observation WetGrass=True est integree directement dans la structure du modèle compile. Dans Infer.NET, une variable observee n’est plus une variable aleatoire mais une constante, ce qui simplifie certains facteurs.

L’inference remonte maintenant des effets vers les causes (abduction), mettant a jour les probabilites de Rain, Sprinkler et Cloudy.

Analyse de l’inférence conditionnelle

Résultats : P(X | WetGrass=True)

Variable Prior Posterior Changement
Rain 0.500 0.784 +57%
Sprinkler 0.300 0.404 +35%
Cloudy 0.500 0.604 +21%

Interprétation : - L’herbe mouillée est une forte évidence pour la pluie (car P(W|R,¬S)=0.9) - L’arroseur est aussi plus probable, mais moins fortement - Le temps nuageux augmente car il favorise la pluie, qui explique bien l’herbe mouillée

Raisonnement abductif : L’inférence “remonte” des effets vers les causes. C’est le cœur du diagnostic bayésien.

Phenomene de l’Explaining Away

Le phenomene d’explaining away est central en raisonnement bayesien. Il se produit lorsque deux causes alternatives d’un même effet “competent” pour l’expliquer.

scénario : L’herbe est mouillee. Deux causes possibles : pluie ou arroseur. - Si on observe seulement l’herbe mouillee, les deux causes deviennent plus probables - Mais si on apprend EN PLUS qu’il a plu, l’arroseur devient MOINS necessaire comme explication

Verifions ce phenomene en ajoutant l’observation Rain=True au modèle précédent.

// Demonstation de l'Explaining Away
Variable<bool> cloudy3 = Variable.Bernoulli(0.5);
Variable<bool> sprinkler3 = Variable.New<bool>();
Variable<bool> rain3 = Variable.New<bool>();
Variable<bool> wetGrass3 = Variable.New<bool>();

using (Variable.If(cloudy3))
{
    sprinkler3.SetTo(Variable.Bernoulli(0.1));
    rain3.SetTo(Variable.Bernoulli(0.8));
}
using (Variable.IfNot(cloudy3))
{
    sprinkler3.SetTo(Variable.Bernoulli(0.5));
    rain3.SetTo(Variable.Bernoulli(0.2));
}

using (Variable.If(sprinkler3))
{
    using (Variable.If(rain3))
        wetGrass3.SetTo(Variable.Bernoulli(0.99));
    using (Variable.IfNot(rain3))
        wetGrass3.SetTo(Variable.Bernoulli(0.9));
}
using (Variable.IfNot(sprinkler3))
{
    using (Variable.If(rain3))
        wetGrass3.SetTo(Variable.Bernoulli(0.9));
    using (Variable.IfNot(rain3))
        wetGrass3.SetTo(Variable.Bernoulli(0.0));
}

// Observations : WetGrass=True ET Rain=True
wetGrass3.ObservedValue = true;
rain3.ObservedValue = true;

InferenceEngine moteur3 = new InferenceEngine();
moteur3.Compiler.CompilerChoice = CompilerChoice.Roslyn;
moteur3.ShowFactorGraph = true;  // Graphe avec deux observations

Console.WriteLine("=== Explaining Away ===");
Console.WriteLine($"P(Sprinkler | WetGrass, Rain) = {moteur3.Infer<Bernoulli>(sprinkler3).GetProbTrue():F3}");
Console.WriteLine("\nComparaison :");
Console.WriteLine("  P(Sprinkler | WetGrass) ~ 0.40");  // coherent avec P=0,404 (cellule precedente)
Console.WriteLine("  P(Sprinkler | WetGrass, Rain) ~ 0.19");
Console.WriteLine("\n=> La pluie 'explique' l'herbe mouillee, reduisant P(Sprinkler)");
=== Explaining Away ===
Compiling model...done.
P(Sprinkler | WetGrass, Rain) = 0,194

Comparaison :
  P(Sprinkler | WetGrass) ~ 0.40
  P(Sprinkler | WetGrass, Rain) ~ 0.19

=> La pluie 'explique' l'herbe mouillee, reduisant P(Sprinkler)

Visualisation du graphe illustrant le phenomene d’Explaining Away.

// Visualisation du graphe avec Explaining Away (Rain et WetGrass observes)
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_02_22_40_58.svg
Model node0 Bernoulli(0,1) node1 Random node0->node1 dist node2 vbool25 node1->node2 node8 True node2->node8 condition node3 vbool24 node3->node2 condition node9 True node3->node9 condition node4 Bernoulli(0,5) node5 Random node4->node5 dist node5->node2 node6 Bernoulli(0,99) node7 Random node6->node7 dist node7->node8 node9->node8 condition node10 Bernoulli(0,9) node11 Random node10->node11 dist node11->node8 node12 Bernoulli(0,9) node13 Random node12->node13 dist node13->node8 node14 Bernoulli(0) node15 Random node14->node15 dist node15->node8 node16 Bernoulli(0,8) node17 Random node16->node17 dist node17->node9 node18 Bernoulli(0,2) node19 Random node18->node19 dist node19->node9 node20 Bernoulli(0,5) node21 Random node20->node21 dist node21->node3

Structure de l’Explaining Away dans le graphe

Dans ce graphe, deux variables sont observees (Rain=True, WetGrass=True). Observez comment le graphe se simplifie :

  • Rain n’est plus une variable aleatoire mais une constante
  • WetGrass également, ce qui “coupe” certains chemins d’inference

Le phenomene d’explaining away emerge de la structure de collider au niveau de WetGrass. Les deux causes (Sprinkler et Rain) etaient independantes marginalement, mais deviennent dependantes une fois l’effet (WetGrass) observe.

Explication du phénomène “Explaining Away”

Résultat clé : P(Sprinkler | WetGrass, Rain) = 0.194 vs P(Sprinkler | WetGrass) ≈ 0.40

Quand on observe que l’herbe est mouillée ET qu’il a plu, la pluie “explique” suffisamment l’observation. L’arroseur devient moins nécessaire comme explication.

Analogie intuitive : Si vous arrivez au bureau trempé et que je sais qu’il pleut dehors, je ne vais pas penser que vous êtes aussi passé sous un arroseur. Une explication suffit.

Structure graphique :

(Sprinkler) → (WetGrass) ← (Rain)
            [collider]

Au collider, les deux causes sont marginalement indépendantes mais deviennent conditionnellement dépendantes quand l’effet est observé. C’est contre-intuitif mais fondamental en raisonnement bayésien.

Diagnostiquer la convergence : EP a-t-elle vraiment converge ?

Toutes les inferences ci-dessus affichent une marginale sans jamais nous dire si le moteur a reellement atteint la solution. C’est un reflexe essential : un algorithme d’inference approximatif (comme Expectation Propagation, EP) peut converger lentement, osciller, ou s’arreter avant la solution sur un graphe mal conditionne. Infer.NET expose le nombre d’iterations (NumberOfIterations) ; en faisant varier ce budget, on verifie la convergence : si la marginale ne bouge plus entre N et 2N iterations, EP a converge ; sinon, il faut plus d’iterations ou un autre algorithme.

Appliquons ce diagnostic au collider Wet Grass en regime d’explaining-away (WetGrass=T, Sprinkler=T), en balayant le budget d’iterations et en comparant a la solution exacte (calculee par enumeration brute : un Bayes net discret est exactement resoluble).

// Diagnostic de convergence EP : balayer NumberOfIterations + comparaison a la solution exacte
Variable<bool> cloudyD = Variable.Bernoulli(0.5).Named("cloudyD");
Variable<bool> sprinklerD = Variable.New<bool>().Named("sprinklerD");
Variable<bool> rainD = Variable.New<bool>().Named("rainD");
Variable<bool> wetGrassD = Variable.New<bool>().Named("wetGrassD");
using (Variable.If(cloudyD)) { sprinklerD.SetTo(Variable.Bernoulli(0.1)); rainD.SetTo(Variable.Bernoulli(0.8)); }
using (Variable.IfNot(cloudyD)) { sprinklerD.SetTo(Variable.Bernoulli(0.5)); rainD.SetTo(Variable.Bernoulli(0.2)); }
using (Variable.If(sprinklerD)) {
    using (Variable.If(rainD)) wetGrassD.SetTo(Variable.Bernoulli(0.99));
    using (Variable.IfNot(rainD)) wetGrassD.SetTo(Variable.Bernoulli(0.9));
}
using (Variable.IfNot(sprinklerD)) {
    using (Variable.If(rainD)) wetGrassD.SetTo(Variable.Bernoulli(0.9));
    using (Variable.IfNot(rainD)) wetGrassD.SetTo(Variable.Bernoulli(0.0));
}
wetGrassD.ObservedValue = true;
sprinklerD.ObservedValue = true;

Console.WriteLine("=== Diagnostic de convergence : EP sur le collider (WG=T, Spr=T) ===");
foreach (int nIter in new[] {1, 2, 5, 10}) {
    var engD = new InferenceEngine(new ExpectationPropagation());
    engD.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    engD.NumberOfIterations = nIter;
    Console.WriteLine($"  nIter={nIter,3}: P(Rain|...)={engD.Infer<Bernoulli>(rainD).GetProbTrue():F5}  P(Cloudy|...)={engD.Infer<Bernoulli>(cloudyD).GetProbTrue():F5}");
}

// Solution exacte par enumeration brute (C,R balayes, S=1 W=1 fixes)
double Z = 0, numR = 0, numC = 0;
for (int C = 0; C <= 1; C++) {
    double pc = 0.5;
    double ps1 = (C == 1) ? 0.1 : 0.5;
    for (int R = 0; R <= 1; R++) {
        double pr = (C == 1) ? ((R == 1) ? 0.8 : 0.2) : ((R == 1) ? 0.2 : 0.8);
        double pw = (R == 1) ? 0.99 : 0.9;
        double w = pc * ps1 * pr * pw;
        Z += w;
        if (R == 1) numR += w;
        if (C == 1) numC += w;
    }
}
Console.WriteLine();
Console.WriteLine("=== Solution exacte (enumeration brute) ===");
Console.WriteLine($"  P(Rain=T | S=1,W=1)   = {numR/Z:F5}");
Console.WriteLine($"  P(Cloudy=T | S=1,W=1) = {numC/Z:F5}");
=== Diagnostic de convergence : EP sur le collider (WG=T, Spr=T) ===
Compiling model...done.
  nIter=  1: P(Rain|...)=0,32039  P(Cloudy|...)=0,17476
Compiling model...done.
  nIter=  2: P(Rain|...)=0,32039  P(Cloudy|...)=0,17476
Compiling model...done.
  nIter=  5: P(Rain|...)=0,32039  P(Cloudy|...)=0,17476
Compiling model...done.
  nIter= 10: P(Rain|...)=0,32039  P(Cloudy|...)=0,17476

=== Solution exacte (enumeration brute) ===
  P(Rain=T | S=1,W=1)   = 0,32039
  P(Cloudy=T | S=1,W=1) = 0,17476

Lecture du diagnostic

Verdict empirique : sur ce collider, EP converge des la premiere iteration et sa marginale (P(Rain)=0,32039) coincide au 5e decimale avec la solution exacte par enumeration. La verification par sweep d’iterations est donc concluante : la stabilite parfaite entre nIter=1 et nIter=10 garantit qu’EP n’a pas ete arrete prematurement.

Pourquoi une convergence si rapide ? Le collider Cloudy -> {Sprinkler, Rain} -> WetGrass contient bien un cycle non dirige, mais conditionner sur Sprinkler=T supprime l’arc entrant : le graphe resident devient un arbre, sur lequel EP (equivalente a la belief propagation exacte sur les arbres) resout la solution en une seule passe de messages.

Quand EP est-elle vraiment iterative (et approximative) ? Le cas ou EP ne peut pas etre exact est celui des modeles continus a likelihood non conjuguee – typiquement la regression logistique bayesienne (prior gaussien, likelihood logistique) ou les variables tronquees / a queues lourdes. La, EP doit approcher chaque message par une Gaussienne, itererer, et le resultat depend du budget d’iterations : c’est l’usage authentique du diagnostic que nous venons d’appliquer. (Ref. : Winn & Bishop, Model-Based Machine Learning §3.4, sur la convergence d’EP et le choix du nombre d’iterations.)

Lecon methodologique : toujours verifier la convergence d’un algorithme d’inference approximatif (sweep d’iterations, comparaison a un solveur exact quand le modele est assez petit, ou croiser plusieurs algorithmes EP / VMP / Gibbs). Une marginale unique sans diagnostic de convergence n’est pas une preuve – c’est un aveuglement volontaire sur la sante du moteur.

4.bis – Quand EP est-elle vraiment iterative ? Un exemple en modele continu non conjugue

Le diagnostic de convergence applique au collider Wet Grass (cellules 30-32) etait decevant parce que trivial : sur un modele discret, EP se ramene souvent a la belief propagation exacte sur le graphe resident (un arbre conditionne sur Sprinkler=T), et la marginale est exacte des la premiere iteration.

Pour voir EP vraiment iterative (et vraiment approximative), il faut quitter le monde discret et aller vers un modele continu a likelihood non conjuguee. Le cas pedagogique canonique est la regression logistique bayesienne :

w ~ Gaussienne(0, priorVar)   // prior
b ~ Gaussienne(0, priorVar)   // prior
y_n ~ Bernoulli(sigmoid(w * x_n + b))   // likelihood logistique, non conjuguee

Le sigmoid casse la conjuguaison Gaussienne-Bernoulli : la vraisemblance produit un message qui n’est pas gaussien, et EP doit l’approcher par une gaussienne au sens d’un match de moments (minimisation du KL(ber || gauss) <-> projection sur la famille exponentielle). Cette approximation n’est pas exacte : a chaque iteration, on propage une approximation d’une approximation, et le resultat depend du budget d’iterations NumberOfIterations.

Ci-dessous, on balaye 3 forces du prior (faible / moyen / tres fort) et 6 budgets d’iteration (1, 2, 5, 10, 20, 50) sur 3 seeds (1, 2, 3) en suivant les recommandations du diagnotic precedent : NumberOfIterations est une variable de notre diagnostic, et on cherche la stabilite de la marginale posterieure de w entre budgets successifs.

Pourquoi la variance du prior (priorVar) commande-t-elle tout ?

Le diagnostic que nous allons executer est une grille : priorVar in {1, 1000, 0.01} x NumberOfIterations in {1, 2, 5, 10, 20, 50} x seed in {1, 2, 3} (54 cellules). Le but : voir quelles regions sont stable et quelles regions donnent des posterior tres differents selon le budget d’iteration et le seed.

L’intuition pedagogique :

  • priorVar tres petit (0.01) : le prior domine la likelihood, le posterior est presque le prior, EP converge en 1-2 iterations.
  • priorVar intermediaire (1.0) : prior et likelihood jouent a peu pres a forces egales, l’EP est bien conditionnee et converge en ~10 iterations.
  • priorVar tres grand (1000), le ‘vague prior’ classique : le prior est presque inconditionnel, la likelihood prend tous les degres de liberte, et la posterior est tres non-lineairement deformee. EP entre dans un regime ou la projection Gaussienne est tres mauvaise approximation, et converge vers des points fixes differents selon le seed – c’est la divergence authentique d’EP, le probleme que ce notebook veut faire toucher du doigt.

Reference : Winn & Bishop, Model-Based Machine Learning, section 3.4 sur la convergence et l’instabilite d’EP. La documentation officielle de Infer.NET mentionne explicitement que sur les modeles non-conjugues, le budget d’iterations est un hyper-parametre a valider empiriquement, pas un choix defaut.

// Diagnostic EP sur un modele continu non conjugue : regression logistique bayesienne.
//
// Pourquoi cette cellule : la cellule precedente ("Lecture du diagnostic") annoncait
// "le cas ou EP ne peut pas etre exact est celui des modeles continus a likelihood
// non conjuguee -- typiquement la regression logistique bayesienne". Ce diagnostic
// concrete la promesse : on balaye NumberOfIterations, on multiplie les seeds, et on
// observe quand EP converge, quand elle oscille, et quand elle diverge.
//
// Modele : w ~ Gaussienne(0, priorVar), b ~ Gaussienne(0, priorVar),
//          y_n ~ Bernoulli(sigmoid(w * x_n + b)).
// Donnees : 10 obs, w_vrai = 2.0, b_vrai = -1.0, x uniforme dans [-2, 2].
//
// Reproductibilite : la grille est SELF-CONTAINED (boucle priorVar x nIter x seed),
// donc un "Run All" a froid reproduit l'integralite des 54 inferences ci-dessous
// (3 sets x 6 nIter x 3 seeds) -- le diagnostic n'est plus transporte depuis une
// console app externe (regle F : re-exec via le kernel local, pas de contournement).
// Les cas ou EP diverge (priorVar=1000) sont captures par try/catch et affiches
// comme "EXC: ..." -- c'est le CONTENU du diagnostic, pas un crash.

int nObs = 10;
double[] priorVars = { 1.0, 1000.0, 0.01 };
string[] setNames = {
    "Set 1 (priorVar=1, informative prior)",
    "Set 2 (priorVar=1000, vague prior -- EP mal conditionnee)",
    "Set 3 (priorVar=0.01, tres informatif -- prior domine)"
};
int[] nIters = { 1, 2, 5, 10, 20, 50 };
int[] seeds = { 1, 2, 3 };
double wTrue = 2.0, bTrue = -1.0;

for (int s = 0; s < priorVars.Length; s++)
{
    double priorVar = priorVars[s];
    Console.WriteLine($"=== {setNames[s]} ===");
    foreach (int seed in seeds)
    {
        // Donnees synthetiques deterministes par seed (meme seed => meme mini-batch).
        var rng = new System.Random(seed);
        var xData = new double[nObs];
        var yData = new bool[nObs];
        for (int i = 0; i < nObs; i++)
        {
            xData[i] = rng.NextDouble() * 4 - 2;
            double z = wTrue * xData[i] + bTrue;
            double p = 1.0 / (1.0 + System.Math.Exp(-z));
            yData[i] = rng.NextDouble() < p;
        }

        // Modele : w, b ~ N(0, priorVar) ; y_n ~ Bernoulli(sigmoid(w*x_n + b)).
        var w = Variable.New<double>().Named("w");
        var b = Variable.New<double>().Named("b");
        w.SetTo(Variable.GaussianFromMeanAndVariance(0, priorVar));
        b.SetTo(Variable.GaussianFromMeanAndVariance(0, priorVar));
        for (int i = 0; i < nObs; i++)
        {
            var y_i = Variable.New<bool>().Named("y_" + i);
            double x_i = xData[i];
            y_i.SetTo(Variable.Bernoulli(Variable.Logistic(w * x_i + b)));
            y_i.ObservedValue = yData[i];
        }

        var engine = new InferenceEngine(new ExpectationPropagation());
        engine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
        engine.ShowProgress = false;

        foreach (int nIter in nIters)
        {
            engine.NumberOfIterations = nIter;
            try
            {
                var postW = engine.Infer<Gaussian>(w);
                var postB = engine.Infer<Gaussian>(b);
                Console.WriteLine($"  nIter={nIter,3} seed={seed} | w=({postW.GetMean():F4},{postW.GetVariance():F4}) b=({postB.GetMean():F4},{postB.GetVariance():F4})");
            }
            catch (System.Exception ex)
            {
                string msg = ex.Message.Split('\n')[0];
                Console.WriteLine($"  nIter={nIter,3} seed={seed} | EXC: {ex.GetType().Name}: {msg}");
            }
        }
    }
    Console.WriteLine();
}
=== Set 1 (priorVar=1, informative prior) ===
  nIter=  1 seed=1 | w=(1,0254,0,4431) b=(-1,2310,0,3830)
  nIter=  2 seed=1 | w=(0,7751,0,4975) b=(-1,2036,0,4294)
  nIter=  5 seed=1 | w=(0,7917,0,4787) b=(-1,2136,0,4221)
  nIter= 10 seed=1 | w=(0,7916,0,4788) b=(-1,2136,0,4221)
  nIter= 20 seed=1 | w=(0,7916,0,4788) b=(-1,2136,0,4221)
  nIter= 50 seed=1 | w=(0,7916,0,4788) b=(-1,2136,0,4221)
  nIter=  1 seed=2 | w=(0,5984,0,7076) b=(0,9894,0,6847)
  nIter=  2 seed=2 | w=(1,3736,0,3218) b=(0,4797,0,4031)
  nIter=  5 seed=2 | w=(1,3103,0,3500) b=(0,4653,0,4243)
  nIter= 10 seed=2 | w=(1,3103,0,3499) b=(0,4653,0,4242)
  nIter= 20 seed=2 | w=(1,3103,0,3499) b=(0,4653,0,4242)
  nIter= 50 seed=2 | w=(1,3103,0,3499) b=(0,4653,0,4242)
  nIter=  1 seed=3 | w=(1,3259,0,6995) b=(-0,1112,0,5512)
  nIter=  2 seed=3 | w=(1,6290,0,4459) b=(-0,1967,0,4004)
  nIter=  5 seed=3 | w=(1,5991,0,4711) b=(-0,1931,0,4127)
  nIter= 10 seed=3 | w=(1,5991,0,4711) b=(-0,1931,0,4127)
  nIter= 20 seed=3 | w=(1,5991,0,4711) b=(-0,1931,0,4127)
  nIter= 50 seed=3 | w=(1,5991,0,4711) b=(-0,1931,0,4127)

=== Set 2 (priorVar=1000, vague prior -- EP mal conditionnee) ===
  nIter=  1 seed=1 | w=(2,8190,416,3263) b=(-2,6717,126,0183)
  nIter=  2 seed=1 | w=(18,4059,58,9885) b=(-14,9162,24,6963)
  nIter=  5 seed=1 | w=(8,3418,13,7448) b=(-6,8811,6,5919)
  nIter= 10 seed=1 | w=(4,4550,7,0346) b=(-4,5997,3,8263)
  nIter= 20 seed=1 | w=(4,1397,6,2549) b=(-4,3794,3,4654)
  nIter= 50 seed=1 | w=(4,1335,6,2400) b=(-4,3749,3,4585)
  nIter=  1 seed=2 | EXC: ArgumentException: mean >= 1
  nIter=  2 seed=2 | EXC: ArgumentException: mean >= 1
  nIter=  5 seed=2 | EXC: ArgumentException: mean >= 1
  nIter= 10 seed=2 | EXC: ArgumentException: mean >= 1
  nIter= 20 seed=2 | EXC: ArgumentException: mean >= 1
  nIter= 50 seed=2 | EXC: ArgumentException: mean >= 1
  nIter=  1 seed=3 | EXC: ArgumentException: mean <= 0
  nIter=  2 seed=3 | w=(31,7569,577,4331) b=(-4,2624,20,9662)
  nIter=  5 seed=3 | w=(43,2607,181,9588) b=(-5,2609,15,0185)
  nIter= 10 seed=3 | w=(40,1127,295,8907) b=(-4,8246,16,9224)
  nIter= 20 seed=3 | w=(40,9375,265,5626) b=(-4,9429,16,4808)
  nIter= 50 seed=3 | w=(41,0891,260,0735) b=(-4,9643,16,3947)

=== Set 3 (priorVar=0.01, tres informatif -- prior domine) ===
  nIter=  1 seed=1 | w=(0,0286,0,0098) b=(-0,0387,0,0098)
  nIter=  2 seed=1 | w=(0,0282,0,0098) b=(-0,0387,0,0098)
  nIter=  5 seed=1 | w=(0,0282,0,0098) b=(-0,0387,0,0098)
  nIter= 10 seed=1 | w=(0,0282,0,0098) b=(-0,0387,0,0098)
  nIter= 20 seed=1 | w=(0,0282,0,0098) b=(-0,0387,0,0098)
  nIter= 50 seed=1 | w=(0,0282,0,0098) b=(-0,0387,0,0098)
  nIter=  1 seed=2 | w=(0,0449,0,0096) b=(0,0099,0,0098)
  nIter=  2 seed=2 | w=(0,0450,0,0096) b=(0,0099,0,0098)
  nIter=  5 seed=2 | w=(0,0450,0,0096) b=(0,0099,0,0098)
  nIter= 10 seed=2 | w=(0,0450,0,0096) b=(0,0099,0,0098)
  nIter= 20 seed=2 | w=(0,0450,0,0096) b=(0,0099,0,0098)
  nIter= 50 seed=2 | w=(0,0450,0,0096) b=(0,0099,0,0098)
  nIter=  1 seed=3 | w=(0,0443,0,0097) b=(-0,0095,0,0098)
  nIter=  2 seed=3 | w=(0,0443,0,0097) b=(-0,0095,0,0098)
  nIter=  5 seed=3 | w=(0,0443,0,0097) b=(-0,0095,0,0098)
  nIter= 10 seed=3 | w=(0,0443,0,0097) b=(-0,0095,0,0098)
  nIter= 20 seed=3 | w=(0,0443,0,0097) b=(-0,0095,0,0098)
  nIter= 50 seed=3 | w=(0,0443,0,0097) b=(-0,0095,0,0098)

Lecture du diagnostic non conjugue

Trois regimes, trois verdicts distincts :

Regime priorVar Comportement Diagnostic
Set 1 1.0 (informatif) Converge en ~10 iter, les 3 seeds donnent 3 marginales differentes (les donnees sont differentes par seed) mais chacune est stable a 4 decimales entre nIter=10 et nIter=50 EP saine
Set 2 1000.0 (vague) 2 seeds sur 3 convergent vers 2 points fixes distincts (w proche de 4.13 ou 41.1) ; le 3e seed leve ArgumentException: mean >= 1 a tous les budgets d’iterations (nIter=1 comme nIter=50) – il ne converge jamais EP diverge et n’est pas reproductible
Set 3 0.01 (tres informatif) Converge en 1-2 iter, posterieur proche de N(0, 0.01) sur les 3 seeds EP dominee par le prior

Verdict principal – Set 2 (priorVar=1000) :

  • non-reproductibilite inter-seed : 2 seeds convergent vers des w posterieurs distincts ({4,13 ; 41,1}), le 3e seed ne produit aucune inferance valide (exception a tous les budgets) – au lieu d’un unique point fixe. Preuve directe qu’EP a converge vers un minimum local dependant de l’initialisation (le seed pilote l’echantillonnage des donnees synthetiques, et l’asymetrie y=0/y=1 du mini-batch de 10 obs change le point d’entree des messages EP).
  • exceptions numeriques inter-seed : ArgumentException: mean >= 1 / mean <= 0 apparaissent quand les messages gaussiens approchent une Bernoulli pleine ou vide ; pour le seed le plus mal conditionne, l’exception est levee a tous les budgets d’iterations. Ces exceptions sont deterministes par seed (le meme seed reproduit la meme exception au meme budget), ce sont des echecs d’approximation, pas du bruit.
  • aucun ‘bon’ budget d’iterations : meme a nIter=50, les posterieurs restent sur 2 points fixes distincts (et le 3e seed en exception). Multiplier les iterations ne resout pas la divergence ; il faudrait un meilleur approximant (EP a du second ordre, variational mean-field, MCMC).

Lecon methodologique :

Le sweep d’iterations qu’on a appris sur le collider discret devient, sur le non-conjugue, un diagnostic d’instabilite operationnel, pas un exercice de cours. Quand le moteur diverge ou donne des resultats non-reproductibles, le bon reflexe n’est pas de pousser NumberOfIterations : il faut changer de moteur (Variational Message Passing, echantillonnage, ou un EP d’ordre 2) ou reguler le modele (un prior informatif – Set 1 et Set 3 montrent que prior priorVar=1 ou priorVar=0.01 rendent EP saine). Cf. Infer-9-Classification.ipynb (regression logistique bayesienne sur donnees reelles) pour le pendant applique de ce diagnostic.

Convergence cross-de ce grain :

Le diagnostic precedent (cells 30-32) etait une convergence triviale sur un modele discret exact. Cette cellule est sa contrepartie : la ou EP est vraiment iterative et vraiment approximative, son etat de sante est mesurable et le diagnostic a une valeur reelle (le cas vague-prior est un signal d’alarme operationnel, pas un cas d’ecole). Le mini-batch nObs=10 est volontairement petit pour rendre la divergence tres visible – un mini-batch plus grand regulariserait naturellement la posterior et EP, mais le signal apprendrait moins bien.

(Suite a l’issue jsboige/CoursIA#8892 – EP mixture collapse diagnostic, qui suit la piste prec=10 symetrique dans Infer-2b-Debugging-Bonnes-Pratiques.ipynb – le meme theme d’instabilite d’EP sous posterior multi-modale. Les deux notebooks se completent : #8892 montre le collapse sur discret, cette cellule montre la divergence sur continu.)

5. D-Separation et indépendance Conditionnelle

Definition

La D-separation est un critere graphique pour determiner l’indépendance conditionnelle dans un reseau bayesien.

Trois structures de base

Structure Nom indépendance
A -> B -> C chaîne A | C | B
A <- B -> C Fork (cause commune) A | C | B
A -> B <- C Collider (effet commun) A | C, mais A /|/ C | B

Application au reseau Wet Grass

Le reseau Wet Grass contient deux structures cles :

  1. Fork (S <- C -> R) : Sprinkler et Rain ont une cause commune (Cloudy)
    • Marginalement : S et R sont dependants (l’info passe par C)
    • Conditionnellement a C : S et R sont independants
  2. Collider (S -> W <- R) : WetGrass est un effet commun
    • Marginalement : S et R sont independants via ce chemin
    • Conditionnellement a W : S et R deviennent dependants (explaining away)
// Verification de la D-separation

// Test 1 : Sprinkler et Rain sans observation sur WetGrass
Variable<bool> c4 = Variable.Bernoulli(0.5);
Variable<bool> s4 = Variable.New<bool>();
Variable<bool> r4 = Variable.New<bool>();

using (Variable.If(c4)) { s4.SetTo(Variable.Bernoulli(0.1)); r4.SetTo(Variable.Bernoulli(0.8)); }
using (Variable.IfNot(c4)) { s4.SetTo(Variable.Bernoulli(0.5)); r4.SetTo(Variable.Bernoulli(0.2)); }

// Observation : Sprinkler = true
s4.ObservedValue = true;

InferenceEngine m4 = new InferenceEngine();
m4.Compiler.CompilerChoice = CompilerChoice.Roslyn;

Console.WriteLine("=== Test D-separation (sans WetGrass) ===");
Console.WriteLine($"P(Rain | Sprinkler) = {m4.Infer<Bernoulli>(r4).GetProbTrue():F3}");
Console.WriteLine("P(Rain) marginale = 0.500");
Console.WriteLine("\n=> S et R ne sont PAS marginalement independants !");
Console.WriteLine("   Observer S donne de l'information sur C, qui informe R.");
=== Test D-separation (sans WetGrass) ===
Compiling model...done.
P(Rain | Sprinkler) = 0,300
P(Rain) marginale = 0.500

=> S et R ne sont PAS marginalement independants !
   Observer S donne de l'information sur C, qui informe R.

Analyse du résultat

résultat : P(Rain | Sprinkler=True) = 0.300 != P(Rain) = 0.500

Ceci confirme que S et R sont marginalement dependants dans la structure fork.

Explication : Observer Sprinkler=True donne de l’information sur Cloudy via le theoreme de Bayes :

\[P(\text{Cloudy} | S=T) = \frac{P(S=T|\text{Cloudy}) \cdot P(\text{Cloudy})}{P(S=T)} = \frac{0.1 \times 0.5}{0.3} \approx 0.167\]

Donc il fait probablement beau (ensoleille), et :

\[P(\text{Rain} | S=T) \approx P(R|C) \cdot P(C|S) + P(R|\neg C) \cdot P(\neg C|S) = 0.8 \times 0.167 + 0.2 \times 0.833 \approx 0.30\]

Point cle : Dans un fork, les variables enfants sont independantes conditionnellement au parent, pas marginalement. L’information “passe” par la cause commune.


Verification de l’indépendance conditionnelle

Pour verifier la D-separation, testons ce qui se passe quand on observe Cloudy en plus de Sprinkler. Si la théorie est correcte, Rain ne devrait plus changer.

// Test 2 : D-separation avec Cloudy observe
// Si on conditionne sur Cloudy, S et R deviennent independants

Variable<bool> c5 = Variable.Bernoulli(0.5);
Variable<bool> s5 = Variable.New<bool>();
Variable<bool> r5 = Variable.New<bool>();

using (Variable.If(c5)) { s5.SetTo(Variable.Bernoulli(0.1)); r5.SetTo(Variable.Bernoulli(0.8)); }
using (Variable.IfNot(c5)) { s5.SetTo(Variable.Bernoulli(0.5)); r5.SetTo(Variable.Bernoulli(0.2)); }

// Observations : Cloudy = false ET Sprinkler = true
c5.ObservedValue = false;  // On fixe Cloudy
s5.ObservedValue = true;

InferenceEngine m5 = new InferenceEngine();
m5.Compiler.CompilerChoice = CompilerChoice.Roslyn;

Console.WriteLine("=== Test D-separation (avec Cloudy observe) ===");
Console.WriteLine($"P(Rain | Sprinkler, Cloudy=False) = {m5.Infer<Bernoulli>(r5).GetProbTrue():F3}");
Console.WriteLine("P(Rain | Cloudy=False) = 0.200");
Console.WriteLine("\n=> S et R sont independants conditionnellement a C !");
Console.WriteLine("   Une fois C connu, observer S n'apporte plus d'information sur R.");
=== Test D-separation (avec Cloudy observe) ===
Compiling model...done.
P(Rain | Sprinkler, Cloudy=False) = 0,200
P(Rain | Cloudy=False) = 0.200

=> S et R sont independants conditionnellement a C !
   Une fois C connu, observer S n'apporte plus d'information sur R.

Verification de l’indépendance conditionnelle :

Le résultat P(Rain | S, C=F) = 0.200 = P(Rain | C=F) confirme que :

\[S \perp\!\!\!\perp R \mid C\]

Sprinkler et Rain sont conditionnellement independants sachant Cloudy. C’est exactement ce que predit la D-separation pour une structure fork.

Test résultat Interpretation
P(R | S) = 0.30 != P(R) = 0.50 S et R dependants marginalement
P(R | S, C) = 0.20 = P(R | C) = 0.20 S et R independants sachant C

Exercice : Verifier le Collider avec Explaining Away

Dans le reseau Wet Grass, la structure Sprinkler -> WetGrass <- Rain forme un collider. La théorie de la D-separation predit que Sprinkler et Rain sont independants marginalement, mais deviennent dependants quand WetGrass est observe.

Objectif : Verifiez experimentalement cette prediction en implementant les deux tests.

étapes : 1. Sans observation sur WetGrass : calculez P(Sprinkler) et P(Sprinkler | Rain=True). Sont-ils egaux ? 2. Avec observation WetGrass=True : calculez P(Sprinkler | WetGrass=True) et P(Sprinkler | Rain=True, WetGrass=True). Sont-ils egaux ? 3. Concluez sur la (in)dépendance conditionnelle

Indices : - Pour le test 1, créez le modèle sans ObservedValue sur WetGrass et comparez avec Rain=True observe - Pour le test 2, ajoutez WetGrass.ObservedValue = true - La différence entre les deux résultats quantifie l’effet “explaining away”

// Exercice : Verifier le Collider avec Explaining Away
// TODO: Test 1 - Sans observer WetGrass, calculez P(Sprinkler) et P(Sprinkler | Rain=True)
// Indice: Creez le reseau WetGrass complet SANS ObservedValue sur wetGrass

// TODO: Test 2 - Avec WetGrass=True observe, calculez P(Sprinkler | WetGrass) et P(Sprinkler | Rain, WetGrass)
// Indice: Ajoutez wetGrass.ObservedValue = true puis comparez avec/sans rain.ObservedValue = true

// TODO: Afficher les resultats dans un tableau comparatif
// Console.WriteLine($"| Sans WetGrass | P(S)={p1:F3} | P(S|R)={p2:F3} |");
// Console.WriteLine($"| Avec WetGrass | P(S|W)={p3:F3} | P(S|R,W)={p4:F3} |");

// Question: Que confirment ces resultats sur la structure collider ?
Console.WriteLine("Exercice a completer : Verifier le Collider");
Exercice a completer : Verifier le Collider

6. Inference Causale vs Observationnelle

différence fondamentale

Type Question Notation
Observationnel P(Y|X=x) Que se passe-t-il quand on observe X=x ?
Interventionnel P(Y|do(X=x)) Que se passe-t-il quand on force X=x ?

Exemple

  • Observer qu’il pleut : P(WetGrass | Rain=True)
  • Faire pleuvoir artificiellement : P(WetGrass | do(Rain=True))

Dans le second cas, on “coupe” les arcs entrants vers Rain (il ne depend plus de Cloudy).


Implementation de l’opérateur do()

Pour simuler une intervention (opérateur do()), on modifie le modèle en : 1. Coupant les arcs entrants vers la variable manipulee 2. Fixant sa valeur a la valeur d’intervention

Comparons P(Cloudy | Rain=True) (observation) avec P(Cloudy | do(Rain=True)) (intervention).

// Comparaison inference observationnelle vs interventionnelle

// OBSERVATIONNEL : P(Cloudy | Rain=True)
Variable<bool> cObs = Variable.Bernoulli(0.5);
Variable<bool> rObs = Variable.New<bool>();

using (Variable.If(cObs)) { rObs.SetTo(Variable.Bernoulli(0.8)); }
using (Variable.IfNot(cObs)) { rObs.SetTo(Variable.Bernoulli(0.2)); }

rObs.ObservedValue = true;

InferenceEngine mObs = new InferenceEngine();
mObs.Compiler.CompilerChoice = CompilerChoice.Roslyn;

double pCloudyObs = mObs.Infer<Bernoulli>(cObs).GetProbTrue();

// INTERVENTIONNEL : P(Cloudy | do(Rain=True))
// On "coupe" l'arc Cloudy -> Rain en fixant Rain independamment
Variable<bool> cInt = Variable.Bernoulli(0.5);
Variable<bool> rInt = Variable.Bernoulli(1.0);  // Force a True, independant de Cloudy

InferenceEngine mInt = new InferenceEngine();
mInt.Compiler.CompilerChoice = CompilerChoice.Roslyn;

double pCloudyInt = mInt.Infer<Bernoulli>(cInt).GetProbTrue();

Console.WriteLine("=== Observationnel vs Interventionnel ===");
Console.WriteLine($"P(Cloudy | Rain=True) = {pCloudyObs:F3} (observationnel)");
Console.WriteLine($"P(Cloudy | do(Rain=True)) = {pCloudyInt:F3} (interventionnel)");
Console.WriteLine("\n=> Observer la pluie informe sur le temps nuageux");
Console.WriteLine("   Faire pleuvoir ne change pas le temps nuageux");
Compiling model...done.
Compiling model...done.
=== Observationnel vs Interventionnel ===
P(Cloudy | Rain=True) = 0,800 (observationnel)
P(Cloudy | do(Rain=True)) = 0,500 (interventionnel)

=> Observer la pluie informe sur le temps nuageux
   Faire pleuvoir ne change pas le temps nuageux

Analyse : Causalité vs Corrélation

Résultats : - P(Cloudy | Rain=True) = 0.800 — observationnel - P(Cloudy | do(Rain=True)) = 0.500 — interventionnel

Différence fondamentale :

Type Sémantique Résultat
Observation “Je vois qu’il pleut” Le temps est probablement nuageux
Intervention “Je fais pleuvoir (machine à pluie)” Le temps reste incertain

L’opérateur do() de Pearl “coupe” les arcs entrants vers la variable manipulée. Rain ne dépend plus de Cloudy dans le graphe modifié.

Application pratique : Cette distinction est cruciale en médecine. Observer une corrélation entre un traitement et la guérison n’implique pas que le traitement cause la guérison (biais de sélection possible). Un essai randomisé (intervention) est nécessaire pour établir la causalité.

Exercice : Explaining Away dans le diagnostic medical

Dans le reseau de diagnostic (Cold -> Fatigue <- Flu), les deux maladies sont des causes alternatives de la fatigue. Lorsqu’on observe la fatigue, un phenomene d’explaining away apparait.

Objectif : Quantifiez l’effet d’explaining away en comparant P(Cold | Fatigue=True, Flu=True) avec P(Cold | Fatigue=True).

étapes : 1. Implementez le reseau Cold -> Fatigue <- Flu avec les mêmes CPT que dans l’exemple guide 2. Calculez P(Cold | Fatigue=True) sans observer Flu 3. Calculez P(Cold | Fatigue=True, Flu=True) en ajoutant Flu=True comme observation 4. Expliquez pourquoi P(Cold) diminue quand Flu=True est observe

Indices : - Utilisez les mêmes CPT que la section 9 : P(Cold)=0.02, P(Flu)=0.01 - P(Fatigue | Cold=T, Flu=T) = 0.95, P(Fatigue | Cold=T, Flu=F) = 0.6 - P(Fatigue | Cold=F, Flu=T) = 0.9, P(Fatigue | Cold=F, Flu=F) = 0.1 - La grippe “explique” la fatigue, rendant le rhume moins necessaire comme explication

// Exercice : Explaining Away dans le diagnostic medical
// TODO: Definir les variables Cold et Flu avec leurs priors
// Indice: Variable<bool> cold = Variable.Bernoulli(0.02);

// TODO: Definir Fatigue avec sa CPT (4 cas: Cold/Flu, Cold/!Flu, !Cold/Flu, !Cold/!Flu)
// Indice: utilisez Variable.If/IfNot imbriques comme dans l'exemple guide

// TODO: Test 1 - Observer Fatigue=True, inferer P(Cold)
// Indice: fatigue.ObservedValue = true; puis engine.Infer<Bernoulli>(cold)

// TODO: Test 2 - Observer Fatigue=True ET Flu=True, inferer P(Cold)
// Indice: Ajoutez flu.ObservedValue = true; et comparez les deux resultats

// Question: Pourquoi P(Cold) baisse-t-il quand on sait que le patient a la grippe ?
Console.WriteLine("Exercice a completer : Explaining Away dans le diagnostic");
Exercice a completer : Explaining Away dans le diagnostic

7. modèle Causal : Direction de la Causalite

Question

Etant donne des données observationnelles entre A et B, comment determiner si A cause B ou B cause A ?

Approche bayesienne

Comparer les evidences des deux modèles : - \(M_1\) : A -> B - \(M_2\) : B -> A

// Modele pour identifier la direction causale

// Donnees simulees : A cause B avec bruit
double[] dataA = { 1.0, 2.0, 3.0, 4.0, 5.0 };
double[] dataB = { 2.1, 4.2, 5.8, 8.1, 10.3 };  // B ~ 2*A + bruit

int n = dataA.Length;

// Modele 1 : A -> B (A cause B)
Variable<bool> evidence1 = Variable.Bernoulli(0.5);
using (Variable.If(evidence1))
{
    Variable<double> slope1 = Variable.GaussianFromMeanAndPrecision(0, 0.1);
    Variable<double> intercept1 = Variable.GaussianFromMeanAndPrecision(0, 0.1);
    Variable<double> noise1 = Variable.GammaFromShapeAndScale(2, 0.5);

    for (int i = 0; i < n; i++)
    {
        Variable<double> aPrior1 = Variable.GaussianFromMeanAndPrecision(0, 0.1);
        aPrior1.ObservedValue = dataA[i];
        Variable<double> bPred1 = slope1 * aPrior1 + intercept1;
        Variable<double> bObs1 = Variable.GaussianFromMeanAndPrecision(bPred1, noise1);
        bObs1.ObservedValue = dataB[i];
    }
}

InferenceEngine mCausal1 = new InferenceEngine();
mCausal1.Compiler.CompilerChoice = CompilerChoice.Roslyn;
double logEvidence1 = mCausal1.Infer<Bernoulli>(evidence1).LogOdds;

// Modele 2 : B -> A (miroir — B cause A)
Variable<bool> evidence2 = Variable.Bernoulli(0.5);
using (Variable.If(evidence2))
{
    Variable<double> slope2 = Variable.GaussianFromMeanAndPrecision(0, 0.1);
    Variable<double> intercept2 = Variable.GaussianFromMeanAndPrecision(0, 0.1);
    Variable<double> noise2 = Variable.GammaFromShapeAndScale(2, 0.5);

    for (int i = 0; i < n; i++)
    {
        Variable<double> bPrior2 = Variable.GaussianFromMeanAndPrecision(0, 0.1);
        bPrior2.ObservedValue = dataB[i];
        Variable<double> aPred2 = slope2 * bPrior2 + intercept2;
        Variable<double> aObs2 = Variable.GaussianFromMeanAndPrecision(aPred2, noise2);
        aObs2.ObservedValue = dataA[i];
    }
}

InferenceEngine mCausal2 = new InferenceEngine();
mCausal2.Compiler.CompilerChoice = CompilerChoice.Roslyn;
double logEvidence2 = mCausal2.Infer<Bernoulli>(evidence2).LogOdds;

// Selection par Bayes Factor
double logBF = logEvidence1 - logEvidence2;
double BF = Math.Exp(logBF);

Console.WriteLine("=== Selection de la direction causale ===");
Console.WriteLine($"Log Evidence (A -> B) = {logEvidence1:F3}");
Console.WriteLine($"Log Evidence (B -> A) = {logEvidence2:F3}");
Console.WriteLine($"Log Bayes Factor = {logBF:F3}");
Console.WriteLine($"Bayes Factor = {BF:F3}");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
=== Selection de la direction causale ===
Log Evidence (A -> B) = -22,920
Log Evidence (B -> A) = -31,978
Log Bayes Factor = 9,058
Bayes Factor = 8590,338

Interpretation : le Bayes Factor tranche la direction causale

résultats (les deux modèles ajustés sur les mêmes données) : - Log Evidence (A → B) = -22,920 - Log Evidence (B → A) = -31,978 - Log Bayes Factor = +9,058 → Bayes Factor ≈ 8590

Chaque Log Evidence est le logarithme de la vraisemblance marginale du modèle (la probabilité des données intégrée sur les priors des paramètres). Pour comparer deux modèles candidats, on calcule le Bayes Factor :

\[\text{Bayes Factor} = \frac{P(\text{Data} | M_1)}{P(\text{Data} | M_2)} = \exp(\log E_1 - \log E_2)\]

Echelle d’interpretation du Bayes Factor (Kass & Raftery, 1995) :

log(BF) BF Interpretation
< 1 < 3 Evidence negligeable
1-3 3-20 Evidence positive
3-5 20-150 Evidence forte
> 5 > 150 Evidence très forte

Avec log(BF) ≈ 9,1 (BF ≈ 8590), nous sommes très au-delà du seuil > 5 : l’évidence en faveur du modèle A → B est « très forte ». Cela correspond à la vérité générative — les données ont été simulées par B ≈ 2·A + bruit — et la sélection bayésienne la recouvre correctement.

Pourquoi le modèle inverse (B → A) obtient-il une evidence nettement plus basse ? Les valeurs de A ({1, 2, 3, 4, 5}) sont régulières et peu bruitées ; dans le sens A → B, le bruit additif se dépose naturellement sur B (qui est effectivement bruité). Inverser la flèche force le modèle à expliquer la régularité de A à partir des valeurs plus dispersées de B : le bruit doit alors être attribué à A, ce qui appauvrit l’ajustement et fait chuter la vraisemblance marginale. C’est précisément ce que capte le Bayes Factor — et c’est ce qui rend la sélection de direction non triviale : la méthode ne se contente pas d’ajuster un modèle, elle pénalise explicitement la mauvaise orientation causale.

Limitation : Cette approche suppose des modèles linéaires gaussiens. Pour des relations non-linéaires ou des variables catégorielles, d’autres critères (BIC, WAIC, cross-validation prédictive) ou une analyse causale plus structurelle (contraintes de Pearl, variables instrumentales) seraient nécessaires. Le Bayes Factor est aussi sensible au choix des priors : des priors trop larges pénalisent toujours les modèles les plus complexes.

8. modèle BUGS Rats (modèle hiérarchique)

Contexte

Le modèle “Rats” de BUGS est un exemple classique de modèle hiérarchique : - Plusieurs rats suivis dans le temps - Chaque rat a sa propre trajectoire de croissance - Les paramètres individuels sont tires d’une distribution de population

Structure hiérarchique

Population: mu_alpha, sigma_alpha, mu_beta, sigma_beta
     |
     v
Individu: alpha[i], beta[i] ~ N(mu, sigma)
     |
     v
Observation: y[i,t] = alpha[i] + beta[i] * t + epsilon

Implementation Infer.NET du modèle hiérarchique

Le modèle suivant illustre la structure hiérarchique avec : - Niveau population : hyperparametres mu_alpha, mu_beta, tau_alpha, tau_beta - Niveau individuel : paramètres alpha[i], beta[i] pour chaque rat - Niveau observation : mesures de poids y[i,t]

Cette structure permet le partage d’information entre individus via les distributions de population.

// Modele hierarchique simplifie (style BUGS Rats)

int nRats = 5;
int nTimePoints = 4;
double[] times = { 8.0, 15.0, 22.0, 29.0 };  // Ages en jours

// Donnees simulees : poids des rats au fil du temps
double[,] weights = new double[,] {
    { 151, 199, 246, 283 },
    { 145, 199, 249, 293 },
    { 147, 214, 263, 312 },
    { 155, 200, 237, 272 },
    { 135, 188, 230, 280 }
};

// Parametres de population
Variable<double> muAlpha = Variable.GaussianFromMeanAndVariance(150, 1000);
Variable<double> muBeta = Variable.GaussianFromMeanAndVariance(5, 100);
Variable<double> tauAlpha = Variable.GammaFromShapeAndScale(1, 1);
Variable<double> tauBeta = Variable.GammaFromShapeAndScale(1, 1);
Variable<double> tauNoise = Variable.GammaFromShapeAndScale(1, 1);

// Parametres individuels
Range ratRange = new Range(nRats);
VariableArray<double> alpha = Variable.Array<double>(ratRange);
VariableArray<double> beta = Variable.Array<double>(ratRange);

using (Variable.ForEach(ratRange))
{
    alpha[ratRange] = Variable.GaussianFromMeanAndPrecision(muAlpha, tauAlpha);
    beta[ratRange] = Variable.GaussianFromMeanAndPrecision(muBeta, tauBeta);
}

// Observations
for (int i = 0; i < nRats; i++)
{
    for (int t = 0; t < nTimePoints; t++)
    {
        Variable<double> meanWeight = alpha[i] + beta[i] * (times[t] - 22.0);
        Variable<double> obsWeight = Variable.GaussianFromMeanAndPrecision(meanWeight, tauNoise);
        obsWeight.ObservedValue = weights[i, t];
    }
}

InferenceEngine mRats = new InferenceEngine(new ExpectationPropagation());
mRats.Compiler.CompilerChoice = CompilerChoice.Roslyn;
mRats.ShowFactorGraph = true;  // Graphe du modele hierarchique

Console.WriteLine("=== Modele Hierarchique (Rats) ===");
Console.WriteLine($"mu_alpha (intercept moyen) : {mRats.Infer<Gaussian>(muAlpha)}");
Console.WriteLine($"mu_beta (pente moyenne) : {mRats.Infer<Gaussian>(muBeta)}");
=== Modele Hierarchique (Rats) ===
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
mu_alpha (intercept moyen) : Gaussian(241,7, 22,96)
mu_beta (pente moyenne) : Gaussian(6,68, 0,2134)

Visualisation du graphe hiérarchique du modèle Rats.

// Visualisation du graphe hierarchique (Rats)
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_02_22_52_49.svg
Model node0 150 node1 GaussianFromMeanAndVariance node0->node1 mean node3 vdouble512 node1->node3 node2 1000 node2->node1 variance node4 Gaussian node3->node4 mean node6 vdouble[]0[index0] node4->node6 node5 vdouble518 node5->node4 precision node10 vdouble[]0[vint2] node6->node10 node14 vdouble[]0[vint4] node6->node14 node18 vdouble[]0[vint6] node6->node18 node22 vdouble[]0[vint8] node6->node22 node26 vdouble[]0[vint10] node6->node26 node30 vdouble[]0[vint12] node6->node30 node34 vdouble[]0[vint14] node6->node34 node38 vdouble[]0[vint16] node6->node38 node42 vdouble[]0[vint18] node6->node42 node46 vdouble[]0[vint20] node6->node46 node50 vdouble[]0[vint22] node6->node50 node54 vdouble[]0[vint24] node6->node54 node58 vdouble[]0[vint26] node6->node58 node62 vdouble[]0[vint28] node6->node62 node66 vdouble[]0[vint30] node6->node66 node70 vdouble[]0[vint32] node6->node70 node74 vdouble[]0[vint34] node6->node74 node78 vdouble[]0[vint36] node6->node78 node82 vdouble[]0[vint38] node6->node82 node86 vdouble[]0[vint40] node6->node86 node7 1 node8 Sample node7->node8 shape node8->node5 node9 1 node9->node8 scale node11 Plus node10->node11 a node13 vdouble535 node11->node13 node12 vdouble534 node12->node11 b node166 Gaussian node13->node166 mean node15 Plus node14->node15 a node17 vdouble541 node15->node17 node16 vdouble540 node16->node15 b node168 Gaussian node17->node168 mean node19 Plus node18->node19 a node21 vdouble547 node19->node21 node20 vdouble546 node20->node19 b node170 Gaussian node21->node170 mean node23 Plus node22->node23 a node25 vdouble553 node23->node25 node24 vdouble552 node24->node23 b node172 Gaussian node25->node172 mean node27 Plus node26->node27 a node29 vdouble559 node27->node29 node28 vdouble558 node28->node27 b node174 Gaussian node29->node174 mean node31 Plus node30->node31 a node33 vdouble565 node31->node33 node32 vdouble564 node32->node31 b node176 Gaussian node33->node176 mean node35 Plus node34->node35 a node37 vdouble571 node35->node37 node36 vdouble570 node36->node35 b node178 Gaussian node37->node178 mean node39 Plus node38->node39 a node41 vdouble577 node39->node41 node40 vdouble576 node40->node39 b node180 Gaussian node41->node180 mean node43 Plus node42->node43 a node45 vdouble583 node43->node45 node44 vdouble582 node44->node43 b node182 Gaussian node45->node182 mean node47 Plus node46->node47 a node49 vdouble589 node47->node49 node48 vdouble588 node48->node47 b node184 Gaussian node49->node184 mean node51 Plus node50->node51 a node53 vdouble595 node51->node53 node52 vdouble594 node52->node51 b node186 Gaussian node53->node186 mean node55 Plus node54->node55 a node57 vdouble601 node55->node57 node56 vdouble600 node56->node55 b node188 Gaussian node57->node188 mean node59 Plus node58->node59 a node61 vdouble607 node59->node61 node60 vdouble606 node60->node59 b node190 Gaussian node61->node190 mean node63 Plus node62->node63 a node65 vdouble613 node63->node65 node64 vdouble612 node64->node63 b node192 Gaussian node65->node192 mean node67 Plus node66->node67 a node69 vdouble619 node67->node69 node68 vdouble618 node68->node67 b node194 Gaussian node69->node194 mean node71 Plus node70->node71 a node73 vdouble625 node71->node73 node72 vdouble624 node72->node71 b node196 Gaussian node73->node196 mean node75 Plus node74->node75 a node77 vdouble631 node75->node77 node76 vdouble630 node76->node75 b node198 Gaussian node77->node198 mean node79 Plus node78->node79 a node81 vdouble637 node79->node81 node80 vdouble636 node80->node79 b node200 Gaussian node81->node200 mean node83 Plus node82->node83 a node85 vdouble643 node83->node85 node84 vdouble642 node84->node83 b node202 Gaussian node85->node202 mean node87 Plus node86->node87 a node89 vdouble649 node87->node89 node88 vdouble648 node88->node87 b node160 Gaussian node89->node160 mean node90 vdouble[]1[vint41] node91 Multiply node90->node91 a node91->node88 node92 7 node92->node91 b node93 vdouble[]1[vint3] node94 Multiply node93->node94 a node94->node12 node95 -14 node95->node94 b node96 vdouble[]1[vint5] node97 Multiply node96->node97 a node97->node16 node98 -7 node98->node97 b node99 vdouble[]1[vint7] node100 Multiply node99->node100 a node100->node20 node101 0 node101->node100 b node102 vdouble[]1[vint9] node103 Multiply node102->node103 a node103->node24 node104 7 node104->node103 b node105 vdouble[]1[vint11] node106 Multiply node105->node106 a node106->node28 node107 -14 node107->node106 b node108 vdouble[]1[vint13] node109 Multiply node108->node109 a node109->node32 node110 -7 node110->node109 b node111 vdouble[]1[vint15] node112 Multiply node111->node112 a node112->node36 node113 0 node113->node112 b node114 vdouble[]1[vint17] node115 Multiply node114->node115 a node115->node40 node116 7 node116->node115 b node117 vdouble[]1[vint19] node118 Multiply node117->node118 a node118->node44 node119 -14 node119->node118 b node120 vdouble[]1[vint21] node121 Multiply node120->node121 a node121->node48 node122 -7 node122->node121 b node123 vdouble[]1[vint23] node124 Multiply node123->node124 a node124->node52 node125 0 node125->node124 b node126 vdouble[]1[vint25] node127 Multiply node126->node127 a node127->node56 node128 7 node128->node127 b node129 vdouble[]1[vint27] node130 Multiply node129->node130 a node130->node60 node131 -14 node131->node130 b node132 vdouble[]1[vint29] node133 Multiply node132->node133 a node133->node64 node134 -7 node134->node133 b node135 vdouble[]1[vint31] node136 Multiply node135->node136 a node136->node68 node137 0 node137->node136 b node138 vdouble[]1[vint33] node139 Multiply node138->node139 a node139->node72 node140 7 node140->node139 b node141 vdouble[]1[vint35] node142 Multiply node141->node142 a node142->node76 node143 -14 node143->node142 b node144 vdouble[]1[vint37] node145 Multiply node144->node145 a node145->node80 node146 -7 node146->node145 b node147 vdouble[]1[vint39] node148 Multiply node147->node148 a node148->node84 node149 0 node149->node148 b node150 vdouble515 node151 Gaussian node150->node151 mean node153 vdouble[]1[index0] node151->node153 node152 vdouble521 node152->node151 precision node153->node90 node153->node93 node153->node96 node153->node99 node153->node102 node153->node105 node153->node108 node153->node111 node153->node114 node153->node117 node153->node120 node153->node123 node153->node126 node153->node129 node153->node132 node153->node135 node153->node138 node153->node141 node153->node144 node153->node147 node154 1 node155 Sample node154->node155 shape node155->node152 node156 1 node156->node155 scale node157 5 node158 GaussianFromMeanAndVariance node157->node158 mean node158->node150 node159 100 node159->node158 variance node162 280 node160->node162 node161 vdouble524 node161->node160 precision node161->node166 precision node161->node168 precision node161->node170 precision node161->node172 precision node161->node174 precision node161->node176 precision node161->node178 precision node161->node180 precision node161->node182 precision node161->node184 precision node161->node186 precision node161->node188 precision node161->node190 precision node161->node192 precision node161->node194 precision node161->node196 precision node161->node198 precision node161->node200 precision node161->node202 precision node163 1 node164 Sample node163->node164 shape node164->node161 node165 1 node165->node164 scale node167 151 node166->node167 node169 199 node168->node169 node171 246 node170->node171 node173 283 node172->node173 node175 145 node174->node175 node177 199 node176->node177 node179 249 node178->node179 node181 293 node180->node181 node183 147 node182->node183 node185 214 node184->node185 node187 263 node186->node187 node189 312 node188->node189 node191 155 node190->node191 node193 200 node192->node193 node195 237 node194->node195 node197 272 node196->node197 node199 135 node198->node199 node201 188 node200->node201 node203 230 node202->node203

Anatomie du modèle hiérarchique

Le graphe illustre la structure a deux niveaux du modèle Rats :

Niveau population (hyperparametres) : - muAlpha, muBeta : moyennes de population - tauAlpha, tauBeta : precisions de population

Niveau individuel (paramètres) : - alpha[i], beta[i] : paramètres pour chaque rat - Les arrays sont representes par des facteurs repliques

Niveau observation : - Les observations de poids sont attachees aux predictions individuelles

Cette structure plate est caractéristique des modèles hiérarchiques compiles : les boucles ForEach sont deroulees en facteurs individuels partageant les mêmes hyperparametres.

Analyse du modèle hiérarchique

Résultats : - μ_alpha (poids moyen à t=22 jours) ≈ 241.7 g - μ_beta (croissance moyenne) ≈ 6.68 g/jour

Interprétation biologique : - À 22 jours, les rats pèsent en moyenne ~242g - Ils prennent environ 6.7g par jour

Avantages du modèle hiérarchique :

Aspect Explication
Partage d’information Les rats “empruntent” de l’information les uns aux autres via la population
Shrinkage Les estimations individuelles sont tirées vers la moyenne
Robustesse Moins sensible aux outliers individuels
Généralisation Peut prédire pour un nouveau rat non observé

Concept clé : La hiérarchie “régularise” naturellement les estimations. Un rat avec peu d’observations sera tiré vers la moyenne de population, évitant le sur-ajustement.

9. Exemple guide : Reseau de Diagnostic Medical

Enonce

Construisez un reseau bayesien pour le diagnostic de deux maladies :

Structure :

(Cold)     (Flu)
   \         /
    v       v
   (Fatigue)
    |
    v
  (Fever)

CPTs : - P(Cold) = 0.02 - P(Flu) = 0.01 - P(Fatigue | Cold, Flu) : voir table - P(Fever | Fatigue) : 0.8 si fatigue, 0.05 sinon

Question : P(Flu | Fever=True) ?


Application pratique : systèmes d’aide au diagnostic

Les reseaux bayesiens sont largement utilises en medecine pour le diagnostic assiste par ordinateur. Historiquement :

système Annee Domaine Particularite
MYCIN 1976 Infections bacteriennes règles avec facteurs de certitude
INTERNIST-1 1982 Medecine interne 500+ maladies, 3500 symptomes
QMR 1991 Medecine interne Version bayesienne d’INTERNIST
DXplain 1986-present Diagnostic general 2400+ maladies

L’exercice suivant illustre les principes de base d’un tel système.


Implementation du reseau de diagnostic

Construisons le reseau avec les CPT specifiees. Le raisonnement diagnostique ira des symptomes observes (fievre) vers les causes probables (maladies).

// Exemple guide : Reseau de diagnostic

Variable<bool> cold = Variable.Bernoulli(0.02).Named("cold");
Variable<bool> flu = Variable.Bernoulli(0.01).Named("flu");
Variable<bool> fatigue = Variable.New<bool>().Named("fatigue");
Variable<bool> fever = Variable.New<bool>().Named("fever");

// CPT pour Fatigue
// Cold=F, Flu=F -> P(Fatigue) = 0.1
// Cold=F, Flu=T -> P(Fatigue) = 0.9
// Cold=T, Flu=F -> P(Fatigue) = 0.6
// Cold=T, Flu=T -> P(Fatigue) = 0.95

using (Variable.If(cold))
{
    using (Variable.If(flu))
        fatigue.SetTo(Variable.Bernoulli(0.95));
    using (Variable.IfNot(flu))
        fatigue.SetTo(Variable.Bernoulli(0.6));
}
using (Variable.IfNot(cold))
{
    using (Variable.If(flu))
        fatigue.SetTo(Variable.Bernoulli(0.9));
    using (Variable.IfNot(flu))
        fatigue.SetTo(Variable.Bernoulli(0.1));
}

// CPT pour Fever
using (Variable.If(fatigue))
{
    fever.SetTo(Variable.Bernoulli(0.8));
}
using (Variable.IfNot(fatigue))
{
    fever.SetTo(Variable.Bernoulli(0.05));
}

// Observation
fever.ObservedValue = true;

InferenceEngine mDiag = new InferenceEngine();
mDiag.Compiler.CompilerChoice = CompilerChoice.Roslyn;
mDiag.ShowFactorGraph = true;  // Graphe du reseau de diagnostic

Console.WriteLine("=== Diagnostic Medical ===");
Console.WriteLine($"P(Flu | Fever) = {mDiag.Infer<Bernoulli>(flu).GetProbTrue():F3}");
Console.WriteLine($"P(Cold | Fever) = {mDiag.Infer<Bernoulli>(cold).GetProbTrue():F3}");
Console.WriteLine($"P(Fatigue | Fever) = {mDiag.Infer<Bernoulli>(fatigue).GetProbTrue():F3}");
=== Diagnostic Medical ===
Compiling model...done.
P(Flu | Fever) = 0,052
P(Cold | Fever) = 0,073
P(Fatigue | Fever) = 0,681

Visualisation du graphe du reseau de diagnostic medical.

// Visualisation du graphe de diagnostic medical
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_02_22_55_11.svg
Model node0 Bernoulli(0,01) node1 Random node0->node1 dist node2 flu node1->node2 node5 fatigue node2->node5 condition node3 Bernoulli(0,95) node4 Random node3->node4 dist node4->node5 node17 True node5->node17 condition node6 cold node6->node5 condition node7 Bernoulli(0,6) node8 Random node7->node8 dist node8->node5 node9 Bernoulli(0,9) node10 Random node9->node10 dist node10->node5 node11 Bernoulli(0,1) node12 Random node11->node12 dist node12->node5 node13 Bernoulli(0,02) node14 Random node13->node14 dist node14->node6 node15 Bernoulli(0,8) node16 Random node15->node16 dist node16->node17 node18 Bernoulli(0,05) node19 Random node18->node19 dist node19->node17

Structure du reseau de diagnostic

Le graphe montre le reseau bayesien de diagnostic avec :

  • Deux racines independantes : Cold et Flu (maladies avec faibles prevalences)
  • Variable intermediaire : Fatigue (symptome cause par les maladies)
  • Variable observee : Fever (symptome terminal, observee)

Cette structure en diamant illustre : 1. Causes multiples pour Fatigue (Cold et Flu sont des causes alternatives) 2. chaîne causale Maladie -> Fatigue -> Fever

L’inference diagnostique remonte de Fever observee vers les causes potentielles (maladies), en passant par l’intermediaire Fatigue.

Analyse du diagnostic médical

Résultats : P(Maladie | Fever=True)

Maladie Prior Posterior Ratio
Flu 0.01 0.052 ×5.2
Cold 0.02 0.073 ×3.6

Observations :

  1. Explaining away partiel : Cold et Flu sont tous deux plus probables avec la fièvre, mais moins que si l’un des deux était certain (chacun “explique partiellement” la fièvre).

  2. P(Fatigue | Fever) = 0.681 : La fièvre est fortement associée à la fatigue car P(Fever|Fatigue) = 0.8 >> P(Fever|¬Fatigue) = 0.05.

  3. Diagnostic différentiel : Avec seulement la fièvre, il reste difficile de distinguer Cold de Flu. Des symptômes supplémentaires (courbatures, durée) seraient nécessaires.

Application clinique : Ce type de réseau bayésien est utilisé dans les systèmes d’aide au diagnostic (MYCIN, DXplain). L’ajout de symptômes affine progressivement les probabilités des maladies.

Extension : Diagnostic avec symptomes multiples

Pour ameliorer le diagnostic, on pourrait ajouter d’autres symptomes :

(Cold)     (Flu)
   \   \   /   /
    \   \ /   /
     v   v   v
   (Fatigue) (BodyAche)
       |         |
       v         v
    (Fever)  (Headache)

Table de discrimination :

Symptome Cold Flu Ratio diagnostique
Fatigue legere 60% 90% 1.5
Fievre elevee (>39C) 10% 70% 7.0
Courbatures 20% 80% 4.0
Duree > 7 jours 30% 60% 2.0

Exercice complementaire : Etendre le modèle avec le symptome “BodyAche” et observer comment P(Flu|Fever, BodyAche) evolue par rapport a P(Flu|Fever).

10. Resume

Concepts cles

Concept Description
Reseau bayesien DAG avec variables et CPTs
CPT Table de Probabilite Conditionnelle
D-separation Critere graphique d’indépendance
Explaining away Causes alternatives deviennent moins probables
Observation vs Intervention P(Y|X) vs P(Y|do(X))
modèle hiérarchique paramètres individuels tires d’une population

Patterns Infer.NET

Pattern Code Utilisation
Variable racine Variable.Bernoulli(p) Prior marginal
CPT binaire Variable.If(x) + SetTo() Probabilites conditionnelles
Observation x.ObservedValue = true Conditionner l’inference
Intervention (do) Variable.Bernoulli(1.0) (indépendant) Couper les arcs entrants
Boucle hiérarchique Variable.ForEach(range) modèles multiniveaux

Distributions utilisees

Distribution paramètre Contexte
Bernoulli probabilite p Variables booleennes
Gaussian moyenne, precision Variables continues
Gamma shape, scale Priors sur precisions (positifs)

Prochaine étape

Dans Infer-7-Skills-IRT, nous explorerons :

  • Les modèles d’evaluation de competences
  • Item Response Theory (IRT) avec le modèle Difficulty-Ability
  • Le modèle DINA (Noisy-And) pour les competences multiples
  • Les relations many-to-many entre competences et questions

11. Exercice : Extension du Reseau - Symptome de Fievre

Enonce

Etendez le reseau bayesien de diagnostic en ajoutant un nouveau symptome : la fievre (temperature > 38.5C).

Structure du symptome : - P(fievre | flu=True) = 0.85 : la grippe cause souvent de la fievre - P(fievre | flu=False) = 0.05 : rarement de la fievre sans grippe

scénario : un patient a toux=True, fievre=True, ecoulementNasal=False.

  1. Ajoutez la variable fievre au reseau existant
  2. Observez les symptomes du patient
  3. Comparez P(cold) et P(flu) avec et sans l’observation de fievre

Indice : Utilisez Variable.If(flu) et Variable.IfNot(flu) pour définir la CPT de fievre.

// Exercice : Extension du reseau bayesien avec symptome de fievre
// TODO: Creer le moteur d'inference

// TODO: Definir les maladies (meme structure que dans l'exemple guide)

// TODO: Definir les symptomes existants toux et ecoulementNasal
// (utilisez les memes CPT que dans l'exemple guide)

// TODO: Ajouter le nouveau symptome : fievre (dependant uniquement de flu)

// TODO: Observer les symptomes du patient

// TODO: Inferer et afficher
// Comparez avec le resultat sans observation de fievre
Console.WriteLine("Exercice a completer");
Exercice a completer

Conclusion

Ce notebook a couvert les reseaux bayesiens : structure DAG, CPT, D-separation, explaining away, et modèles hiérarchiques.

Concept Point cle
Reseau bayesien DAG ou P(jointe) = produit des CPT
CPT Probabilites conditionnelles encodees via Variable.If/IfNot
Explaining away Au collider, observer l’effet rend les causes dependantes
D-separation Critere graphique pour l’indépendance conditionnelle
do() vs observer Intervention = couper les arcs entrants, observation = conditionner
Structure indépendance
chaîne A->B->C A indep. C | B
Fork A<-B->C A indep. C | B
Collider A->B<-C A indep. C, mais A dep. C | B
Distribution rôle
Bernoulli Variables booleennes (maladies, symptomes, meteo)
Gaussian Variables continues (modèles hiérarchiques, regression)
Gamma Priors sur precisions

Apport des reseaux bayesiens : Ils permettent le raisonnement abductif (des effets vers les causes), essentiel pour le diagnostic medical, le debogage et l’analyse causale. La distinction observation vs intervention (do-calculus de Pearl) est fondamentale pour la science des données.

Retour au sommet