Infer-9-Classification : Classification Bayesienne

Serie : Programmation Probabiliste avec Infer.NET (9/19)
Duree estimee : 50 minutes
Prerequis : Infer-8-TrueSkill


Objectifs

  • Implementer la regression logistique bayesienne
  • Comprendre le Bayes Point Machine (BPM)
  • Appliquer l’inference bayesienne aux tests cliniques (A/B testing)
  • Gerer l’incertitude dans les predictions

Sources et références canoniques

Ce notebook est distillé des sources fondatrices suivantes (audit fidélité, voir #8081) :

  • Bayes Point Machine — Herbrich, R., Graepel, T. & Campbell, C. (2001). Bayes Point Machines. Journal of Machine Learning Research, 1:245–279. DOI: 10.1162/153244301753683717. Papier fondateur : le BPM approxime la prédiction bayésienne complète par un unique point représentatif (le « Bayes point »), estimé par Expectation Propagation.
  • Régression logistique bayésienne (probit, EP) — Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. §4.3 (Probit Regression), §4.5 (Bayesian Logistic Regression).
  • Exemple de référence Infer.NET — dépôt dotnet/infer, dossier Examples (Bayes Point Machine, classification). Implémentation canonique C# dont ce notebook est dérivé.

1. Configuration

Cette section prepare l’environnement pour les modèles de classification bayesienne. Contrairement aux classifieurs déterministes, l’approche bayesienne fournit non seulement une prediction mais aussi une mesure de confiance via des distributions de probabilite.

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

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

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

Chargement du helper de visualisation des graphes de facteurs.

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

// Verification de la disponibilite de Graphviz
if (FactorGraphHelper.IsGraphvizAvailable())
    Console.WriteLine("Graphviz disponible - les graphes de facteurs seront affiches automatiquement.");
else
    Console.WriteLine("Graphviz non installe - utilisez viz-js.com pour visualiser les fichiers .gv generes.");
Graphviz disponible - les graphes de facteurs seront affiches automatiquement.

Environnement pret

Les packages Infer.NET charges incluent : - Microsoft.ML.Probabilistic : Structures de données probabilistes (distributions, variables) - Microsoft.ML.Probabilistic.Compiler : Compilation des modèles en code executable - Microsoft.ML.Probabilistic.Math : Fonctions mathematiques (MMath.NormalCdf pour le probit)

Les algorithmes d’inference disponibles pour la classification :

Algorithme Usage Precision
ExpectationPropagation (EP) modèles probit, BPM Haute
VariationalMessagePassing (VMP) modèles gaussiens mixtes Moderee
GibbsSampling modèles complexes très haute (mais lent)

Note : Pour la classification, EP est généralement prefere car il gere bien les facteurs de troncature (comparaisons avec seuils) inherents aux modèles probit.

2. Classification Probabiliste

différence avec la classification classique

Approche Sortie Incertitude
Classique Classe predite Non
Probabiliste P(classe) Oui
Bayesienne Distribution sur P(classe) Oui + incertitude sur le modèle

Avantages bayesiens

  • Quantification de l’incertitude
  • Regularisation naturelle (priors)
  • Mise a jour incrementale
  • Pas de surapprentissage si bon prior

3. Regression Logistique Bayesienne (1 feature)

Architecture du modèle probit

Le modèle de regression logistique bayesienne utilise une variable latente gaussienne :

\[y_i = \mathbb{1}[w \cdot x_i - b + \epsilon_i > 0]\]

ou : - \(w\) est le poids (prior Gaussien) - \(b\) est le seuil (prior Gaussien) - \(\epsilon_i \sim \mathcal{N}(0, 1/\tau)\) est le bruit (precision \(\tau\) avec prior Gamma)

Structure du graphe de facteurs :

poids ~ Gaussian(0, 10)     seuil ~ Gaussian(0, 10)     bruitPrecision ~ Gamma(2, 0.5)
        \                         /                              /
         \                       /                              /
          [score = poids*x - seuil]                            /
                    \                                         /
                     \                                       /
                      [scoreBruite ~ Gaussian(score, bruitPrecision)]
                                      |
                                      v
                              [y = scoreBruite > 0]

Cette formulation permet d’utiliser l’Expectation Propagation pour inferer les posterieurs sur poids et seuil.

// Donnees : classification binaire avec 1 feature
double[] features = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 };
bool[] labels = { false, false, false, true, false, true, true, true };
int n = features.Length;

// Modele : y = sigmoid(w * x - b)
// Equivalent probit : y = I(w * x - b + noise > 0)

Variable<double> poids = Variable.GaussianFromMeanAndVariance(0, 10).Named("poids");
Variable<double> seuil = Variable.GaussianFromMeanAndVariance(0, 10).Named("seuil");
Variable<double> bruitPrecision = Variable.GammaFromShapeAndScale(2, 0.5).Named("bruit");

Range dataRange = new Range(n);
VariableArray<double> xObs = Variable.Array<double>(dataRange).Named("x");
VariableArray<bool> yObs = Variable.Array<bool>(dataRange).Named("y");

using (Variable.ForEach(dataRange))
{
    Variable<double> score = poids * xObs[dataRange] - seuil;
    Variable<double> scoreBruite = Variable.GaussianFromMeanAndPrecision(score, bruitPrecision);
    yObs[dataRange] = (scoreBruite > 0);
}

xObs.ObservedValue = features;
yObs.ObservedValue = labels;

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

Gaussian poidsPost = moteur.Infer<Gaussian>(poids);
Gaussian seuilPost = moteur.Infer<Gaussian>(seuil);

Console.WriteLine("=== Regression Logistique Bayesienne ===");
Console.WriteLine($"\nPoids : {poidsPost}");
Console.WriteLine($"Seuil : {seuilPost}");
Console.WriteLine($"\nInterpretation : classe 1 si feature > {seuilPost.GetMean() / poidsPost.GetMean():F2}");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
=== Regression Logistique Bayesienne ===

Poids : Gaussian(0,8144, 0,03683)
Seuil : Gaussian(3,378, 0,7493)

Interpretation : classe 1 si feature > 4,15

Analyse du modèle de régression logistique

Résultats : - Poids ≈ 0.81 (positif → classe 1 augmente avec la feature) - Seuil ≈ 3.38 - Point de décision : feature > 4.15 → classe 1

Interprétation géométrique : Le modèle probit définit une frontière de décision à x ≈ 4.15. Les données montrent effectivement une transition entre les classes autour de x=4-5.

Incertitude sur les paramètres : - σ(poids) ≈ 0.19 → relativement certain - σ(seuil) ≈ 0.87 → plus incertain

Avantage bayésien : Contrairement à la régression logistique classique qui donne des estimations ponctuelles, nous obtenons des distributions complètes sur les paramètres, permettant de propager l’incertitude aux prédictions.

// Visualisation du graphe de facteurs de la regression logistique
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_01_17_37_52.svg
Model node0 0 node1 GaussianFromMeanAndVariance node0->node1 mean node3 poids node1->node3 node2 10 node2->node1 variance node4 Multiply node3->node4 a node6 vdouble11[index0] node4->node6 node5 x[index0] node5->node4 b node7 Minus node6->node7 a node9 vdouble12[index0] node7->node9 node8 seuil node8->node7 b node13 Gaussian node9->node13 mean node10 0 node11 GaussianFromMeanAndVariance node10->node11 mean node11->node8 node12 10 node12->node11 variance node15 vdouble13[index0] node13->node15 node14 bruit node14->node13 precision node19 IsPositive node15->node19 x node16 2 node17 Sample node16->node17 shape node17->node14 node18 0,5 node18->node17 scale node20 y[index0] node19->node20

Lecture du graphe de facteurs - Regression Logistique

Le graphe ci-dessus montre la structure du modèle de classification probit :

