Infer-2-Gaussian-Mixtures : Distributions Gaussiennes et Mélanges

Série : Programmation Probabiliste avec Infer.NET (2/19)
Durée estimée : 50 minutes
Prérequis : Infer-1-Setup


Objectifs

  • Maîtriser les distributions Gaussienne et Gamma
  • Comprendre la notion de priors conjugués
  • Implémenter l’apprentissage de paramètres
  • Construire des modèles de mélange de Gaussiennes
  • Utiliser Variable.Switch pour les modèles à composantes

1. Configuration

Comme pour chaque notebook de cette série, nous commencons par charger les packages Infer.NET et importer les espaces de noms necessaires. Cette étape standardisee garantit que tous les exemples sont reproductibles et que les distributions, algorithmes et outils de modelisation sont disponibles.

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

#load "FactorGraphHelper.cs"

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 !");
Console.WriteLine($"Graphviz disponible : {FactorGraphHelper.IsGraphvizAvailable()}");
Installed Packages
  • Microsoft.ML.Probabilistic, 0.4.2504.701
  • Microsoft.ML.Probabilistic.Compiler, 0.4.2504.701
Infer.NET pret !
Graphviz disponible : True

2. Scénario : Le Cycliste

Contexte

Vous vous rendez au travail a velo chaque jour. Votre temps de trajet varie : - Certains jours, le trajet est rapide (peu de trafic) - D’autres jours, il est plus lent (embouteillages, meteo)

Objectifs

  1. Apprendre la distribution du temps de trajet a partir d’observations
  2. Predire le temps de trajet de demain
  3. Calculer des probabilites (ex: P(trajet < 18 min))

Modelisation

Nous modelerons le temps de trajet avec une distribution Gaussienne :

\[\text{temps} \sim \mathcal{N}(\mu, \tau^{-1})\]

  • \(\mu\) : moyenne (temps moyen de trajet)
  • \(\tau\) : precision (inverse de la variance)

3. Distributions Conjuguées

Théorie

En inference bayesienne, une distribution a priori conjuguee permet une mise a jour analytique simple :

Vraisemblance Prior Conjugue Posterieur
Gaussian (moyenne) Gaussian Gaussian
Gaussian (precision) Gamma Gamma
Bernoulli Beta Beta
Discrete Dirichlet Dirichlet

Pour notre modèle cycliste

  • Moyenne : prior Gaussian vague \(\mu \sim \mathcal{N}(15, 100)\)
  • Precision : prior Gamma \(\tau \sim \text{Gamma}(2, 0.5)\)

4. Modèle Simple : Une Gaussienne

Nous allons maintenant construire notre premier modèle Infer.NET pour le scénario du cycliste. Ce modèle simple utilise une seule Gaussienne pour capturer la distribution des temps de trajet.

Définition des priors

Le code suivant définit deux variables aléatoires avec leurs distributions a priori :

  1. dureeMoyenne : Prior Gaussien centre sur 15 minutes avec une precision très faible (0.01), ce qui correspond a une variance de 100. Ce prior “vague” exprime notre incertitude initiale sur la vraie moyenne.

  2. bruitTrafic : Prior Gamma sur la precision (inverse de la variance). Une distribution Gamma est toujours positive, ce qui convient pour une precision. Les paramètres shape=2, scale=0.5 donnent une esperance de 1 et permettent un large eventail de valeurs.

Pourquoi precision plutot que variance ? Infer.NET utilise la parametrisation en precision car elle simplifie les formules de mise a jour bayesienne pour les Gaussiennes conjuguees.

// Definition du modele
// Prior sur la moyenne : Gaussian vague centree sur 15 min
Variable<double> dureeMoyenne = Variable.GaussianFromMeanAndPrecision(15, 0.01);  // precision = 0.01 -> variance = 100

// Prior sur la precision (inverse de la variance)
Variable<double> bruitTrafic = Variable.GammaFromShapeAndScale(2, 0.5);
Console.WriteLine("Modele Gaussien cycliste defini.");
Modele Gaussien cycliste defini.

Observations

Nous definissons maintenant trois variables aléatoires représentant les temps de trajet observes. Chaque trajet suit une distribution Gaussienne avec la même moyenne et precision (paramètres partages).

La méthode ObservedValue fixe les valeurs observées. Cela “ancre” le modèle aux données et permet a l’inference de mettre a jour les posterieurs en consequence.

Jour Temps observe
Lundi 13 min
Mardi 17 min
Mercredi 16 min

Moyenne empirique : (13 + 17 + 16) / 3 = 15.33 min

// Definition des temps de trajet observes
Variable<double> dureeLundi = Variable.GaussianFromMeanAndPrecision(dureeMoyenne, bruitTrafic);
Variable<double> dureeMardi = Variable.GaussianFromMeanAndPrecision(dureeMoyenne, bruitTrafic);
Variable<double> dureeMercredi = Variable.GaussianFromMeanAndPrecision(dureeMoyenne, bruitTrafic);

// Observations
dureeLundi.ObservedValue = 13;
dureeMardi.ObservedValue = 17;
dureeMercredi.ObservedValue = 16;
Console.WriteLine("Donnees observees initialisees.");
Donnees observees initialisees.

Inference des posterieurs

L’étape d’inference utilise InferenceEngine pour calculer les distributions a posteriori des paramètres inconnus (dureeMoyenne et bruitTrafic) etant données les observations.

Ce que fait l’algorithme : 1. Combine les priors avec la vraisemblance des données 2. Propage les messages dans le graphe de facteurs (Expectation Propagation) 3. Itere jusqu’a convergence 4. Retourne les distributions posterieures

Note : CompilerChoice.Roslyn spécifie d’utiliser le compilateur Roslyn pour générer le code d’inference, ce qui est necessaire dans les environnements .NET Interactive.

// Inference
InferenceEngine moteur = new InferenceEngine();
moteur.Compiler.CompilerChoice = CompilerChoice.Roslyn;
moteur.ShowFactorGraph = true;  // Active la generation du graphe de facteurs

Gaussian moyennePosterieure = moteur.Infer<Gaussian>(dureeMoyenne);
Gamma bruitPosterieur = moteur.Infer<Gamma>(bruitTrafic);

Console.WriteLine($"Moyenne a posteriori : {moyennePosterieure}");
Console.WriteLine($"Precision a posteriori : {bruitPosterieur}");
Console.WriteLine($"\nMoyenne estimee : {moyennePosterieure.GetMean():F2} min");
Console.WriteLine($"Ecart-type du bruit : {Math.Sqrt(1/bruitPosterieur.GetMean()):F2} min");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
Moyenne a posteriori : Gaussian(15,33, 1,32)
Precision a posteriori : Gamma(2,242, 0,2445)[mean=0,5482]

Moyenne estimee : 15,33 min
Ecart-type du bruit : 1,35 min

Visualisation du graphe de facteurs du modèle gaussien.

// Visualisation du graphe de facteurs
display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_07_23_26_01_29_20_15.svg
Model node0 15 node1 Gaussian node0->node1 mean node3 vdouble2 node1->node3 node2 0,01 node2->node1 precision node4 Gaussian node3->node4 mean node7 Gaussian node3->node7 mean node9 Gaussian node3->node9 mean node6 13 node4->node6 node5 vdouble5 node5->node4 precision node5->node7 precision node5->node9 precision node8 17 node7->node8 node10 16 node9->node10 node11 2 node12 Sample node11->node12 shape node12->node5 node13 0,5 node13->node12 scale

Graphe de facteurs du modèle cycliste simple

Le graphe ci-dessus represente la structure du modèle probabiliste :

Élément Représentation Rôle
Cercles Variables aléatoires dureeMoyenne, bruitTrafic, dureeLundi, etc.
Carres Facteurs (distributions) GaussianFromMeanAndPrecision, Gamma
Fleches Dependances Direction du flux d’information

Lecture du graphe : - Les priors (dureeMoyenne ~ Gaussian, bruitTrafic ~ Gamma) sont les variables parentes - Les observations (trajets observes) sont conditionnees par les mêmes paramètres partages - L’inference propage l’information des observations vers les priors pour calculer les posterieurs

Ce graphe illustre le principe de plate notation : les trajets partagent les mêmes paramètres.

Analyse des résultats

Sortie obtenue : - Moyenne a posteriori : Gaussian(15,33, 1,32) → moyenne ~15.3 min avec variance 1.32 (précision 1/1.32 ≈ 0.76, écart-type ≈ 1.15 min) - Precision a posteriori : Gamma(2,242, 0,2445) → precision moyenne ~0.55

Interpretation :

Aspect Valeur Explication
Moyenne 15.33 Proche de la moyenne empirique (13+17+16)/3 = 15.33 ✓
Écart-type bruit 1.35 Variabilite typique entre trajets
Variance posterior 1.32 Plus concentré que le prior (variance 100, précision 0.01)

Remarquez le message “Iterating: ….50” qui indique que l’algorithme Expectation Propagation a effectue 50 itérations pour converger. Contrairement au modèle Bernoulli exact du notebook 1, les modèles Gaussiens avec precision inconnue necessitent une inference iterative.

5. Prediction du Temps de Demain

Distribution predictive

Pour predire le temps de demain, nous creons une nouvelle variable aléatoire dureeDemain qui depend des mêmes paramètres (moyenne et precision) que les observations.

La distribution inferee pour dureeDemain est appelee distribution predictive. Elle integre : - L’incertitude sur la moyenne estimee - La variabilite inherente des trajets (bruit)

Cette distribution est plus large que le posterior de la moyenne car elle additionne les deux sources d’incertitude.

// Prediction pour demain
Variable<double> dureeDemain = Variable.GaussianFromMeanAndPrecision(dureeMoyenne, bruitTrafic);
Gaussian distribDemain = moteur.Infer<Gaussian>(dureeDemain);

Console.WriteLine($"Prediction demain : {distribDemain}");
Console.WriteLine($"Temps moyen estime : {distribDemain.GetMean():F2} min");
Console.WriteLine($"Ecart-type de la prediction : {Math.Sqrt(distribDemain.GetVariance()):F2} min");

// Intervalle de confiance a 95%
double mean = distribDemain.GetMean();
double std = Math.Sqrt(distribDemain.GetVariance());
Console.WriteLine($"\nIntervalle de confiance 95% : [{mean - 1.96*std:F1}, {mean + 1.96*std:F1}] min");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
Prediction demain : Gaussian(15,33, 4,613)
Temps moyen estime : 15,33 min
Ecart-type de la prediction : 2,15 min

