069 One-Class SVM 상세
키워드: One-Class SVM, ocsvm
개요
One-Class SVM은 정상 데이터만으로 경계를 학습하는 이상치 탐지 알고리즘입니다. 정상 데이터를 둘러싸는 결정 경계를 만들어 경계 밖의 데이터를 이상치로 판단합니다.
실습 환경
- Python 버전: 3.11 권장
- 필요 패키지:
pycaret[full]>=3.0
One-Class SVM 원리
핵심 아이디어
목표: 정상 데이터를 최대한 포함하면서
원점에서 가장 멀리 떨어진 초평면 찾기
╭──────────────────╮
╱ ●●●●● ╲
│ ●●●●●●●● │ 정상 영역
│ ●●●●●● │ (Hyperplane 내부)
╲ ●●●● ╱
╰──────────────────╯
★ ★ 이상치
(Hyperplane 외부)
수학적 표현
특성 공간에서:
- 정상 데이터를 원점에서 분리
- margin을 최대화하는 hyperplane 찾기
min (1/2)||w||² + (1/νn)Σξᵢ - ρ
subject to:
w·φ(xᵢ) ≥ ρ - ξᵢ
ξᵢ ≥ 0
ν: 이상치 비율의 상한 (soft margin)
PyCaret에서 One-Class SVM
from pycaret.anomaly import *
import pandas as pd
import numpy as np
# 069 데이터 생성
np.random.seed(42)
normal = np.random.randn(1000, 3)
outliers = np.random.uniform(-4, 4, (50, 3))
data = pd.DataFrame(np.vstack([normal, outliers]), columns=['F1', 'F2', 'F3'])
# 069 환경 설정
anomaly = setup(data, session_id=42, verbose=False)
# 069 One-Class SVM
ocsvm = create_model('svm')
# 069 fraction 지정
ocsvm_5pct = create_model('svm', fraction=0.05)
주요 하이퍼파라미터
from sklearn.svm import OneClassSVM
# 069 kernel: 커널 함수 (기본 'rbf')
ocsvm = OneClassSVM(kernel='rbf')
ocsvm_linear = OneClassSVM(kernel='linear')
ocsvm_poly = OneClassSVM(kernel='poly', degree=3)
# 069 gamma: RBF 커널 파라미터 (기본 'scale')
ocsvm = OneClassSVM(gamma='scale') # 1 / (n_features * X.var())
ocsvm = OneClassSVM(gamma='auto') # 1 / n_features
ocsvm = OneClassSVM(gamma=0.1)
# 069 nu: 이상치 비율 상한 (기본 0.5)
# 069 - 학습 데이터 중 이상치로 분류될 비율의 상한
# 069 - 지지 벡터 비율의 하한
ocsvm = OneClassSVM(nu=0.05)
ocsvm = OneClassSVM(nu=0.1)
결정 경계 시각화
from sklearn.svm import OneClassSVM
import numpy as np
import matplotlib.pyplot as plt
# 2D 데이터 생성
np.random.seed(42)
X_train = np.random.randn(200, 2)
# 069 모델 학습
ocsvm = OneClassSVM(kernel='rbf', nu=0.1, gamma='scale')
ocsvm.fit(X_train)
# 069 그리드 생성
xx, yy = np.meshgrid(np.linspace(-4, 4, 200), np.linspace(-4, 4, 200))
Z = ocsvm.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 069 시각화
plt.figure(figsize=(10, 8))
plt.contourf(xx, yy, Z, levels=np.linspace(Z.min(), 0, 7), cmap='Blues')
plt.contour(xx, yy, Z, levels=[0], linewidths=2, colors='red')
plt.contourf(xx, yy, Z, levels=[0, Z.max()], colors='orange', alpha=0.3)
plt.scatter(X_train[:, 0], X_train[:, 1], c='white', s=20, edgecolors='black')
plt.title('One-Class SVM Decision Boundary')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.colorbar(label='Decision Function')
plt.savefig('ocsvm_boundary.png', dpi=150)
gamma 파라미터 영향
from sklearn.svm import OneClassSVM
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
X_train = np.random.randn(200, 2)
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
gammas = [0.01, 0.1, 1.0]
for ax, gamma in zip(axes, gammas):
ocsvm = OneClassSVM(kernel='rbf', nu=0.1, gamma=gamma)
ocsvm.fit(X_train)
xx, yy = np.meshgrid(np.linspace(-4, 4, 100), np.linspace(-4, 4, 100))
Z = ocsvm.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, levels=np.linspace(Z.min(), 0, 7), cmap='Blues')
ax.contour(xx, yy, Z, levels=[0], linewidths=2, colors='red')
ax.scatter(X_train[:, 0], X_train[:, 1], c='white', s=20, edgecolors='black')
ax.set_title(f'gamma = {gamma}')
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
plt.tight_layout()
plt.savefig('ocsvm_gamma.png', dpi=150)
# 069 gamma 작음: 부드러운 경계
# 069 gamma 큼: 복잡한 경계 (과적합 위험)
nu 파라미터 영향
from sklearn.svm import OneClassSVM
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
X_train = np.random.randn(200, 2)
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
nus = [0.01, 0.1, 0.3]
for ax, nu in zip(axes, nus):
ocsvm = OneClassSVM(kernel='rbf', nu=nu, gamma='scale')
ocsvm.fit(X_train)
xx, yy = np.meshgrid(np.linspace(-4, 4, 100), np.linspace(-4, 4, 100))
Z = ocsvm.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, levels=np.linspace(Z.min(), 0, 7), cmap='Blues')
ax.contour(xx, yy, Z, levels=[0], linewidths=2, colors='red')
ax.scatter(X_train[:, 0], X_train[:, 1], c='white', s=20, edgecolors='black')
# 이상치로 분류된 학습 데이터
outlier_mask = ocsvm.predict(X_train) == -1
ax.scatter(X_train[outlier_mask, 0], X_train[outlier_mask, 1],
c='red', marker='x', s=100)
ax.set_title(f'nu = {nu} (outliers: {outlier_mask.sum()})')
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
plt.tight_layout()
plt.savefig('ocsvm_nu.png', dpi=150)
# 069 nu 작음: 타이트한 경계
# 069 nu 큼: 느슨한 경계 (더 많은 이상치 허용)
커널 비교
from sklearn.svm import OneClassSVM
from sklearn.metrics import f1_score
import numpy as np
import pandas as pd
# 069 데이터 생성
np.random.seed(42)
normal = np.random.randn(500, 5)
outliers = np.random.uniform(-4, 4, (50, 5))
X = np.vstack([normal, outliers])
y_true = np.array([0] * 500 + [1] * 50)
# 069 커널별 비교
kernels = ['linear', 'rbf', 'poly', 'sigmoid']
results = []
for kernel in kernels:
try:
if kernel == 'poly':
ocsvm = OneClassSVM(kernel=kernel, nu=0.1, degree=3)
else:
ocsvm = OneClassSVM(kernel=kernel, nu=0.1)
ocsvm.fit(X)
y_pred = ocsvm.predict(X)
y_pred = (y_pred == -1).astype(int)
f1 = f1_score(y_true, y_pred)
results.append({'Kernel': kernel, 'F1': f1})
except Exception as e:
print(f"{kernel} 오류: {e}")
df = pd.DataFrame(results)
print("커널별 성능:")
print(df.round(4))
스케일링의 중요성
from sklearn.svm import OneClassSVM
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import f1_score
import numpy as np
# 069 스케일이 다른 특성
np.random.seed(42)
normal = np.column_stack([
np.random.randn(500) * 1, # 범위 작음
np.random.randn(500) * 100, # 범위 큼
np.random.randn(500) * 1000 # 범위 매우 큼
])
outliers = np.column_stack([
np.random.uniform(-3, 3, 50),
np.random.uniform(-300, 300, 50),
np.random.uniform(-3000, 3000, 50)
])
X = np.vstack([normal, outliers])
y_true = np.array([0] * 500 + [1] * 50)
# 069 스케일링 없이
ocsvm_raw = OneClassSVM(nu=0.1)
ocsvm_raw.fit(X)
y_pred_raw = (ocsvm_raw.predict(X) == -1).astype(int)
# 069 스케일링 후
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
ocsvm_scaled = OneClassSVM(nu=0.1)
ocsvm_scaled.fit(X_scaled)
y_pred_scaled = (ocsvm_scaled.predict(X_scaled) == -1).astype(int)
print(f"스케일링 없이 F1: {f1_score(y_true, y_pred_raw):.4f}")
print(f"스케일링 후 F1: {f1_score(y_true, y_pred_scaled):.4f}")
PyCaret 종합 예제
from pycaret.anomaly import *
import pandas as pd
import numpy as np
from sklearn.metrics import f1_score, precision_score, recall_score
# 069 데이터 생성
np.random.seed(42)
normal = np.random.randn(950, 4)
outliers = np.random.uniform(-4, 4, (50, 4))
data = pd.DataFrame(np.vstack([normal, outliers]),
columns=['F1', 'F2', 'F3', 'F4'])
y_true = np.array([0] * 950 + [1] * 50)
# 069 환경 설정 (스케일링 포함)
anomaly = setup(data, normalize=True, session_id=42, verbose=False)
# 069 One-Class SVM
ocsvm = create_model('svm', fraction=0.05)
# 069 결과 확인
result = assign_model(ocsvm)
y_pred = result['Anomaly'].values
print("One-Class SVM 결과:")
print(f"Precision: {precision_score(y_true, y_pred):.4f}")
print(f"Recall: {recall_score(y_true, y_pred):.4f}")
print(f"F1: {f1_score(y_true, y_pred):.4f}")
One-Class SVM vs Isolation Forest
from pycaret.anomaly import *
import pandas as pd
import numpy as np
from sklearn.metrics import f1_score
import time
# 069 데이터 생성
np.random.seed(42)
normal = np.random.randn(1000, 10)
outliers = np.random.uniform(-4, 4, (50, 10))
data = pd.DataFrame(np.vstack([normal, outliers]))
y_true = np.array([0] * 1000 + [1] * 50)
anomaly = setup(data, normalize=True, session_id=42, verbose=False)
# 069 Isolation Forest
start = time.time()
iforest = create_model('iforest', fraction=0.05)
iforest_result = assign_model(iforest)
iforest_time = time.time() - start
# 069 One-Class SVM
start = time.time()
ocsvm = create_model('svm', fraction=0.05)
ocsvm_result = assign_model(ocsvm)
ocsvm_time = time.time() - start
print("=== 비교 ===")
print(f"\nIsolation Forest:")
print(f" F1: {f1_score(y_true, iforest_result['Anomaly']):.4f}")
print(f" 시간: {iforest_time:.2f}초")
print(f"\nOne-Class SVM:")
print(f" F1: {f1_score(y_true, ocsvm_result['Anomaly']):.4f}")
print(f" 시간: {ocsvm_time:.2f}초")
| 항목 | One-Class SVM | Isolation Forest |
|---|---|---|
| 학습 방식 | 경계 학습 | 격리 기반 |
| 시간 복잡도 | O(n²~n³) | O(n log n) |
| 대용량 | 느림 | 빠름 |
| 고차원 | 커널 의존 | 강건 |
| 파라미터 | gamma, nu | contamination |
장단점
장점:
- 유연한 결정 경계 (커널)
- 이론적 기반 탄탄
- 비선형 경계 학습 가능
단점:
- 대용량 데이터에서 느림
- 파라미터 튜닝 필요 (gamma, nu)
- 스케일링 필수
정리
- One-Class SVM은 정상 데이터를 둘러싸는 경계 학습
- RBF 커널이 가장 많이 사용됨
- gamma: 경계의 복잡도, nu: 이상치 허용 비율
- 스케일링 필수
- 소규모~중규모 데이터에 적합
다음 글 예고
다음 글에서는 LOF 알고리즘 상세를 다룹니다.
PyCaret 머신러닝 마스터 시리즈 #069