Variables (ellipses) : - poids : Coefficient lineaire appris (prior Gaussien) - seuil : Seuil de decision (prior Gaussien)
- bruit : Precision du bruit (prior Gamma) - x, y : données observees (rectangles = observations)

Facteurs (rectangles noirs) : - Facteurs gaussiens : encodent les priors et la vraisemblance - Facteur de comparaison (> 0) : lie le score bruite a l’etiquette binaire

Structure de plate : La boucle ForEach créé une “plate” (repetition) sur les n echantillons. Chaque observation partage les mêmes paramètres poids et seuil, ce qui permet l’apprentissage a partir de plusieurs exemples.

Flux d’inference EP : Les messages passent entre facteurs et variables jusqu’a convergence. Les posterieurs sur poids et seuil integrent l’information de toutes les observations.

4. Prediction avec Incertitude

// Prediction pour de nouvelles valeurs
double[] nouveauxX = { 2.5, 4.5, 6.5, 9.0 };

Console.WriteLine("=== Predictions avec incertitude ===");
Console.WriteLine();

foreach (double x in nouveauxX)
{
    // Modele de prediction
    Variable<Gaussian> poidsPrior = Variable.Observed(poidsPost);
    Variable<Gaussian> seuilPrior = Variable.Observed(seuilPost);
    
    Variable<double> poidsPred = Variable.Random<double, Gaussian>(poidsPrior);
    Variable<double> seuilPred = Variable.Random<double, Gaussian>(seuilPrior);
    Variable<double> bruitPred = Variable.GammaFromShapeAndScale(2, 0.5);
    
    Variable<double> scorePred = poidsPred * x - seuilPred;
    Variable<double> scoreBruitePred = Variable.GaussianFromMeanAndPrecision(scorePred, bruitPred);
    Variable<bool> predLabel = (scoreBruitePred > 0);
    
    InferenceEngine moteurPred = new InferenceEngine(new ExpectationPropagation());
    moteurPred.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    
    Bernoulli prediction = moteurPred.Infer<Bernoulli>(predLabel);
    Console.WriteLine($"x = {x:F1} : P(classe=1) = {prediction.GetProbTrue():F3}");
}
=== Predictions avec incertitude ===

Compiling model...done.
x = 2,5 : P(classe=1) = 0,218
Compiling model...done.
x = 4,5 : P(classe=1) = 0,561
Compiling model...done.
x = 6,5 : P(classe=1) = 0,822
Compiling model...done.
x = 9,0 : P(classe=1) = 0,951

Interpretation des predictions probabilistes

Feature (x) P(classe=1) Interpretation
2.5 0.218 très probablement classe 0
4.5 0.561 Zone d’incertitude (proche de 0.5)
6.5 0.822 Probablement classe 1
9.0 0.951 très probablement classe 1

Observations cles :

  1. Gradient de confiance : La probabilite augmente de maniere monotone avec x, coherent avec le poids positif appris.

  2. Zone de transition : Autour de x=4.5, le modèle est incertain (P proche de 0.5). Cela correspond au point de decision calcule (x=4.15).

  3. Propagation de l’incertitude : Ces probabilites ne sont pas juste sigmoid(w*x - b) avec des paramètres fixes. Elles integrent l’incertitude sur w et b apprise pendant l’entrainement.

différence avec la classification déterministe : Un classifieur classique donnerait une prediction binaire (0 ou 1). Ici, nous obtenons une probabilite calibree qui reflette notre confiance reelle basee sur les données disponibles.

Exercice : Influence du prior sur la frontiere de decision

Le modèle de regression logistique bayesienne utilise des priors Gaussiens sur les poids. Le choix du prior (variance) influence la frontiere de decision apprise.

Objectif : Comparez les frontieres de decision obtenues avec un prior large (variance = 100) vs un prior étroit (variance = 0.1) sur le même jeu de données.

étapes : 1. Reprenez les données {1,2,3,4,5,6,7,8} avec labels {F,F,F,T,F,T,T,T} 2. Entrainez un modèle avec Variable.GaussianFromMeanAndVariance(0, 100) pour le poids 3. Entrainez un autre modèle avec Variable.GaussianFromMeanAndVariance(0, 0.1) pour le poids 4. Comparez les poids posterieurs et les points de decision (seuil/poids)

Indices : - Un prior etroit (faible variance) = forte regularisation = poids proches de zero - Un prior large (grande variance) = faible regularisation = poids libres de prendre de grandes valeurs - Calculez le point de decision avec seuil.GetMean() / poids.GetMean()

// Exercice : Influence du prior sur la frontiere de decision
// TODO: Definir les donnees d'entrainement (reutiliser les donnees de la section 3)
// Indice: double[] features = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 };

// TODO: Modele 1 - Prior large (variance = 100)
// Indice: Variable.GaussianFromMeanAndVariance(0, 100) pour poids et seuil

// TODO: Modele 2 - Prior etroit (variance = 0.1)
// Indice: Variable.GaussianFromMeanAndVariance(0, 0.1) pour poids et seuil

// TODO: Comparer les resultats
// Console.WriteLine($"Prior large : poids={w1}, seuil={b1}, decision={b1.GetMean()/w1.GetMean():F2}");
// Console.WriteLine($"Prior etroit : poids={w2}, seuil={b2}, decision={b2.GetMean()/w2.GetMean():F2}");
Console.WriteLine("Exercice a completer : Influence du prior");
Exercice a completer : Influence du prior

5. Classification Multi-Features

Extension aux dimensions superieures

Passer d’une seule feature a plusieurs features generalise le modèle :

Aspect 1 feature p features
paramètre \(w\) (scalaire) \(\mathbf{w}\) (vecteur)
Score \(w \cdot x - b\) \(\mathbf{w}^T \mathbf{x} - b\)
Frontiere Point sur \(\mathbb{R}\) Hyperplan dans \(\mathbb{R}^p\)
Priors 2 Gaussiennes p+1 Gaussiennes

Le graphe de facteurs s’etend naturellement avec un produit scalaire entre le vecteur de poids et le vecteur de features.

// Donnees avec 2 features
double[,] featuresMulti = {
    { 1.0, 2.0 },
    { 2.0, 1.5 },
    { 1.5, 3.0 },
    { 3.0, 3.5 },
    { 4.0, 2.0 },
    { 3.5, 4.0 },
    { 5.0, 3.0 },
    { 4.5, 5.0 }
};
bool[] labelsMulti = { false, false, false, true, false, true, true, true };

int nSamples = labelsMulti.Length;
int nFeatures = 2;

Range sampleRange = new Range(nSamples).Named("sample");
Range featureRange = new Range(nFeatures).Named("feature");

// Poids pour chaque feature
VariableArray<double> poidsMulti = Variable.Array<double>(featureRange).Named("poids");
poidsMulti[featureRange] = Variable.GaussianFromMeanAndVariance(0, 10).ForEach(featureRange);

Variable<double> seuilMulti = Variable.GaussianFromMeanAndVariance(0, 10).Named("seuil");

VariableArray2D<double> xMulti = Variable.Array<double>(sampleRange, featureRange).Named("x");
VariableArray<bool> yMulti = Variable.Array<bool>(sampleRange).Named("y");

using (Variable.ForEach(sampleRange))
{
    // Score = somme(poids[f] * x[f]) - seuil
    Variable<double> scoreMulti = Variable.Sum(
        Variable.Array<double>(featureRange).Named("produit"));
    
    // Alternative plus simple : calculer explicitement
    Variable<double> score0 = poidsMulti[0] * xMulti[sampleRange, 0];
    Variable<double> score1 = poidsMulti[1] * xMulti[sampleRange, 1];
    Variable<double> scoreTot = score0 + score1 - seuilMulti;
    Variable<double> scoreNoise = Variable.GaussianFromMeanAndVariance(scoreTot, 1);
    yMulti[sampleRange] = (scoreNoise > 0);
}