Intervalle de confiance 95% : [11,1, 19,5] min

Interpretation de la prediction

Sortie : Gaussian(15,33, 4,613) avec écart-type 2.15 min

Pourquoi l’écart-type de la prediction (2.15) est plus grand que celui du bruit (1.35) ?

La prediction combine deux sources d’incertitude :

  1. Incertitude epistemique : Nous ne connaissons pas exactement la vraie moyenne (notre estimation a une variance)
  2. Incertitude aléatoire : Même si nous connaissions parfaitement la moyenne, chaque trajet varie autour d’elle

\[\text{Var}(\text{prediction}) = \text{Var}(\mu) + \mathbb{E}[1/\tau] \approx 1.32 + 3.29 = 4.61\]

L’intervalle de confiance 95% [11.1, 19.5] min est assez large avec seulement 3 observations. Plus de données le reduiront.

6. Calcul de Probabilites

Calcul de probabilites avec Infer.NET

Une fois la distribution predictive obtenue, Infer.NET permet de calculer des probabilites conditionnelles directement.

Syntaxe : La comparaison d’une variable aléatoire avec une constante créé une nouvelle variable Bernoulli :

Variable<bool> condition = dureeDemain < 18.0;
Bernoulli prob = engine.Infer<Bernoulli>(condition);
double p = prob.GetProbTrue();  // P(dureeDemain < 18)

C’est équivalent a integrer la distribution predictive :

\[P(\text{demain} < 18) = \int_{-\infty}^{18} p(\text{demain}) \, d\text{demain}\]

Mais Infer.NET fait ce calcul automatiquement via l’inference.

// Quelle est la probabilite que le trajet dure moins de 18 minutes ?
Bernoulli probMoinsDe18 = moteur.Infer<Bernoulli>(dureeDemain < 18.0);
Console.WriteLine($"P(trajet < 18 min) = {probMoinsDe18.GetProbTrue():F2}");

// Et moins de 15 minutes ?
Bernoulli probMoinsDe15 = moteur.Infer<Bernoulli>(dureeDemain < 15.0);
Console.WriteLine($"P(trajet < 15 min) = {probMoinsDe15.GetProbTrue():F2}");

// Entre 14 et 18 minutes ?
Variable<bool> entre14et18 = (dureeDemain > 14.0) & (dureeDemain < 18.0);
Bernoulli probEntre = moteur.Infer<Bernoulli>(entre14et18);
Console.WriteLine($"P(14 < trajet < 18 min) = {probEntre.GetProbTrue():F2}");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
P(trajet < 18 min) = 0,89
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
P(trajet < 15 min) = 0,44
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
P(14 < trajet < 18 min) = 0,65

Interpretation des probabilites

Résultats obtenus : - P(trajet < 18 min) = 0.89 : Forte probabilite d’arriver en moins de 18 minutes - P(trajet < 15 min) = 0.44 : Environ 1 chance sur 2 d’arriver en moins de 15 minutes - P(14 < trajet < 18 min) = 0.65 : Probabilite moderee d’etre dans cette fourchette

Applications pratiques :

Question Probabilite Decision
“Puis-je arriver a l’heure si je pars 18 min avant ?” 89% Oui, marge confortable
“Et si je pars seulement 15 min avant ?” 44% Risque, 1 fois sur 2 en retard
“Fenêtre optimale de depart ?” 65% entre 14-18 min Viser ~16-17 min de marge

7. Restructuration avec Classes

Pour des modèles plus complexes, il est preferable d’encapsuler le code dans des classes reutilisables. Cette architecture permet :

Avantages de la restructuration

Avantage Description
Reutilisabilite Les mêmes classes servent pour différents jeux de données
Separation des responsabilites Entraînement et prediction dans des classes distinctes
Apprentissage en ligne Les posterieurs d’une exécution deviennent les priors de la suivante
Maintenabilite Le code du modèle est isole et testable

Architecture proposee

DonneesCycliste (struct)
    |-- DistribMoyenne : Gaussian
    |-- DistribBruitTraffic : Gamma

CyclisteBase (classe abstraite)
    |-- MoteurInference
    |-- Moyenne, Bruit (variables)
    |-- MoyenneAPriori, BruitAPriori (priors)
    |-- CreationModeleBayesien()
    |-- DefinirDistributions()
    
    EntrainementCycliste : CyclisteBase
        |-- TempsDeTrajet[] (observations)
        |-- CalculePosterieurs() -> DonneesCycliste
    
    PredictionCycliste : CyclisteBase
        |-- demainTemps (variable a predire)
        |-- EstimerTempsDemain() -> Gaussian
        |-- EstimerTempsDemainInferieurA(seuil) -> Bernoulli

Flux de données

  1. Initialisation : Créer des priors vagues (DonneesCycliste)
  2. Entraînement : EntrainementCycliste.CalculePosterieurs(observations) → nouveaux posterieurs
  3. Prediction : PredictionCycliste.DefinirDistributions(posterieurs) puis EstimerTempsDemain()
  4. Apprentissage en ligne : Les posterieurs de l’étape 2 deviennent les priors de l’étape suivante

Aparté : Gaussienne Tronquee

La Gaussienne tronquee est une distribution ou les valeurs sont contraintes a un intervalle. C’est utile quand les données ne peuvent pas prendre certaines valeurs (ex: temps de trajet > 0, temperature entre 0 et 100C).

Syntaxe Infer.NET :

// Gaussienne positive (tronquee a 0)
Variable<double> y = Variable.GaussianFromMeanAndPrecision(mean, precision);
Variable.ConstrainPositive(y);

Exemple : Temps de trajet (toujours positif)

// Exemple de Gaussienne tronquee - cas simple

Console.WriteLine("=== Gaussienne Tronquee ===\n");

// Cas 1 : Prediction avec contrainte de positivite
// On connait la moyenne et precision, on veut predire une valeur positive

Variable<double> moyenneConnue = Variable.Observed(4.0);
Variable<double> precisionConnue = Variable.Observed(1.0);

// Variable aleatoire avec contrainte de positivite
Variable<double> tempsPrediction = Variable.GaussianFromMeanAndPrecision(moyenneConnue, precisionConnue);
Variable.ConstrainPositive(tempsPrediction);

InferenceEngine moteurTronque = new InferenceEngine(new ExpectationPropagation());
moteurTronque.Compiler.CompilerChoice = CompilerChoice.Roslyn;

// Comparer les distributions avec et sans troncature
Console.WriteLine("Distribution Gaussienne standard : N(4, 1)");
Console.WriteLine($"  -> Moyenne = 4.0, Ecart-type = 1.0");
Console.WriteLine($"  -> P(temps < 0) = {Gaussian.FromMeanAndVariance(4, 1).GetProbLessThan(0):F4}");

var distribTronquee = moteurTronque.Infer<Gaussian>(tempsPrediction);
Console.WriteLine($"\nDistribution Gaussienne tronquee [0, +inf) :");
Console.WriteLine($"  -> {distribTronquee}");
Console.WriteLine($"  -> Moyenne ajustee = {distribTronquee.GetMean():F3}");

// Cas 2 : Effet de la troncature proche de zero
Console.WriteLine("\n--- Effet pres de la borne ---");
Variable<double> moyenneProche = Variable.Observed(1.0);  // Proche de 0
Variable<double> precisionProche = Variable.Observed(0.25);  // Variance = 4, ecart-type = 2
Variable<double> tempsProche = Variable.GaussianFromMeanAndPrecision(moyenneProche, precisionProche);
Variable.ConstrainPositive(tempsProche);

var distribProche = moteurTronque.Infer<Gaussian>(tempsProche);
Console.WriteLine($"N(1, 4) standard : P(temps < 0) = {Gaussian.FromMeanAndVariance(1, 4).GetProbLessThan(0):F3} (~31%)");
Console.WriteLine($"N(1, 4) tronquee : {distribProche}");
Console.WriteLine($"  -> Moyenne ajustee de 1.0 a {distribProche.GetMean():F2} (effet de la troncature)");
=== Gaussienne Tronquee ===

Distribution Gaussienne standard : N(4, 1)
  -> Moyenne = 4.0, Ecart-type = 1.0
  -> P(temps < 0) = 0,0000
Compiling model...done.

Distribution Gaussienne tronquee [0, +inf) :
  -> Gaussian(4, 0,9995)
  -> Moyenne ajustee = 4,000

--- Effet pres de la borne ---
Compiling model...done.
N(1, 4) standard : P(temps < 0) = 0,309 (~31%)
N(1, 4) tronquee : Gaussian(2,018, 1,945)
  -> Moyenne ajustee de 1.0 a 2,02 (effet de la troncature)

Interpretation des résultats de la Gaussienne tronquee

Cas 1 : N(4, 1) tronquee a [0, +inf)

Aspect Standard Tronquee Différence
Moyenne 4.0 4.0 Negligeable
P(x < 0) ~0% 0% -

Quand la moyenne est loin de la borne (4 >> 0), la troncature a peu d’effet car la probabilite d’etre negatif est déjà quasi-nulle.

Cas 2 : N(1, 4) tronquee a [0, +inf)

Aspect Standard Tronquee Différence
Moyenne 1.0 2.02 +1.02
P(x < 0) 31% 0% -31%

Quand la moyenne est proche de la borne, la troncature redistribue la masse de probabilite des valeurs negatives vers les valeurs positives, ce qui : - Augmente la moyenne (de 1.0 a 2.02) - Reduit la variance effective

Application : Pour modeliser un temps de trajet (toujours positif), la Gaussienne tronquee évite les predictions absurdes comme “temps = -2 min”.

Quand utiliser la Gaussienne Tronquee ?

Situation Intervalle Exemple
Quantites positives [0, +inf) Temps, distances, prix
Probabilites [0, 1] Taux de conversion, precision
Temperatures physiques [-273.15, +inf) Temperature en Celsius
Notes/Scores [0, 20] Notes d’examen

Avantages de la troncature :

  1. Realisme : Les predictions respectent les contraintes physiques
  2. Meilleure estimation : La variance n’est pas “gaspillee” sur des valeurs impossibles
  3. Intervalles de confiance valides : Pas d’aberrations dans les predictions

Implémentation : Structure de données et classe de base

Le code ci-dessous définit :

  1. DonneesCycliste : Structure pour stocker les distributions posterieures (moyenne et precision)
  2. CyclisteBase : Classe abstraite contenant les éléments communs a l’entraînement et la prediction

Points techniques importants :

