Infer-13-Crowdsourcing : Agregation de Labels et Fiabilite

Serie : Programmation Probabiliste avec Infer.NET (13/19)
Duree estimee : 55 minutes
Prerequis : Infer-11-Topic-Models


Objectifs

  • Comprendre le problème de l’agregation de labels
  • Implementer le modèle Honest Worker
  • Construire le modèle Biased Worker avec matrice de confusion
  • Explorer le modèle hiérarchique Community

Sources canoniques

Ce notebook s’inscrit dans la tradition de l’inférence par maximum de vraisemblance puis bayésienne pour les réponses d’annotateurs multiples. Les modèles présentés ont des ancêtres canoniques identifiés ci-dessous, et sont développés ici en Infer.NET pour leur formulation probabiliste moderne (variables latentes + inférence variationnelle).

Peres seminales :

Source Annee Role dans ce notebook
Dawid & Skene, Maximum likelihood estimation of observer error-rates using the EM algorithm, JRSS-A 142(3):257-286 1979 Inspire les modèles de matrice de confusion par worker (Honest Worker, Biased Worker)
Whitehill, Wu, Bergsma, Barber & Moors, Whose Vote Should Count More: Optimal Integration of Labels from Labelers of Unknown Expertise, NIPS 2009 GLAD : ajoute la difficulté par item (mentionné dans section Comparative)
Raykar, Yu, Zhao, Valadez, Florin, Bogoni, Moy, Learning from Crowds, JMLR 11:1297-1332 2010 Formalisation bayésienne du prior sur la matrice de confusion, inspire Biased Worker
Karger, Oh & Shah, Iterative Learning from Crowds, NIPS Workshop on Crowdsourcing 2011 Algorithme itératif pour l’apprentissage actif avec convergence garantie
Kim & Ghahramani, Bayesian Classifier Combination, ICML 2012 BCC = version bayésienne de Dawid-Skene (mentionné dans Comparative)
Settles, Active Learning Literature Survey, Computer Sciences TR 1648, U. Wisconsin-Madison 2010 (rev 2012) Cadre général de l’apprentissage actif (uncertainty sampling, information gain)
MBML Book, Crowdsourcing (Chap. 7 sub-page) en ligne Présentation pédagogique d’ensemble sur mbmlbook.com

Note de fidélité : Le notebook développe les formalismes avec Infer.NET (VMP / EP), pas avec les algorithmes EM historiques. Les équations sont alignées sur Dawid-Skene 1979 et Raykar 2010 ; les choix d’architecture (Beta prior, Dirichlet confusion matrix, hiérarchie Community) reflètent la pratique probabiliste moderne issue de Raykar 2010 et Karger 2011.

1. Configuration

Nous chargeons les packages pour les modèles de crowdsourcing. Ces modèles probabilistes permettent d’agreger des annotations provenant de multiples travailleurs de fiabilites variables, tout en estimant simultanement la verite latente et la qualite de chaque annotateur.

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

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

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

Chargement du helper de visualisation des graphes de facteurs.

// Chargement du helper pour visualiser les factor graphs
#load "FactorGraphHelper.cs"

Console.WriteLine("FactorGraphHelper charge - Graphviz disponible : " + FactorGraphHelper.IsGraphvizAvailable());
FactorGraphHelper charge - Graphviz disponible : True

Le FactorGraphHelper permet de visualiser les graphes de facteurs generes par Infer.NET. Quand ShowFactorGraph = true, le moteur genere un fichier .gv (format Graphviz) que le helper convertit en SVG pour affichage inline.

Les packages Infer.NET sont prêts pour les modèles de crowdsourcing. Ce notebook utilise principalement Microsoft.ML.Probabilistic (moteur d’inférence), Compiler (compilation des modèles) et les distributions Beta, Dirichlet pour les priors.

2. Le problème du Crowdsourcing

Contexte

Pourquoi ce probleme compte. Le chapitre 7 « Harnessing the Crowd » de Model-Based Machine Learning (Winn & Bishop), dont cette serie est distillee, ouvre sur un cas concret : lors du seisme de 2010 en Haiti (magnitude 7.0), des dizaines de milliers de messages, SMS et tweets envoyes depuis le terrain ont ete tries et categorises a la main par des volontaires aux competences et biais variables. La plateforme Ushahidi (« temoignage » en swahili), creee en 2008 lors des violences post-electorales au Kenya pour cartographier les temoignages, y a ete deployee pour collecter et diffuser l’information de crise (utilisee par l’armee, l’ONU et les ONG). Le defi : a partir de rapports contradictoires et bruites emanant de travailleurs de fiabilite inegale, inferer la situation reelle sur le terrain. C’est precisement le probleme modelise dans ce notebook. Le meme principe s’applique a des taches plus routinieres.

On veut annoter un grand nombre d’images (spam/non-spam, sentiment, etc.) en utilisant des travailleurs non-experts sur des plateformes comme Amazon Mechanical Turk.

Defis

Defi Description
Fiabilite variable Certains travailleurs sont meilleurs que d’autres
Biais Certains ont tendance a repondre toujours la même chose
Verite inconnue On ne connait pas le vrai label
Cout Limiter le nombre d’annotations par item

Solution

modèle probabiliste qui estime simultanement : - Le vrai label de chaque item - La qualite de chaque travailleur

Formalisation mathematique

Le problème d’agregation de labels peut se formaliser comme suit :

Variables latentes : - \(t_i \in \{1, \ldots, K\}\) : vrai label de l’item \(i\) - \(\theta_w\) : paramètres de qualite du worker \(w\)

Variables observees : - \(l_{w,i}\) : label donne par le worker \(w\) pour l’item \(i\)

Objectif : Inferer \(P(t_i | \{l_{w,i}\}_{w=1}^W)\) pour chaque item

La formule de Bayes donne :

\[P(t_i = k | \text{labels}) \propto P(t_i = k) \prod_{w} P(l_{w,i} | t_i = k, \theta_w)\]

Note : La difficulté réside dans l’estimation jointe des \(t_i\) et des \(\theta_w\), car les deux sont inconnus. C’est un problème de type “poule et oeuf” que l’inférence bayésienne résout élégamment. ### Origine du formalisme

Ce formalisme reprend le cadre de Dawid & Skene (1979) : chaque worker \(w\) a une matrice de confusion, et l’on infère conjointement les vrais labels \(t_i\) et les paramètres des workers par maximum de vraisemblance (EM dans le papier original). Les modèles bayésiens ultérieurs (Raykar et al. 2010, Kim & Ghahramani 2012) remplacent l’EM par une inférence variationnelle ; c’est exactement ce que fait Infer.NET dans ses postérieurs Beta et Dirichlet utilisés ci-dessous.

Reference : La littérature pédagogique sur ce cadre est résumée par Raykar dans son tutoriel Learning from Crowds (2011), et le chapitre dédié du MBML Book (sub-page Crowdsourcing sur mbmlbook.com) présente les mêmes modèles avec un point de vue purement probabiliste.

3. modèle Honest Worker

Hypothese

Chaque travailleur a une capacite (probabilite de donner la bonne reponse).

modèle

\[\text{capacite}_w \sim \text{Beta}(\alpha, \beta)\]

\[P(\text{label}_w = \text{vrai label}) = \text{capacite}_w\] \[P(\text{label}_w \neq \text{vrai label}) = \frac{1 - \text{capacite}_w}{K-1}\]

ou K est le nombre de classes.

Structure graphique du modèle Honest Worker

flowchart TD
    B["Beta(a, b)"] --> CAP["capacite[w]<br/>(par worker)"]
    CAP --> P["P(correct) = cap[w]"]
    P --> OK["[Si correct]"]
    P --> KO["[Si incorrect]"]
    OK --> L1["label[w, i] = t[i]"]
    KO --> L2["label[w, i] ~ Uniform(K)"]

Le prior \(\text{Beta}(2, 1)\) encode une croyance que les workers sont “plutot honnetes” :

Prior Moyenne Interpretation
Beta(1, 1) 0.50 Aucune information
Beta(2, 1) 0.67 Workers legerement fiables
Beta(5, 1) 0.83 Workers très fiables
Beta(1, 2) 0.33 Workers souvent faux
// Donnees de crowdsourcing
// 5 items, 4 workers, 2 classes (0 ou 1)

int nItems = 5;
int nWorkers = 4;
int nClasses = 2;

