#r "nuget: Microsoft.ML, 5.0.0"
Console.WriteLine("Microsoft.ML 5.0.0 charge");- Microsoft.ML, 5.0.0
Microsoft.ML 5.0.0 charge
Navigation : Index | << Précédent
A la fin de ce notebook, vous saurez :
AnomalyDetectionCatalog)Le ML-8 regroupait des points en clusters sans etiquettes. La detection d’anomalies repond a une question différente : etant donne un regime de fonctionnement normal (apprentissage), quels points s’en ecartent suffisamment pour etre soupconnes anormaux ? C’est encore de l’apprentissage non-supervise au sens ou le modèle n’apprend qu’a partir de données normales — mais la tâche est un test (chaque point est-il dans la distribution normale ?), pas une partition.
Le cas d’usage industriel canonique est la maintenance predictive : une machine saine (capteurs : temperature, pression, vibration) opere dans un etroite ellipse de regimx ; une usure, une fuite ou un desequilibre la fait deriver hors de cette zone. On veut declencher une alerte avant la casse. ML.NET fournit le trainer Randomized PCA via mlContext.AnomalyDetection.Trainers.RandomizedPca.
L’intuition (Pfiefer et al., 2011 ; Lakhina et al., 2004) : la PCA projette les données normales sur un sous-espace de faible dimension (les k premières composantes). Un point normal se reconstruit bien dans ce sous-espace (faible residu) ; un point anormal, lui, s’etale hors du sous-espace et laisse un fort residu de reconstruction. Ce residu est le score d’anomalie : plus il est eleve, plus le point s’ecarte du regime appris.
Subtilite importante. Le trainer est non-supervise au Fit (il ignore toute etiquette), mais l’Evaluate a besoin d’une colonne
Label(colonnefloat, 1.0 = anomalie, 0.0 = normal) sur le jeu de test pour calculer l’AUC. On s’entraine donc sur des données normales et l’on evalue sur un jeu de test labellise melangeant normaux et anomalies.
Microsoft.ML 5.0.0 charge
On reference les espaces de noms ML.NET et System.Linq (pour Min, Max, Count sur les tableaux de scores).
Espaces de noms importes (Microsoft.ML + System.Linq)
Comme dans ML-1, on créé le MLContext — le point d’entree commun. On fixe la graine (seed: 42) pour rendre la PCA randomisee déterministe (sinon la projection stochastique varie d’une exécution a l’autre).
On simule trois capteurs d’une machine industrielle. Plutot que les valeurs brutes, on travaille avec les ecarts au point de fonctionnement nominal : c’est cette deviation qui est informative. Le regime sain est ainsi centre sur l’origine :
| Feature | Ecart-type (regime sain) | Regime sain (normal) | Regime defaillant (anomalie) |
|---|---|---|---|
| Temperature | 2.0 | ~0 (nominal) | surchauffe (~+5) |
| Pressure | 0.1 | ~0 (nominal) | surpression / fuite (~+0.8) |
| Vibration | 0.8 | ~0 (nominal) | fortes vibrations (~+2.5) |
On construit deux jeux distincts :
trainData (200 points normaux uniquement) — c’est le regime que le modèle apprend. Le Label vaut 0.0 partout et le trainer l’ignore au Fit.testData (120 points normaux + 40 anomalies, labellises) — sert a evaluer l’AUC et a tuner le seuil.Les anomalies sont volontairement moderees (2,5 a 8 ecarts-types selon le capteur, pic de pression a 8 ecarts-types) afin que la detection reste non-triviale : un peu de chevauchement subsiste, et le choix du seuil aura un vrai cout.
Definissons d’abord les classes de données ML.NET et un utilitaire gaussien (Box-Muller).
public class SensorData
{
public float Temperature { get; set; } // capteur 1 (deg C)
public float Pressure { get; set; } // capteur 2 (bar)
public float Vibration { get; set; } // capteur 3 (mm/s)
public float Label { get; set; } // 1.0 = anomalie, 0.0 = normal (ignore au Fit, utilise par Evaluate)
}
public class AnomalyPrediction
{
[ColumnName("Score")]
public float Score { get; set; } // score d'anomalie = residu de reconstruction PCA
[ColumnName("PredictedLabel")]
public bool PredictedLabel { get; set; } // true = anomalie (score > seuil calibre au Fit)
}
public static class RandUtil
{
static readonly Random _r = new Random(42);
// Box-Muller : echantillonne un flottant selon N(mean, std)
public static float NextGaussian(float mean, float std)
{
double u1 = 1.0 - _r.NextDouble();
double u2 = 1.0 - _r.NextDouble();
double z = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2);
return (float)(mean + z * std);
}
}
Console.WriteLine("Classes SensorData / AnomalyPrediction / RandUtil definies");Classes SensorData / AnomalyPrediction / RandUtil definies
On genere maintenant les deux jeux. Le regime sain centre les trois ecarts autour de 0 (machine reglee au nominal) avec un bruit gaussien realiste. La pression est une variable etroitement regulate (faible ecart-type) ; les anomalies simulent une fuite ou un desequilibre qui la fait deriver fortement (pic de ~8 ecarts-types), accompagne d’une surchauffe et de vibrations. Ce decalage orthogonal au plan de variance normale est précisément ce que la PCA detecte.
// === Regime NORMAL (machine saine) : ecarts au point nominal, centres sur 0 ===
// 200 points d'entrainement (tous normaux) + 120 points de test normaux
var trainNormal = new List<SensorData>();
var testNormal = new List<SensorData>();
for (int i = 0; i < 200; i++)
trainNormal.Add(new SensorData {
Temperature = RandUtil.NextGaussian(0f, 2.0f),
Pressure = RandUtil.NextGaussian(0f, 0.1f),
Vibration = RandUtil.NextGaussian(0f, 0.8f)
});
for (int i = 0; i < 120; i++)
testNormal.Add(new SensorData {
Temperature = RandUtil.NextGaussian(0f, 2.0f),
Pressure = RandUtil.NextGaussian(0f, 0.1f),
Vibration = RandUtil.NextGaussian(0f, 0.8f)
});
// === ANOMALIES (machine defaillante, 2,5 a 8 ecarts-types selon le capteur) ===
var testAnomalies = new List<SensorData>();
for (int i = 0; i < 40; i++)
testAnomalies.Add(new SensorData {
Temperature = RandUtil.NextGaussian(5.0f, 2.0f),
Pressure = RandUtil.NextGaussian(0.8f, 0.1f),
Vibration = RandUtil.NextGaussian(2.5f, 0.8f),
Label = 1.0f // <-- etiquette de verite, utilisee uniquement par Evaluate
});
var testDataRows = testNormal.Concat(testAnomalies).ToList();
Console.WriteLine($"Train : {trainNormal.Count} points (tous normaux)");
Console.WriteLine($"Test : {testNormal.Count} normaux + {testAnomalies.Count} anomalies = {testDataRows.Count} points");Train : 200 points (tous normaux)
Test : 120 normaux + 40 anomalies = 160 points
On charge les deux jeux dans des IDataView, comme en ML-1 et ML-8.
IDataView charges : trainData (normal) + testData (normal + anomalies labellises)
Le pipeline a deux étapes :
"Features",2.Pourquoi PAS de normalisation MinMax ici, contrairement a ML-8 ? K-Means mesure des distances euclidiennes et exige que les features soient a la même echelle. La detection d’anomalies par PCA, elle, exploite justement la structure de variance : les composantes principales sont définies par les directions de plus grande variance. Normaliser ecraserait cette structure. C’est une différence fondamentale entre les deux familles non-supervisees.
Pourquoi
rank: 2? Avec 3 features, la PCA de rang 2 projette les données sur un plan ; le score d’anomalie est la composante hors-plan (le residu le long de la 3e direction). Si l’on choisissaitrank: 3(le maximum), tout serait explique et le residu serait nul : la detection deviendrait impossible. Le rang contrôle donc la sensibilite du detecteur (cf. Exercice 1). C’est un piege frequent : le rang par defaut est 20, qui planterait ici avec seulement 3 features.
Nos données sont déjà recentrees sur l’origine (ecarts au point nominal) : la PCA passe donc par le centroid, condition essentielle pour que le residu reflete bien l’ecart au regime normal.
var pipeline = mlContext.Transforms
.Concatenate("Features", nameof(SensorData.Temperature), nameof(SensorData.Pressure), nameof(SensorData.Vibration))
.Append(mlContext.AnomalyDetection.Trainers.RandomizedPca(featureColumnName: "Features", rank: 2));
Console.WriteLine("Pipeline cree : Concatenate -> RandomizedPca(rank=2)");Pipeline cree : Concatenate -> RandomizedPca(rank=2)
On entraine le modèle sur trainData (données normales uniquement) via Fit. Le modèle apprend ainsi la geometrie du regime sain ; il n’a jamais vu d’anomalies.
Contrairement au clustering (ML-8), la detection d’anomalies se laisse evaluer des lors que le jeu de test est labellise. On dispose de deux metriques complementaires :
AreaUnderRocCurve (AUC, area under the ROC curve) : probabilite qu’un point anormal tire au hasard ait un score plus eleve qu’un point normal tire au hasard. 1.0 = separation parfaite ; 0.5 = hasard.DetectionRateAtFalsePositiveCount : parmi les vraies anomalies, quelle fraction est detectee avant que le modèle ne produise K fausses alarmes (par defaut, K = 10). C’est la metrique operationnelle : a quel point mon detecteur est-il utile a faible taux de fausse alarme ?On obtient ces metriques via mlContext.AnomalyDetection.Evaluate, qui lit la colonne Label (verite terrain) et la colonne Score (sortie du modèle).
var scoredTest = model.Transform(testData);
var metrics = mlContext.AnomalyDetection.Evaluate(scoredTest, labelColumnName: "Label");
Console.WriteLine("=== Metriques de detection d'anomalies (rank=2) ===");
Console.WriteLine($" AreaUnderRocCurve (AUC) : {metrics.AreaUnderRocCurve:F4}");
Console.WriteLine($" DetectionRate @ 10 faux positifs : {metrics.DetectionRateAtFalsePositiveCount:F4}");=== Metriques de detection d'anomalies (rank=2) ===
AreaUnderRocCurve (AUC) : 0,8477
DetectionRate @ 10 faux positifs : 0,2250
| Metrique | Valeur | Lecture |
|---|---|---|
| AreaUnderRocCurve (AUC) | 0,848 | bonne separation (1,0 = parfait, 0,5 = hasard) |
| DetectionRate @ 10 FP | 0,225 | fraction des anomalies detectee avant 10 fausses alarmes |
Un AUC de 0,85 signale une bonne separation : un point anormal tire au hasard a ~85 % de chances d’etre score plus haut qu’un point normal. Ce n’est pas 1,0 car le chevauchement subsiste — une anomalie dont le pic de pression est modere ressemble, dans le sous-espace PCA, a un point normal bruite. Le Detection Rate @ 10 FP (0,225) traduit cette limite : a très faible budget de fausses alarmes, on ne rattrape qu’un quart des anomalies. Le sweep de seuil ci-dessous montrera comment on ameliore ce taux en acceptant plus de fausses alarmes.
On créé un PredictionEngine (comme en ML-1 et ML-8) pour inferer le score de nouveaux points. La prediction renvoie :
Score : le residu de reconstruction PCA (plus eleve = plus anomalous),PredictedLabel : un booléen, true si Score depasse un seuil calibre pendant le Fit (par defaut, le trainer suppose ~10 % de contamination).Avertissement sur le seuil par defaut. Le seuil embarque est calibre sur une hypothese de contamination (~10 %). Si votre jeu d’entrainement est integrallement normal (ce qui est l’ideal), ce seuil est trop bas : le modèle flagge les ~10 % de normaux les plus extremes. C’est pourquoi l’Exercice 2 vous fera tuner ce seuil vous-même — le
Score, lui, est fiable.
var engine = mlContext.Model.CreatePredictionEngine<SensorData, AnomalyPrediction>(model);
SensorData[] testPoints = new[] {
new SensorData { Temperature = 0f, Pressure = 0.0f, Vibration = 0.0f }, // profil sain (au nominal)
new SensorData { Temperature = 3f, Pressure = 0.2f, Vibration = 1.0f }, // legerement off
new SensorData { Temperature = 5f, Pressure = 0.8f, Vibration = 2.5f }, // profil defaillant (fuite)
};
Console.WriteLine("=== Score d'anomalie de nouveaux points ===");
foreach (var s in testPoints)
{
var p = engine.Predict(s);
string flag = p.PredictedLabel ? "*** ANOMALIE ***" : "normal";
Console.WriteLine($" T={s.Temperature,4:F1} P={s.Pressure,4:F2} V={s.Vibration,4:F1} -> Score={p.Score,8:F4} [{flag}]");
}=== Score d'anomalie de nouveaux points ===
T= 0,0 P=0,00 V= 0,0 -> Score= 0,1268 [normal]
T= 3,0 P=0,20 V= 1,0 -> Score= 0,0644 [normal]
T= 5,0 P=0,80 V= 2,5 -> Score= 0,1432 [normal]
Les scores sont proches (0,06 a 0,14) et les trois points sont flagges normaux — le seuil par defaut (calibre pour ~10 % de contamination) est trop conservateur quand on s’entraine sur un jeu integralement normal. Deux points pedagogiques :
PredictedLabel par defaut.Le cas ci-dessus (pic de pression a 8 ecarts-types) est unidimensionnel : un simple seuil sur Pressure seul le detecte, et la PCA n’apporte rien de plus (son AUC de 0,85 est même inferieure a ce que donnerait un seuil direct sur la pression). Ou la detection d’anomalies par PCA brille-t-elle vraiment ?
Sur des anomalies multivariees subtiles : des points ou chaque capteur pris isolement est dans la norme, mais dont la combinaison est anormale. Aucun seuil par capteur isole ne les voit ; seul le residu hors-sous-espace de la PCA les isole. C’est précisément la capacite distinctive que la demo principale (et l’Exercice 3) ne montrait pas — l’Exercice 3 explorait seulement des decalages moyens reduits, qui restent unidimensionnels.
On reconstruit un jeu ou les trois capteurs sont physiquement couples en regime sain : la vibration du palier reflete a la fois la temperature et la pression, Vibration = 0,4 * Temperature + 0,5 * Pressure. Les points sains vivent donc sur un plan 2D dans l’espace 3D des capteurs ; la troisieme direction (orthogonale au plan) a une variance quasi nulle — c’est elle que la PCA de rang 2 laisse de cote. Les anomalies violent ce couplage : Temperature et Pressure restent dans leurs gammes normales, mais la Vibration s’ecarte de la relation predite. Ces points sortent du plan sain -> residu eleve.
// === Redemption : anomalies MULTIVARIEES subtiles (couplage V=0.4T+0.5P brise) ===
// Regime sain : Vibration = 0.4*Temperature + 0.5*Pressure + bruit (les 3 capteurs
// sont couples -> les points sains vivent sur un plan 2D, PC3 ~ 0).
// Anomalies : T et P dans leurs memes marginales, MAIS Vibration = couplage + rupture.
var trainMv = new List<SensorData>();
var testMvNormal = new List<SensorData>();
for (int i = 0; i < 200; i++) {
float T = RandUtil.NextGaussian(0f, 2.0f);
float P = RandUtil.NextGaussian(0f, 0.1f);
float V = 0.4f * T + 0.5f * P + RandUtil.NextGaussian(0f, 0.05f);
trainMv.Add(new SensorData { Temperature = T, Pressure = P, Vibration = V });
}
for (int i = 0; i < 120; i++) {
float T = RandUtil.NextGaussian(0f, 2.0f);
float P = RandUtil.NextGaussian(0f, 0.1f);
float V = 0.4f * T + 0.5f * P + RandUtil.NextGaussian(0f, 0.05f);
testMvNormal.Add(new SensorData { Temperature = T, Pressure = P, Vibration = V });
}
var testMvAnom = new List<SensorData>();
for (int i = 0; i < 40; i++) {
float T = RandUtil.NextGaussian(0f, 2.0f); // meme marginale que le normal
float P = RandUtil.NextGaussian(0f, 0.1f); // meme marginale
float V = 0.4f * T + 0.5f * P + RandUtil.NextGaussian(0f, 0.05f) // couplage nominal
+ RandUtil.NextGaussian(0f, 1.0f); // + RUPTURE (couplage brise)
testMvAnom.Add(new SensorData { Temperature = T, Pressure = P, Vibration = V, Label = 1.0f });
}
var testMv = testMvNormal.Concat(testMvAnom).ToList();
// AUC = P(score_anomalie > score_normal) via Mann-Whitney sur le jeu de test.
static double AucMv<T>(IEnumerable<(T score, bool isAnom)> pts) where T : IComparable<T> {
var a = pts.Where(p => p.isAnom).Select(p => p.score).ToList();
var n = pts.Where(p => !p.isAnom).Select(p => p.score).ToList();
if (a.Count == 0 || n.Count == 0) return 0.5;
double wins = 0;
foreach (var av in a) foreach (var nv in n) { int c = av.CompareTo(nv); if (c > 0) wins += 1; else if (c == 0) wins += 0.5; }
return wins / (a.Count * (double)n.Count);
}
// MEME pipeline que la demo principale : Concatenate -> RandomizedPca(rank=2).
var trainMvDv = mlContext.Data.LoadFromEnumerable(trainMv);
var testMvDv = mlContext.Data.LoadFromEnumerable(testMv);
var pipeMv = mlContext.Transforms
.Concatenate("Features", nameof(SensorData.Temperature), nameof(SensorData.Pressure), nameof(SensorData.Vibration))
.Append(mlContext.AnomalyDetection.Trainers.RandomizedPca(featureColumnName: "Features", rank: 2));
var modelMv = pipeMv.Fit(trainMvDv);
var scoredMv = modelMv.Transform(testMvDv);
var scoresMv = scoredMv.GetColumn<float>("Score").ToArray();
var labelsMv = scoredMv.GetColumn<float>("Label").ToArray();
double aucPcaMv = AucMv(scoresMv.Zip(labelsMv, (s, l) => (score: (double)s, isAnom: l >= 0.5f)));
// Baselines triviales : un seuil par capteur isole (valeur signee, puis |ecart a la moyenne|).
double mT = trainMv.Average(x => x.Temperature);
double mV = trainMv.Average(x => x.Vibration);
double mP = trainMv.Average(x => x.Pressure);
double aucT = AucMv(testMv.Select(x => (score: (double)x.Temperature, isAnom: x.Label >= 0.5f)));
double aucV = AucMv(testMv.Select(x => (score: (double)x.Vibration, isAnom: x.Label >= 0.5f)));
double aucP = AucMv(testMv.Select(x => (score: (double)x.Pressure, isAnom: x.Label >= 0.5f)));
double aucAbsT = AucMv(testMv.Select(x => (score: Math.Abs(x.Temperature - mT), isAnom: x.Label >= 0.5f)));
double aucAbsV = AucMv(testMv.Select(x => (score: Math.Abs(x.Vibration - mV), isAnom: x.Label >= 0.5f)));
double bestUnivar = new[] { aucT, aucV, aucP, aucAbsT, aucAbsV }.Max();
Console.WriteLine("=== Anomalies multivariees subtiles (couplage V=0.4T+0.5P brise) ===");
Console.WriteLine($" AUC PCA rank=2 : {aucPcaMv:F3}");
Console.WriteLine($" AUC seuil Temperature seul : {aucT:F3} (|ecart| {aucAbsT:F3})");
Console.WriteLine($" AUC seuil Vibration seul : {aucV:F3} (|ecart| {aucAbsV:F3})");
Console.WriteLine($" AUC seuil Pressure seul : {aucP:F3}");
Console.WriteLine($" Meilleur seuil univarie : {bestUnivar:F3}");
Console.WriteLine($" Avantage PCA : +{aucPcaMv - bestUnivar:F3}");=== Anomalies multivariees subtiles (couplage V=0.4T+0.5P brise) ===
AUC PCA rank=2 : 0,945
AUC seuil Temperature seul : 0,464 (|ecart| 0,443)
AUC seuil Vibration seul : 0,460 (|ecart| 0,646)
AUC seuil Pressure seul : 0,514
Meilleur seuil univarie : 0,646
Avantage PCA : +0,299
| Méthode | AUC | Lecture |
|---|---|---|
Seuil Temperature seul |
~0,5 | aucun signal (même marginale que le normal) |
Seuil Vibration seul |
~0,5-0,6 | la rupture est a moyenne nulle : la Vibration drift des deux cotes |
Seuil Pressure seul |
~0,5 | aucun signal |
| PCA rank=2 (residu hors-plan) | ~0,95 | detecte la violation du couplage |
Chaque capteur isole est a peu pres au hasard (~0,5-0,6), parce que les anomalies ont ete construites avec les mêmes distributions marginales que le regime sain. Pourtant la PCA atteint ~0,95 : elle a appris le plan de couplage sain, et les points qui le violent sortent du plan (residu eleve le long de la 3e composante). C’est la capacite que aucune méthode univariee ne peut reproduire : la detection d’anomalies par PCA est un detecteur de structure multivariee, pas de seuils par capteur.
Contraste avec la demo principale : sur le pic de pression a 8 sigma, la PCA etait superflue (un seuil sur Pressure suffisait, et battait même la PCA). Ici, la PCA est le seul outil qui marche. Les deux cas ensemble montrent quand la PCA est justifiee : non pas sur des pics unidimensionnels qu’un seuil capte, mais sur des anomalies relationnelles que seule la geometrie du sous-espace revelle. C’est le cœur de la Lakhina et al. (2004) — detecter les anomalies qui resident dans le sous-espace residual, invisibles feature par feature.
Le score brut est une grandeur continue ; la decision operationnelle (alerte ou non) exige un seuil. Ce seuil arbitre entre deux erreurs :
Abaisser le seuil augmente le TPR (on rate moins d’anomalies) mais augmente aussi le FPR (plus de fausses alarmes). Le bon seuil depend du cout relatif d’une casse ratee vs d’une intervention inutile.
Plutot que de tester des seuils absolus (dont l’echelle depend des données), on balaie des seuils relatifs repartis entre le min et le max des scores observes. Pour chacun, on calcule TPR, FPR et precision.
var scored = model.Transform(testData);
var scores = scored.GetColumn<float>("Score").ToArray();
var labels = scored.GetColumn<float>("Label").ToArray();
int totalAnom = labels.Count(l => l >= 0.5f);
int totalNorm = labels.Length - totalAnom;
float smin = scores.Min(), smax = scores.Max();
Console.WriteLine($"=== Trade-off seuil : TPR (detection) vs FPR (fausse alarme) ===");
Console.WriteLine($" ({totalAnom} vraies anomalies, {totalNorm} vrais normaux ; scores de {smin:F3} a {smax:F3})");
Console.WriteLine($" Seuil | TPR(detect) | FPR(fausse alarme) | Precision");
Console.WriteLine($" ------+-------------+--------------------+----------");
for (int t = 1; t <= 9; t++)
{
float thr = smin + (smax - smin) * t / 10f;
int tp = 0, fp = 0;
for (int i = 0; i < scores.Length; i++)
{
bool flagged = scores[i] > thr;
if (flagged && labels[i] >= 0.5f) tp++;
else if (flagged && labels[i] < 0.5f) fp++;
}
double tpr = totalAnom > 0 ? (double)tp / totalAnom : 0;
double fpr = totalNorm > 0 ? (double)fp / totalNorm : 0;
double prec = (tp + fp) > 0 ? (double)tp / (tp + fp) : 0;
Console.WriteLine($" {thr,5:F2} | {tpr,11:F2} | {fpr,18:F2} | {prec,10:F2}");
}=== Trade-off seuil : TPR (detection) vs FPR (fausse alarme) ===
(40 vraies anomalies, 120 vrais normaux ; scores de 0,001 a 0,513)
Seuil | TPR(detect) | FPR(fausse alarme) | Precision
------+-------------+--------------------+----------
0,05 | 1,00 | 0,41 | 0,45
0,10 | 0,93 | 0,23 | 0,58
0,15 | 0,38 | 0,13 | 0,48
0,21 | 0,12 | 0,05 | 0,45
0,26 | 0,03 | 0,03 | 0,20
0,31 | 0,03 | 0,01 | 0,50
0,36 | 0,03 | 0,01 | 0,50
0,41 | 0,03 | 0,01 | 0,50
0,46 | 0,03 | 0,00 | 1,00
Le sweep dessine la courbe ROC de facon lisible :
| Seuil | TPR (detection) | FPR (fausse alarme) | Precision | Lecture |
|---|---|---|---|---|
| 0,05 | 1,00 | 0,41 | 0,45 | on rattrape tout, mais 41 % de fausses alarmes |
| 0,10 | 0,93 | 0,23 | 0,58 | bon compromis operationnel |
| 0,21 | 0,12 | 0,05 | 0,45 | FPR < 5 %, mais on rate 88 % des anomalies |
| 0,46 | 0,03 | 0,00 | 1,00 | précis mais quasi inutile (3 % de detection) |
Le seuil 0,10 est un point de fonctionnement raisonnable : on detecte 93 % des anomalies pour 23 % de fausses alarmes. Si le metier exige FPR <= 5 %, il faut monter a ~0,21 — mais on accepte alors de rater la plupart des anomalies. Ce compromis n’a pas de reponse universelle : il depend du cout relatif d’une casse ratee (très eleve en maintenance predictive) face au cout d’une intervention inutile. L’Exercice 2 formalise cette accord sur une contrainte TPR >= 0,95.
Le rang de la PCA contrôle la sensibilite du detecteur. Avec 3 features :
rank: 1 -> sous-espace 1D, residu sur 2 dimensions (detecteur très sensible : beaucoup de normaux flagges),rank: 2 -> sous-espace plan, residu sur 1 dimension (compromis utilise dans le notebook),rank: 3 -> tout est explique, residu nul (aucune detection possible).Objectifs : 1. Boucler sur rank = 1, 2, 3, entrainer un RandomizedPca pour chaque valeur 2. Evaluer l’AUC sur testData 3. Verifier que rank: 3 donne un AUC ~0.5 (aucun pouvoir de detection) et comparer 1 vs 2
Indice : recopiez le pipeline ci-dessus en parametrant rank. Pour rank: 3, les scores sont tous quasi nuls : la PCA a tout explique, il ne reste plus de residu a mesurer.
// Exercice 1 : Effet du rang PCA sur l'AUC
// TODO etudiant : boucle sur rank = 1, 2, 3, entraine un RandomizedPca pour chaque, evalue l'AUC.
// Indice : recopie le pipeline ci-dessus en parametrant rank. Pour rank:3, les scores sont ~0
// (la PCA a tout explique) -> AUC ~0.5 (aucun pouvoir de detection).
// Etape 1 : pour rank dans {1, 2, 3}, construis le pipeline Concatenate -> NormalizeMinMax -> RandomizedPca(rank)
// Etape 2 : entraine sur trainData, transforme testData
// Etape 3 : evalue l'AUC (mlContext.AnomalyDetection.Evaluate)
// Etape 4 : affiche rank | AUC et conclus sur le compromis sensibilite / pouvoir discriminant
Console.WriteLine("Exercice 1 a completer");Exercice 1 a completer
On veut un detecteur qui rate au plus 5 % des anomalies (TPR >= 0.95) tout en minimisant les fausses alarmes. Le sweep ci-dessus est trop grossier (9 seuils reguliers).
Objectifs : 1. Calculer une courbe ROC fine : 50 seuils repartis entre smin et smax, TPR et FPR pour chacun 2. Identifier le seuil qui realise TPR >= 0.95 avec FPR minimal 3. Reporter ce seuil operationnel et sa precision
Indice : même boucle que le sweep, mais avec 50 seuils ; parcourir les couples (TPR, FPR) et garder le premier seuil (du plus eleve au plus bas) qui atteint TPR >= 0.95.
// Exercice 2 : Accorder le seuil a un cout operationnel
// TODO etudiant : courbe ROC fine (50 seuils), trouver le seuil a TPR >= 0.95 et FPR minimal.
// TPR = TP / totalAnom ; FPR = FP / totalNorm
// Indice : 50 seuils entre smin et smax ; garde le seuil (le plus eleve d'abord) qui atteint TPR>=0.95.
// Etape 1 : boucle 50 seuils, calcule TPR + FPR pour chacun
// Etape 2 : cherche le seuil realisant TPR >= 0.95 avec FPR minimal
// Etape 3 : affiche le seuil operationnel, son FPR et sa precision
Console.WriteLine("Exercice 2 a completer");Exercice 2 a completer
Les anomalies actuelles sont decalees de 2,5 a 8 ecarts-types selon le capteur : faciles a detecter. Que se passe-t-il avec des anomalies subtiles (~1.5 sigma), qui se rapprochent du regime normal ?
Objectifs : 1. Regenerer testAnomalies avec un decalage reduit (ex. Temperature ~ N(2, 2), Pressure ~ N(0.25, 0.1), Vibration ~ N(1.0, 0.8)) 2. Reconstituer testData, re-evaluer l’AUC du modèle (sans re-entrainer) 3. Constater la chute de l’AUC et conclure sur la limite de la detection PCA
Indice : plus les anomalies se rapprochent du regime normal, plus elles entrent dans le nuage appris et plus leur residu ressemble a celui d’un point normal. L’AUC chute vers 0.5. La PCA ne detecte bien que les anomalies orthogonales au sous-espace normal ; les anomalies « inline » (le long d’une direction normale) lui echappent.
// Exercice 3 : Anomalies subtiles (limite de l'approche PCA)
// TODO etudiant : regenere des anomalies subtiles (~1.5 sigma) et re-evalue l'AUC.
// Indice : anomalies plus proches du regime normal -> residu ressemblant a un point normal -> AUC chute.
// La PCA ne detecte bien que les anomalies ORTHOGONALES au sous-espace normal (ici : un pic de pression).
// Etape 1 : regenere testAnomalies avec decalage reduit (Temperature~2, Pressure~0.25, Vibration~1.0)
// Etape 2 : reconstitue testData, re-evalue l'AUC du modele existant (sans re-fit)
// Etape 3 : compare l'AUC a la valeur du notebook et conclus sur la limite de la detection PCA
Console.WriteLine("Exercice 3 a completer");Exercice 3 a completer
Ce notebook a introduit la detection d’anomalies non-supervisee avec Randomized PCA :
| Concept | Description |
|---|---|
| Detection d’anomalies | Apprentissage non-supervise : on apprend le regime normal, on teste si un point s’en ecarte |
AnomalyDetectionCatalog |
Catalogue ML.NET pour Randomized PCA (mlContext.AnomalyDetection.Trainers.RandomizedPca) |
| Score d’anomalie | Residu de reconstruction dans le sous-espace PCA (plus eleve = plus anomalous) |
rank |
Dimension du sous-espace ; contrôle la sensibilite (rank=n_features -> aucune detection) |
| AUC | Separation normaux/anormaux (1.0 = parfait, 0.5 = hasard) |
| DetectionRate@K FP | Fraction des anomalies detectee avant K fausses alarmes (metrique operationnelle) |
| Seuil de decision | Arbitre TPR vs FPR ; s’accorde au cout operationnel, pas au seuil par defaut |
Points cles :
rank ne peut pas depasser le nombre de features, et rank = n_features annule toute detection (residu nul). Le defaut (20) planterait ici.Algorithme (PCA pour la detection d’anomalies)
Metriques d’evaluation (ROC et detection rate)
Cas d’usage (maintenance predictive)