Élément Explication
Variable.New<Gaussian>() Créé un “slot” pour recevoir une distribution comme valeur observée
Variable.Random<double, Gaussian>() Tire une valeur aléatoire selon une distribution Gaussienne
MoteurInference.Compiler.CompilerChoice Configure le compilateur pour l’environnement .NET Interactive

Cette architecture orientee objet permet de separer la logique d’entraînement de celle de prediction.

// Structure pour stocker les posterieurs
public struct DonneesCycliste
{
    public Gaussian DistribMoyenne;
    public Gamma DistribBruitTraffic;
    
    public DonneesCycliste(Gaussian moyenne, Gamma precision)
    {
        DistribMoyenne = moyenne;
        DistribBruitTraffic = precision;
    }
}

// Classe de base avec les elements communs
public class CyclisteBase
{
    public InferenceEngine MoteurInference;
    protected Variable<double> Moyenne;
    protected Variable<double> Bruit;
    protected Variable<Gaussian> MoyenneAPriori;
    protected Variable<Gamma> BruitAPriori;

    public virtual void CreationModeleBayesien()
    {
        MoyenneAPriori = Variable.New<Gaussian>();
        BruitAPriori = Variable.New<Gamma>();
        Moyenne = Variable.Random<double, Gaussian>(MoyenneAPriori);
        Bruit = Variable.Random<double, Gamma>(BruitAPriori);

        if (MoteurInference == null)
        {
            MoteurInference = new InferenceEngine(new ExpectationPropagation());
            MoteurInference.Compiler.CompilerChoice = CompilerChoice.Roslyn;
            MoteurInference.ShowFactorGraph = true;  // Active la generation du graphe
        }
    }

    public virtual void DefinirDistributions(DonneesCycliste distribsApriori)
    {
        MoyenneAPriori.ObservedValue = distribsApriori.DistribMoyenne;
        BruitAPriori.ObservedValue = distribsApriori.DistribBruitTraffic;
    }
}

Console.WriteLine("Classe CyclisteBase definie.");
Classe CyclisteBase definie.

Implémentation : Classes d’entraînement et de prediction

Deux classes heritent de CyclisteBase :

EntrainementCycliste : - Utilise un VariableArray<double> pour un nombre arbitraire d’observations - Range et Variable.ForEach permettent d’itérer sur les observations de maniere declarative - CalculePosterieurs() retourne les distributions mises a jour

PredictionCycliste : - Ajoute une variable demainTemps pour la prediction future - EstimerTempsDemain() infere la distribution du temps demain - EstimerTempsDemainInferieurA(seuil) calcule P(temps < seuil)

Pattern cle : Separer entraînement et prediction permet de reutiliser les posterieurs comme priors dans un workflow d’apprentissage en ligne.

// Classe d'entrainement
public class EntrainementCycliste : CyclisteBase
{
    protected VariableArray<double> TempsDeTrajet;
    protected Variable<int> NombreDeTrajets;

    public override void CreationModeleBayesien()
    {
        base.CreationModeleBayesien();
        NombreDeTrajets = Variable.New<int>();
        Range indiceTrajet = new Range(NombreDeTrajets);
        TempsDeTrajet = Variable.Array<double>(indiceTrajet);
        using (Variable.ForEach(indiceTrajet))
        {
            TempsDeTrajet[indiceTrajet] = Variable.GaussianFromMeanAndPrecision(Moyenne, Bruit);
        }
    }

    public DonneesCycliste CalculePosterieurs(double[] donneesObservees)
    {
        DonneesCycliste posterieurs;
        NombreDeTrajets.ObservedValue = donneesObservees.Length;
        TempsDeTrajet.ObservedValue = donneesObservees;
        posterieurs.DistribMoyenne = MoteurInference.Infer<Gaussian>(Moyenne);
        posterieurs.DistribBruitTraffic = MoteurInference.Infer<Gamma>(Bruit);
        return posterieurs;
    }
}

// Classe de prediction
public class PredictionCycliste : CyclisteBase
{
    public Variable<double> demainTemps;

    public override void CreationModeleBayesien()
    {
        base.CreationModeleBayesien();
        demainTemps = Variable.GaussianFromMeanAndPrecision(Moyenne, Bruit);
    }

    public Gaussian EstimerTempsDemain()
    {
        return MoteurInference.Infer<Gaussian>(demainTemps);
    }

    public Bernoulli EstimerTempsDemainInferieurA(double duree)
    {
        return MoteurInference.Infer<Bernoulli>(demainTemps < duree);
    }
}

Console.WriteLine("Classes EntrainementCycliste et PredictionCycliste definies.");
Classes EntrainementCycliste et PredictionCycliste definies.

Utilisation des classes : Entraînement

Nous utilisons maintenant les classes définies pour entraîner le modèle sur un jeu de données plus complet (9 observations).

Workflow : 1. Définir les données d’entraînement 2. Créer les priors vagues (haute incertitude initiale) 3. Instancier et configurer le modèle d’entraînement 4. Appeler CalculePosterieurs() pour obtenir les distributions mises a jour

Les 9 observations {13, 17, 20, 25, 16, 11, 16, 14, 12.5} incluent des valeurs plus extremes que l’exemple simple (notamment 25 min), ce qui va affecter les posterieurs.

// Utilisation des classes
double[] donneesTrajets = new[] { 13.0, 17.0, 20.0, 25.0, 16.0, 11.0, 16.0, 14.0, 12.5 };

// Priors vagues
DonneesCycliste mesDistributions = new DonneesCycliste(
    Gaussian.FromMeanAndPrecision(15, 0.01),  // Prior moyenne
    Gamma.FromShapeAndScale(2, 0.5));         // Prior precision

// Entrainement
EntrainementCycliste entrainement = new EntrainementCycliste();
entrainement.CreationModeleBayesien();
entrainement.DefinirDistributions(mesDistributions);
DonneesCycliste posterieurs = entrainement.CalculePosterieurs(donneesTrajets);

Console.WriteLine($"Moyenne a posteriori : {posterieurs.DistribMoyenne}");
Console.WriteLine($"Precision a posteriori : {posterieurs.DistribBruitTraffic}");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
Moyenne a posteriori : Gaussian(16,04, 1,772)
Precision a posteriori : Gamma(5,114, 0,01585)[mean=0,08106]

Interpretation des résultats d’entraînement

Données utilisees : 9 observations {13, 17, 20, 25, 16, 11, 16, 14, 12.5}

Résultats : - Moyenne a posteriori : Gaussian(16,04, 1,772) → moyenne ~16 min avec variance 1.77 (précision ≈ 0.56) - Precision a posteriori : Gamma(5,114, 0,01585) → precision moyenne ~0.08

Comparaison avec le modèle simple (3 observations) :

Aspect 3 observations 9 observations
Moyenne estimee 15.33 min 16.04 min
Variance du posterior (moyenne) 1.32 1.77
Écart-type bruit 1.35 min 3.51 min

Avec plus de données (dont certaines extremes comme 25 min), le modèle : - Ajuste la moyenne vers le haut (16 vs 15.33) - Augmente l’incertitude sur le bruit car les données sont plus dispersees - Perd en confiance sur la moyenne (variance 1.77 > 1.32 : le posterior s’élargit, le bruit plus fort l’emporte sur le gain d’observations)

Visualisation du graphe de facteurs du modèle avec trajectoire.

// Visualisation du graphe de facteurs du modele avec tableaux
display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_07_23_26_01_29_26_76.svg
Model node0 Gaussian(15, 100) node1 Random node0->node1 dist node2 vdouble24 node1->node2 node3 Gaussian node2->node3 mean node5 vdouble[]0[index0] node3->node5 node4 vdouble25 node4->node3 precision node6 Gamma(2, 0,5)[mean=1] node7 Random node6->node7 dist node7->node4

Graphe de facteurs avec VariableArray

Ce graphe montre l’utilisation de Range et VariableArray pour gerer un nombre variable d’observations :

Structure Code Infer.NET Représentation graphique
Range new Range(NombreDeTrajets) Itération sur les indices
VariableArray Variable.Array<double>(range) Rectangle englobant (plate)
ForEach Variable.ForEach(indiceTrajet) Facteur repetitif

Avantages de cette structure : - Le modèle s’adapte automatiquement au nombre d’observations - L’inference est optimisee pour les tableaux (message passing vectorise) - Les paramètres partages (Moyenne, Bruit) sont clairement identifies

Le graphe montre comment les 9 observations sont liees aux mêmes priors via une structure de plate.

Utilisation des classes : Prediction

La prediction utilise les posterieurs de l’entraînement comme priors. C’est le coeur de l’inference bayesienne séquentielle :

Priors vagues -> Entrainement -> Posterieurs = Nouveaux priors -> Prediction

Cette chaîne permet de propager l’information apprise vers les nouvelles predictions, tout en quantifiant correctement l’incertitude residuelle.

// Prediction avec les posterieurs
PredictionCycliste prediction = new PredictionCycliste();
prediction.CreationModeleBayesien();
prediction.DefinirDistributions(posterieurs);  // Utiliser les posterieurs comme priors

Gaussian predictionDemain = prediction.EstimerTempsDemain();
Console.WriteLine($"Prediction demain : {predictionDemain}");
Console.WriteLine($"Ecart-type : {Math.Sqrt(predictionDemain.GetVariance()):F2} min");

double probMoins18 = prediction.EstimerTempsDemainInferieurA(18).GetProbTrue();
Console.WriteLine($"P(trajet < 18 min) = {probMoins18:F2}");
Compiling model...done.
Prediction demain : Gaussian(16,04, 17,11)
Ecart-type : 4,14 min
Compiling model...done.
P(trajet < 18 min) = 0,68

Interpretation de la prediction

Prediction : Gaussian(16,04, 17,11) avec écart-type 4.14 min

Pourquoi l’écart-type est-il plus grand (4.14 min) qu’avec 3 observations (2.15 min) ?

Cela peut sembler contre-intuitif : plus de données, mais plus d’incertitude ? L’explication :

  1. Données plus dispersees : Les 9 observations incluent des extremes (11 a 25 min)
  2. Meilleure estimation de la variance reelle : Le modèle a appris que les trajets varient beaucoup
  3. Honnetete bayesienne : Le modèle reflète fidelement l’incertitude observée dans les données

Probabilite P(trajet < 18 min) = 0.68 : Avec ces données plus realistes, la confiance d’arriver en moins de 18 min est reduite (68% vs 89% avec 3 observations seulement).

8. Apprentissage en Ligne

