Обнаружение выбросов с использованием алгоритмов Scikit-Learn

Machine LearningMachine LearningBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этом практическом занятии показано, как использовать Scikit-Learn для обнаружения выбросов в классических наборах данных для обнаружения аномалий с использованием алгоритмов локального коэффициента выбросов (LOF) и изолирующего леса (IForest). Производительность алгоритмов оценивается в контексте обнаружения выбросов, и кривые ROC используются для построения результатов.

Советы по использованию ВМ

После запуска ВМ щелкните в верхнем левом углу, чтобы переключиться на вкладку Notebook и получить доступ к Jupyter Notebook для практики.

Иногда вам может потребоваться подождать несколько секунд, пока Jupyter Notebook не загрузится полностью. Валидация операций не может быть автоматизирована из-за ограничений Jupyter Notebook.

Если вы сталкиваетесь с проблемами во время обучения, не стесняйтесь обращаться к Labby. Оставьте отзыв после занятия, и мы оперативно решим проблему для вас.

Предварительная обработка данных

Первым шагом является предварительная обработка набора данных. В этом примере мы используем реальные-world наборы данных, доступные в модуле datasets Scikit-Learn. Размер выборки некоторых наборов данных уменьшается, чтобы ускорить вычисления. После предварительной обработки данных, целевые переменные наборов данных будут иметь два класса, 0, представляющий нормальные объекты, и 1, представляющий выбросы. Функция preprocess_dataset возвращает данные и целевую переменную.

import numpy as np
from sklearn.datasets import fetch_kddcup99, fetch_covtype, fetch_openml
from sklearn.preprocessing import LabelBinarizer
import pandas as pd

rng = np.random.RandomState(42)

def preprocess_dataset(dataset_name):
    ## загрузка и векторизация
    print(f"Загрузка данных {dataset_name}")
    if dataset_name in ["http", "smtp", "SA", "SF"]:
        dataset = fetch_kddcup99(subset=dataset_name, percent10=True, random_state=rng)
        X = dataset.data
        y = dataset.target
        lb = LabelBinarizer()

        if dataset_name == "SF":
            idx = rng.choice(X.shape[0], int(X.shape[0] * 0.1), replace=False)
            X = X[idx]  ## уменьшить размер выборки
            y = y[idx]
            x1 = lb.fit_transform(X[:, 1].astype(str))
            X = np.c_[X[:, :1], x1, X[:, 2:]]
        elif dataset_name == "SA":
            idx = rng.choice(X.shape[0], int(X.shape[0] * 0.1), replace=False)
            X = X[idx]  ## уменьшить размер выборки
            y = y[idx]
            x1 = lb.fit_transform(X[:, 1].astype(str))
            x2 = lb.fit_transform(X[:, 2].astype(str))
            x3 = lb.fit_transform(X[:, 3].astype(str))
            X = np.c_[X[:, :1], x1, x2, x3, X[:, 4:]]
        y = (y!= b"normal.").astype(int)
    if dataset_name == "forestcover":
        dataset = fetch_covtype()
        X = dataset.data
        y = dataset.target
        idx = rng.choice(X.shape[0], int(X.shape[0] * 0.1), replace=False)
        X = X[idx]  ## уменьшить размер выборки
        y = y[idx]

        ## нормальные объекты - те, у которых атрибут 2
        ## выбросы - те, у которых атрибут 4
        s = (y == 2) + (y == 4)
        X = X[s, :]
        y = y[s]
        y = (y!= 2).astype(int)
    if dataset_name in ["glass", "wdbc", "cardiotocography"]:
        dataset = fetch_openml(
            name=dataset_name, version=1, as_frame=False, parser="pandas"
        )
        X = dataset.data
        y = dataset.target

        if dataset_name == "glass":
            s = y == "tableware"
            y = s.astype(int)
        if dataset_name == "wdbc":
            s = y == "2"
            y = s.astype(int)
            X_mal, y_mal = X[s], y[s]
            X_ben, y_ben = X[~s], y[~s]

            ## уменьшено до 39 точек (9,8% выбросов)
            idx = rng.choice(y_mal.shape[0], 39, replace=False)
            X_mal2 = X_mal[idx]
            y_mal2 = y_mal[idx]
            X = np.concatenate((X_ben, X_mal2), axis=0)
            y = np.concatenate((y_ben, y_mal2), axis=0)
        if dataset_name == "cardiotocography":
            s = y == "3"
            y = s.astype(int)
    ## 0 представляет нормальные объекты, а 1 - выбросы
    y = pd.Series(y, dtype="category")
    return (X, y)

Функция предсказания выбросов

Следующим шагом является определение функции предсказания выбросов. В этом примере мы используем алгоритмы LocalOutlierFactor и IsolationForest. Функция compute_prediction возвращает средний показатель выброса для X.

from sklearn.neighbors import LocalOutlierFactor
from sklearn.ensemble import IsolationForest

def compute_prediction(X, model_name):
    print(f"Вычисление предсказания для {model_name}...")
    if model_name == "LOF":
        clf = LocalOutlierFactor(n_neighbors=20, contamination="auto")
        clf.fit(X)
        y_pred = clf.negative_outlier_factor_
    if model_name == "IForest":
        clf = IsolationForest(random_state=rng, contamination="auto")
        y_pred = clf.fit(X).decision_function(X)
    return y_pred

Построение и интерпретация результатов

Последним шагом является построение и интерпретация результатов. Производительность алгоритма зависит от того, насколько высокая точность обнаружения положительных объектов (TPR) при низком значении ложноположительного обнаружения (FPR). Лучшие алгоритмы имеют кривую в верхнем левом углу графика, а площадь под кривой (AUC) близка к 1. Диагональная пунктирная линия представляет случайную классификацию выбросов и нормальных объектов.

import math
import matplotlib.pyplot as plt
from sklearn.metrics import RocCurveDisplay

datasets_name = [
    "http",
    "smtp",
    "SA",
    "SF",
    "forestcover",
    "glass",
    "wdbc",
    "cardiotocography",
]

models_name = [
    "LOF",
    "IForest",
]

## параметры построения графика
cols = 2
linewidth = 1
pos_label = 0  ## означает, что 0 относится к положительному классу
rows = math.ceil(len(datasets_name) / cols)

fig, axs = plt.subplots(rows, cols, figsize=(10, rows * 3), sharex=True, sharey=True)

for i, dataset_name in enumerate(datasets_name):
    (X, y) = preprocess_dataset(dataset_name=dataset_name)

    for model_idx, model_name in enumerate(models_name):
        y_pred = compute_prediction(X, model_name=model_name)
        display = RocCurveDisplay.from_predictions(
            y,
            y_pred,
            pos_label=pos_label,
            name=model_name,
            linewidth=linewidth,
            ax=axs[i // cols, i % cols],
            plot_chance_level=(model_idx == len(models_name) - 1),
            chance_level_kw={
                "linewidth": linewidth,
                "linestyle": ":",
            },
        )
    axs[i // cols, i % cols].set_title(dataset_name)
plt.tight_layout(pad=2.0)  ## расстояние между подграфиками
plt.show()

Резюме

В этом практическом занятии показано, как использовать Scikit-Learn для обнаружения выбросов в классических наборах данных для обнаружения аномалий с использованием алгоритмов локального коэффициента выбросов (LOF) и изолирующего леса (IForest). Производительность алгоритмов оценивалась в контексте обнаружения выбросов, и кривые ROC использовались для построения результатов.