DecInfer-05-Decision-Networks : Reseaux de Decision

Serie : Programmation Probabiliste avec Infer.NET (5/10)
Duree estimee : 55 minutes
Prerequis : Infer-3 (Factor Graphs), DecInfer-01 et 03 (Utilite), DecInfer-04 (Attributs multiples)


Objectifs

  • Etendre les reseaux bayesiens avec noeuds de decision et d’utilite
  • Calculer la politique optimale dans un reseau de decision
  • Comprendre les arcs informationnels
  • Modeliser des decisions séquentielles

1. Des Reseaux Bayesiens aux Reseaux de Decision

Rappel : Reseaux Bayesiens

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

Avant de modeliser des reseaux de decision, nous chargeons la bibliotheque Infer.NET. Cette cellule est necessaire pour toutes les demonstrations de ce notebook.

// Installation Infer.NET
#r "nuget: Microsoft.ML.Probabilistic"
#r "nuget: Microsoft.ML.Probabilistic.Compiler"

using Microsoft.ML.Probabilistic;
using Microsoft.ML.Probabilistic.Distributions;
using Microsoft.ML.Probabilistic.Models;
using Microsoft.ML.Probabilistic.Algorithms;

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

Chargement du helper de visualisation des graphes de facteurs.

// Chargement du helper pour visualiser les factor graphs inline
#load "../../Infer/FactorGraphHelper.cs"

// Verification de la disponibilite de Graphviz
if (FactorGraphHelper.IsGraphvizAvailable())
    Console.WriteLine("Graphviz disponible - les factor graphs seront affiches en SVG inline");
else
    Console.WriteLine("Graphviz non installe - les factor graphs seront disponibles en fichiers .gv");
Graphviz disponible - les factor graphs seront affiches en SVG inline

2. Types de Noeuds

Les reseaux de decision utilisent une notation graphique standardisee (Howard, 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.

// Representation d'un reseau de decision simple

public enum NodeType { Chance, Decision, Utility }

public class DecisionNode
{
    public string Name { get; set; }
    public NodeType Type { get; set; }
    public List<string> Parents { get; set; } = new List<string>();
    
    public string ToSymbol()
    {
        return Type switch
        {
            NodeType.Chance => $"[{Name}]",
            NodeType.Decision => $"<{Name}>",
            NodeType.Utility => $"(({Name}))",
            _ => Name
        };
    }
}

// Exemple : Decision medicale
var network = new List<DecisionNode>
{
    new DecisionNode { Name = "Maladie", Type = NodeType.Chance },
    new DecisionNode { Name = "Test", Type = NodeType.Chance, Parents = { "Maladie" } },
    new DecisionNode { Name = "Traiter", Type = NodeType.Decision, Parents = { "Test" } },
    new DecisionNode { Name = "Sante", Type = NodeType.Chance, Parents = { "Maladie", "Traiter" } },
    new DecisionNode { Name = "Utilite", Type = NodeType.Utility, Parents = { "Sante", "Traiter" } },
};

Console.WriteLine("Reseau de decision : Diagnostic Medical\n");
foreach (var node in network)
{
    string parents = node.Parents.Any() ? $" <- {string.Join(", ", node.Parents)}" : "";
    Console.WriteLine($"  {node.ToSymbol()}{parents}");
}
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.

Exemple

[Résultat Test] ----> <Traitement>

Signifie : L’agent connait le résultat du test avant de decider du traitement.

Absence d’arc = L’agent ne connait pas la valeur de la variable.

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

No-forgetting assumption

Dans les decisions séquentielles, on suppose généralement que l’agent n’oublie pas : - Toutes les observations passees restent accessibles - Toutes les decisions passees sont connues

Cette hypothese simplifie l’analyse mais n’est pas toujours realiste (memoire limitee, cout de stockage).

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 (DecInfer-06).

La cellule suivante illustre concretement l’impact des arcs informationnels en comparant deux scénarios : decision sans information (a priori) et decision avec information (après observation du test).

// Impact des arcs informationnels

Console.WriteLine("Scenarios d'information :\n");

// Prior sur la maladie
double pMaladie = 0.1;
double sensibilite = 0.95; // P(test+|malade)
double specificite = 0.90; // P(test-|sain)

// Utilites
double U_traiter_malade = 90;    // Guerison
double U_traiter_sain = 70;      // Effets secondaires inutiles
double U_pasTraiter_malade = 10; // Maladie non traitee
double U_pasTraiter_sain = 100;  // Parfait

// Scenario 1 : Pas d'information (decision a priori)
double EU_traiter_apriori = pMaladie * U_traiter_malade + (1 - pMaladie) * U_traiter_sain;
double EU_pasTraiter_apriori = pMaladie * U_pasTraiter_malade + (1 - pMaladie) * U_pasTraiter_sain;

Console.WriteLine("Scenario 1 : Decision SANS information (pas de test)");
Console.WriteLine($"  P(malade) = {pMaladie:P0}");
Console.WriteLine($"  E[U(traiter)] = {EU_traiter_apriori:F1}");
Console.WriteLine($"  E[U(pas traiter)] = {EU_pasTraiter_apriori:F1}");
Console.WriteLine($"  => Decision : {(EU_traiter_apriori > EU_pasTraiter_apriori ? "TRAITER" : "PAS TRAITER")}\n");

// Scenario 2 : Decision AVEC information (apres test)
// Calculer P(malade|test+) et P(malade|test-) par Bayes
double pTestPositif = pMaladie * sensibilite + (1 - pMaladie) * (1 - specificite);
double pMaladeSachantTestPositif = (pMaladie * sensibilite) / pTestPositif;
double pMaladeSachantTestNegatif = (pMaladie * (1 - sensibilite)) / (1 - pTestPositif);

Console.WriteLine("Scenario 2 : Decision AVEC information (apres test)");
Console.WriteLine($"  P(test+) = {pTestPositif:P1}");
Console.WriteLine($"  P(malade|test+) = {pMaladeSachantTestPositif:P1}");
Console.WriteLine($"  P(malade|test-) = {pMaladeSachantTestNegatif:P1}");

// Decision si test positif
double EU_traiter_testPos = pMaladeSachantTestPositif * U_traiter_malade + (1 - pMaladeSachantTestPositif) * U_traiter_sain;
double EU_pasTraiter_testPos = pMaladeSachantTestPositif * U_pasTraiter_malade + (1 - pMaladeSachantTestPositif) * U_pasTraiter_sain;

Console.WriteLine($"  Si test+ : traiter={EU_traiter_testPos:F1}, pas traiter={EU_pasTraiter_testPos:F1} => {(EU_traiter_testPos > EU_pasTraiter_testPos ? "TRAITER" : "PAS TRAITER")}");

// Decision si test negatif
double EU_traiter_testNeg = pMaladeSachantTestNegatif * U_traiter_malade + (1 - pMaladeSachantTestNegatif) * U_traiter_sain;
double EU_pasTraiter_testNeg = pMaladeSachantTestNegatif * U_pasTraiter_malade + (1 - pMaladeSachantTestNegatif) * U_pasTraiter_sain;

Console.WriteLine($"  Si test- : traiter={EU_traiter_testNeg:F1}, pas traiter={EU_pasTraiter_testNeg:F1} => {(EU_traiter_testNeg > EU_pasTraiter_testNeg ? "TRAITER" : "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

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 ?

  1. A priori : P(malade) = 10% est faible. Le cout des effets secondaires (U=70 vs 100) domine.

  2. 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.

  3. 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). L’idee est de resoudre le problème de la fin vers le debut.

Algorithme pas a pas

Étape 0 : Preparation - Ordonner les noeuds de decision temporellement : D1 → D2 → … → Dn - Identifier les parents informationnels de chaque decision

É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] - Stocker cette action optimale et sa valeur

