Más sobre ciencia de datos en: cienciadedatos.net


Introducción

Isolation Forest es un método no supervisado para la detección de anomalías (outliers) en conjuntos de datos no etiquetados, es decir, cuando no se conoce a priori si una observación es anómala o normal.

El algoritmo utiliza un conjunto de árboles, de forma conceptualmente similar a Random Forest, aunque con un objetivo distinto: en lugar de aprender reglas predictivas, busca aislar observaciones individuales. Un modelo Isolation Forest está compuesto por múltiples árboles binarios llamados isolation trees.

Cada isolation tree se construye separando recursivamente las observaciones mediante divisiones binarias. A diferencia de los árboles de clasificación o regresión, las variables y los puntos de corte se seleccionan de forma completamente aleatoria, sin optimizar ninguna función de impureza. Las observaciones con valores poco frecuentes o extremos suelen quedar aisladas tras pocas divisiones, mientras que las observaciones normales requieren recorridos más largos dentro del árbol.

La longitud del camino (número de divisiones necesarias para aislar una observación) es, por tanto, el elemento clave del algoritmo.

Algoritmo Isolation Tree


1) Crear un nodo raíz que contiene las $N$ observaciones de entrenamiento.

2) Seleccionar aleatoriamente un atributo $i$ y un valor aleatorio $a$ dentro del rango observado de dicho atributo.

3) Crear dos nuevos nodos separando las observaciones acorde al criterio $x_i \leq a$ o $x_i > a$.

4) Repetir los pasos 2 y 3 hasta que el nodo contenga una única observación o se alcance una profundidad máxima predefinida.



Diagrama de un isolation tree. En azul, el camino necesario para llegar a aislar una observacion normal. En rojo, el camino para aislar una anomalía.

Algoritmo Isolation Forest

El modelo Isolation Forest se obtiene combinando múltiples isolation trees, cada uno entrenado a partir de una muestra generada mediante bootstrapping del conjunto de datos original.

Para cada observación, el modelo calcula la longitud promedio del camino necesario para aislarla a través de todos los árboles del bosque. Cuanto menor es esta longitud promedio, mayor es la probabilidad de que la observación sea una anomalía. En la literatura, esta magnitud suele denominarse path length y, de forma informal, a veces se la llama “distancia de aislamiento”, aunque no representa una distancia en el sentido matemático.

Consideraciones prácticas

Al ser un método no supervisado, no hay forma de conocer el valor óptimo a partir del cual se debe de considerar que se trata de una anomalía. La puntuación asignada a cada observación es una medida relativa respecto al resto de observaciones. En la práctica, suelen considerarse como potenciales outliers aquellas observaciones cuya distancia predicha está por debajo de un determinado cuantil. Por ejemplo, si se considera que hay un 1% de anomalías, se utiliza como límite de decisión el cuantil 0.01 de todas las distancias calculadas.

Cuando se dispone de muchas observaciones, aislarlas todas en nodos terminales requiere de árboles con muchas ramificaciones, lo que se traduce en un coste computacional muy elevado. Una forma de aliviar este problema es determinar una profundidad máxima hasta la que se puede crecer el árbol. A aquellas observaciones que, una vez alcanzado el criterio de parada, no han llegado a nodos terminales individuales, se les suma el número de divisiones teóricas promedio $c(r)$ que se necesita para aislar mediante particiones binarias un nodo de $r$ observaciones.

$$c(r) = \log(r-1) - \frac{2(r - 1)}{r} + 0.5772$$

Isolation Forest en Python

Tres de las principales implementaciones de Isolation Forest están disponibles en Scikit Learn, H2O y PyOD, todas altamente optimizadas, aunque con diferencias relevantes en su uso:

  • Scikit Learn: al entrenar el modelo, se tiene que especificar el porcentaje de anomalías que se espera en los datos de entrenamiento (contamination). Con este valor, el modelo aprende el valor a partir del cual una observación se considera anomalía. Al aplicar el método predict() se obtiene -1 si es anomalía (outlier) o 1 si es un dato normal (inliers). Para recuperar la métrica de anomalía en lugar de la clasificación, hay que emplear el método score_samples(). Este último devuelve el valor negativo de la distancia de aislamiento, normalizada tal como se propone en el paper original.

  • H2O: En la implementación de H2O, el método predict() devuelve directamente la puntuación de aislamiento. El usuario debe definir explícitamente el umbral de decisión, habitualmente mediante cuantiles calculados sobre las observaciones de entrenamiento.

  • PyOD: incluye una implementación de Isolation Forest, basada en la de Scikit Learn, pero con algunas funcionalidades adicionales.

