🤖AI/ML

SVM

Support Vector Machine (서포트 벡터 머신)

최적의 결정 경계(hyperplane)를 찾는 분류 알고리즘. 커널 트릭으로 비선형 분류 가능. 전통 ML의 대표 기법.

📖 상세 설명

SVM(Support Vector Machine, 서포트 벡터 머신)은 두 클래스를 분리하는 최적의 초평면(hyperplane)을 찾는 지도학습 알고리즘입니다. "최적"이란 마진(margin), 즉 결정 경계와 가장 가까운 데이터 포인트 사이의 거리를 최대화하는 것을 의미합니다. 이 가장 가까운 점들을 서포트 벡터(Support Vectors)라고 부르며, 결정 경계는 오직 이 서포트 벡터들에 의해서만 결정됩니다.

1992년 Vladimir Vapnik과 Corinna Cortes에 의해 개발되어, 딥러닝 이전 시대(2000-2012)에 가장 강력한 분류 알고리즘으로 군림했습니다. 통계적 학습 이론(VC dimension)에 기반한 탄탄한 수학적 토대를 가지며, 구조적 위험 최소화(SRM) 원리로 과적합을 방지합니다. 고차원 데이터에서도 잘 동작하여 텍스트 분류의 표준이었습니다. 딥러닝 등장 후에도 소규모 데이터셋, 해석 가능성이 중요한 도메인에서 여전히 사용됩니다.

커널 트릭(Kernel Trick)은 SVM의 핵심 확장입니다. 원본 공간에서 선형 분리가 불가능한 데이터를 더 높은(무한) 차원으로 암시적으로 매핑해 분리 가능하게 만듭니다. RBF(Radial Basis Function, Gaussian), Polynomial, Sigmoid 등 다양한 커널이 있으며, 실제로 고차원 계산을 하지 않고 커널 함수 K(x_i, x_j)로 내적만 계산합니다. phi(x)를 명시적으로 계산하지 않아도 되는 이 트릭이 SVM의 효율성 비결입니다.

텍스트 분류(스팸 필터), 이미지 분류(얼굴 인식), 생물정보학(유전자 발현 분석), 금융(사기 탐지, 신용 평가) 등에 활용됩니다. 특히 피처 수가 샘플 수보다 많은 경우(p >> n, 유전자 데이터 등)에 효과적입니다. 단, 대규모 데이터에서는 학습 복잡도가 O(n^2~n^3)로 증가하여 10만 샘플 이상에서는 LinearSVC나 SGDClassifier 사용을 권장합니다.

💻 코드 예제

from sklearn import svm
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report

# 데이터 생성 및 전처리
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 스케일링 (SVM에 필수!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 기본 SVM (RBF 커널)
svm_rbf = svm.SVC(kernel='rbf', C=1.0, gamma='scale')
svm_rbf.fit(X_train_scaled, y_train)

# 선형 SVM (대규모 데이터에 적합)
svm_linear = svm.LinearSVC(C=1.0, max_iter=10000)
svm_linear.fit(X_train_scaled, y_train)

# 하이퍼파라미터 튜닝
param_grid = {
    'C': [0.1, 1, 10, 100],
    'gamma': ['scale', 'auto', 0.01, 0.1],
    'kernel': ['rbf', 'poly']
}

grid_search = GridSearchCV(
    svm.SVC(),
    param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1
)
grid_search.fit(X_train_scaled, y_train)

print(f"최적 파라미터: {grid_search.best_params_}")
print(f"최적 점수: {grid_search.best_score_:.4f}")

# 평가
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test_scaled)
print(classification_report(y_test, y_pred))

# 서포트 벡터 확인
print(f"서포트 벡터 수: {len(best_model.support_)}")
# 서포트 벡터가 적을수록 일반화 성능이 좋음

# === 대규모 데이터용 SGD 기반 SVM ===
from sklearn.linear_model import SGDClassifier

# hinge loss = 선형 SVM과 동일
sgd_svm = SGDClassifier(
    loss='hinge',           # SVM과 동일한 손실 함수
    penalty='l2',           # L2 정규화
    alpha=1e-4,             # 정규화 강도 (1/C)
    max_iter=1000,
    tol=1e-3,
    random_state=42
)
sgd_svm.fit(X_train_scaled, y_train)

# 100만 샘플도 빠르게 학습 가능!
print(f"SGD SVM 정확도: {sgd_svm.score(X_test_scaled, y_test):.4f}")

# === 다중 클래스 SVM ===
from sklearn.multiclass import OneVsRestClassifier

# SVM은 기본적으로 이진 분류이므로 다중 클래스는 One-vs-Rest 사용
multi_svm = OneVsRestClassifier(
    svm.SVC(kernel='rbf', C=1.0, gamma='scale')
)
# OneVsOneClassifier도 가능 (클래스 쌍마다 분류기)

# === SVM 회귀 (SVR) ===
from sklearn.svm import SVR