// Labels donnes par chaque worker pour chaque item (-1 = pas d'annotation)
int[,] labels = {
    // W1  W2  W3  W4
    {  1,  1,  1,  0 },  // Item 0 : consensus 1
    {  0,  0,  0,  0 },  // Item 1 : consensus 0
    {  1,  1,  0,  1 },  // Item 2 : majorite 1
    {  0,  1,  0,  0 },  // Item 3 : majorite 0
    {  1,  0,  1,  1 }   // Item 4 : majorite 1
};

// Vrais labels (pour evaluation - inconnus du modele)
int[] vraisLabels = { 1, 0, 1, 0, 1 };

Console.WriteLine("=== Donnees Crowdsourcing ===");
Console.WriteLine("\nLabels donnes par les workers :");
Console.Write("        ");
for (int w = 0; w < nWorkers; w++) Console.Write($"W{w+1} ");
Console.WriteLine("| Vrai");

for (int i = 0; i < nItems; i++)
{
    Console.Write($"Item {i} : ");
    for (int w = 0; w < nWorkers; w++)
    {
        Console.Write($" {labels[i, w]} ");
    }
    Console.WriteLine($" |  {vraisLabels[i]}");
}
=== Donnees Crowdsourcing ===

Labels donnes par les workers :
        W1 W2 W3 W4 | Vrai
Item 0 :  1  1  1  0  |  1
Item 1 :  0  0  0  0  |  0
Item 2 :  1  1  0  1  |  1
Item 3 :  0  1  0  0  |  0
Item 4 :  1  0  1  1  |  1

Lecture des données

Le tableau ci-dessus montre un scénario typique de crowdsourcing :

Pattern Items Observation
Consensus fort 1 Tous les workers s’accordent sur 0
Consensus majoritaire 0, 2, 3, 4 3 workers sur 4 s’accordent
Dissident W4 (Item 0), W3 (Item 2) Un worker contredit les autres

Question cle : Comment distinguer un worker peu fiable d’un worker qui a raison contre la majorite ?

Remarque : Dans ce jeu de données, W4 se trompe sur l’Item 0 (dit 0 alors que le vrai est 1). Le modèle devrait detecter cette tendance a l’erreur.

Implementation du modèle Honest Worker

Nous implementons maintenant le modèle pour un seul item (Item 2) ou il y a desaccord. Le modèle Infer.NET : 1. définit un prior uniforme sur le vrai label 2. Assigne une capacite Beta(2,1) a chaque worker 3. Modelise la probabilite que chaque worker reponde correctement via Variable.Bernoulli

// Modele Honest Worker simplifie (pour un item)

int itemIdx = 2;  // Item avec desaccord
int[] labelsItem = { labels[itemIdx, 0], labels[itemIdx, 1], labels[itemIdx, 2], labels[itemIdx, 3] };

// Prior sur le vrai label (uniforme)
Variable<int> vraiLabel = Variable.DiscreteUniform(nClasses).Named("vraiLabel");

// Capacites des workers
Range workerRange = new Range(nWorkers).Named("worker");
VariableArray<double> capacite = Variable.Array<double>(workerRange).Named("capacite");
capacite[workerRange] = Variable.Beta(2, 1).ForEach(workerRange);  // Prior : plus de 50%

// Labels observes
VariableArray<int> labelObs = Variable.Array<int>(workerRange).Named("labelObs");

using (Variable.ForEach(workerRange))
{
    Variable<bool> estCorrect = Variable.Bernoulli(capacite[workerRange]);
    using (Variable.If(estCorrect))
    {
        labelObs[workerRange] = vraiLabel;  // Repond correctement
    }
    using (Variable.IfNot(estCorrect))
    {
        labelObs[workerRange] = Variable.DiscreteUniform(nClasses);  // Reponse aleatoire
    }
}

labelObs.ObservedValue = labelsItem;

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

Discrete vraiLabelPost = moteurHW.Infer<Discrete>(vraiLabel);
Beta[] capacitePost = moteurHW.Infer<Beta[]>(capacite);

Console.WriteLine($"\n=== Honest Worker pour Item {itemIdx} ===");
Console.WriteLine($"Labels observes : {string.Join(", ", labelsItem)}");
Console.WriteLine($"Vrai label : {vraisLabels[itemIdx]}\n");

Console.WriteLine($"P(vrai label = 0) = {vraiLabelPost.GetProbs()[0]:F3}");
Console.WriteLine($"P(vrai label = 1) = {vraiLabelPost.GetProbs()[1]:F3}");

Console.WriteLine("\nCapacites des workers :");
for (int w = 0; w < nWorkers; w++)
{
    Console.WriteLine($"  Worker {w+1} : {capacitePost[w].GetMean():F3}");
}
Compiling model...done.
Iterating: 
.........|.........|.........|.........|.........| 50

=== Honest Worker pour Item 2 ===
Labels observes : 1, 1, 0, 1
Vrai label : 1

P(vrai label = 0) = 0,038
P(vrai label = 1) = 0,962

Capacites des workers :
  Worker 1 : 0,692
  Worker 2 : 0,692
  Worker 3 : 0,508
  Worker 4 : 0,692

Analyse du modèle Honest Worker

Item analysé : Item 2 avec labels [1, 1, 0, 1] (vrai label = 1)

Mesure Valeur Interprétation
P(vrai=1) 0.962 Forte confiance malgré le désaccord
P(vrai=0) 0.038 Très faible probabilité
Capacité W1, W2, W4 0.692 Workers en accord avec la majorité
Capacité W3 0.508 Worker minoritaire ≈ aléatoire

Observations clés :

  1. Consensus bayésien : 3/4 workers disent “1” → P(vrai=1) = 96.2%, bien plus que le 75% du vote brut

  2. Estimation de capacité :

    • Workers majoritaires : capacité ~0.69 (supérieure au prior Beta(2,1) = 0.67)
    • Worker minoritaire : capacité ~0.51 (proche du hasard)
  3. Effet du prior : Beta(2,1) encode l’hypothèse que les workers sont “honnêtes” (> 50% correct)

Pourquoi P(vrai=1) > 75% ?

Le modèle bayésien pondère les votes par la capacité estimée. Les workers en accord renforcent mutuellement leur crédibilité, tandis que W3 minoritaire perd en influence.

// Visualisation du graphe de facteurs Honest Worker
display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_08_20_26_02_14_57_42.svg
Model node0 2 node1 DiscreteUniform node0->node1 size node2 vraiLabel node1->node2 node3 Copy node2->node3 value node4 labelObs[worker] node3->node4 node5 vbool0[worker] node5->node4 condition node6 capacite[worker] node7 Bernoulli node6->node7 probTrue node7->node5 node8 2 node9 DiscreteUniform node8->node9 size node9->node4 node10 Beta(2,1)[mean=0,6667] node11 Random node10->node11 dist node11->node6

Lecture du graphe de facteurs Honest Worker

Le graphe ci-dessus montre la structure du modèle Honest Worker :

élément Representation rôle
vraiLabel Noeud variable (ellipse) Variable latente : vrai label de l’item
capacite[w] Noeud variable (ellipse) Capacite de chaque worker (prior Beta)
labelObs[w] Noeud observe (ellipse grisee) Labels observes des workers
Bernoulli Facteur (rectangle) Relie capacite a la probabilite de reponse correcte
DiscreteUniform Facteur (rectangle) Prior uniforme sur vraiLabel

Structure conditionnelle : Le modèle utilise Variable.If/IfNot pour créer une structure en “gate” : - Si estCorrect = true : labelObs = vraiLabel - Si estCorrect = false : labelObs ~ Uniform(K)

Cette structure permet a l’inference de propager l’information des labels observes vers l’estimation du vrai label et des capacites des workers.

4. modèle Biased Worker

Amelioration

Au lieu d’une simple capacite, chaque worker a une matrice de confusion qui capture ses biais.

modèle

\[\text{confusion}_{w}[c] \sim \text{Dirichlet}(\alpha)\]

\[P(\text{label}_w = l | \text{vrai} = c) = \text{confusion}_w[c][l]\]

Comparaison Honest vs Biased Worker

Aspect Honest Worker Biased Worker
paramètres par worker 1 (capacite) \(K^2\) (matrice)
Type d’erreur Symetrique Asymetrique
Biais detecte Non Oui
Complexite Faible Moyenne
données requises Peu Plus