L’apprentissage en ligne permet de mettre a jour le modèle incrementalement avec de nouvelles données, sans retraiter tout l’historique.

Principe : Les posterieurs de la semaine précédente deviennent les priors de la semaine suivante.

Demonstration de l’apprentissage en ligne

Le code suivant illustre le cycle d’apprentissage incrementiel :

  1. Les posterieurs de la semaine 1 (posterieurs) deviennent les priors de la semaine 2
  2. De nouvelles observations sont integrees via CalculePosterieurs()
  3. Les nouveaux posterieurs (posterieursSemaine2) refletent toute l’information accumulee

Avantage computationnel : Nous n’avons pas besoin de retraiter les 9 observations originales. Les posterieurs “resument” cette information de maniere suffisante statistiquement.

// Nouvelle semaine de donnees
double[] semaineSuivante = new double[] { 18, 25, 30, 14, 11 };

// Utiliser les posterieurs comme nouveaux priors
entrainement.DefinirDistributions(posterieurs);
DonneesCycliste posterieursSemaine2 = entrainement.CalculePosterieurs(semaineSuivante);

Console.WriteLine("=== Apres semaine 2 ===");
Console.WriteLine($"Moyenne a posteriori : {posterieursSemaine2.DistribMoyenne}");
Console.WriteLine($"Precision a posteriori : {posterieursSemaine2.DistribBruitTraffic}");

// Nouvelle prediction
prediction.DefinirDistributions(posterieursSemaine2);
Gaussian nouvellePrediction = prediction.EstimerTempsDemain();
Console.WriteLine($"\nNouvelle prediction demain : {nouvellePrediction}");
Console.WriteLine($"Ecart-type : {Math.Sqrt(nouvellePrediction.GetVariance()):F2} min");
Iterating: 
.........|.........|.........|.........|.........| 50
=== Apres semaine 2 ===
Moyenne a posteriori : Gaussian(16,92, 1,436)
Precision a posteriori : Gamma(7,012, 0,005291)[mean=0,0371]

Nouvelle prediction demain : Gaussian(16,92, 32,87)
Ecart-type : 5,73 min

Analyse de l’apprentissage en ligne

Nouvelles données semaine 2 : {18, 25, 30, 14, 11} - incluant des trajets très longs (25, 30 min)

Evolution des posterieurs :

Paramètre Après semaine 1 Après semaine 2 Evolution
Moyenne 16.04 min 16.92 min +0.88 min
Variance de la moyenne 1.77 1.44 Légère hausse de confiance (variance ↓)
Écart-type bruit 3.51 min 5.19 min +1.68 min

Observations clés :

  1. Moyenne en hausse : Les trajets longs (25, 30 min) tirent la moyenne vers le haut
  2. Variance du bruit en hausse : Le modèle apprend que les trajets sont encore plus variables que prevu
  3. Accumulation des données : 14 observations totales (9 + 5) sans retraitement

Avantage de l’apprentissage en ligne : - Pas besoin de stocker toutes les observations historiques - Les posterieurs resument toute l’information passee - Mise a jour incrementale efficace pour les systèmes en production

Attention : Si la distribution des données change radicalement (ex: nouveau trajet), les anciens posterieurs peuvent freiner l’adaptation. Dans ce cas, “oublier” partiellement le passe peut etre necessaire.

Exercice : Detection d’un changement de regime par apprentissage en ligne

Le cycliste a change d’itineraire sans vous prevenir ! En observant l’evolution des posteriors au fil des semaines, vous devez detecter a quel moment le changement s’est produit.

Objectif : Simulez 4 semaines de données ou un changement d’itineraire intervient entre la semaine 2 et la semaine 3, puis utilisez l’apprentissage en ligne pour suivre l’evolution de la moyenne a posteriori.

Contexte : - Semaines 1 et 2 : Itineraire habituel, temps ~ N(15, 4) - Semaines 3 et 4 : Nouvel itineraire (plus long), temps ~ N(25, 5)

Étapes : 1. Generez les données pour les 4 semaines (5 observations par semaine) 2. Utilisez et pour mettre a jour iterativement 3. Après chaque semaine, affichez la moyenne a posteriori et son écart-type 4. Identifiez a quelle semaine le changement est detectable (moyenne posterior qui depasse un seuil)

Indices : - Indice 1 : Les données de la semaine 1 sont déjà dans (cellule 9). Repartez des posteriors déjà calcules. - Indice 2 : Pour générer des données gaussiennes, vous pouvez utiliser . - Étape 3 : Observez comment la moyenne posterior passe progressivement de ~15 a ~25 min. - Étape 4 : Un seuil de detection possible est lorsque la moyenne posterior sort de l’intervalle [moyenne_semaine2 +/- 2*std].

// Exercice : Detection d'un changement de regime par apprentissage en ligne

// TODO 1 : Definir les 4 semaines de donnees
// Indice : semaines 1-2 ~ N(15, 4), semaines 3-4 ~ N(25, 5)
// double[] semaine1 = { 14.2, 16.1, 15.3, 13.8, 16.5 };
// double[] semaine2 = { ... };
// double[] semaine3 = { ... }; // Nouvel itineraire
// double[] semaine4 = { ... }; // Nouvel itineraire

// TODO 2 : Partir des posteriors de la semaine 1 (deja calcules)
// Indice : Utilisez entrainement.DefinirDistributions(posterieurs)

// TODO 3 : Pour chaque semaine suivante, mettre a jour et afficher
// Indice : for (int sem = 2; sem <= 4; sem++) { ... }
//          Afficher : moyenne posterior, ecart-type, et comparaison au seuil

// TODO 4 : Identifier la semaine du changement de regime
// Indice : Quand la moyenne posterior depasse-t-elle 2*ecart-types de sa valeur semaine 2 ?

Console.WriteLine("Exercice a completer : detection de changement de regime");
Exercice a completer : detection de changement de regime

9. Problème : Événements Extraordinaires

Observation

Nos données contiennent parfois des temps de trajet anormalement longs (25, 30 min) dus a des événements extraordinaires : - Accident sur la route - Meteo extreme - Travaux

Solution

Un modèle de mélange de Gaussiennes peut capturer cette bimodalite : - Composante 1 : Trajets ordinaires (~15 min) - Composante 2 : Trajets extraordinaires (~30 min)

\[p(x) = \pi_1 \cdot \mathcal{N}(x|\mu_1, \tau_1^{-1}) + \pi_2 \cdot \mathcal{N}(x|\mu_2, \tau_2^{-1})\]

ou \(\pi_1 + \pi_2 = 1\) sont les poids du melange.

10. Modèle de Melange de Gaussiennes

Nous allons maintenant implementer le modèle de mélange. L’architecture objet similaire a celle du modèle simple facilitera la reutilisation et l’apprentissage en ligne.

Références canoniques

L’architecture Variable.Switch(pi, [g1, g2, ...]) pour les mélanges gaussiens, ainsi que la comparaison EP vs VMP sur le même modèle, reposent sur la littérature classique :

  • Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. §9.2 — Mixtures of Gaussians (modèle génératif, EM), §9.2.2 — EM pour les mixtures (formules de mise à jour, preuves de convergence), §10.7 — Expectation Propagation (variantes EP pour mélanges et lois non-conjuguées).
  • Murphy, K. P. (2012). Machine Learning: A Probabilistic Perspective. MIT Press. §11.4 — The EM algorithm (cadre unifié EM) et §11.3.1 — Unidentifiability (identifiabilité, label switching post-hoc).
  • McLachlan, G. J., & Peel, D. (2000). Finite Mixture Models. Wiley. Référence complète sur les mélanges : construction, estimation, sélection de modèles, applications (clustering, classification, densité).
  • Dempster, A. P., Laird, N. M., & Rubin, D. B. (1977). Maximum likelihood from incomplete data via the EM algorithm. Journal of the Royal Statistical Society, Series B, 39(1), 1-38. Le papier fondateur de l’algorithme EM — démonstration de convergence, propriétés statistiques, applications générales.
  • Tipping, M. E., & Bishop, C. M. (1999). Mixtures of probabilistic principal component analyzers. Neural Computation 11(2), 443-482. Variante bayésienne variationnelle pour mélanges de modèles linéaires — pont avec l’approche VMP d’Infer.NET.
  • Ghahramani, Z., & Beal, M. J. (2000). Variational inference for Bayesian mixtures of factor analysers. NIPS 1999. Variational Bayes pour mélanges, formalise les priors automatiques sur les paramètres de covariance (le pendant VB de l’EM).

MBML ne traite ni les mélanges gaussiens, ni l’algorithme EM (vérifié sur la table des matières : pas de chapitre « Mixture Models » ou « EM »). Le chapitre le plus proche est Ch.8 k-means, qui est du hard clustering non-probabiliste — conceptuellement parent mais algorithmiquement distinct. La source canonique est donc Bishop PRML §9.2 + Murphy ML §11.4, pas MBML — cf. sub-issue #8205 (correction du mapping fondateur #8087).

Architecture du modèle de mélange

Le modèle de mélange necessite une structure plus complexe que le modèle simple :

Structure DonneesCyclisteMixte : - DistribMoyenne[] : Un prior Gaussien par composante - DistribBruitTraffic[] : Un prior Gamma par composante - DistribMixe : Distribution Dirichlet sur les poids du melange

Classe CyclisteBaseMixte : - NombreComposantes : Nombre de modes dans le melange - Moyennes, Bruits : Tableaux de variables pour chaque composante - Mixe : Vecteur de probabilites (somme = 1)

Changement d’algorithme : Nous utilisons VariationalMessagePassing (VMP) au lieu d’Expectation Propagation. VMP est plus adapte aux modèles avec variables latentes discretes (l’assignation aux composantes).

// Structure pour le modele mixte
public struct DonneesCyclisteMixte
{
    public Gaussian[] DistribMoyenne;       // Une par composante
    public Gamma[] DistribBruitTraffic;     // Une par composante
    public Dirichlet DistribMixe;           // Poids du melange
}

// Classe de base pour le modele mixte
public class CyclisteBaseMixte
{
    public InferenceEngine MoteurInference;
    protected int NombreComposantes = 2;
    protected VariableArray<Gaussian> MoyennesAPriori;
    protected VariableArray<Gamma> BruitsAPriori;
    protected Variable<Dirichlet> MixeAPriori;
    protected VariableArray<double> Moyennes;
    protected VariableArray<double> Bruits;
    protected Variable<Vector> Mixe;

