Infer-7-Skills-IRT : Evaluation de Competences et Psychometrie

Serie : Programmation Probabiliste avec Infer.NET (7/19)
Duree estimee : 60 minutes
Prerequis : Infer-4-Bayesian-Networks


Objectifs

  • Comprendre les modèles d’evaluation cognitive
  • Implementer le modèle IRT (Item Response Theory) Difficulty-Ability
  • Construire le modèle DINA (Noisy-And) pour competences multiples
  • Gerer les relations many-to-many entre competences et questions
  • Estimer les paramètres slip et guess

Sources et références canoniques

Ce notebook est distillé des sources fondatrices de la psychométrie et du diagnostic cognitif (audit fidélité, voir #8081) :

  • Item Response Theory (IRT) — Rasch, G. (1960). Probabilistic Models for Some Intelligence and Attainment Tests. Danish Institute for Educational Research. Lord, F. M. (1980). Applications of Item Response Theory to Practical Testing Problems. Erlbaum. Birnbaum, A. (1968). Some latent trait models and their use in inferring an examinee’s ability, dans Lord & Novick, Statistical Theories of Mental Test Scores. Addison-Wesley. La formulation \(P=\sigma(\text{capacite}-\text{difficulte})\) implémentée ici est le modèle 1PL / Rasch ; les extensions 2PL (paramètre de discrimination) et 3PL (paramètre de guessing) sont dues à Birnbaum.
  • Modèle DINA — de la Torre, J. (2009). DINA Model and Parameter Estimation: A Didactic. Journal of Educational and Behavioral Statistics, 34(1):115–130 (DOI 10.3102/1076998607309474). Cadre canonique du diagnostic cognitif : matrice \(Q\), paramètres slip/guess, règle conjonctive (Noisy-AND).
  • Diagnosis cognitif (cadre général) — Rupp, A. A., Templin, J. & Henson, R. A. (2010). Diagnostic Measurement: Theory, Methods, and Applications. Guilford Press. Synthèse des modèles DINA, DINO, NIDA et de l’estimation des Q-matrices.

1. Configuration

Nous preparons l’environnement pour les modèles d’evaluation de competences. Ces modèles psychometriques (IRT, DINA) utilisent des variables latentes pour representer les capacites non observees des etudiants et les difficultes des questions.

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

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

Console.WriteLine("Infer.NET pret !");
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 la visualisation des graphes de facteurs
#load "FactorGraphHelper.cs"

Console.WriteLine("FactorGraphHelper charge.");
Console.WriteLine($"Graphviz disponible : {FactorGraphHelper.IsGraphvizAvailable()}");
FactorGraphHelper charge.
Graphviz disponible : True

Visualisation des graphes de facteurs

Le helper FactorGraphHelper permet d’afficher les graphes de facteurs generes par Infer.NET directement dans le notebook. Ces visualisations montrent :

  • Variables observees (rectangles gris) : données fournies au modèle
  • Variables latentes (ovales) : paramètres a inferer
  • Facteurs (carres noirs) : relations probabilistes entre variables

Pour activer la generation des graphes, on utilise engine.ShowFactorGraph = true sur chaque moteur d’inference.

Composants charges

Package rôle
Microsoft.ML.Probabilistic Moteur d’inference principal
Microsoft.ML.Probabilistic.Compiler Compilation des modèles en code C#

Les modèles IRT et DINA utilisent des variables latentes continues (capacites) ou discretes (competences binaires) pour representer des caractéristiques non directement observables des etudiants.

2. Introduction a l’Evaluation Cognitive

problème

Comment evaluer les competences d’un etudiant a partir de ses reponses a un test ?

Defis

Defi Description
Capacite latente La competence n’est pas directement observable
Bruit Les reponses peuvent etre correctes par chance ou incorrectes par erreur
Difficulte variable Les questions ont des difficultes différentes
Competences multiples Une question peut necessiter plusieurs competences

Approches

modèle caractéristique
IRT classique Capacite unidimensionnelle + difficulte des questions
DINA Competences discretes multiples + matrice Q
Bayesien hiérarchique paramètres appris de maniere adaptative

Origine pedagogique : Model-Based Machine Learning (Chap. 2)

Ce notebook est une distillation pratique en Infer.NET du Chapitre 2 — Assessing People’s Skills de Model-Based Machine Learning (Winn, Bishop, Diethe, Guiver & Zaykov, 2020 — mbmlbook.com), qui resout le meme probleme : inferer les competences d’un candidat a partir de ses reponses a un test, en modelisant capacite latente, difficulte des questions et bruit d’observation (slip/guess, lien probit).

Modele implante Racine academique
IRT (Difficulty-Ability, lien Probit) Rasch (1960), Lord (1980), Birnbaum (1968) — cf. section 3
DINA (Deterministic Input, Noisy-AND) Junker & Sijtsma (2001)

La structure pedagogique (probleme -> defis -> modele -> graphe de facteurs) reprend la demarche de modelisation du chapitre MBML ; les scenarios numeriques et l’implementation Infer.NET sont propres au notebook.

3. modèle IRT : Difficulty-Ability

Formulation

\[P(\text{correct}_{ij}) = \sigma(\text{capacite}_i - \text{difficulte}_j)\]

Ou \(\sigma\) est la fonction logistique (ou probit dans notre cas).

Structure

capacite[i] ~ N(0, 1)    pour chaque etudiant
difficulte[j] ~ N(0, 1)  pour chaque question

avantage[i,j] = capacite[i] - difficulte[j]
reponse[i,j] ~ Probit(avantage[i,j])

Fidélité à la source (Rasch 1960 ; Lord 1980). Le modèle implémenté est le 1PL (one-parameter logistic) / modèle de Rasch : un seul paramètre par item (la difficulté), la capacité étant scalée sur une échelle commune. C’est le modèle IRT le plus parcimonieux ; il impose la propriété forte de specific objectivity (les comparaisons entre personnes sont indépendantes de l’ensemble d’items, et réciproquement) que Rasch défendait comme une exigence de mesure, pas seulement une commodité. Le passage du logistique (\(\sigma\)) au probit utilisé en Infer.NET est une variante de lien équivalente pour l’inférence (cf. Infer-9, Bishop PRML §4.3.5/§4.5) mais ne change pas la structure fondamentale du modèle.

// Modele IRT Difficulty-Ability

int nEtudiants = 10;
int nQuestions = 5;

// Donnees simulees : matrice de reponses (true = correct)
bool[,] reponses = new bool[,] {
    // Q1    Q2     Q3     Q4     Q5
    { true,  true,  true,  false, false },  // Etudiant 1 (bon)
    { true,  true,  false, false, false },  // Etudiant 2
    { true,  false, false, false, false },  // Etudiant 3 (faible)
    { true,  true,  true,  true,  false },  // Etudiant 4 (tres bon)
    { false, false, false, false, false },  // Etudiant 5 (tres faible)
    { true,  true,  false, true,  false },  // Etudiant 6
    { true,  true,  true,  false, true },   // Etudiant 7
    { true,  false, true,  false, false },  // Etudiant 8
    { true,  true,  true,  true,  true },   // Etudiant 9 (excellent)
    { false, true,  false, false, false }   // Etudiant 10
};

// Definition du modele
Range etudiant = new Range(nEtudiants).Named("etudiant");
Range question = new Range(nQuestions).Named("question");

// Capacites latentes des etudiants
VariableArray<double> capacite = Variable.Array<double>(etudiant).Named("capacite");
capacite[etudiant] = Variable.GaussianFromMeanAndPrecision(0, 1).ForEach(etudiant);

// Difficultes des questions
VariableArray<double> difficulte = Variable.Array<double>(question).Named("difficulte");
difficulte[question] = Variable.GaussianFromMeanAndPrecision(0, 1).ForEach(question);

// Discrimination (bruit)
Variable<double> discrimination = Variable.GammaFromShapeAndScale(2, 0.5).Named("discrimination");

// Reponses observees
VariableArray2D<bool> reponseVar = Variable.Array<bool>(etudiant, question).Named("reponse");

using (Variable.ForEach(etudiant))
{
    using (Variable.ForEach(question))
    {
        Variable<double> avantage = capacite[etudiant] - difficulte[question];
        Variable<double> avantageBruite = Variable.GaussianFromMeanAndPrecision(avantage, discrimination);
        reponseVar[etudiant, question] = (avantageBruite > 0);
    }
}

// Observations
reponseVar.ObservedValue = reponses;

Console.WriteLine("Modele IRT defini.");
Modele IRT defini.

Structure des données

Les données sont organisees en une matrice de reponses (10 etudiants x 5 questions) :

Etudiant Q1 Q2 Q3 Q4 Q5 Score
E1 T T T F F 3/5
E2 T T F F F 2/5
E3 T F F F F 1/5
E4 T T T T F 4/5
E5 F F F F F 0/5
E6 T T F T F 3/5
E7 T T T F T 4/5
E8 T F T F F 2/5
E9 T T T T T 5/5
E10 F T F F F 1/5

Patterns interessants : - E1 et E6 ont le même score mais des patterns différents (Q3 vs Q4) - E7 reussit Q5 (difficile) mais pas Q4 → questions de difficulte similaire ?

Lancement de l’inference

Le moteur d’inference va maintenant estimer simultanement : - Les capacites latentes de chaque etudiant (variables non observees) - Les difficultes de chaque question (également latentes)

L’algorithme utilise est Expectation Propagation (EP), adapte aux modèles avec des variables continues et des observations binaires (reponses correctes/incorrectes).

Note technique : L’inference conjointe de capacites et difficultes est possible car les observations (matrice de reponses) contraignent suffisamment le problème. C’est le principe de la calibration IRT utilisee dans les tests standardises.

// Inference
InferenceEngine moteurIRT = new InferenceEngine(new ExpectationPropagation());
moteurIRT.Compiler.CompilerChoice = CompilerChoice.Roslyn;
moteurIRT.ShowFactorGraph = true;  // Genere un fichier .gv pour visualisation

Gaussian[] capacitePost = moteurIRT.Infer<Gaussian[]>(capacite);
Gaussian[] difficultePost = moteurIRT.Infer<Gaussian[]>(difficulte);

Console.WriteLine("=== Capacites des etudiants (IRT) ===");
for (int i = 0; i < nEtudiants; i++)
{
    int nCorrect = 0;
    for (int j = 0; j < nQuestions; j++) if (reponses[i, j]) nCorrect++;
    Console.WriteLine($"Etudiant {i+1} : capacite = {capacitePost[i].GetMean():F2} +/- {Math.Sqrt(capacitePost[i].GetVariance()):F2} (score: {nCorrect}/{nQuestions})");
}

Console.WriteLine("\n=== Difficultes des questions ===");
for (int j = 0; j < nQuestions; j++)
{
    int nReussi = 0;
    for (int i = 0; i < nEtudiants; i++) if (reponses[i, j]) nReussi++;
    Console.WriteLine($"Question {j+1} : difficulte = {difficultePost[j].GetMean():F2} (taux reussite: {nReussi}/{nEtudiants})");
}
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
=== Capacites des etudiants (IRT) ===
Etudiant 1 : capacite = 0,26 +/- 0,60 (score: 3/5)
Etudiant 2 : capacite = -0,26 +/- 0,60 (score: 2/5)
Etudiant 3 : capacite = -0,81 +/- 0,62 (score: 1/5)
Etudiant 4 : capacite = 0,81 +/- 0,62 (score: 4/5)
Etudiant 5 : capacite = -1,44 +/- 0,67 (score: 0/5)
Etudiant 6 : capacite = 0,27 +/- 0,59 (score: 3/5)
Etudiant 7 : capacite = 0,81 +/- 0,61 (score: 4/5)
Etudiant 8 : capacite = -0,27 +/- 0,59 (score: 2/5)
Etudiant 9 : capacite = 1,44 +/- 0,67 (score: 5/5)
Etudiant 10 : capacite = -0,81 +/- 0,61 (score: 1/5)

=== Difficultes des questions ===
Question 1 : difficulte = -1,00 (taux reussite: 8/10)
Question 2 : difficulte = -0,64 (taux reussite: 7/10)
Question 3 : difficulte = 0,00 (taux reussite: 5/10)
Question 4 : difficulte = 0,64 (taux reussite: 3/10)
Question 5 : difficulte = 1,00 (taux reussite: 2/10)

Visualisation du graphe de facteurs du modèle IRT.

// Visualisation du graphe de facteurs IRT
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_00_23_55_33.svg
Model node0 capacite[etudiant] node1 Minus node0->node1 a node3 vdouble15[etudiant][question] node1->node3 node2 difficulte[question] node2->node1 b node10 Gaussian node3->node10 mean node4 0 node5 Gaussian node4->node5 mean node5->node2 node6 1 node6->node5 precision node7 0 node8 Gaussian node7->node8 mean node8->node0 node9 1 node9->node8 precision node12 vdouble16[etudiant][question] node10->node12 node11 discrimination node11->node10 precision node16 IsPositive node12->node16 x node13 2 node14 Sample node13->node14 shape node14->node11 node15 0,5 node15->node14 scale node17 reponse[etudiant,question] node16->node17

Graphe de facteurs du modèle IRT

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

élément Representation Signification
capacite[i] Variable continue (ovale) Capacite latente de chaque etudiant
difficulte[j] Variable continue (ovale) Difficulte de chaque question
discrimination Variable continue paramètre de bruit global
reponse[i,j] Variable observee (rectangle) Reponses correctes/incorrectes
Facteurs Carres noirs Relations probabilistes (Gaussian, IsPositive)

La structure bidimensionnelle (etudiants x questions) créé une grille de facteurs connectant chaque capacite a chaque difficulte via les observations.

Analyse détaillée des résultats IRT

Observation clé : La capacité estimée est fortement corrélée au score brut, mais pas parfaitement identique.

Étudiant Score Capacité Observation
E5 0/5 -1.44 Le plus faible
E9 5/5 +1.44 Le plus fort
E1, E6 3/5 ~+0.27 Niveau moyen

Ordre des questions par difficulté : Q1 < Q2 < Q3 < Q4 < Q5

Cela correspond aux taux de réussite observés (80%, 70%, 50%, 30%, 20%).

Avantage du modèle IRT sur le score brut : - L’écart-type reflète l’incertitude : elle est plus grande aux scores extrêmes (0/5 et 5/5 : sigma ≈ 0,67) et plus faible aux scores intermédiaires (2/5, 3/5 : sigma ≈ 0,59-0,60). Effet de plafond/plancher : un élève qui réussit tout (5/5) ou rate tout (0/5) sature les items, et le modèle ne peut pas situer sa capacité exacte au-delà de ce que les items mesurent ; les scores intermédiaires, eux, engagent les items discriminants proches du niveau de l’élève, ce qui resserre le posterieur. - La capacité tient compte de la difficulté des questions réussies - Un étudiant qui réussit Q5 (difficile) a une capacité plus élevée qu’un autre avec le même score mais sur des questions faciles

3bis. Évaluation du Modèle : Courbes ROC

Une courbe ROC (Receiver Operating Characteristic) permet d’évaluer la qualité des prédictions d’un modèle de classification. Pour l’IRT, nous pouvons prédire si un étudiant va répondre correctement à une question et comparer aux réponses réelles.

Métriques d’évaluation

Métrique Formule Interprétation
AUC Aire sous la courbe ROC 0.5 = aléatoire, 1.0 = parfait
TPR TP / (TP + FN) Taux de vrais positifs (sensibilité)
FPR FP / (FP + TN) Taux de faux positifs (1 - spécificité)

Calcul de la courbe ROC

Le code suivant calcule la courbe ROC en : 1. Calculant P(correct) pour chaque paire (etudiant, question) selon le modèle 2. Triant les predictions par probabilite decroissante 3. Calculant TPR/FPR pour chaque seuil de classification 4. Integrant l’aire sous la courbe (AUC) par la méthode des trapezes

Approximation probit-logit : Le modèle utilise une fonction probit, mais nous approximons avec une logistique (facteur 1.7) pour simplifier le calcul.

// Calcul de la courbe ROC pour le modele IRT

Console.WriteLine("=== Evaluation ROC du modele IRT ===\n");

// Calculer les probabilites de reponse correcte pour chaque paire (etudiant, question)
List<(double prob, bool actual)> predictions = new List<(double, bool)>();

for (int i = 0; i < nEtudiants; i++)
{
    for (int j = 0; j < nQuestions; j++)
    {
        // P(correct) approxime par la fonction probit
        double avantage = capacitePost[i].GetMean() - difficultePost[j].GetMean();
        // Approximation de Phi (CDF normale) par fonction logistique
        double probCorrect = 1.0 / (1.0 + Math.Exp(-1.7 * avantage));
        predictions.Add((probCorrect, reponses[i, j]));
    }
}

// Trier par probabilite decroissante
var sorted = predictions.OrderByDescending(p => p.prob).ToList();

// Calculer les points de la courbe ROC
int totalPos = sorted.Count(p => p.actual);
int totalNeg = sorted.Count - totalPos;

List<(double fpr, double tpr)> rocPoints = new List<(double, double)>();
rocPoints.Add((0.0, 0.0));

int tp = 0, fp = 0;
double lastProb = 1.0;

foreach (var pred in sorted)
{
    if (pred.actual) tp++;
    else fp++;
    
    double tpr = (double)tp / totalPos;
    double fpr = (double)fp / totalNeg;
    rocPoints.Add((fpr, tpr));
}

// Calcul de l'AUC (methode des trapezes)
double auc = 0;
for (int i = 1; i < rocPoints.Count; i++)
{
    double width = rocPoints[i].fpr - rocPoints[i-1].fpr;
    double height = (rocPoints[i].tpr + rocPoints[i-1].tpr) / 2;
    auc += width * height;
}

Console.WriteLine($"AUC (Area Under Curve) : {auc:F3}");
Console.WriteLine($"  0.5 = aleatoire, 1.0 = parfait");
Console.WriteLine($"  Interpretation : {(auc > 0.9 ? "Excellent" : auc > 0.8 ? "Bon" : auc > 0.7 ? "Acceptable" : "Faible")}");

// Afficher quelques points de la courbe
Console.WriteLine("\nPoints cles de la courbe ROC :");
Console.WriteLine("| Seuil | FPR  | TPR  |");
Console.WriteLine("|-------|------|------|");
double[] seuils = { 0.9, 0.7, 0.5, 0.3, 0.1 };
foreach (double seuil in seuils)
{
    int tpS = predictions.Count(p => p.prob >= seuil && p.actual);
    int fpS = predictions.Count(p => p.prob >= seuil && !p.actual);
    double tprS = (double)tpS / totalPos;
    double fprS = (double)fpS / totalNeg;
    Console.WriteLine($"| {seuil:F1}   | {fprS:F2} | {tprS:F2} |");
}
=== Evaluation ROC du modele IRT ===

AUC (Area Under Curve) : 0,949
  0.5 = aleatoire, 1.0 = parfait
  Interpretation : Excellent

Points cles de la courbe ROC :
| Seuil | FPR  | TPR  |
|-------|------|------|
| 0,9   | 0,00 | 0,28 |
| 0,7   | 0,00 | 0,64 |
| 0,5   | 0,16 | 0,84 |
| 0,3   | 0,36 | 1,00 |
| 0,1   | 0,72 | 1,00 |

Interprétation de la Courbe ROC

AUC (Area Under Curve) mesure la capacité discriminante du modèle :

AUC Interprétation Action suggérée
0.9-1.0 Excellent Le modèle est très fiable
0.8-0.9 Bon Utilisable en production
0.7-0.8 Acceptable À améliorer si possible
0.5-0.7 Faible Revoir le modèle

Lecture de la table : - Seuil 0.7 : Si on prédit “correct” quand P(correct) > 0.7 - TPR = % de vraies bonnes réponses capturées - FPR = % de fausses alertes (mauvaises réponses prédites correctes)

Application pour l’IRT :

Le modèle IRT devrait avoir une AUC élevée car : 1. Il capture la vraie capacité de chaque étudiant 2. Il tient compte de la difficulté de chaque question 3. La prédiction P(correct) = f(capacité - difficulté) est bien calibrée

Note : Avec seulement 50 observations (10 étudiants × 5 questions), l’AUC peut être bruitée. Un test plus grand donnerait une estimation plus stable.


Transition : La courbe ROC nous montre que le modèle IRT capture bien la structure des données. Cependant, l’IRT suppose une capacite unidimensionnelle. Pour des tests evaluant des competences distinctes, nous avons besoin du modèle DINA presente dans la section suivante.

Exercice : Ajouter un etudiant et predire ses reponses

Le modèle IRT de la section 3 a estime les capacites de 10 etudiants et les difficultes de 5 questions. Utilisez ces résultats pour evaluer un nouvel etudiant.

Objectif : Un 11e etudiant repond aux 5 questions avec le pattern {true, true, false, false, false}. Estimez sa capacite et predisez sa probabilite de succes a une 6e question hypothetique de difficulte 0.5.

étapes : 1. Reprenez les posterieurs des difficultes ( déjà calcules dans la section 3) 2. Ajoutez le nouvel etudiant au modèle avec ses reponses observees 3. Inferez sa capacite posterieure 4. Utilisez le modèle probit pour calculer P(correct | capacite, difficulte=0.5)

Indices : - Fixez les difficultes comme variables observees avec Variable.Observed(difficultePost[j]) puis Variable.Random<double, Gaussian>(...) - La probabilite de reussite se calcule via la fonction probit : 1.0 / (1.0 + Math.Exp(-1.7 * avantage)) - Comparez la capacite estimee du nouvel etudiant avec les autres etudiants

// Exercice : Ajouter un etudiant et predire ses reponses
// TODO: Definir les reponses du nouvel etudiant
// bool[] reponsesNouvel = { true, true, false, false, false };

// TODO: Fixer les difficultes des questions a leurs posterieurs (section 3)
// Indice: Pour chaque question, Variable<Gaussian> diffObs = Variable.Observed(difficultePost[j]);
// Puis Variable<double> diff = Variable.Random<double, Gaussian>(diffObs);

// TODO: Definir la capacite du nouvel etudiant avec un prior N(0,1)
// Variable<double> capaciteNouvel = Variable.GaussianFromMeanAndPrecision(0, 1);

// TODO: Observer les reponses du nouvel etudiant et inferer sa capacite
// Indice: meme structure que le modele IRT de la section 3

// TODO: Calculer P(correct) pour une question de difficulte 0.5
// double avantage = capacitePost.GetMean() - 0.5;
// double probCorrect = 1.0 / (1.0 + Math.Exp(-1.7 * avantage));
Console.WriteLine("Exercice a completer : Predire les reponses d'un nouvel etudiant");
Exercice a completer : Predire les reponses d'un nouvel etudiant

4. modèle DINA : Competences Discretes

Motivation

Le modèle IRT suppose une capacite unidimensionnelle. En realite, un test peut evaluer plusieurs competences.

modèle DINA (Deterministic Input, Noisy And)

  • Chaque etudiant possede ou non chaque competence (variable binaire)
  • Chaque question necessite un sous-ensemble de competences (matrice Q)
  • Reponse correcte si toutes les competences requises sont presentes
  • Avec des erreurs slip et guess

paramètres

paramètre Description
Slip P(incorrect | a toutes les competences) - erreur d’inattention
Guess P(correct | manque une competence) - reponse au hasard

Fidélité à la source (de la Torre 2009). Le modèle DINA (Deterministic Input, Noisy-And) repose sur trois objets canoniques : (1) la matrice \(Q\) qui encode quelles compétences chaque question requiert, (2) le profil latent binaire de l’étudiant (possède/ne possède pas chaque compétence), et (3) la règle conjonctive idéalisée — la réponse est correcte si et seulement si toutes les compétences requises sont présentes, puis bruitée par les deux paramètres appris slip \(s\) (erreur d’inattention malgré la maîtrise) et guess \(g\) (succès au hasard malgré une lacune). Le DINA se distingue de l’IRT par sa multidimensionnalité discrète : au lieu d’une capacité scalaire continue, il infère un profil de compétences — c’est ce passage du trait latent au diagnostic cognitif qui justifie toute la section 4.

Preparation des données DINA

Nous allons maintenant définir un scénario concret pour le modèle DINA :

  • 8 etudiants avec différents profils de competences
  • 6 questions avec des exigences variees (1 a 3 competences)
  • 3 competences (C1, C2, C3)

Les données sont construites pour illustrer comment le modèle discrimine entre les profils. Par exemple, un etudiant ayant C1 et C2 (mais pas C3) reussira Q1, Q2, Q4 mais echouera sur Q3, Q5, Q6.

// Modele DINA simplifie

int nEtud = 8;
int nQuest = 6;
int nCompetences = 3;

// Matrice Q : quelles competences sont requises pour chaque question
// Q1 : C1 seulement
// Q2 : C2 seulement
// Q3 : C3 seulement
// Q4 : C1 et C2
// Q5 : C2 et C3
// Q6 : C1, C2 et C3
bool[,] matriceQ = new bool[,] {
    // C1    C2     C3
    { true,  false, false },  // Q1
    { false, true,  false },  // Q2
    { false, false, true  },  // Q3
    { true,  true,  false },  // Q4
    { false, true,  true  },  // Q5
    { true,  true,  true  }   // Q6
};

// Donnees : reponses des etudiants
bool[,] repDINA = new bool[,] {
    // Q1    Q2     Q3     Q4     Q5     Q6
    { true,  true,  true,  true,  true,  true  },  // E1 : a tout
    { true,  true,  false, true,  false, false },  // E2 : C1, C2 seulement
    { true,  false, true,  false, false, false },  // E3 : C1, C3 seulement
    { false, true,  true,  false, true,  false },  // E4 : C2, C3 seulement
    { true,  false, false, false, false, false },  // E5 : C1 seulement
    { false, true,  false, false, false, false },  // E6 : C2 seulement
    { false, false, true,  false, false, false },  // E7 : C3 seulement
    { false, false, false, false, false, false }   // E8 : rien
};

Console.WriteLine("Donnees DINA definies.");
Console.WriteLine("\nMatrice Q (competences requises par question) :");
for (int q = 0; q < nQuest; q++)
{
    string req = "";
    for (int c = 0; c < nCompetences; c++)
        if (matriceQ[q, c]) req += $"C{c+1} ";
    Console.WriteLine($"  Q{q+1} : {req}");
}
Donnees DINA definies.

Matrice Q (competences requises par question) :
  Q1 : C1 
  Q2 : C2 
  Q3 : C3 
  Q4 : C1 C2 
  Q5 : C2 C3 
  Q6 : C1 C2 C3 

Lecture de la matrice Q

La matrice Q définit la structure du test. Chaque ligne correspond a une question, chaque colonne a une competence.

Question C1 C2 C3 Interpretation
Q1 1 0 0 Evalue C1 seul
Q2 0 1 0 Evalue C2 seul
Q3 0 0 1 Evalue C3 seul
Q4 1 1 0 Necessite C1 ET C2
Q5 0 1 1 Necessite C2 ET C3
Q6 1 1 1 Necessite les 3 competences

Conception de test : Les questions multi-competences (Q4, Q5, Q6) sont plus discriminantes mais aussi plus difficiles. Un bon test melange des questions a competence unique (pour le diagnostic) et des questions composites (pour valider la maitrise globale).

// Note sur l'implementation DINA complete
// Le modele DINA avec AND dynamique sur les competences requises est complexe a
// implementer dans Infer.NET en raison des limitations sur SetTo dans les blocs conditionnels.
// Nous utilisons ci-dessous une version simplifiee qui illustre les concepts.

// Affichage de la structure du modele DINA
Console.WriteLine("=== Structure du modele DINA ===");
Console.WriteLine();
Console.WriteLine("Pour chaque etudiant e et question q :");
Console.WriteLine("  1. Verifier si e a toutes les competences requises par q (selon matrice Q)");
Console.WriteLine("  2. Si oui : P(correct) = 1 - slip");
Console.WriteLine("  3. Si non : P(correct) = guess");
Console.WriteLine();
Console.WriteLine("Parametres a estimer :");
Console.WriteLine("  - Competences[e,c] : chaque etudiant a-t-il chaque competence ?");
Console.WriteLine("  - slip : probabilite d'erreur malgre les competences");
Console.WriteLine("  - guess : probabilite de reussite sans les competences");
=== Structure du modele DINA ===

Pour chaque etudiant e et question q :
  1. Verifier si e a toutes les competences requises par q (selon matrice Q)
  2. Si oui : P(correct) = 1 - slip
  3. Si non : P(correct) = guess

Parametres a estimer :
  - Competences[e,c] : chaque etudiant a-t-il chaque competence ?
  - slip : probabilite d'erreur malgre les competences
  - guess : probabilite de reussite sans les competences

Limites de l’implementation dans Infer.NET

Le modèle DINA complet necessite de calculer dynamiquement le ET logique sur un sous-ensemble variable de competences pour chaque question. Cette opération est difficile a exprimer dans Infer.NET en raison des restrictions sur SetTo dans les blocs conditionnels imbriques.

La cellule suivante presente une implementation simplifiee pour une question spécifique, illustrant le raisonnement DINA.

Implementation simplifiee pour une question

Pour illustrer le raisonnement DINA, nous implementons le modèle pour une seule question (Q4) qui necessite deux competences (C1 ET C2). Cette approche permet de :

  1. définir explicitement la condition aC1 & aC2
  2. Observer une reponse et voir comment cela affecte les probabilites des competences
  3. Estimer les paramètres slip et guess a partir des données

Le code utilise des priors Beta(1,9) pour slip et guess, centres sur des valeurs faibles (~0.1), ce qui correspond a l’hypothese que les erreurs d’inattention et les reponses au hasard sont relativement rares.

// Version simplifiee : modele DINA pour une question specifique

// Question Q4 necessite C1 ET C2
Variable<bool> aC1 = Variable.Bernoulli(0.5).Named("aC1");
Variable<bool> aC2 = Variable.Bernoulli(0.5).Named("aC2");

Variable<double> slipQ4 = Variable.Beta(1, 9).Named("slip");
Variable<double> guessQ4 = Variable.Beta(1, 9).Named("guess");

Variable<bool> toutesCompQ4 = (aC1 & aC2).Named("toutesComp");
Variable<bool> reponseQ4 = Variable.New<bool>().Named("reponse");

using (Variable.If(toutesCompQ4))
{
    reponseQ4.SetTo(!Variable.Bernoulli(slipQ4));
}
using (Variable.IfNot(toutesCompQ4))
{
    reponseQ4.SetTo(Variable.Bernoulli(guessQ4));
}

// Observation : l'etudiant a repondu correctement
reponseQ4.ObservedValue = true;

InferenceEngine mDINA = new InferenceEngine();
mDINA.Compiler.CompilerChoice = CompilerChoice.Roslyn;
mDINA.ShowFactorGraph = true;  // Genere un fichier .gv pour visualisation

Console.WriteLine("=== DINA : Inference des competences ===");
Console.WriteLine($"Question Q4 necessite C1 ET C2");
Console.WriteLine($"Observation : reponse correcte\n");
Console.WriteLine($"P(C1) = {mDINA.Infer<Bernoulli>(aC1).GetProbTrue():F3}");
Console.WriteLine($"P(C2) = {mDINA.Infer<Bernoulli>(aC2).GetProbTrue():F3}");
Console.WriteLine($"P(C1 ET C2) = {mDINA.Infer<Bernoulli>(toutesCompQ4).GetProbTrue():F3}");
Console.WriteLine($"\nSlip estime : {mDINA.Infer<Beta>(slipQ4).GetMean():F3}");
Console.WriteLine($"Guess estime : {mDINA.Infer<Beta>(guessQ4).GetMean():F3}");
=== DINA : Inference des competences ===
Question Q4 necessite C1 ET C2
Observation : reponse correcte

Compiling model...done.
P(C1) = 0,833
P(C2) = 0,833
P(C1 ET C2) = 0,750

Slip estime : 0,093
Guess estime : 0,120

Visualisation du graphe de facteurs du modèle DINA simplifie.

// Visualisation du graphe de facteurs DINA simplifie
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_00_24_00_74.svg
Model node0 Bernoulli(0,5) node1 Random node0->node1 dist node2 aC1 node1->node2 node3 And node2->node3 a node5 toutesComp node3->node5 node4 aC2 node4->node3 b node12 True node5->node12 condition node6 Bernoulli(0,5) node7 Random node6->node7 dist node7->node4 node8 slip node9 Bernoulli node8->node9 probTrue node10 vbool7 node9->node10 node11 Not node10->node11 b node11->node12 node13 guess node14 Bernoulli node13->node14 probTrue node14->node12 node15 Beta(1,9)[mean=0,1] node16 Random node15->node16 dist node16->node13 node17 Beta(1,9)[mean=0,1] node18 Random node17->node18 dist node18->node8

Graphe de facteurs du modèle DINA simplifie

Le graphe illustre la structure conditionnelle du modèle DINA :

élément rôle
aC1, aC2 Competences binaires (Bernoulli prior 0.5)
toutesComp Conjonction aC1 AND aC2
slip, guess paramètres de bruit (Beta priors)
reponse Variable observee (true = correct)

Structure conditionnelle : La reponse depend de toutesComp via deux branches : - Si toutesComp=true : P(correct) = 1 - slip - Si toutesComp=false : P(correct) = guess

Cette structure en gate (porte conditionnelle) est caractéristique des modèles a competences discretes.

Analyse du modèle DINA simplifié

Résultats : - P(C1) = P(C2) = 0.833 (augmenté depuis le prior de 0.5) - P(C1 ET C2) = 0.750 - Slip estimé : ~0.09, Guess estimé : ~0.12

Interprétation :

L’observation d’une réponse correcte à Q4 (qui nécessite C1 ET C2) favorise les deux compétences :

  1. Sans guess : La seule façon de répondre correctement est d’avoir C1 ET C2
  2. Avec guess : Il y a une petite probabilité (~10%) de réussir sans les compétences

Le modèle infère que l’étudiant a probablement les deux compétences, mais conserve une incertitude résiduelle due au guess possible.

Remarque : P(C1 ET C2) = 0.75 ≠ P(C1) × P(C2) = 0.69 car les observations créent une dépendance entre C1 et C2 (explaining away : si l’un est absent, l’autre doit être présent pour expliquer la réussite par guess).

5. Relations Many-to-Many

problème

Dans le modèle DINA, une question peut necessiter plusieurs competences, et une competence peut etre evaluee par plusieurs questions.

Representation avec Subarray

Infer.NET permet d’utiliser Variable.Subarray pour extraire les competences requises pour chaque question.

Demonstration avec plusieurs etudiants

Nous etendons maintenant le modèle a 4 etudiants et 2 questions pour illustrer comment l’inference combine les observations de plusieurs sources :

  • Q1 : Necessite C1 seulement
  • Q2 : Necessite C1 ET C2

Les paramètres slip (0.1) et guess (0.1) sont fixes pour simplifier l’inference sur les competences.

Objectif : Montrer comment les reponses a Q1 et Q2 permettent de separer les etudiants qui ont C1 seul de ceux qui ont aussi C2.

// Demonstration avec plusieurs etudiants et questions

int nE = 4;
int nC = 3;

// Prior : chaque etudiant a 50% de chance d'avoir chaque competence
Range rE = new Range(nE).Named("etudiant");
Range rC = new Range(nC).Named("competence");

VariableArray2D<bool> competences = Variable.Array<bool>(rE, rC).Named("competences");
competences[rE, rC] = Variable.Bernoulli(0.5).ForEach(rE, rC);

// Simuler des observations sur plusieurs questions
// Q1 necessite C1 seulement
// Q2 necessite C1 ET C2

// Observations pour Q1 (C1 seulement)
bool[] obsQ1 = { true, true, false, true };  // E1, E2, E4 ont repondu correctement

for (int e = 0; e < nE; e++)
{
    Variable<bool> toutQ1 = competences[e, 0];  // Seulement C1
    Variable<bool> repQ1 = Variable.New<bool>().Named($"repQ1_E{e+1}");
    using (Variable.If(toutQ1))
    {
        repQ1.SetTo(Variable.Bernoulli(0.9));  // 1 - slip
    }
    using (Variable.IfNot(toutQ1))
    {
        repQ1.SetTo(Variable.Bernoulli(0.1));  // guess
    }
    repQ1.ObservedValue = obsQ1[e];
}

// Observations pour Q2 (C1 ET C2)
bool[] obsQ2 = { true, false, false, true };

for (int e = 0; e < nE; e++)
{
    Variable<bool> toutQ2 = competences[e, 0] & competences[e, 1];
    Variable<bool> repQ2 = Variable.New<bool>().Named($"repQ2_E{e+1}");
    using (Variable.If(toutQ2))
    {
        repQ2.SetTo(Variable.Bernoulli(0.9));
    }
    using (Variable.IfNot(toutQ2))
    {
        repQ2.SetTo(Variable.Bernoulli(0.1));
    }
    repQ2.ObservedValue = obsQ2[e];
}

InferenceEngine mMany = new InferenceEngine();
mMany.Compiler.CompilerChoice = CompilerChoice.Roslyn;
mMany.ShowFactorGraph = true;  // Genere un fichier .gv pour visualisation

Bernoulli[,] compPost = mMany.Infer<Bernoulli[,]>(competences);

Console.WriteLine("=== Inference des competences (many-to-many) ===");
Console.WriteLine("Q1 necessite C1, Q2 necessite C1 ET C2\n");
Console.WriteLine("Observations : Q1=[T,T,F,T], Q2=[T,F,F,T]\n");

for (int e = 0; e < nE; e++)
{
    Console.Write($"Etudiant {e+1} : ");
    for (int c = 0; c < nC; c++)
    {
        Console.Write($"C{c+1}={compPost[e,c].GetProbTrue():F2} ");
    }
    Console.WriteLine();
}
Compiling model...done.
=== Inference des competences (many-to-many) ===
Q1 necessite C1, Q2 necessite C1 ET C2

Observations : Q1=[T,T,F,T], Q2=[T,F,F,T]

Etudiant 1 : C1=0,98 C2=0,89 C3=0,50 
Etudiant 2 : C1=0,83 C2=0,17 C3=0,50 
Etudiant 3 : C1=0,06 C2=0,48 C3=0,50 
Etudiant 4 : C1=0,98 C2=0,89 C3=0,50 

Visualisation du graphe de facteurs many-to-many.

// Visualisation du graphe de facteurs many-to-many
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_00_24_02_59.svg
Model node0 competences[vint15,vint16] node1 And node0->node1 a node3 vbool32 node1->node3 node2 competences[vint17,vint18] node2->node1 b node33 True node3->node33 condition node4 competences[vint19,vint20] node5 And node4->node5 a node7 vbool38 node5->node7 node6 competences[vint21,vint22] node6->node5 b node28 False node7->node28 condition node8 competences[vint23,vint24] node9 And node8->node9 a node11 vbool44 node9->node11 node10 competences[vint25,vint26] node10->node9 b node23 False node11->node23 condition node12 competences[vint27,vint28] node13 And node12->node13 a node15 vbool50 node13->node15 node14 competences[vint29,vint30] node14->node13 b node18 True node15->node18 condition node16 Bernoulli(0,9) node17 Random node16->node17 dist node17->node18 node19 Bernoulli(0,1) node20 Random node19->node20 dist node20->node18 node21 Bernoulli(0,9) node22 Random node21->node22 dist node22->node23 node24 Bernoulli(0,1) node25 Random node24->node25 dist node25->node23 node26 Bernoulli(0,9) node27 Random node26->node27 dist node27->node28 node29 Bernoulli(0,1) node30 Random node29->node30 dist node30->node28 node31 Bernoulli(0,9) node32 Random node31->node32 dist node32->node33 node34 Bernoulli(0,1) node35 Random node34->node35 dist node35->node33 node36 Bernoulli(0,9) node37 Random node36->node37 dist node38 True node37->node38 node39 competences[vint13,vint14] node39->node38 condition node40 Bernoulli(0,1) node41 Random node40->node41 dist node41->node38 node42 Bernoulli(0,9) node43 Random node42->node43 dist node44 False node43->node44 node45 competences[vint11,vint12] node45->node44 condition node46 Bernoulli(0,1) node47 Random node46->node47 dist node47->node44 node48 Bernoulli(0,9) node49 Random node48->node49 dist node50 True node49->node50 node51 competences[vint9,vint10] node51->node50 condition node52 Bernoulli(0,1) node53 Random node52->node53 dist node53->node50 node54 Bernoulli(0,9) node55 Random node54->node55 dist node56 True node55->node56 node57 competences[vint7,vint8] node57->node56 condition node58 Bernoulli(0,1) node59 Random node58->node59 dist node59->node56 node60 Bernoulli(0,5) node61 Random node60->node61 dist node62 competences[etudiant,competence] node61->node62 node62->node0 node62->node2 node62->node4 node62->node6 node62->node8 node62->node10 node62->node12 node62->node14 node62->node39 node62->node45 node62->node51 node62->node57

Graphe de facteurs many-to-many

Ce graphe montre la structure many-to-many caractéristique des modèles DINA :

Aspect Observation
Matrice competences[4,3] 12 variables binaires (4 etudiants x 3 competences)
Reponses Q1 Connectees uniquement a C1 (1 competence requise)
Reponses Q2 Connectees a C1 AND C2 via facteurs conjonctifs
C3 Non connecte aux observations (reste au prior 0.5)

Pattern de connexion : - Chaque etudiant a ses propres variables de competences - Chaque question connecte les competences requises aux observations - Les facteurs AND implementent la logique “toutes les competences necessaires”

Cette structure permet l’inference independante des profils de competences pour chaque etudiant.

Analyse des inférences many-to-many

Pattern des résultats :

Étudiant Q1(C1) Q2(C1∧C2) C1 inféré C2 inféré Interprétation
E1 ✓ ✓ 0.98 0.89 A probablement les deux
E2 ✓ ✗ 0.83 0.17 A C1, pas C2
E3 ✗ ✗ 0.06 0.48 N’a pas C1
E4 ✓ ✓ 0.98 0.89 A probablement les deux

Points clés :

  1. C3 reste à 0.50 pour tous les étudiants car aucune question ne l’évalue
  2. E2 : Réussit Q1 (C1 seul) mais échoue Q2 (C1∧C2) → P(C2) chute à 0.17
  3. E1 vs E4 : Mêmes observations, mêmes inférences (cohérence du modèle)

Puissance du modèle : Les questions multi-compétences (Q2) permettent de discriminer entre les compétences mieux qu’avec des questions à compétence unique.

6. Estimation des paramètres Slip/Guess

Objectif

Apprendre les paramètres slip et guess a partir des données.

Approche

  • Utiliser des priors Beta sur slip et guess
  • L’inference met a jour ces distributions

Simulation de données pour l’estimation

Pour evaluer la capacite du modèle a estimer slip et guess, nous :

  1. Fixons les vraies valeurs : slip=0.1, guess=0.2, P(competence)=0.6
  2. Simulons 20 observations selon le modèle generatif
  3. Inferons les paramètres a partir des observations seules (sans connaitre les vraies competences)

Cette approche permet de comparer les estimations aux vraies valeurs et d’evaluer le biais eventuel de l’inference.

Defi : L’estimation est difficile car les competences individuelles sont non observees. Le modèle doit les inferer en même temps que slip et guess.

// Estimation de slip/guess avec donnees multiples

// Simuler des donnees ou on connait les vraies competences
int nObs = 20;
Random rng = new Random(42);

double vraiSlip = 0.1;
double vraiGuess = 0.2;

// Generer des donnees
bool[] vraiComp = new bool[nObs];    // Vrai etat de competence
bool[] obsRep = new bool[nObs];       // Reponse observee

for (int i = 0; i < nObs; i++)
{
    vraiComp[i] = rng.NextDouble() < 0.6;  // 60% ont la competence
    if (vraiComp[i])
    {
        obsRep[i] = rng.NextDouble() > vraiSlip;  // Correct sauf slip
    }
    else
    {
        obsRep[i] = rng.NextDouble() < vraiGuess;  // Incorrect sauf guess
    }
}

// Modele pour estimer slip et guess
Variable<double> slipEst = Variable.Beta(1, 1).Named("slip");
Variable<double> guessEst = Variable.Beta(1, 1).Named("guess");
Variable<double> pComp = Variable.Beta(1, 1).Named("pComp");

Range rObs = new Range(nObs).Named("observation");
VariableArray<bool> comp = Variable.Array<bool>(rObs).Named("competence");
VariableArray<bool> rep = Variable.Array<bool>(rObs).Named("reponse");

comp[rObs] = Variable.Bernoulli(pComp).ForEach(rObs);

using (Variable.ForEach(rObs))
{
    using (Variable.If(comp[rObs]))
    {
        rep[rObs] = !Variable.Bernoulli(slipEst);
    }
    using (Variable.IfNot(comp[rObs]))
    {
        rep[rObs] = Variable.Bernoulli(guessEst);
    }
}

rep.ObservedValue = obsRep;

InferenceEngine mSlipGuess = new InferenceEngine(new ExpectationPropagation());
mSlipGuess.Compiler.CompilerChoice = CompilerChoice.Roslyn;
mSlipGuess.ShowFactorGraph = true;  // Genere un fichier .gv pour visualisation

Console.WriteLine("=== Estimation Slip/Guess ===");
Console.WriteLine($"Vraies valeurs : slip={vraiSlip}, guess={vraiGuess}\n");
Console.WriteLine($"Estimations :");
Console.WriteLine($"  Slip : {mSlipGuess.Infer<Beta>(slipEst)}");
Console.WriteLine($"  Guess : {mSlipGuess.Infer<Beta>(guessEst)}");
Console.WriteLine($"  P(competence) : {mSlipGuess.Infer<Beta>(pComp)}");
=== Estimation Slip/Guess ===
Vraies valeurs : slip=0,1, guess=0,2

Estimations :
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
  Slip : Beta(3,871,7,698)[mean=0,3346]
  Guess : Beta(7,698,3,871)[mean=0,6654]
  P(competence) : Beta(1,1)[mean=0,5]

Analyse critique des estimations

Observation : Les estimations divergent significativement des vraies valeurs :

paramètre Vraie valeur Estimation Ecart
Slip 0.10 ~0.33 +0.23
Guess 0.20 ~0.67 +0.47
P(comp) 0.60 0.50 -0.10

Pourquoi cette divergence ?

  1. problème d’identifiabilite : Sans connaitre les vraies competences, le modèle ne peut pas distinguer :

    • Un etudiant competent qui fait une erreur (slip)
    • Un etudiant incompetent qui devine correctement (guess)
  2. Symetrie du problème : Noter que slip + guess ≈ 1.0, ce qui suggere que le modèle “inverse” partiellement l’interpretation.

  3. Taille d’echantillon : 20 observations sont insuffisantes pour estimer 3 paramètres latents de maniere fiable.

Lecon importante : L’estimation de slip/guess sans information supplementaire sur les vraies competences est un problème mal pose. En pratique, on utilise : - Des ancres (items dont on connait la difficulte) - Des priors informatifs bases sur des études pilotes - Des contraintes (ex: slip < 0.3, guess < 0.3)

Graphe de facteurs pour l’estimation slip/guess

Ce graphe illustre le problème d’identifiabilite dans l’estimation des paramètres de bruit :

Variable Type rôle
slip Continue (Beta) paramètre global d’erreur d’inattention
guess Continue (Beta) paramètre global de reponse au hasard
pComp Continue (Beta) Probabilite globale d’avoir la competence
competence[i] Binaire latent Competence de chaque individu
reponse[i] Binaire observe Reponse de chaque individu

problème visible : Les paramètres slip, guess et pComp sont tous trois connectes a chaque observation via les competences latentes. Sans information supplementaire, le modèle ne peut pas distinguer les différentes sources de bruit.

// Visualisation du graphe de facteurs pour l'estimation slip/guess
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_00_24_04_16.svg
Model node0 Beta(1,1)[mean=0,5] node1 Random node0->node1 dist node2 slip node1->node2 node3 Bernoulli node2->node3 probTrue node4 vbool59[observation] node3->node4 node5 Not node4->node5 b node6 reponse[observation] node5->node6 node7 competence[observation] node7->node6 condition node8 guess node9 Bernoulli node8->node9 probTrue node9->node6 node10 Beta(1,1)[mean=0,5] node11 Random node10->node11 dist node11->node8 node12 pComp node13 Bernoulli node12->node13 probTrue node13->node7 node14 Beta(1,1)[mean=0,5] node15 Random node14->node15 dist node15->node12

Version amelioree avec priors informatifs

Voici comment contraindre les estimations avec des priors realistes :

// Version avec priors informatifs pour slip/guess

// Memes donnees simulees
Variable<double> slipInf = Variable.Beta(2, 18).Named("slipInformatif");  // Prior centre sur 0.1
Variable<double> guessInf = Variable.Beta(3, 12).Named("guessInformatif");  // Prior centre sur 0.2
Variable<double> pCompInf = Variable.Beta(6, 4).Named("pCompInformatif");  // Prior centre sur 0.6

Range rObs2 = new Range(nObs).Named("observation2");
VariableArray<bool> comp2 = Variable.Array<bool>(rObs2).Named("competence2");
VariableArray<bool> rep2 = Variable.Array<bool>(rObs2).Named("reponse2");

comp2[rObs2] = Variable.Bernoulli(pCompInf).ForEach(rObs2);

using (Variable.ForEach(rObs2))
{
    using (Variable.If(comp2[rObs2]))
    {
        rep2[rObs2] = !Variable.Bernoulli(slipInf);
    }
    using (Variable.IfNot(comp2[rObs2]))
    {
        rep2[rObs2] = Variable.Bernoulli(guessInf);
    }
}

rep2.ObservedValue = obsRep;

InferenceEngine mInf = new InferenceEngine(new ExpectationPropagation());
mInf.Compiler.CompilerChoice = CompilerChoice.Roslyn;
mInf.ShowFactorGraph = true;  // Genere un fichier .gv pour visualisation

Console.WriteLine("=== Estimation avec Priors Informatifs ===");
Console.WriteLine($"Vraies valeurs : slip={vraiSlip}, guess={vraiGuess}, P(comp)=0.6\n");
Console.WriteLine($"Priors : Beta(2,18) pour slip, Beta(3,12) pour guess, Beta(6,4) pour P(comp)\n");
Console.WriteLine($"Estimations :");
var slipPost = mInf.Infer<Beta>(slipInf);
var guessPost = mInf.Infer<Beta>(guessInf);
var pCompPost = mInf.Infer<Beta>(pCompInf);
Console.WriteLine($"  Slip : moyenne={slipPost.GetMean():F3} (vraie: {vraiSlip})");
Console.WriteLine($"  Guess : moyenne={guessPost.GetMean():F3} (vraie: {vraiGuess})");
Console.WriteLine($"  P(competence) : moyenne={pCompPost.GetMean():F3} (vraie: 0.6)");
=== Estimation avec Priors Informatifs ===
Vraies valeurs : slip=0,1, guess=0,2, P(comp)=0.6

Priors : Beta(2,18) pour slip, Beta(3,12) pour guess, Beta(6,4) pour P(comp)

Estimations :
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50
  Slip : moyenne=0,092 (vraie: 0,1)
  Guess : moyenne=0,212 (vraie: 0,2)
  P(competence) : moyenne=0,650 (vraie: 0.6)

Visualisation du graphe avec priors informatifs pour les paramètres slip et guess.

// Visualisation du graphe avec priors informatifs
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_00_24_05_82.svg
Model node0 Beta(2,18)[mean=0,1] node1 Random node0->node1 dist node2 slipInformatif node1->node2 node3 Bernoulli node2->node3 probTrue node4 vbool68[observation2] node3->node4 node5 Not node4->node5 b node6 reponse2[observation2] node5->node6 node7 competence2[observation2] node7->node6 condition node8 guessInformatif node9 Bernoulli node8->node9 probTrue node9->node6 node10 Beta(3,12)[mean=0,2] node11 Random node10->node11 dist node11->node8 node12 pCompInformatif node13 Bernoulli node12->node13 probTrue node13->node7 node14 Beta(6,4)[mean=0,6] node15 Random node14->node15 dist node15->node12

Comparaison avec priors informatifs

Le graphe a la même structure que le précédent, mais les priors sont maintenant informatifs :

paramètre Prior uniforme Prior informatif
slip Beta(1,1) = U(0,1) Beta(2,18) centre sur 0.1
guess Beta(1,1) = U(0,1) Beta(3,12) centre sur 0.2
pComp Beta(1,1) = U(0,1) Beta(6,4) centre sur 0.6

Impact sur l’inference : Les priors informatifs agissent comme des “pseudo-observations” qui guident l’estimation vers des valeurs realistes, brisant la symetrie du problème d’identifiabilite.

Impact des priors informatifs

L’utilisation de priors informatifs (Beta(2,18) pour slip, Beta(3,12) pour guess) permet de :

  1. Regulariser les estimations vers des valeurs realistes
  2. Briser la symetrie du problème d’identifiabilite
  3. Incorporer les connaissances du domaine (slip et guess sont généralement faibles)
Configuration Slip estime Guess estime Fiabilite
Priors uniformes Beta(1,1) ~0.33 ~0.67 Faible
Priors informatifs Plus proche de 0.1 Plus proche de 0.2 Meilleure

Bonne pratique : En psychometrie, les priors pour slip et guess sont souvent contraints a etre inferieurs a 0.3, ce qui correspond a la realite empirique des tests bien concus.

7. Comparaison IRT vs DINA

Aspect IRT DINA
Capacite Continue, unidimensionnelle Discrete, multidimensionnelle
Interpretation “Niveau global” “Competences spécifiques”
Complexite Simple, robuste Plus complexe, informatif
Utilisation Tests standardises Diagnostic pedagogique
Inference Plus facile Necessite matrice Q

Quand utiliser chaque modèle ?

scénario modèle recommande Justification
Test standardise (SAT, GMAT) IRT Une seule dimension mesuree, grand echantillon
Diagnostic pedagogique DINA Identifier les competences manquantes
Certification professionnelle DINA Verifier les prerequis spécifiques
Classement de joueurs TrueSkill (prochain notebook) Competences relatives, matchs

Formulation mathematique comparee

IRT (2PL) : \[P(\text{correct}) = \frac{1}{1 + e^{-a(\theta - b)}}\]

ou \(\theta\) = capacite, \(b\) = difficulte, \(a\) = discrimination

DINA : \[P(\text{correct}) = (1-s)^{\eta} \cdot g^{1-\eta}\]

ou \(\eta = \prod_{k} \alpha_k^{q_{jk}}\) (1 si toutes competences requises, 0 sinon), \(s\) = slip, \(g\) = guess

8. Exemple guide : Evaluer un Nouvel Etudiant

Enonce

Un nouvel etudiant passe un test de 5 questions. Le test evalue 2 competences : - Q1, Q2 : Competence 1 seulement - Q3, Q4 : Competence 2 seulement - Q5 : Competences 1 ET 2

résultats : Q1=correct, Q2=correct, Q3=incorrect, Q4=correct, Q5=incorrect

Question : Quelles sont les probabilites que l’etudiant possede C1 et C2 ?

Implementation de l’exercice

Le code suivant construit un modèle DINA pour le scénario decrit :

Question Competences résultat
Q1 C1 Correct
Q2 C1 Correct
Q3 C2 Incorrect
Q4 C2 Correct
Q5 C1 ET C2 Incorrect

Les paramètres slip=0.1 et guess=0.15 sont fixes (valeurs typiques en psychometrie).

Prediction intuitive : Avec 2/2 sur C1 et 1/2 sur C2, on s’attend a P(C1) eleve et P(C2) incertain. L’echec a Q5 devrait trancher en defaveur de C2.

// Exemple guide : Evaluation d'un nouvel etudiant

Variable<bool> c1 = Variable.Bernoulli(0.5).Named("C1");
Variable<bool> c2 = Variable.Bernoulli(0.5).Named("C2");

double slip = 0.1;
double guess = 0.15;

// Q1 : C1 seulement -> correct
Variable<bool> rQ1 = Variable.New<bool>().Named("Q1");
using (Variable.If(c1)) { rQ1.SetTo(Variable.Bernoulli(1 - slip)); }
using (Variable.IfNot(c1)) { rQ1.SetTo(Variable.Bernoulli(guess)); }
rQ1.ObservedValue = true;

// Q2 : C1 seulement -> correct
Variable<bool> rQ2 = Variable.New<bool>().Named("Q2");
using (Variable.If(c1)) { rQ2.SetTo(Variable.Bernoulli(1 - slip)); }
using (Variable.IfNot(c1)) { rQ2.SetTo(Variable.Bernoulli(guess)); }
rQ2.ObservedValue = true;

// Q3 : C2 seulement -> incorrect
Variable<bool> rQ3 = Variable.New<bool>().Named("Q3");
using (Variable.If(c2)) { rQ3.SetTo(Variable.Bernoulli(1 - slip)); }
using (Variable.IfNot(c2)) { rQ3.SetTo(Variable.Bernoulli(guess)); }
rQ3.ObservedValue = false;

// Q4 : C2 seulement -> correct
Variable<bool> rQ4 = Variable.New<bool>().Named("Q4");
using (Variable.If(c2)) { rQ4.SetTo(Variable.Bernoulli(1 - slip)); }
using (Variable.IfNot(c2)) { rQ4.SetTo(Variable.Bernoulli(guess)); }
rQ4.ObservedValue = true;

// Q5 : C1 ET C2 -> incorrect
Variable<bool> c1etc2 = (c1 & c2).Named("C1_ET_C2");
Variable<bool> rQ5 = Variable.New<bool>().Named("Q5");
using (Variable.If(c1etc2)) { rQ5.SetTo(Variable.Bernoulli(1 - slip)); }
using (Variable.IfNot(c1etc2)) { rQ5.SetTo(Variable.Bernoulli(guess)); }
rQ5.ObservedValue = false;

InferenceEngine mEx = new InferenceEngine();
mEx.Compiler.CompilerChoice = CompilerChoice.Roslyn;
mEx.ShowFactorGraph = true;  // Genere un fichier .gv pour visualisation

Console.WriteLine("=== Evaluation du nouvel etudiant ===");
Console.WriteLine("Resultats : Q1=T, Q2=T, Q3=F, Q4=T, Q5=F\n");
Console.WriteLine($"P(C1) = {mEx.Infer<Bernoulli>(c1).GetProbTrue():F3}");
Console.WriteLine($"P(C2) = {mEx.Infer<Bernoulli>(c2).GetProbTrue():F3}");
Console.WriteLine($"\nInterpretation :");
Console.WriteLine("- L'etudiant a probablement C1 (2/2 correct sur questions C1)");
Console.WriteLine("- C2 est moins certain (1/2 correct, et Q5 echoue)");
=== Evaluation du nouvel etudiant ===
Resultats : Q1=T, Q2=T, Q3=F, Q4=T, Q5=F

Compiling model...done.
P(C1) = 0,958
P(C2) = 0,091

Interpretation :
- L'etudiant a probablement C1 (2/2 correct sur questions C1)
- C2 est moins certain (1/2 correct, et Q5 echoue)

Analyse du diagnostic de l’etudiant

résultats : - P(C1) = 0.958 : L’etudiant a très probablement C1 - P(C2) = 0.091 : L’etudiant n’a probablement PAS C2

Raisonnement du modèle :

Observation Impact sur C1 Impact sur C2
Q1=T (C1) Forte hausse -
Q2=T (C1) Confirmation -
Q3=F (C2) - Baisse
Q4=T (C2) - Legere hausse
Q5=F (C1 ET C2) Legere baisse Forte baisse

Cle : L’echec a Q5 (C1 ET C2) avec P(C1) eleve implique que C2 est probablement manquant (explaining away).

Application pedagogique : Ce diagnostic permet de cibler la remediation sur C2 specifiquement, plutot que de faire reprendre tout le cours a l’etudiant.

Graphe de facteurs de l’exercice diagnostic

Ce graphe montre le modèle DINA complet pour l’evaluation de l’etudiant :

Question Variables connectees Observation
Q1, Q2 C1 uniquement true, true
Q3, Q4 C2 uniquement false, true
Q5 C1 AND C2 false

Propagation des messages :

  1. Q1=T et Q2=T → forte evidence pour C1 (deux confirmations)
  2. Q3=F et Q4=T → evidence ambigue pour C2 (une erreur, une reussite)
  3. Q5=F avec C1 probable → evidence contre C2 (explaining away)

Le graphe visualise pourquoi P(C1)=0.96 >> P(C2)=0.09 : les messages provenant de Q5 “expliquent” l’echec par l’absence de C2 plutot que de C1.

// Visualisation du graphe de facteurs de l'exercice
FactorGraphHelper.GetLatestFactorGraphHtml().DisplayAs("text/html");
Model_09_03_26_00_24_07_63.svg
Model node0 Bernoulli(0,5) node1 Random node0->node1 dist node2 C1 node1->node2 node5 True node2->node5 condition node10 True node2->node10 condition node13 And node2->node13 a node3 Bernoulli(0,9) node4 Random node3->node4 dist node4->node5 node6 Bernoulli(0,15) node7 Random node6->node7 dist node7->node5 node8 Bernoulli(0,9) node9 Random node8->node9 dist node9->node10 node11 Bernoulli(0,15) node12 Random node11->node12 dist node12->node10 node15 C1_ET_C2 node13->node15 node14 C2 node14->node13 b node20 False node14->node20 condition node25 True node14->node25 condition node30 False node15->node30 condition node16 Bernoulli(0,5) node17 Random node16->node17 dist node17->node14 node18 Bernoulli(0,9) node19 Random node18->node19 dist node19->node20 node21 Bernoulli(0,15) node22 Random node21->node22 dist node22->node20 node23 Bernoulli(0,9) node24 Random node23->node24 dist node24->node25 node26 Bernoulli(0,15) node27 Random node26->node27 dist node27->node25 node28 Bernoulli(0,9) node29 Random node28->node29 dist node29->node30 node31 Bernoulli(0,15) node32 Random node31->node32 dist node32->node30

Exemple guide supplementaire : Conception d’un test diagnostic

Imaginez que vous devez concevoir un test pour evaluer 3 competences en programmation : - C1 : Syntaxe de base (variables, boucles) - C2 : Structures de données (tableaux, listes) - C3 : Algorithmes (tri, recherche)

Questions :

  1. Combien de questions minimum faut-il pour diagnostiquer chaque competence de maniere fiable ?

  2. Proposez une matrice Q pour 6 questions qui permette de distinguer les 8 profils possibles d’etudiants (2^3 combinaisons de competences).

  3. Si un etudiant echoue uniquement sur les questions necessitant C3, quelle remediation proposeriez-vous ?

Indice : Pour distinguer tous les profils, il faut au moins une question testant chaque competence isolement (Q1-C1, Q2-C2, Q3-C3) et des questions composites pour valider les interactions.

9. Resume

Concept Description
IRT modèle capacite-difficulte pour tests unidimensionnels
DINA modèle a competences discretes multiples
Matrice Q Definition des competences requises par question
Slip Erreur d’inattention (correct -> incorrect)
Guess Reponse au hasard (incorrect -> correct)
Many-to-many Questions multiples, competences multiples

Prochaine étape

Dans Infer-2b-Debugging-Bonnes-Pratiques, nous explorerons :

  • Le diagnostic des problemes de convergence
  • La comparaison des algorithmes (EP, VMP, Gibbs)
  • Les outils de debug d’Infer.NET
  • Les bonnes pratiques de modelisation

Points cles a retenir

  1. IRT : Ideal pour les tests standardises avec une dimension principale (intelligence, aptitude verbale)

  2. DINA : Plus adapte au diagnostic pedagogique car il identifie les competences spécifiques manquantes

  3. Slip et Guess : Ces paramètres de bruit sont difficiles a estimer sans priors informatifs ou données abondantes

  4. Matrice Q : La conception du test (quelles competences pour chaque question) est cruciale pour le diagnostic

  5. Inference bayesienne : Permet de quantifier l’incertitude sur les estimations et d’integrer les connaissances prealables

Perspective : Ces modèles sont utilises en production par des plateformes educatives (Duolingo, Khan Academy) pour personnaliser les parcours d’apprentissage.

// Nettoyage des fichiers generes (optionnel)
int cleaned = FactorGraphHelper.CleanupGeneratedFiles();
Console.WriteLine($"Fichiers .gv et .svg nettoyes : {cleaned}");
Fichiers .gv et .svg nettoyes : 12

10. Exercice : Comparer Deux Classes

Enonce

Comparez les competences entre deux classes d’etudiants (classe A et classe B) sur le même test de 5 questions. - Q1, Q2, Q4 testent la competence C1 ; Q3, Q5 testent C2 ; Q4 teste les deux

Reponses classe A : {true, true, false, true, false} Reponses classe B : {false, false, true, false, true}

  1. Estimez P(maitrise C1) et P(maitrise C2) pour chaque classe
  2. Quelle classe est plus forte en C1 ? En C2 ?

Indice : Appliquez le modèle DINA deux fois (une fois par classe) avec les mêmes a priori.

// Exercice : Comparaison de competences entre deux classes
bool[] reponsesA = { true, true, false, true, false };
bool[] reponsesB = { false, false, true, false, true };

// Structure du test : Q1-C1, Q2-C1, Q3-C2, Q4-C1+C2, Q5-C2
// Slip rate (P(reponse fausse si maitrise) = 0.1
// Guess rate (P(reponse correcte si non-maitrise) = 0.25

// TODO: Pour CHAQUE classe, definir les variables de competences C1 et C2

// --- Classe A ---

// TODO: Modeliser les reponses aux 5 questions pour la classe A
// Q1 (teste C1 seulement) : P(Q1=T | c1A) = 1-slip = 0.9, P(Q1=T | !c1A) = guess = 0.25

// TODO: Observer les reponses de la classe A et inferer les competences

// --- Classe B (meme structure, reponses differentes) ---

// TODO: Comparer et interpreter
Console.WriteLine("Exercice a completer");
Exercice a completer

11. Exercice : Concevoir un Test Diagnostic Optimal

Enonce

Vous devez concevoir un test diagnostic pour evaluer 4 competences en programmation Python :

  • C1 : Variables et types
  • C2 : Boucles et itérations
  • C3 : Fonctions et portee
  • C4 : Structures de données (listes, dictionnaires)

Objectif : Proposez une matrice Q (questions x competences) et un ensemble de questions qui permette de diagnostiquer efficacement chaque profil d’etudiant.

tâches

  1. Conception : Proposez une matrice Q pour 6-8 questions. Justifiez vos choix (pourquoi cette combinaison de competences par question ?).
  2. Simulation : Simulez les reponses de 5 etudiants avec des profils différents (ex: un bon en tout, un faible en boucles, un expert fonctions mais debutant structures).
  3. Inference : Utilisez le modèle DINA pour inferer les competences de chaque etudiant et verifier que le diagnostic est coherent.

Indice

  • Chaque competence doit etre testee par au moins 2 questions (une seule et une composite)
  • Les questions composites sont plus discriminantes mais augmentent l’ambiguite
  • Verifiez que votre matrice Q permet de distinguer les profils (ex: l’etudiant qui a C1+C2 mais pas C3+C4)

étapes suggerees :

  1. définir la matrice Q et les questions
  2. Choisir des valeurs realistes pour slip (0.1) et guess (0.15)
  3. Simuler les reponses pour 5 profils différents
  4. Construire le modèle DINA et inferer les competences
  5. Comparer les diagnostics aux vrais profils
// Exercice : Concevoir un test diagnostic pour 4 competences Python

// TODO: Definir la matrice Q (6-8 questions x 4 competences)
// Indice : chaque competence doit etre testee par au moins 2 questions
// bool[,] matriceQ = new bool[,] { ... };

// TODO: Definir 5 profils d'etudiants avec des competences differentes
// bool[,] profils = new bool[,] { ... };

// TODO: Simuler les reponses avec slip=0.1, guess=0.15
// bool[,] reponsesSimulees = ... ;

// TODO: Construire le modele DINA pour chaque etudiant
// et inferer les competences

// TODO: Comparer les competences inferiees aux vrais profils
// Afficher un tableau : Etudiant | Vrai profil | Profil inferre | Accord ?

Console.WriteLine("Exercice a completer : test diagnostic optimal");
Exercice a completer : test diagnostic optimal

Exercice : Analyser la fiabilite discriminante des items

Le modèle IRT de la section 3 a estime les capacites de 10 etudiants et les difficultes de 5 questions. Cependant, toutes les questions n’apportent pas la même quantite d’information. La fonction d’information d’un item mesure la precision avec laquelle une question distingue les etudiants selon leur capacite.

Objectif : Calculez l’indice de discrimination de chaque question et identifiez laquelle est la plus (et la moins) informative pour le diagnostic.

étapes : 1. Pour chaque question, calculez la correlation point-biserial entre la reponse observee et la capacite estimee des etudiants 2. Calculez la fonction d’information I(theta) = discrimination^2 * P(correct) * (1 - P(correct)) pour chaque question a theta = capacite moyenne 3. Affichez un classement des questions du plus informatif au moins informatif

Indices : - La correlation point-biserial se calcule comme : r_pb = (M_correct - M_incorrect) / sigma * sqrt(p * q) ou M_correct = moyenne des capacites des etudiants ayant repondu correctement - La variable capacitePost (tableau de Gaussian) et difficultePost sont disponibles depuis la section 3 - La discrimination a ete estimee dans le modèle IRT : moteurIRT.Infer<Gamma>(discrimination) - Un item est bon discriminateur si |r_pb| > 0.3 ; il est faible si |r_pb| < 0.15

// Exercice : Analyser la fiabilite discriminante des items

// TODO: Recuperer la discrimination estimee dans la section 3
// var discriminationPost = moteurIRT.Infer<Gamma>(discrimination);
// double discriminationMean = discriminationPost.GetMean();

// TODO: Pour chaque question, calculer la correlation point-biserial
// Etape 1 : Extraire les capacites moyennes des etudiants
// double[] capacites = capacitePost.Select(c => c.GetMean()).ToArray();
// Etape 2 : Calculer l'ecart-type global des capacites
// Etape 3 : Pour chaque question j, separer les etudiants en deux groupes (correct/incorrect)
//           et calculer r_pb = (M_correct - M_incorrect) / sigma * sqrt(p * q)

// TODO: Calculer la fonction d'information I(theta) pour chaque question
// a theta = moyenne des capacites
// double thetaMoyen = capacites.Average();
// Pour chaque question j : I(theta) = discrimination^2 * P(correct) * (1 - P(correct))

// TODO: Afficher le classement des questions par informativite decroissante
// Console.WriteLine("Question | r_pb | I(theta) | Qualite");
// Indice: |r_pb| > 0.3 = bon, 0.15 < |r_pb| < 0.3 = acceptable, |r_pb| < 0.15 = faible

var result = "Exercice a completer";
Console.WriteLine(result);
Exercice a completer

Conclusion

Ce notebook a couvert les modèles d’evaluation cognitive : IRT (Item Response Theory) pour la capacite unidimensionnelle et DINA pour les competences discretes multiples.

modèle Principe Quand l’utiliser
IRT Capacite continue - difficulte, fonction probit Tests standardises, classement global
DINA Competences binaires + matrice Q, ET logique Diagnostic pedagogique, remediation ciblee
Concept Point cle
Slip P(incorrect | competence) : erreur d’inattention
Guess P(correct | pas competence) : reponse au hasard
Matrice Q définit quelles competences chaque question requiert
Explaining away Q5 incorrect + C1 probable => C2 improbable
Distribution rôle
Gaussian Capacites IRT et difficultes des questions
Bernoulli Competences binaires DINA et reponses observees
Beta Priors sur slip, guess et probabilites de competence
ExpectationPropagation Algorithme pour les facteurs de comparaison

Lecon pratique : L’estimation simultanee de slip/guess et des competences est un problème d’identifiabilite. Des priors informatifs (Beta concentres sur les valeurs attendues) sont indispensables pour obtenir des estimations fiables. Le DINA excelle pour le diagnostic individualise : il identifie les competences manquantes, permettant une remediation ciblee.

Retour au sommet