Infer-2b-Debugging-Bonnes-Pratiques : Troubleshooting et Bonnes Pratiques

Serie : Programmation Probabiliste avec Infer.NET (2b/19)
Duree estimee : 45 minutes
Prerequis : Infer-2-Gaussian-Mixtures Jumeau Python : PyMC-02b-Debugging-Python


Objectifs

  • Diagnostiquer les problemes courants d’inference
  • Comparer les algorithmes (EP, VMP, Gibbs)
  • Utiliser les outils de debug d’Infer.NET
  • Appliquer les bonnes pratiques de modelisation

1. Configuration

#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 !");
Installing Packages
  • Microsoft.ML.Probabilistic
  • Microsoft.ML.Probabilistic.Compiler
Infer.NET pret !

Packages charges

Package rôle
Microsoft.ML.Probabilistic Distributions, moteur d’inference, algorithmes (EP, VMP, Gibbs)
Microsoft.ML.Probabilistic.Compiler Compilation des modèles en code C# optimise

Les namespaces importants pour le debugging sont : - Algorithms : contient ExpectationPropagation, VariationalMessagePassing, GibbsSampling - Compiler : options de compilation et generation de code - Distributions : tous les types de distributions (Gaussian, Gamma, Beta, etc.)

Note technique : Ce notebook est oriente troubleshooting. Il suppose que vous avez déjà explore plusieurs notebooks de la serie et rencontre des comportements inattendus. Les exemples sont volontairement simplifies pour isoler chaque type de problème.

Le debugging en programmation probabiliste differe du debugging classique :

Debugging classique Debugging probabiliste
Erreur = crash ou mauvaise valeur Erreur = distribution inattendue ou divergence
Cause souvent déterministe Cause souvent liee aux priors ou a l’algorithme
Solution : corriger le code Solution : ajuster le modèle ou l’algorithme

Outil de visualisation

Le FactorGraphHelper est un utilitaire qui permet d’afficher les graphes de facteurs generes par Infer.NET directement dans le notebook. Ces graphes sont essentiels pour :

  • Verifier la structure du modèle probabiliste
  • Identifier les connexions manquantes ou incorrectes
  • Comprendre le flux des messages d’inference

Prerequis : Graphviz doit etre installe sur le système pour generer les visualisations SVG. Si Graphviz n’est pas disponible, le helper retournera False et les graphes ne seront pas affichés.

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

Console.WriteLine("FactorGraphHelper charge !");
Console.WriteLine($"Graphviz disponible : {FactorGraphHelper.IsGraphvizAvailable()}");
Console.WriteLine("Usage: display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()))");
FactorGraphHelper charge !
Graphviz disponible : True
Usage: display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()))

2. Erreurs Courantes et Solutions

2.1 Catalogue des Erreurs

Erreur Cause Solution
Model has no support Observation impossible sous le prior Elargir le prior ou verifier les observations
Improper distribution Divergence de l’inference Utiliser des priors plus informatifs
Could not find method opération non supportee Reformuler avec des opérations de base
Compilation timeout modèle trop complexe Simplifier ou compiler separement
Memory exceeded Trop de variables Reduire la taille ou utiliser des arrays
// Exemple 1 : Erreur "Model has no support"

Console.WriteLine("=== Erreur : Model has no support ===");
Console.WriteLine();

// PROBLEME : Observer une valeur impossible sous le prior
try
{
    Variable<double> x = Variable.GaussianFromMeanAndPrecision(0, 1000);  // Prior tres concentre autour de 0
    x.ObservedValue = 100;  // Observation tres eloignee
    
    InferenceEngine engine = new InferenceEngine();
    engine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    var result = engine.Infer(x);  // Peut echouer ou donner des resultats etranges
}
catch (Exception e)
{
    Console.WriteLine($"Erreur : {e.Message.Substring(0, Math.Min(100, e.Message.Length))}...");
}

// SOLUTION : Utiliser un prior plus large
Console.WriteLine("\nSOLUTION : Elargir le prior");
Variable<double> x2 = Variable.GaussianFromMeanAndPrecision(50, 0.01);  // Prior large
x2.ObservedValue = 100;

InferenceEngine engine2 = new InferenceEngine();
engine2.Compiler.CompilerChoice = CompilerChoice.Roslyn;
Console.WriteLine($"Resultat avec prior large : {engine2.Infer(x2)}");
=== Erreur : Model has no support ===

Compiling model...done.

SOLUTION : Elargir le prior
Compiling model...done.
Resultat avec prior large : Gaussian.PointMass(100)

Interpretation des résultats :

Le résultat Gaussian.PointMass(100) signifie que le posterieur est une distribution degeneree concentree exactement sur la valeur observee. C’est le comportement attendu quand :

  1. L’observation est déterministe (ObservedValue)
  2. Le prior est suffisamment large pour “accepter” cette valeur

règle pratique : Si votre prior a une precision prec, alors les observations situees a plus de 3/sqrt(prec) ecarts-types du centre auront un support quasi-nul. Avec prec=1000, cela represente environ 0.1 unites autour de la moyenne.

// Visualisation du factor graph pour comprendre le probleme de support

Console.WriteLine("=== Visualisation : Modele avec prior large ===");

Variable<double> xVis = Variable.GaussianFromMeanAndPrecision(50, 0.01).Named("x_prior_large");
xVis.ObservedValue = 100;

InferenceEngine engineVis = new InferenceEngine();
engineVis.Compiler.CompilerChoice = CompilerChoice.Roslyn;
engineVis.ShowFactorGraph = true;
engineVis.ShowProgress = false;

var resultVis = engineVis.Infer(xVis);
Console.WriteLine($"Resultat : {resultVis}");
Console.WriteLine();
Console.WriteLine("Le factor graph montre la structure simple : prior -> observation");

display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
FactorGraphHelper.CleanupGeneratedFiles();
=== Visualisation : Modele avec prior large ===
Resultat : Gaussian.PointMass(100)

Le factor graph montre la structure simple : prior -> observation
Model_08_19_26_03_10_41_48.svg
Model node0 50 node1 Gaussian node0->node1 mean node3 100 node1->node3 node2 0,01 node2->node1 precision

2.2 problème de convergence

Le deuxième type d’erreur courant concerne la convergence vers des modes non désirés. Cela se produit souvent dans les modèles de mélange où les variables ont des rôles symétriques.

Choix de paramètre (transparence) — la cellule suivante illustre la défaillance avec un prior symétrique de précision prec=10. Avec un prior beaucoup plus plat (prec=0.01, à peine informatif), EP diverge numériquement sur ces données bimodales : la magnitude des moyennes postérieures explosait (~1e71 à 1e74) et n’était pas reproductible d’un processus à l’autre (mesuré po-2026 c.743). prec=10 rend le collapse symétrique net et reproductible (3 runs identiques sur 3) : le paramètre est changé pour rendre l’échec démontrable et exploitable pédagogiquement, pas pour le masquer. Le contraste qui suit (prior asymétrique) montre ensuite comment réparer cette convergence.

// Exemple 2 : Probleme de convergence — EP sur un melange gaussien bimodal.
// Demo mesurable (mesure po-2026 c.743) : on rend la defaillance reproductible
// au lieu de se contenter de la decrire en prose.

Console.WriteLine("=== Probleme : Convergence vers mode symetrique ===");
Console.WriteLine();

// Donnees bimodales : 30 points autour de -5, 30 autour de +5.
Rand.Restart(42);   // reproductibilite (3 runs identiques, cf mesure c.743)
double[] donnees = new double[60];
for (int i = 0; i < 30; i++) donnees[i] = Rand.Normal(-5.0, 1.0);
for (int i = 30; i < 60; i++) donnees[i] = Rand.Normal(5.0, 1.0);
Console.WriteLine($"Donnees : 60 points bimodaux, moyenne empirique = {donnees.Average():F3}");
Console.WriteLine();