xMulti.ObservedValue = featuresMulti;
yMulti.ObservedValue = labelsMulti;

InferenceEngine moteurMulti = new InferenceEngine(new ExpectationPropagation());
moteurMulti.Compiler.CompilerChoice = CompilerChoice.Roslyn;
moteurMulti.ShowFactorGraph = true;  // Activer la generation du graphe de facteurs

Gaussian[] poidsPostMulti = moteurMulti.Infer<Gaussian[]>(poidsMulti);
Gaussian seuilPostMulti = moteurMulti.Infer<Gaussian>(seuilMulti);

Console.WriteLine("=== Classification Multi-Features ===");
for (int f = 0; f < nFeatures; f++)
{
    Console.WriteLine($"Poids feature {f+1} : {poidsPostMulti[f]}");
}
Console.WriteLine($"Seuil : {seuilPostMulti}");
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
=== Classification Multi-Features ===
Poids feature 1 : Gaussian(0,5454, 0,06499)
Poids feature 2 : Gaussian(1,254, 0,07999)
Seuil : Gaussian(5,058, 0,6716)

Analyse des poids multi-features

résultats obtenus (moyennes postérieures EP de la cellule ci-dessus) : - Poids feature 1 (x1) : 0.545 ± 0.25 - Poids feature 2 (x2) : 1.254 ± 0.28 - Seuil : 5.058 ± 0.82

Interpretation geometrique :

La frontiere de decision est définie par l’equation (coefficients = moyennes postérieures non arrondies ; intercept = seuil/poids2) : \[0.545 \cdot x_1 + 1.254 \cdot x_2 - 5.058 = 0\]

Soit : \(x_2 = -0.44 \cdot x_1 + 4.03\)

Aspect Valeur Signification
Pente frontiere -0.44 Frontiere quasi-horizontale
Intercept 4.03 Coupe l’axe x2 vers 4
Ratio poids 2.3 Feature 2 a 2.3x plus d’influence que feature 1

Importance relative : Le poids de la feature 2 est plus de deux fois celui de la feature 1, indiquant que x2 est plus discriminant pour la classification. Cela pourrait orienter la collecte de données futures.

// Visualisation du graphe de facteurs multi-features
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_01_17_43_39.svg
Model node0 poids[vint5][sample] node1 Multiply node0->node1 a node3 vdouble69[sample] node1->node3 node2 x[sample,vint6] node2->node1 b node8 Plus node3->node8 a node4 poids[vint7][sample] node5 Multiply node4->node5 a node7 vdouble72[sample] node5->node7 node6 x[sample,vint8] node6->node5 b node7->node8 b node9 vdouble73[sample] node8->node9 node10 Minus node9->node10 a node12 vdouble74[sample] node10->node12 node11 seuil node11->node10 b node16 GaussianFromMeanAndVariance node12->node16 mean node13 0 node14 GaussianFromMeanAndVariance node13->node14 mean node14->node11 node15 10 node15->node14 variance node18 vdouble76[sample] node16->node18 node17 1 node17->node16 variance node19 IsPositive node18->node19 x node20 y[sample] node19->node20 node21 0 node22 GaussianFromMeanAndVariance node21->node22 mean node24 poids[feature] node22->node24 node23 10 node23->node22 variance node24->node0 node24->node4

Graphe de facteurs multi-features - Structure vectorielle

Le graphe multi-features illustre l’extension du modèle a plusieurs dimensions :

différences avec le modèle 1D : - poids est maintenant un tableau avec un élément par feature - Le graphe montre une double structure de plates : - Une sur les features (pour les poids) - Une sur les echantillons (pour les observations)

opérations visibles : - Multiplication poids[f] * x[i,f] pour chaque feature - Addition des scores partiels - Soustraction du seuil - Ajout du bruit gaussien - Comparaison avec zero

Partage de paramètres : Les mêmes poids poids[0] et poids[1] sont utilises pour tous les echantillons, ce qui est la cle de la generalisation. Le graphe encode cette contrainte structurelle.

6. Bayes Point Machine (BPM)

Principe

Le BPM est une méthode de classification bayesienne qui : - Marginalise sur tous les hyperplans separateurs possibles - Donne des probabilites calibrees (mesure hors echantillon ci-dessous, section 6) - Utilise EP pour l’inference

Formulation

\[P(y=1|x) = \int P(y=1|x,w) P(w|D) dw\]

Fidélité à la source (Herbrich, Graepel & Campbell, 2001). L’intégrale ci-dessus est la prédiction bayésienne complète — la moyenne sur tout le postérieur \(P(w|D)\). Le Bayes Point Machine à proprement parler l’approxime par un point représentatif unique, le « Bayes point » (centre de masse de la version space), estimé efficacement par EP plutôt que par le coûteux calcul intégral. C’est cette approximation par un seul hyperplan représentatif qui définit le BPM et le distingue d’un classifieur bayésien pleinement marginalisé.

Note technique : modèle probit vs logit

Le code précédent utilise un modèle probit (comparaison avec bruit gaussien) plutot qu’un modèle logit (fonction sigmoid). Voici la différence :

Aspect Probit Logit
Lien \(\Phi^{-1}(p) = w^T x\) \(\log\frac{p}{1-p} = w^T x\)
Distribution latente Gaussienne Logistique
Inference bayesienne Plus facile (EP) Plus difficile
Equivalence pratique Quasi-identique pour la plupart des cas

En Infer.NET, le modèle probit est prefere car il se prete naturellement a l’Expectation Propagation avec des distributions gaussiennes.

\[P(y=1|x) = \Phi(w^T x) = \int_{-\infty}^{w^T x} \mathcal{N}(z|0,1) dz\]

ou \(\Phi\) est la fonction de repartition de la loi normale standard.

Référence. Bishop (2006), PRML §4.3.5 (Probit Regression) et §4.5 (Bayesian Logistic Regression, approximation de Laplace). Le choix du lien probit (bruit gaussien latent) plutôt que logit (bruit logistique) est précisément ce qui rend l’inférence par EP tractable en Infer.NET — le lien probit étant introduit au §4.3.5.

// Bayes Point Machine simplifie

public class SimpleBPM
{
    private int nFeatures;
    private Gaussian[] poidsPosteriors;
    private Gaussian seuilPosterior;
    private InferenceEngine moteur;
    
    public SimpleBPM(int nFeatures)
    {
        this.nFeatures = nFeatures;
        this.moteur = new InferenceEngine(new ExpectationPropagation());
        this.moteur.Compiler.CompilerChoice = CompilerChoice.Roslyn;
        this.moteur.ShowFactorGraph = true;  // Activer la generation du graphe de facteurs
    }
    
    public void Entrainer(double[,] X, bool[] y)
    {
        int n = y.Length;
        Range sampleRange = new Range(n);
        Range featureRange = new Range(nFeatures);
        
        VariableArray<double> poids = Variable.Array<double>(featureRange);
        poids[featureRange] = Variable.GaussianFromMeanAndVariance(0, 1).ForEach(featureRange);
        
        Variable<double> seuil = Variable.GaussianFromMeanAndVariance(0, 1);
        
        VariableArray2D<double> xVar = Variable.Array<double>(sampleRange, featureRange);
        VariableArray<bool> yVar = Variable.Array<bool>(sampleRange);
        
        using (Variable.ForEach(sampleRange))
        {
            Variable<double> score = Variable.Constant(0.0);
            for (int f = 0; f < nFeatures; f++)
            {
                score = score + poids[f] * xVar[sampleRange, f];
            }
            score = score - seuil;
            Variable<double> scoreNoise = Variable.GaussianFromMeanAndVariance(score, 1);
            yVar[sampleRange] = (scoreNoise > 0);
        }
        
        xVar.ObservedValue = X;
        yVar.ObservedValue = y;
        
        poidsPosteriors = moteur.Infer<Gaussian[]>(poids);
        seuilPosterior = moteur.Infer<Gaussian>(seuil);
    }
    