Librerías

Las librerías utilizadas en este documento son:

# Tratamiento de datos
# ==============================================================================
import numpy as np
import pandas as pd
from mat4py import loadmat

# Gráficos
# ==============================================================================
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
plt.rcParams['lines.linewidth'] = 1.5
plt.rcParams['font.size'] = 8
import seaborn as sns

# Preprocesado y modelado
# ==============================================================================
from sklearn.ensemble import IsolationForest
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

import h2o
from h2o.estimators import H2OExtendedIsolationForestEstimator
from h2o.estimators import H2OGeneralizedLowRankEstimator
from h2o.transforms.preprocessing import H2OScaler

# Configuración warnings
# ==============================================================================
import warnings
warnings.filterwarnings('once')

Isolation Forest

Datos

Los datos empleados se han obtenido de Outlier Detection DataSets (ODDS), un repositorio con datos comúnmente empleados para comparar la capacidad que tienen diferentes algoritmos a la hora de identificar anomalías (outliers). Shebuti Rayana (2016). ODDS Library. Stony Brook, NY: Stony Brook University, Department of Computer Science.

Todos estos conjuntos de datos están etiquetados, se conoce si las observaciones son o no anomalías (variable y). Aunque los métodos que se describen en el documento son no supervisados, es decir, no hacen uso de la variable respuesta, conocer la verdadera clasificación permite evaluar su capacidad para identificar correctamente las anomalías.

  • Cardiotocography dataset link:
    • Número de observaciones: 1831
    • Número de variables: 21
    • Número de outliers: 176 (9.6%)
    • y: 1 = outliers, 0 = inliers
    • Observaciones: todas las variables están centradas y escaladas (media 0, sd 1).
    • Referencia: C. C. Aggarwal and S. Sathe, “Theoretical foundations and algorithms for outlier ensembles.” ACM SIGKDD Explorations Newsletter, vol. 17, no. 1, pp. 24–47, 2015. Saket Sathe and Charu C. Aggarwal. LODES: Local Density meets Spectral Outlier Detection. SIAM Conference on Data Mining, 2016.

Los datos están disponibles en formato MATLAB (.mat). Para leer su contenido se emplea la función loadmat() del paquete mat4py.

# Lectura de datos
# ==============================================================================
datos = loadmat(filename='cardio.mat')
X = pd.DataFrame(datos['X'])
X.columns = ["col_" + str(i) for i in X.columns]
y = pd.Series(np.array(datos['y']).flatten())

Modelo

La clase sklearn.ensemble.IsolationForest incorpora las principales funcionalidades que se necesitan a la hora de trabajar con modelos Isolation Forest. Los principales argumentos para entrenar este tipo de modelos son:

  • n_estimators: número de árboles que forman el modelo.

  • max_samples: número de observaciones empleadas para entrenar cada árbol.

  • contamination: proporción de anomalías esperadas en los datos de entrenamiento. En base a este valor, se establece el límite acorde al cual se clasifican las observaciones en normales o anómalas.

  • random_state: semilla para garantizar la reproducibilidad de los resultados.

Se procede a entrenar un modelo asumiendo que hay un 1% de observaciones anómalas en el conjunto de entrenamiento.

# Definición y entrenamiento del modelo IsolationForest
# ==============================================================================
modelo_isof = IsolationForest(
                n_estimators  = 1000,
                max_samples   ='auto',
                contamination = 0.01,
                n_jobs        = -1,
                random_state  = 123,
            )

modelo_isof.fit(X=X)
IsolationForest(contamination=0.01, n_estimators=1000, n_jobs=-1,
                random_state=123)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Predicción

IsolationForest tienen dos métodos de predicción con los que se obtiene distinta información. Con el método predict() se devuelve directamente la clasificación de anomalía (-1) o no anomalía (1) acorde a la proporción de contaminación que se ha indicado en la definición del modelo.

