// Visualisation graphique du système expert Bayesien multi-sources : posterior + utilites.
// Technique C548-L2 : SVG inline via SvgChartHelper.cs (#6942 MERGED), zero-dependance NuGet.
// Les deux traces (posterior + utilite) sont tracees en deux chartes distinctes car elles
// partagent PAS un même axe X categoriel (panne: OK/RAM/Disque/CPU vs action: Rien/Remplacer...).
// SvgChartHelper.Overlay (#6958 MERGED) requiert un axe X numerique partage => inapplicable.
// Le helper Overlay reste disponible pour les courbes VoI type DecInfer-06 EVPI ; ici c'est
// l'usage canonique Bar() qui s'applique. Cf #6927 / #3801 / #6942 / #6958 / See #6954 (App-7b).
#load "../../Infer/SvgChartHelper.cs"
int nPannes = 4; // 0=OK, 1=RAM, 2=Disque, 3=CPU
Range panneRange = new Range(nPannes).Named("panneRange");
string[] pannes = { "OK", "RAM", "Disque", "CPU" };
// === Panne latente (verite a inferer) ===
Variable<int> panne = Variable.DiscreteUniform(nPannes).Named("panne");
panne.SetValueRange(panneRange);
// === Source 1 : Logs système (fiabilite a inferer) ===
// Prior sur la precision des logs (Gamma car precision > 0)
Variable<double> precisionLogs = Variable.GammaFromShapeAndScale(5, 2).Named("precLogs");
// Scores observes dans les logs pour chaque type de panne
// (Plus le score est eleve, plus la panne est probable selon les logs)
double[] scoresLogsObserves = { 0.1, 0.15, 0.65, 0.10 }; // Logs suggerent Disque
// Le score observe est un indicateur bruite de la vraie panne
// Modèle : Si panne=k, alors score[k] est tire d'une distribution plus haute
VariableArray<double> vraiScoreMoyen = Variable.Array<double>(panneRange).Named("vraiScoreMoyen");
vraiScoreMoyen[panneRange] = Variable.GaussianFromMeanAndPrecision(0.3, 1.0).ForEach(panneRange);
// On observe les scores (simplifie : on conditionne sur la panne)
// Score eleve pour la vraie panne, bas pour les autres
Variable.ConstrainTrue(
Variable.Bernoulli(0.9) // 90% chance que le score max corresponde a la vraie panne
);
// === Source 2 : Avis utilisateur (matrice de confusion) ===
// L'utilisateur peut confondre les symptomes
// Matrice de confusion fixee (connaissance experte)
double[,] confusionUser = {
// L'utilisateur dit: OK RAM Disque CPU
/* Vraie panne OK */ { 0.85, 0.05, 0.05, 0.05 },
/* Vraie panne RAM */ { 0.05, 0.60, 0.20, 0.15 },
/* Vraie panne Disque */{ 0.05, 0.10, 0.75, 0.10 },
/* Vraie panne CPU */ { 0.05, 0.15, 0.15, 0.65 }
};
Variable<int> avisUser = Variable.New<int>().Named("avisUser");
avisUser.SetValueRange(panneRange);
// Avis utilisateur conditionne a la vraie panne
using (Variable.Case(panne, 0))
avisUser.SetTo(Variable.Discrete(confusionUser[0, 0], confusionUser[0, 1], confusionUser[0, 2], confusionUser[0, 3]));
using (Variable.Case(panne, 1))
avisUser.SetTo(Variable.Discrete(confusionUser[1, 0], confusionUser[1, 1], confusionUser[1, 2], confusionUser[1, 3]));
using (Variable.Case(panne, 2))
avisUser.SetTo(Variable.Discrete(confusionUser[2, 0], confusionUser[2, 1], confusionUser[2, 2], confusionUser[2, 3]));
using (Variable.Case(panne, 3))
avisUser.SetTo(Variable.Discrete(confusionUser[3, 0], confusionUser[3, 1], confusionUser[3, 2], confusionUser[3, 3]));
// Observation : l'utilisateur dit "Disque"
avisUser.ObservedValue = 2;
// === Source 3 : Capteur RAM (très fiable pour RAM, bruit pour autres) ===
Variable<bool> alerteRAM = Variable.New<bool>().Named("alerteRAM");
using (Variable.Case(panne, 0)) // OK
alerteRAM.SetTo(Variable.Bernoulli(0.02)); // Faux positif 2%
using (Variable.Case(panne, 1)) // RAM
alerteRAM.SetTo(Variable.Bernoulli(0.95)); // Detection 95%
using (Variable.Case(panne, 2)) // Disque
alerteRAM.SetTo(Variable.Bernoulli(0.05)); // Bruit 5%
using (Variable.Case(panne, 3)) // CPU
alerteRAM.SetTo(Variable.Bernoulli(0.08)); // Bruit 8%
// Observation : pas d'alerte RAM
alerteRAM.ObservedValue = false;
// === Inference ===
InferenceEngine engineExpert = new InferenceEngine();
engineExpert.Compiler.CompilerChoice = Microsoft.ML.Probabilistic.Compiler.CompilerChoice.Roslyn;
engineExpert.Algorithm = new ExpectationPropagation();
Console.WriteLine("=== Système Expert Bayesien Multi-Sources ===\n");
Console.WriteLine("Sources combinees :");
Console.WriteLine(" 1. Logs système : scores = [OK:0.10, RAM:0.15, Disque:0.65, CPU:0.10]");
Console.WriteLine(" 2. Avis utilisateur : 'Disque'");
Console.WriteLine(" 3. Capteur RAM : pas d'alerte\n");
var posteriorPanne = engineExpert.Infer<Discrete>(panne);
var posteriorPrecLogs = engineExpert.Infer<Gamma>(precisionLogs);
Console.WriteLine("Posterior sur la panne :");
// === Visualisation SVG inline canon (C548-L2) ===
// Avant : trace Plotly.js + cdn.plot.ly (rend BLANC en static, cf #6927).
// Apres : 2 chartes SvgChartHelper.Bar() distinctes (axes categoriels distincts => Overlay N/A).
var panneProbsVec = posteriorPanne.GetProbs();var panneProbs = new double[nPannes];for (int i = 0; i < nPannes; i++) panneProbs[i] = panneProbsVec[i];var panneColors = new[] { "#4C72B0", "#DD8452", "#55A868", "#C44E52" };
Console.WriteLine(" Mapping pedagogique (couleurs du chart Plotly originel preservees en legende Console) :");
for (int i = 0; i < nPannes; i++)
Console.WriteLine($" {pannes[i],-8} : {panneProbs[i]:P2} (Plotly color {panneColors[i]})");
// Bar 1 : posterior P(panne), couleur uniforme canon C548-L2.
display(SvgChartHelper.Bar(
"Posterior P(panne) - 4 categories",
pannes,
panneProbs,
width: 560,
height: 320));
// Cout attendu de chaque action (matrice identique au bloc Decision robuste ci-dessous,
// renommee ici chartCouts/chartActions pour eviter la collision de noms).
var chartCouts = new double[,] {
// OK RAM Disque CPU
/* Rien */ { 0, -500, -1000, -800 },
/* Rempl. RAM */{ -100, 0, -900, -700 },
/* Rempl. Disk*/{ -150, -400, 0, -600 },
/* Rempl. CPU */{ -300, -300, -800, 0 }
};
var chartActions = new[] { "Rien", "Remplacer RAM", "Remplacer Disque", "Remplacer CPU" };
var chartEU = new double[4];
for (int a = 0; a < 4; a++)
for (int p = 0; p < nPannes; p++)
chartEU[a] += panneProbs[p] * chartCouts[a, p];
int chartBestA = 0;
for (int a = 1; a < 4; a++)
if (chartEU[a] > chartEU[chartBestA]) chartBestA = a;
Console.WriteLine();
Console.WriteLine($"Diagnostic recommande : {chartActions[chartBestA]} (E[U] = {chartEU[chartBestA]:F0})");
Console.WriteLine(" Mapping pedagogique (meilleure action en vert, autres en gris) :");
var euColors = new[] { "#2CA02C", "#7F7F7F", "#7F7F7F", "#7F7F7F" };
for (int a = 0; a < 4; a++)
{
string tag = a == chartBestA ? "[BEST]" : "[--]";
Console.WriteLine($" {chartActions[a],-16} {tag} : E[U] = {chartEU[a],7:F0} (Plotly color {euColors[a]})");
}
// Bar 2 : E[U(action)], couleur uniforme canon.
var euLabelsComposed = new string[4];
for (int a = 0; a < 4; a++)
{
string tag = a == chartBestA ? "[BEST]" : "[--]";
euLabelsComposed[a] = $"{chartActions[a]} {tag}";
}
display(SvgChartHelper.Bar(
"E[U(action)] - utilite attendue par action",
euLabelsComposed,
chartEU,
width: 560,
height: 320));
Console.WriteLine();
Console.WriteLine($"\nPrecision inferee des logs : {posteriorPrecLogs.GetMean():F2} (shape={posteriorPrecLogs.Shape:F1}, scale={1.0/posteriorPrecLogs.Rate:F2})");
Comment choisir γ ?
Le coefficient γ depend de :