svr = SVR(kernel='rbf', C=100, gamma=0.1, epsilon=0.1)
# epsilon: 오차 허용 범위 (epsilon-tube)
svr.fit(X_train_scaled, y_train_regression)

# === 커널 함수 시각화 이해 ===
# RBF 커널: K(x,y) = exp(-gamma * ||x-y||^2)
# - gamma 높음: 결정 경계 복잡 (과적합 위험)
# - gamma 낮음: 결정 경계 부드러움 (과소적합 위험)

# Polynomial 커널: K(x,y) = (gamma * x^T y + coef0)^degree
poly_svm = svm.SVC(kernel='poly', degree=3, coef0=1)

# === 불균형 데이터 처리 ===
from sklearn.svm import SVC

# class_weight='balanced'로 자동 가중치 적용
imbalanced_svm = SVC(
    kernel='rbf',
    class_weight='balanced',  # 소수 클래스에 높은 가중치
    C=1.0
)
# 또는 수동 설정: class_weight={0: 1, 1: 10}

# === 확률 출력 (probability=True) ===
prob_svm = SVC(kernel='rbf', probability=True)
prob_svm.fit(X_train_scaled, y_train)

# 예측 확률 출력 (Platt scaling 적용)
probs = prob_svm.predict_proba(X_test_scaled)
print(f"클래스 0 확률: {probs[0, 0]:.2f}")
print(f"클래스 1 확률: {probs[0, 1]:.2f}")

# === 결정 경계 시각화 (2D) ===
import matplotlib.pyplot as plt
import numpy as np

def plot_decision_boundary(model, X, y):
    """SVM 결정 경계 시각화"""
    h = 0.02
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    plt.contourf(xx, yy, Z, alpha=0.4)
    plt.scatter(X[:, 0], X[:, 1], c=y, alpha=0.8)
    plt.title("SVM Decision Boundary")

# === 최신 활용 사례 ===
# 딥러닝 시대에도 SVM은 여전히 사용됨:
# 1. 생물정보학: 유전자 발현 데이터 (피처 > 샘플)
# 2. 텍스트 분류: 스팸 필터, 감성 분석 (TF-IDF + SVM)
# 3. 이상 탐지: One-class SVM
# 4. 앙상블: SVM을 base learner로 사용

# One-class SVM (이상 탐지)
from sklearn.svm import OneClassSVM

anomaly_detector = OneClassSVM(nu=0.1, kernel='rbf')
anomaly_detector.fit(X_normal)  # 정상 데이터만으로 학습
predictions = anomaly_detector.predict(X_test)  # -1: 이상, 1: 정상

# === SVM vs 딥러닝 언제 선택? ===
# SVM이 좋은 경우:
# - 데이터 < 10,000 샘플
# - 피처 > 샘플 수 (고차원)
# - 해석 가능성 필요
# - GPU 없는 환경

# 딥러닝이 좋은 경우:
# - 데이터 > 100,000 샘플
# - 이미지, 텍스트, 시퀀스 데이터
# - 복잡한 비선형 패턴

🗣️ 실무에서 이렇게 말하세요

💬 모델 선택 논의에서
"데이터가 1,000개 미만이고 피처가 많으면 SVM 써보세요. 딥러닝은 오버피팅 될 가능성 높고, Random Forest보다 SVM이 이론적으로 일반화 보장이 더 좋아요. RBF 커널에 C, gamma 그리드서치 돌리면 됩니다."
💬 면접에서
"SVM은 마진을 최대화하는 초평면을 찾는 알고리즘입니다. 커널 트릭으로 비선형 분류가 가능한데, 실제로 고차원 매핑을 하지 않고 커널 함수의 내적만 계산합니다. RBF 커널의 gamma는 결정 경계의 유연성을 조절하고, C는 마진 오류와 마진 크기의 트레이드오프를 제어합니다."
💬 성능 문제 해결에서
"SVM 학습이 너무 오래 걸리면 LinearSVC로 바꾸거나 SGDClassifier에 hinge loss 쓰세요. 10만 개 이상이면 커널 SVM은 현실적으로 어렵고, 근사 커널(Nystrom, Random Fourier Features)이나 딥러닝이 나아요."

⚠️ 흔한 실수 & 주의사항

피처 스케일링 생략

SVM은 거리 기반이라 피처 스케일에 민감합니다. StandardScaler나 MinMaxScaler로 반드시 정규화하세요.

대규모 데이터에 커널 SVM 사용

커널 SVM의 학습 복잡도는 O(n^2~n^3)입니다. 10만 개 이상이면 LinearSVC나 SGDClassifier를 사용하세요.

올바른 방법

C와 gamma는 반드시 교차 검증으로 튜닝하세요. C가 너무 크면 오버피팅, gamma가 너무 크면 결정 경계가 복잡해져 오버피팅됩니다.

🔗 관련 용어

📚 더 배우기