Étape 2 : Remonter (Dn-1, Dn-2, …) - Pour chaque decision précédente : - Utiliser les valeurs optimales des decisions suivantes - Repeter le processus de maximisation

Étape 3 : Première decision (D1) - La dernière itération donne la decision optimale initiale - La politique complete est l’ensemble des decisions conditionnelles

Complexite

Nombre de decisions Complexite Commentaire
1 O(|A| × |S|) A = actions, S = etats
n séquentielles O(|A|^n × |S|^n) Exponentielle en n
Avec info parfaite O(|S| × |A|) Lineaire car decision par etat

Pourquoi “backward” ?

On commence par la fin car l’utilite finale depend des decisions futures. En resolvant d’abord les decisions futures, on connait leur contribution a l’utilite esperee, ce qui permet de resoudre les decisions anterieures.

La cellule suivante implemente une classe SimpleDecisionNetwork qui resout le problème de diagnostic medical par backward induction. Elle calcule la politique optimale (quelle action prendre pour chaque observation) et l’utilite esperee globale.

// Implementation d'un solveur de reseau de decision simple

public class SimpleDecisionNetwork
{
    // Noeud chance : Maladie (M)
    public double P_M { get; set; } = 0.1; // P(malade)
    
    // Noeud chance : Test (T) | M
    public double P_TposGivenM { get; set; } = 0.95;  // Sensibilite
    public double P_TposGivenNotM { get; set; } = 0.10; // 1-Specificite
    
    // Noeud decision : Traiter (D) avec parent informationnel T
    // Options : D=true (traiter), D=false (pas traiter)
    
    // Noeud utilite : U(M, D)
    public double U_traiter_malade { get; set; } = 90;
    public double U_traiter_sain { get; set; } = 70;
    public double U_pasTraiter_malade { get; set; } = 10;
    public double U_pasTraiter_sain { get; set; } = 100;
    
    public double GetUtility(bool malade, bool traiter)
    {
        if (traiter && malade) return U_traiter_malade;
        if (traiter && !malade) return U_traiter_sain;
        if (!traiter && malade) return U_pasTraiter_malade;
        return U_pasTraiter_sain;
    }
    
    public (Dictionary<bool, bool> policy, double expectedUtility) Solve()
    {
        // Politique : Test result -> Decision
        var policy = new Dictionary<bool, bool>();
        
        // P(T+)
        double pTpos = P_M * P_TposGivenM + (1 - P_M) * P_TposGivenNotM;
        double pTneg = 1 - pTpos;
        
        // Pour T+ : calculer decision optimale
        double pM_givenTpos = (P_M * P_TposGivenM) / pTpos;
        double EU_traiter_Tpos = pM_givenTpos * U_traiter_malade + (1 - pM_givenTpos) * U_traiter_sain;
        double EU_pasTraiter_Tpos = pM_givenTpos * U_pasTraiter_malade + (1 - pM_givenTpos) * U_pasTraiter_sain;
        policy[true] = EU_traiter_Tpos > EU_pasTraiter_Tpos;
        double bestU_Tpos = Math.Max(EU_traiter_Tpos, EU_pasTraiter_Tpos);
        
        // Pour T- : calculer decision optimale
        double pM_givenTneg = (P_M * (1 - P_TposGivenM)) / pTneg;
        double EU_traiter_Tneg = pM_givenTneg * U_traiter_malade + (1 - pM_givenTneg) * U_traiter_sain;
        double EU_pasTraiter_Tneg = pM_givenTneg * U_pasTraiter_malade + (1 - pM_givenTneg) * U_pasTraiter_sain;
        policy[false] = EU_traiter_Tneg > EU_pasTraiter_Tneg;
        double bestU_Tneg = Math.Max(EU_traiter_Tneg, EU_pasTraiter_Tneg);
        
        // Utilite esperee totale
        double totalEU = pTpos * bestU_Tpos + pTneg * bestU_Tneg;
        
        return (policy, totalEU);
    }
}

var dn = new SimpleDecisionNetwork();
var (policy, eu) = dn.Solve();

Console.WriteLine("=== Resolution du Reseau de Decision ===");
Console.WriteLine();
Console.WriteLine("Politique optimale :");
Console.WriteLine($"  Si test POSITIF : {(policy[true] ? "TRAITER" : "PAS TRAITER")}");
Console.WriteLine($"  Si test NEGATIF : {(policy[false] ? "TRAITER" : "PAS TRAITER")}");
Console.WriteLine();
Console.WriteLine($"Utilite esperee : {eu:F2}");
=== Resolution du Reseau de Decision ===

Politique optimale :
  Si test POSITIF : TRAITER
  Si test NEGATIF : PAS TRAITER

Utilite esperee : 95,90

Interpretation de la politique optimale

La classe SimpleDecisionNetwork implemente l’algorithme de backward induction de maniere explicite. Le résultat est une politique conditionnelle :

Observation P(malade|obs) Action optimale E[U|obs, action*]
Test positif 51.4% TRAITER 80.3
Test negatif 0.6% PAS TRAITER 99.4

L’utilite esperee globale de 95.90 est calculee comme :

\[E[U] = P(\text{test}+) \times E[U|\text{test}+, \text{action}^*] + P(\text{test}-) \times E[U|\text{test}-, \text{action}^*]\]

\[E[U] = 0.185 \times 80.3 + 0.815 \times 99.4 = 95.90\]

Remarque technique : 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 - un concept que nous approfondirons dans le notebook suivant.

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.

[Marche] -----> [Étude] -----> <Investir>
    |                              |
    +----------------------------> ((Profit))

La cellule suivante applique les mêmes principes a un problème d’investissement. L’investisseur peut commander une étude de marche (couteuse) avant de decider s’il investit. Le code compare les deux stratégies : decision directe vs decision informee.

// Reseau de decision : Investissement

// Marche (M) : Favorable (F) ou Defavorable (D)
double pFavorable = 0.4;

// Etude de marche (E) : Positive ou Negative
// Qualite de l'etude
double pEposGivenF = 0.8;  // Detecte correctement marche favorable
double pEposGivenD = 0.3;  // Faux positif

// Profits selon marche et decision
double profit_investir_favorable = 500000;    // Gros profit
double profit_investir_defavorable = -200000; // Grosse perte
double profit_pasInvestir = 0;                // Statu quo
double cout_etude = 20000;                    // Cout de l'etude

// Calculer la politique optimale
Console.WriteLine("=== Decision d'Investissement ===");
Console.WriteLine($"P(marche favorable) = {pFavorable:P0}");
Console.WriteLine($"Cout de l'etude = {cout_etude:N0} EUR\n");

// Option 1 : Pas d'etude, decision directe
double EU_investir_direct = pFavorable * profit_investir_favorable + 
                            (1 - pFavorable) * profit_investir_defavorable;
double EU_pasInvestir_direct = profit_pasInvestir;
double bestEU_sansEtude = Math.Max(EU_investir_direct, EU_pasInvestir_direct);