    public double Predire(double[] x)
    {
        // Score moyen
        double scoreMoyen = 0;
        double scoreVariance = 0;
        
        for (int f = 0; f < nFeatures; f++)
        {
            scoreMoyen += poidsPosteriors[f].GetMean() * x[f];
            scoreVariance += poidsPosteriors[f].GetVariance() * x[f] * x[f];
        }
        scoreMoyen -= seuilPosterior.GetMean();
        scoreVariance += seuilPosterior.GetVariance() + 1;  // +1 pour le bruit
        
        // Probit : P(score + noise > 0) - utiliser MMath.NormalCdf
        return MMath.NormalCdf(scoreMoyen / Math.Sqrt(scoreVariance));
    }
}

Console.WriteLine("Classe SimpleBPM definie (avec ShowFactorGraph active).");
Classe SimpleBPM definie (avec ShowFactorGraph active).

Implementation du Bayes Point Machine

La classe SimpleBPM encapsule le workflow complet :

  1. Entrainement (Entrainer) :
    • définit les priors Gaussiens sur les poids et le seuil
    • Construit le graphe de facteurs avec le modèle probit
    • Execute l’inference EP pour obtenir les posterieurs
  2. Prediction (Predire) :
    • Calcule le score moyen : \(\mu_{score} = \mathbf{w}_{post}^T \mathbf{x} - b_{post}\)
    • Calcule la variance totale : \(\sigma^2_{score} = \sum_i \text{Var}(w_i) x_i^2 + \text{Var}(b) + 1\)
    • Retourne \(\Phi(\mu_{score} / \sigma_{score})\) via MMath.NormalCdf

Formule de prediction avec incertitude :

\[P(y=1|\mathbf{x}) = \Phi\left(\frac{\mathbf{\mu}_w^T \mathbf{x} - \mu_b}{\sqrt{\mathbf{x}^T \Sigma_w \mathbf{x} + \sigma_b^2 + 1}}\right)\]

ou \(\Phi\) est la CDF de la loi normale standard.

Validation sur les données 2D

Testons maintenant notre classe SimpleBPM sur les données multi-features définies precedemment pour valider l’implementation. Le BPM devrait donner des predictions coherentes avec l’inference directe, mais avec une API plus simple pour les predictions.

// Utilisation du BPM
var bpm = new SimpleBPM(2);
bpm.Entrainer(featuresMulti, labelsMulti);

Console.WriteLine("=== Predictions BPM ===");
double[][] testPoints = {
    new[] { 1.0, 1.0 },
    new[] { 3.0, 3.0 },
    new[] { 5.0, 4.0 },
    new[] { 2.0, 4.0 }
};

foreach (var point in testPoints)
{
    double prob = bpm.Predire(point);
    Console.WriteLine($"({point[0]}, {point[1]}) : P(classe=1) = {prob:F3}");
}
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
=== Predictions BPM ===
(1, 1) : P(classe=1) = 0,268
(3, 3) : P(classe=1) = 0,603
(5, 4) : P(classe=1) = 0,751
(2, 4) : P(classe=1) = 0,633

Analyse des predictions BPM

Point (x1, x2) P(classe=1) Position relative Verdict
(1, 1) 0.268 Loin sous la frontiere Classe 0
(3, 3) 0.603 Proche de la frontiere Incertain
(5, 4) 0.751 Au-dessus de la frontiere Classe 1
(2, 4) 0.633 Legèrement au-dessus Classe 1 (incertain)

caractéristiques du Bayes Point Machine :

  1. Calibration : Les probabilites proches de 0.5 indiquent correctement les zones d’incertitude pres de la frontiere.

  2. Marginalisation : Le BPM considere tous les hyperplans plausibles ponderes par leur probabilite, pas un seul hyperplan optimal.

  3. Regularisation implicite : Le prior Gaussien sur les poids (variance = 1) empeche le surapprentissage en penalisant les poids extremes.

Comparaison SVM vs BPM : Un SVM donnerait une frontiere “dure” et une classification binaire. Le BPM fournit des probabilites calibrees, particulierement utiles quand le cout d’erreur est asymetrique ou quand il faut prioriser les cas incertains pour une revue humaine.

// Visualisation du graphe de facteurs du BPM
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_01_17_45_30.svg
Model node0 vdouble[]4[vint12][index3] node1 Multiply node0->node1 a node3 vdouble91[index3] node1->node3 node2 vdouble[,]1[index3,vint13] node2->node1 b node12 Plus node3->node12 b node4 vdouble[]4[vint14][index3] node5 Multiply node4->node5 a node7 vdouble95[index3] node5->node7 node6 vdouble[,]1[index3,vint15] node6->node5 b node9 Plus node7->node9 b node8 vdouble92[index3] node8->node9 a node10 vdouble96[index3] node9->node10 node13 Minus node10->node13 a node11 0 node11->node12 a node12->node8 node15 vdouble97[index3] node13->node15 node14 vdouble86 node14->node13 b node19 GaussianFromMeanAndVariance node15->node19 mean node16 0 node17 GaussianFromMeanAndVariance node16->node17 mean node17->node14 node18 1 node18->node17 variance node21 vdouble99[index3] node19->node21 node20 1 node20->node19 variance node22 IsPositive node21->node22 x node23 vbool[]2[index3] node22->node23 node24 0 node25 GaussianFromMeanAndVariance node24->node25 mean node27 vdouble[]4[index4] node25->node27 node26 1 node26->node25 variance node27->node0 node27->node4

Graphe de facteurs du Bayes Point Machine

Le graphe du BPM encapsule la classe SimpleBPM et montre :

Structure identique au modèle multi-features : - Le BPM utilise exactement la même structure de modèle que la section précédente - La différence est dans l’encapsulation (classe reutilisable)

Points cles visibles : 1. Prior unitaire : Variance = 1 sur les poids (regularisation implicite) 2. Score additif : Somme des contributions de chaque feature 3. modèle probit : Comparaison avec bruit gaussien

Marginalisation : Le nom “Bayes Point Machine” vient du fait que la prediction marginalise sur l’ensemble des hyperplans separateurs possibles, ponderes par leur probabilite a posteriori. Le graphe encode cette structure de marginalisation via les connexions entre variables et facteurs.

Calibration hors echantillon du BPM : du claim a la mesure (exemple execute)

La section 6 affiche des probabilites « calibrees » sur des points choisis a la main – une affirmation, pas une mesure. Le jumeau PyMC-9 etendait la meme question en exercice non resolu. Les deux twins mesurent desormais la propriete hors echantillon, avec le meme protocole :

  1. jeu 2 features, 2 classes chevauchantes (N = 400, 200 par classe), decoupe 75/25 stratifiee – les etiquettes de test ne servent jamais pendant l’inference ;
  2. le SimpleBPM ci-dessus, entraine sur le train seul (300 points) ;
  3. probabilites sur le test (100 points) via Predire, qui propage l’incertitude posterieure des poids dans la CDF normale ;
  4. Brier score et AUC (discrimination seule) ;
  5. diagramme de fiabilite : 10 bins de meme largeur, avec effectifs ;
  6. un contre-temoin mal calibre : q = p5/(p5 + (1-p)^5), transformation strictement croissante – l’AUC ne bouge pas, la calibration est detruite (surconfiance).
// --- Jeu 2D chevauchant + split train/test stratifie (protocole commun avec PyMC-9) ---
var rndCal = new Random(42);
(int Npc, int TestPc, double sd) = (200, 50, 1.1);
double[] mu0 = { 2.0, 2.0 }, mu1 = { 3.5, 3.5 };
double Gauss() { double u1 = 1.0 - rndCal.NextDouble(), u2 = rndCal.NextDouble();
                 return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); }