// ---- Cas 1 : Prior SYMETRIQUE (prec = 10) ----
// Un prior identique sur les deux composantes laisse EP libre d'attribuer
// chaque point a l'une ou l'autre : la solution ou les deux moyennes collapses
// vers la meme valeur est un attracteur (label switching / mode symetrique).
Console.WriteLine("--- Cas 1 : Prior symetrique  means[k] ~ Gaussian(0, prec=10) ---");
{
    Range k = new Range(2);
    Range n = new Range(donnees.Length);
    VariableArray<double> x = Variable.Array<double>(n).Named("x_sym");
    x.ObservedValue = donnees;
    VariableArray<double> means = Variable.Array<double>(k).Named("means_sym");
    means[k] = Variable.GaussianFromMeanAndPrecision(0.0, 10.0).ForEach(k);
    VariableArray<int> assign = Variable.Array<int>(n).Named("assign_sym");
    using (Variable.ForEach(n))
    {
        assign[n] = Variable.DiscreteUniform(k);
        using (Variable.Switch(assign[n]))
            x[n].SetTo(Variable.GaussianFromMeanAndPrecision(means[assign[n]], 1.0));
    }
    var engine = new InferenceEngine(new ExpectationPropagation());
    engine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    engine.ShowProgress = false;
    var post = engine.Infer<Gaussian[]>(means);
    Console.WriteLine($"  means[0] = {post[0].GetMean():F4}");
    Console.WriteLine($"  means[1] = {post[1].GetMean():F4}");
    Console.WriteLine("  -> Collapse symetrique : les deux composantes sont identiques");
    Console.WriteLine("     (le modele ne parvient pas a separer les deux modes).");
}
Console.WriteLine();

// ---- Cas 2 : Prior ASYMETRIQUE (la solution) ----
// Casser la symetrie du prior (means[0] tire vers -5, means[1] vers +5) leve
// l'ambiguite : EP separe proprement les deux modes de la distribution.
Console.WriteLine("--- Cas 2 : Prior asymetrique  means[0] ~ G(-5,1), means[1] ~ G(+5,1) ---");
{
    Range k = new Range(2);
    Range n = new Range(donnees.Length);
    VariableArray<double> x = Variable.Array<double>(n).Named("x_asym");
    x.ObservedValue = donnees;
    VariableArray<double> means = Variable.Array<double>(k).Named("means_asym");
    means[0] = Variable.GaussianFromMeanAndVariance(-5.0, 1.0);
    means[1] = Variable.GaussianFromMeanAndVariance(5.0, 1.0);
    VariableArray<int> assign = Variable.Array<int>(n).Named("assign_asym");
    using (Variable.ForEach(n))
    {
        assign[n] = Variable.DiscreteUniform(k);
        using (Variable.Switch(assign[n]))
            x[n].SetTo(Variable.GaussianFromMeanAndPrecision(means[assign[n]], 1.0));
    }
    var engine = new InferenceEngine(new ExpectationPropagation());
    engine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    engine.ShowProgress = false;
    var post = engine.Infer<Gaussian[]>(means);
    Console.WriteLine($"  means[0] = {post[0].GetMean():F4}");
    Console.WriteLine($"  means[1] = {post[1].GetMean():F4}");
    Console.WriteLine("  -> Convergence : les deux modes sont separement identifies.");
}
=== Probleme : Convergence vers mode symetrique ===

Donnees : 60 points bimodaux, moyenne empirique = -0,124

--- Cas 1 : Prior symetrique  means[k] ~ Gaussian(0, prec=10) ---
  means[0] = 5,0737
  means[1] = 5,0737
  -> Collapse symetrique : les deux composantes sont identiques
     (le modele ne parvient pas a separer les deux modes).

--- Cas 2 : Prior asymetrique  means[0] ~ G(-5,1), means[1] ~ G(+5,1) ---
  means[0] = -5,1493
  means[1] = 4,9088
  -> Convergence : les deux modes sont separement identifies.

Le problème du “label switching”

Dans les modèles de melange, le problème des modes symetriques est connu sous le nom de label switching. Si les composantes sont echangeables (même prior), l’inference peut :

  1. Converger vers la moyenne : toutes les composantes au même endroit
  2. Osciller entre permutations : les labels des composantes s’echangent entre itérations

Les solutions incluent :

Solution Avantage Inconvenient
Priors asymetriques Simple a implementer Introduit un biais
Contraintes d’ordre Mathematiquement propre Complexifie le modèle
Post-traitement Pas de biais Necessite analyse manuelle

3. Comparaison des Algorithmes d’Inference

Quand utiliser quel algorithme ?

Algorithme Forces Faiblesses Usage recommande
EP Rapide, bon pour melanges continus Peut diverger, approximatif modèles Gaussian, Probit
VMP Stable, bon pour discret Sous-estime l’incertitude LDA, melanges categoriques
Gibbs Exact asymptotiquement Lent, diagnostics difficiles Validation, petits modèles

La cellule suivante compare EP et VMP sur un problème simple d’estimation de moyenne avec precision inconnue.

Configuration du test : - 6 observations autour de 2.0 - Prior vague sur la moyenne : Gaussian(0, 0.01) - Prior informatif sur la precision : Gamma(2, 0.5)

Ce type de modèle (données gaussiennes avec paramètres inconnus) est un cas classique ou EP et VMP donnent des résultats comparables mais avec des niveaux d’incertitude différents.

// Comparaison EP vs VMP sur un modele simple

Console.WriteLine("=== Comparaison EP vs VMP ===");
Console.WriteLine();

// Modele : estimation de moyenne avec observations bruitees
double[] observations = { 2.1, 1.9, 2.3, 2.0, 1.8, 2.2 };
int nObs = observations.Length;

// Fonction pour creer et inferer le modele avec EP
Gaussian InferAvecEP()
{
    Variable<double> mean = Variable.GaussianFromMeanAndPrecision(0, 0.01);
    Variable<double> prec = Variable.GammaFromShapeAndScale(2, 0.5);
    
    Range r = new Range(nObs);
    VariableArray<double> obs = Variable.Array<double>(r);
    obs[r] = Variable.GaussianFromMeanAndPrecision(mean, prec).ForEach(r);
    obs.ObservedValue = observations;
    
    InferenceEngine engine = new InferenceEngine(new ExpectationPropagation());
    engine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    engine.ShowProgress = false;
    
    return engine.Infer<Gaussian>(mean);
}

// Fonction pour creer et inferer le modele avec VMP
Gaussian InferAvecVMP()
{
    Variable<double> mean = Variable.GaussianFromMeanAndPrecision(0, 0.01);
    Variable<double> prec = Variable.GammaFromShapeAndScale(2, 0.5);
    
    Range r = new Range(nObs);
    VariableArray<double> obs = Variable.Array<double>(r);
    obs[r] = Variable.GaussianFromMeanAndPrecision(mean, prec).ForEach(r);
    obs.ObservedValue = observations;
    
    InferenceEngine engine = new InferenceEngine(new VariationalMessagePassing());
    engine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    engine.ShowProgress = false;
    
    return engine.Infer<Gaussian>(mean);
}

var resultEP = InferAvecEP();
var resultVMP = InferAvecVMP();

Console.WriteLine($"Observations : {string.Join(", ", observations)}");
Console.WriteLine($"Moyenne empirique : {observations.Average():F3}");
Console.WriteLine();
Console.WriteLine($"EP  : {resultEP}");
Console.WriteLine($"VMP : {resultVMP}");
Console.WriteLine();
Console.WriteLine("Note : VMP tend a avoir une variance plus faible (sous-estime l'incertitude)");
=== Comparaison EP vs VMP ===

Observations : 2,1, 1,9, 2,3, 2, 1,8, 2,2
Moyenne empirique : 2,050

EP  : Gaussian(2,048, 0,0808)
VMP : Gaussian(2,048, 0,07725)