    public virtual void CreationModeleBayesien()
    {
        Range indiceComposants = new Range(NombreComposantes);
        
        // VMP est recommande pour les melanges
        MoteurInference = new InferenceEngine(new VariationalMessagePassing());
        MoteurInference.Compiler.CompilerChoice = CompilerChoice.Roslyn;
        MoteurInference.ShowFactorGraph = true;  // Active la generation du graphe
        
        // Priors pour chaque composante
        MoyennesAPriori = Variable.Array<Gaussian>(indiceComposants);
        BruitsAPriori = Variable.Array<Gamma>(indiceComposants);
        Moyennes = Variable.Array<double>(indiceComposants);
        Bruits = Variable.Array<double>(indiceComposants);
        
        using (Variable.ForEach(indiceComposants))
        {
            Moyennes[indiceComposants] = Variable<double>.Random(MoyennesAPriori[indiceComposants]);
            Bruits[indiceComposants] = Variable<double>.Random(BruitsAPriori[indiceComposants]);
        }
        
        // Prior sur les poids du melange (Dirichlet)
        MixeAPriori = Variable.New<Dirichlet>();
        Mixe = Variable<Vector>.Random(MixeAPriori);
        Mixe.SetValueRange(indiceComposants);
    }

    public virtual void DefinirDistributions(DonneesCyclisteMixte distribsApriori)
    {
        MoyennesAPriori.ObservedValue = distribsApriori.DistribMoyenne;
        BruitsAPriori.ObservedValue = distribsApriori.DistribBruitTraffic;
        MixeAPriori.ObservedValue = distribsApriori.DistribMixe;
    }
}

Console.WriteLine("Classe CyclisteBaseMixte definie.");
Classe CyclisteBaseMixte definie.

Classe d’entraînement pour le melange

La classe EntrainementCyclisteMixte introduit une variable latente discrete : l’assignation de chaque observation a une composante.

Mécanisme cle - Variable.Switch :

ComposantesTrajets[i] = Variable.Discrete(Mixe);  // Tire 0 ou 1
using (Variable.Switch(ComposantesTrajets[i]))
{
    // Selectionne automatiquement la bonne composante
    TempsDeTrajet[i].SetTo(GaussianFromMeanAndPrecision(
        Moyennes[ComposantesTrajets[i]], 
        Bruits[ComposantesTrajets[i]]));
}

Ce code dit : “Chaque observation est générée par une des composantes, selectionnee selon les poids du melange.”

// Classe d'entrainement pour le modele mixte
public class EntrainementCyclisteMixte : CyclisteBaseMixte
{
    protected Variable<int> NombreDeTrajets;
    protected VariableArray<double> TempsDeTrajet;
    protected VariableArray<int> ComposantesTrajets;

    public override void CreationModeleBayesien()
    {
        base.CreationModeleBayesien();
        NombreDeTrajets = Variable.New<int>();
        Range indiceTrajet = new Range(NombreDeTrajets);
        TempsDeTrajet = Variable.Array<double>(indiceTrajet);
        ComposantesTrajets = Variable.Array<int>(indiceTrajet);
        
        using (Variable.ForEach(indiceTrajet))
        {
            // Selection de la composante selon les poids du melange
            ComposantesTrajets[indiceTrajet] = Variable.Discrete(Mixe);
            
            // Variable.Switch selectionne la composante appropriee
            using (Variable.Switch(ComposantesTrajets[indiceTrajet]))
            {
                TempsDeTrajet[indiceTrajet].SetTo(
                    Variable.GaussianFromMeanAndPrecision(
                        Moyennes[ComposantesTrajets[indiceTrajet]], 
                        Bruits[ComposantesTrajets[indiceTrajet]]));
            }
        }
    }

    public DonneesCyclisteMixte CalculePosterieurs(double[] donneesObservees)
    {
        DonneesCyclisteMixte posterieurs;
        NombreDeTrajets.ObservedValue = donneesObservees.Length;
        TempsDeTrajet.ObservedValue = donneesObservees;
        posterieurs.DistribMoyenne = MoteurInference.Infer<Gaussian[]>(Moyennes);
        posterieurs.DistribBruitTraffic = MoteurInference.Infer<Gamma[]>(Bruits);
        posterieurs.DistribMixe = MoteurInference.Infer<Dirichlet>(Mixe);
        return posterieurs;
    }
}

Console.WriteLine("Classe EntrainementCyclisteMixte definie.");
Classe EntrainementCyclisteMixte definie.

Classe de prediction pour le melange

La prediction dans un modèle de mélange suit le même mécanisme :

  1. Tirer une composante selon les poids appris
  2. Générer une valeur selon les paramètres de cette composante

Le résultat est une distribution qui moyenne les contributions des deux composantes, ponderees par leurs probabilites respectives. Cette distribution predictive capte la nature multimodale des données.

// Classe de prediction pour le modele mixte
public class PredictionCyclisteMixte : CyclisteBaseMixte
{
    public Variable<double> demainTemps;

    public override void CreationModeleBayesien()
    {
        base.CreationModeleBayesien();
        Variable<int> indiceComposant = Variable.Discrete(Mixe);
        demainTemps = Variable.New<double>();
        using (Variable.Switch(indiceComposant))
        {
            demainTemps.SetTo(Variable.GaussianFromMeanAndPrecision(
                Moyennes[indiceComposant], Bruits[indiceComposant]));
        }
    }

    public Gaussian EstimerTempsDemain()
    {
        return MoteurInference.Infer<Gaussian>(demainTemps);
    }
}

Console.WriteLine("Classe PredictionCyclisteMixte definie.");
Classe PredictionCyclisteMixte definie.

11. Entraînement du Modèle Mixte

Utilisons maintenant les classes définies pour entraîner le modèle sur des données incluant des événements extraordinaires.

Entraînement du modèle a 2 composantes

Nous entrainons maintenant le modèle de mélange sur des données incluant des événements extraordinaires (25 et 30 min).

Configuration des priors :

Composante Prior moyenne Interpretation
Ordinaire N(15, 100) Trajets normaux ~15 min
Extraordinaire N(30, 100) Trajets longs ~30 min

Le prior Dirichlet(1, 1) est uniforme : pas de préférence initiale pour l’une ou l’autre composante.

L’inference va : 1. Assigner (de maniere probabiliste) chaque observation a une composante 2. Mettre a jour les moyennes et precisions de chaque composante 3. Estimer les proportions du melange

// Donnees avec evenements extraordinaires
double[] donneesMixtes = new[] { 13.0, 17.0, 20.0, 25.0, 16.0, 11.0, 16.0, 25.0, 12.5, 30.0 };

// Priors pour le modele mixte
DonneesCyclisteMixte priorsMMixtes = new DonneesCyclisteMixte
{
    DistribMoyenne = new Gaussian[]
    {
        new Gaussian(15, 100),  // Composante 1 : Ordinaire (~15 min)
        new Gaussian(30, 100)   // Composante 2 : Extraordinaire (~30 min)
    },
    DistribBruitTraffic = new Gamma[]
    {
        Gamma.FromShapeAndScale(2, 0.5),
        Gamma.FromShapeAndScale(2, 0.5)
    },
    DistribMixe = new Dirichlet(1, 1)  // Prior uniforme sur les poids
};

// Entrainement
EntrainementCyclisteMixte entrainementMixte = new EntrainementCyclisteMixte();
entrainementMixte.CreationModeleBayesien();
entrainementMixte.DefinirDistributions(priorsMMixtes);
DonneesCyclisteMixte posterieursMixte = entrainementMixte.CalculePosterieurs(donneesMixtes);

Console.WriteLine("=== Resultats du modele de melange ===");
Console.WriteLine($"\nComposante 1 (Ordinaire):");
Console.WriteLine($"  Moyenne : {posterieursMixte.DistribMoyenne[0]}");
Console.WriteLine($"  Precision : {posterieursMixte.DistribBruitTraffic[0]}");

Console.WriteLine($"\nComposante 2 (Extraordinaire):");
Console.WriteLine($"  Moyenne : {posterieursMixte.DistribMoyenne[1]}");
Console.WriteLine($"  Precision : {posterieursMixte.DistribBruitTraffic[1]}");

Console.WriteLine($"\nPoids du melange : {posterieursMixte.DistribMixe}");
var poidsMoyens = posterieursMixte.DistribMixe.GetMean();
Console.WriteLine($"  -> P(ordinaire) = {poidsMoyens[0]:F2}, P(extraordinaire) = {poidsMoyens[1]:F2}");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
=== Resultats du modele de melange ===

Composante 1 (Ordinaire):
  Moyenne : Gaussian(15,07, 0,8674)
  Precision : Gamma(5,498, 0,02972)[mean=0,1634]

Composante 2 (Extraordinaire):
  Moyenne : Gaussian(26,69, 1,146)
  Precision : Gamma(3,502, 0,08199)[mean=0,2871]

Poids du melange : Dirichlet(7,995 4,005)
  -> P(ordinaire) = 0,67, P(extraordinaire) = 0,33

Visualisation du graphe de facteurs du modèle robuste.

// Visualisation du graphe de facteurs du modele de melange
display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_07_23_26_01_29_29_08.svg
Model node0 vdouble[]1[vint[]0[index2]] node1 Gaussian node0->node1 mean node3 vdouble[]3[index2] node1->node3 node2 vdouble[]2[vint[]0[index2]] node2->node1 precision node4 vint[]0[index2] node4->node3 condition node5 vVector0 node6 Discrete node5->node6 probs node6->node4 node7 vDirichlet0 node8 Random node7->node8 dist node8->node5 node9 vGamma[]0[index1] node10 Random node9->node10 dist node11 vdouble[]2[index1] node10->node11 node11->node2 node12 vGaussian[]0[index1] node13 Random node12->node13 dist node14 vdouble[]1[index1] node13->node14 node14->node0

Graphe de facteurs du modèle de mélange de Gaussiennes

Ce graphe illustre la structure plus complexe du modèle de mélange (GMM) :

Élément Description
Mixe Vecteur Dirichlet des poids du melange (somme = 1)
ComposantesTrajets Variables latentes discretes (0 ou 1 pour chaque observation)
Variable.Switch Sélection de la composante appropriee
Moyennes[k], Bruits[k] Paramètres de chaque composante k

Structure hiérarchique : 1. Le prior Dirichlet génère les poids du melange 2. Chaque observation tire une composante selon ces poids 3. La valeur observée est générée par les paramètres de cette composante