# Predicción clasificación
# ==============================================================================
clasificacion_predicha = modelo_isof.predict(X=X)
clasificacion_predicha
array([1, 1, 1, ..., 1, 1, 1], shape=(1831,))

Con el método score_samples(), en lugar de la clasificación, se obtiene el valor de anomalía predicho por el modelo. Es importante destacar que este valor no es la distancia de aislamiento promedio, sino una normalización de la misma propuesta en el paper original.

Como resultado de la normalización, y de multiplicarla por -1, los valores de anomalía quedan acotados en el rango [-1, 0]. Cuanto más próximo a -1 es el valor, mayor evidencia de anomalía. Valores entre -0.5 y 0 son los esperados para observaciones normales.

# Predicción valor anomalía
# ==============================================================================
score_anomalia = modelo_isof.score_samples(X=X)
score_anomalia
array([-0.41908062, -0.42765109, -0.45293005, ..., -0.47186108,
       -0.46324072, -0.53377418], shape=(1831,))

¿Qué relación hay entre predict() y score_samples()?

Durante el entrenamiento del modelo, se indicó que la proporción de anomalías (contamination) era del 1%. Esta información se utiliza para identificar cuál es el score de anomalía con el que solo un 1% de las observaciones se considerarían anomalías, es decir, el cuantil 0.01.

cuantil_01 = np.quantile(score_anomalia, q=0.01)
cuantil_01
np.float64(-0.5864598467056028)

Este es el valor que se almacena automáticamente en el atributo .offset_ en función de la proporción de continuación indicada.

modelo_isof.offset_
np.float64(-0.5864598467056028)
# Distribución de los valores de anomalía
# ==============================================================================
fig, ax = plt.subplots(figsize=(6, 3))
sns.kdeplot(
    data = score_anomalia,
    fill = True,
    ax   = ax
)
sns.rugplot(score_anomalia, ax=ax, color='black')
ax.axvline(cuantil_01, c='red', linestyle='--', label='cuantil 0.01')
ax.set_title('Distribución de los valores de anomalía')
ax.set_xlabel('Score de anomalía')
ax.legend(loc='upper left');

Si se utiliza el valor del cuantil para clasificar las observaciones, los resultados obtenidos son equivalentes a los devueltos por predict().

all(clasificacion_predicha == np.where(score_anomalia < cuantil_01, -1, 1))
True

Detección de anomalías

Una vez que la distancia de separación ha sido calculada, se puede emplear como criterio para identificar anomalías. Asumiendo que las observaciones con valores atípicos en alguna de sus variables se separan del resto con mayor facilidad, aquellas observaciones con menor distancia promedio deberían ser las más atípicas. Debido a la normalización que se realiza en la implementación de Scikit Learn, esto se traduce en que, cuanto más negativo es el score predicho mayor evidencia de anomalía.

En la práctica, si se está empleando esta estrategia de detección es porque no se dispone de datos etiquetados, es decir, no se conoce qué observaciones son realmente anomalías. Sin embargo, como en este ejemplo se dispone de la clasificación real, se puede verificar si realmente los datos anómalos tienen menores distancias.

# Distribución de los valores de anomalía
# ==============================================================================
resultados = pd.DataFrame({
                    'score'    : score_anomalia,
                    'anomalia' : y.astype(str)
                })

fig, ax = plt.subplots(figsize=(4, 3))
sns.boxplot(
    x     = 'anomalia',
    y     = 'score',
    hue   = 'anomalia',
    data  = resultados,
    ax    = ax
)
ax.set_title('Score de anomalía según clasificación real')
ax.set_ylabel('Score de anomalía')
ax.set_xlabel('clasificación (0 = normal, 1 = anomalía)')
ax.get_legend();

La distribución de los valores de anomalía (score) en el grupo de las anomalías es claramente inferior (más negativo). Sin embargo, al existir solapamiento, si se clasifican las n observaciones con menor score como anomalías, se incurriría en errores de falsos positivos.

Acorde a la documentación, el set de datos Cardiotocogrpahy contiene 176 anomalías. Véase la matriz de confusión resultante si se clasifican como anomalías las 176 observaciones con menor score predicho.