double[] Point(double[] mu) => new[] { mu[0] + sd * Gauss(), mu[1] + sd * Gauss() };

var pts = new List<double[]>(); var labs = new List<bool>();
for (int i = 0; i < Npc; i++) { pts.Add(Point(mu0)); labs.Add(false); }
for (int i = 0; i < Npc; i++) { pts.Add(Point(mu1)); labs.Add(true); }
var order = Enumerable.Range(0, pts.Count).OrderBy(_ => rndCal.NextDouble()).ToArray();   // shuffle seede
var X2 = pts.Select(p => p).ToArray(); var y2 = labs.ToArray();
int nTrain = pts.Count - 2 * TestPc;                                                     // 300 / 100

double[,] Xtr = new double[nTrain, 2], Xte = new double[2 * TestPc, 2];
bool[] ytr = new bool[nTrain], yte = new bool[2 * TestPc];
for (int i = 0; i < pts.Count; i++)
{
    if (i < nTrain) { Xtr[i, 0] = X2[order[i]][0]; Xtr[i, 1] = X2[order[i]][1]; ytr[i] = y2[order[i]]; }
    else { int j = i - nTrain; Xte[j, 0] = X2[order[i]][0]; Xte[j, 1] = X2[order[i]][1]; yte[j] = y2[order[i]]; }
}
Console.WriteLine($"Train : {nTrain} ({ytr.Count(v => v)} positifs) | Test : {2 * TestPc} "
                  + $"({yte.Count(v => v)} positifs) -- etiquettes de test inertes pendant l'inference");

var bpmCal = new SimpleBPM(2);
bpmCal.Entrainer(Xtr, ytr);   // EP sur le train SEUL -- le SimpleBPM de la section 6, tel quel
Train : 300 (154 positifs) | Test : 100 (46 positifs) -- etiquettes de test inertes pendant l'inference
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
// --- Probabilites sur le test + metriques + contre-temoin ---
double[] ProbsOf(double[,] X)
    => Enumerable.Range(0, X.GetLength(0))
                 .Select(i => bpmCal.Predire(new[] { X[i, 0], X[i, 1] })).ToArray();
double[] pTe = ProbsOf(Xte);
double[] qTe = pTe.Select(p => { double a = Math.Pow(p, 5), b = Math.Pow(1 - p, 5);
                                 return a / (a + b); }).ToArray();   // accentuation monotone

double Brier(double[] p, bool[] y) => p.Select((pi, i) => (pi - (y[i] ? 1.0 : 0.0)) * (pi - (y[i] ? 1.0 : 0.0))).Average();
double Auc(double[] p, bool[] y)   // Mann-Whitney par rangs (main, sans dependance)
{
    int n1 = y.Count(v => v), n0 = y.Length - n1;
    double inv = 0;
    for (int i = 0; i < p.Length; i++) for (int j = 0; j < p.Length; j++)
        if (y[i] && !y[j]) inv += p[i] > p[j] ? 1.0 : (p[i] == p[j] ? 0.5 : 0.0);
    return inv / (n0 * n1);
}
double Acc(double[] p, bool[] y) => p.Select((pi, i) => (pi > 0.5) == y[i] ? 1.0 : 0.0).Average();

Console.WriteLine("=== Test (100 points jamais vus) : calibration vs discrimination ===");
Console.WriteLine($"{"modele",-30}{"Brier",8}{"AUC",8}{"acc@0.5",9}");
Console.WriteLine($"{"BPM EP (calibre)",-30}{Brier(pTe, yte),8:F3}{Auc(pTe, yte),8:F3}{Acc(pTe, yte),9:F3}");
Console.WriteLine($"{"contre-temoin p^5 (accentue)",-30}{Brier(qTe, yte),8:F3}{Auc(qTe, yte),8:F3}{Acc(qTe, yte),9:F3}");
Console.WriteLine("\nMeme AUC (transformation strictement croissante), Brier degrade :");
Console.WriteLine("la discrimination est preservee, la calibration ne l'est pas.");
=== Test (100 points jamais vus) : calibration vs discrimination ===
modele                           Brier     AUC  acc@0.5
BPM EP (calibre)                 0,096   0,954    0,870
contre-temoin p^5 (accentue)     0,110   0,954    0,870

Meme AUC (transformation strictement croissante), Brier degrade :
la discrimination est preservee, la calibration ne l'est pas.
// --- Diagramme de fiabilite (10 bins, effectifs) + graphique ---
(int Bin, int N, double Pp, double Fo)[] Fiabilite(double[] p, bool[] y)
{
    return Enumerable.Range(0, 10).Select(k =>
    {
        double lo = k / 10.0, hi = (k + 1) / 10.0;
        var m = Enumerable.Range(0, p.Length).Where(i => (k == 9 ? p[i] >= lo && p[i] <= 1.0
                                                            : p[i] >= lo && p[i] < hi)).ToArray();
        return (k, m.Length,
                m.Any() ? m.Select(i => p[i]).Average() : double.NaN,
                m.Any() ? m.Count(i => y[i]) / (double)m.Length : double.NaN);
    }).ToArray();
}
Console.WriteLine("Bin | effectif | p_pred moyen | freq observee   (BPM EP)");
foreach (var r in Fiabilite(pTe, yte))
    Console.WriteLine($"{r.Bin,3} | {r.N,8} | {r.Pp,12:F3} | {r.Fo,12:F3}");
Bin | effectif | p_pred moyen | freq observee   (BPM EP)
  0 |       13 |        0,050 |        0,000
  1 |       16 |        0,136 |        0,000
  2 |        8 |        0,248 |        0,125
  3 |        6 |        0,358 |        0,167
  4 |        8 |        0,445 |        0,375
  5 |        4 |        0,558 |        0,500
  6 |        6 |        0,653 |        0,667
  7 |        9 |        0,729 |        0,667
  8 |       10 |        0,836 |        0,900
  9 |       20 |        0,956 |        1,000
#load "SvgChartHelper.cs"
var gridCal = Enumerable.Range(0, 21).Select(j => j / 20.0).ToArray();
var rowsBpm = Fiabilite(pTe, yte).Where(r => r.N >= 5).ToArray();
var rowsCtm = Fiabilite(qTe, yte).Where(r => r.N >= 5).ToArray();
SvgChartHelper.Overlay(
    "Diagramme de fiabilite (test, n=100)",
    "probabilite predite (moyenne du bin)", "frequence observee",
    new[] {
        new SvgSeries("calibration parfaite", gridCal, gridCal, TraceStyle.Line, "#888888"),
        new SvgSeries("BPM EP", rowsBpm.Select(r => r.Pp).ToArray(), rowsBpm.Select(r => r.Fo).ToArray(),
            TraceStyle.LineMarkers, "#2a6dba"),
        new SvgSeries("contre-temoin p^5", rowsCtm.Select(r => r.Pp).ToArray(), rowsCtm.Select(r => r.Fo).ToArray(),
            TraceStyle.LineMarkers, "#e41a1c"),
    })
Diagramme de fiabilite (test, n=100)-0.060.220.50.781.0600.250.50.751probabilite predite (moyenne du bin)frequence observeecalibration parfaiteBPM EPcontre-temoin p^5