Note : VMP tend a avoir une variance plus faible (sous-estime l'incertitude)

Interpretation des résultats EP vs VMP :

Les deux algorithmes estiment la moyenne autour de 2.05 (proche de la moyenne empirique 2.05), mais avec des caractéristiques différentes :

Métrique EP VMP Interpretation
Moyenne posterieure ~2.05 ~2.05 Accord sur l’estimation ponctuelle
Variance posterieure Plus large Plus etroite VMP sous-estime l’incertitude

Pourquoi VMP sous-estime l’incertitude ?

VMP (Variational Message Passing) approxime le posterieur par une distribution factorisee. Cette hypothese d’indépendance ignore les correlations entre variables, ce qui conduit typiquement a des posterieurs trop “confiants”.

EP (Expectation Propagation) maintient des correlations locales via les messages, produisant des approximations plus realistes de l’incertitude.

Quand cela importe : La sous-estimation de l’incertitude par VMP peut etre problematique pour : - La prise de decision sous incertitude - Les intervalles de prediction - La propagation de l’incertitude dans des modèles hiérarchiques

Quand EP devient-elle vraiment instable ? Le cas non-conjugué

L’exemple conjugué ci-dessus (estimation d’une moyenne gaussienne) est un régime trop favorable pour EP : la vraisemblance est gaussienne, donc le marginal exact est lui-même gaussien et EP le retrouve directement — aucune instabilité n’est possible. La ligne « Peut diverger » du tableau du §3 ne s’y manifeste pas, et c’est ce qui rend la comparaison ci-dessus trompeuse quant à la faiblesse réelle d’EP.

Pour observer le comportement distinctif d’EP, il faut un modèle non-conjugué : une vraisemblance dont le marginal n’a pas de forme fermée gaussienne, forçant EP à approcher chaque message factor→variable. Le cas d’école est la régression logistique bayésienne, dont la vraisemblance BernoulliFromLogOdds(w·x) n’est conjuguée à aucun prior gaussien. C’est exactement le régime que le notebook Infer-4 cite comme motivation pour surveiller la convergence d’EP.

La cellule suivante construit un tel modèle (2 features, 8 observations linéairement séparables) et mesure la moyenne postérieure du vecteur de poids w au fil des itérations, pour EP et VMP. On cherche à voir comment EP se comporte quand elle doit approximer : divergence franche (NaN/Inf) ? oscillation ? convergence monotone ?

// Modèle NON-CONJUGUÉ : régression logistique bayésienne.
// Vraisemblance BernoulliFromLogOdds(w.x) : EP doit approcher chaque message,
// c'est ici que son instabilité (vs le cas conjugué du §3) devient observable.
double[][] X = new double[][] {
    new double[]{0.5,0.8}, new double[]{0.9,0.4}, new double[]{0.7,0.9}, new double[]{0.95,0.6},
    new double[]{0.1,0.2}, new double[]{0.2,0.15}, new double[]{0.3,0.1}, new double[]{0.15,0.25},
};
bool[] Y = new bool[]{ true, true, true, true, false, false, false, false };
int nFeatLogit = X[0].Length;
Range nObsLogit = new Range(X.Length).Named("nObsLogit");
Variable<Vector> wLogit = Variable.VectorGaussianFromMeanAndVariance(
    Vector.Zero(nFeatLogit), PositiveDefiniteMatrix.IdentityScaledBy(nFeatLogit, 10.0)).Named("wLogit");
VariableArray<Vector> Xv = Variable.Array<Vector>(nObsLogit).Named("Xv");
Xv.ObservedValue = X.Select(v => Vector.FromArray(v)).ToArray();
VariableArray<bool> yv = Variable.Array<bool>(nObsLogit).Named("yv");
yv.ObservedValue = Y;
using (Variable.ForEach(nObsLogit)) {
    yv[nObsLogit] = Variable.BernoulliFromLogOdds(Variable.InnerProduct(wLogit, Xv[nObsLogit]));
}

// Renvoie la moyenne postérieure de wLogit selon l'algorithme et le nombre d'itérations.
Vector RunLogit(string algo, int nIters, out bool nan) {
    nan = false;
    InferenceEngine engLogit = algo=="EP" ? new InferenceEngine(new ExpectationPropagation())
                                          : new InferenceEngine(new VariationalMessagePassing());
    engLogit.NumberOfIterations = nIters;
    engLogit.ShowProgress = false;
    engLogit.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    var postLogit = engLogit.Infer<VectorGaussian>(wLogit);
    var meanVec = postLogit.GetMean();
    if (double.IsNaN(meanVec[0]) || double.IsInfinity(meanVec[0])) nan = true;
    return meanVec;
}

Console.WriteLine("=== EP vs VMP sur régression logistique (non-conjugué) ===");
Console.WriteLine();
Console.WriteLine("algo  iters   mean[0]   mean[1]    flag");
Console.WriteLine(new string('-', 44));
foreach (var algo in new[]{"EP","VMP"}) foreach (var it in new[]{1,2,5,10,50,100}) {
    bool nan; var meanVec = RunLogit(algo, it, out nan);
    string flag = nan ? "NaN/Inf" : "ok";
    Console.WriteLine(algo.PadRight(5) + " " + it.ToString().PadLeft(4) + "   "
                      + meanVec[0].ToString("F3").PadLeft(7) + "   " + meanVec[1].ToString("F3").PadLeft(7)
                      + "   " + flag);
}
=== EP vs VMP sur régression logistique (non-conjugué) ===

algo  iters   mean[0]   mean[1]    flag
--------------------------------------------
EP       1    30,500    27,000   ok
EP       2    -7,490    -6,986   ok
EP       5     1,507     1,191   ok
EP      10     1,446     1,138   ok
EP      50     1,446     1,138   ok
EP     100     1,446     1,138   ok
VMP      1     0,168     0,640   ok
VMP      2     0,911     1,077   ok
VMP      5     1,312     1,085   ok
VMP     10     1,368     1,075   ok
VMP     50     1,453     1,143   ok
VMP    100     1,453     1,143   ok

Lecture du résultat — ce que « EP peut diverger » signifie concrètement :

Algorithme Itérations 1→2 Itération ≥ 5 Verdict mesuré
EP +30,5 puis −7,5 (changement de signe !) ≈ +1,45 (stable) Oscille violemment, puis converge
VMP +0,17 → +0,91 (monotone) ≈ +1,45 (stable) Progression régulière dès le départ

Deux faits honnêtes, mesurés dans la cellule précédente :

  1. EP ne part pas en NaN sur ce modèle. L’implémentation Infer.NET (EP amortie) contient les messages : toutes les itérations renvoient une valeur finie. La « divergence » cataclysmique n’est donc pas la bonne lecture de la ligne du tableau §3 sur ce cas d’école.

  2. EP oscille fortement avant de converger : à l’itération 1, le message sur-corrige (poids à +30,5, soit ~20× la valeur finale) ; à l’itération 2, le poids change de signe (−7,5 : il prédit momentanément la classe opposée). C’est l’instabilité réelle de la propagation de messages non-conjuguée : chaque message approximatif déplace trop la gaussienne, et la correction suivante la rejette de l’autre côté. VMP, qui propage des moments (moyenne/variance) plutôt que des messages factoriels naturels, est monotone dès la première itération.

Leçon de debugging : face à un résultat EP surprenant, augmentez NumberOfIterations et observez la trajectoire plutôt que le seul point final. Une oscillation précoce qui se stabilise (EP ici) se distingue d’une divergence franche (valeurs NaN/Inf, ou croissance monotone non bornée). Le réflexe « EP = instable » est juste mais doit être diagnostiqué, jamais présupposé : sur modèle conjugué EP est impeccable (cf. §3 ci-dessus), sur modèle non-conjugué elle oscille mais converge si le modèle est bien posé. C’est cette nuance que la simple ligne de tableau ne pouvait pas capturer.

// Exercice : Diagnostic d'un modele de melange gaussien

// Modele avec problemes (a corriger)
double[] dataMelange = { -4.2, -3.8, -5.1, -4.5, -3.9, 5.3, 4.8, 5.1, 4.6, 5.5 };
int nData = dataMelange.Length;
int nComposantes = 2;

// TODO 1 : Definir des priors asymetriques pour les moyennes des 2 composantes
// Indice : composante 0 centree vers -5, composante 1 centree vers +5
// Variable<double> mean0 = ...
// Variable<double> mean1 = ...

// TODO 2 : Definir un prior raisonnable pour la precision commune
// Indice : GammaFromShapeAndScale avec shape >= 1

// TODO 3 : Creer le tableau d'observations avec VariableArray
// Indice : chaque observation est tiree d'une des deux composantes

// TODO 4 : Choisir l'algorithme d'inference adapte (EP ou VMP)
// Indice : pour un melange gaussien, EP est generalement recommande

// TODO 5 : Executer l'inference et verifier les posterieurs avec DiagnosticGaussian()

Console.WriteLine("Exercice a completer : diagnostiquer et corriger le melange gaussien");
Exercice a completer : diagnostiquer et corriger le melange gaussien

Exercice 1 : Diagnostic d’un modèle de melange gaussien

Objectif : Identifiez et corrigez les problemes dans un modèle de melange gaussien a 2 composantes.

Contexte : Un modèle de melange gaussien tente de separer des données issues de deux distributions distinctes, mais l’inference produit des résultats inattendus (moyennes identiques ou divergence).

étapes : 1. Analysez le modèle fourni et identifiez au moins 2 problemes parmi : prior symetrique, algorithme mal adapte, observation hors-support 2. Corrigez chaque problème en vous basant sur les bonnes pratiques vues dans ce notebook 3. Comparez les résultats obtenus avec EP et VMP sur votre modèle corrige

Indices : - Indice 1 : Les composantes d’un melange doivent avoir des priors distincts pour eviter le label switching - Indice 2 : Testez avec engine.ShowProgress = true pour observer la convergence - Indice 3 : Utilisez DiagnosticGaussian() pour verifier la sante des posterieurs

// Visualisation du factor graph du modele EP vs VMP
// Le graphe est identique pour les deux algorithmes - seul le calcul des messages differe

// TODO etudiant : Implementer la visualisation du factor graph du modele EP vs VMP.
// Le graphe doit montrer la structure partagee (hyperparametres mean/precision + observations y).
// Indice 1 : definir 6 observations bruitees (cf cellules 13-15 pour le modele canonique)
// Indice 2 : creer Variable<double> mean = ... .Named("mean") et Variable<double> prec = ... .Named("precision")
// Indice 3 : creer VariableArray<double> obs[r] = Variable.GaussianFromMeanAndPrecision(mean, prec).ForEach(r)
// Indice 4 : InferenceEngine engVis = new InferenceEngine(); engVis.ShowFactorGraph = true;
// Indice 5 : afficher avec display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml())) et nettoyer via FactorGraphHelper.CleanupGeneratedFiles()

Console.WriteLine("Exercice a completer : visualiser le factor graph EP vs VMP");
Exercice a completer : visualiser le factor graph EP vs VMP

4. Outils de Debug Infer.NET

4.1 Options du Moteur

engine.ShowProgress = true;           // Affiche les itérations
engine.ShowSchedule = true;           // Affiche l'ordre des messages
engine.ShowFactorGraph = true;        // Genere le graphe de facteurs
engine.Compiler.WriteSourceFiles = true;  // Sauvegarde le code genere
engine.Compiler.ShowWarnings = true;  // Affiche les avertissements
// Demonstration des outils de debug

Console.WriteLine("=== Outils de Debug ===");
Console.WriteLine();

// Modele simple pour demonstration
Variable<double> mu = Variable.GaussianFromMeanAndPrecision(0, 1).Named("mu");
Variable<double> y = Variable.GaussianFromMeanAndPrecision(mu, 1).Named("y");
y.ObservedValue = 5.0;

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

// Activer les options de debug
debugEngine.ShowProgress = true;
debugEngine.Compiler.ShowWarnings = true;

Console.WriteLine("Options de debug activees :");
Console.WriteLine("  - ShowProgress : affiche les iterations");
Console.WriteLine("  - ShowWarnings : affiche les avertissements du compilateur");
Console.WriteLine();

var muPost = debugEngine.Infer<Gaussian>(mu);
Console.WriteLine($"\nResultat : mu ~ {muPost}");
=== Outils de Debug ===

Options de debug activees :
  - ShowProgress : affiche les iterations
  - ShowWarnings : affiche les avertissements du compilateur

Compiling model...done.

Resultat : mu ~ Gaussian(2,5, 0,5)

Interpretation du résultat :

Le posterieur Gaussian(2.5, 0.5) resulte de la mise a jour bayesienne :

\[\mu_{\text{post}} = \frac{\tau_{\text{prior}} \cdot \mu_{\text{prior}} + \tau_{\text{likelihood}} \cdot y}{\tau_{\text{prior}} + \tau_{\text{likelihood}}} = \frac{1 \cdot 0 + 1 \cdot 5}{1 + 1} = 2.5\]

\[\tau_{\text{post}} = \tau_{\text{prior}} + \tau_{\text{likelihood}} = 1 + 1 = 2 \quad \Rightarrow \quad \sigma^2_{\text{post}} = 0.5\]

Ou \(\tau\) represente la precision (inverse de la variance). Le posterieur est exactement a mi-chemin entre le prior (0) et l’observation (5), car les deux ont la même precision.

4.2 Visualisation des Factor Graphs

L’option ShowFactorGraph = true genere des fichiers .gv (DOT) et .svg (si Graphviz est installe). Le helper FactorGraphHelper permet d’afficher ces graphes directement dans le notebook.

// Demonstration de la visualisation du Factor Graph

Console.WriteLine("=== Visualisation du Factor Graph ===");
Console.WriteLine();

// Modele hierarchique pour une visualisation interessante
Variable<double> hyperMean = Variable.GaussianFromMeanAndPrecision(0, 0.1).Named("hyperMean");
Variable<double> hyperPrec = Variable.GammaFromShapeAndScale(2, 0.5).Named("hyperPrec");
Variable<double> obs1 = Variable.GaussianFromMeanAndPrecision(hyperMean, hyperPrec).Named("obs1");
Variable<double> obs2 = Variable.GaussianFromMeanAndPrecision(hyperMean, hyperPrec).Named("obs2");

obs1.ObservedValue = 3.0;
obs2.ObservedValue = 5.0;

InferenceEngine fgEngine = new InferenceEngine();
fgEngine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
fgEngine.ShowFactorGraph = true;  // Generer le graphe
fgEngine.ShowProgress = false;

var hyperMeanPost = fgEngine.Infer<Gaussian>(hyperMean);
Console.WriteLine($"Resultat : hyperMean ~ {hyperMeanPost}");

// Afficher le factor graph inline
display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));