Exemple de biais : Un annotateur de spam qui marque tout comme “spam” aura :

\[\text{confusion} = \begin{pmatrix} 0.3 & 0.7 \\ 0.1 & 0.9 \end{pmatrix}\]

  • Vrai=non-spam : 30% correct, 70% erreur
  • Vrai=spam : 10% erreur, 90% correct

Le modèle Honest Worker ne peut pas capturer cette asymetrie.

Implementation du modèle Biased Worker

Nous allons maintenant estimer la matrice de confusion du Worker 4 (W4), qui semble avoir fait des erreurs systématiques. Contrairement a Honest Worker, ce modèle utilise un prior Dirichlet sur chaque ligne de la matrice de confusion.

Note technique : La méthode SetValueRange() est necessaire pour que Variable.Switch() fonctionne correctement avec les indices de la matrice.

// Modele Biased Worker simplifie

// Pour un worker specifique, estimer sa matrice de confusion
// etant donne plusieurs annotations avec vrais labels connus

// Worker 3 (W4) semble avoir fait des erreurs
int[] w4Labels = { labels[0, 3], labels[1, 3], labels[2, 3], labels[3, 3], labels[4, 3] };
// W4 labels : 0, 0, 1, 0, 1
// Vrais     : 1, 0, 1, 0, 1
// W4 se trompe sur item 0 (vrai=1, dit 0)

// Prior sur la matrice de confusion (Dirichlet par ligne)
Range classRange = new Range(nClasses).Named("class");

VariableArray<Vector> confusion = Variable.Array<Vector>(classRange).Named("confusion");
double[] dirichletPrior = { 10, 1 };  // Prior : plus de chances de repondre correctement

using (Variable.ForEach(classRange))
{
    // Pour chaque vrai label, distribution sur les reponses possibles
    confusion[classRange] = Variable.Dirichlet(dirichletPrior);
}

// Observations
Range itemRange2 = new Range(nItems).Named("item");
VariableArray<int> vraiLabelsVar = Variable.Array<int>(itemRange2).Named("vrai");
VariableArray<int> workerLabelsVar = Variable.Array<int>(itemRange2).Named("workerLabel");

// IMPORTANT: SetValueRange est necessaire pour utiliser Variable.Switch()
vraiLabelsVar.SetValueRange(classRange);

using (Variable.ForEach(itemRange2))
{
    using (Variable.Switch(vraiLabelsVar[itemRange2]))
    {
        workerLabelsVar[itemRange2] = Variable.Discrete(confusion[vraiLabelsVar[itemRange2]]);
    }
}

vraiLabelsVar.ObservedValue = vraisLabels;
workerLabelsVar.ObservedValue = w4Labels;

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

Dirichlet[] confusionPost = moteurBW.Infer<Dirichlet[]>(confusion);

Console.WriteLine("=== Biased Worker (W4) ===");
Console.WriteLine("\nMatrice de confusion estimee :");
Console.WriteLine("           Repond 0  Repond 1");
for (int c = 0; c < nClasses; c++)
{
    Vector probs = confusionPost[c].GetMean();
    Console.WriteLine($"Vrai = {c} :   {probs[0]:F2}       {probs[1]:F2}");
}
Compiling model...done.
=== Biased Worker (W4) ===

Matrice de confusion estimee :
           Repond 0  Repond 1
Vrai = 0 :   0,92       0,08
Vrai = 1 :   0,79       0,21

Analyse de la matrice de confusion (W4)

Données : W4 labels = [0, 0, 1, 0, 1], Vrais = [1, 0, 1, 0, 1] - Erreur sur Item 0 : vrai=1, W4 dit 0 - Correct sur Items 1, 2, 3, 4

Matrice de confusion estimée :

Répond 0 Répond 1
Vrai=0 0.92 0.08
Vrai=1 0.79 0.21

Interprétation :

Sur les données brutes, W4 obtient 2 bonnes réponses sur 2 pour les items de vrai=0 (Items 1 et 3), et 2 bonnes sur 3 pour les items de vrai=1 (Items 2 et 4 corrects, Item 0 erroné) :

  • Vrai=0 → 2/2 = 100% correct en brut (posterior 0.92 sur la diagonale)
  • Vrai=1 → 2/3 ≈ 67% correct en brut, mais le posterior donne P(Répond 1 | Vrai=1) = 0.21

Pourquoi le posterior (0.21) diffère tant du taux brut (67%) : le prior Dirichlet(10, 1) est appliqué de façon identique aux deux lignes de la matrice, ce qui revient à supposer a priori que le worker répond “0” avec probabilité 10/11 ≈ 91%, quelle que soit la vraie classe. Avec seulement 3 items en vrai=1, ce prior lisse fortement la ligne et tire la masse vers “Répond 0” (posterior 0.79). Les données ne dominent donc pas ici : l’écart entre brut (67%) et posterior (21%) mesure l’effet du prior sur un petit échantillon, et non une erreur massive de W4 sur la classe 1.

  1. Asymétrie : W4 se trompe une fois sur la classe 1 (Item 0) et jamais sur la classe 0. Le modèle Biased Worker capture cette asymétrie, qu’un Honest Worker à capacité scalaire ne pourrait pas représenter.

  2. Prior Dirichlet(10,1) : Encode une préférence marquée pour la réponse “0”. C’est utile comme point de départ régularisé sur un petit jeu supervisé, mais à garder à l’esprit lors de la lecture des postérieurs.

Avantage du modèle Biased Worker :

Contrairement à Honest Worker qui suppose une erreur symétrique, ce modèle représente chaque classe par sa propre ligne de la matrice de confusion, ce qui permet de capturer : - Les biais asymétriques (ex: tend à prédire “spam” même pour du non-spam) - Les confusions spécifiques entre classes (ex: confond chats avec chiens, mais pas avec oiseaux)

// Visualisation du graphe de facteurs Biased Worker
display(HTML(FactorGraphHelper.GetLatestFactorGraphHtml()));
Model_08_20_26_02_15_00_08.svg
Model node0 confusion[vrai[item]] node1 Discrete node0->node1 probs node2 workerLabel[item] node1->node2 node3 vrai[item] node3->node2 condition node4 vDirichlet0 node5 Random node4->node5 dist node6 confusion[class] node5->node6 node6->node0

Lecture du graphe de facteurs Biased Worker

Le graphe de facteurs du modèle Biased Worker est plus riche que celui de Honest Worker :

élément Representation rôle
confusion[c] Noeud variable (ellipse) Matrice de confusion : distribution Dirichlet par classe
vrai[i] Noeud observe (ellipse grisee) Vrais labels (connus dans ce cas supervise)
workerLabel[i] Noeud observe (ellipse grisee) Labels donnes par le worker
Dirichlet Facteur (rectangle) Prior sur chaque ligne de la matrice de confusion
Discrete Facteur (rectangle) Lie le vrai label au label predit via la matrice
Switch Facteur (rectangle) sélection conditionnelle de la ligne de confusion

différence cle avec Honest Worker :

Dans Honest Worker, un worker avait une capacite scalaire \(c \in [0,1]\).

Dans Biased Worker, un worker a une matrice de confusion \(K \times K\) ou chaque ligne est une distribution Dirichlet. Cela permet de capturer : - Les biais asymetriques (ex: tend a predire “spam” même pour du non-spam) - Les confusions spécifiques entre classes (ex: confond chats avec chiens, mais pas avec oiseaux)

Variable.Switch : Cette construction Infer.NET permet de sélectionner dynamiquement une ligne de la matrice selon la valeur du vrai label. C’est l’equivalent d’un “indexage probabiliste”.

5. Agregation de Tous les Items

après avoir explore les modèles au niveau individuel, comparons leurs performances sur l’ensemble des items. Nous utiliserons le vote majoritaire comme baseline simple.

// Agregation par vote majoritaire (baseline)

Console.WriteLine("=== Comparaison des Methodes ===");
Console.WriteLine("\nVote majoritaire vs Vrai label :");

int correctMajority = 0;

for (int i = 0; i < nItems; i++)
{
    int count0 = 0, count1 = 0;
    for (int w = 0; w < nWorkers; w++)
    {
        if (labels[i, w] == 0) count0++;
        else count1++;
    }
    
    int majority = count0 > count1 ? 0 : 1;
    bool correct = majority == vraisLabels[i];
    if (correct) correctMajority++;
    
    Console.WriteLine($"Item {i} : Majoritaire={majority}, Vrai={vraisLabels[i]} {(correct ? "OK" : "ERREUR")}");
}