Lecture : ce que la mesure etablit

  • Le BPM EP est proche de la diagonale sur les bins peuples : la calibration affirmee plus haut est desormais mesuree hors echantillon. Avec 100 points de test, chaque bin attend ~10 evenements – les extremites peu peuplees ne portent pas de conclusion forte.
  • Le contre-temoin garde l’AUC et perd le Brier : reordonner les scores ne change rien a la discrimination, mais pousser les probabilites vers 0 et 1 cree un modele surconfiant dont les probabilites mentent des qu’on les utilise comme des risques (seuil metier, cout attendu).
  • EP vs posterior-moyenne : Predire propage la variance posterieure des poids dans la CDF normale (approximation moment-matched) ; le jumeau PyMC moyenne la CDF sur les tirages NUTS. Les AUC des deux moteurs doivent etre proches ; un ecart de Brier mesure cette difference d’integration, pas une difference de protocole.
  • Ce qu’il faut retenir : une bonne discrimination (AUC) ne suffit pas ; la calibration est une propriete separee, et elle se mesure sur des donnees retenues, jamais sur l’ajustement.

7. Test Clinique Bayesien (A/B Testing)

Contexte

Comparer l’efficacite d’un nouveau traitement vs placebo.

Approche bayesienne

  • Prior sur l’efficacite de chaque traitement
  • Mise a jour avec les observations
  • Probabilite que le traitement soit meilleur
// Test clinique A/B bayesien

// Donnees observees
int nPlacebo = 100;
int guerisPlacebo = 30;  // 30% guerison

int nTraitement = 100;
int guerisTraitement = 45;  // 45% guerison

// Modele : taux de guerison pour chaque groupe
Variable<double> tauxPlacebo = Variable.Beta(1, 1).Named("tauxPlacebo");  // Prior uniforme
Variable<double> tauxTraitement = Variable.Beta(1, 1).Named("tauxTraitement");

// Observations (distribution binomiale)
Variable<int> obsPlacebo = Variable.Binomial(nPlacebo, tauxPlacebo);
Variable<int> obsTraitement = Variable.Binomial(nTraitement, tauxTraitement);

obsPlacebo.ObservedValue = guerisPlacebo;
obsTraitement.ObservedValue = guerisTraitement;

// Traitement est meilleur ?
Variable<bool> traitementMeilleur = (tauxTraitement > tauxPlacebo);

InferenceEngine moteurClinique = new InferenceEngine();
moteurClinique.Compiler.CompilerChoice = CompilerChoice.Roslyn;
moteurClinique.ShowFactorGraph = true;  // Activer la generation du graphe de facteurs

Beta tauxPlaceboPost = moteurClinique.Infer<Beta>(tauxPlacebo);
Beta tauxTraitementPost = moteurClinique.Infer<Beta>(tauxTraitement);
Bernoulli traitementMeilleurPost = moteurClinique.Infer<Bernoulli>(traitementMeilleur);

Console.WriteLine("=== Test Clinique Bayesien ===");
Console.WriteLine($"\nPlacebo : {guerisPlacebo}/{nPlacebo} guerisons");
Console.WriteLine($"Traitement : {guerisTraitement}/{nTraitement} guerisons");

Console.WriteLine($"\nTaux placebo : {tauxPlaceboPost}");
Console.WriteLine($"  Moyenne : {tauxPlaceboPost.GetMean():F3}");
Console.WriteLine($"  IC 95% : [{tauxPlaceboPost.GetMean() - 2*Math.Sqrt(tauxPlaceboPost.GetVariance()):F3}, {tauxPlaceboPost.GetMean() + 2*Math.Sqrt(tauxPlaceboPost.GetVariance()):F3}]");

Console.WriteLine($"\nTaux traitement : {tauxTraitementPost}");
Console.WriteLine($"  Moyenne : {tauxTraitementPost.GetMean():F3}");

Console.WriteLine($"\nP(traitement meilleur) = {traitementMeilleurPost.GetProbTrue():F3}");
Compiling model...compilation had 1 warning(s).
  [1] DifferenceBetaOp.DifferenceAverageConditional(tauxTraitement_uses_F[1], tauxPlacebo_uses_F[1]) has quality band Experimental which is less than the recommended quality band (Preview)
done.
=== Test Clinique Bayesien ===

Placebo : 30/100 guerisons
Traitement : 45/100 guerisons

Taux placebo : Beta(31,71)[mean=0,3039]
  Moyenne : 0,304
  IC 95% : [0,213, 0,395]

Taux traitement : Beta(46,56)[mean=0,451]
  Moyenne : 0,451

P(traitement meilleur) = 0,986

Analyse du test clinique

Résultats : - Placebo : 30% guérison (IC 95% : 21%-39%) - Traitement : 45% guérison (IC 95% : 35%-55%) - P(traitement meilleur) = 0.986

Comparaison avec l’approche fréquentiste :

Aspect Fréquentiste Bayésien
Question “Peut-on rejeter H0 ?” “Quelle est P(traitement meilleur) ?”
Réponse p-value < 0.05 → significatif P = 98.6% → très probable
Interprétation Difficile à communiquer Directe et intuitive

Note importante : Le warning “quality band Experimental” indique que l’opérateur DifferenceBetaOp est encore en développement dans Infer.NET. Les résultats restent fiables mais cette fonctionnalité pourrait évoluer.

Application clinique : Avec 98.6% de probabilité que le traitement soit meilleur, un comité d’éthique pourrait recommander de proposer le traitement au groupe placebo.

// Visualisation du graphe de facteurs du test clinique
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_01_17_49_19.svg
Model node0 Beta(1,1)[mean=0,5] node1 Random node0->node1 dist node2 tauxPlacebo node1->node2 node4 Binomial node2->node4 p node7 Minus node2->node7 b node3 100 node3->node4 n node5 30 node4->node5 node6 tauxTraitement node6->node7 a node12 Binomial node6->node12 p node8 vdouble126 node7->node8 node14 IsPositive node8->node14 x node9 Beta(1,1)[mean=0,5] node10 Random node9->node10 dist node10->node6 node11 100 node11->node12 n node13 45 node12->node13 node15 vbool16 node14->node15

Graphe de facteurs du test A/B clinique

Ce graphe illustre le modèle comparatif Beta-Binomial :

Structure parallele : - Deux branches independantes pour tauxPlacebo et tauxTraitement - Chaque branche : Prior Beta -> Facteur Binomial -> Observation

Variables : - tauxPlacebo, tauxTraitement : paramètres latents (probabilites de guerison) - Les observations sont des comptages binomiaux (rectangles)

Facteur de comparaison : Le facteur tauxTraitement > tauxPlacebo (visible comme opérateur de différence) est le coeur du test A/B. Il permet de repondre directement : “Quelle est la probabilite que le traitement soit meilleur ?”

Independence a priori : Les deux taux ont des priors independants Beta(1,1). L’information sur la comparaison vient uniquement des données observees, pas d’hypotheses a priori sur leur relation.

modèle conjugue Beta-Binomial

Le test clinique utilise le modèle classique Beta-Binomial :

Prior : \(\theta \sim \text{Beta}(1, 1)\) (uniforme sur [0,1])

Vraisemblance : \(k | \theta \sim \text{Binomial}(n, \theta)\)

Posterior : \(\theta | k \sim \text{Beta}(1 + k, 1 + n - k)\)

Cette conjugaison permet une inference exacte. La question “le traitement est-il meilleur ?” se traduit par : \[P(\theta_{traitement} > \theta_{placebo} | \text{données})\]

Infer.NET calcule cette probabilite via l’opérateur DifferenceBetaOp.

8. Effet de la Taille d’Echantillon

Puissance statistique bayesienne

Une question naturelle est : “Combien de patients faut-il recruter pour detecter une différence ?” L’analyse suivante montre comment la certitude evolue avec la taille de l’echantillon, a effet constant (30% vs 45%).

// Impact de la taille d'echantillon

Console.WriteLine("=== Impact de la taille d'echantillon ===");
Console.WriteLine("\nMeme ratio (30% vs 45%), differentes tailles :\n");

int[] tailles = { 10, 50, 100, 500, 1000 };