Console.WriteLine("Option 1 : Decision directe (sans etude)");
Console.WriteLine($"  E[Profit(investir)] = {EU_investir_direct:N0} EUR");
Console.WriteLine($"  E[Profit(pas investir)] = {EU_pasInvestir_direct:N0} EUR");
Console.WriteLine($"  => Decision : {(EU_investir_direct > EU_pasInvestir_direct ? "INVESTIR" : "NE PAS INVESTIR")}");
Console.WriteLine($"  => E[Profit] = {bestEU_sansEtude:N0} EUR\n");

// Option 2 : Avec etude
double pEpos = pFavorable * pEposGivenF + (1 - pFavorable) * pEposGivenD;
double pEneg = 1 - pEpos;

// Si etude positive
double pF_givenEpos = (pFavorable * pEposGivenF) / pEpos;
double EU_investir_Epos = pF_givenEpos * profit_investir_favorable + 
                          (1 - pF_givenEpos) * profit_investir_defavorable;
bool investir_siEpos = EU_investir_Epos > profit_pasInvestir;
double bestEU_Epos = Math.Max(EU_investir_Epos, profit_pasInvestir);

// Si etude negative
double pF_givenEneg = (pFavorable * (1 - pEposGivenF)) / pEneg;
double EU_investir_Eneg = pF_givenEneg * profit_investir_favorable + 
                          (1 - pF_givenEneg) * profit_investir_defavorable;
bool investir_siEneg = EU_investir_Eneg > profit_pasInvestir;
double bestEU_Eneg = Math.Max(EU_investir_Eneg, profit_pasInvestir);

double EU_avecEtude = pEpos * bestEU_Epos + pEneg * bestEU_Eneg - cout_etude;

Console.WriteLine("Option 2 : Avec etude de marche");
Console.WriteLine($"  P(etude positive) = {pEpos:P1}");
Console.WriteLine($"  Si E+ : P(favorable|E+) = {pF_givenEpos:P1} => {(investir_siEpos ? "INVESTIR" : "NE PAS INVESTIR")}");
Console.WriteLine($"  Si E- : P(favorable|E-) = {pF_givenEneg:P1} => {(investir_siEneg ? "INVESTIR" : "NE PAS INVESTIR")}");
Console.WriteLine($"  => E[Profit avec etude] = {EU_avecEtude:N0} EUR\n");

Console.WriteLine("=== Comparaison ===");
Console.WriteLine($"Sans etude : {bestEU_sansEtude:N0} EUR");
Console.WriteLine($"Avec etude : {EU_avecEtude:N0} EUR");
Console.WriteLine($"=> Valeur de l'etude : {EU_avecEtude - bestEU_sansEtude:N0} EUR");
Console.WriteLine($"=> Decision : {(EU_avecEtude > bestEU_sansEtude ? "COMMANDER L'ETUDE" : "DECIDER DIRECTEMENT")}");
=== 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

Analyse de la decision d’investissement

Les résultats revelent plusieurs phenomenes interessants :

Comparaison des stratégies

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

Decomposition de la valeur de l’étude

La valeur de l’étude (24 000 EUR) se decompose ainsi :

  1. Gain brut d’information : L’étude permet d’eviter d’investir quand le signal est negatif

    • Sans étude : on investit toujours (EU = 80 000 EUR)
    • Avec E- (50% des cas) : on n’investit pas, evitant une perte esperee
  2. Cout de l’étude : -20 000 EUR

  3. 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.

Conditions pour que l’information ait de la valeur

L’information a de la valeur si et seulement si elle peut changer la decision optimale. Dans cet exemple : - E+ confirme l’investissement (64% favorable) - E- dissuade l’investissement (16% favorable)

La qualite de l’étude (sensibilite/specificite) determine a quel point les posteriors divergent du prior.

Exercice 1 : Reseau de decision pour un lancement de produit

Une startup envisage de lancer un nouveau produit. Le marche peut etre porteur ou sature. Avant de decider, elle peut commander une étude de marché couteuse.

Structure du reseau :

[Marche] ----> [Étude] ----> <Lancer?> ----> ((Profit))
     |                          ^
     +--------------------------+

Paramètres : - P(marche porteur) = 0.35 - Étude : sensibilite = 0.75, specificite = 0.70 - Profits : lancer sur marche porteur = 300 000 EUR, lancer sur marche sature = -150 000 EUR, ne pas lancer = 0 EUR - Cout de l’étude = 25 000 EUR

Étapes : 1. Calculez les posteriors P(porteur|étude+) et P(porteur|étude-) par theoreme de Bayes 2. Pour chaque résultat d’étude, determinez s’il faut lancer ou non (comparez E[U] de chaque action) 3. Calculez l’utilite esperee de la stratégie “commander l’étude” 4. Calculez l’utilite esperee de la stratégie “decider sans étude” 5. (Indice) : A quel seuil de P(marche porteur) l’étude perd-elle toute valeur decisionnelle ?

Cet exercice reprend le schema de la section 5 avec des paramètres différents. Il vous permet de pratiquer la backward induction sur un problème de type “option reelle”.

// Exercice : Reseau de decision pour un lancement de produit

// Parametres
double pPorteur = 0.35;              // P(marche porteur)
double sensEtude = 0.75;             // P(etude+|porteur)
double specEtude = 0.70;             // P(etude-|sature)
double profit_lancer_porteur = 300000;
double profit_lancer_sature = -150000;
double profit_pasLancer = 0;
double coutEtude = 25000;

// TODO 1 : Calculez les posteriors P(porteur|etude+) et P(porteur|etude-)
// Indice : P(etude+) = pPorteur * sensEtude + (1 - pPorteur) * (1 - specEtude)
// Puis P(porteur|etude+) = (pPorteur * sensEtude) / P(etude+)

// TODO 2 : Pour chaque resultat d'etude, determinez s'il faut lancer
// Calculez E[Profit(lancer)] et E[Profit(pas lancer)] pour chaque cas
// Indice : E[Profit(lancer|obs)] = P(porteur|obs) * profit_lancer_porteur + (1 - P(porteur|obs)) * profit_lancer_sature

// TODO 3 : Calculez l'utilite esperee de la strategie "commander l'etude"
// E[Profit avec etude] = P(etude+) * bestProfit(etude+) + P(etude-) * bestProfit(etude-) - coutEtude

// TODO 4 : Calculez l'utilite esperee de la strategie "decider sans etude"
// E[Profit sans etude] = max(E[Profit(lancer)], E[Profit(pas lancer)]) avec prior

// TODO 5 : (Bonus) Trouvez le seuil de P(porteur) ou l'etude n'a plus de valeur
// Indice : a quel prior l'agent prendrait-il la meme decision quel que soit le resultat de l'etude ?

Console.WriteLine("Exercice a completer");
Exercice a completer

L’exemple précédent illustrait une situation avec une seule decision après observation. Dans de nombreux problemes reels, nous devons prendre plusieurs decisions dans le temps, chacune pouvant reveler de l’information utile pour les suivantes.

6. Decisions Séquentielles

Ordre des decisions

Quand il y a plusieurs noeuds de decision, leur ordre temporel doit etre specifie.

Exemple : Test puis Traitement

<Faire Test?> ----> [Résultat] ----> <Traiter?> ----> ((Utilite))
                                          ^
                                          |
[Maladie] ---------------------------------+

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)

