본문 바로가기
머신러닝

[머신러닝] 이상 탐지(Anomaly Detection)

by PIAI 2022. 5. 11.

from sklearn.tree import DecisionTreeClassifier
이상 탐지(Anomaly Detection)의 정의

 

  • 주어진 데이터의 정상 여부를 판별하는 문제
  • 신용 카드 사기, 대출 사기 탐지 등이 대표적인 예시

이상 탐지 문제의 가장 큰 특징은 정상 데이터와 이상 데이터의 비율의 불균형입니다.

암 진단으로 예시를 들었을 때, 암이 아닌 환자가 99명이고 암인 환자가 1명이면 모든 데이터를 암이 아닌 환자라고 예측하면 정확도가 99프로로 엄청 높게 나옵니다. 

 

데이터 불균형(Class Imbalance)의 해결 방법(샘플링)

 

Under Sampling

큰 그룹의 데이터를 덜 뽑아서 데이터의 비율을 맞추는 방법입니다.

Over Sampling

작은 그룹의 데이터를 더 뽑아서 데이터의 비율을 맞추는 방법입니다.

 

Under Sampling

Random Under Sampling

말 그대로 무작위로 삭제하는 방법입니다. 그렇게 때문에 빠르고 쉽지만, 정보의 손실이 있을 수도 있습니다.

Near Miss Under Sampling

적은 그룹 근처에 있는 큰 그룹의 데이터를 선택하는 방법입니다.

 

  1. Near-Miss ver.1
    • 가장 가까운 3개의 작은 그룹의 데이터의 평균 거리
  2. Near-Miss ver.2
    • 가장 먼 3개의 작은 그룹의 데이터의 평균 거리
  3. Near-Miss ver.3
    • 가장 가까운 작은 그룹의 데이터와의 거리

 

Over Sampling

Simple Over Sampling

작은 그룹에서 데이터를 더 많이 추출하는 방법

장점

빠르게 적용이 가능합니다.

단점

  • 데이터의 새로운 정보를 제공하지 않습니다.
  • 과대 적합의 위험이 큽니다.

SMOTE(synthetic minority oversampling technique)

합성 데이터를 생성해서 데이터의 비율을 맞추는 방법입니다.

 

  1. Minority Group에서 임의의 데이터를 선택합니다.
  2. KNN으로 선택된 데이터와 Minority Group 중 가까운 이웃을 찾습니다.
  3. 선택된 데이터와 가까운 이웃 사이의 거리를 측정합니다.
  4. 0~1 사이의 임의의 수를 곲하여 데이터를 생성합니다.
  5. 데이터의 비율이 같아질 때 까지 1~4를 반복합니다.

Step 1) Minority Group에서 임의의 데이터를 선택합니다.

Step 2) KNN으로 선택된 데이터와 작은 그룹 중 가까운 이웃을 찾습니다.

k = 1

Step 3) 선택된 데이터와 가까운 이웃 사이의 거리를 측정합니다.

Step 4) 0~1 사이의 임의의 수를 곱하여 데이터를 생성합니다.

임의의 수 = 0.5

 

Step 5) 데이터의 비율이 같아질 때까지 1~4를 반복합니다.

데이터 불균형(Class Imbalance)의 해결 방법(샘플링)

 

정보가 손실되지는 않지만, 원래 데이터를 OverSampling 하였기 때문에 새로운 데이터를 잘 예측하지 못합니다.

 

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_moons

data, label = make_moons(n_samples=300, shuffle=True, noise=0.5, random_state=2021)
plt.scatter(data[:, 0], data[:, 1], c=label)

 

 

from imblearn.datasets import make_imbalance
from collections import Counter
from sklearn.model_selection import train_test_split

def ratio_func(y, multiplier, minority_class):
    target_stats = Counter(y)
    return {minority_class: int(multiplier * target_stats[minority_class])}

data, label = make_imbalance(
    data,
    label,
    sampling_strategy=ratio_func,
    **{"multiplier": 0.1, "minority_class": 1,}
)

train_x, test_x, train_y, test_y = train_test_split(data, label, test_size=0.3, random_state=2021, stratify=label)
plt.scatter(train_x[:, 0], train_x[:, 1], c=train_y)