// Nettoyer les fichiers generes
int cleaned = FactorGraphHelper.CleanupGeneratedFiles();
Console.WriteLine($"\nFichiers nettoyes : {cleaned}");
=== Visualisation du Factor Graph ===

Resultat : hyperMean ~ Gaussian(3,74, 0,9106)
Model_08_19_26_03_10_48_73.svg
Model node0 0 node1 Gaussian node0->node1 mean node3 hyperMean node1->node3 node2 0,1 node2->node1 precision node4 Gaussian node3->node4 mean node7 Gaussian node3->node7 mean node6 3 node4->node6 node5 hyperPrec node5->node4 precision node5->node7 precision node8 5 node7->node8 node9 2 node10 Sample node9->node10 shape node10->node5 node11 0,5 node11->node10 scale

Fichiers nettoyes : 2

Comment lire un Factor Graph ?

Le graphe de facteurs visualise la structure du modèle probabiliste :

élément Representation Signification
Cercles Variables Variables aléatoires du modèle
Carres Facteurs Distributions ou contraintes
Aretes Connexions Dependances entre variables et facteurs
Couleur grise Observe Variables avec valeurs fixees

Utilite pour le debugging :

Le factor graph permet de verifier visuellement que : 1. Les variables sont connectees comme prevu 2. Les observations sont bien marquees 3. Il n’y a pas de composantes deconnectees 4. La structure hiérarchique est correcte

