Un réseau de neurones entraîné à recopier son entrée en la faisant passer par un goulot étroit. Pour y parvenir, il doit apprendre une version compressée des données normales. Ce qui s'en écarte est mal reconstruit : l'erreur de reconstruction devient un score d'anomalie.
Un copiste qui a reproduit des milliers de formulaires : il recopie sans effort un chiffre bien écrit, mais bute sur une tache d'encre qui ne ressemble à rien de ce qu'il connaît.
Les 64 pixels d'une image traversent des couches de neurones de plus en plus étroites, jusqu'à un goulot de 8 nombres : le code.
Des couches symétriques repartent de ces 8 nombres pour redessiner les 64 pixels. On entraîne le réseau pour que la sortie ressemble le plus possible à l'entrée.
Une image semblable à celles de l'entraînement est bien reconstruite. Une image d'un autre type laisse un grand écart entre entrée et sortie. On trie les données par erreur décroissante.
1 797 chiffres manuscrits en 8 x 8 pixels servent d'exemples normaux. On y ajoute 20 scans ratés simulés, des taches aléatoires, pour vérifier que le réseau les repère sans les avoir jamais vus.
Chaque scan reçoit une erreur de reconstruction. Sur le jeu d'exemple, l'erreur médiane des scans ratés est environ 3 fois celle des vrais chiffres, et 19 des 20 scans ratés figurent parmi les 30 pires. L'opérateur ne contrôle que cette file.
On fixe la taille de la file selon la capacité de contrôle, puis on mesure la part de vrais problèmes qu'elle contient. Sans anomalies étiquetées, on en simule, comme ici, ou on fait vérifier un échantillon de la file à la main.
Noms donnés pour R (nnet, une seule couche cachée) et Python (scikit-learn). Pour des réseaux plus profonds : keras3 ou torch en R, Keras ou PyTorch en Python.
La taille du goulot. Trop large, le réseau recopie tout, anomalies comprises. Trop étroit, il reconstruit mal même les données normales. Ici, 8 nombres pour 64 pixels.
On entraîne sur des données aussi propres que possible : l'historique validé, sans les anomalies connues. C'est ce qui rend l'erreur de reconstruction informative.
Pénalité sur les poids du réseau, qui limite le surapprentissage. Une petite valeur, comme 1e-4 (défaut de scikit-learn), suffit souvent.
Nombre d'itérations d'entraînement. On vérifie que l'erreur ne baisse plus ; si le package signale une non-convergence, on augmente.
# Scans de formulaires : autoencodeur en R
library(nnet)
chiffres <- read.csv("chiffres_manuscrits.csv")
X <- as.matrix(chiffres[, names(chiffres) != "chiffre"]) / 16 # pixels entre 0 et 1
set.seed(42)
# 20 scans ratés simulés (taches aléatoires) pour contrôler la détection
rates <- matrix(runif(20 * 64) * (runif(20 * 64) < 0.3), nrow = 20)
X_tout <- rbind(X, rates)
# Autoencodeur à une couche cachée de 8 neurones : l'entrée sert aussi de sortie
ae <- nnet(X, X, size = 8, linout = TRUE, decay = 1e-4, maxit = 500, MaxNWts = 2000, trace = FALSE)
# Erreur de reconstruction : un scan mal reconstruit est atypique
erreur <- rowMeans((predict(ae, X_tout) - X_tout)^2)
n <- nrow(X)
cat("Erreur médiane, vrais chiffres :", round(median(erreur[1:n]), 4), "\n")
cat("Erreur médiane, scans ratés :", round(median(erreur[-(1:n)]), 4), "\n")
cat("Scans ratés parmi les 30 pires :", sum(order(erreur, decreasing = TRUE)[1:30] > n), "sur 20\n")
# Scans de formulaires : autoencodeur en Python
import numpy as np
import pandas as pd
from sklearn.neural_network import MLPRegressor
chiffres = pd.read_csv("chiffres_manuscrits.csv")
X = chiffres.drop(columns="chiffre").values / 16 # pixels ramenés entre 0 et 1
# 20 scans ratés simulés (taches aléatoires) pour contrôler la détection
rng = np.random.default_rng(42)
rates = rng.uniform(0, 1, size=(20, 64)) * (rng.uniform(size=(20, 64)) < 0.3)
X_tout = np.vstack([X, rates])
# Goulot de 8 neurones : 64 pixels compressés en 8 nombres, puis reconstruits
ae = MLPRegressor(hidden_layer_sizes=(32, 8, 32), max_iter=500, random_state=42)
ae.fit(X, X) # entrée = sortie : le réseau apprend à recopier les vrais chiffres
# Erreur de reconstruction : un scan mal reconstruit est atypique
erreur = ((ae.predict(X_tout) - X_tout) ** 2).mean(axis=1)
print("Erreur médiane, vrais chiffres :", round(np.median(erreur[:-20]), 4))
print("Erreur médiane, scans ratés :", round(np.median(erreur[-20:]), 4))
top = np.argsort(erreur)[::-1][:30]
print("Scans ratés parmi les 30 pires :", int((top >= len(X)).sum()), "sur 20")
Il est entraîné à reconstruire des données normales. Face à une donnée inhabituelle, il la reconstruit mal, faute d'avoir appris sa structure. On calcule l'erreur de reconstruction de chaque ligne et on examine d'abord les plus grandes.
Les deux compressent les données en quelques dimensions. L'ACP ne trouve que des combinaisons linéaires ; l'autoencodeur, grâce à ses fonctions d'activation non linéaires, peut apprendre des structures courbes. En contrepartie, il demande plus de données, de réglages et de temps de calcul.
Non. Il suffit d'exemples majoritairement normaux. Quelques anomalies étiquetées restent utiles pour évaluer la détection et fixer le seuil d'alerte, mais elles ne servent pas à l'entraînement.
Un autoencodeur linéaire entraîné aux moindres carrés retrouve le sous-espace de l'ACP. L'ACP est plus rapide et reproductible.
Voir la fiche → plus simple sur tableauxDétecte les anomalies dans des données tabulaires sans réseau de neurones ni architecture à régler.
Voir la fiche → la version générativeUn autoencodeur probabiliste, capable aussi de générer de nouveaux exemples réalistes.
Voir la fiche →Dataistudio forme les équipes au machine learning et à l'IA, sur des cas concrets.
Nous utilisons des cookies de mesure d'audience et de suivi publicitaire pour comprendre la fréquentation du site et l'efficacité de nos annonces. Rien n'est déposé sans votre accord. En savoir plus