Console.WriteLine($"\nPrecision vote majoritaire : {correctMajority}/{nItems} = {100.0 * correctMajority / nItems:F0}%");
=== Comparaison des Methodes ===

Vote majoritaire vs Vrai label :
Item 0 : Majoritaire=1, Vrai=1 OK
Item 1 : Majoritaire=0, Vrai=0 OK
Item 2 : Majoritaire=1, Vrai=1 OK
Item 3 : Majoritaire=0, Vrai=0 OK
Item 4 : Majoritaire=1, Vrai=1 OK

Precision vote majoritaire : 5/5 = 100%

Analyse du vote majoritaire

Résultat : 100% de précision (5/5 items corrects)

Item Votes (0:1) Majorité Vrai Résultat
0 1:3 1 1 OK
1 4:0 0 0 OK
2 1:3 1 1 OK
3 3:1 0 0 OK
4 1:3 1 1 OK

Observations :

  1. Performance parfaite : Dans ce petit exemple, le vote majoritaire suffit
  2. Cas favorables : Tous les items ont un consensus clair (3:1 ou 4:0)
  3. Limitation : Avec des cas 2:2, le vote majoritaire est indécis

Quand le modèle probabiliste est meilleur :

  1. Peu d’annotations : 1-2 votes par item → besoin de pondérer par qualité
  2. Workers biaisés : Vote majoritaire ignore les biais systématiques
  3. Items difficiles : Les modèles estiment aussi l’incertitude
  4. Données manquantes : Annotations incomplètes gérées naturellement

Note : La vraie valeur des modèles de crowdsourcing apparaît sur de grands datasets avec workers hétérogènes.

Quand Dawid-Skene surpasse-t-il VRAIMENT le vote majoritaire ?

Sur le jeu de données ci-dessus (§5), le vote majoritaire atteint 100 % de précision (5/5) : les quatre workers sont fiables, le problème est trop facile pour que le modèle Biased Worker (Dawid-Skene) montre sa valeur. L’analyse ci-dessus l’admet elle-même (« La vraie valeur des modèles de crowdsourcing apparaît sur de grands datasets avec workers hétérogènes »).

Pour rendre la capacité distinctive de Dawid-Skene visible dans les nombres, il faut un problème où le vote majoritaire a quelque chose à perdre : un jeu de données contaminé par des workers biaisés. C’est précisément la situation omniprésente dans le vrai crowdsourcing (annotateurs inattentifs, bots, spammeurs). Construisons ce cas : 3 workers fiables (avec quelques erreurs honnêtes) + 2 « spammers » qui votent toujours 1.

La cellule suivante implémente from-scratch l’algorithme EM de Dawid-Skene (Dawid & Skene, 1979) en C# pur, sans Infer.NET : chaque worker est modélisé par sa propre matrice de confusion \(\pi_j[k,l] = P(\text{pred}=l \mid \text{vrai}=k)\), estimée par EM. Les variables sont préfixées ds (Dawid-Skene) pour éviter toute collision de portée avec le §5 (nItems, labels, vraisLabels…).

// --- Cas discriminant : workers biaisés (le vote majoritaire échoue) ---
// Sur le jeu de données précédent (section 5), le vote majoritaire et
// Dawid-Skene atteignent tous les deux 100 % : les quatre workers sont
// fiables, le problème est trop facile pour que le modèle Dawid-Skene montre
// sa valeur. Construisons un cas où des workers biaisés (des "spammers" qui
// votent toujours 1) font échouer le vote majoritaire, et regardons si
// Dawid-Skene récupère les vrais labels.

// 3 workers fiables (avec quelques erreurs honnêtes) + 2 spammers (toujours 1)
int[] dsTrue = { 1, 0, 1, 0, 1, 0, 1, 0 };
int dsN = dsTrue.Length;
// W1 parfait, W2 erreur item 3, W3 erreur item 7, W4/W5 toujours 1 (spammers)
int[,] dsLabels = {
    {1, 0, 1, 0, 1, 0, 1, 0},  // W1 fiable parfait (= vrai)
    {1, 0, 1, 1, 1, 0, 1, 0},  // W2 : item 3 vrai=0 -> dit 1
    {1, 0, 1, 0, 1, 0, 1, 1},  // W3 : item 7 vrai=0 -> dit 1
    {1, 1, 1, 1, 1, 1, 1, 1},  // W4 spammer : toujours 1
    {1, 1, 1, 1, 1, 1, 1, 1},  // W5 spammer : toujours 1
};
int dsWorkers = dsLabels.GetLength(0);
int dsClasses = 2;

Console.WriteLine("=== Cas discriminant : 3 workers fiables + 2 spammers (toujours 1) ===");
Console.Write("            ");
for (int w = 0; w < dsWorkers; w++) Console.Write("W{0} ", w + 1);
Console.WriteLine("| Vrai");
for (int i = 0; i < dsN; i++)
{
    Console.Write("  Item {0} :  ", i);
    for (int w = 0; w < dsWorkers; w++) Console.Write("{0}  ", dsLabels[w, i]);
    Console.WriteLine("| {0}", dsTrue[i]);
}

// Vote majoritaire (baseline)
int dsMvCorrect = 0;
int[] dsMvPred = new int[dsN];
Console.WriteLine("\n--- Vote majoritaire (baseline) ---");
for (int i = 0; i < dsN; i++)
{
    int ones = 0;
    for (int w = 0; w < dsWorkers; w++) if (dsLabels[w, i] == 1) ones++;
    int zeros = dsWorkers - ones;
    dsMvPred[i] = ones > zeros ? 1 : 0;
    if (dsMvPred[i] == dsTrue[i]) dsMvCorrect++;
}
Console.Write("Predictions : [");
for (int i = 0; i < dsN; i++) Console.Write("{0}{1}", dsMvPred[i], i < dsN - 1 ? ", " : "");
Console.WriteLine("]");
Console.WriteLine("Precision   : {0:F1}% ({1} / {2})", 100.0 * dsMvCorrect / dsN, dsMvCorrect, dsN);
Console.Write("Items en erreur : [");
bool dsFirst = true;
for (int i = 0; i < dsN; i++) if (dsMvPred[i] != dsTrue[i]) { Console.Write("{0}{1}", dsFirst ? "" : ", ", i); dsFirst = false; }
Console.WriteLine("]");

// Dawid-Skene : EM sur les matrices de confusion par worker.
// Init diagonal-dominant canonique : chaque worker suppose fiable a priori
// (P(pred=k|vrai=k)=0.8, hors-diagonale=0.1). L'EM va ensuite re-estimer ces
// matrices a partir des donnees : les spammers (W4/W5) se reveleront par une
// ligne "Pred 1" saturant a 1.0 sur les deux lignes "Vrai 0" et "Vrai 1".
double[,,] dsConf = new double[dsWorkers, dsClasses, dsClasses];
for (int w = 0; w < dsWorkers; w++)
    for (int k = 0; k < dsClasses; k++)
        for (int l = 0; l < dsClasses; l++)
            dsConf[w, k, l] = (k == l) ? 0.8 : 0.1;
