비지도학습
import matplotlib.pyplot as plt; plt.clf()
import pandas as pd; import numpy as np
import seaborn as sns
df = sns.load_dataset('iris')
y = df['species']; X = df.drop('species', axis=1)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
클러스터링
| 속성 | K-Means | DBSCAN | GMM (Gaussian Mixture) | 계층 군집 (Hierarchical) |
|---|---|---|---|---|
| 핵심 원리 | 중심점 (Centroid) 기반 | 밀도 (Density) 기반 | 확률 분포 (Distribution) 기반 | 연결 (Connectivity) 기반 |
| 상세 작동 방식 | 개의 중심점을 잡고 가까운 데이터들을 할당하며 중심을 업데이트 | 일정 반경(eps) 내에 최소 개수(min_samples)의 데이터가 모여있는지 확인 | 데이터가 여러 가우시안 분포의 혼합으로 생성되었다 가정, EM 알고리즘으로 파라미터 최적화 | 데이터 간 거리를 계산해 가까운 것부터 병합(Bottom-up)하거나 분할, 단계적으로 진행 |
| 장점 및 특징 | 단순한 구현, 빠른 계산 속도, 대용량 데이터 효율성 | 임의의 복잡한 형태 탐색 가능, 노이즈 및 이상치 처리 탁월 | 연성 군집화(Soft Clustering) 지원, 다양한 기하학적 형태(타원형 등) 모델링 | 군집 수 사전 설정 불필요, 덴드로그램을 통한 시각적 해석 우수 |
| 단점 및 한계 | 군집 수() 사전 결정 필요, 이상치 및 비구형 분포에 취약 | 밀도 차이가 큰 데이터셋에서 성능 저하, 매개변수 설정의 민감도 | 군집 수() 사전 결정 필요, 초기값 민감도 및 지역 최적해 위험 | 높은 계산 복잡도(), 대용량 데이터 부적합, 병합 취소 불가 |
| 스케일링 | 필수 | 필수 | 이론상 불필요 | 필수 |
| 통계학적/수학적 근거 (거리 측정 방식) | 유클리디안 거리 기반 (분산이 큰 변수가 목적 함수 지배) | 단일 반경 기반 (스케일 다르면 밀도 탐색 왜곡) | 확률 밀도 기반 (공분산 행렬이 각 변수의 스케일을 자체적으로 보정) | 유클리디안/맨해튼 거리 기반 (연결법 계산 시 스케일 큰 변수에 종속) |
| 학습 패러다임 | 귀납적 (Inductive) | 변환적 (Transductive) | 귀납적 (Inductive) | 변환적 (Transductive) |
| 모델 파라미터 유무 | 존재함 (중심점) | 없음 (밀도 구조 의존) | 존재함 (평균, 공분산 등) | 없음 (계층 구조 의존) |
predict() 지원 여부 | 지원함 (새 데이터 할당 가능) | 지원 안 함 (에러 발생) | 지원함 (확률 기반 새 데이터 할당) | 지원 안 함 (에러 발생) |
fit() 후 labels_ 생성 | 생성됨 | 생성됨 | 생성 안 됨 (predict 호출 필수) | 생성됨 |
-
K-Means나 GMM은 학습이 끝나면 군집을 대표하는 파라미터(중심점 μ, 공분산 Σ)를 산출하여 메모리에 저장한다. 반면 계층형 군집은 이러한 절대적인 기준점을 만들지 않고, 오직 데이터 포인트들 간의 상대적인 거리만을 기록한 덴드로그램(트리 구조) 자체를 결과물로 삼는다. 기준점이 없으니 새로운 데이터가 들어왔을 때 비교할 대상이 없다.
-
계층형 군집화는 주어진 닫힌 데이터셋 내부의 기하학적 위상(Topology)만을 설명하는 데 특화된 변환적 학습(Transductive Learning) 알고리즘이다. 미래의 미지의 데이터(Unseen data)에 대한 일반화(Generalization) 모델을 만드는 것이 목적이 아니므로 predict가 존재하지 않는 것이 통계학적으로 지극히 타당하다.
- 거리 행렬(Distance Matrix)의 연쇄적 붕괴. 예를 들어, 멀리 떨어져 있던 두 군집 A와 B 사이에 가 나타나면, 단일 연결법(Single Linkage) 등에서는 이 새로운 점을 징검다리 삼아 A와 B가 원래 트리 구조와 전혀 다른 단계에서 조기에 병합되어 버린다. 즉, 트리의 뼈대 전체가 재구성되어야만 수학적 모순이 사라진다.
from sklearn.datasets import make_blobs
# # 1. 데이터 생성 (K-Means의 한계를 보여주기 위해 타원형 분포를 갖도록 선형 변환 적용)
X, _ = make_blobs(n_samples=500, centers=3, cluster_std=0.5, random_state=42)
X_train, _ = make_blobs(n_samples=500, centers=3, cluster_std=0.5, random_state=42)
X_test, _ = make_blobs(n_samples=500, centers=3, cluster_std=0.5, random_state=42)
# X, y_true = make_blobs(n_samples=500, centers=3, cluster_std=0.5, random_state=42)
# transformation = [[0.60834549, -0.63667341], [-0.40887718, 0.85253229]]
# X_aniso = np.dot(X, transformation)from sklearn.preprocessing import StandardScaler
if '전처리':
# 대부분의 머신러닝 알고리즘은 입력 데이터의 스케일에 민감하므로, 표준화가 필요함
# gmm 은 필수는 아니지만 일반적으로 진행
stdscaler = StandardScaler()
X_scaled = stdscaler.fit_transform(X)
if 'train_test':
X_train_scaled = stdscaler.fit_transform(X_train)
X_test_scaled = stdscaler.transform(X_test)if 'KMeans':
from sklearn.cluster import KMeans
kmeans = KMeans(
n_clusters=3, # 생성할 군집의 수 (k)
init='k-means++', # 초기 중심점 설정 방식
n_init=10, # 초기 중심점 무작위 선정을 몇 번 반복할 것인지
max_iter=300, # EM 알고리즘 최대 반복 횟수
random_state=42 # 결과 재현성을 위한 시드 고정
)
# 4. 모델 적합 및 군집 레이블 할당
kmeans.fit(X_scaled)
df['cluster_label'] = kmeans.labels_
# fit과 predict를 한 번에 수행하여 군집 레이블을 얻을 수도 있다.
# labels = kmeans.fit_predict(X_scaled)
# 5. 군집 중심점 확인 (원래 스케일로 복원)
centroids_scaled = kmeans.cluster_centers_
centroids_original = stdscaler.inverse_transform(centroids_scaled)
kmeans.cluster_centers_
kmeans.inertia_
kmeans.n_iter_
if 'DBSCAN':
from sklearn.cluster import DBSCAN
# eps: 반경, min_samples: 핵심 이웃의 최소 개수
dbscan = DBSCAN(eps=0.5, min_samples=5)
dbscan_labels = dbscan.fit_predict(X_scaled)
# label 출력, -1값은 이상치
dbscan.labels_
if 'GMM, Gaussian Mixture Model':
from sklearn.mixture import GaussianMixture
# 2. GMM 모델 객체 생성 및 학습 (EM 알고리즘 수행)
# n_components: 혼합할 가우시안 분포의 개수 (사전 정의할 군집 수 K)
# covariance_type: 공분산 행렬의 유형 설정 (기하학적 형태 결정)
# init_params: 초기화 방식 ('kmeans'를 통해 초기 중심점을 잡는 것이 일반적)
gmm = GaussianMixture(n_components=3, covariance_type='full'
, init_params='kmeans', random_state=42)
gmm.fit(X)
# 3. 군집 예측 및 확률 계산
# Hard Clustering: 가장 확률이 높은 군집의 인덱스 반환
labels = gmm.predict(X)
df['gmm_cluster'] = labels
df['gmm_cluster'].value_counts() # 군집별 할당된 데이터 개수
# Soft Clustering: 각 데이터가 K개의 군집에 속할 사후 확률(Posterior Probability) 반환
probs = gmm.predict_proba(X)
# 5. 군집화 성능 평가 (실루엣 계수)
# 주의: GMM은 밀도/확률 기반 모델이므로 거리 기반 지표인 실루엣 계수가
# K-Means만큼 정확하게 모델의 성능을 대변하지 않을 수 있으나, 참고 지표로 산출 가능.
sil_score = silhouette_score(X, labels)
# 계층적 클러스터링
if '덴드로그램':
if '병합적 계층 군집화의 계층 구조 생성':
from scipy.cluster.hierarchy import linkage, dendrogram
# import matplotlib.pyplot as plt; plt.clf()
# from scipy.cluster.hierarchy import dendrogram, linkage
# 링크 생성
linked = linkage(X_scaled, method='ward', metric='euclidean')
# 덴드로그램 작성
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(12, 8))
# 목표 군집 개수 설정
k = 3
# linked 행렬의 구조: [군집1, 군집2, 병합거리, 포함된 관측치 수]
# 뒤에서 k번째 병합 거리가 하한(Lower Bound)
lower_bound = linked[-k, 2]
# 뒤에서 k-1번째 병합 거리가 상한(Upper Bound)
upper_bound = linked[-(k-1), 2]
t_3_clusters = linked[-(k-1), 2]
print(f"군집 3개를 형성하기 위한 거리(t)의 범위: {lower_bound:.4f} <= t < {upper_bound:.4f}")
dendrogram(linked,
orientation='top',
labels=X_scaled['location'].unique().tolist()[0:5],
distance_sort='descending',
color_threshold=t_3_clusters,
show_leaf_counts=True)
ax.set(title='Hierarchical Clustering Dendrogram based on ACF'
, xlabel='Country', ylabel='Euclidean Distance'
)
plt.xticks(rotation=90)
plt.tight_layout()
plt.show()
if '병합적 계층 군집화의 최종적인 데이터별 군집 할당':
from scipy.cluster.hierarchy import fcluster
# criterion='maxclust'로 설정하고, t 파라미터에 목표 군집 수인 3을 입력
cluster_labels = fcluster(linked, t=3, criterion='maxclust')
# # 혹은 연결 행렬(linked)을 바탕으로, 거리(distance) 3을 기준으로 트리를 절단하여 레이블 산출
# cluster_labels = fcluster(linked, t=3, criterion='distance')
# 기존 데이터프레임에 파생변수로 군집 레이블 할당
# df2_solve는 이전에 정의한 데이터프레임 객체라고 가정
X_scaled['Cluster_Ward'] = cluster_labels
print(X_scaled['Cluster_Ward'].value_counts())
if '병합적 접근, AgglomerativeClustering':
from sklearn.cluster import AgglomerativeClustering
agg_clustering = AgglomerativeClustering(
# linkage: 군집 간 병합 방식 (ward가 디폴트 및 권장)
linkage='ward'
# n_clusters: 도출할 최종 군집 수
# AgglomerativeClustering() 형태로 선언하면 자동으로 n_clusters=2
# n_clusters=3으로 군집 개수를 명시적으로 고정
, n_clusters=3
# distance_threshold: 클러스터 병합 시 거리 임계값
, distance_threshold=10
# 절단 근거가 최소한 하나는 있어야 한다
)
# 학습 및 군집 레이블 할당을 동시에 수행 (fit_predict)
agg_labels = agg_clustering.fit_predict(X_scaled)
# 결과 확인
df2_solve['Cluster_Agg'] = agg_labels.fit_predict(X_scaled)
print(df2_solve['Cluster_Agg'].value_counts())| 비교 항목 | 파라미터 생략 시 | 파라미터 지정 시 (color_threshold=n) |
|---|---|---|
| 적용 임계값 | 분석가가 지정한 값 | |
| 색상 구분 기준 | 기계적, 임의적 기준 | 분석가의 탐색적 결정에 따른 기준 |
| 주요 목적 | 단순히 트리의 형태를 보기 좋게 출력 | 최종 모델에 적용할 분할 기준의 시각적 검증 |
| 분석 신뢰도 | 낮음 (최적의 군집 수와 무관함) | 높음 (분석가의 근거가 개입됨) |
- 이를 통해 분석가는 “거리 n에서 트리를 잘랐을 때 데이터가 정확히 몇 개의 군집으로 나뉘고, 어떤 데이터들이 같은 군집으로 묶이는지”를 명확한 색상 대비로 확인할 수 있다.
- 이후 이 n 값은 AgglomerativeClustering의 distance_threshold 파라미터로 그대로 사용된다.
군집도, 분리도
| 평가 지표 및 탐색 기법 | 측정 기준 | 주요 특징 | 최적 상태 | 주요 적용 모델 |
|---|---|---|---|---|
| 실루엣 계수 (Silhouette) | 응집도 및 분리도 | 각 데이터 포인트와 군집들 간의 평균 거리를 계산, 응집도와 분리도를 동시에 수치화함 (고려함) | 1에 가까울수록 우수 (0은 군집경계, 음수는 잘못 할당됨을 의미) | K-Means, Hierarchical |
| Calinski-Harabasz Index | 분산 비율 (Variance Ratio) | (군집 간 분산, b/w) 과 (군집 내 분산, Within)의 비율을 계산함. | 값이 클수록 우수 (군집화가 잘됨) | K-Means, Hierarchical |
| Davies-Bouldin Index | 거리 비율 (Distance Ratio) | (군집 내 데이터 간의 평균 거리)와 (군집 중심 간의 거리) 비율을 산출함. | 값이 작을수록 우수 (0에 가까울수록/수렴할수록) | K-Means, Hierarchical |
| Inertia (SSE) | 응집도 (오차 제곱합) | 각 데이터 포인트와 해당 군집 중심점 간의 유클리디안 거리 제곱합. 분리도는 고려하지 않음. 오직 응집도만 나타냄. | 값이 작을수록 우수 | K-Means (전용) |
| AIC / BIC | 정보량 기준 (최대우도법) | 모델의 우도(Likelihood)를 극대화하면서 동시에 추정 파라미터 수 증가에 따른 과적합 페널티를 부여함. | 값이 작을수록 우수 | GMM (확률 생성 모델) |
| Elbow Method (엘보우 기법) | 군집 내 오차 제곱합(SSE) 변화율 | 군집 수()를 증가시키며 Inertia(SSE)를 계산하고, 그 감소 폭을 시각화함. 통계적 엄밀성보다는 경험적 판단에 의존함. | 그래프의 기울기가 급감하다 완만해지는 변곡점(Elbow) | K-Means |
| Dendrogram (덴드로그램) | 군집 간 병합 거리 (Linkage Distance) | 개별 데이터가 계층적(상향식)으로 병합되는 전체 과정을 트리(Tree) 구조로 시각화함. y축은 병합 시점의 거리를 나타냄. | 가장 긴 수직선(도약 구간)을 가로지르는 임계점(Threshold) | Hierarchical |
# if '군집 평가 지표 산출':
if '실루엣&엘보우':
lst_inertia, lst_silhouette = [], []
k_values = range(2, 21)
for k in k_values:
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
# 엘보우 방법 (비용 함수 값)
lst_inertia.append(kmeans.inertia_)
# 실루엣 계수
from sklearn.metrics import silhouette_score
silhouette_avg = silhouette_score(X, kmeans.labels_)
lst_silhouette.append(silhouette_avg)
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
if True:
axes[0].plot(k_values, lst_inertia, marker='o')
# 급격하게 완만해지는 클러스터로 선정
axes[0].set(title = '엘보우 방법')
axes[1].plot(k_values, lst_silhouette, marker='o')
# 가장 큰 값을 가지는 클러스터로 선정
axes[1].set(title = '실루엣 계수')
plt.tight_layout()
plt.show()
if 'Calinski-Harabasz, Variance Ratio Criterion':
from sklearn.metrics import calinski_harabasz_score
ch_score = calinski_harabasz_score(X_scaled, labels)
if 'Davies-Bouldin Index, DBI':
from sklearn.metrics import davies_bouldin_score
db_score = davies_bouldin_score(X_scaled, labels)
if 'Inertia':
# K-Means 전용 군집도 지표
inertia = kmeans.inertia_
if '덴드로그램':
plt.figure(figsize=(10, 7))
dendrogram(linked , color_threshold=n)
plt.xticks([])
plt.show()
# 4. 결과 출력
print(f"--- Clustering Evaluation Metrics ---")
print(f"Average Silhouette Score: {silhouette_avg:.4f}")
print(f"Calinski-Harabasz Index: {ch_score:.4f}")
print(f"Davies-Bouldin Index : {db_score:.4f}")
print(f"K-Means Inertia : {inertia:.4f}")연관분석
- 지지도(Support): 특정 아이템 집합이 전체 거래에서 차지하는 비율.
- 신뢰도(Confidence): A가 발생했을 때 B가 발생할 확률. (A → B)
- 향상도(Lift): A와 B가 독립적일 때, A가 발생했을 때 B가 발생할 확률의 비율.
| 알고리즘 | 작동 원리 및 통계/수학적 특징 | 장단점 및 ADP 실무 적용 가이드 |
|---|---|---|
| Apriori | 후보 생성(Candidate Generation) 방식. 개의 빈발 항목집합을 찾기 위해 개의 빈발 항목집합을 결합한다. 너비 우선 탐색(BFS) 기반. | 구현이 직관적이고 연관 분석의 기본이 되나, 매 단계마다 전체 데이터베이스를 스캔해야 하므로 데이터 크기에 따라 연산 비용이 기하급수적으로 증가한다. 소규모 데이터에 적합하다. |
| FP-Growth | 후보를 생성하지 않고 데이터를 FP-Tree(Frequent Pattern Tree)라는 접두사 트리(Prefix Tree) 구조로 압축한 뒤, 깊이 우선 탐색(DFS)을 통해 빈발 항목을 추출한다. 구조화된 데이터에서 빠르게 연관 규칙을 생성하는 데 사용됨. | 데이터베이스 스캔이 단 2회로 제한되어 Apriori보다 연산 속도가 압도적으로 빠르다. ADP 실기 시험에서 대규모 트랜잭션 데이터의 메모리 초과 방지 및 빠른 처리를 위해 최우선으로 사용해야 하는 알고리즘이다. |
| Eclat | 수직적 데이터 구조(Vertical Data Format) 활용. 각 항목이 포함된 트랜잭션 ID(TID) 목록의 교집합을 구하여 지지도 확률을 계산한다. | 깊이 우선 탐색(DFS)을 사용하며, TID 리스트의 교집합 연산만 수행하므로 속도가 빠르다. 단, mlxtend 패키지에서는 기본 지원하지 않으므로 실기 시험용으로는 부적합하다. |
| Association Rules (규칙 생성) | 도출된 빈발 항목 집합을 기반으로 신뢰도(Confidence), 향상도(Lift), 레버리지(Leverage) 등 통계적 조건부 확률 메트릭을 계산하여 기준치(Threshold) 이상의 규칙만 필터링한다. | 빈발 항목 탐색 후 필수적으로 수행해야 하는 단계다. Python mlxtend 패키지에서는 association_rules 함수로 구현되어 있으며, 평가 지표 중 Lift를 기준으로 정렬하여 결과의 타당성을 해석하는 것이 ADP 실기의 핵심이다. |
-
시간 복잡도 (Time Complexity): Apriori는 최악의 경우 (d는 고유 아이템 수)의 시간 복잡도를 가진다. 반면 FP-Growth는 후보 항목을 생성하지 않고 트리 구축 및 순회 시간에만 비례하므로 실행 시간을 비약적으로 단축시킨다.
-
공간 복잡도 (Space Complexity): Apriori는 수많은 후보 집합 배열을 메모리에 유지해야 하므로 병목 현상이 발생한다. FP-Growth는 트랜잭션을 압축한 FP-Tree 구축을 위한 메모리만 필요로 하므로 공간 효율성이 뛰어나다.
사용 라이브러리: Python에서는 mlxtend 패키지의 mlxtend.frequent_patterns 내 apriori, fpgrowth, association_rules 함수를 사용하는 것이 표준이다.
데이터 전처리: 제공된 범용 데이터셋들은 대부분 세로형(Long format)의 Raw 트랜잭션 형태다. mlxtend 알고리즘에 투입하기 위해서는 반드시 데이터를 groupby와 unstack을 활용하여 가로형(Wide format)의 One-Hot Encoded DataFrame(Item matrix)으로 변환하는 전처리 코드를 숙달해야 한다.
import pandas as pd; import random
random.seed(42)
data, items = [], ['Milk', 'Bread', 'Eggs', 'Butter', 'Cheese', 'Coke', 'Juice']
for i in range(10):
transaction = random.sample(items, random.randint(2, 4))
data.append(transaction)
print(data[:3])
from mlxtend.preprocessing import TransactionEncoder
te = TransactionEncoder()
te_array = te.fit(data).transform(data)
df = pd.DataFrame(te_array, columns=te.columns_)
df.head(2)[['Milk', 'Juice', 'Eggs', 'Bread'], ['Bread', 'Coke'], ['Coke', 'Juice']]
| Bread | Butter | Cheese | Coke | Eggs | Juice | Milk | |
|---|---|---|---|---|---|---|---|
| 0 | True | False | False | False | True | True | True |
| 1 | True | False | False | True | False | False | False |
# 장바구니 분석은, 무조건 ont-hot encoding 형태로 만들어야함
onehot = pd.get_dummies(df)
if 'apriori':
from mlxtend.frequent_patterns import apriori, association_rules
# 빈발 아이템셋 생성
frequent_itemsets = apriori(onehot, min_support=0.2, use_colnames=True)
# 연관 규칙 생성
rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1)
if 'FP-Growth':
from mlxtend.frequent_patterns import fpgrowth, association_rules
# FP-Growth 빈발 아이템셋 생성
frequent_itemsets = fpgrowth(onehot, min_support=0.2, use_colnames=True)
# 연관 규칙 생성
rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1)
rules.head()| antecedents | consequents | antecedent support | consequent support | support | confidence | lift | leverage | conviction | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | (Bread) | (Milk) | 0.5 | 0.4 | 0.3 | 0.600000 | 1.500000 | 0.10 | 1.5 |
| 1 | (Milk) | (Bread) | 0.4 | 0.5 | 0.3 | 0.750000 | 1.500000 | 0.10 | 2.0 |
| 2 | (Milk) | (Juice) | 0.4 | 0.6 | 0.3 | 0.750000 | 1.250000 | 0.06 | 1.6 |
| 3 | (Juice) | (Milk) | 0.6 | 0.4 | 0.3 | 0.500000 | 1.250000 | 0.06 | 1.2 |
| 4 | (Bread, Milk) | (Juice) | 0.3 | 0.6 | 0.2 | 0.666667 | 1.111111 | 0.02 | 1.2 |
이상치 탐지
세 알고리즘 모두 비지도 학습 기반의 이상치 탐지(Anomaly Detection) 목적으로 사용되지만, 내부적으로 이상치를 정의하고 찾아내는 수학적 원리가 완전히 다르다.
| 구분 | Isolation Forest | One-Class SVM | Local Outlier Factor (LOF) |
|---|---|---|---|
| 기반 알고리즘 | 트리 (Tree) 기반 | 서포트 벡터 머신 (SVM) 기반 | K-최근접 이웃 (KNN) / 밀도 기반 |
| 작동 원리 | 변수와 기준값을 무작위로 선택해 분할. 고립되기 쉬운(적은 분할 횟수) 관측치를 이상치로 판별 | 커널 트릭(RBF 등)으로 고차원 매핑 후, 정상 데이터를 감싸는 최소 크기의 구(Sphere) 형태 결정 경계 생성 | 개별 데이터의 밀도를 주변 이웃 데이터의 밀도와 상대적으로 비교하여 밀도가 현저히 낮은 곳을 이상치로 판별 |
| 이상치 판단 기준 | 짧은 평균 경로 길이 (Tree Depth) | 결정 경계 (Decision Boundary) 를 벗어나면 이상치 | 주변 이웃 대비 낮은 상대적 밀도 (LOF Score) |
| 장점 | 고차원 데이터에서 연산 속도가 빠르고 메모리 효율이 높음 (Baseline 표준) | 정규분포를 따르지 않거나 고도로 비선형적인 데이터의 정교한 경계 모델링 가능 | 전역적(Global) 기준이 아닌 국소적(Local) 밀도 차이를 반영하여 복잡한 군집 내 이상치 탐지 탁월 (데이터 내의 여러 군집 간의 밀도 차이를 반영한 국소적 이상치 탐지 특화, 조밀한 군집과 듬성한 군집이 혼재할 때 최적) |
| 단점 / 주의점 | 축에 평행한 분할만 수행하므로 복잡한 비선형 패턴 탐지에는 한계가 있을 수 있음 | 하이퍼파라미터 (여백크기 결정하는 nu, 커널굴곡 결정하는 gamma) 에 매우 민감하며, 대용량 데이터에서 행렬 연산 비용 급증 | 계산 복잡도가 높으며, 값(이웃 수) 설정에 따라 성능이 크게 좌우됨 |
| 메서드 | sklearn.ensemble.IsolationForest | sklearn.svm.OneClassSVM | sklearn.neighbors.LocalOutlierFactor |
Isolation Forest
-
데이터를 무작위로 선택한 변수와 무작위 기준값으로 반복해서 분할(Split)하여 개별 관측치를 고립시킨다.
-
정상 데이터는 군집해 있으므로 완전히 고립시키기 위해 트리의 깊이(분할 횟수)가 깊어져야 하지만, 이상치는 군집에서 떨어져 있어 트리의 상단(적은 분할 횟수)에서 쉽게 고립된다는 구조적 특성을 이용한다.
LOF
- 정상 데이터는 주변 이웃들과 비슷한 밀도를 가지지만, 이상치는 이웃들의 밀도에 비해 자신의 밀도가 현저히 낮게 산출되는 원리를 수치화(LOF Score)하여 탐지
.fit_predict()
LocalOutlierFactor(LOF)가 .fit() 메서드를 아예 사용하지 않는 것은 아니다.
정확히 말하면, 기본 설정(novelty=False)에서는 새로운 데이터에 대한 .predict() 기능을 지원하지 않기 때문에, 학습과 탐지를 동시에 수행하는 .fit_predict()를 주로 사용하는 것이다.
이렇게 설계된 핵심적인 수학적, 구조적 이유는 다음과 같다.
1. 밀도 계산의 상대성 (K-Nearest Neighbors 구조 변동)
- Isolation Forest나 One-Class SVM은 학습(fit) 과정을 통해 데이터를 나누는 ‘분할 기준(Tree)‘이나 ‘결정 경계(Hyperplane)‘라는 고정된 규칙을 생성하여 메모리에 저장한다.
- 반면, LOF는 사전에 고정되는 규칙이 없다. 오직 주어진 데이터 셋 내부에서 특정 데이터가 주변 K개의 이웃과 비교해 밀도가 얼마나 옅은지를 상대적으로 계산한다.
- 만약 기존 데이터로 모델을 .fit() 해둔 상태에서 새로운 데이터가 유입된다면, 기존 데이터들의 K-최근접 이웃 구조와 국소 도달 밀도(Local Reachability Density)가 연쇄적으로 변동된다.
- 즉, 새로운 데이터가 들어오는 순간 기존에 계산해 둔 밀도 기준이 무용지물이 되기 때문에 사전에 모델을 ‘학습시켜 고정한다’는 개념이 성립하지 않는다.
2. 이상치 탐지(Outlier Detection)의 목적
- LOF의 기본 형태는 훈련 셋과 테스트 셋이 분리되지 않은 단일 데이터 셋 내에서 오염된(이상치) 데이터를 솎아내는 **이상치 탐지(Outlier Detection)**에 맞춰져 있다.
- 따라서 전체 데이터를 한 번에 넣고 밀도를 계산해 결과를 반환하는 fit_predict()를 사용하는 것이 알고리즘의 본래 목적에 부합한다.
예외: 참신성 탐지 (Novelty Detection)
-
만약 훈련 데이터(정상 데이터)만으로 밀도 기준을 고정하고, 나중에 들어오는 새로운 테스트 데이터가 이상치인지 판별하는 참신성 탐지(Novelty Detection) 목적으로 LOF를 사용해야 한다면 파라미터를 변경해야 한다.
-
단, 이 방식을 사용할 때는 새로운 데이터(X_test)가 기존 훈련 데이터(X_train)의 이웃 구조에 영향을 미치지 않는다는 수학적 가정이 강제 적용된다.
| 구분 | 이상치 탐지 (Outlier Detection, 기본 설정) | 참신성 탐지 (Novelty Detection, 예외 설정) |
|---|---|---|
| 목적 | 단일 데이터셋 내부의 오염된 데이터(이상치) 식별 | 정상 데이터로만 구성된 훈련셋 학습 후, 새로운 데이터의 이상 여부 판별 |
| 파라미터 설정 | novelty=False (기본값) | novelty=True |
| 주요 메서드 | .fit_predict(X) | .fit(X_train) 적용 후 .predict(X_test) |
| 수학적 모델 고정 여부 | 불가능 (고정된 결정 경계나 분할 규칙이 없음) | 강제 고정 (훈련 데이터의 구조를 기준점으로 동결) |
| 밀도 및 이웃 구조 연산 | 데이터가 추가/변경될 때마다 -최근접 이웃 구조와 국소 도달 밀도(LRD)가 연쇄적으로 변동됨 | 새로운 테스트 데이터가 유입되더라도 기존 훈련 데이터의 이웃 구조와 LRD에 영향을 주지 않는다고 수학적으로 강제 가정함 |
| 타 알고리즘과의 비교 | Isolation Forest, One-Class SVM은 학습 시 고정된 규칙(Tree, Hyperplane)을 생성하므로 기본적으로 예측 분리 가능 | (동일) |
if 'IsolationForest':
from sklearn.ensemble import IsolationForest
model = IsolationForest(contamination=0.1, random_state=42)
if 'OneClassSVM':
from sklearn.svm import OneClassSVM
model = OneClassSVM(gamma='auto', nu=0.1)
if 'LocalOutlierFactor':
from sklearn.neighbors import LocalOutlierFactor
model = LocalOutlierFactor(n_neighbors=20)
model.fit(X)
predictions = model.predict(X) #이상치 -1로
# predictions = model.fit_predict(X) # 이상치 -1로
if 'novelty':
from sklearn.neighbors import LocalOutlierFactor
# novelty=True 로 설정하면 fit 과 predict 를 분리해서 사용할 수 있다.
lof_novelty = LocalOutlierFactor(novelty=True)
# 1. 정상 데이터로만 밀도 기준 학습 및 고정
lof_novelty.fit(X_train)
# 2. 새로운 데이터에 대한 이상치 여부 예측
y_pred_test = lof_novelty.predict(X_test)