# Matriz de confusión de la clasificación final
# ==============================================================================
resultados = (
    resultados
    .sort_values('score', ascending=True)
    .reset_index(drop=True)
)
resultados['clasificacion'] = np.where(resultados.index <= 176, 1, 0)
pd.crosstab(
    resultados['anomalia'],
    resultados['clasificacion'],
    rownames=['valor real'],
    colnames=['prediccion']
)
prediccion 0 1
valor real
0.0 1571 84
1.0 83 93

De las 176 observaciones identificadas como anomalías, solo el 53% (93/176) lo son. El porcentaje de falsos positivos (47%) es elevado, el método de isolation forest no consigue resultados notables en este set de datos.

Combinación de PCA e Isolation Forest

El algoritmo de isolation forest, al igual que muchos otros, se ve perjudicado a medida que aumenta la dimensionalidad del conjunto de datos, es decir, el número de variables. A este fenómeno se le conoce como curse of dimensionality. La razón subyacente por la que aparece este problema es que, en un espacio de alta dimensionalidad, las observaciones están tan alejadas unas de otras que la diferencia entre normal y anómalo desaparece, todo son anomalías.

A esto se le une que, en la practica, muchas de las variables disponibles solo hacen que añadir ruido, no son útiles para discriminar entre observaciones.

Una forma con la que tratar de mitigar el problema es transformar los datos mediante un método de reducción de dimensionalidad antes de entrenar el modelo, por ejemplo, mediante un PCA.

Modelo

Mediante los Pipeline de sklearn es sencillo combinar varios modelos en un único objeto.

# Pipeline de un PCA y un IsolationForest
# ==============================================================================
modelo_PCA = PCA(n_components=0.9)
modelo_isof = IsolationForest(
                n_estimators  = 1000,
                max_samples   = 100,
                contamination = 0.01,
                n_jobs        = -1,
                random_state  = 123,
            )