double[,] dsPost = new double[dsN, dsClasses];
for (int iter = 0; iter < 20; iter++)
{
    // E-step : P(vrai=k | labels) prop. prod_w Conf[w,k,label[i,w]], prior uniforme
    for (int i = 0; i < dsN; i++)
    {
        for (int k = 0; k < dsClasses; k++)
        {
            double p = 0.5; // prior uniforme
            for (int w = 0; w < dsWorkers; w++) p *= dsConf[w, k, dsLabels[w, i]];
            dsPost[i, k] = p;
        }
        double ps = dsPost[i, 0] + dsPost[i, 1];
        dsPost[i, 0] /= ps; dsPost[i, 1] /= ps;
    }
    // M-step : Conf[w,k,l] = sum_i Post[i,k] where label[i,w]==l, normalise par ligne
    for (int w = 0; w < dsWorkers; w++)
        for (int k = 0; k < dsClasses; k++)
        {
            double c0 = 0.0, c1 = 0.0;
            for (int i = 0; i < dsN; i++)
            {
                if (dsLabels[w, i] == 0) c0 += dsPost[i, k];
                else c1 += dsPost[i, k];
            }
            double s = c0 + c1;
            dsConf[w, k, 0] = s > 0 ? c0 / s : 0.5;
            dsConf[w, k, 1] = s > 0 ? c1 / s : 0.5;
        }
}
int[] dsDsPred = new int[dsN];
int dsDsCorrect = 0;
for (int i = 0; i < dsN; i++)
{
    dsDsPred[i] = dsPost[i, 1] > dsPost[i, 0] ? 1 : 0;
    if (dsDsPred[i] == dsTrue[i]) dsDsCorrect++;
}
Console.WriteLine("\n--- Dawid-Skene (EM) ---");
Console.Write("Predictions : [");
for (int i = 0; i < dsN; i++) Console.Write("{0}{1}", dsDsPred[i], i < dsN - 1 ? ", " : "");
Console.WriteLine("]");
Console.WriteLine("Precision   : {0:F1}% ({1} / {2})", 100.0 * dsDsCorrect / dsN, dsDsCorrect, dsN);

int dsGain = dsDsCorrect - dsMvCorrect;
Console.WriteLine("\n>>> Dawid-Skene récupère {0} item(s) que le vote majoritaire ratait ({1:F1}% vs {2:F1}%).",
    dsGain, 100.0 * dsDsCorrect / dsN, 100.0 * dsMvCorrect / dsN);

Console.WriteLine("\n--- Matrices de confusion estimées (le biais des spammers est reconnu) ---");
for (int w = 0; w < dsWorkers; w++)
{
    string nom = w >= 3 ? "W" + (w + 1) + " (SPAMMER)" : "W" + (w + 1);
    Console.WriteLine("\n{0} :", nom);
    Console.WriteLine("           Pred 0   Pred 1");
    Console.WriteLine("  Vrai 0 :  {0:F2}     {1:F2}", dsConf[w, 0, 0], dsConf[w, 0, 1]);
    Console.WriteLine("  Vrai 1 :  {0:F2}     {1:F2}", dsConf[w, 1, 0], dsConf[w, 1, 1]);
}
=== Cas discriminant : 3 workers fiables + 2 spammers (toujours 1) ===
            W1 W2 W3 W4 W5 | Vrai
  Item 0 :  1  1  1  1  1  | 1
  Item 1 :  0  0  0  1  1  | 0
  Item 2 :  1  1  1  1  1  | 1
  Item 3 :  0  1  0  1  1  | 0
  Item 4 :  1  1  1  1  1  | 1
  Item 5 :  0  0  0  1  1  | 0
  Item 6 :  1  1  1  1  1  | 1
  Item 7 :  0  0  1  1  1  | 0

--- Vote majoritaire (baseline) ---
Predictions : [1, 0, 1, 1, 1, 0, 1, 1]
Precision   : 75,0% (6 / 8)
Items en erreur : [3, 7]

--- Dawid-Skene (EM) ---
Predictions : [1, 0, 1, 0, 1, 0, 1, 0]
Precision   : 100,0% (8 / 8)

>>> Dawid-Skene récupère 2 item(s) que le vote majoritaire ratait (100,0% vs 75,0%).

--- Matrices de confusion estimées (le biais des spammers est reconnu) ---

W1 :
           Pred 0   Pred 1
  Vrai 0 :  1,00     0,00
  Vrai 1 :  0,00     1,00

W2 :
           Pred 0   Pred 1
  Vrai 0 :  0,75     0,25
  Vrai 1 :  0,00     1,00

W3 :
           Pred 0   Pred 1
  Vrai 0 :  0,75     0,25
  Vrai 1 :  0,00     1,00

W4 (SPAMMER) :
           Pred 0   Pred 1
  Vrai 0 :  0,00     1,00
  Vrai 1 :  0,00     1,00

W5 (SPAMMER) :
           Pred 0   Pred 1
  Vrai 0 :  0,00     1,00
  Vrai 1 :  0,00     1,00

Lecture — Dawid-Skene récupère ce que le vote majoritaire rate

Le contraste est net : sur les 8 items, le vote majoritaire tombe à 75 % (il se trompe sur les items 3 et 7, où les 2 spammers + une erreur honnête d’un worker fiable font basculer la majorité vers 1 alors que le vrai label est 0), tandis que Dawid-Skene retrouve les 8 vrais labels (100 %) — soit +2 items récupérés.

Le mécanisme est lisible dans les matrices de confusion estimées : le modèle a reconnu que W4 et W5 votent toujours 1, indépendamment de la vérité (\(P(\text{pred}=1 \mid \text{vrai}=0) = 1{,}0\) et \(P(\text{pred}=1 \mid \text{vrai}=1) = 1{,}0\) — un vote qui ne transporte aucune information sur le vrai label). Dawid-Skene déprécie donc ces workers (leurs votes ne pèsent plus dans l’estimation) et s’appuie sur les workers fiables W1–W3 pour reconstruire la vérité. Le vote majoritaire, lui, traite tous les votes à parts égales — il est aveugle au biais.

C’est la raison d’être du modèle de Dawid-Skene : ce n’est pas sur les données propres qu’il surpasse la baseline, mais sur les données bruitées et biaisées — exactement ce que l’on rencontre en production (étiquetage humain à grande échelle, annotation médicale multi-experts, modération de contenu). C’est aussi ce que le modèle Community (§6) généralise en regroupant les workers par profil de fiabilité, et ce que l’apprentissage actif (§7) exploite pour cibler les items où annoter davantage apporterait le plus d’information.

5bis. Exercice : Detection de Workers Contradictoires par Matrice d’Accord

Contexte

Dans la section précédente, nous avons vu que le vote majoritaire atteint 100% sur notre petit jeu de données. Cependant, cette méthode ignore les relations entre workers. Une matrice d’accord inter-annotateurs permet de detecter des workers qui contredisent systematiquement les autres.

Objectif : Construire une matrice d’accord entre workers, puis identifier les paires de workers les plus en desaccord.

Enonce

A partir de la matrice labels déjà définie (5 items, 4 workers) :

  1. Calculez la matrice d’accord agreement[w1, w2] = proportion d’items ou labels[i, w1] == labels[i, w2]
  2. Identifiez la paire de workers avec le taux d’accord le plus faible
  3. Pour cette paire, determinez lequel des deux workers est le plus credible en comparant avec les vrais labels

Indice : La matrice d’accord est symetrique et sa diagonale vaut 1.0. Pour chaque paire (w1, w2), comptez le nombre d’items ou ils s’accordent et divisez par nItems. Utilisez vraisLabels pour determiner qui a raison quand ils divergent.

// Exercice : Detection de workers contradictoires par matrice d'accord

// TODO: Etape 1 - Calculer la matrice d'accord entre workers
// double[,] agreement = new double[nWorkers, nWorkers];
// Pour chaque paire (w1, w2), calculer la proportion d'accord

// TODO: Etape 2 - Afficher la matrice d'accord
// Console.WriteLine("Matrice d'accord inter-annotateurs :");

// TODO: Etape 3 - Trouver la paire avec le plus faible taux d'accord

// TODO: Etape 4 - Comparer les deux workers divergents avec les vrais labels
// Pour determiner lequel est le plus fiable
Console.WriteLine("Exercice a completer");
Exercice a completer

6. modèle Community (hiérarchique)

Idee

Les workers appartiennent a des communautes avec des caractéristiques similaires.

Structure

Communaute[c] : matrice de confusion partagee
    |
    v
Worker[w] : appartient a communaute z[w]
    |
    v
Label[w,i] : genere selon confusion[z[w]]

Origine du formalisme

Le modèle Community s’inscrit dans la lignée des modèles hiérarchiques bayésiens introduits pour le crowdsourcing par Raykar et al. (2010), qui montrent comment un prior partagé entre workers similaires régularise les estimations sur les nouveaux annotateurs (cold-start). L’idée de regrouper les workers en communautés avec des paramètres partagés est un cas particulier de partial pooling hiérarchique, sur le même principe que les modèles à effets aléatoires (Gelman & Hill, Data Analysis Using Regression and Multilevel/Hierarchical Models, 2007).