Dans le graphe ci-dessus, on voit que hyperMean et hyperPrec sont les hyperparamètres partages par les deux observations obs1 et obs2, formant un modèle hiérarchique classique.

5. Bonnes Pratiques de Modelisation

5.1 Nommage des Variables

// BON : Noms explicites avec .Named()
Variable<double> capaciteEtudiant = Variable.GaussianFromMeanAndPrecision(0, 1).Named("capacite");

// MAUVAIS : Variables anonymes
Variable<double> x = Variable.GaussianFromMeanAndPrecision(0, 1);

5.2 Priors Informatifs

Situation Prior recommande
Moyenne inconnue Gaussian large (precision ~0.01)
Precision inconnue Gamma(2, 0.5) ou plus concentre
Probabilite Beta(1, 1) pour uniforme, Beta(2, 2) pour centre
Poids melange Dirichlet(1, 1, …) pour uniforme

Demonstration de l’impact des priors

Le choix des priors est souvent la source principale de problemes en programmation probabiliste. Un prior mal choisi peut :

  1. Rendre l’inference impossible : si l’observation a probabilite nulle sous le prior
  2. Biaiser les résultats : si le prior “domine” les données
  3. Ralentir la convergence : si le prior est très différent des données

La cellule suivante illustre l’impact de différents priors Beta sur l’estimation d’une probabilite binomiale avec seulement 5 observations.

// Demonstration de l'importance des priors

Console.WriteLine("=== Impact du choix des Priors ===");
Console.WriteLine();

// Observations : 3 succes sur 5 essais
int succes = 3, echecs = 2;

// Differents priors pour la probabilite
var priors = new (string nom, double a, double b)[] {
    ("Uniforme Beta(1,1)", 1, 1),
    ("Centre Beta(2,2)", 2, 2),
    ("Informatif Beta(5,5)", 5, 5),
    ("Biaise succes Beta(8,2)", 8, 2)
};

Console.WriteLine($"Observations : {succes} succes, {echecs} echecs");
Console.WriteLine($"MLE (maximum de vraisemblance) : {(double)succes/(succes+echecs):F2}");
Console.WriteLine();

foreach (var (nom, a, b) in priors)
{
    Variable<double> p = Variable.Beta(a, b);
    Variable<int> obs = Variable.Binomial(succes + echecs, p);
    obs.ObservedValue = succes;
    
    InferenceEngine eng = new InferenceEngine();
    eng.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    eng.ShowProgress = false;
    
    Beta postP = eng.Infer<Beta>(p);
    Console.WriteLine($"{nom,-25} -> Posterieur : mean = {postP.GetMean():F3}");
}

Console.WriteLine();
Console.WriteLine("Observation : Le prior influence le posterieur, surtout avec peu de donnees.");
=== Impact du choix des Priors ===

Observations : 3 succes, 2 echecs
MLE (maximum de vraisemblance) : 0,60

Uniforme Beta(1,1)        -> Posterieur : mean = 0,571
Centre Beta(2,2)          -> Posterieur : mean = 0,556
Informatif Beta(5,5)      -> Posterieur : mean = 0,533
Biaise succes Beta(8,2)   -> Posterieur : mean = 0,733

Observation : Le prior influence le posterieur, surtout avec peu de donnees.

Analyse detaillee des résultats :

Prior Alpha Beta Moyenne prior Moyenne posterieure Ecart au MLE
Uniforme 1 1 0.500 0.571 -0.029
Centre 2 2 0.500 0.556 -0.044
Informatif 5 5 0.500 0.533 -0.067
Biaise 8 2 0.800 0.733 +0.133

Le posterieur Beta suit la formule analytique :

\[p \mid \text{data} \sim \text{Beta}(\alpha + \text{succes}, \beta + \text{echecs})\]

\[\mathbb{E}[p \mid \text{data}] = \frac{\alpha + \text{succes}}{\alpha + \beta + \text{succes} + \text{echecs}}\]

Interpretation bayesienne :

  • Le prior uniforme (Beta(1,1)) est le plus “neutre” et donne un résultat proche du MLE
  • Les priors centres (Beta(2,2) et Beta(5,5)) “tirent” le posterieur vers 0.5
  • Le prior biaise (Beta(8,2)) domine les observations et maintient une estimation elevee

La force de l’effet du prior depend du ratio entre les pseudo-observations du prior (\(\alpha + \beta\)) et les observations reelles (5 dans cet exemple).

// Exercice : Test de depistage et impact du prior
// TODO etudiant : Creer un modele pour un test medical avec sensibilite=0.9, faux_positifs=0.05
// TODO etudiant : Observer un resultat positif et calculer P(malade | test+)
// Indice : Utilisez Variable.Bernoulli pour la maladie et If/IfNot pour la vraisemblance du test
Console.WriteLine("Exercice a completer : test de depistage medical");
Exercice a completer : test de depistage medical

Exercice 2 : Impact du prior sur un diagnostic medical

Objectif : Modeliser un test de depistage avec des priors différents et observer l’impact sur le diagnostic posterieur.

Contexte : Un test medical a un taux de faux positifs de 5% et un taux de sensibilite de 90%. La prevalence de la maladie est de 1%. Vous observez un test positif. Quelle est la probabilite que le patient soit reellement malade ?

Indices : - Indice 1 : Modelisez la probabilite de maladie avec Variable.Beta(alpha, beta) ou Variable.Bernoulli(prevalence) - Indice 2 : Testez avec Beta(1, 99) (prevalence 1%), puis Beta(10, 90) (prevalence 10%) - Étape 1 : définir le modèle avec la probabilite de maladie comme variable latente - Étape 2 : Ajouter la vraisemblance du test (sensibilite et taux de faux positifs) - Étape 3 : Observer un test positif et calculer le posterieur pour différentes prevalences

6. Checklist de Debugging

Quand votre modèle ne fonctionne pas, verifiez :

étape 1 : Verification du modèle - [ ] Les types des variables sont corrects (double vs int vs bool) - [ ] Les observations sont dans le support du prior - [ ] Les arrays ont les bonnes dimensions - [ ] Les Range sont correctement définis

étape 2 : Verification de l’Inference - [ ] L’algorithme est adapte au modèle (EP/VMP/Gibbs) - [ ] Le nombre d’itérations est suffisant - [ ] Les warnings de compilation sont examines

étape 3 : Verification des résultats - [ ] Les posterieurs ne sont pas degeneres (variance > 0) - [ ] Les moyennes sont dans des plages raisonnables - [ ] Les predictions sur données connues sont correctes

6.1 Fonctions de diagnostic automatisees

Plutot que d’inspecter manuellement chaque posterieur, il est recommande de créer des fonctions de diagnostic reutilisables. Les fonctions ci-dessous verifient automatiquement les conditions de sante des posterieurs pour les trois types de distributions les plus courants : Gaussian, Gamma et Beta.

Ces fonctions peuvent etre integrees dans un pipeline de validation pour detecter automatiquement : - Les distributions degenerees (variance quasi-nulle) - Les inferences non informatives (variance très elevee) - Les valeurs hors plage attendue

// Fonctions utilitaires de diagnostic pour differents types de distributions