La cellule suivante implemente une decision séquentielle a deux étapes : d’abord decider si on fait le test, puis decider si on traite. L’algorithme de backward induction resout d’abord la deuxieme decision, puis remonte a la première.

// Decision sequentielle : Faire test puis traiter

// Parametres
double pMaladie_seq = 0.2;
double sensibilite_seq = 0.90;
double specificite_seq = 0.85;
double cout_test = 50;

// Utilites (sans cout test)
double U_traiterMalade = 100;
double U_traiterSain = 60;
double U_pasTraiterMalade = 20;
double U_pasTraiterSain = 100;

// Resolution par backward induction
Console.WriteLine("=== Decision Sequentielle : Test puis Traitement ===\n");

// D'abord : calculer politique optimale pour D2 (traiter) sachant test fait
double pTpos_seq = pMaladie_seq * sensibilite_seq + (1 - pMaladie_seq) * (1 - specificite_seq);

double pM_Tpos = (pMaladie_seq * sensibilite_seq) / pTpos_seq;
double EU_traiter_Tpos = pM_Tpos * U_traiterMalade + (1 - pM_Tpos) * U_traiterSain;
double EU_pasTraiter_Tpos = pM_Tpos * U_pasTraiterMalade + (1 - pM_Tpos) * U_pasTraiterSain;
double bestU_Tpos = Math.Max(EU_traiter_Tpos, EU_pasTraiter_Tpos) - cout_test;

double pM_Tneg = (pMaladie_seq * (1 - sensibilite_seq)) / (1 - pTpos_seq);
double EU_traiter_Tneg = pM_Tneg * U_traiterMalade + (1 - pM_Tneg) * U_traiterSain;
double EU_pasTraiter_Tneg = pM_Tneg * U_pasTraiterMalade + (1 - pM_Tneg) * U_pasTraiterSain;
double bestU_Tneg = Math.Max(EU_traiter_Tneg, EU_pasTraiter_Tneg) - cout_test;

double EU_faireTest = pTpos_seq * bestU_Tpos + (1 - pTpos_seq) * bestU_Tneg;

// Si pas de test : decision a priori
double EU_traiter_apriori = pMaladie_seq * U_traiterMalade + (1 - pMaladie_seq) * U_traiterSain;
double EU_pasTraiter_apriori = pMaladie_seq * U_pasTraiterMalade + (1 - pMaladie_seq) * U_pasTraiterSain;
double EU_pasTest = Math.Max(EU_traiter_apriori, EU_pasTraiter_apriori);

Console.WriteLine("Etape 2 : Politique de traitement si test fait");
Console.WriteLine($"  Si T+ : P(M|T+)={pM_Tpos:P1} => {(EU_traiter_Tpos > EU_pasTraiter_Tpos ? "Traiter" : "Pas traiter")}");
Console.WriteLine($"  Si T- : P(M|T-)={pM_Tneg:P1} => {(EU_traiter_Tneg > EU_pasTraiter_Tneg ? "Traiter" : "Pas traiter")}\n");

Console.WriteLine("Etape 1 : Decision de faire le test");
Console.WriteLine($"  E[U | faire test] = {EU_faireTest:F1}");
Console.WriteLine($"  E[U | pas test] = {EU_pasTest:F1} ({(EU_traiter_apriori > EU_pasTraiter_apriori ? "traiter" : "pas traiter")})\n");