L’algorithme d’inference iteratif de Karger, Oh & Shah (2011) (NIPS Workshop on Crowdsourcing) formalise précisément ce type de modèle et montre comment des mises à jour alternées item-worker-item convergent en \(O(1/n)\) vers le bon label ; la structure ci-dessus en est une spécialisation à deux communautés.

Implementation du modèle Community

Nous definissons la structure de base d’un modèle hiérarchique avec deux communautes (Experts et Spammeurs). En pratique, ce modèle necessite plus de données pour estimer correctement les paramètres des communautes.

Note : L’implementation complete avec inference sur tous les items est plus complexe et necessite une specification soigneuse des dependances entre variables.

// Modele Community simplifie

// Simulons 2 communautes :
// - Experts (haute precision)
// - Spammeurs (repondent aleatoirement)

int nCommunities = 2;

// Distribution sur les communautes
Variable<Vector> pCommunity = Variable.Dirichlet(new double[] { 1, 1 }).Named("pCommunity");

Range communityRange = new Range(nCommunities).Named("community");
Range workerRange3 = new Range(nWorkers).Named("worker");

// Capacite par communaute
VariableArray<double> communityCapacity = Variable.Array<double>(communityRange).Named("communityCapacity");
communityCapacity[communityRange] = Variable.Beta(2, 1).ForEach(communityRange);

// Assignation des workers aux communautes
VariableArray<int> workerCommunity = Variable.Array<int>(workerRange3).Named("workerCommunity");
workerCommunity[workerRange3] = Variable.Discrete(pCommunity).ForEach(workerRange3);

Console.WriteLine("=== Modele Community ===");
Console.WriteLine("\nHypothese : 2 communautes (Experts / Spammeurs)");
Console.WriteLine("\nLe modele infere l'appartenance de chaque worker.");
=== Modele Community ===

Hypothese : 2 communautes (Experts / Spammeurs)

Le modele infere l'appartenance de chaque worker.

Structure hiérarchique du modèle Community

Le modèle Community introduit un niveau de hiérarchie supplementaire :

Niveau 1 (Communautes) : \[\theta_c \sim \text{Prior}(\text{capacite par communaute})\]

Niveau 2 (Workers) : \[z_w \sim \text{Categorical}(\pi) \quad \text{(appartenance communaute)}\] \[\text{qualite}_w = \theta_{z_w}\]

Niveau 3 (Annotations) : \[l_{w,i} \sim P(\cdot | t_i, \text{qualite}_w)\]

Avantages :

Benefice Explication
Partage d’information Workers d’une même communaute s’informent mutuellement
Nouveaux workers Un nouveau worker est initialise selon sa communaute
Interpretabilite On peut nommer les communautes (experts, novices, spammeurs)
Regularisation Moins de paramètres que Biased Worker complet

Analogie : C’est similaire a un modèle de melange gaussien (GMM), mais applique aux workers au lieu des données.

7. Apprentissage Actif

Objectif

Choisir quels items faire annoter et par quels workers pour maximiser l’information gagnee.

stratégies

stratégie Description
Uncertainty sampling Annoter les items les plus incertains
Worker evaluation Tester les workers sur des items connus
Information gain Maximiser la reduction d’entropie

Formulation mathematique de l’apprentissage actif

L’objectif est de sélectionner la paire (item, worker) qui maximise le gain d’information :

\[\text{Gain}(i, w) = H(t_i) - \mathbb{E}_{l_{w,i}}[H(t_i | l_{w,i})]\]

ou \(H(\cdot)\) est l’entropie de Shannon.

Interpretation : - \(H(t_i)\) : incertitude actuelle sur le vrai label - \(\mathbb{E}[H(t_i | l_{w,i})]\) : incertitude attendue après avoir observe l’annotation

Entropie binaire : Pour \(K=2\) classes avec probabilite \(p\) :

\[H(p) = -p \log_2(p) - (1-p) \log_2(1-p)\]

\(p\) \(H(p)\) Interpretation
0.50 1.00 Incertitude maximale
0.75 0.81 Incertitude elevee
0.90 0.47 Incertitude moderee
0.99 0.08 Quasi-certain

Origine du critere information gain

L’apprentissage actif dans le crowdsourcing a été formalisé par Karger, Oh & Shah (2011) dans leur algorithme iteratif qui choisit la paire (item, worker) maximisant la reduction d’entropie sur la posterior des labels. Le critere d’information gain \(H(t_i) - \mathbb{E}_{l_{w,i}}[H(t_i | l_{w,i})]\) ci-dessus est exactement leur fonction objectif.

Le cadre plus général de l’active learning (avant crowdsourcing) est posé par Settles (2010, rev. 2012) dans sa synthèse Active Learning Literature Survey : uncertainty sampling, information density, expected error reduction. Notre démarche d’entropie binaire \(H(p)\) relève directement de la famille uncertainty sampling (p. 12-15) de Settles, appliquée à un problème de classification binaire.

Implementation de l’uncertainty sampling

Nous calculons l’entropie pour chaque item afin d’identifier ceux qui beneficieraient le plus d’annotations supplementaires. L’entropie est maximale (1.0) quand la distribution est 50/50, et nulle quand il y a consensus parfait.

// Selection d'items basee sur l'incertitude

Console.WriteLine("=== Apprentissage Actif ===");
Console.WriteLine("\nSelection des items les plus incertains :");

// Calculer l'incertitude (entropie) pour chaque item base sur le vote majoritaire
var incertitudes = new List<(int item, double entropie)>();

for (int i = 0; i < nItems; i++)
{
    int count0 = 0, count1 = 0;
    for (int w = 0; w < nWorkers; w++)
    {
        if (labels[i, w] == 0) count0++;
        else count1++;
    }
    
    double p0 = (double)count0 / nWorkers;
    double p1 = (double)count1 / nWorkers;
    
    // Entropie binaire
    double entropie = 0;
    if (p0 > 0) entropie -= p0 * Math.Log2(p0);
    if (p1 > 0) entropie -= p1 * Math.Log2(p1);
    
    incertitudes.Add((i, entropie));
    Console.WriteLine($"Item {i} : votes 0:{count0} / 1:{count1}, entropie = {entropie:F3}");
}

var prioritaire = incertitudes.OrderByDescending(x => x.entropie).First();
Console.WriteLine($"\n=> Item {prioritaire.item} devrait etre annote en priorite (entropie max)");
=== Apprentissage Actif ===

Selection des items les plus incertains :
Item 0 : votes 0:1 / 1:3, entropie = 0,811
Item 1 : votes 0:4 / 1:0, entropie = 0,000
Item 2 : votes 0:1 / 1:3, entropie = 0,811
Item 3 : votes 0:3 / 1:1, entropie = 0,811
Item 4 : votes 0:1 / 1:3, entropie = 0,811

=> Item 0 devrait etre annote en priorite (entropie max)

Analyse de l’apprentissage actif

Résultats d’entropie :

Item Votes (0:1) Entropie Incertitude
1 4:0 0.000 Nulle (consensus total)
0, 2, 3, 4 1:3 ou 3:1 0.811 Égale (même distribution 75/25)

Observations :

  1. Item 1 exclu : Consensus parfait → pas besoin d’annotation supplémentaire
  2. Items 0, 2, 3, 4 équivalents : Même niveau d’incertitude (1 dissident sur 4)
  3. Choix arbitraire : L’algorithme sélectionne Item 0 (premier dans l’ordre)

Stratégies d’amélioration :

Critère Description
Entropie Annoter les items les plus incertains
Utilité attendue Combiner incertitude × importance de l’item
Diversité workers Solliciter des workers différents
Gold questions Mélanger des items connus pour évaluer les workers

Note : Avec une entropie max de 1.0 (50/50), une entropie de 0.811 correspond à une distribution 75/25 (1 dissident sur 4), soit 25% de désaccord.

8. Exemple guide : Crowdsourcing d’Images

Enonce

Simulez un scénario de classification d’images (chat/chien) avec 8 workers de qualites variables.

Contexte et solution

Cet exercice simule un scénario realiste de classification d’images par crowdsourcing :

Configuration : - 10 images (chats et chiens melanges aleatoirement) - 8 annotateurs avec des qualites variant de 0.50 (aleatoire) a 0.95 (expert)

Qualites simulees : [0.95, 0.90, 0.85, 0.80, 0.70, 0.60, 0.55, 0.50]