void DiagnosticGaussian(Gaussian posterior, string nom)
{
    Console.WriteLine($"=== Diagnostic : {nom} ===");
    Console.WriteLine($"  Distribution : {posterior}");
    Console.WriteLine($"  Moyenne : {posterior.GetMean():F4}");
    double variance = posterior.GetVariance();
    Console.WriteLine($"  Variance : {variance:F6}");
    
    if (variance < 1e-10)
        Console.WriteLine("  [ALERTE] Variance tres faible - possible degenerescence");
    if (variance > 1e6)
        Console.WriteLine("  [ALERTE] Variance tres elevee - inference non informative");
    Console.WriteLine();
}

void DiagnosticGamma(Gamma posterior, string nom)
{
    Console.WriteLine($"=== Diagnostic : {nom} ===");
    Console.WriteLine($"  Distribution : {posterior}");
    Console.WriteLine($"  Moyenne : {posterior.GetMean():F4}");
    double variance = posterior.GetVariance();
    Console.WriteLine($"  Variance : {variance:F6}");
    
    if (variance < 1e-10)
        Console.WriteLine("  [ALERTE] Variance tres faible - possible degenerescence");
    if (variance > 1e6)
        Console.WriteLine("  [ALERTE] Variance tres elevee - inference non informative");
    Console.WriteLine();
}

void DiagnosticBeta(Beta posterior, string nom)
{
    Console.WriteLine($"=== Diagnostic : {nom} ===");
    Console.WriteLine($"  Distribution : {posterior}");
    Console.WriteLine($"  Moyenne : {posterior.GetMean():F4}");
    double variance = posterior.GetVariance();
    Console.WriteLine($"  Variance : {variance:F6}");
    
    if (variance < 1e-10)
        Console.WriteLine("  [ALERTE] Variance tres faible - possible degenerescence");
    Console.WriteLine();
}

// Exemple d'utilisation
Variable<double> test = Variable.GaussianFromMeanAndPrecision(0, 0.1);
Variable<double> yTest = Variable.GaussianFromMeanAndPrecision(test, 1);
yTest.ObservedValue = 3.0;

InferenceEngine diagEngine = new InferenceEngine();
diagEngine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
diagEngine.ShowProgress = false;

var testPost = diagEngine.Infer<Gaussian>(test);
DiagnosticGaussian(testPost, "test");
=== Diagnostic : test ===
  Distribution : Gaussian(2,727, 0,9091)
  Moyenne : 2,7273
  Variance : 0,909091

Interpretation des diagnostics :

Les fonctions de diagnostic ci-dessus verifient plusieurs conditions de sante du posterieur :

Condition Seuil Signification si viole
Variance trop faible < 10^-10 Distribution degeneree, possible erreur numérique
Variance trop elevee > 10^6 Inference non informative, données insuffisantes

Pour l’exemple test, le diagnostic montre : - Moyenne ~2.73 : compromise entre le prior (0) et l’observation (3) - Variance ~0.91 : incertitude reduite par l’observation mais non nulle

Bonne pratique : Integrez ces diagnostics dans vos pipelines d’inference pour detecter automatiquement les cas problematiques, surtout dans les modèles complexes avec de nombreuses variables latentes.


Tableau recapitulatif des concepts

Concept Description Application au debugging
Support Ensemble des valeurs possibles sous une distribution Verifier que les observations sont dans le support du prior
Precision Inverse de la variance (1/sigma^2) Plus la precision est elevee, plus la distribution est concentree
Label switching Permutation des labels dans les modèles de melange Utiliser des priors asymetriques ou des contraintes d’ordre
EP Expectation Propagation Algorithme rapide pour modèles continus, peut diverger
VMP Variational Message Passing Stable mais sous-estime l’incertitude
Factor Graph Representation graphique du modèle Visualiser la structure pour identifier les erreurs

Distributions utilisees dans ce notebook

Distribution paramètres Usage typique
Gaussian (mean, precision) Variables continues, estimations de moyennes
Gamma (shape, scale) Precisions, variances, taux
Beta (alpha, beta) Probabilites binomiales, proportions
Dirichlet (alpha_1, …, alpha_k) Proportions multinomiales, poids de melange

7. Exemple guide : Debugger un modèle

Enonce

Le modèle ci-dessous a plusieurs problemes. Identifiez et corrigez-les.

// Visualisation du factor graph du modele corrige
// Utile pour verifier que la structure est correcte apres debugging

Console.WriteLine("=== Visualisation : Modele corrige ===");

Variable<double> moyenneVis = Variable.GaussianFromMeanAndPrecision(25, 0.01).Named("moyenne");
Variable<double> precisionVis = Variable.GammaFromShapeAndScale(2, 0.5).Named("precision");
Variable<double> observationVis = Variable.GaussianFromMeanAndPrecision(moyenneVis, precisionVis).Named("observation");
observationVis.ObservedValue = 50;

InferenceEngine exEngineVis = new InferenceEngine();
exEngineVis.Compiler.CompilerChoice = CompilerChoice.Roslyn;
exEngineVis.ShowFactorGraph = true;
exEngineVis.ShowProgress = false;

var moyPostVis = exEngineVis.Infer<Gaussian>(moyenneVis);
Console.WriteLine($"Moyenne posterieure : {moyPostVis}");
Console.WriteLine();
Console.WriteLine("Le factor graph valide la structure :");
Console.WriteLine("  - 'moyenne' avec prior Gaussian large");
Console.WriteLine("  - 'precision' avec prior Gamma(2, 0.5)");
Console.WriteLine("  - 'observation' conditionnee sur les deux parametres");

display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
FactorGraphHelper.CleanupGeneratedFiles();
=== Visualisation : Modele corrige ===
Moyenne posterieure : Gaussian(49,48, 2,826)

Le factor graph valide la structure :
  - 'moyenne' avec prior Gaussian large
  - 'precision' avec prior Gamma(2, 0.5)
  - 'observation' conditionnee sur les deux parametres
Model_08_19_26_03_10_50_34.svg
Model node0 25 node1 Gaussian node0->node1 mean node3 moyenne node1->node3 node2 0,01 node2->node1 precision node4 Gaussian node3->node4 mean node6 50 node4->node6 node5 precision node5->node4 precision node7 2 node8 Sample node7->node8 shape node8->node5 node9 0,5 node9->node8 scale

A votre tour : identifiez les problemes dans le modèle suivant.

Étape manquante : faire TOURNER le modèle cassé pour le diagnostiquer