Console.WriteLine("=== Politique Optimale Globale ===");
Console.WriteLine($"D1 : {(EU_faireTest > EU_pasTest ? "FAIRE LE TEST" : "NE PAS FAIRE LE TEST")}");
if (EU_faireTest > EU_pasTest)
{
    Console.WriteLine($"D2 : Si T+ => {(EU_traiter_Tpos > EU_pasTraiter_Tpos ? "TRAITER" : "PAS TRAITER")}");
    Console.WriteLine($"     Si T- => {(EU_traiter_Tneg > EU_pasTraiter_Tneg ? "TRAITER" : "PAS TRAITER")}");
}
else
{
    Console.WriteLine($"D2 : {(EU_traiter_apriori > EU_pasTraiter_apriori ? "TRAITER" : "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.

Pourquoi ne pas faire le test ?

Facteur Valeur Impact
Prior P(maladie) 20% Relativement faible
Cout du test 50 points Penalise chaque branche
Utilite “pas traiter sain” 100 Très attractive
Decision a priori optimale Pas traiter EU = 84.0
Decision avec test Conditionnelle EU = 43.6 (après cout)

Le cout du test (50 points) est applique dans les deux branches (T+ et T-), ce qui reduit significativement l’utilite esperee.

Decomposition du calcul

\[E[U|\text{faire test}] = P(T+) \times (E[U^*|T+] - 50) + P(T-) \times (E[U^*|T-] - 50)\]

Le cout fixe de 50 points fait basculer la decision vers “ne pas tester”.

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.

Quand le test deviendrait-il rentable ?

Le test serait rentable si : - Son cout etait inferieur a environ 10 points, OU - Le prior P(maladie) etait plus eleve (ex: 40%), OU
- L’ecart d’utilite “traiter malade vs pas traiter malade” etait plus grand

Exercice 2 : Decision séquentielle - Maintenance preventive d’une machine

Dans cette usine, une machine critique peut etre en bon etat ou defaillant. Le responsable maintenance doit prendre deux decisions séquentielles :

  1. D1 : Faire ou non une inspection (cout = 30 points d’utilite)
  2. D2 : Effectuer ou non la maintenance preventive (cout = 100 points si effectuee)

Structure du reseau de decision :

[Etat Machine] ----> [Résultat Inspection] ----> <Faire Maintenance?> ----> ((Utilite))
       |                     ^                         ^
       +---------------------+-------------------------+

Paramètres : - P(machine defaillante) = 0.25 - Inspection : sensibilite = 0.85, specificite = 0.80 - Utilites (hors couts) : maintenance sur machine defaillante = 150, maintenance sur machine saine = 50, pas de maintenance sur machine defaillante = -200 (arret de production), pas de maintenance sur machine saine = 120

Étapes : 1. Calculez la politique optimale de D2 (maintenance) conditionnellement au résultat de l’inspection 2. Calculez l’utilite esperee de la branche “faire inspection” en integrant le cout 3. Calculez l’utilite esperee de la branche “pas d’inspection” (decision a priori) 4. Determinez la politique optimale globale (D1 + D2) 5. (Indice) : Quel seuil de cout d’inspection rend l’inspection non rentable ? Testez avec cout_inspection

// Exercice : Decision sequentielle - Maintenance preventive d'une machine

// Parametres
double pDefaillant = 0.25;        // P(machine defaillante)
double sensibilite_insp = 0.85;   // P(inspection+|defaillant)
double specificite_insp = 0.80;   // P(inspection-|saine)
double cout_inspection = 30;
double cout_maintenance = 100;

// Utilites (hors couts inspection et maintenance)
double U_maint_defaillant = 150;   // Maintenance sur machine defaillante
double U_maint_sain = 50;          // Maintenance sur machine saine
double U_pasMaint_defaillant = -200; // Pas de maintenance sur defaillant
double U_pasMaint_sain = 120;       // Pas de maintenance sur machine saine

// TODO 1 : Backward induction - Etape 2 (D2 : maintenance)
// Calculez P(defaillant|inspection+) et P(defaillant|inspection-)
// Puis calculez E[U(maintenance)] et E[U(pas maintenance)] pour chaque cas
// Indice : utilisez le theoreme de Bayes comme dans l'exemple precedent

// TODO 2 : Calculez E[U de la branche "faire inspection"]
// E[U|faire insp] = P(insp+) * bestU(insp+) + P(insp-) * bestU(insp-) - cout_inspection
// N'oubliez pas de soustraire le cout de l'inspection ET le cout de la maintenance si effectuee

// TODO 3 : Calculez E[U de la branche "pas d'inspection"]
// Decision a priori : maintenance ou pas ? (sans information)
// Indice : comparez E[U(maintenir)] et E[U(pas maintenir)] avec le prior P(defaillant)

// TODO 4 : Affichez la politique optimale globale (D1 et D2)
// D1 : faire inspection ou pas ?
// D2 : quelle action conditionnellement au resultat de l'inspection ?

// TODO 5 : (Bonus) Trouvez le cout d'inspection seuil
// Au-delaà de quel cout l'inspection n'est plus rentable ?
// Indice : resolvez E[U|faire insp] = E[U|pas insp] pour cout_inspection

Console.WriteLine("Exercice a completer");
Exercice a completer

Approche pratique avec Infer.NET

Dans la cellule suivante, nous allons implementer un reseau de decision complet en utilisant Infer.NET pour l’inference bayesienne :

  1. Modelisation : Variables maladie et testPositif avec leurs dependances
  2. Enumeration : Pour chaque valeur possible du test (positif/negatif)
  3. Inference : Calcul de P(maladie|test) via engine.Infer<Bernoulli>()
  4. Decision : Sélection de l’action maximisant E[U]

Point technique : Infer.NET n’a pas de noeuds de decision natifs. Nous simulons la resolution en enumerant manuellement les observations possibles et en calculant l’utilite esperee pour chaque action.

7. Implementation avec Infer.NET

Infer.NET n’a pas de support natif pour les noeuds de decision, mais nous pouvons :

  1. Modeliser les variables de chance
  2. Enumerer les decisions
  3. Calculer E[U] pour chaque configuration
// Reseau de decision avec Infer.NET

// Probleme : Diagnostic et traitement

// Variables de chance
Variable<bool> maladie = Variable.Bernoulli(0.15).Named("maladie");

// Test (conditionnel a la maladie)
Variable<bool> testPositif = Variable.New<bool>().Named("test");
using (Variable.If(maladie))
    testPositif.SetTo(Variable.Bernoulli(0.92)); // Sensibilite
using (Variable.IfNot(maladie))
    testPositif.SetTo(Variable.Bernoulli(0.12)); // 1-Specificite

// Inference
InferenceEngine engine = new InferenceEngine();
engine.Compiler.CompilerChoice = Microsoft.ML.Probabilistic.Compiler.CompilerChoice.Roslyn;

// Enumerer les observations possibles et calculer E[U] pour chaque decision
var utilites = new Dictionary<string, double>
{
    {"traiter_malade", 85},
    {"traiter_sain", 65},
    {"pasTraiter_malade", 15},
    {"pasTraiter_sain", 100}
};

Console.WriteLine("=== Reseau de Decision avec Infer.NET ===\n");

foreach (bool obsTest in new[] { true, false })
{
    // Observer le test
    testPositif.ObservedValue = obsTest;
    
    // Inferrer P(maladie|test)
    Bernoulli posteriorMaladie = engine.Infer<Bernoulli>(maladie);
    double pM = posteriorMaladie.GetProbTrue();
    
    // Calculer E[U] pour chaque decision
    double EU_traiter = pM * utilites["traiter_malade"] + (1 - pM) * utilites["traiter_sain"];
    double EU_pasTraiter = pM * utilites["pasTraiter_malade"] + (1 - pM) * utilites["pasTraiter_sain"];
    
    string decision = EU_traiter > EU_pasTraiter ? "TRAITER" : "PAS TRAITER";
    
    Console.WriteLine($"Observation : test = {(obsTest ? "POSITIF" : "NEGATIF")}");
    Console.WriteLine($"  P(maladie|test) = {pM:P1}");
    Console.WriteLine($"  E[U(traiter)] = {EU_traiter:F1}");
    Console.WriteLine($"  E[U(pas traiter)] = {EU_pasTraiter:F1}");
    Console.WriteLine($"  => Decision optimale : {decision}\n");
    
    // Nettoyer l'observation pour la prochaine iteration
    testPositif.ClearObservedValue();
}
=== Reseau de Decision avec Infer.NET ===

Compiling model...done.
Observation : test = POSITIF
  P(maladie|test) = 57,5 %
  E[U(traiter)] = 76,5
  E[U(pas traiter)] = 51,1
  => Decision optimale : TRAITER

Compiling model...done.
Observation : test = NEGATIF
  P(maladie|test) = 1,6 %
  E[U(traiter)] = 65,3
  E[U(pas traiter)] = 98,7
  => Decision optimale : PAS TRAITER

Interpretation des résultats Infer.NET

L’implementation ci-dessus montre comment utiliser Infer.NET pour les reseaux de decision :

Stratégie d’implementation

  1. Modeliser les variables de chance avec Infer.NET (ici : maladie, testPositif)
  2. Observer chaque configuration possible des parents informationnels
  3. Inferer les posteriors avec engine.Infer<Bernoulli>()
  4. Calculer E[U] pour chaque action et sélectionner la meilleure

Résultats obtenus

Test observe P(maladie|test) E[U(traiter)] E[U(pas traiter)] Decision
POSITIF 57.5% 76.5 51.1 TRAITER
NEGATIF 1.6% 65.3 98.7 PAS TRAITER

Note technique : Le message “Compiling model…done.” apparait car Infer.NET compile le modèle en code C# optimise lors de la première inference. Les inferences suivantes reutilisent ce code compile.

Avantages de l’approche Infer.NET

Avantage Description
Inference exacte Pas d’approximation pour les modèles discrets
Modèles complexes Supporte des structures arbitrairement complexes
Observations partielles Gere naturellement les données manquantes
Apprentissage Peut apprendre les paramètres a partir de données

Limitations

Infer.NET ne fournit pas de support natif pour les noeuds de decision. L’enumeration des decisions doit etre faite manuellement, ce qui peut devenir couteux pour de nombreuses decisions.

Visualisation du Factor Graph : Reseau de Diagnostic

Le modèle Infer.NET ci-dessus peut etre visualise sous forme de factor graph. Cette representation montre :

  • Variables (noeuds ronds) : maladie, test
  • Facteurs (noeuds carres) : prior Bernoulli, likelihood conditionnelle

La cellule suivante genere et affiche le factor graph si Graphviz est disponible.

// Generation et affichage du factor graph pour le reseau de diagnostic
// Le modele Maladie -> Test est un exemple classique de reseau bayesien a deux noeuds

Variable<bool> maladieDN = Variable.Bernoulli(0.15).Named("Maladie");
Variable<bool> testDN = Variable.New<bool>().Named("TestResult");

using (Variable.If(maladieDN))
    testDN.SetTo(Variable.Bernoulli(0.92)); // P(test+|malade) = sensibilite
using (Variable.IfNot(maladieDN))
    testDN.SetTo(Variable.Bernoulli(0.12)); // P(test+|sain) = 1 - specificite

// Configurer le moteur pour generer le factor graph
InferenceEngine engineDN = new InferenceEngine();
engineDN.Compiler.CompilerChoice = Microsoft.ML.Probabilistic.Compiler.CompilerChoice.Roslyn;
engineDN.ShowFactorGraph = true;

// Executer l'inference (genere le fichier .gv)
var postDN = engineDN.Infer<Bernoulli>(maladieDN);
Console.WriteLine($"Prior : P(Maladie) = {postDN.GetProbTrue():P1}");
Console.WriteLine();

// Afficher le factor graph inline si disponible
FactorGraphHelper.GetLatestFactorGraphHtml(700).DisplayAs("text/html");
Compiling model...done.
Prior : P(Maladie) = 15,0 %
Model_09_23_26_09_16_09_35.svg
Model node0 Bernoulli(0,15) node1 Random node0->node1 dist node2 Maladie node1->node2 node5 TestResult node2->node5 condition node3 Bernoulli(0,92) node4 Random node3->node4 dist node4->node5 node6 Bernoulli(0,12) node7 Random node6->node7 dist node7->node5

Interpretation du résultat de visualisation

Sortie obtenue : P(Maladie) = Bernoulli(0,15)

Paramètre Valeur Signification
Distribution Bernoulli Variable binaire (malade/sain)
Probabilite 0.15 Prior inchange car aucune observation

Note : Sans observation du test, le posterior est identique au prior. Le factor graph montre les connexions entre variables, pas les valeurs. L’inference calcule les marginales en propageant les messages le long de ces connexions.

Maintenant que nous avons vu comment calculer des politiques optimales avec Infer.NET, examinons comment visualiser la structure interne du modèle. Cette visualisation est particulierement utile pour :

  • Verifier que le modèle correspond bien a notre intention
  • Identifier les dependances implicites
  • Comprendre pourquoi l’inference peut etre lente sur certains modèles

7.1 Visualisation du Factor Graph

Infer.NET peut generer le graphe de facteurs du modèle. C’est utile pour : - Verifier la structure du modèle - Comprendre les dependances - Debugger les erreurs d’inference

// Visualisation du graphe de facteurs du modele de diagnostic

// Recreer le modele (nouvelle instance pour generation du graphe)
Variable<bool> maladieFG = Variable.Bernoulli(0.15).Named("Maladie");
Variable<bool> testFG = Variable.New<bool>().Named("TestResult");

using (Variable.If(maladieFG))
    testFG.SetTo(Variable.Bernoulli(0.92));
using (Variable.IfNot(maladieFG))
    testFG.SetTo(Variable.Bernoulli(0.12));

// Activer la generation du graphe de facteurs
InferenceEngine engineFG = new InferenceEngine();
engineFG.Compiler.CompilerChoice = Microsoft.ML.Probabilistic.Compiler.CompilerChoice.Roslyn;
engineFG.ShowFactorGraph = true;  // Active la generation du graphe

Console.WriteLine("=== Generation du Factor Graph ===\n");
Console.WriteLine("Le graphe de facteurs sera genere lors de l'inference.");
Console.WriteLine("Sur un environnement avec Graphviz installe, un fichier .dgml sera cree.\n");

// Effectuer l'inference (declenche la generation du graphe)
try 
{
    var posterior = engineFG.Infer<Bernoulli>(maladieFG);
    Console.WriteLine($"Inference reussie : P(Maladie) = {posterior}");
    Console.WriteLine();
    Console.WriteLine("Structure du modele :");
    Console.WriteLine("  [Maladie] -----> [TestResult]");
    Console.WriteLine("      |                 |");
    Console.WriteLine("      v                 v");
    Console.WriteLine("  (Bernoulli)    (Conditionnel)");
    Console.WriteLine();
    Console.WriteLine("Dans le factor graph :");
    Console.WriteLine("  - Noeuds ronds = Variables (Maladie, TestResult)");
    Console.WriteLine("  - Noeuds carres = Facteurs (Prior, Likelihood)");
    Console.WriteLine("  - Aretes = Connexions facteur-variable");
}
catch (Exception ex)
{
    Console.WriteLine($"Note : {ex.Message}");
    Console.WriteLine("La visualisation graphique necessite Graphviz.");
}
=== Generation du Factor Graph ===

Le graphe de facteurs sera genere lors de l'inference.
Sur un environnement avec Graphviz installe, un fichier .dgml sera cree.

Compiling model...done.
Inference reussie : P(Maladie) = Bernoulli(0,15)

Structure du modele :
  [Maladie] -----> [TestResult]
      |                 |
      v                 v
  (Bernoulli)    (Conditionnel)

Dans le factor graph :
  - Noeuds ronds = Variables (Maladie, TestResult)
  - Noeuds carres = Facteurs (Prior, Likelihood)
  - Aretes = Connexions facteur-variable

Affichage du Factor Graph

Le code suivant affiche le factor graph genere par Infer.NET. Ce graphe montre :

  • Les variables aleatoires (cercles) : les noeuds du reseau de decision
  • Les facteurs (rectangles) : les distributions conditionnelles et contraintes
  • Les aretes : les dependances entre variables

Cette visualisation aide a comprendre la structure du modèle probabiliste.

// Affichage du factor graph genere par la cellule precedente
// Le graphe montre la structure : Maladie (prior) -> TestResult (likelihood)
FactorGraphHelper.GetLatestFactorGraphHtml(700).DisplayAs("text/html");
Model_09_23_26_09_16_10_55.svg
Model node0 Bernoulli(0,15) node1 Random node0->node1 dist node2 Maladie node1->node2 node5 TestResult node2->node5 condition node3 Bernoulli(0,92) node4 Random node3->node4 dist node4->node5 node6 Bernoulli(0,12) node7 Random node6->node7 dist node7->node5

Comprendre les Factor Graphs

Le graphe de facteurs (factor graph) est la representation interne utilisee par Infer.NET pour l’inference. Comprendre cette structure aide a debugger et optimiser les modèles.

Correspondance Reseau Bayesien / Factor Graph

Reseau Bayesien Factor Graph Exemple
Variable Noeud rond Maladie, TestResult
CPT P(B|A) Noeud carre (facteur) P(Test|Maladie)
Prior P(A) Facteur unaire P(Maladie) = 0.15
Arc A -> B Aretes A-facteur-B Connexion via le facteur

Algorithme d’inference (Message Passing)

Infer.NET utilise l’algorithme Expectation Propagation (EP) ou Belief Propagation (BP) :

  1. Les messages circulent le long des aretes
  2. Chaque facteur combine les messages entrants
  3. Les messages sont propages jusqu’a convergence
  4. Les marginales sont lues aux noeuds variables

Pourquoi c’est important : La structure du factor graph determine la complexite de l’inference. Un graphe avec des cycles necessites des itérations, tandis qu’un arbre permet une inference exacte en un seul passage.

Visualisation avec Graphviz

Sur un système avec Graphviz installe, ShowFactorGraph = true genere un fichier .dgml visualisable dans Visual Studio ou converti en image PNG/SVG.

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

Cet exercice demande de calculer l’utilite esperee pour deux actions (reviser / ne pas reviser) etant donne l’incertitude sur la difficulte de l’examen.

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

// Parametres
double pDifficile = 0.4; // P(examen difficile)

// Notes esperees
double note_revise_facile = 18;
double note_revise_difficile = 14;
double note_pasRevise_facile = 14;
double note_pasRevise_difficile = 8;

// Cout de revision (en points d'utilite)
double coutRevision = 2;

// TODO 1 : Calculer l'utilite esperee de "reviser"
// Formule : E[U(reviser)] = P(facile) * (note_revise_facile - coutRevision) + P(difficile) * (note_revise_difficile - coutRevision)

// TODO 2 : Calculer l'utilite esperee de "ne pas reviser"
// Formule : E[U(pas reviser)] = P(facile) * note_pasRevise_facile + P(difficile) * note_pasRevise_difficile

// TODO 3 : Comparer et afficher la decision optimale
// Indice : utilisez un if/else sur les deux utilites esperees

// TODO 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 coutRevision
Console.WriteLine("Exercice a completer");
Exercice a completer

Analyse de vos résultats

Après avoir implemente le code, verifiez :

  1. Quelle action a la plus grande utilite esperee ?
  2. Le cout seuil est-il coherent avec les paramètres du problème ?
  3. 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 illustre comment combiner Decision Networks et MAUT avec des attributs incertains.

Visualisation du Factor Graph : Modèle MAUT Multi-Attribut

Le modèle MAUT ci-dessus utilise des variables independantes pour chaque attribut de chaque site. La cellule suivante genere le factor graph montrant la structure de ce modèle a variables multiples.

// Reseau de Decision Multi-Attribut : Site d'Aeroport
// 3 sites candidats, 3 attributs incertains modelises avec Infer.NET

int nSites = 3;
string[] sites = { "Site A (cotier)", "Site B (rural)", "Site C (periurbain)" };

// Priors sur les attributs basés sur études préliminaires
// Couts (millions EUR) - incertitude sur conditions de terrain
double[] coutMean = { 500, 300, 400 };
double[] coutVar = { 10000, 5000, 8000 };

// Securite (score 0-1) - incertitude sur conditions locales
double[] secAlpha = { 8, 5, 6 };
double[] secBeta = { 2, 5, 4 };

// Nuisances (score 1-10) - incertitude sur impact reel
double[] nuisMean = { 3, 1, 5 };
double[] nuisVar = { 1, 0.5, 2 };

// Poids MAUT (determines par les parties prenantes)
double w_cout = 0.35, w_securite = 0.40, w_nuisances = 0.25;

Console.WriteLine("=== Decision Multi-Attribut avec Infer.NET : Site d'Aeroport ===\n");
Console.WriteLine($"Poids : Cout={w_cout:P0}, Securite={w_securite:P0}, Nuisances={w_nuisances:P0}\n");

// Creer le modele Infer.NET pour chaque site
var engineAeroport = new InferenceEngine();
engineAeroport.Compiler.CompilerChoice = Microsoft.ML.Probabilistic.Compiler.CompilerChoice.Roslyn;

Console.WriteLine("Distributions des attributs par site :\n");
Console.WriteLine("Site                  | Cout (MEUR)      | Securite         | Nuisances");
Console.WriteLine("----------------------|------------------|------------------|------------------");

var distributionsCout = new Gaussian[nSites];
var distributionsSec = new Beta[nSites];
var distributionsNuis = new Gaussian[nSites];

for (int s = 0; s < nSites; s++)
{
    // Creer les variables pour ce site
    Variable<double> cout_s = Variable.GaussianFromMeanAndVariance(coutMean[s], coutVar[s]).Named($"cout_{s}");
    Variable<double> sec_s = Variable.Beta(secAlpha[s], secBeta[s]).Named($"sec_{s}");
    Variable<double> nuis_s = Variable.GaussianFromMeanAndVariance(nuisMean[s], nuisVar[s]).Named($"nuis_{s}");
    
    // Inferer les distributions
    distributionsCout[s] = engineAeroport.Infer<Gaussian>(cout_s);
    distributionsSec[s] = engineAeroport.Infer<Beta>(sec_s);
    distributionsNuis[s] = engineAeroport.Infer<Gaussian>(nuis_s);
    
    Console.WriteLine($"{sites[s],-21} | {distributionsCout[s].GetMean():F0} +/- {Math.Sqrt(distributionsCout[s].GetVariance()):F0} | {distributionsSec[s].GetMean():P0} +/- {Math.Sqrt(distributionsSec[s].GetVariance()):P0} | {distributionsNuis[s].GetMean():F1} +/- {Math.Sqrt(distributionsNuis[s].GetVariance()):F1}");
}

Console.WriteLine("\n=== Calcul de l'Utilite Esperee par Site ===\n");

// Normalisation et calcul EU
double[] EU = new double[nSites];
for (int s = 0; s < nSites; s++)
{
    // Normaliser les attributs (0-1)
    double normCout = 1 - (distributionsCout[s].GetMean() - 300) / 200;  // 300-500 MEUR → 1-0
    double normSec = distributionsSec[s].GetMean();  // Deja 0-1
    double normNuis = 1 - (distributionsNuis[s].GetMean() - 1) / 9;  // 1-10 → 1-0

    EU[s] = w_cout * normCout + w_securite * normSec + w_nuisances * normNuis;
    
    Console.WriteLine($"{sites[s],-21} :");
    Console.WriteLine($"  v_cout={normCout:F3}, v_sec={normSec:F3}, v_nuis={normNuis:F3}");
    Console.WriteLine($"  => EU = {EU[s]:F3}");
}

// Decision optimale
int bestSite = EU.Select((v, i) => (v, i)).OrderByDescending(x => x.v).First().i;
Console.WriteLine($"\n=== Decision Optimale : {sites[bestSite]} (EU = {EU[bestSite]:F3}) ===");
=== Decision Multi-Attribut avec Infer.NET : Site d'Aeroport ===

Poids : Cout=35 %, Securite=40 %, Nuisances=25 %

Distributions des attributs par site :

Site                  | Cout (MEUR)      | Securite         | Nuisances
----------------------|------------------|------------------|------------------
Compiling model...done.
Compiling model...done.
Compiling model...done.
Site A (cotier)       | 500 +/- 100 | 80 % +/- 12 % | 3,0 +/- 1,0
Compiling model...done.
Compiling model...done.
Compiling model...done.
Site B (rural)        | 300 +/- 71 | 50 % +/- 15 % | 1,0 +/- 0,7
Compiling model...done.
Compiling model...done.
Compiling model...done.
Site C (periurbain)   | 400 +/- 89 | 60 % +/- 15 % | 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) ===

Interpretation de l’analyse multi-attribut

Cet exemple combine Decision Networks et MAUT (Multi-Attribute Utility Theory) pour un problème realiste de choix de site.

Résultats attendus de l’analyse

L’exécution du code ci-dessus produit :

Site Cout (normalise) Securite (normalise) Nuisances (normalise) 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

Note : Les valeurs exactes dependent de l’exécution et des distributions inferees.

Rôle de l’incertitude

Contrairement a MAUT déterministe, ici chaque attribut est incertain : - Les couts suivent une distribution gaussienne (incertitude geologique) - La securite suit une distribution Beta (incertitude sur les conditions) - Les nuisances suivent une gaussienne (incertitude environnementale)

Infer.NET calcule les esperances de ces distributions pour le scoring MAUT.

Extensions possibles

Extension Description
Analyse de sensibilite Varier les poids pour voir si la decision change
Dominance stochastique Comparer les distributions completes, pas seulement les moyennes
Information supplementaire Modeliser une étude de terrain comme source d’information
// Generation du factor graph pour le modele MAUT d'un site (Site B comme exemple)
// Ce modele montre 3 variables independantes : Cout, Securite, Nuisances

Variable<double> coutMAUT = Variable.GaussianFromMeanAndVariance(300, 5000).Named("Cout_SiteB");
Variable<double> secMAUT = Variable.Beta(5, 5).Named("Securite_SiteB");
Variable<double> nuisMAUT = Variable.GaussianFromMeanAndVariance(1, 0.5).Named("Nuisances_SiteB");

var engineMAUT = new InferenceEngine();
engineMAUT.Compiler.CompilerChoice = Microsoft.ML.Probabilistic.Compiler.CompilerChoice.Roslyn;
engineMAUT.ShowFactorGraph = true;

// Inferer pour generer le graphe
var postCout = engineMAUT.Infer<Gaussian>(coutMAUT);
var postSec = engineMAUT.Infer<Beta>(secMAUT);
var postNuis = engineMAUT.Infer<Gaussian>(nuisMAUT);

Console.WriteLine("Factor Graph du modele MAUT pour Site B :");
Console.WriteLine($"  Cout ~ Gaussian({postCout.GetMean():F0}, {Math.Sqrt(postCout.GetVariance()):F0})");
Console.WriteLine($"  Securite ~ Beta(5,5) = {postSec.GetMean():P0}");
Console.WriteLine($"  Nuisances ~ Gaussian({postNuis.GetMean():F1}, {Math.Sqrt(postNuis.GetVariance()):F1})");
Console.WriteLine();

// Afficher le factor graph
FactorGraphHelper.GetLatestFactorGraphHtml(700).DisplayAs("text/html");
Compiling model...done.
Compiling model...done.
Compiling model...done.
Factor Graph du modele MAUT pour Site B :
  Cout ~ Gaussian(300, 71)
  Securite ~ Beta(5,5) = 50 %
  Nuisances ~ Gaussian(1,0, 0,7)
Model_09_23_26_09_16_15_21.svg
Model node0 1 node1 GaussianFromMeanAndVariance node0->node1 mean node3 Nuisances_SiteB node1->node3 node2 0,5 node2->node1 variance

Exercice 3 : Reseau de decision avec Infer.NET - Assurance qualite

Une usine fabrique des composants electroniques. Chaque lot peut contenir des pieces defectueuses (variable de chance inobservable directement). L’agent de qualite peut :

  • Action A : Expedier le lot tel quel (pas de cout, mais risque de reclamations)
  • Action B : Tester une piece echantillon, puis decider d’expedier ou de trier le lot

Structure du reseau :

[Defaut Lot] ----> [Résultat Test] ----> <Expedier ou Trier?> ----> ((Cout total))
       |                    ^                      ^
       +--------------------+----------------------+

Paramètres : - P(lot defectueux) = 0.20 (20% des lots ont un taux de defaut eleve) - Test echantillon : sensibilite = 0.88, specificite = 0.75 - Couts : expedier un lot defectueux = -500 EUR (reclamations), expedier un lot sain = +200 EUR, trier le lot (quel que soit l’etat) = -50 EUR (cout fixe de tri)

Étapes : 1. Modelisez les variables de chance avec Infer.NET (Variable.Bernoulli pour le defaut, Variable.If/Variable.IfNot pour le test conditionnel) 2. Pour chaque observation du test (positif/negatif), utilisez engine.Infer<Bernoulli>() pour calculer P(defaut|test) 3. Calculez E[U] de chaque action (expedier vs trier) conditionnellement a chaque résultat de test 4. Determinez la politique optimale : que faire si test positif ? si test negatif ? 5. (Bonus) : Pour quel seuil de P(defaut) la politique change-t-elle (seuil de decision) ?

Indices : - Reutilisez le pattern de la section 7 : boucle foreach (bool obsTest in new[] { true, false }) avec testPositif.ObservedValue = obsTest - N’oubliez pas testPositif.ClearObservedValue() entre les itérations - Le cout de tri (-50 EUR) est identique que le lot soit defectueux ou sain

// Exercice : Reseau de decision avec Infer.NET - Assurance qualite

// Parametres
double pDefaut = 0.20;              // P(lot defectueux)
double sensibiliteQA = 0.88;        // P(test+|defaut)
double specificiteQA = 0.75;        // P(test-|sain)
double cout_expedier_defaut = -500; // Reclamations
double cout_expedier_sain = 200;    // Profit normal
double cout_trier = -50;            // Cout fixe de tri

// TODO 1 : Modelisez les variables de chance avec Infer.NET
// Indice : Variable<bool> defaut = Variable.Bernoulli(pDefaut).Named("defaut");
// Variable<bool> testPos = Variable.New<bool>().Named("testQA");
// using (Variable.If(defaut)) testPos.SetTo(Variable.Bernoulli(sensibiliteQA));
// using (Variable.IfNot(defaut)) testPos.SetTo(Variable.Bernoulli(1 - specificiteQA));

// TODO 2 : Creez le moteur d'inference et bouclez sur les observations
// InferenceEngine engineQA = new InferenceEngine();
// engineQA.Compiler.CompilerChoice = CompilerChoice.Roslyn;
// foreach (bool obs in new[] { true, false }) { testPos.ObservedValue = obs; ... }

// TODO 3 : Pour chaque observation, calculez P(defaut|test) puis E[U] de chaque action
// double pD = engineQA.Infer<Bernoulli>(defaut).GetProbTrue();
// E[U(expedir)] = pD * cout_expedier_defaut + (1 - pD) * cout_expedier_sain
// E[U(trier)] = cout_trier (fixe, independant de l'etat)

// TODO 4 : Affichez la politique optimale conditionnelle
// Si test positif : expedier ou trier ?
// Si test negatif : expedier ou trier ?

// TODO 5 : (Bonus) Trouvez le seuil de P(defaut) ou la politique change
// Indice : resolvez pD * cout_expedier_defaut + (1 - pD) * cout_expedier_sain = cout_trier pour pD

Console.WriteLine("Exercice a completer");
Exercice a completer

Recapitulatif des concepts Infer.NET utilises

Distributions utilisees

Distribution Usage dans ce notebook Exemple
Bernoulli Variables binaires (maladie, test) Variable.Bernoulli(0.15)
Gaussian Attributs continus incertains (couts) Variable.GaussianFromMeanAndVariance(500, 10000)
Beta Scores bornes [0,1] (securite) Variable.Beta(8, 2)

Patterns Infer.NET

Pattern Description Section
Modèle conditionnel using (Variable.If(...)) pour CPT 7
Observation variable.ObservedValue = ... 7
Enumeration Boucle sur les observations possibles 7
Factor Graph ShowFactorGraph = true 7.1

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

Pour aller plus loin

Si vous voulez… Consultez…
Calculer la valeur de l’information DecInfer-06-Value-Information
Decisions robustes DecInfer-07-Expert-Systems
Decisions séquentielles (MDPs) DecInfer-08-Sequential

Prochaine étape

Dans DecInfer-06-Value-Information, nous verrons :

  • La valeur de l’information parfaite (EVPI)
  • La valeur de l’information d’echantillon (EVSI)
  • Des applications : droits de forage, chasse au tresor

References

  • Howard & Matheson (1984) : Influence Diagrams
  • Shachter (1986) : Evaluating Influence Diagrams
  • Russell & Norvig : AI, Chapter 16.5
Retour au sommet