Cette distribution est typique des plateformes comme Amazon Mechanical Turk : - Quelques experts (qualite > 0.90) - Une majorite de workers moyens (0.60-0.80) - Quelques spammeurs/bots (qualite ~ 0.50)

Objectif : Comparer l’estimation de qualite par consensus avec les vraies qualites.

Le code ci-dessous genere des annotations synthetiques et compare les qualites vraies des annotateurs aux qualites estimees par accord avec la majorite.

// Exemple guide : Crowdsourcing d'images

int nImages = 10;
int nAnnotateurs = 8;
Random rng = new Random(42);

// Vrais labels (0=chat, 1=chien)
int[] vraisLabelsImg = Enumerable.Range(0, nImages).Select(i => rng.Next(2)).ToArray();

// Qualites des annotateurs (differentes)
double[] qualites = { 0.95, 0.90, 0.85, 0.80, 0.70, 0.60, 0.55, 0.50 };

// Generer les annotations
int[,] annotations = new int[nImages, nAnnotateurs];

for (int i = 0; i < nImages; i++)
{
    for (int a = 0; a < nAnnotateurs; a++)
    {
        if (rng.NextDouble() < qualites[a])
            annotations[i, a] = vraisLabelsImg[i];  // Correct
        else
            annotations[i, a] = 1 - vraisLabelsImg[i];  // Erreur
    }
}

Console.WriteLine("=== Crowdsourcing Images (Chat=0 / Chien=1) ===");
Console.WriteLine($"\nQualites vraies des annotateurs : {string.Join(", ", qualites.Select(q => $"{q:F2}"))}\n");

// Evaluation par vote majoritaire
int correctes = 0;
for (int i = 0; i < nImages; i++)
{
    int votes1 = Enumerable.Range(0, nAnnotateurs).Count(a => annotations[i, a] == 1);
    int prediction = votes1 > nAnnotateurs / 2 ? 1 : 0;
    if (prediction == vraisLabelsImg[i]) correctes++;
}

Console.WriteLine($"Precision vote majoritaire : {100.0 * correctes / nImages:F0}%");

// Estimation des qualites basee sur le consensus
Console.WriteLine("\nEstimation des qualites (accord avec majorite) :");
for (int a = 0; a < nAnnotateurs; a++)
{
    int accords = 0;
    for (int i = 0; i < nImages; i++)
    {
        int votes1 = Enumerable.Range(0, nAnnotateurs).Count(x => annotations[i, x] == 1);
        int majorite = votes1 > nAnnotateurs / 2 ? 1 : 0;
        if (annotations[i, a] == majorite) accords++;
    }
    double qualiteEstimee = (double)accords / nImages;
    Console.WriteLine($"  Annotateur {a+1} : vraie={qualites[a]:F2}, estimee={qualiteEstimee:F2}");
}
=== Crowdsourcing Images (Chat=0 / Chien=1) ===

Qualites vraies des annotateurs : 0,95, 0,90, 0,85, 0,80, 0,70, 0,60, 0,55, 0,50

Precision vote majoritaire : 100%

Estimation des qualites (accord avec majorite) :
  Annotateur 1 : vraie=0,95, estimee=1,00
  Annotateur 2 : vraie=0,90, estimee=1,00
  Annotateur 3 : vraie=0,85, estimee=1,00
  Annotateur 4 : vraie=0,80, estimee=0,90
  Annotateur 5 : vraie=0,70, estimee=1,00
  Annotateur 6 : vraie=0,60, estimee=0,80
  Annotateur 7 : vraie=0,55, estimee=0,70
  Annotateur 8 : vraie=0,50, estimee=0,50

Analyse de l’exercice crowdsourcing

Configuration : 10 images, 8 annotateurs avec qualités de 0.50 à 0.95

Résultats :

Annotateur Qualité vraie Qualité estimée Écart
1 0.95 1.00 +0.05
2 0.90 1.00 +0.10
3 0.85 1.00 +0.15
4 0.80 0.90 +0.10
5 0.70 1.00 +0.30
6 0.60 0.80 +0.20
7 0.55 0.70 +0.15
8 0.50 0.50 0.00

Observations :

  1. Surestimation systématique : L’estimation par accord avec la majorité surestime la qualité
  2. Raison : Le vote majoritaire est généralement correct → s’y accorder donne un bon score
  3. Annotateur 8 (0.50) : Seul cas bien estimé car vraiment aléatoire
  4. Annotateur 5 (0.70 → 1.00) : Surestimation maximale car chance d’être du bon côté

Limites de l’estimation par consensus :

  • Ne détecte pas les biais corrélés entre workers
  • Nécessite des gold questions (items de vérité connue) pour calibrer
  • Un modèle Honest Worker donnerait des estimations plus conservatrices

9. Resume et Guide de Choix

Comparaison des modèles

Critere Vote majoritaire Honest Worker Biased Worker Community
Simplicite très simple Simple Moyen Complexe
Annotations requises Quelques ~5/item ~10/item ~20/item
Biais detectes Non Non Oui Oui
Nouveaux workers - Cold start Cold start Via communaute
Scalabilite Excellente Bonne Moyenne Moyenne

Concepts cles

Concept Description
Honest Worker Capacite unique par worker
Biased Worker Matrice de confusion par worker
Community Workers groupes en communautes
Apprentissage actif sélection optimale des annotations
Gold standard Items avec vrai label connu pour evaluation

Recommandations pratiques

  1. Commencer simple : Vote majoritaire + gold questions pour filtrer les spammeurs

  2. Evoluer si necessaire :

    • Desaccords frequents → Honest Worker
    • Biais systématiques → Biased Worker
    • Beaucoup de workers → Community
  3. Toujours inclure des gold questions : 5-10% d’items de verite connue pour detecter les spammeurs et calibrer les estimations


Prochaine étape

Dans Infer-14-Sequences, nous explorerons les Hidden Markov Models (HMM), l’algorithme de Viterbi, et l’application au motif finding en bioinformatique.

References completes et sources canoniques

Les modèles présentés dans ce notebook s’inscrivent dans une lignée bien identifiée de la littérature sur l’agrégation de labels bruités. Les sources primaires et syntheses pedagogiques utilisees comme reference sont les suivantes.

Sources primaires (par ordre d’importance)

Reference Annee Contribution
Dawid & Skene, Maximum likelihood estimation of observer error-rates using the EM algorithm, Journal of the Royal Statistical Society, Series A 142(3):257-286 1979 Première formulation EM de l’agrégation de labels avec matrice de confusion par worker. Inspire directement le modèle Honest Worker et le modèle Biased Worker.
Raykar, Yu, Zhao, Valadez, Florin, Bogoni & Moy, Learning from Crowds, Journal of Machine Learning Research 11:1297-1332 2010 Formalisation bayésienne avec prior Beta sur la matrice de confusion. Justifie le choix de Variable.Beta(2,1) et Variable.Dirichlet(10,1) dans ce notebook.
Karger, Oh & Shah, Iterative Learning from Crowds, NIPS Workshop on Computational Social Science and the Wisdom of Crowds 2011 Algorithme itératif à garantie de convergence pour l’agrégation ; inspire la structure modèle Community et le critère information gain de la section 7.
Whitehill, Wu, Bergsma, Barber & Moors, Whose Vote Should Count More: Optimal Integration of Labels from Labelers of Unknown Expertise, NIPS 2009 Modèle GLAD combinant capacité du worker et difficulté de l’item. Mentionné comme extension dans la section Comparative.
Kim & Ghahramani, Bayesian Classifier Combination, Proceedings of ICML 2012 Modèle BCC = version bayésienne de Dawid-Skene avec hiérarchie sur les hyper-paramètres. Mentionné dans la section Comparative.

Syntheses et tutoriels

Reference Annee Usage
Settles, Active Learning Literature Survey, Computer Sciences Technical Report 1648, University of Wisconsin-Madison 2010, rev. 2012 Cadre général de l’apprentissage actif (uncertainty sampling), section 7 du notebook.
Raykar, Learning from Crowds (tutorial), http://algorithmics.umd.edu/talks/raykar-crowds-jst-11.pdf 2011 Slides du tutoriel JST ; présentation structurée des trois modèles vus ici.
MBML Book, Crowdsourcing (Chap. 7 / sub-page mbmlbook.com/crowdsourcing), https://mbmlbook.com/crowdsourcing en ligne Présentation pédagogique d’ensemble, inspire l’approche incrémentielle du notebook (vote majoritaire -> Honest -> Biased -> Community).