foreach (int n in tailles)
{
    int gP = (int)(n * 0.30);
    int gT = (int)(n * 0.45);
    
    Variable<double> tP = Variable.Beta(1, 1);
    Variable<double> tT = Variable.Beta(1, 1);
    
    Variable.ConstrainEqual(Variable.Binomial(n, tP), gP);
    Variable.ConstrainEqual(Variable.Binomial(n, tT), gT);
    
    Variable<bool> meilleur = (tT > tP);
    
    InferenceEngine m = new InferenceEngine();
    m.Compiler.CompilerChoice = CompilerChoice.Roslyn;
    
    double prob = m.Infer<Bernoulli>(meilleur).GetProbTrue();
    Console.WriteLine($"n = {n,4} : P(traitement meilleur) = {prob:F4}");
}

Console.WriteLine("\n=> Plus de donnees = plus de certitude");
=== Impact de la taille d'echantillon ===

Meme ratio (30% vs 45%), differentes tailles :

Compiling model...compilation had 1 warning(s).
  [1] DifferenceBetaOp.DifferenceAverageConditional(vdouble128_uses_F[1], vdouble127_uses_F[1]) has quality band Experimental which is less than the recommended quality band (Preview)
done.
n =   10 : P(traitement meilleur) = 0,6702
Compiling model...compilation had 1 warning(s).
  [1] DifferenceBetaOp.DifferenceAverageConditional(vdouble131_uses_F[1], vdouble130_uses_F[1]) has quality band Experimental which is less than the recommended quality band (Preview)
done.
n =   50 : P(traitement meilleur) = 0,9258
Compiling model...compilation had 1 warning(s).
  [1] DifferenceBetaOp.DifferenceAverageConditional(vdouble134_uses_F[1], vdouble133_uses_F[1]) has quality band Experimental which is less than the recommended quality band (Preview)
done.
n =  100 : P(traitement meilleur) = 0,9862
Compiling model...compilation had 1 warning(s).
  [1] DifferenceBetaOp.DifferenceAverageConditional(vdouble137_uses_F[1], vdouble136_uses_F[1]) has quality band Experimental which is less than the recommended quality band (Preview)
done.
n =  500 : P(traitement meilleur) = 1,0000
Compiling model...compilation had 1 warning(s).
  [1] DifferenceBetaOp.DifferenceAverageConditional(vdouble140_uses_F[1], vdouble139_uses_F[1]) has quality band Experimental which is less than the recommended quality band (Preview)
done.
n = 1000 : P(traitement meilleur) = 1,0000

=> Plus de donnees = plus de certitude

Convergence de la certitude avec la taille d’echantillon

Taille (n) P(traitement meilleur) Interpretation
10 0.670 Faible evidence (proche du hasard)
50 0.926 Evidence moderee
100 0.986 Evidence forte
500 ~1.000 Evidence très forte
1000 ~1.000 Evidence quasi-certaine

Loi de convergence :

La certitude croit approximativement comme \(\sqrt{n}\) : - Doubler la confiance necessite quadrupler l’echantillon - Passer de 67% a 99% necessite ~10x plus de données (n=10 -> n=100 dans le tableau ci-dessus)

Implications pratiques :

Contexte Taille recommandee Justification
Test pilote n = 50 Detection effet important (>90%)
Essai clinique n = 100-500 Standard reglementaire
Decision critique n > 500 Minimiser risque d’erreur

Avantage bayesien : Contrairement aux tests frequentistes qui donnent une reponse binaire (significatif/non-significatif), l’approche bayesienne permet un arret adaptatif : on peut arreter l’essai des que P(traitement meilleur) depasse un seuil predetermine (ex: 95%), ou au contraire continuer si l’evidence est insuffisante.

Exercice : Test A/B avec prior informatif

Dans le test clinique précédent, nous avons utilise Beta(1,1) comme prior uniforme. En pratique, on dispose souvent d’informations a priori issues d’études précédentes.

Objectif : Comparez l’analyse du test clinique avec un prior uniforme vs un prior informatif qui encode la connaissance d’études précédentes (taux de guerison historique = 25%).

étapes : 1. Reprenez les données : placebo 30/100, traitement 45/100 2. Analyse 1 : prior uniforme Beta(1,1) (déjà fait) 3. Analyse 2 : prior informatif Beta(5,15) pour le placebo (moyenne = 0.25, equivaut a 20 observations) 4. Comparez P(traitement meilleur) dans les deux cas

Indices : - Beta(5,15) encode 5 succes et 15 echecs “virtuels”, soit une moyenne de 5/(5+15) = 0.25 - Le prior informatif reduit la variance du posterior et accelere la convergence - Comment le prior informatif sur le placebo affecte-t-il la comparaison ?

// Exercice : Test A/B avec prior informatif
// TODO: Reutiliser les donnees (placebo 30/100, traitement 45/100)
// Indice: nPlacebo = 100, guerisPlacebo = 30, etc.

// TODO: Analyse 1 - Prior uniforme Beta(1,1) (identique a la section 7)
// Indice: Variable<double> tauxPlacebo = Variable.Beta(1, 1);

// TODO: Analyse 2 - Prior informatif Beta(5,15) pour le placebo
// Indice: Variable<double> tauxPlaceboInfo = Variable.Beta(5, 15);
// Ce prior encode 20 "observations virtuelles" avec 25% de taux de guerison

// TODO: Comparer P(traitement meilleur) dans les deux cas
// Console.WriteLine($"Prior uniforme : P(traitement meilleur) = {p1:F3}");
// Console.WriteLine($"Prior informatif : P(traitement meilleur) = {p2:F3}");
Console.WriteLine("Exercice a completer : Test A/B avec prior informatif");
Exercice a completer : Test A/B avec prior informatif

9. Exemple guide : Classification Spam

Enonce

Construisez un classificateur bayesien pour detecter les spams bases sur 3 features : - Nombre de mots en majuscules - Presence du mot “gratuit” - Longueur du message (en centaines de caractères)

données

Objectif de l’exercice

Construire un classificateur de spam en utilisant le Bayes Point Machine implemente precedemment. Les features choisies representent des caractéristiques typiques des spams :

Feature Description Valeur typique spam
Majuscules Nombre de mots en majuscules Eleve (>15)
Gratuit Presence du mot “gratuit” (0/1) 1
Longueur Taille du message (en centaines de caractères) Faible (<2)

Ces features sont simples mais illustrent les principes d’un filtre anti-spam reel.

// Exemple guide : Classification spam

// Donnees d'entrainement
// [nbMajuscules, presenceGratuit (0/1), longueur]
double[,] spamFeatures = {
    { 5, 0, 2.5 },   // Non spam
    { 3, 0, 3.0 },   // Non spam
    { 15, 1, 1.0 },  // Spam
    { 20, 1, 0.5 },  // Spam
    { 2, 0, 4.0 },   // Non spam
    { 25, 1, 1.5 },  // Spam
    { 8, 0, 2.0 },   // Non spam
    { 18, 1, 0.8 }   // Spam
};
bool[] spamLabels = { false, false, true, true, false, true, false, true };

// Entrainement
var spamClassifier = new SimpleBPM(3);
spamClassifier.Entrainer(spamFeatures, spamLabels);

Console.WriteLine("=== Classificateur Spam ===");

// Test sur nouveaux emails
var testEmails = new (double[], string)[] {
    (new[] { 4.0, 0.0, 3.0 }, "Email normal"),
    (new[] { 22.0, 1.0, 1.0 }, "OFFRE GRATUITE!!!"),
    (new[] { 10.0, 0.0, 2.5 }, "Email avec quelques majuscules"),
    (new[] { 30.0, 1.0, 0.5 }, "CLIQUEZ ICI GRATUIT")
};

Console.WriteLine("\nPredictions :");
foreach (var (features, desc) in testEmails)
{
    double probSpam = spamClassifier.Predire(features);
    string verdict = probSpam > 0.5 ? "SPAM" : "OK";
    Console.WriteLine($"  {desc,-30} : P(spam)={probSpam:F3} -> {verdict}");
}
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
=== Classificateur Spam ===