pipeline_pca_isof = make_pipeline(StandardScaler(), modelo_PCA, modelo_isof)
pipeline_pca_isof.fit(X=X)
Pipeline(steps=[('standardscaler', StandardScaler()),
                ('pca', PCA(n_components=0.9)),
                ('isolationforest',
                 IsolationForest(contamination=0.01, max_samples=100,
                                 n_estimators=1000, n_jobs=-1,
                                 random_state=123))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Predicción

# Predicción scores
# ==============================================================================
score_anomalia = pipeline_pca_isof.score_samples(X=X)
score_anomalia
array([-0.41306992, -0.42741431, -0.47682121, ..., -0.4604572 ,
       -0.44811813, -0.50387696], shape=(1831,))

Detección de anomalías

# Matriz de confusión de la clasificación final
# ==============================================================================
resultados = pd.DataFrame({'score': score_anomalia, 'anomalia' : y})
resultados = (
    resultados
    .sort_values('score', ascending=True)
    .reset_index(drop=True)
)
resultados['clasificacion'] = np.where(resultados.index <= 176, 1, 0)
pd.crosstab(
    resultados.anomalia,
    resultados.clasificacion,
    rownames=['valor real'],
    colnames=['prediccion']
)
prediccion 0 1
valor real
0.0 1569 86
1.0 85 91

En este conjunto de datos, la combinación de PCA e Isolation Forest no consigue mejorar los resultados obtenidos.

Extended isolation forest

El algoritmo Extended Isolation Forest es una versión mejorada de su predecesor, el Isolation Forest. El algoritmo original introdujo una manera innovadora de detectar anomalías, pero tiene un sesgo debido a la forma en que los árboles deciden cómo dividir los datos.
La versión extendida corrige este sesgo, haciendo que el algoritmo original sea solo un caso especial de la versión generalizada.

El problema del sesgo surge porque, en el algoritmo original, la división de los datos se hace siguiendo un patrón similar al de los árboles binarios de búsqueda (BST). En cada punto de ramificación se elige:

  • Una característica específica.
  • Un valor de corte.

Esto provoca que las divisiones estén alineadas con los ejes y generen un sesgo.

La versión extendida introduce una pendiente aleatoria en cada división. En lugar de seleccionar directamente la característica y el valor, se elige:

  • Una pendiente aleatoria $n$ (un vector en el espacio de características), que define la inclinación del hiperplano de corte.
  • Una intersección aleatoria $p$, que determina dónde pasa ese hiperplano por el espacio de los datos.

Cada componente de la pendiente $n$ se genera de forma independiente a partir de una distribución gaussiana $N(0,1)$, y la intersección $p$ se obtiene de una distribución uniforme dentro de los límites de los datos que se van a dividir.

Con este enfoque, un punto $x$ se asigna a un lado de la división siguiendo la regla:

$$ (x - p) \cdot n \leq 0 $$

Gracias a esto:

  • Las divisiones ya no están limitadas a estar paralelas a los ejes.
  • El algoritmo detecta anomalías de manera más precisa y justa.
  • Se mantiene la simplicidad y eficiencia del Isolation Forest original.


Particiones generadas por el algoritmo isolation forest.


Particiones generadas por el algoritmo extended isolation forest.
# Iniciación del cluster H2O y tranferencia de datos
# ==============================================================================
h2o.init(verbose=False)
h2o.remove_all()
h2o.no_progress()
X = h2o.H2OFrame(X)
# Definición y entrenamiento del modelo IsolationForest
# ==============================================================================
modelo_isof = H2OExtendedIsolationForestEstimator(
                ntrees          = 500,
                sample_size     = 100,
                extension_level = X.shape[1] - 1,
                seed            = 2345
              )

modelo_isof.train(x=X.columns, training_frame=X)
modelo_isof.summary()
Model Summary:
number_of_trees size_of_subsample extension_level seed number_of_trained_trees min_depth max_depth mean_depth min_leaves max_leaves mean_leaves min_isolated_point max_isolated_point mean_isolated_point min_not_isolated_point max_not_isolated_point mean_not_isolated_point min_zero_splits max_zero_splits mean_zero_splits
500 100 20 2345.0 500.0 6.0 6.0 6.0 7.0 39.0 19.326 0.0 18.0 6.036 82.0 100.0 93.964 0.0 15.0 6.246
# Predicción clasificación
# ==============================================================================
score_anomalia = modelo_isof.predict(X)
score_anomalia = score_anomalia.as_data_frame()['anomaly_score']
# Distribución de los valores de anomalía
# ==============================================================================
fig, ax = plt.subplots(figsize=(6, 3))
sns.kdeplot(
    x     = score_anomalia,
    fill  = True,
    ax    = ax
)
sns.rugplot(score_anomalia,  ax=ax, color='black')
ax.set_title('Distribución de los scores de anomalía')
ax.set_xlabel('Score de anomalía');
# Matriz de confusión de la clasificación final
# ==============================================================================
resultados = pd.DataFrame({'score': score_anomalia, 'anomalia': y})

resultados = (
    resultados
    .sort_values('score', ascending=False)
    .reset_index(drop=True)
)
resultados['clasificacion'] = np.where(resultados.index <= 176, 1, 0)
pd.crosstab(
    resultados['anomalia'],
    resultados['clasificacion'],
    rownames=['valor real'],
    colnames=['prediccion']
)
prediccion 0 1
valor real
0.0 1579 76
1.0 75 101

La detección de anomalías con Extended Isolation Forest mejora ligeramente los resultados obtenidos con la versión original del algoritmo de Isolation Forest.

PCA y Extended isolation forest

# Reducción de dimensionalidad con PCA en H2O seguido de Extended Isolation Forest
# ==============================================================================
pipeline_pca = make_pipeline(StandardScaler(), PCA(n_components=0.95))
# Se convierte de nuevo X a pandas dataframe
X = X.as_data_frame()
pipeline_pca.fit(X)
proyecciones = pipeline_pca.transform(X)
proyecciones = h2o.H2OFrame(proyecciones)
modelo_isof = H2OExtendedIsolationForestEstimator(
                ntrees          = 500,
                sample_size     = 100,
                extension_level = proyecciones.shape[1] - 1,
                seed            = 2345
              )
_ = modelo_isof.train(x=proyecciones.columns, training_frame=proyecciones)
modelo_isof.summary()
Model Summary:
number_of_trees size_of_subsample extension_level seed number_of_trained_trees min_depth max_depth mean_depth min_leaves max_leaves mean_leaves min_isolated_point max_isolated_point mean_isolated_point min_not_isolated_point max_not_isolated_point mean_not_isolated_point min_zero_splits max_zero_splits mean_zero_splits
500 100 13 2345.0 500.0 6.0 6.0 6.0 7.0 39.0 17.418 0.0 16.0 4.792 84.0 100.0 95.208 0.0 17.0 6.844
# Predicción score de anomalía
# ==============================================================================
score_anomalia = modelo_isof.predict(proyecciones)
score_anomalia = score_anomalia.as_data_frame()['anomaly_score']
# Matriz de confusión de la clasificación final
# ==============================================================================
resultados = pd.DataFrame({'score': score_anomalia, 'anomalia': y})
resultados = resultados.sort_values('score', ascending=False).reset_index(drop=True)
resultados['clasificacion'] = np.where(resultados.index <= 176, 1, 0)
pd.crosstab(
    resultados['anomalia'],
    resultados['clasificacion'],
    rownames=['valor real'],
    colnames=['prediccion']
)
prediccion 0 1
valor real
0.0 1579 76
1.0 75 101

En este caso, preprocesar los datos con PCA no parece aportar nada.

GLRM y Extended isolation forest

GLRM (Generalized Low Rank Models) es una técnica de reducción de dimensionalidad que generaliza métodos clásicos como PCA, SVD o NMF. Utiliza una matriz de factores de bajo rango para aproximar los datos originales, permitiendo capturar patrones complejos y manejar datos faltantes. GLRM es versátil, aplicable a diversos tipos de datos y útil en tareas como compresión, imputación y detección de anomalías.

Se procede a entrenar un modelo GLRM para reducir la dimensionalidad de los datos antes de aplicar Extended Isolation Forest.

# Modelo GLRM
# ==============================================================================
X = h2o.H2OFrame(X)
glrm_model = H2OGeneralizedLowRankEstimator(
                k=min(X.shape),
                loss="absolute",
                transform="standardize"
            )
_ = glrm_model.train(training_frame=X)
glrm_model.summary()
Model Summary:
number_of_iterations final_step_size final_objective_value
87.0 0.0000783 1116.9306400

Representando la varianza acumulada en función del número de componentes, se observa que con 13 componentes ya se explica el 90% de la varianza total.

# Selección de componentes basada en varianza acumulada
# ==============================================================================
varianza_acumulada = (
    glrm_model.varimp(use_pandas=True)
    .set_index("")
    .transpose()
)
varianza_acumulada
Standard deviation Proportion of Variance Cumulative Proportion
pc1 2.350122 2.630036e-01 0.263004
pc2 1.902645 1.723837e-01 0.435387
pc3 1.336649 8.507769e-02 0.520465
pc4 1.176094 6.586650e-02 0.586332
pc5 1.112758 5.896335e-02 0.645295
pc6 1.011128 4.868472e-02 0.693980
pc7 0.961964 4.406544e-02 0.738045
pc8 0.936282 4.174400e-02 0.779789
pc9 0.836101 3.328878e-02 0.813078
pc10 0.791265 2.981432e-02 0.842892
pc11 0.754825 2.713150e-02 0.870024
pc12 0.701819 2.345478e-02 0.893478
pc13 0.576776 1.584144e-02 0.909320
pc14 0.482282 1.107599e-02 0.920396
pc15 0.425125 8.606237e-03 0.929002
pc16 0.320303 4.885440e-03 0.933888
pc17 0.260599 3.233893e-03 0.937121
pc18 0.248042 2.929758e-03 0.940051
pc19 0.022971 2.512610e-05 0.940076
pc20 0.008350 3.319838e-06 0.940080
pc21 0.000173 1.417900e-09 0.940080

Se procede a transformar los datos originales utilizando las 13 primeras componentes obtenidas con GLRM.

# Modelo GLRM con k=14
# ==============================================================================
glrm_model = H2OGeneralizedLowRankEstimator(
                k=13,
                loss="absolute",
                transform="standardize"
            )

_ = glrm_model.train(training_frame=X)
proyecciones = glrm_model.predict(X)
# Modelo Extended Isolation Forest
# ==============================================================================
modelo_isof = H2OExtendedIsolationForestEstimator(
                ntrees          = 500,
                sample_size     = 100,
                extension_level = proyecciones.shape[1] - 1
              )

_ = modelo_isof.train(x=proyecciones.columns, training_frame=proyecciones)
# Predicción
# ==============================================================================
score_anomalia = modelo_isof.predict(proyecciones)
score_anomalia = score_anomalia.as_data_frame()['anomaly_score']

# Matriz de confusión de la clasificación final
# ==============================================================================
resultados = pd.DataFrame({'score': score_anomalia,'anomalia': y})
resultados = (
    resultados
    .sort_values('score', ascending=False)
    .reset_index(drop=True)
)
resultados['clasificacion'] = np.where(resultados.index <= 176, 1, 0)
pd.crosstab(
    resultados['anomalia'],
    resultados['clasificacion'],
    rownames=['valor real'],
    colnames=['prediccion']
)
prediccion 0 1
valor real
0.0 1583 72
1.0 71 105

De entre todas las combinaciones probadas, la que mejores resultados obtiene es la de GLRM y Extended Isolation Forest.

Información de sesión

import session_info
session_info.show(html=False)
-----
h2o                 3.46.0.9
mat4py              0.6.0
matplotlib          3.10.8
numpy               2.2.6
pandas              2.3.3
seaborn             0.13.2
session_info        v1.0.1
sklearn             1.7.2
-----
IPython             9.8.0
jupyter_client      8.7.0
jupyter_core        5.9.1
-----
Python 3.13.11 | packaged by Anaconda, Inc. | (main, Dec 10 2025, 21:28:48) [GCC 14.3.0]
Linux-6.14.0-37-generic-x86_64-with-glibc2.39
-----
Session information updated at 2026-01-22 23:27

Bibliografía

Outlier Analysis Aggarwal, Charu C.

Outlier Ensembles: An Introduction by Charu C. Aggarwal, Saket Sathe

https://www.h2o.ai/blog/anomaly-detection-with-isolation-forests-using-h2o/

Liu, Fei Tony, Ting, Kai Ming and Zhou, Zhi-Hua. “Isolation forest.” Data Mining, 2008. ICDM’08. Eighth IEEE International Conference on

Liu, Fei Tony, Ting, Kai Ming and Zhou, Zhi-Hua. “Isolation-based anomaly detection.” ACM Transactions on Knowledge Discovery from Data (TKDD) 6.1 (2012)

Fei Tony Liu, Kai Ming Ting, and Zhi-Hua Zhou. 2012. Isolation-Based Anomaly Detection. ACM Trans. Knowl. Discov. Data 6, 1, Article 3 (March 2012), 39 pages. DOI:https://doi.org/10.1145/2133360.2133363

Instrucciones para citar

¿Cómo citar este documento?

Si utilizas este documento o alguna parte de él, te agradecemos que lo cites. ¡Muchas gracias!

Detección de anomalías con Isolation Forest y python por Joaquín Amat Rodrigo, disponible bajo una licencia Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0 DEED) en https://www.cienciadedatos.net/documentos/py22-deteccion-anomalias-isolation-forest-python.html

¿Te ha gustado el artículo? Tu ayuda es importante

Tu contribución me ayudará a seguir generando contenido divulgativo gratuito. ¡Muchísimas gracias! 😊

Become a GitHub Sponsor

Creative Commons Licence

Este documento creado por Joaquín Amat Rodrigo tiene licencia Attribution-NonCommercial-ShareAlike 4.0 International.

Se permite:

  • Compartir: copiar y redistribuir el material en cualquier medio o formato.

  • Adaptar: remezclar, transformar y crear a partir del material.

Bajo los siguientes términos:

  • Atribución: Debes otorgar el crédito adecuado, proporcionar un enlace a la licencia e indicar si se realizaron cambios. Puedes hacerlo de cualquier manera razonable, pero no de una forma que sugiera que el licenciante te respalda o respalda tu uso.

  • No-Comercial: No puedes utilizar el material para fines comerciales.

  • Compartir-Igual: Si remezclas, transformas o creas a partir del material, debes distribuir tus contribuciones bajo la misma licencia que el original.