Applications industrielles

Les modèles de ce notebook sont déployés à grande échelle dans l’industrie :

Domaine Application Exemple concret
NLP Annotation de corpus Sentiment analysis, NER, traduction (Amazon Mechanical Turk)
Vision Labellisation d’images ImageNet (Deng et al. 2009) utilise massivement le vote multi-annotateur
Medical Diagnostic assisté Dermatologie, radiologie (panel de radiologues experts)
Moderation Filtrage de contenu Spam, contenu inapproprié (réseaux sociaux)
IA generative RLHF (Reinforcement Learning from Human Feedback) Alignement de LLMs (Ouyang et al. 2022, Training language models to follow instructions with human feedback, arXiv:2203.02155)

Fidélité aux sources : Les équations et les choix de prior (Beta(2,1), Dirichlet(10,1), structure hiérarchique Community) sont alignés sur Dawid-Skene 1979 et Raykar 2010. La structure du critère information gain suit Karger 2011. Les applications ci-dessus sont données pour le contexte industriel (et non comme références mathématiques).

10. Exercice : Crowdsourcing avec Annotateurs d’Expertise Variable

Enonce

Un scénario de crowdsourcing avec 3 experts (qualite ~0.90) et 5 novices (qualite ~0.60).

Vrais labels de 6 images : {false, true, false, true, true, false} (false=chat, true=chien).

Annotateurs (indices 0-2 = experts, 3-7 = novices) avec P(correct) indiquees : - Experts (ann 0-2) : P(correct) = 0.90 - Novices (ann 3-7) : P(correct) = 0.60

étapes : 1. Generez les labels synthetiques par groupe 2. Construisez le modèle avec des a priori Beta différents par groupe (Beta(9,1) vs Beta(6,4)) 3. Comparez la precision inferee avec la règle de majorite simple

Indice : Pour generer les labels synthetiques, utilisez Random.NextDouble() compare a la qualite du groupe. Pour le modèle, vous pouvez créer deux tableaux de capacites Variable.Array<double> avec des priors Beta distincts pour chaque groupe, puis les combiner dans un seul tableau d’observations.

// Exercice : Crowdsourcing avec expertise variable par groupe
int nImages = 6;
int nExperts = 3;
int nNovices = 5;

// Vrais labels (pour validation externe uniquement)
bool[] vraisLabels = { false, true, false, true, true, false };

// TODO: Generer des labels synthetiques

// TODO: Definir des a priori differents par groupe

// TODO: Construire le modele Dawid-Skene simplifie
// Pour chaque image : vrai label + annotations conditionnelles par groupe
//     // Annotations experts et novices conditionnelles sur vraiLabel[img]...

// TODO: Inferer les vrais labels et comparer avec majorite simple
// Calculez la precision de chaque methode
Console.WriteLine("Exercice a completer");
Exercice a completer

10bis. Exercice : Detection de Workers Spammeurs

Dans les plateformes de crowdsourcing, certains workers (appeles “spammers”) soumettent des reponses aleatoires ou systématiques sans lire les questions. Leur identification est cruciale pour maintenir la qualite des annotations.

Objectif : Implementer une méthode de detection heuristique des spammeurs, puis mesurer l’amelioration de la qualite des résultats après filtrage.

données fournies : - 6 items binaires (labels 0 ou 1), annotes par 5 workers - Workers W3 et W5 sont des spammeurs (repondent toujours 0) - Les vrais labels sont fournis pour evaluer la qualite

étapes :

  1. Analyse de variance – Pour chaque worker, calculer la variance de ses reponses. Un spammeur qui repond toujours la même chose a une variance proche de 0.
  2. Accord majoritaire – Calculer le taux d’accord de chaque worker avec le vote majoritaire (par item, le label le plus frequent l’emporte). Les spammeurs ont un accord faible car ils ne suivent pas le consensus.
  3. Identification – Un worker est classe spammeur si sa variance est proche de 0 ET son accord avec la majorite est < 60%.
  4. Filtrage – Recalculer le vote majoritaire en excluant les spammeurs identifies, puis comparer la precision (avant/après).

Indices : - # Indice : La variance d’une serie constante vaut 0. En C#, calculez mean_of_squares - square_of_mean. - # Indice : Pour le vote majoritaire par item, comptez les votes > 0.5 pour chaque item. - # Indice : W3 et W5 repondent toujours 0, leur variance sera 0. Ce sont vos cibles.

// Exercice : Detection de workers spammeurs par variance + accord
// On dispose de 6 items binaires (labels 0 ou 1), annotes par 5 workers

string[][] workerLabels = {
    new[] { "1", "1", "0", "1", "0", "1" },  // W1: bon worker
    new[] { "1", "0", "1", "1", "0", "1" },  // W2: bon worker
    new[] { "0", "0", "0", "0", "0", "0" },  // W3: spammeur (toujours 0)
    new[] { "1", "1", "0", "0", "1", "0" },  // W4: bon worker
    new[] { "0", "0", "0", "0", "0", "0" }   // W5: spammeur (toujours 0)
};
int[] vraisLabels = { 1, 1, 0, 1, 0, 1 };

// TODO: Etape 1 - Calculer la variance des reponses de chaque worker
// Indice : Convertir en int, calculer mean puis mean_of_squares - square_of_mean

// TODO: Etape 2 - Calculer le taux d'accord avec le vote majoritaire
// Indice : Pour chaque item, trouver le label majoritaire, puis compter les matchs

// TODO: Etape 3 - Identifier les spammeurs (variance < 0.05 ET accord < 0.6)
// Indice : W3 et W5 auront variance=0 et accord faible

// TODO: Etape 4 - Recalculer le vote majoritaire sans spammeurs, comparer precision
// Indice : Exclure les indices des spammeurs, puis voter sur les labels restants

Console.WriteLine("Exercice a completer : detection de workers spammeurs");
Exercice a completer : detection de workers spammeurs

Conclusion

Ce notebook a traite un problème central de l’apprentissage a partir de données humaines : agreger des labels bruites fournis par des annotateurs de fiabilite inconnue pour reconstruire la verite terrain. La cle est de ne pas se contenter du label, mais d’inferer conjointement le vrai label de chaque item et la fiabilite de chaque worker.

Une montee en expressivite, au prix de la donnee

Les quatre approches vues forment une progression ou chaque modèle capture une source de bruit supplementaire, en echange d’un cout d’annotation croissant :

modèle Ce qu’il capture Limite levee
Vote majoritaire Rien (compte les voix) Baseline naive, sensible aux spammeurs
Honest Worker Une competence scalaire par worker Pondere les workers fiables
Biased Worker Une matrice de confusion par worker Detecte les biais systématiques par classe
Community Des groupes de workers (hiérarchie) Resout le cold-start via partage de force

Le fil conducteur est bayesien : un worker incertain voit son vote automatiquement dilue, et un item controverse reste a forte entropie posterieure – ce qui alimente directement l’apprentissage actif (uncertainty sampling), qui concentre l’effort d’annotation la ou il reduit le plus l’incertitude.

Ce que Infer.NET apporte

L’inference jointe labels + fiabilites est exactement le genre de problème a variables latentes ou un calcul manuel (style EM) devient vite intraitable. Infer.NET propage les croyances sur le graphe de facteurs et restitue des posterieurs calibres sans derivation analytique : le modelisateur declare la structure (workers, items, matrices de confusion, communautes) et laisse le moteur estimer le tout simultanement. La structure hiérarchique du modèle Community, en particulier, illustre comment le partage de paramètres entre workers regularise les estimations des nouveaux annotateurs.

A retenir

Le bon modèle de crowdsourcing n’est pas le plus sophistique, mais le plus simple qui capture le bruit reellement present dans vos données. Commencer par le vote majoritaire avec des gold questions, puis monter en complexite (Honest -> Biased -> Community) seulement quand les desaccords, biais ou le cold-start l’imposent. Dans tous les cas, garder 5-10 % d’items a verite connue reste le filet de securite qui calibre les estimations et demasque les spammeurs.

Ce raisonnement – inferer des variables latentes (le vrai label) a partir d’observations bruitees mediees par des paramètres de fiabilite – prepare directement les modèles a etats caches du notebook suivant (Infer-14-Sequences), ou la structure latente devient temporelle.

Retour au sommet