Local Outlier Factor (LOF) : Guide Complet — Principes, Exemples et Implémentation Python

Local Outlier Factor (LOF) : Guide Complet — Principes, Exemples et Implémentation Python

Local Outlier Factor (LOF) : Guide complet — Principes, Exemples et Implémentation Python

Résumé — Le Local Outlier Factor (LOF) est un algorithme de détection d’anomalies basé sur la densité locale relative. Contrairement aux méthodes globales comme l’Elliptic Envelope, le LOF identifie les anomalies en comparant la densité de chaque point à celle de ses voisins. Proposé par Breunig et al. en 2000, il excelle dans les situations où les clusters ont des densités très hétérogènes.


Principe mathématique

Le LOF repose sur quatre concepts imbriqués qui mesurent la localité de chaque point :

1. k-distance : La k-distance d’un point p est la distance au k-ième voisin le plus proche. Tous les points à une distance inférieure ou égale sont les voisins de p, notés $N_k(p)$.

2. Reachability Distance : La reachability distance entre $p$ et $o$ est le maximum entre la k-distance de $o$ et la distance euclidienne entre $p$ et $o$ :

$$reachdist_k(p, o) = \max(k\text{-distance}(o), d(p, o))$$

Cette astuce est cruciale : elle stabilise les distances à l’intérieur d’un cluster (tous les points proches ont la même reachability distance, celle de la k-distance du centre), ce qui rend la comparaison de densité plus robuste.

3. Local Reachability Density (LRD) : La densité locale d’un point $p$ est l’inverse de la moyenne des reachability distances entre $p$ et ses $k$ voisins :

$$LRD(p) = \frac{1}{\frac{1}{|N_k(p)|} \sum_{o \in N_k(p)} reachdist_k(p, o)}$$

Un LRD élevé signifie que $p$ est dans une zone dense (ses voisins sont proches), tandis qu’un LRD faible indique un environnement isolé.

4. Local Outlier Factor : Le score LOF final est le ratio moyen entre la LRD des voisins et la LRD du point lui-même :

$$LOF(p) = \frac{1}{|N_k(p)|} \sum_{o \in N_k(p)} \frac{LRD(o)}{LRD(p)}$$

Interprétation :
– $LOF \approx 1$ : le point a une densité comparable à celle de ses voisins → normal
– $LOF \gg 1$ : le point est dans une zone beaucoup moins dense que ses voisins → anomalie
– $LOF < 1$ : le point est dans une zone plus dense que ses voisins → cœur de cluster


Intuition

Imaginez que vous cherchez votre chemin dans une grande ville. Dans le centre-ville, tout le monde est serré — la densité de personnes est très élevée. En banloieue, les gens sont plus dispersés.

Maintenant, imaginez un parisien au milieu du Sahara. Il n’est pas anormal parce qu’il est seul — il est anormal parce que la densité autour de lui est radicalement différente de ce qu’elle devrait être pour lui.

À l’inverse, imaginez un bédouin au milieu du Sahara. Il ne sera pas détecté comme anomalie, car la densité autour de lui est typique du lieu.

C’est toute la force du LOF : il ne mesure pas l’isolement absolu (comme le ferait Isolation Forest), mais l’isolement relatif au contexte local. Un point au milieu d’un cluster dense n’est pas suspect, mais un point à la frontière d’un cluster, là où la densité chute brutalement, le devient.

Comparaison avec les autres méthodes :
Isolation Forest : détecte les anomalies par la facilité d’isolation — efficace globalement mais rate les anomalies locales.
One-Class SVM : apprend une frontière globale — inadapté aux clusters de densités variées.
DBSCAN : classe les points comme bruit globalement — ne donne pas de score gradué.
LOF : mesure la densité relative locale — le seul qui détecte à la fois les anomalies globales et les anomalies locales subtiles.


Implémentation Python

Exemple 1 : Détection d’anomalies de base

import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import LocalOutlierFactor
from sklearn.datasets import make_blobs

# Données avec clusters de densités différentes
X1 = np.random.randn(300, 2) * 0.3          # cluster dense
X2 = np.random.randn(100, 2) * 0.8 + 3      # cluster diffus
X3 = np.array([[6, 1], [6.2, 0.8], [5.8, 1.2]])  # anomalies isolées
X = np.vstack([X1, X2, X3])

# LOF
lof = LocalOutlierFactor(n_neighbors=20, contamination='auto')
labels = lof.fit_predict(X)  # -1 = anomalie, 1 = normal
scores = lof.negative_outlier_factor_  # scores natifs (plus négatif = plus anormal)

