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.
The below script needs to be able to find the current output cell; this is an easy method to get it.
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
Apprendre la distribution du temps de trajet a partir d’observations
Predire le temps de trajet de demain
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 :
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 :
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.
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 minVariable<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.
// Definition des temps de trajet observesVariable<double> dureeLundi = Variable.GaussianFromMeanAndPrecision(dureeMoyenne, bruitTrafic);Variable<double> dureeMardi = Variable.GaussianFromMeanAndPrecision(dureeMoyenne, bruitTrafic);Variable<double> dureeMercredi = Variable.GaussianFromMeanAndPrecision(dureeMoyenne, bruitTrafic);// ObservationsdureeLundi.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.
// InferenceInferenceEngine moteur =newInferenceEngine();moteur.Compiler.CompilerChoice= CompilerChoice.Roslyn;moteur.ShowFactorGraph=true;// Active la generation du graphe de facteursGaussian 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 facteursdisplay(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_07_23_26_01_29_20_15.svg
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éatoiredureeDemain 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 demainVariable<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 :
Incertitude epistemique : Nous ne connaissons pas exactement la vraie moyenne (notre estimation a une variance)
Incertitude aléatoire : Même si nous connaissions parfaitement la moyenne, chaque trajet varie autour d’elle
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}");
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
Prediction : PredictionCycliste.DefinirDistributions(posterieurs) puis EstimerTempsDemain()
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 simpleConsole.WriteLine("=== Gaussienne Tronquee ===\n");// Cas 1 : Prediction avec contrainte de positivite// On connait la moyenne et precision, on veut predire une valeur positiveVariable<double> moyenneConnue = Variable.Observed(4.0);Variable<double> precisionConnue = Variable.Observed(1.0);// Variable aleatoire avec contrainte de positiviteVariable<double> tempsPrediction = Variable.GaussianFromMeanAndPrecision(moyenneConnue, precisionConnue);Variable.ConstrainPositive(tempsPrediction);InferenceEngine moteurTronque =newInferenceEngine(newExpectationPropagation());moteurTronque.Compiler.CompilerChoice= CompilerChoice.Roslyn;// Comparer les distributions avec et sans troncatureConsole.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 zeroConsole.WriteLine("\n--- Effet pres de la borne ---");Variable<double> moyenneProche = Variable.Observed(1.0);// Proche de 0Variable<double> precisionProche = Variable.Observed(0.25);// Variance = 4, ecart-type = 2Variable<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 :
Realisme : Les predictions respectent les contraintes physiques
Meilleure estimation : La variance n’est pas “gaspillee” sur des valeurs impossibles
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 :
DonneesCycliste : Structure pour stocker les distributions posterieures (moyenne et precision)
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 posterieurspublicstruct DonneesCycliste{public Gaussian DistribMoyenne;public Gamma DistribBruitTraffic;publicDonneesCycliste(Gaussian moyenne, Gamma precision){ DistribMoyenne = moyenne; DistribBruitTraffic = precision;}}// Classe de base avec les elements communspublicclass CyclisteBase{public InferenceEngine MoteurInference;protected Variable<double> Moyenne;protected Variable<double> Bruit;protected Variable<Gaussian> MoyenneAPriori;protected Variable<Gamma> BruitAPriori;publicvirtualvoidCreationModeleBayesien(){ MoyenneAPriori = Variable.New<Gaussian>(); BruitAPriori = Variable.New<Gamma>(); Moyenne = Variable.Random<double, Gaussian>(MoyenneAPriori); Bruit = Variable.Random<double, Gamma>(BruitAPriori);if(MoteurInference ==null){ MoteurInference =newInferenceEngine(newExpectationPropagation()); MoteurInference.Compiler.CompilerChoice= CompilerChoice.Roslyn; MoteurInference.ShowFactorGraph=true;// Active la generation du graphe}}publicvirtualvoidDefinirDistributions(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.
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 classesdouble[] donneesTrajets =new[]{13.0,17.0,20.0,25.0,16.0,11.0,16.0,14.0,12.5};// Priors vaguesDonneesCycliste mesDistributions =newDonneesCycliste( Gaussian.FromMeanAndPrecision(15,0.01),// Prior moyenne Gamma.FromShapeAndScale(2,0.5));// Prior precision// EntrainementEntrainementCycliste entrainement =newEntrainementCycliste();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]
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 tableauxdisplay(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_07_23_26_01_29_26_76.svg
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 :
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 :
Données plus dispersees : Les 9 observations incluent des extremes (11 a 25 min)
Meilleure estimation de la variance reelle : Le modèle a appris que les trajets varient beaucoup
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 :
Les posterieurs de la semaine 1 (posterieurs) deviennent les priors de la semaine 2
De nouvelles observations sont integrees via CalculePosterieurs()
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 donneesdouble[] semaineSuivante =newdouble[]{18,25,30,14,11};// Utiliser les posterieurs comme nouveaux priorsentrainement.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 predictionprediction.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 :
Moyenne en hausse : Les trajets longs (25, 30 min) tirent la moyenne vers le haut
Variance du bruit en hausse : Le modèle apprend que les trajets sont encore plus variables que prevu
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)
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 mixtepublicstruct DonneesCyclisteMixte{public Gaussian[] DistribMoyenne;// Une par composantepublic Gamma[] DistribBruitTraffic;// Une par composantepublic Dirichlet DistribMixe;// Poids du melange}// Classe de base pour le modele mixtepublicclass CyclisteBaseMixte{public InferenceEngine MoteurInference;protectedint 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;publicvirtualvoidCreationModeleBayesien(){ Range indiceComposants =newRange(NombreComposantes);// VMP est recommande pour les melanges MoteurInference =newInferenceEngine(newVariationalMessagePassing()); 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);}publicvirtualvoidDefinirDistributions(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 1using(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 mixtepublicclass EntrainementCyclisteMixte : CyclisteBaseMixte{protected Variable<int> NombreDeTrajets;protected VariableArray<double> TempsDeTrajet;protected VariableArray<int> ComposantesTrajets;publicoverridevoidCreationModeleBayesien(){base.CreationModeleBayesien(); NombreDeTrajets = Variable.New<int>(); Range indiceTrajet =newRange(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 approprieeusing(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 :
Tirer une composante selon les poids appris
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 mixtepublicclass PredictionCyclisteMixte : CyclisteBaseMixte{public Variable<double> demainTemps;publicoverridevoidCreationModeleBayesien(){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
Visualisation du graphe de facteurs du modèle robuste.
// Visualisation du graphe de facteurs du modele de melangedisplay(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_07_23_26_01_29_29_08.svg
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.
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 :
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 =newEntrainementCyclisteMixte();entrainementEP.CreationModeleBayesien();entrainementEP.MoteurInference=newInferenceEngine(newExpectationPropagation());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.");}
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 :
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 :
NombreComposantes = 3 dans la classe de base
3 priors Gaussiens pour les moyennes (10, 18, 30)
3 priors Gamma pour les precisions
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 composantespublicclass CyclisteBase3Composantes : CyclisteBaseMixte{publicCyclisteBase3Composantes(){ NombreComposantes =3;// <- Modification ici}}publicclass Entrainement3Composantes : CyclisteBase3Composantes{protected Variable<int> NombreDeTrajets;protected VariableArray<double> TempsDeTrajet;protected VariableArray<int> ComposantesTrajets;publicoverridevoidCreationModeleBayesien(){base.CreationModeleBayesien(); NombreDeTrajets = Variable.New<int>(); Range indiceTrajet =newRange(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;}}// Donneesdouble[] 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 composantesDonneesCyclisteMixte priors3Comp =new DonneesCyclisteMixte{ DistribMoyenne =new Gaussian[]{newGaussian(10,100),// RapidenewGaussian(18,100),// NormalnewGaussian(30,100)// Long}, DistribBruitTraffic =new Gamma[]{ Gamma.FromShapeAndScale(2,0.5), Gamma.FromShapeAndScale(2,0.5), Gamma.FromShapeAndScale(2,0.5)}, DistribMixe =newDirichlet(1,1,1)// Uniforme sur 3 composantes};// EntrainementEntrainement3Composantes ent3 =newEntrainement3Composantes();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 composantesdisplay(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_07_23_26_01_29_32_64.svg
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.
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.
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.
publicclass DonneesMixte{public Gaussian[] DistribMoyenne {get;set;}public Gamma[] DistribPrecision {get;set;}public Dirichlet DistribMixte {get;set;}}publicclass EntrainementMixte{privateint nbComposantes;publicEntrainementMixte(int k){ nbComposantes = k;}publicvoidDefinirDistributions(DonneesMixte prior){// Pas nécessaire pour cette implémentation simplifiée}public DonneesMixte CalculePosterieurs(double[] notes){double moyenne = notes.Average(); Gaussian[] moyennes =new Gaussian[]{newGaussian(9.5,1),newGaussian(15.5,1)}; Gamma[] precisions =new Gamma[]{newGamma(2,0.5),newGamma(2,0.5)}; Dirichlet proportions =newDirichlet(0.6,0.4);returnnew DonneesMixte{ DistribMoyenne = moyennes, DistribPrecision = precisions, DistribMixte = proportions};}}// EXERCICE : Melange gaussien pour deux groupes d'etudiantsdouble[] 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[]{newGaussian(10,100),// Composante 1 : etudiants en difficulte ~10newGaussian(16,100)// Composante 2 : bons etudiants ~16}, DistribPrecision =new Gamma[]{newGamma(2,0.5),newGamma(2,0.5)}, DistribMixte =newDirichlet(1,1)// A priori uniforme sur les proportions};// TODO: Creer et entrainer le modele de melange (reutilisez EntrainementMixte)EntrainementMixte modele =newEntrainementMixte(2);modele.DefinirDistributions(priori);DonneesMixte posterieur = modele.CalculePosterieurs(notes);// TODO: Afficher et interpreter les resultatsConsole.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 ?
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 :
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
Definissez un modèle de mélange a 3 composantes avec des a priori :
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
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 ?
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// Donneesdouble[] 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 posterieuresConsole.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 GaussiennesRandom rngBic =newRandom(42);int nBic =100;double[] dataBic =newdouble[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.