// --- 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]);
}