# Visualisation
fig, ax = plt.subplots(figsize=(8, 6))
colors = ['red' if l == -1 else 'steelblue' for l in labels]
sizes = [150 if l == -1 else 30 for l in labels]
ax.scatter(X[:, 0], X[:, 1], c=colors, s=sizes, alpha=0.7,
           edgecolors='darkred' if any(l == -1 for l in labels) else 'none')
ax.set_title('LOF : Détection d\'anomalies locale')
ax.set_xlabel('Feature 1')
ax.set_ylabel('Feature 2')
ax.legend(['Normal', 'Anomalie'])
plt.tight_layout()
plt.savefig('lof_detection.png', dpi=150)
print(f"Anomalies détectées : {(labels == -1).sum()} sur {len(labels)} points")
print(f"Scores LOF min : {scores.min():.3f}, max : {scores.max():.3f}")

Exemple 2 : Mode novelty detection (détection de nouvelles anomalies)

from sklearn.neighbors import LocalOutlierFactor
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score

# Générer des données "normales" + quelques anomalies
X, y = make_classification(n_samples=1000, n_features=10, n_informative=5,
                           n_redundant=3, random_state=42)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# Apprentissage sur le mode novelty (supervised-like : on n'a vu que du "normal")
# Ici on utilise novelty=True pour pouvoir appeler predict() sur de nouvelles données
lof_novelty = LocalOutlierFactor(n_neighbors=20, novelty=True, contamination=0.1)
lof_novelty.fit(X_train)  # apprend la distribution des données d'entraînement

# Détection sur les données de test
predictions = lof_novelty.predict(X_test)
scores_test = lof_novelty.score_samples(X_test)  # score continu

# Évaluation
print(f"AUC-ROC : {roc_auc_score(y_test == 0, scores_test):.3f}")

# Matrice de confusion simplifiée
n_detected = (predictions == -1).sum()
n_total = len(predictions)
print(f"Anomalies détectées en novelty mode : {n_detected}/{n_total}")
print(f"Score range : [{scores_test.min():.3f}, {scores_test.max():.3f}]")

Exemple 3 : Visualisation des scores LOF en continu

import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import LocalOutlierFactor

# Grille de points
np.random.seed(42)
X_train = np.vstack([
    np.random.randn(150, 2) * 0.4,
    np.random.randn(80, 2) * 0.6 + [3, 2],
])

# LOF
lof = LocalOutlierFactor(n_neighbors=15, contamination=0.05)
lof.fit_predict(X_train)
scores = lof.negative_outlier_factor_

# Trier par score et visualiser
sorted_idx = np.argsort(scores)
plt.figure(figsize=(10, 3))
plt.bar(range(len(scores)), scores[sorted_idx], color='steelblue')
plt.axhline(y=np.mean(scores), color='red', linestyle='--', label='Moyenne')
plt.title('Distribution des scores LOF (négatif = anomaly)')
plt.xlabel('Points (triés par score)')
plt.ylabel('Score LOF (negative_outlier_factor)')
plt.legend()
plt.tight_layout()
plt.savefig('lof_scores_distribution.png', dpi=150)
print("Visualisation des scores LOF sauvegardée")

Hyperparamètres

Hyperparamètre Valeur défaut Description Recommandation
n_neighbors 20 Nombre de voisins pour estimer la densité locale 10-30; faible = détection locale fine, élevé = plus global
contamination ‘auto’ Proportion attendue d’anomalies ‘auto’ par défaut; fixer si on connaît le taux d’anomalies
metric ‘minkowski’ Métrique de distance entre points ‘euclidean’, ‘manhattan’, ou ‘cosine’ pour texte
p 2 Paramètre de la métrique de Minkowski 1 = Manhattan, 2 = Euclidien
novelty False Si True, permet d’appeler predict() sur de nouvelles données True pour la production, False pour l’exploration
algorithm ‘auto’ Algorithme de recherche des voisins ‘auto’ choisit entre ‘brute’, ‘kd_tree’, ‘ball_tree’
leaf_size 30 Taille des feuilles pour les arbres Baisser pour plus de précision, augmenter pour la vitesse

Avantages du LOF

  1. Détection locale : Identifie les anomalies dans des contextes de densité variable, là où les méthodes globales échouent.
  2. Score continu : Contrairement à DBSCAN qui assigne simplement « bruit » ou « cluster », le LOF fournit un score gradué qui permet de prioriser les investigations.
  3. Pas d’hypothèse de distribution : Aucune hypothèse sur la forme ou la distribution des données — le LOF fonctionne avec des clusters de n’importe quelle géométrie.
  4. Interprétabilité : Un score LOF de 2,5 se lit directement comme « ce point est 2,5 fois moins dense que ses voisins » — une métrique que les métiers comprennent sans formation en statistiques.
  5. Flexibilité des métriques : Support de nombreuses métriques de distance (euclidienne, Manhattan, cosine, Jaccard), adaptables au type de données.