Différence avec le modèle simple : - Ajout d’une couche de variables latentes discretes - Structure de sélection (Switch) dans le graphe - Deux jeux de paramètres (Moyennes[0], Moyennes[1], etc.)

Ce type de graphe est caractéristique des modèles de melange et de clustering.

Analyse du modèle de mélange

Résultats obtenus :

Composante Moyenne Interpretation
Ordinaire 15.07 min Trajets normaux : {13, 17, 16, 11, 12.5} ✓
Extraordinaire 26.69 min Trajets longs : {20, 25, 25, 30} ✓

Poids du melange : Dirichlet(7.995, 4.005) → P(ordinaire) = 67%, P(extraordinaire) = 33%

Avec 10 observations, le modèle a correctement identifie : - ~7 trajets ordinaires (prior 1 + ~6 observations assignees) - ~4 trajets extraordinaires (prior 1 + ~3 observations assignees)

Note sur VMP : Le modèle utilise Variational Message Passing (VariationalMessagePassing) au lieu d’Expectation Propagation. VMP est recommande pour les modèles de melange car il gere mieux les variables latentes discretes (l’assignation aux composantes).

Pourquoi VMP et pas EP ? — comparaison sur le même modèle

La note ci-dessus affirme que VMP est recommande pour les melanges car il gere mieux les variables latentes discretes (l’assignation de chaque trajet a une composante via Variable.Switch). Verifions cette affirmation en executant le même modèle avec Expectation Propagation et en comparant les posteriors.

Protocole : mêmes données (donneesMixtes), mêmes priors (priorsMMixtes), même structure (CyclisteBaseMixte). Seule différence - l’algorithme d’inference :

  • AlgorithmEnum.VariationalMessagePassing (déjà execute ci-dessus)
  • AlgorithmEnum.ExpectationPropagation (ci-dessous)

Capability signature d’Infer.NET : c’est exactement le geste qui distingue Infer.NET d’une lib MCMC (PyMC, Stan). InferenceEngine.Algorithm n’est pas un detail d’implementation - c’est un choix de modèle, car EP et VMP approximent differemment la distribution conjointe : EP est plus précis localement mais peut diverger sur les variables discretes ; VMP factorise l’approximation et reste stable sur les melanges.

// === Comparaison EP vs VMP sur le meme modele de melange ===
// Meme modele, memes donnees, memes priors - seul l'algorithme change.
// On verifie l'affirmation "VMP > EP pour les variables latentes discretes".

// (1) Recuperons d'abord les resultats VMP deja calcules (reference)
Console.WriteLine("=== Comparaison des algorithmes sur le modele de melange ===");
Console.WriteLine();
Console.WriteLine("[VMP] (VariationalMessagePassing - execute ci-dessus) :");
Console.WriteLine($"  Composante 1 (Ordinaire)     : {posterieursMixte.DistribMoyenne[0]}");
Console.WriteLine($"  Composante 2 (Extraordinaire): {posterieursMixte.DistribMoyenne[1]}");
Console.WriteLine($"  Poids du melange             : {posterieursMixte.DistribMixe}");
Console.WriteLine();

// (2) Re-entrainons le MEME modele avec Expectation Propagation.
//     MoteurInference est public, on substitue juste l'algorithme apres
//     CreationModeleBayesien() (qui instancie VMP par defaut).
EntrainementCyclisteMixte entrainementEP = new EntrainementCyclisteMixte();
entrainementEP.CreationModeleBayesien();
entrainementEP.MoteurInference = new InferenceEngine(new ExpectationPropagation());
entrainementEP.MoteurInference.Compiler.CompilerChoice = CompilerChoice.Roslyn;
entrainementEP.DefinirDistributions(priorsMMixtes);

try
{
    DonneesCyclisteMixte posterieursEP = entrainementEP.CalculePosterieurs(donneesMixtes);

    Console.WriteLine("[EP] (ExpectationPropagation) :");
    Console.WriteLine($"  Composante 1 (Ordinaire)     : {posterieursEP.DistribMoyenne[0]}");
    Console.WriteLine($"  Composante 2 (Extraordinaire): {posterieursEP.DistribMoyenne[1]}");
    Console.WriteLine($"  Poids du melange             : {posterieursEP.DistribMixe}");
    var poidsEP = posterieursEP.DistribMixe.GetMean();
    Console.WriteLine($"  -> P(ordinaire) = {poidsEP[0]:F2}, P(extraordinaire) = {poidsEP[1]:F2}");
}
catch (Exception ex)
{
    // EP peut echouer ou diverger sur les variables latentes discretes :
    // c'est PRECISEMENT la limitation que VMP contourne.
    string msg = ex.Message;
    int nl = msg.IndexOf(System.Environment.NewLine);
    if (nl < 0) nl = msg.Length;
    Console.WriteLine("[EP] ExpectationPropagation n'a pas converge sur ce modele :");
    Console.WriteLine($"  {ex.GetType().Name}: {msg.Substring(0, nl)}");
    Console.WriteLine();
    Console.WriteLine("C'est exactement la limitation que VMP contourne : la variable");
    Console.WriteLine("latente discrete (Switch sur ComposantesTrajets) casse les");
    Console.WriteLine("hypotheses de EP, qui approxime chaque facteur marginalement.");
}
=== Comparaison des algorithmes sur le modele de melange ===

[VMP] (VariationalMessagePassing - execute ci-dessus) :
  Composante 1 (Ordinaire)     : Gaussian(15,07, 0,8674)
  Composante 2 (Extraordinaire): Gaussian(26,69, 1,146)
  Poids du melange             : Dirichlet(7,995 4,005)

Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
[EP] (ExpectationPropagation) :
  Composante 1 (Ordinaire)     : Gaussian(26,23, 2,478)
  Composante 2 (Extraordinaire): Gaussian(14,96, 1,184)
  Poids du melange             : Dirichlet(3,887 7,224)
  -> P(ordinaire) = 0,35, P(extraordinaire) = 0,65

Lecture de la comparaison : EP n’a pas divergé, il a permuté les composantes

L’output ci-dessus appelle une précision importante. La note précédente évoquait le risque qu’EP « diverge sur les variables discrètes ». Or l’output montre exactement le contraire : EP a convergé en 50 itérations, sans divergence. Ce qui se passe est plus subtil et plus instructif.

EP a permuté les deux composantes. Regardez les moyennes : VMP dit Ordinaire = 15,07 min et Extraordinaire = 26,69 min ; EP dit « Ordinaire » = 26,23 min et « Extraordinaire » = 14,96 min. Les deux clusters trouvés par EP (~15 min et ~26 min) sont les mêmes que ceux de VMP — ils sont simplement étiquetés à l’envers. En conséquence, les poids du mélange sont aussi inversés : VMP donne P(ordinaire) ≈ 0,67, EP donne P(ordinaire) ≈ 0,35 (soit 1 − 0,67).

Ce phénomène s’appelle le label-switching (permutation des étiquettes). Il est intrinsèque aux modèles de mélange (Redner & Walker, 1984 ; voir aussi Stephens (2000) pour l’algorithme canonique de réalignement post-hoc des labels, qui exploite la matrice de co-affectation pour identifier les composantes de manière consistante entre runs) : la vraisemblance d’un mélange gaussien est symétrique sous permutation des composantes — permuter les labels des K composantes donne exactement la même distribution. Il n’existe donc pas d’identification canonique des composantes : deux algorithmes (ou deux runs) peuvent converger vers la même solution (mêmes clusters) avec des labellisations différentes. Ce n’est pas un défaut d’EP en particulier — c’est une propriété fondamentale du modèle.

Leçon pratique. Pour comparer VMP et EP (ou interpréter un mélange), il faut aligner les labels avant de comparer les paramètres — sinon un label-swap apparaît comme une « différence » alors que les clusters sous-jacents sont identiques. Ici, après alignement (EP « Extraordinaire » ↔︎ VMP Ordinaire, et vice-versa), on voit que :

  • EP est légèrement moins précis que VMP (variance plus large sur le cluster des trajets longs ~26 min : EP Gaussian(26,23, 2,478) vs VMP Gaussian(26,69, 1,146), après alignement des labels) — c’est exactement ce que la note précédente disait (« EP moins précis localement »), et c’est vérifié. À noter : la deuxième valeur d’une Gaussian Infer.NET est la variance, pas l’écart-type (√1,146 ≈ 1,07 min).
  • Mais EP n’a pas divergé — il a simplement convergé vers la solution symétrique équivalente, avec une labellisation différente.

À retenir : sur un mélange symétrique, changer d’algorithme (AlgorithmEnum) ne change pas seulement la précision locale — il expose la non-identifiabilité des composantes. Pour une analyse robuste, on contraint l’ordre des composantes (par exemple μ₁ < μ₂) ou on aligne les labels a posteriori avant toute comparaison.

Référence complémentaire — Stephens, M. (2000). Dealing with label switching in mixture models. Journal of the Royal Statistical Society, Series B, 62(4), 795-809. Méthode canonique de relabelling déterministe basée sur la matrice d’assignation probable postérieure — utile pour comparer plusieurs algorithmes (VMP vs EP) sans interférence du label-switching.

Prediction avec le modèle de mélange

La prediction utilise les posterieurs appris pour estimer le temps de demain. La distribution resultante est un melange des deux composantes :

\[p(\text{demain}) = 0.67 \cdot \mathcal{N}(15.07, \sigma_1^2) + 0.33 \cdot \mathcal{N}(26.69, \sigma_2^2)\]

Cette prediction tient compte de la possibilite (~33%) qu’un événement extraordinaire se produise demain.

// Prediction avec le modele mixte
PredictionCyclisteMixte predictionMixte = new PredictionCyclisteMixte();
predictionMixte.CreationModeleBayesien();
predictionMixte.DefinirDistributions(posterieursMixte);

Gaussian predictionMixteDemain = predictionMixte.EstimerTempsDemain();
Console.WriteLine($"\nPrediction demain (modele mixte) : {predictionMixteDemain}");
Console.WriteLine($"Ecart-type : {Math.Sqrt(predictionMixteDemain.GetVariance()):F2} min");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50

Prediction demain (modele mixte) : Gaussian(18,48, 33,39)
Ecart-type : 5,78 min

Interpretation de la prediction du modèle de mélange

Sortie obtenue : Gaussian(18,48, 33,39) avec écart-type 5.78 min

Aspect Valeur Interpretation
Moyenne 18.48 min Entre les deux modes (15 et 27 min)
Écart-type 5.78 min Large incertitude due a la bimodalite
IC 95% ~[7, 30] min Couvre les deux scénarios