from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, f1_score

tree = DecisionTreeClassifier()
tree.fit(train_x, train_y)

train_pred = tree.predict(train_x)
test_pred = tree.predict(test_x)

tree_train_acc = accuracy_score(train_pred, train_y)
tree_test_acc = accuracy_score(test_pred, test_y)
train_f1_score = f1_score(train_pred, train_y)
test_f1_score = f1_score(test_pred, test_y)

print("Tree train accuracy is {:.4f}".format(tree_train_acc))
print("Tree test accuracy is {:.4f}".format(tree_test_acc))
print("Tree train F1-Score is {:.4f}".format(train_f1_score))
print("Tree test F1-Score is {:.4f}".format(test_f1_score))
Tree train accuracy is 1.0000
Tree test accuracy is 0.9200
Tree train F1-Score is 1.0000
Tree test F1-Score is 0.5000

 

from imblearn.under_sampling import RandomUnderSampler, NearMiss

under_dict = {}

# Random
rus = RandomUnderSampler(random_state=2021)
rus_data, rus_label = rus.fit_resample(train_x, train_y)
under_dict['rus'] = {'data': rus_data, 'label': rus_label}

#Near
for i in range(1, 4):
    near_miss = NearMiss(version=i)
    near_data, near_label = near_miss.fit_resample(train_x, train_y)
    under_dict[f"near_{i}"] = {
        "data": near_data, "label": near_label
    }
    