Limites du LOF

  1. Coût computationnel : $O(n^2)$ en implémentation naïve, car il faut calculer les distances par paires. Des approximations existent mais sacrifient la précision.
  2. Choix de k critique : Le paramètre n_neighbors influence fortement les résultats. Un k trop petit donne des scores instables, un k trop grand dilue les anomalies locales.
  3. Mauvais en très haute dimension : Comme toutes les méthodes basées sur les distances, le LOF souffre de la malédiction de la dimension. Combiner avec une ACP en amont est souvent nécessaire.
  4. Pas de mode transform() natif : Sans novelty=True, on ne peut pas évaluer de nouvelles données sans recalculer sur l’ensemble. Même avec novelty=True, le calcul reste coûteux sur de grands jeux.
  5. Sensibilité aux paramètres : Contrairement à l’Isolation Forest qui est relativement robuste, le LOF nécessite un réglage soigneux de n_neighbors et contamination pour des résultats fiables.

4 cas d’usage concrets

1. Détection de fraude dans le secteur bancaire

En transactions financières, les fraudeurs adaptent constamment leurs techniques pour ressembler au comportement normal. Le LOF excelle ici car il détecte les anomalies locales : une transaction qui semble normale dans l’absolu (montant moyen, heure habituelle) mais qui est atypique par rapport aux transactions similaires du client. Par exemple, un achat électronique de 500 € peut être normal pour un client qui en fait régulièrement, mais anormal pour un client qui n’achète habituellement que des produits alimentaires à moins de 50 €.

2. Surveillance d’équipements industriels (maintenance prédictive)

Sur des plateformes pétrolières, les capteurs surveillent la vibration, la température, la pression, etc. Une légère montée de vibration peut être normale en elle-même, mais si elle survient dans un contexte où toutes les autres mesures sont à leur valeur minimale, le LOF la signale comme suspecte. Cette détection précoce permet d’intervenir avant la panne complète, évitant des arrêts de production coûteux.

3. Détection de maladies rares en santé

En analyse de résultats de laboratoire, un patient peut avoir des valeurs individuelles toutes dans la « plage normale » statistique, mais leur combinaison est inhabituelle. Le LOF détecte ce type d’anomalie multidimensionnelle — un profil où chaque mesure est normale isolément mais dont la combinaison est suspecte. C’est particulièrement utile pour dépister précocement des pathologies rares ou des interactions médicamenteuses.

4. Cybersécurité : détection d’intrusion réseau

Dans un réseau d’entreprise, les connexions « normales » varient selon le département, l’heure et l’utilisateur. Le LOF permet de détecter des comportements qui seraient dans la norme globale du réseau mais anormaux pour un contexte spécifique. Par exemple, un accès au serveur de comptabilité à 3h du matin par un développeur est une anomalie LOF : même si l’accès au serveur n’est pas interdit, il est inhabituel pour le profil de cet utilisateur à cette heure-là.


Bonnes pratiques pour le LOF

  • Normaliser les données : Comme toutes les méthodes basées sur les distances, le LOF est sensible aux échelles. StandardScaler est indispensable.
  • Chercher le bon k : Tester plusieurs valeurs de n_neighbors (10, 20, 30, 50) et comparer la stabilité des scores. Visualiser la distribution des LOF scores pour chaque valeur.
  • Réduction de dimension en amont : Pour des données avec plus de 20-30 dimensions, une ACP ou UMAP préalable améliore considérablement la pertinence des résultats du LOF.
  • Combiner avec Isolation Forest : Utiliser les deux algorithmes et ne conserver comme anomalies que les points détectés par les deux — cela réduit les faux positifs.
  • Utiliser novelty=True en production : Pour un déploiement où de nouvelles données arrivent en continu, le mode novelty permet de scorer les nouvelles requêtes sans recalculer tout le modèle.

Conclusion

Le Local Outlier Factor est probablement l’algorithme de détection d’anomalies le plus nuancé de la boîte à outils du data scientist. En mesurant la densité relative locale plutôt qu’une distance absolue, il capture des anomalies subtiles que les méthodes globales manqueraient inévitablement.

Son principal inconvénient — la complexité computationnelle — est largement compensé par sa capacité à fonctionner sur des données de densités hétérogènes et sa sortie en score continu, qui permet une analyse beaucoup plus riche qu’un simple binaire anormal/normal.

Pour choisir entre les principales méthodes d’anomalie :
Isolation Forest : rapide, robuste, bon premier choix par défaut.
One-Class SVM : quand on veut une frontière précise et que les données sont bien séparables.
Elliptic Envelope : quand les données normales suivent approximativement une distribution gaussienne.
LOF : quand les clusters ont des densités très variables et qu’on veut détecter des anomalies locales subtiles.


Voir aussi


Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.