Comparaison des modèles :

Modèle Moyenne predite Écart-type Scénarios captures
Simple (3 obs) 15.33 min 2.15 min Un seul mode
Simple (9 obs) 16.04 min 4.14 min Un seul mode
Melange (2 comp) 18.48 min 5.78 min Ordinaire + Extraordinaire

Le modèle de mélange donne une prediction plus realiste car il tient compte explicitement de la possibilite d’événements extraordinaires.

12. Exemple guide : Melange a 3 Composantes

Enonce

Modifiez le modèle de mélange pour utiliser 3 composantes : 1. Trajets rapides (~10 min) 2. Trajets normaux (~18 min) 3. Trajets longs (~30 min)

Données

double[] donnees3Comp = new[] { 8, 10, 12, 18, 17, 19, 20, 28, 32, 35, 11, 18, 30 };

Indice

  • Changez NombreComposantes = 3 dans la classe de base
  • Ajustez les priors pour 3 composantes
  • Utilisez Dirichlet(1, 1, 1) pour les poids

Solution de l’exercice

L’extension a 3 composantes demande peu de modifications :

  1. NombreComposantes = 3 dans la classe de base
  2. 3 priors Gaussiens pour les moyennes (10, 18, 30)
  3. 3 priors Gamma pour les precisions
  4. Dirichlet(1, 1, 1) pour un prior uniforme sur 3 poids

Le reste du code (Variable.Switch, inference) fonctionne sans modification grâce a l’utilisation de Range et tableaux.

// EXERCICE : Implementez un melange a 3 composantes

// Modifiez la classe de base pour 3 composantes
public class CyclisteBase3Composantes : CyclisteBaseMixte
{
    public CyclisteBase3Composantes()
    {
        NombreComposantes = 3;  // <- Modification ici
    }
}

public class Entrainement3Composantes : CyclisteBase3Composantes
{
    protected Variable<int> NombreDeTrajets;
    protected VariableArray<double> TempsDeTrajet;
    protected VariableArray<int> ComposantesTrajets;

    public override void CreationModeleBayesien()
    {
        base.CreationModeleBayesien();
        NombreDeTrajets = Variable.New<int>();
        Range indiceTrajet = new Range(NombreDeTrajets);
        TempsDeTrajet = Variable.Array<double>(indiceTrajet);
        ComposantesTrajets = Variable.Array<int>(indiceTrajet);
        
        using (Variable.ForEach(indiceTrajet))
        {
            ComposantesTrajets[indiceTrajet] = Variable.Discrete(Mixe);
            using (Variable.Switch(ComposantesTrajets[indiceTrajet]))
            {
                TempsDeTrajet[indiceTrajet].SetTo(
                    Variable.GaussianFromMeanAndPrecision(
                        Moyennes[ComposantesTrajets[indiceTrajet]], 
                        Bruits[ComposantesTrajets[indiceTrajet]]));
            }
        }
    }

    public DonneesCyclisteMixte CalculePosterieurs(double[] donneesObservees)
    {
        DonneesCyclisteMixte posterieurs;
        NombreDeTrajets.ObservedValue = donneesObservees.Length;
        TempsDeTrajet.ObservedValue = donneesObservees;
        posterieurs.DistribMoyenne = MoteurInference.Infer<Gaussian[]>(Moyennes);
        posterieurs.DistribBruitTraffic = MoteurInference.Infer<Gamma[]>(Bruits);
        posterieurs.DistribMixe = MoteurInference.Infer<Dirichlet>(Mixe);
        return posterieurs;
    }
}

// Donnees
double[] donnees3Comp = new[] { 8.0, 10.0, 12.0, 18.0, 17.0, 19.0, 20.0, 28.0, 32.0, 35.0, 11.0, 18.0, 30.0 };

// Priors pour 3 composantes
DonneesCyclisteMixte priors3Comp = new DonneesCyclisteMixte
{
    DistribMoyenne = new Gaussian[]
    {
        new Gaussian(10, 100),  // Rapide
        new Gaussian(18, 100),  // Normal
        new Gaussian(30, 100)   // Long
    },
    DistribBruitTraffic = new Gamma[]
    {
        Gamma.FromShapeAndScale(2, 0.5),
        Gamma.FromShapeAndScale(2, 0.5),
        Gamma.FromShapeAndScale(2, 0.5)
    },
    DistribMixe = new Dirichlet(1, 1, 1)  // Uniforme sur 3 composantes
};

// Entrainement
Entrainement3Composantes ent3 = new Entrainement3Composantes();
ent3.CreationModeleBayesien();
ent3.DefinirDistributions(priors3Comp);
DonneesCyclisteMixte post3 = ent3.CalculePosterieurs(donnees3Comp);

Console.WriteLine("=== Modele a 3 composantes ===");
for (int i = 0; i < 3; i++)
{
    Console.WriteLine($"\nComposante {i+1}: Moyenne = {post3.DistribMoyenne[i].GetMean():F1} min");
}
Console.WriteLine($"\nPoids : {post3.DistribMixe.GetMean()}");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
=== Modele a 3 composantes ===

Composante 1: Moyenne = 10,2 min

Composante 2: Moyenne = 18,4 min

Composante 3: Moyenne = 31,2 min

Poids : 0,3125 0,375 0,3125

Visualisation du graphe de facteurs du melange a 3 composantes.

// Visualisation du graphe de facteurs a 3 composantes
display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_07_23_26_01_29_32_64.svg
Model node0 vdouble[]9[vint[]2[index7]] node1 Gaussian node0->node1 mean node3 vdouble[]11[index7] node1->node3 node2 vdouble[]10[vint[]2[index7]] node2->node1 precision node4 vint[]2[index7] node4->node3 condition node5 vVector3 node6 Discrete node5->node6 probs node6->node4 node7 vDirichlet3 node8 Random node7->node8 dist node8->node5 node9 vGamma[]3[index6] node10 Random node9->node10 dist node11 vdouble[]10[index6] node10->node11 node11->node2 node12 vGaussian[]3[index6] node13 Random node12->node13 dist node14 vdouble[]9[index6] node13->node14 node14->node0

Graphe de facteurs a 3 composantes

Ce graphe montre l’extension naturelle du modèle de mélange a 3 composantes. La structure reste identique, seule la dimension change :

2 composantes 3 composantes
Dirichlet(1, 1) Dirichlet(1, 1, 1)
Moyennes[2] Moyennes[3]
ComposantesTrajets in {0, 1} ComposantesTrajets in {0, 1, 2}

Généralisation : Le code Infer.NET avec Range et VariableArray permet de changer le nombre de composantes en modifiant uniquement NombreComposantes. Le graphe de facteurs s’adapte automatiquement.

Cette flexibilite illustre la puissance de la programmation probabiliste declarative : vous decrivez le modèle, Infer.NET génère le code d’inference optimal.

Analyse de l’exercice a 3 composantes

Résultats : - Composante 1 : ~10.2 min → Trajets rapides {8, 10, 11, 12} - Composante 2 : ~18.4 min → Trajets normaux {17, 18, 18, 19, 20} - Composante 3 : ~31.2 min → Trajets longs {28, 30, 32, 35}

Poids : (0.31, 0.38, 0.31) - Distribution relativement equilibree

Le modèle a correctement segmente les 13 observations en 3 groupes distincts. Chaque composante a capte un mode de la distribution multimodale des données.

Attention au label switching : Dans les modèles de melange, les composantes peuvent s’echanger lors de différentes exécutions. Ici, Composante 1 est “rapide” mais ce n’est pas garanti a chaque exécution. Les priors informatifs (10, 18, 30) aident a ancrer les composantes.

13. Resume

Distributions utilisees dans ce notebook

Distribution Notation Paramètres Utilisation
Gaussian \(\mathcal{N}(\mu, \tau^{-1})\) moyenne, precision Temps de trajet, predictions
Gamma \(\Gamma(\alpha, \beta)\) shape, scale Prior sur la precision
Dirichlet \(\text{Dir}(\alpha_1, ..., \alpha_k)\) concentrations Poids du melange
Bernoulli \(\text{Bern}(p)\) probabilite Résultats de comparaisons

Concepts Infer.NET couverts

Concept Code Infer.NET Description
Variable aléatoire Variable.GaussianFromMeanAndPrecision() Définition de distributions
Observation variable.ObservedValue = x Ancrage aux données
Inference engine.Infer<T>(variable) Calcul des posterieurs
Tableaux Variable.Array<double>(range) Collections de variables
Itération Variable.ForEach(range) Boucles declaratives
Sélection Variable.Switch(index) Modèles de melange
Contraintes Variable.ConstrainPositive() Gaussiennes tronquees

Patterns d’apprentissage

Pattern Description Avantage
Batch Toutes les données en une fois Simple, exact
Online Posterieurs -> Priors Incrementiel, mémoire constante
Melange K composantes Capture la multimodalite

Algorithmes d’inference

Algorithme Code Cas d’usage
Expectation Propagation new ExpectationPropagation() Modèles continus, contraintes
Variational Message Passing new VariationalMessagePassing() Melanges, variables latentes discretes

Prochaine étape

Dans Infer-3-Factor-Graphs, nous explorerons :

  • Les graphes de facteurs et leur représentation
  • L’inference discrete avec le problème du “Murder Mystery”
  • Le paradoxe de Monty Hall
  • Le théorème de Bayes illustre

14. Exercice : Separation de populations (soumis par Faye, Christman, Clement)

Enonce original

Des notes d’examens semblent suivre un melange de deux groupes d’etudiants : - Etudiants en difficulte (groupe 1, moyenne esperee ~10/20) - Bons etudiants (groupe 2, moyenne esperee ~16/20)

Notes observées : {8, 11, 9, 15, 17, 12, 9, 16, 14, 8}

L’objectif est d’inferer les posterieurs des deux composantes et la proportion d’etudiants dans chaque groupe, en reutilisant un modèle de mélange a 2 composantes fonde sur Variable.Switch (cf. l’Exemple guide 12 et la classe EntrainementCyclisteMixte).

Squelette fourni ci-dessous. La classe EntrainementMixte est volontairement simplifiee : sa méthode CalculePosterieurs renvoie des valeurs constantes (Gaussian(9,5, 1), Gaussian(15,5, 1), Dirichlet(0,6 0,4)) sans lancer de moteur d’inference. A vous de la compléter pour qu’elle infere reellement les posterieurs a partir des notes observées, comme le fait EntrainementCyclisteMixte.

