Scikit-Learn 을 이용한 이상치 탐지

Beginner

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

소개

이 실습에서는 Scikit-Learn 을 사용하여 지역 이상치 탐지 요인 (LOF) 및 격리 숲 (IForest) 알고리즘을 통해 고전적인 이상 탐지 데이터 세트에서 이상치 탐지를 수행하는 방법을 보여줍니다. 알고리즘의 성능은 이상치 탐지 맥락에서 평가되며, ROC 곡선이 결과를 플롯하는 데 사용됩니다.

VM 팁

VM 시작이 완료되면 왼쪽 상단 모서리를 클릭하여 Notebook 탭으로 전환하여 연습을 위한 Jupyter Notebook에 접근합니다.

때때로 Jupyter Notebook 이 완전히 로드되기까지 몇 초 정도 기다려야 할 수 있습니다. Jupyter Notebook 의 제한으로 인해 작업의 유효성 검사를 자동화할 수 없습니다.

학습 중 문제가 발생하면 Labby 에 문의하십시오. 세션 후 피드백을 제공하면 문제를 신속하게 해결해 드리겠습니다.

데이터 전처리

첫 번째 단계는 데이터 세트를 전처리하는 것입니다. 이 예제에서는 Scikit-Learn 의 datasets 모듈에서 사용 가능한 실제 데이터 세트를 사용합니다. 계산 속도를 높이기 위해 일부 데이터 세트의 샘플 크기가 줄어듭니다. 데이터 전처리 후 데이터 세트의 대상은 0 은 내부 데이터 (inliers) 를, 1 은 이상치 (outliers) 를 나타내는 두 개의 클래스를 갖게 됩니다. 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"Loading {dataset_name} data")
    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)

이상치 예측 함수

다음 단계는 이상치 예측 함수를 정의하는 것입니다. 이 예제에서는 LocalOutlierFactorIsolationForest 알고리즘을 사용합니다. compute_prediction 함수는 X 의 평균 이상치 점수를 반환합니다.

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

def compute_prediction(X, model_name):
    print(f"Computing {model_name} prediction...")
    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

결과 플롯 및 해석

마지막 단계는 결과를 플롯하고 해석하는 것입니다. 알고리즘의 성능은 거짓 양성률 (FPR) 이 낮은 값에서 진짜 양성률 (TPR) 이 얼마나 좋은지에 따라 결정됩니다. 최상의 알고리즘은 플롯의 왼쪽 상단에 곡선이 있고 곡선 아래 면적 (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 곡선이 사용되어 결과를 플롯했습니다.