Note (#8081) : la version initiale de cet exemple guidé laissait le modèle cassé en commentaire et passait directement à la version corrigée. Or le geste distinctif d’un notebook de debugging est précisément de faire échouer l’inférence puis de la diagnostiquer — pas seulement de décrire la panne. On exécute ici le modèle cassé (prior trop concentré, Gamma de shape < 1, observation à ~500 écarts-types du centre du prior) et on applique les fonctions DiagnosticGaussian / DiagnosticGamma du §6.1 pour voir le symptôme apparaître dans la sortie, avant d’appliquer les corrections de la cellule suivante.

C’est la boucle exécuter le modèle suspect → diagnostiquer le posterior → corriger qui transforme un catalogue d’erreurs en une vraie démarche de débogueur.

// #8081 — Faire TOURNER le modele casse pour voir le symptome (avant de corriger)
// Le modele ci-dessous est celui laisse en commentaire dans la cellule suivante.
// On l'execute vraiment pour observer ce que donne une inference mal specifiee,
// puis on applique les fonctions de diagnostic du §6.1 (definies plus haut, cellule 35).

Console.WriteLine("=== Modele CASSE execute : prior trop concentre + Gamma(shape<1) + obs a 500 ecarts-types ===");
Console.WriteLine();

Variable<double> m_casse = Variable.GaussianFromMeanAndPrecision(0, 100);    // prior N(0, var=0.01) — beaucoup trop serre
Variable<double> p_casse = Variable.GammaFromShapeAndScale(0.1, 0.1);        // shape < 1 : densite impropre en 0
Variable<double> obs_casse = Variable.GaussianFromMeanAndPrecision(m_casse, p_casse);
obs_casse.ObservedValue = 50;                                                 // observation a ~500 ecarts-types du prior

InferenceEngine eng_casse = new InferenceEngine();
eng_casse.Compiler.CompilerChoice = CompilerChoice.Roslyn;
eng_casse.ShowProgress = false;

var mPost_casse = eng_casse.Infer<Gaussian>(m_casse);
var pPost_casse = eng_casse.Infer<Gamma>(p_casse);
DiagnosticGaussian(mPost_casse, "moyenne (modele casse)");
DiagnosticGamma(pPost_casse, "precision (modele casse)");

Console.WriteLine(">>> Symptome : la moyenne posterieure (~0) n'a quasi pas bouge malgre l'observation a 50.");
Console.WriteLine("    Le prior N(0, var=0.01) etait si concentre que la donnee n'a pas pu le deplacer :");
Console.WriteLine("    le diagnostic de variance seul passe (0.01 est 'raisonnable'), MAIS la moyenne est");
Console.WriteLine("    a 500 ecarts-types de l'observation - echec de type 'prior ecrase la donnee', invisible");
Console.WriteLine("    au seul controle de variance. C'est ce constat qui motive les corrections de la cellule");
Console.WriteLine("    suivante (prior precision 0.01 -> la moyenne suit la donnee, comme on le verra : ~49.5).");
=== Modele CASSE execute : prior trop concentre + Gamma(shape<1) + obs a 500 ecarts-types ===

=== Diagnostic : moyenne (modele casse) ===
  Distribution : Gaussian(0,0002382, 0,01)
  Moyenne : 0,0002
  Variance : 0,010000

=== Diagnostic : precision (modele casse) ===
  Distribution : Gamma(0,6003, 0,0007935)[mean=0,0004764]
  Moyenne : 0,0005
  Variance : 0,000000

>>> Symptome : la moyenne posterieure (~0) n'a quasi pas bouge malgre l'observation a 50.
    Le prior N(0, var=0.01) etait si concentre que la donnee n'a pas pu le deplacer :
    le diagnostic de variance seul passe (0.01 est 'raisonnable'), MAIS la moyenne est
    a 500 ecarts-types de l'observation - echec de type 'prior ecrase la donnee', invisible
    au seul controle de variance. C'est ce constat qui motive les corrections de la cellule
    suivante (prior precision 0.01 -> la moyenne suit la donnee, comme on le verra : ~49.5).
// Exemple guide : Trouvez les problemes dans ce modele

Console.WriteLine("=== Exercice : Debugger ce modele ===");
Console.WriteLine();

// Probleme 1 : Prior trop etroit pour les observations
// Probleme 2 : Variables non nommees
// Probleme 3 : Precision negative (invalide)

// Modele original (avec erreurs)
/*
Variable<double> m = Variable.GaussianFromMeanAndPrecision(0, 100);  // Prior trop concentre
Variable<double> p = Variable.GammaFromShapeAndScale(0.1, 0.1);      // Shape trop petit
Variable<double> obs = Variable.GaussianFromMeanAndPrecision(m, p);
obs.ObservedValue = 50;  // Tres eloigne du prior sur m
*/

// Version corrigee
Variable<double> moyenne = Variable.GaussianFromMeanAndPrecision(25, 0.01).Named("moyenne");  // Prior large
Variable<double> precision = Variable.GammaFromShapeAndScale(2, 0.5).Named("precision");      // Shape >= 1
Variable<double> observation = Variable.GaussianFromMeanAndPrecision(moyenne, precision).Named("obs");
observation.ObservedValue = 50;

InferenceEngine exEngine = new InferenceEngine();
exEngine.Compiler.CompilerChoice = CompilerChoice.Roslyn;
exEngine.ShowProgress = false;

var moyPost = exEngine.Infer<Gaussian>(moyenne);
var precPost = exEngine.Infer<Gamma>(precision);

Console.WriteLine("Corrections appliquees :");
Console.WriteLine("1. Prior sur moyenne : precision 0.01 (large) au lieu de 100 (etroit)");
Console.WriteLine("2. Prior sur precision : Gamma(2, 0.5) au lieu de Gamma(0.1, 0.1)");
Console.WriteLine("3. Variables nommees avec .Named()");
Console.WriteLine();
Console.WriteLine($"Resultats : moyenne ~ {moyPost}, precision ~ {precPost}");
=== Exercice : Debugger ce modele ===

Corrections appliquees :
1. Prior sur moyenne : precision 0.01 (large) au lieu de 100 (etroit)
2. Prior sur precision : Gamma(2, 0.5) au lieu de Gamma(0.1, 0.1)
3. Variables nommees avec .Named()

Resultats : moyenne ~ Gaussian(49,48, 2,826), precision ~ Gamma(2, 0,4876)[mean=0,9752]

Analyse des corrections :

problème original Consequence Correction appliquee
Precision 100 sur le prior Prior concentre autour de 0 (ecart-type ~0.1) Precision 0.01 (ecart-type ~10)
Observation a 50 500 ecarts-types du centre du prior Prior centre a 25 pour couvrir l’observation
Gamma(0.1, 0.1) Shape < 1 donne une densite infinie en 0 Gamma(2, 0.5) avec shape >= 1
Variables anonymes Difficulte a interpreter les erreurs Nommage explicite avec .Named()

Le résultat corrige montre : - Moyenne ~49.5 : proche de l’observation (50) car le prior est large - Precision ~0.98 : estime a partir d’une seule observation (incertitude elevee)

Points clés a retenir

stratégie de debugging en 3 étapes :

  1. Verifier le support : Les observations sont-elles probables sous le prior ?
  2. Verifier l’algorithme : EP pour continu, VMP pour discret, Gibbs pour validation
  3. Verifier les posterieurs : Variance raisonnable ? Moyennes plausibles ?

La plupart des problemes d’inference proviennent de : - Priors mal specifies (trop etroits, mauvais support) - Mauvais choix d’algorithme (EP sur modèle discret, VMP sur correlations fortes) - modèle trop complexe (simplifier d’abord, complexifier ensuite)


8. Resume

problème Symptome Solution
Prior trop etroit “No support” ou posterieurs etranges Elargir le prior
Mode symetrique Posterieurs uniformes Priors asymetriques
Divergence Valeurs infinies ou NaN Changer d’algorithme ou regulariser
Lenteur Compilation longue Simplifier le modèle, cacher les types

Ressources

Exercice 3 : Deboguer un modèle hiérarchique Casse

Enonce

Le modèle ci-dessous tente d’estimer les moyennes de performance de 3 groupes, mais il contient 3 erreurs intentionnelles. Identifiez et corrigez chaque erreur.

Indices : 1. Une precision de distribution ne peut pas etre negative 2. Une variable observee doit dependre du paramètre correct du groupe, pas d’une variable partagee 3. Un tableau de données doit etre indexe par l’indice de boucle courant

Corrigez le code et verifiez que les posterieurs correspondent aux moyennes des groupes.

// Exercice : Corriger ce modele hierarchique bayesien (3 erreurs)
int nGroupes = 3;
int nObs = 4;
double[][] donneesGroupes = {
    new double[] { 12.1, 11.8, 12.5, 12.3 },  // Groupe 0 : ~12
    new double[] { 15.2, 14.9, 15.8, 15.1 },  // Groupe 1 : ~15
    new double[] { 9.8,  10.2, 9.5,  10.1 }   // Groupe 2 : ~10
};

// TODO: Identifier et corriger les 3 erreurs dans ce code :
/*
InferenceEngine engine = new InferenceEngine();
engine.Compiler.CompilerChoice = CompilerChoice.Roslyn;

// ERREUR 1 : Shape de Gamma negatif (invalide)
Variable<double> precisionGlobale = Variable.GammaFromShapeAndScale(-1, 1);

// ERREUR 2 : Precision trop etroite pour la moyenne de population
Variable<double> moyennePopulation = Variable.GaussianFromMeanAndPrecision(10, 10000);

// ERREUR 3 : Les moyennes de groupe ne dependent pas de la population
Variable<double>[] moyenneGroupe = new Variable<double>[nGroupes];
for (int g = 0; g < nGroupes; g++)
{
    moyenneGroupe[g] = Variable.GaussianFromMeanAndPrecision(0, 1);
    for (int j = 0; j < nObs; j++)
    {
        var obs = Variable.GaussianFromMeanAndPrecision(moyenneGroupe[g], precisionGlobale);
        obs.ObservedValue = donneesGroupes[g][j];
    }
}
*/

// TODO 1 : Corriger le prior Gamma (shape doit etre positif)
// Indice : utilisez GammaFromShapeAndScale(2, 0.5) ou similaire

// TODO 2 : Corriger le prior de la moyenne de population
// Indice : la precision ne doit pas etre trop grande, sinon la moyenne est "figee"

// TODO 3 : Lier les moyennes de groupe a la moyenne de population
// Indice : moyenneGroupe[g] ~ Gaussian(moyennePopulation, precisionGroupe)

// TODO 4 : Inferer et afficher les moyennes de chaque groupe et la moyenne de population
Console.WriteLine("Exercice a completer");
Exercice a completer

Conclusion

Ce notebook a couvert les techniques essentielles pour diagnostiquer et corriger les modèles probabilistes Infer.NET.

Concept Point cle
Support du prior Verifier que les observations sont dans le support avant inference
Choix d’algorithme EP pour continu, VMP pour discret, Gibbs pour validation
Priors informatifs Elargir ou ajuster les priors selon le domaine
Factor graphs Visualiser la structure pour detecter les erreurs de connexion
Diagnostics Tester variance non-degeneree et moyennes plausibles
Distribution Usage pour le debug
Gaussian Verifier precision non-infinie, moyenne dans plage attendue
Gamma Shape >= 1 pour eviter densite infinie en 0
Beta Pseudo-observations du prior vs données reelles

Demarche systématique : Support -> Algorithme -> Posterieurs. La plupart des problemes d’inference proviennent de priors mal specifies ou d’un mauvais choix d’algorithme.

Exercice 4 : Debugging d’un modèle de regression bayesienne

Objectif : Identifiez et corrigez les problemes dans un modèle de regression lineaire bayesienne qui produit des posterieurs degeneres.

Contexte : Un chercheur tente d’etablir une relation lineaire entre les heures d’étude (x) et les scores d’examen (y). Le modèle utilise une pente slope, une ordonnee a l’origine intercept et un bruit noise. Malheureusement, les posterieurs sont inutilisables (variance quasi-nulle ou infinie).

Indices : - Indice 1 : Verifiez le prior sur la precision du bruit. Un shape < 1 produit une densite infinie en 0. - Indice 2 : La precision du prior sur la pente est-elle adaptee a l’echelle des données (scores sur ~50-100) ? - Étape 1 : Identifiez les 2 problemes dans le modèle fourni (prior sur le bruit + precision de la pente) - Étape 2 : Corrigez les priors et relancez l’inference - Étape 3 : Utilisez DiagnosticGaussian() pour verifier la sante des posterieurs corriges

// Exercice : Debugging d'un modele de regression bayesienne

// Donnees : heures d'etude (x) et scores d'examen (y)
double[] xData = { 2, 4, 6, 8, 10 };
double[] yData = { 55, 62, 71, 78, 88 };
int nPoints = xData.Length;

// MODELE A CORRIGER (contient 2 problemes) :
/*
Variable<double> slope = Variable.GaussianFromMeanAndPrecision(0, 10000);     // PROBLEME ?
Variable<double> intercept = Variable.GaussianFromMeanAndPrecision(0, 0.01); // OK
Variable<double> noise = Variable.GammaFromShapeAndScale(0.1, 0.1);          // PROBLEME ?

Range r = new Range(nPoints);
VariableArray<double> yObs = Variable.Array<double>(r);
yObs[r] = Variable.GaussianFromMeanAndPrecision(
    intercept + slope * xData[r], noise);

yObs.ObservedValue = yData;

InferenceEngine eng = new InferenceEngine();
eng.Compiler.CompilerChoice = CompilerChoice.Roslyn;
var slopePost = eng.Infer<Gaussian>(slope);
var interceptPost = eng.Infer<Gaussian>(intercept);
*/

// TODO 1 : Corriger le prior sur la pente (precision trop elevee pour des scores ~50-100)
// Indice : avec precision=10000, l'ecart-type est de 0.01, la pente ne peut pas bouger

// TODO 2 : Corriger le prior sur le bruit (shape < 1 = densite infinie en 0)
// Indice : utilisez GammaFromShapeAndScale(2, 1) ou similaire

// TODO 3 : Executer l'inference et verifier avec DiagnosticGaussian()
// Resultat attendu : slope ~ Gaussian(~3.3, ~0.05), intercept ~ Gaussian(~42, ~3)

Console.WriteLine("Exercice a completer : debugging regression bayesienne");
Exercice a completer : debugging regression bayesienne

Synthese : la demarche du debuggeur probabiliste

Les quatre exercices de fin illustrent une même realite : en programmation probabiliste, l’erreur se manifeste rarement par un crash, mais par un posterieur degenere (variance quasi-nulle ou infinie) ou non informatif. Le reflexe du debuggeur classique – ou est le bug dans le code – doit ceder la place a une demarche en trois temps, dont chaque exercice isole un symptome.

Exercice Piege decouvert Ou le diagnostic le repere
Ex 3 (modèle hiérarchique) Gamma de shape negatif + moyenne de population figee shape < 0 leve une exception ; une precision de 10000 fige la moyenne
Ex 4 (regression bayesienne) Prior trop etroit sur la pente + bruit Gamma(0.1, 0.1) DiagnosticGaussian revele une variance nulle ; le bruit a une densite infinie en 0

Les deux familles de fautifs

Dans toute la serie, les pannes se ramenent a deux familles :

  1. Les priors – trop etroits (GaussianFromMeanAndPrecision(0, 10000) fige la variable), hors-support de l’observation (ObservedValue = 100 sous un prior centre sur 0), ou invalides (Gamma de shape < 1, precision negative). La règle empirique reste : un Gamma doit avoir shape >= 1, et la precision d’un prior vague vaut 0.01, pas 10000.
  2. Le choix d’algorithme – EP pour le continu (rapide mais peut diverger), VMP pour le discret (stable mais sous-estime l’incertitude, comme on l’a mesure : variance posterieure plus etroite), Gibbs pour la validation exacte sur petits modèles. Un melange gaussien brise sous VMP peut converger sous EP.

Le factor graph, juge de paix

Quand un posterieur defie l’intuition, le factor graph tranche : il revele les composantes deconnectees, les observations mal indexees (Ex 3, indice 3), et les dependances manquantes. engine.ShowFactorGraph = true associe au FactorGraphHelper n’est pas une fioriture – c’est l’équivalent du print du debuggeur classique.

Ce quil faut emporter

Le debugging probabiliste est avant tout un changement de regard : un mauvais résultat n’est pas un bug a corriger dans le code, mais un signal que le modèle ou l’algorithme ne correspond pas aux hypotheses implicites des données. La demarche systématique – Support, Algorithme, Posterieurs, validee par les fonctions DiagnosticGaussian / DiagnosticGamma / DiagnosticBeta – est transferable a tous les notebooks suivants de la serie (modèles hiérarchiques, series temporelles, modèles de recommandation). Gardez-la sous la main : les pannes se ressemblent, seules les distributions changent.

Retour au sommet