본문으로 건너뛰기

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 SVMIsolation Forest
학습 방식경계 학습격리 기반
시간 복잡도O(n²~n³)O(n log n)
대용량느림빠름
고차원커널 의존강건
파라미터gamma, nucontamination

장단점

장점:

  • 유연한 결정 경계 (커널)
  • 이론적 기반 탄탄
  • 비선형 경계 학습 가능

단점:

  • 대용량 데이터에서 느림
  • 파라미터 튜닝 필요 (gamma, nu)
  • 스케일링 필수

정리

  • One-Class SVM은 정상 데이터를 둘러싸는 경계 학습
  • RBF 커널이 가장 많이 사용됨
  • gamma: 경계의 복잡도, nu: 이상치 허용 비율
  • 스케일링 필수
  • 소규모~중규모 데이터에 적합

다음 글 예고

다음 글에서는 LOF 알고리즘 상세를 다룹니다.


PyCaret 머신러닝 마스터 시리즈 #069