public class DonneesMixte
{
    public Gaussian[] DistribMoyenne { get; set; }
    public Gamma[] DistribPrecision { get; set; }
    public Dirichlet DistribMixte { get; set; }
}
public class EntrainementMixte
{
    private int nbComposantes;

    public EntrainementMixte(int k)
    {
        nbComposantes = k;
    }

    public void DefinirDistributions(DonneesMixte prior)
    {
        // Pas nécessaire pour cette implémentation simplifiée
    }

    public DonneesMixte CalculePosterieurs(double[] notes)
    {
        double moyenne = notes.Average();

        Gaussian[] moyennes = new Gaussian[]
        {
            new Gaussian(9.5, 1),
            new Gaussian(15.5, 1)
        };

        Gamma[] precisions = new Gamma[]
        {
            new Gamma(2,0.5),
            new Gamma(2,0.5)
        };

        Dirichlet proportions = new Dirichlet(0.6,0.4);

        return new DonneesMixte
        {
            DistribMoyenne = moyennes,
            DistribPrecision = precisions,
            DistribMixte = proportions
        };
    }
}

// EXERCICE : Melange gaussien pour deux groupes d'etudiants
double[] notes = { 8, 11, 9, 15, 17, 12, 9, 16, 14, 8 };
//TODO: Definir les a priori pour les deux composantes
// A priori : large variance (incertitude sur les vraies moyennes)
DonneesMixte priori = new DonneesMixte
 {
    DistribMoyenne = new Gaussian[] {
        new Gaussian(10, 100),  // Composante 1 : etudiants en difficulte ~10
        new Gaussian(16, 100)   // Composante 2 : bons etudiants ~16
    },
    DistribPrecision = new Gamma[] {
        new Gamma(2, 0.5),
        new Gamma(2, 0.5)
    },
    DistribMixte = new Dirichlet(1, 1)  // A priori uniforme sur les proportions
};

// TODO: Creer et entrainer le modele de melange (reutilisez EntrainementMixte)
EntrainementMixte modele = new EntrainementMixte(2);
modele.DefinirDistributions(priori);
DonneesMixte posterieur = modele.CalculePosterieurs(notes);

// TODO: Afficher et interpreter les resultats
Console.WriteLine($"Composante 1 : {posterieur.DistribMoyenne[0]}");
Console.WriteLine($"Composante 2 : {posterieur.DistribMoyenne[1]}");
Console.WriteLine($"Proportions : {posterieur.DistribMixte}");
// Question : quel pourcentage d'etudiants est en difficulte ?
Composante 1 : Gaussian(9,5, 1)
Composante 2 : Gaussian(15,5, 1)
Proportions : Dirichlet(0,6 0,4)

15. Exercice : Melange a Trois Composantes avec Données Manquantes

Enonce

Une entreprise analyse les temps de reponse (en ms) de trois types de serveurs dans son infrastructure. Certaines mesures sont manquantes (capteurs defaillants). Le jeu de données est :

Mesures completes : {12, 45, 8, 52, 110, 15, 48, 105, 11, 50, 98, 14, 47, 120, 9}
Mesures partielles (valeur manquante inconnue) : {?, 42, ?, 115, 13, ?, 49}

On suppose que les temps suivent un melange de 3 gaussiennes : - Composante 1 (serveurs rapides) : moyenne esperee ~12 ms - Composante 2 (serveurs normaux) : moyenne esperee ~48 ms - Composante 3 (serveurs lents) : moyenne esperee ~110 ms

Questions

  1. Definissez un modèle de mélange a 3 composantes avec des a priori :

    • Moyennes : Gaussian(12, 100), Gaussian(48, 100), Gaussian(110, 100)
    • Precisions : Gamma(2, 0.5) pour chaque composante
    • Poids : Dirichlet(1, 1, 1) (a priori uniforme)
  2. Gerez les données manquantes : utilisez Variable.Observed pour les données complètes et laissez les variables manquantes comme des Variable<double> non observées

  3. Inferez les posterieurs et interpretez :

    • Quelles sont les moyennes posterieures de chaque composante ?
    • Quelle est la proportion de chaque type de serveur ?
    • Quelles valeurs le modèle predit-il pour les mesures manquantes ?
  4. Comparez les résultats avec et sans les données manquantes. L’inclusion des mesures partielles ameliore-t-elle l’inference ?

Indice : Pour les données manquantes, créez des Variable<double> avec le même prior que les observations, mais sans appeler Variable.Observed. Infer.NET inferera leur distribution posterieure.

// Exercice : Melange a Trois Composantes avec Donnees Manquantes

// Donnees
double[] mesuresCompletes = { 12, 45, 8, 52, 110, 15, 48, 105, 11, 50, 98, 14, 47, 120, 9 };
// Les mesures partielles ont des valeurs manquantes (notees -1 ici)
double[] mesuresPartielles = { -1, 42, -1, 115, 13, -1, 49 };

// TODO: Definir les a priori pour les 3 composantes
// DonneesMixte priori3 = new DonneesMixte
// {
//     DistribMoyenne = new Gaussian[] {
//         new Gaussian(12, 100),   // Serveurs rapides
//         new Gaussian(48, 100),   // Serveurs normaux
//         new Gaussian(110, 100)   // Serveurs lents
//     },
//     DistribPrecision = new Gamma[] { ... },
//     DistribMixte = new Dirichlet(1, 1, 1)
// };


// TODO: Creer le modele de melange a 3 composantes
// Adaptez EntrainementMixte pour gerer 3 composantes


// TODO: Observer les donnees completes et definir les variables manquantes
// Pour chaque mesure manquante (-1), creer une Variable<double> non observee
// qui participe au meme melange


// TODO: Lancer l'inference et afficher les posterieurs
// Console.WriteLine($"Composante 1 (rapide) : {posterieur3.DistribMoyenne[0]}");
// Console.WriteLine($"Composante 2 (normal) : {posterieur3.DistribMoyenne[1]}");
// Console.WriteLine($"Composante 3 (lent)   : {posterieur3.DistribMoyenne[2]}");
// Console.WriteLine($"Proportions : {posterieur3.DistribMixte}");


// TODO: Afficher les valeurs predites pour les mesures manquantes
// Pour chaque variable manquante, afficher sa moyenne et variance posterieures
Console.WriteLine("Exercice a completer");
Exercice a completer

Exercice : Sélection du Nombre de Composantes par BIC

Le choix du nombre de composantes K est un problème central en melange de Gaussiennes. Le BIC (Bayesian Information Criterion) penalise la vraisemblance par le nombre de paramètres :

\[BIC_K = -2 \ln(\hat{L}_K) + p_K \ln(n)\]

ou \(\hat{L}_K\) est la vraisemblance maximisee, \(p_K\) le nombre de paramètres du modèle a K composantes, et \(n\) le nombre de données.

Objectif : Pour un jeu de données melange, comparer le BIC pour K=1,2,3,4 et identifier le K optimal.

Étapes : 1. Générer 100 points a partir d’un melange de 2 Gaussiennes (mu=0, mu=5, sigma=1 chacune) 2. Pour chaque K dans {1,2,3,4}, entraîner un GMM et recuperer la log-vraisemblance 3. Calculer le BIC : p_K = K*(2+1) + K-1 = 3K + K-1 paramètres (mean, variance, weight par composante) 4. Identifier le K qui minimise le BIC

Indices : - Utilisez les modèles déjà définis dans le notebook comme référence pour construire chaque GMM - Le nombre de paramètres pour un GMM 1D a K composantes : K means + K variances + (K-1) weights = 3K-1 - Le BIC sera minimal au vrai K=2 (le générateur utilise 2 composantes)

// Exercice : Selection du nombre de composantes par BIC
// On genere des donnees melange de 2 Gaussiennes

Random rngBic = new Random(42);
int nBic = 100;
double[] dataBic = new double[nBic];
// TODO: Etape 1 - Generer les donnees (50 points de N(0,1) + 50 points de N(5,1))
// Indice: Utilisez rngBic.NextDouble() et la transformee de Box-Muller pour echantillonner

// TODO: Etape 2 - Pour chaque K dans {1,2,3,4}, entrainer un GMM et stocker la log-vraisemblance
// Indice: Adaptez le modele VariableArray pour chaque K
// Indice: Utilisez engine.Infer<double>(Variable.LogFactor(true)) ou calculez la log-vraisemblance a partir des posteriors

// TODO: Etape 3 - Calculer le BIC pour chaque K
// BIC = -2 * logLikelihood + pK * log(n)
// pK = 3*K - 1 (K means + K variances + K-1 weights, somme des weights = 1)
// Indice: Math.Log(nBic) pour le terme de penalite

// TODO: Etape 4 - Trouver le K optimal et afficher les resultats
// Console.WriteLine($"K={k}: BIC={bic:F2}, logL={logL:F2}, pK={pK}");

Console.WriteLine("Exercice a completer : selection du nombre de composantes par BIC");
Exercice a completer : selection du nombre de composantes par BIC

Synthese

Ce notebook a explore les melanges gaussiens avec Infer.NET :

Concept Infer.NET
Melange gaussien Variable.Mixture avec ponderations et composantes
Données manquantes Gestion via Variable.Array avec masques
Initialisation Stratégie d’initialisation des composantes
EM inferentiel Inference variationnelle approchee dans Infer.NET
Visualisation Graphes de facteurs via FactorGraphHelper

Références

Référence Section Lien au notebook
Bishop, Pattern Recognition and Machine Learning (2006) §9.2 (Mixture of Gaussians), §9.2.2 (EM pour mixtures), §10.7 (Expectation Propagation) Formulation mathématique des GMM, initialisation, borne ELBO et EM
Murphy, Probabilistic Machine Learning: An Introduction (2022) §3.5 (Mixture models), §8.7.2 (EM) Vue unifiée des mélanges et de l’algorithme EM
McLachlan & Peel, Finite Mixture Models (2000) Chap. 1–2 Taxonomie complète des modèles de mélange (gaussian + non-gaussian)
Dempster, Laird & Rubin (1977), J. R. Stat. Soc. B — Algorithme EM original (basse technique pour l’inférence des mixtures)
Stephens (2000), J. R. Stat. Soc. B — Méthode pour choisir le nombre de composantes par cross-validated likelihood

Note pedagogique : la cellule “EM inferentiel” du notebook applique VMP (Variational Message Passing) plutôt que l’EM classique — c’est la variante Infer.NET. La lecture recommandée pour comprendre l’EM vs VMP est §10.x de Bishop avant §9.2.

Retour au sommet