Predictions :
  Email normal                   : P(spam)=0,053 -> OK
  OFFRE GRATUITE!!!              : P(spam)=0,952 -> SPAM
  Email avec quelques majuscules : P(spam)=0,334 -> OK
  CLIQUEZ ICI GRATUIT            : P(spam)=0,984 -> SPAM

Analyse du classificateur spam

résultats de prediction :

Email Majuscules Gratuit Longueur P(spam) Verdict
Email normal 4 Non 3.0 0.053 OK
OFFRE GRATUITE!!! 22 Oui 1.0 0.952 SPAM
Quelques majuscules 10 Non 2.5 0.334 OK
CLIQUEZ ICI GRATUIT 30 Oui 0.5 0.984 SPAM

Facteurs discriminants appris :

Le modèle a appris que le spam est caracterise par : 1. Nombre eleve de majuscules (poids positif) 2. Presence du mot “gratuit” (poids positif fort) 3. Messages courts (poids negatif sur la longueur)

Cas interessants :

  • “Email avec quelques majuscules” (P=0.334) : Malgre 10 majuscules, l’absence de “gratuit” et la longueur normale le sauvent du classement spam.
  • Les deux emails avec “gratuit” ont P(spam) > 95%, montrant l’importance de cette feature.

Application industrielle : Ce type de classificateur bayesien est utilise dans les filtres anti-spam reels. L’avantage de l’approche probabiliste est de pouvoir définir un seuil de decision adapte au contexte : seuil bas (0.3) pour un filtre agressif, seuil haut (0.8) pour eviter les faux positifs dans un contexte professionnel.

// Visualisation du graphe de facteurs du classificateur spam
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_01_17_51_55.svg
Model node0 vdouble[]8[vint72][index19] node1 Multiply node0->node1 a node3 vdouble155[index19] node1->node3 node2 vdouble[,]3[index19,vint73] node2->node1 b node18 Plus node3->node18 b node4 vdouble[]8[vint74][index19] node5 Multiply node4->node5 a node7 vdouble159[index19] node5->node7 node6 vdouble[,]3[index19,vint75] node6->node5 b node16 Plus node7->node16 b node8 vdouble[]8[vint76][index19] node9 Multiply node8->node9 a node11 vdouble163[index19] node9->node11 node10 vdouble[,]3[index19,vint77] node10->node9 b node13 Plus node11->node13 b node12 vdouble160[index19] node12->node13 a node14 vdouble164[index19] node13->node14 node19 Minus node14->node19 a node15 vdouble156[index19] node15->node16 a node16->node12 node17 0 node17->node18 a node18->node15 node21 vdouble165[index19] node19->node21 node20 vdouble150 node20->node19 b node25 GaussianFromMeanAndVariance node21->node25 mean node22 0 node23 GaussianFromMeanAndVariance node22->node23 mean node23->node20 node24 1 node24->node23 variance node27 vdouble167[index19] node25->node27 node26 1 node26->node25 variance node28 IsPositive node27->node28 x node29 vbool[]4[index19] node28->node29 node30 0 node31 GaussianFromMeanAndVariance node30->node31 mean node33 vdouble[]8[index20] node31->node33 node32 1 node32->node31 variance node33->node0 node33->node4 node33->node8

Graphe du classificateur spam - BPM a 3 features

Le graphe de l’exercice spam reprend la structure du BPM multi-features avec 3 dimensions :

Features encodees : 1. poids[0] : Nombre de mots en majuscules 2. poids[1] : Presence du mot “gratuit” (0/1) 3. poids[2] : Longueur du message

Application pratique : Ce type de graphe est le coeur des filtres bayesiens anti-spam (comme SpamBayes ou les filtres Gmail). L’avantage est de pouvoir ajouter de nouvelles features facilement en etendant le vecteur de poids, sans changer la structure du modèle.

Prediction : La méthode Predire() utilise les posterieurs pour calculer P(spam|x) en integrant l’incertitude sur les poids appris.

10. Resume et Synthese

Concepts cles

Concept Description
Regression logistique bayesienne Priors sur poids, posterieurs après données
Bayes Point Machine Marginalisation sur hyperplans
Test A/B bayesien P(traitement meilleur) directement
Incertitude Quantifiee a chaque étape
Calibration Probabilites refletent la vraie incertitude

Distributions utilisees

Distribution rôle paramètres
Gaussian Prior/Posterior sur poids (moyenne, variance)
Gamma Prior sur precision du bruit (shape, scale)
Beta Prior/Posterior sur probabilites (alpha, beta)
Bernoulli Prediction de classe (probTrue)
Binomial Comptage de succes (n, p)

Comparaison des approches

Aspect Classification classique Classification bayesienne
Sortie Classe predite Distribution sur les classes
Incertitude modèle Non Oui (distributions sur paramètres)
Incertitude prediction Non Oui (probabilites calibrees)
Regularisation Explicite (L1/L2) Implicite (priors)
Surapprentissage Risque eleve Reduit naturellement
Interpretabilite Limitee Elevee (intervalles de credibilite)

Prochaine étape

Dans Infer-8-TrueSkill, nous explorerons :

  • Le système de classement TrueSkill (Xbox Live)
  • Les matchs 1v1 et la mise a jour des skills
  • La gestion des matchs nuls
  • L’extension aux équipes et multi-joueurs

11. Exercice : Detecteur de Critiques Negatives

Enonce

Classifiez des critiques de films comme positives (true) ou negatives (false) selon deux features : - Feature 0 : nombre de mots negatifs (“mauvais”, “nul”, “ennuyeux”…) - Feature 1 : presence du mot “chef-d’oeuvre” (0 ou 1)

données d’entrainement : - Positives : [0,1], [1,0], [0,1], [2,1] → labels = {true, true, true, true} - Negatives : [5,0], [4,0], [3,0], [6,0] → labels = {false, false, false, false}

Entrainer le BPM et classer 3 nouvelles critiques : [1,0], [0,1], [3,0].

Indice : Reutilisez exactement la structure BPM de la section 6 (Bayes Point Machine) avec 2 features.

// Exemple guide : Classification de critiques de films - BPM 2 features
using Microsoft.ML.Probabilistic.Math;

// TODO: Definir les donnees d'entrainement
// Feature 0 = nb mots negatifs, Feature 1 = chef-d-oeuvre (0 ou 1)

// TODO: Entrainer le BPM (reutiliser la structure de l'exemple guide)

// TODO: Inferer les poids du classifieur

// TODO: Classer les nouvelles critiques (inference predictive)
// Pour chaque critique : calculer P(positive | features)
Console.WriteLine("Exercice a completer");
Exercice a completer

Conclusion

Ce notebook a couvert la classification bayesienne : regression logistique probit, Bayes Point Machine et test A/B clinique.

modèle mécanisme Apport bayesien
Regression probit Score latent gaussien + seuil Distributions sur les poids, pas d’estimations ponctuelles
Bayes Point Machine Marginalisation sur hyperplans Probabilites calibrees, regularisation implicite
Test A/B Beta-Binomial conjugue P(traitement meilleur) directement, arret adaptatif
Distribution rôle
Gaussian Priors sur poids et seuil du modèle probit
Gamma Precision du bruit dans le score latent
Beta Taux de guerison (prior uniforme, posterior conjugue)
Bernoulli Predictions de classe
Binomial Comptage de succes (observations cliniques)

Avantage cle : Contrairement a la classification déterministe, l’approche bayesienne propage l’incertitude des paramètres aux predictions. Les probabilites obtenues sont calibrees, permettant des decisions seuillees adaptees au cout d’erreur (ex : filtre anti-spam agressif vs conservateur).

Retour au sommet