SVM은 이진 분류 작업을 위한 기계 학습 모델로, 특성 공간에서 최대 간격의 초평면을 생성할 수 있는 모델 구조를 가집니다.
SVM 모델을 구성하는 핵심은 두 개의 서로 다른 클래스 샘플 간의 간격을 최대화하는 초평면을 찾는 것입니다. 일반적으로 간격 최대화 방식의 최적화 계산을 통해 SVM을 구성합니다.
1. 선형 분리 가능
------
작은 샘플, 비선형 및 고차원 패턴 인식 문제에 적합합니다.
어느 분리선이 더 나은가요?
d는 초평면이 전체 데이터셋에 대한 "분류 여유도"를 반영하며, d가 클수록 초평면이 모든 샘플로부터 더 "안전한 거리"를 유지하여 분류 안정성이 향상됩니다.
**데이터셋의 최소 함수 간격을 최대화하여 모든 샘플에 대한 가장 "강건한" 최적 초평면을 찾는 것이 SVM 분류 모델의 핵심 최적화 로직입니다**
2. 선형 분리 불가능
--------
실험 1 - 선형 SVM 분류
# 필요한 라이브러리 임포트
import numpy as np # 수치 계산용
import matplotlib.pyplot as plt # 데이터 시각화용
from sklearn.datasets import make_blobs # 시뮬레이션 데이터셋 생성용
# 한글 폰트 설정
plt.rcParams['font.sans-serif'] = ['Malgun Gothic', 'AppleGothic', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False # 마이너스 기호 표시 문제 해결
# 2차원 분류 데이터셋 생성
# n_samples=50: 50개의 샘플 생성
# centers=2: 2개의 클러스터 중심 (이진 분류 문제)
# random_state=0: 재현성을 위한 난수 시드
# cluster_std=0.6: 데이터 분산 정도 제어
X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)
# 생성된 데이터셋 시각화
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plt.title("생성된 이진 분류 데이터셋")
plt.show()
# 여러 가능한 분리선 시각화
xfit = np.linspace(-1, 3.5) # x축 좌표 생성
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
# 세 가지 다른 직선을 분리선으로 시각화
plt.plot(xfit, xfit + 0.65, '-k') # 직선 1: y = x + 0.65
plt.plot(xfit, 0.5 * xfit + 1.6, '-k') # 직선 2: y = 0.5x + 1.6
plt.plot(xfit, -0.2 * xfit + 2.9, '-k') # 직선 3: y = -0.2x + 2.9
plt.xlim(-1, 3.5)
plt.title("다양한 가능한 분리 경계")
plt.show()
# SVM 분류기 임포트
from sklearn.svm import SVC # 지원 벡터 머신 분류기
# SVM 모델 구축
# kernel='linear': 선형 커널 함수 사용
# C=1E10: 정규화 매개변수 (값이 클수록 오분류에 대한 패널티가 강함)
svm_model = SVC(kernel='linear', C=1E10)
svm_model.fit(X, y) # 훈련 데이터로 모델 학습
# SVM 결정 경계 시각화 함수 정의
def visualize_svm_boundary(model, ax=None, show_support=True):
"""
2D SVM의 결정 함수와 지원 벡터 시각화
매개변수:
model: 훈련된 SVM 모델
ax: 그래프 좌표축 객체
show_support: 지원 벡터 표시 여부
"""
if ax is None:
ax = plt.gca() # 현재 좌표축 가져오기
# 좌표축 범위 가져오기
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# 결정 함수 계산을 위한 그리드 생성
x = np.linspace(xlim[0], xlim[1], 30)
y = np.linspace(ylim[0], ylim[1], 30)
Y, X = np.meshgrid(y, x)
# 그리드 포인트를 2D 배열로 변환하고 결정 함수 계산
xy = np.vstack([X.ravel(), Y.ravel()]).T
P = model.decision_function(xy).reshape(X.shape)
# 결정 경계와 간격선 그리기
ax.contour(X, Y, P, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])
# 필요시 지원 벡터 표시
if show_support:
ax.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1],
s=300, linewidth=1, facecolors='none')
# 좌표축 범위 유지
ax.set_xlim(xlim)
ax.set_ylim(ylim)
# SVM 최적 분리 경계 시각화
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
visualize_svm_boundary(svm_model)
plt.title("SVM 최적 분리 경계 (지원 벡터 포함)")
plt.show()
# 다른 샘플 수에서 SVM 분류 효과 시각화 함수
def plot_svm_with_samples(sample_count=10, axis=None):
"""다양한 샘플 수에서 SVM 분류 결과 시각화"""
# 200개 샘플 데이터셋 생성
X, y = make_blobs(n_samples=200, centers=2, random_state=0, cluster_std=0.60)
X = X[:sample_count] # 지정된 수의 샘플만 사용
y = y[:sample_count]
# SVM 모델 훈련
model = SVC(kernel='linear', C=1E10)
model.fit(X, y)
# 좌표축 설정
axis = axis or plt.gca()
axis.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
axis.set_xlim(-1, 4)
axis.set_ylim(-1, 6)
visualize_svm_boundary(model, axis)
# 두 서브플롯으로 다른 샘플 수 비교
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
# 각각 60개와 120개 샘플로 SVM 분류 결과 시각화
for axis, count in zip(ax, [60, 120]):
plot_svm_with_samples(count, axis)
axis.set_title(f'샘플 수 N = {count}')
plt.show()
실험 2 - 선형 분리 불가능 - 커널 트릭
# -*- coding: utf-8 -*-
"""
SVM을 사용한 비선형 데이터 처리 예제
선형 커널과 RBF 커널의 비선형 분류 작업에서의 성능 차이를 보여줌
환형 데이터 생성을 통해 커널 함수가 저차원 비선형 문제를 고차원 공간으로 매핑하여 해결하는 방법 설명
"""
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_circles
# 한글 폰트 설정
plt.rcParams['font.sans-serif'] = ['Malgun Gothic', 'AppleGothic', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
# 환형 데이터셋 생성: 100개 샘플, 내외부 반지름 비율 0.1, 0.1 노이즈 추가
X, y = make_circles(100, factor=.1, noise=.1)
# 선형 커널 SVM 시도
linear_svm = SVC(kernel='linear').fit(X, y)
def visualize_svm_boundary(model, ax=None, show_support=True):
"""
SVM 결정 경계와 지원 벡터 시각화
매개변수:
model: 훈련된 SVM 모델
ax: 그래프 좌표축
show_support: 지원 벡터 표시 여부
"""
if ax is None:
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# 결정 경계 그리기 위한 그리드 생성
x = np.linspace(xlim[0], xlim[1], 30)
y = np.linspace(ylim[0], ylim[1], 30)
Y, X = np.meshgrid(y, x)
xy = np.vstack([X.ravel(), Y.ravel()]).T
# 결정 함수 값 계산 및 그리드 형태로 변환
P = model.decision_function(xy).reshape(X.shape)
# 결정 경계와 간격선 그리기
ax.contour(X, Y, P, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])
# 지원 벡터 표시
if show_support:
ax.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1],
s=300, linewidth=1, facecolors='none', edgecolors='blue')
# 좌표축 범위 설정
ax.set_xlim(xlim)
ax.set_ylim(ylim)
# 원본 데이터 포인트 시각화
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
visualize_svm_boundary(linear_svm, show_support=False)
plt.title("선형 커널 SVM 분류 결과 (비선형 데이터)")
plt.show()
# 고차원 매핑을 통한 비선형 문제 해결 방법 시연
from mpl_toolkits import mplot3d
# RBF 매핑 계산: 2D 데이터를 3D 공간으로 매핑
# 가우시안 RBF: r = exp(-(x² + y²))
r = np.exp(-(X ** 2).sum(1))
def plot_3D(elevation=30, azimuth=30, data=X, labels=y):
"""3D 공간에서의 데이터 분포 시각화"""
ax = plt.subplot(projection='3d')
# 3D 산점도 그리기, z축은 RBF 매핑 값
ax.scatter3D(data[:, 0], data[:, 1], r, c=labels, s=50, cmap='autumn')
ax.view_init(elevation, azimuth) # 시점 설정
ax.set_xlabel('x축')
ax.set_ylabel('y축')
ax.set_zlabel('z축 (RBF 매핑 값)')
plt.title("2D 데이터를 3D 공간으로 매핑한 후의 분포")
# 3D 뷰 시각화 (45도 각도)
plot_3D(elevation=45, azimuth=45)
plt.show()
# RBF 커널 SVM 사용
rbf_svm = SVC(kernel='rbf', C=1E6)
rbf_svm.fit(X, y)
# 원본 데이터 포인트 시각화
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
visualize_svm_boundary(rbf_svm)
plt.scatter(rbf_svm.support_vectors_[:, 0], rbf_svm.support_vectors_[:, 1],
s=300, lw=1, facecolors='none', edgecolors='blue')
plt.title("RBF 커널 SVM 분류 결과 (비선형 데이터)")
plt.show()
실험 3 - SVM 회귀 - 주택 가격 문제
# -*- coding: utf-8 -*-
"""
SVM 회귀 모델을 사용한 주택 가격 예측 (캘리포니아 주택 데이터셋)
Windows 환경에서의 한글/영문 혼합 표시를 위한 설정
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVR
from sklearn.metrics import r2_score, mean_squared_error
import warnings
warnings.filterwarnings('ignore')
# Windows 한글 표시 설정
plt.rcParams["font.family"] = ["Malgun Gothic", "AppleGothic", "WenQuanYi Micro Hei"]
plt.rcParams['axes.unicode_minus'] = False
sns.set_style("whitegrid")
# 1. 데이터 로드 및 탐색
housing = fetch_california_housing()
X = housing.data
y = housing.target
feature_names = housing.feature_names
print("=== 데이터셋 기본 정보 ===")
print(f"샘플 수: {X.shape[0]}, 특성 수: {X.shape[1]}")
print(f"특성 이름: {', '.join(feature_names)}")
print(f"주택 가격 범위: {y.min():.2f} - {y.max():.2f} (10만 달러)\n")
# 2. 데이터 시각화
# 2.1 특성 상관관계 히트맵
plt.figure(figsize=(10, 6))
corr_data = np.c_[X, y]
corr_df = pd.DataFrame(corr_data, columns=list(feature_names) + ['주택 가격'])
sns.heatmap(corr_df.corr(), annot=True, cmap='coolwarm', fmt='.2f')
plt.title('특성과 주택 가격의 상관관계 히트맵')
plt.tight_layout()
plt.show()
# 2.2 주택 가격 분포 히스토그램
plt.figure(figsize=(8, 5))
sns.histplot(y, kde=True, color='lightblue')
plt.title('주택 가격 중앙값 분포')
plt.xlabel('주택 가격 중앙값 (10만 달러)')
plt.ylabel('샘플 수')
plt.axvline(np.mean(y), color='red', linestyle='--', label=f'평균: {np.mean(y):.2f}')
plt.legend()
plt.tight_layout()
plt.show()
# 3. 데이터 전처리
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=33
)
print(f"훈련 세트 크기: {X_train.shape[0]}개, 테스트 세트 크기: {X_test.shape[0]}개\n")
scaler_X = StandardScaler()
scaler_y = StandardScaler()
X_train_scaled = scaler_X.fit_transform(X_train)
X_test_scaled = scaler_X.transform(X_test)
y_train_scaled = scaler_y.fit_transform(y_train.reshape(-1, 1)).ravel()
y_test_scaled = scaler_y.transform(y_test.reshape(-1, 1)).ravel()
# 4. 모델 훈련 및 평가
print("=== 모델 훈련 결과 ===")
models = {
'선형 커널': SVR(kernel='linear'),
'다항식 커널': SVR(kernel='poly'),
'RBF 커널': SVR(kernel='rbf')
}
results = {}
for name, model in models.items():
model.fit(X_train_scaled, y_train_scaled)
y_pred_scaled = model.predict(X_test_scaled)
y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
r2 = r2_score(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
results[name] = {'prediction': y_pred, 'r2': r2, 'mse': mse}
print(f"{name}:")
print(f" R² 점수: {r2:.4f}")
print(f" 평균 제곱 오차: {mse:.4f}\n")
# 5. 모델 결과 시각화
# 5.1 예측값과 실제값 비교
plt.figure(figsize=(12, 4))
for i, (name, res) in enumerate(results.items(), 1):
plt.subplot(1, 3, i)
plt.scatter(y_test, res['prediction'], alpha=0.5, s=10, label=f'R² = {res["r2"]:.4f}')
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'r--', label='이상적인 선')
plt.title(f'{name} 예측 결과 비교')
plt.xlabel('실제 주택 가격 (10만 달러)')
plt.ylabel('예측 주택 가격 (10만 달러)')
plt.legend()
plt.tight_layout()
plt.show()
# 5.2 오차 분포 비교
plt.figure(figsize=(10, 6))
for name, res in results.items():
error = res['prediction'] - y_test
sns.histplot(error, kde=True, alpha=0.5, label=name)
plt.axvline(0, color='black', linestyle='--', label='오차 0')
plt.title('다양한 모델의 예측 오차 분포')
plt.xlabel('오차 (10만 달러)')
plt.ylabel('샘플 수')
plt.legend()
plt.show()
실험 4 - C와 gamma 매개변수의 영향
# -*- coding: utf-8 -*-
"""
SVM 매개변수 튜닝 시각화 예제
C 매개변수와 gamma 매개변수가 SVM 분류 결과에 미치는 영향을 보여줌
"""
import numpy as np
from sklearn.svm import SVC
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
# Windows 한글 표시 설정
plt.rcParams["font.family"] = ["Malgun Gothic", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams['axes.unicode_minus'] = False
# 시뮬레이션 데이터셋 생성
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plt.title("원본 데이터셋 시각화")
plt.show()
# 1. 정규화 매개변수 C 조절
def visualize_svm_boundary(model, ax=None, show_support=True):
"""
2D SVM 결정 경계와 지원 벡터 시각화
매개변수:
model: 훈련된 SVM 모델
ax: 그래프 좌표축
show_support: 지원 벡터 표시 여부
"""
if ax is None:
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
x = np.linspace(xlim[0], xlim[1], 30)
y = np.linspace(ylim[0], ylim[1], 30)
Y, X = np.meshgrid(y, x)
xy = np.vstack([X.ravel(), Y.ravel()]).T
P = model.decision_function(xy).reshape(X.shape)
ax.contour(X, Y, P, colors='k',
levels=[-1, 0, 1], alpha=0.5,
linestyles=['--', '-', '--'])
if show_support:
ax.scatter(model.support_vectors_[:, 0],
model.support_vectors_[:, 1],
s=300, linewidth=1, facecolors='none', edgecolors='blue')
ax.set_xlim(xlim)
ax.set_ylim(ylim)
# C 매개변수가 SVM 선형 커널 분류 결과에 미치는 영향
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8)
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
# 다른 C 값으로 훈련 및 시각화
for axis, C_value in zip(ax, [20, 0.2]):
model = SVC(kernel='linear', C=C_value).fit(X, y)
axis.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
visualize_svm_boundary(model, axis)
axis.set_title(f'C = {C_value:.1f}', size=14)
plt.suptitle("다양한 C 값이 SVM 선형 커널 분류 결과에 미치는 영향", size=16)
plt.show()
# 2. 커널 매개변수 gamma 조절 (비선형 커널용)
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=1.1)
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
# 다른 gamma 값으로 훈련 및 시각화
for axis, gamma_value in zip(ax, [5, 0.1]):
model = SVC(kernel='rbf', gamma=gamma_value).fit(X, y)
axis.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
visualize_svm_boundary(model, axis)
axis.set_title(f'gamma = {gamma_value:.1f}', size=14)
plt.suptitle("다양한 gamma 값이 SVM RBF 커널 분류 결과에 미치는 영향", size=16)
plt.show()