fig, axs = plt.subplots(2, 2, figsize=(10, 10))
for idx, (name, sample) in enumerate(under_dict.items()):
    ax = axs[idx//2, idx%2]
    d, l = sample['data'], sample['label']
    ax.scatter(d[:, 0], d[:, 1], c=l)
    ax.set_title(name)

 

 

from sklearn.tree import DecisionTreeClassifier

under_model = {}
for name, sample in under_dict.items():
    under_tree = DecisionTreeClassifier()
    under_tree.fit(sample['data'], sample['label'])
    under_model[name] = under_tree
    
under_pred = {}
for name, under_tree in under_model.items():
    under_test_pred = under_tree.predict(test_x)
    under_pred[name] = under_test_pred
    
for name, pred in under_pred.items():
    acc = accuracy_score(test_y, pred)
    print(f"{name} Sampling test accuracy i {acc:.4f}")

print()
for name, pred in under_pred.items():
    f1 = f1_score(test_y, pred)
    print(f"{name} Sampling test F1-Score is {f1:.4f}")
rus Sampling test accuracy i 0.8400
near_1 Sampling test accuracy i 0.2600
near_2 Sampling test accuracy i 0.3400
near_3 Sampling test accuracy i 0.6000

rus Sampling test F1-Score is 0.5000
near_1 Sampling test F1-Score is 0.1778
near_2 Sampling test F1-Score is 0.1951
near_3 Sampling test F1-Score is 0.2857

 

#Over Sampling
from imblearn.over_sampling import SMOTE

smote = SMOTE(random_state=2021)

smote_data, smote_label = smote.fit_resample(train_x, train_y)

fig, axs = plt.subplots(1, 2, figsize=(10, 5))

axs[0].scatter(train_x[:, 0], train_x[:, 1], c=train_y)
axs[0].set_title('raw data')

axs[1].scatter(smote_data[:, 0], smote_data[:, 1], c=smote_label)
axs[1].set_title('smote data')

 

 

smote_tree = DecisionTreeClassifier()
smote_tree.fit(smote_data, smote_label)

smote_test_pred = smote_tree.predict(test_x)

smote_acc = accuracy_score(test_y, smote_test_pred)
smote_f1 = f1_score(test_y, smote_test_pred)
print(f"SMOTE test accuracy is {smote_acc:.4f}")
print(f"SMOTE test F1-SCORE is {smote_f1:.4f}")
SMOTE test accuracy is 0.9200
SMOTE test F1-SCORE is 0.6667

 

 

데이터 불균형(Class Imbalance)의 해결 방법(모델)

 

Out-of-Distribution

  • 모델은 정상 범위의 데이터(In-Distribution)만을 학습
  • 정상 범위에서 먼 데이터(Out-of-Distribution)를 이상으로 판단하는 이상 탐지 모델

모델은 정상 범위(In-Distribution)의 데이터만을 학습

정상 범위에서 먼 데이터(Out-of-Distribution)를 탐지

 

 

 

Isolation Forest

  • Regression Decision Tree를 기반으로 한 Random Forest
  • 정상 데이터는 더 많은 분할을 비정상 데이터는 덜 분할한다는 개념
  • 랜덤으로 데이터를 split 하여 모든 관측치를 고립시키며 구현됩니다.

각 관측치를 고립(=분리)시키는 것은 이상치가 정상 데이터보다 쉽습니다.

 

 

https://velog.io/@vvakki_/Isolation-Forest-%EB%AF%B8%EC%99%84%EC%84%B1

 

학습 방법

  • 정상 데이터는 tree의 terminal node와 근접하며, 경로 길이가 큽니다.
  • 이상치는 tree의 root node와 근접하며, 경로의 길이가 작습니다.

OCSVM

  • One-Class SVM
  • 데이터를 Mapping 한 뒤 정상 데이터를 원점에서 멀어지게 하는 방법
  • Feature Space에서 원점에서 가까운 데이터는 비정상 데이터
  • Feature Space에서 원점에서 먼 데이터는 정상 데이터

 

PCA

  • 차원 축소를 이용하는 방법
  • 차원 축소 전 원래 데이터의 위치와 비교하여 거리가 먼 데이터를 비정상으로 판단

 

 

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from imblearn.datasets import make_imbalance
from collections import Counter
from sklearn.model_selection import train_test_split
from sklearn.ensemble import IsolationForest
from sklearn.svm import OneClassSVM
from sklearn.decomposition import PCA

data, label = make_moons(n_samples=300, shuffle=True, noise=0.5, random_state=2021)

def ratio_func(y, multiplier, minority_class):
    target_stats = Counter(y)
    return {minority_class: int(multiplier * target_stats[minority_class])}

data, label = make_imbalance(
    data,
    label,
    sampling_strategy=ratio_func,
    **{"multiplier": 0.1, "minority_class": 1,}
)

train_x, test_x, train_y, test_y = train_test_split(data, label, test_size=0.3, stratify=label)

isol_forest = IsolationForest()
isol_forest.fit(train_x, train_y)
isol_test_pred = isol_forest.predict(test_x)
isol_test_acc = accuracy_score(test_y, isol_test_pred == -1)
isol_test_f1 = f1_score(test_y, isol_test_pred == -1)

ocsvm = OneClassSVM()
ocsvm.fit(train_x, train_y)
ocsvm_test_pred = ocsvm.predict(test_x)
ocsvm_test_acc = accuracy_score(test_y, ocsvm_test_pred == -1)
ocsvm_test_f1 = f1_score(test_y, ocsvm_test_pred == -1)

pca = PCA(n_components=1)
pca.fit(train_x)
test_latent = pca.transform(test_x)
test_recon = pca.inverse_transform(test_latent)
recon_diff = (test_x - test_recon) ** 2
pca_pred = recon_diff.mean(1)

plt.scatter(test_x[:, 0], test_x[:, 1], c=test_y)
plt.plot(test_recon[:, 0], test_recon[:, 1])

PCA 벡터

from sklearn.metrics import roc_curve, roc_auc_score, roc_curve

print('isol', isol_test_acc)
print('isol', isol_test_f1)
print('ocsvm ', ocsvm_test_acc)
print('ocsvm ', ocsvm_test_f1)
print('pca roc_auc :', roc_auc_score(test_y, pca_pred))

 

isol 0.68
isol 0.3333333333333333
ocsvm  0.54
ocsvm  0.25806451612903225
pca roc_auc : 0.8577777777777778

'머신러닝' 카테고리의 다른 글

[머신러닝] 추천 시스템  (0) 2022.05.12
[머신러닝] 차원축소  (0) 2022.05.10
[머신러닝] 군집화  (0) 2022.05.06
[머신러닝] SVM  (0) 2022.05.06
[머신러닝] K-Nearest Neighbors  (0) 2022.05.06

댓글