SVM(Support Vector Machine)은 선형, 비선형 분류, 회귀, 이상치 탐색에도 사용이 가능한 다목적 머신러닝 모델입니다.
SVM은 특히 복잡한 분류 문제에 잘 맞고 작거나 중간 크기의 데이터셋에 적합합니다.
이번 Chapter에서는 SVM의 핵심 개념, 사용방법과 작동원리에 대하여 알아보겠습니다.
Chap5 목차
- 소프트 마진 분류
- 다항식 커널
- 유사도 특성
- 가우시안 RBF 커널
- 계산 복잡도
- 결정 함수와 목적 함수
- 콰드라틱 프로그래밍(Qudratic Programming: QP)
- 쌍대 문제
- 커널/온라인 SVM(해당 내용은 차후 포스팅 예정)
※ 4. SVM이론의 경우 SVM에 관하여 깊이 알고 싶을 때 보면 됨.
1. 선형 SVM 분류
○ 기본 아이디어(사진으로 설명)
● 두 클래스가 직선으로 구분됨(= 선형적으로 구분)
● 왼쪽 그래프에 세 개의 선형 분류기에서 만들어진 결정 경계가 보임(설명을 위한 임의의 직선임)
- 점선으로 나타난 결정 경계를 만드는 모델은 클래스 분류를 적절하게 하지 못하고 있음
- 다른 두 모델은 훈련 세트에 대해 완벽하게 작동
- But 결정 경계가 샘플에 너무 가까움 So, 새로운 샘플에 잘 작동하지 못할 것
● 오른쪽 그래프에 있는 실선은 SVM 분류기의 결정 경계
- 두 개의 크래스를 구분 + 제일 가까운 훈련 샘플로부터 가능한 멀리 떨어져 있음
- SVM 분류기를 클래스 사이에 가장 폭이 넓은 도로를 찾는 것으로 생각 가능
→ 라지 마진 분류(Large Margin Classification)라 부름
- 도로 바깥에 훈련 샘플을 추가해도 결정 경계에 영향을 미치지 않음
- 도로 경계에 위치한 샘플에 의해 전적으로 결정
※ SVM은 특성 스케일에 민감한 편이기에 특성 스케일을 조정하면 결정 경계가 훨씬 좋아짐
[Large Margin Classification 코드]
from sklearn.svm import SVC
from sklearn import datasets
iris = datasets.load_iris()
X = iris["data"][:, (2, 3)] # 꽃잎 길이, 꽃잎 너비
y = iris["target"]
setosa_or_versicolor = (y == 0) | (y == 1)
X = X[setosa_or_versicolor]
y = y[setosa_or_versicolor]
# SVM 분류 모델
svm_clf = SVC(kernel="linear", C=float("inf"))
svm_clf.fit(X, y)
# 세 개의 선형 모델
x0 = np.linspace(0, 5.5, 200)
pred_1 = 5*x0 - 20
pred_2 = x0 - 1.8
pred_3 = 0.1 * x0 + 0.5
def plot_svc_decision_boundary(svm_clf, xmin, xmax):
w = svm_clf.coef_[0]
b = svm_clf.intercept_[0]
# 결정 경계에서 w0*x0 + w1*x1 + b = 0 이므로
# => x1 = -w0/w1 * x0 - b/w1
x0 = np.linspace(xmin, xmax, 200)
decision_boundary = -w[0]/w[1] * x0 - b/w[1]
margin = 1/w[1]
gutter_up = decision_boundary + margin
gutter_down = decision_boundary - margin
svs = svm_clf.support_vectors_
plt.scatter(svs[:, 0], svs[:, 1], s=180, facecolors='#FFAAAA')
plt.plot(x0, decision_boundary, "k-", linewidth=2)
plt.plot(x0, gutter_up, "k--", linewidth=2)
plt.plot(x0, gutter_down, "k--", linewidth=2)
fig, axes = plt.subplots(ncols=2, figsize=(10,2.7), sharey=True)
plt.sca(axes[0])
plt.plot(x0, pred_1, "g--", linewidth=2)
plt.plot(x0, pred_2, "m-", linewidth=2)
plt.plot(x0, pred_3, "r-", linewidth=2)
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs", label="Iris versicolor")
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo", label="Iris setosa")
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.legend(loc="upper left", fontsize=14)
plt.axis([0, 5.5, 0, 2])
plt.sca(axes[1])
plot_svc_decision_boundary(svm_clf, 0, 5.5)
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs")
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo")
plt.xlabel("Petal length", fontsize=14)
plt.axis([0, 5.5, 0, 2])
plt.show()
1-1 소프트 마진 분류
○ 하드 마진 분류(Hard Margin Classification) : 모든 샘플이 도로 바깥쪽에 올바르게 분류되어 있는 상태
● 문제점 : 선형적으로 구분 가능해야 작동 + 이상치에 민감
● 결정 경계가 다르고 일반화하는 것에 대한 문제가 있음
○ 소프트 마진 분류(Soft Margin Classification)
● 이 문제를 해결하기 위해서는 유연한 모델이 필요
● 도로 폭을 가능한 넓게 유지하고
● 마진 오류(Margin violation) 사이에 적절한 균형을 잡아야 함
※ 마진 오류 : 샘플이 도로 중간이나 반대쪽에 있는 경우
○ SVM 모델 생성 시 여러 하이퍼 파라미터를 지정 가능
● c를 낮게 설정(왼쪽 그림)하거나 높게 설정(오른쪽 그림)한 모델을 얻을 수 있음
● 마진 오류는 일반적으로 적은 것이 좋음. 하지만 왼쪽의 경우 마진 오류가 많지만 일반화가 더 잘 될 것으로 보임
● SVM 분류기는 로지스틱 회귀와 달리 클래스에 대한 확률을 제공하지 않음
[tip] SVM 모델이 과대적합이라면 c를 감소시켜 모델을 규제 가능
※ LinearSVC는 predict_proba 메서드를 제공하지 않지만,
SVM 모델은 probability=True로 매개변수를 지정하면 predict_proba() 메서드를 제공
[SVM 모델 훈련 코드]
○ 데이터 적재, 특성 스케일 변경, Iris-Vriginia를 감지하기 위한 선형 SVM 모델 훈련
● SGDClassifier 모델 사용
SGDClassifier(loss="hinge", alpha=1/(m*C))와 같이 작성
● LinearSVC 클래스 사용 대신 SVC 클래스(선형 커널 사용)로 대체 가능
SVC 모델을 만들 때 SVC(kernel="linear', C=1)로 작성
※ LinearSVC 사용 시 tip
1. LinearSVC는 규제에 편향을 포함 So, 훈련 세트에서 평균을 빼서 중앙에 맞춰야 함
: StandardScaler를 사용해 데이터 스케일을 맞추면 자동
2. loss 매개변수를 hinge로 지정
3. 훈련 샘플보다 특성이 많지 않다면 성능을 높이기 위해 dual 매개변수를 False로 지정해야 함
import numpy as np
from sklearn import datasets
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
iris = datasets.load_iris()
X = iris["data"][:, (2, 3)] # 꽃잎 길이, 꽃잎 너비
y = (iris["target"] == 2).astype(np.float64) # Iris virginica
svm_clf = Pipeline([
("scaler", StandardScaler()),
("linear_svc", LinearSVC(C=1, loss="hinge", random_state=42)),
])
svm_clf.fit(X, y)
○ 모델 예측
svm_clf.predict([[5.5, 1.7]])
○ 위쪽 사진 시각화 코드
scaler = StandardScaler()
svm_clf1 = LinearSVC(C=1, loss="hinge", random_state=42)
svm_clf2 = LinearSVC(C=100, loss="hinge", random_state=42)
scaled_svm_clf1 = Pipeline([
("scaler", scaler),
("linear_svc", svm_clf1),
])
scaled_svm_clf2 = Pipeline([
("scaler", scaler),
("linear_svc", svm_clf2),
])
scaled_svm_clf1.fit(X, y)
scaled_svm_clf2.fit(X, y)
# 스케일되지 않은 파라미터로 변경
b1 = svm_clf1.decision_function([-scaler.mean_ / scaler.scale_])
b2 = svm_clf2.decision_function([-scaler.mean_ / scaler.scale_])
w1 = svm_clf1.coef_[0] / scaler.scale_
w2 = svm_clf2.coef_[0] / scaler.scale_
svm_clf1.intercept_ = np.array([b1])
svm_clf2.intercept_ = np.array([b2])
svm_clf1.coef_ = np.array([w1])
svm_clf2.coef_ = np.array([w2])
# 서포트 벡터 찾기
# (libsvm과 달리 liblinear 라이브러리에서 제공하지 않기 때문에 LinearSVC에는 서포트 벡터가 저장되어 있지 않음
t = y * 2 - 1
support_vectors_idx1 = (t * (X.dot(w1) + b1) < 1).ravel()
support_vectors_idx2 = (t * (X.dot(w2) + b2) < 1).ravel()
svm_clf1.support_vectors_ = X[support_vectors_idx1]
svm_clf2.support_vectors_ = X[support_vectors_idx2]
fig, axes = plt.subplots(ncols=2, figsize=(10,2.7), sharey=True)
plt.sca(axes[0])
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^", label="Iris virginica")
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs", label="Iris versicolor")
plot_svc_decision_boundary(svm_clf1, 4, 5.9)
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.legend(loc="upper left", fontsize=14)
plt.title("$C = {}$".format(svm_clf1.C), fontsize=16)
plt.axis([4, 5.9, 0.8, 2.8])
plt.sca(axes[1])
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^")
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs")
plot_svc_decision_boundary(svm_clf2, 4, 5.99)
plt.xlabel("Petal length", fontsize=14)
plt.title("$C = {}$".format(svm_clf2.C), fontsize=16)
plt.axis([4, 5.9, 0.8, 2.8])
2. 비선형 SVM 분류
비선형 데이터셋을 다루는 방법
[방법 1] 다항 특성과 같은 특성을 더 추가하기
● 아래 예제 데이터셋 : moons 데이터셋(사이킷런 의 make_moons 함수를 사용해 만든 반달 모양 데이터셋)
● PolynomialFeatures변환기, StandardScaler, LinearSVC를 연결한 Pipeline 생성
- 데이터셋 : 두 개의 반원 모양으로 데이터 포인트가 놓여 있는 이진 분류를 위한 작은 데이터셋
from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
polynomial_svm_clf = Pipeline([
("poly_features", PolynomialFeatures(degree=3)),
("scaler", StandardScaler()),
("svm_clf", LinearSVC(C=10, loss="hinge", random_state=42))
])
polynomial_svm_clf.fit(X, y)
- 시각화
def plot_predictions(clf, axes):
x0s = np.linspace(axes[0], axes[1], 100)
x1s = np.linspace(axes[2], axes[3], 100)
x0, x1 = np.meshgrid(x0s, x1s)
X = np.c_[x0.ravel(), x1.ravel()]
y_pred = clf.predict(X).reshape(x0.shape)
y_decision = clf.decision_function(X).reshape(x0.shape)
plt.contourf(x0, x1, y_pred, cmap=plt.cm.brg, alpha=0.2)
plt.contourf(x0, x1, y_decision, cmap=plt.cm.brg, alpha=0.1)
plot_predictions(polynomial_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

2-1 다항식 커널
○ 다항식 특성을 추가하는 방법은 간단하고 잘 작동하는 편
○ But 낮은 차수의 다항식은 매우 복잡한 데이터셋을 잘 표현하지 못하고 높은 차수의 다항식은 굉장히 많은 특성을 추가하여 모델을 느리게 만듦
○ SVM을 사용할 땐 커널 트릭(kernel trick)이라는 수학적 기교를 적용 가능
● kernel trick : 실제로는 특성을 추가하지 않으면서 다항식 특성을 많이 추가한 결과를 얻게 해 줌
[방법 2] kernel trick 사용하기
- 다항식 커널을 사용한 SVM 분류기 생성
1) 3차 다항식 커널을 사용해 SVM 분류기 훈련
from sklearn.svm import SVC
poly_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5))
])
poly_kernel_svm_clf.fit(X, y)
2) 10차 다항식 커널을 사용해 SVM 분류기 훈련
coef0 매개변수 : 다항식 커널에 있는 상수항 r
poly100_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=10, coef0=100, C=5))
])
poly100_kernel_svm_clf.fit(X, y)
- 시각화
fig, axes = plt.subplots(ncols=2, figsize=(10.5, 4), sharey=True)
plt.sca(axes[0])
plot_predictions(poly_kernel_svm_clf, [-1.5, 2.45, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.4, -1, 1.5])
plt.title(r"$d=3, r=1, C=5$", fontsize=18)
plt.sca(axes[1])
plot_predictions(poly100_kernel_svm_clf, [-1.5, 2.45, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.4, -1, 1.5])
plt.title(r"$d=10, r=100, C=5$", fontsize=18)
plt.ylabel("")
plt.show()

2-2 유사도 특성
[방법 3] 각 샘플이 특정 랜드마크와 얼마나 닮았는지 측정하는 유사도 함수를 계산한 특성에 추가하기
e.g. 앞에서 본 1차원 데이터셋에 두 개의 랜드 마크 x_1 = -2와 x_1 = 1을 추가
r = 0.3인 가우시안 방사 기저 함수(RDF;Radial Basis Function)를 유사도 함수로 정의
[가우시안 RBF 공식]
$$ \phi_{\gamma}(x,l) = \textup{exp}(-r\left\|x-l \right\|^{2}) $$
함수의 값은 0(랜드마크에서 멀리 떨어진 경우) ~ 1(랜드마크와 동일한 위치)까지 변화하며 종 모양으로 나타남
○ x_1 = -1 샘플은 첫 번째 랜드마크에서 1만큼 떨어져 있고 두 번째 랜드 마크에서 2만큼 떨어져 있음
∴ 새로 만든 특성은 아래와 같습니다.
$$ x_{2}= \textup{exp}(-0.3 * 1^2)\approx 0.74 $$
$$ x_{3}= \textup{exp}(-0.3 * 2^2)\approx 0.30 $$
위의 사진의 오른쪽 그래프는 변환된 데이터셋을 보여주듯 선형적 구분이 가능합니다.
Then.. How to chose Landmark?
○ 데이터셋에 있는 모든 샘플 위치에 랜드마크 설정 → 차원이 커짐 → 변환된 훈련 세트가 선형적 구분될 가능성 높아짐
○ 단점 : 훈련 세트에 있는 n 개의 특성을 가진 m개의 샘플이 m개의 특성을 가진 m개의 샘플로 변환(특성이 너무 많아짐)
2-3 가우시안 RBF 커널
[RBF 커널을 사용한 SVM 분류기 생성 코드]
rbf_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="rbf", gamma=5, C=0.001))
])
rbf_kernel_svm_clf.fit(X, y)
- 위의 작성한 모델은 왼쪽 아래, 나머지 그래프들은 gamma(r)와 C를 바꿔 훈련시킨 모양
○ gamma를 증가시키면 종 모양 그래프가 좁아져서 각 샘플의 영향 범위가 작아짐
○ gamma를 감소시키면 넓은 종모양을 만들며 샘플이 넓은 범위에 영향을 주며 결정 경계가 더 부드러워짐
So, 하이퍼파라미터 r이 규제의 역할을 담당(하이퍼파라미터 C와 비슷)
● 과대적합일 경우 : 감소
● 과소적합일 경우 : 증가
from sklearn.svm import SVC
gamma1, gamma2 = 0.1, 5
C1, C2 = 0.001, 1000
hyperparams = (gamma1, C1), (gamma1, C2), (gamma2, C1), (gamma2, C2)
svm_clfs = []
for gamma, C in hyperparams:
rbf_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="rbf", gamma=gamma, C=C))
])
rbf_kernel_svm_clf.fit(X, y)
svm_clfs.append(rbf_kernel_svm_clf)
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(10.5, 7), sharex=True, sharey=True)
for i, svm_clf in enumerate(svm_clfs):
plt.sca(axes[i // 2, i % 2])
plot_predictions(svm_clf, [-1.5, 2.45, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.45, -1, 1.5])
gamma, C = hyperparams[i]
plt.title(r"$\gamma = {}, C = {}$".format(gamma, C), fontsize=16)
if i in (0, 1):
plt.xlabel("")
if i in (1, 3):
plt.ylabel("")
plt.show()

2-4 계산 복잡도
○ LinearSVC 파이썬 클래스는 선형 SVM을 위한 최적화된 알고리즘을 구현한 liblinear라이브러리를 기반
해당 라이브러리는 커널 트릭을 지원하지 않지만, 훈련 샘플과 특성 수에 거의 선형적으로 늘어남
● 해당 알고리즘의 훈련 시간 복잡도 : 대략 O(mn)
○ SVC는 커널 트릭 알고리즘을 구현한 libsvm 라이브러리를 기반
● 해당 알고리즘의 훈련 시간 복잡도 : 보통 O(m^2 * n) ~ O(m^3 * n) 사이
● 훈련 샘플 수가 커지면 엄청 느려진다는 의미지만, 작거나 중간 규모의 훈련 세트에 잘 맞음
● 특히, 희소 특성(Sparse Features; 각 샘플에 0이 아닌 특성이 몇 개 없는 경우)인 경우 잘 확장됨
3. SVM 회귀
○ SVM을 분류가 아닌 회귀에 적용하는 방법은 목표를 반대로 하는 것
○ 일정한 마진 오류 안에서 두 클래스 간의 도로 폭이 가능한 한 최대가 되도록 하는 대신,
SVM 회귀는 제한된 마진 오류 안에서 도로 안에 가능한 한 많은 샘플이 들어가도록 학습
○ 아래 그림은 무작위로 생성한 선형 데이터셋에 훈련시킨 두 개의 선형 SVM 회귀모델을 보여줌
※ Ɛ : varepsilon, 엡실
● 왼쪽 : Ɛ = 1.5, 오른쪽 : Ɛ = 0.5
● 마진 안에서는 훈련 샘플이 추가되어도 모델의 예측에는 영향이 없다
● So, 해당 모델을 Ɛ에 민감하지 않다(Ɛ- insensitive)고 함
[생성 코드]
svm_reg1 = LinearSVR(epsilon=1.5, random_state=42)
svm_reg2 = LinearSVR(epsilon=0.5, random_state=42)
svm_reg1.fit(X, y)
svm_reg2.fit(X, y)
def find_support_vectors(svm_reg, X, y):
y_pred = svm_reg.predict(X)
off_margin = (np.abs(y - y_pred) >= svm_reg.epsilon)
return np.argwhere(off_margin)
svm_reg1.support_ = find_support_vectors(svm_reg1, X, y)
svm_reg2.support_ = find_support_vectors(svm_reg2, X, y)
eps_x1 = 1
eps_y_pred = svm_reg1.predict([[eps_x1]])
def plot_svm_regression(svm_reg, X, y, axes):
x1s = np.linspace(axes[0], axes[1], 100).reshape(100, 1)
y_pred = svm_reg.predict(x1s)
plt.plot(x1s, y_pred, "k-", linewidth=2, label=r"$\hat{y}$")
plt.plot(x1s, y_pred + svm_reg.epsilon, "k--")
plt.plot(x1s, y_pred - svm_reg.epsilon, "k--")
plt.scatter(X[svm_reg.support_], y[svm_reg.support_], s=180, facecolors='#FFAAAA')
plt.plot(X, y, "bo")
plt.xlabel(r"$x_1$", fontsize=18)
plt.legend(loc="upper left", fontsize=18)
plt.axis(axes)
fig, axes = plt.subplots(ncols=2, figsize=(9, 4), sharey=True)
plt.sca(axes[0])
plot_svm_regression(svm_reg1, X, y, [0, 2, 3, 11])
plt.title(r"$\epsilon = {}$".format(svm_reg1.epsilon), fontsize=18)
plt.ylabel(r"$y$", fontsize=18, rotation=0)
#plt.plot([eps_x1, eps_x1], [eps_y_pred, eps_y_pred - svm_reg1.epsilon], "k-", linewidth=2)
plt.annotate(
'', xy=(eps_x1, eps_y_pred), xycoords='data',
xytext=(eps_x1, eps_y_pred - svm_reg1.epsilon),
textcoords='data', arrowprops={'arrowstyle': '<->', 'linewidth': 1.5}
)
plt.text(0.91, 5.6, r"$\epsilon$", fontsize=20)
plt.sca(axes[1])
plot_svm_regression(svm_reg2, X, y, [0, 2, 3, 11])
plt.title(r"$\epsilon = {}$".format(svm_reg2.epsilon), fontsize=18)
plt.show()
○ 사이킷런 의 LinearSVR을 사용해 선형 SVM 회귀를 적용
● SVR은 SVC의 회귀 버전이고 LinearSVR은 LinearSVC의 회귀 법전
● 비선형 회귀 작업을 처리하기 위해서는 커널 SVM 모델을 사용.
● 아래 사진과 같이 임의의 2차 방정식 형태의 훈련세트에 2차 다항 커널을 사용한 SVM 회귀를 보여줌
● 왼쪽 그래프는 규제가 적고(큰 C), 오른쪽은 규제가 많음(적은 C)
[Pyhton 코드]
from sklearn.svm import SVR
svm_poly_reg1 = SVR(kernel="poly", degree=2, C=100, epsilon=0.1, gamma="scale")
svm_poly_reg2 = SVR(kernel="poly", degree=2, C=0.01, epsilon=0.1, gamma="scale")
svm_poly_reg1.fit(X, y)
svm_poly_reg2.fit(X, y)
fig, axes = plt.subplots(ncols=2, figsize=(9, 4), sharey=True)
plt.sca(axes[0])
plot_svm_regression(svm_poly_reg1, X, y, [-1, 1, 0, 1])
plt.title(r"$degree={}, C={}, \epsilon = {}$".format(svm_poly_reg1.degree, svm_poly_reg1.C, svm_poly_reg1.epsilon), fontsize=18)
plt.ylabel(r"$y$", fontsize=18, rotation=0)
plt.sca(axes[1])
plot_svm_regression(svm_poly_reg2, X, y, [-1, 1, 0, 1])
plt.title(r"$degree={}, C={}, \epsilon = {}$".format(svm_poly_reg2.degree, svm_poly_reg2.C, svm_poly_reg2.epsilon), fontsize=18)
plt.show()
4. SVM 이론(SVM에 관하여 깊이 들어가는 내용이라 SVM에 대해 깊이 알고 싶으면 참고)
4-1 결정함수와 목적함수
4-1-1 결정 함수와 예측
○ 선형 SVM 분류기 모델은 단순히 결정 함수를 계산해서 새로운 샘플 x의 클래스를 예측
○ 결괏값이 0보다 크면 예측된 클래스 \hat{y}는 양성 클래스(1), 그렇지 않다면 음성 클래스(0)가 됨
아래의 선형 SVM 분류기 예측식 참고
[결정 함수]
$$ w^{T}x + b = w_{1}x_{1}+...+w_{n}x_{x}+b $$
[선형 SVM 분류기 예측]
$$ \hat{y}=\begin{cases}0& w_{T}x + b < 0 \\ 1 & w_{T}x + b \geq 0\end{cases} $$
[그림]의 오른쪽에 있는 모델의 결정 함수가 아래 그림과 같이 나타나 있음.
○ 2차원 평면 : 특성이 두 개(꽃잎의 너비, 길이)인 데이터셋이기 때문
○ 결정 경계 : 결정 함수의 값이 0인 점들로 이루어짐(두 평면의 교차점; 아래 그림의 굵은 실선)
※ 일반적으로 말하면 n개의 특성일 때 결정함수는 n차원의 초평면이고, 결정 경계는 (n-1) 차원의 초평면
○ 점선 : 결정 함수의 값이 1 | -1 인 점들
● 이 선분은 결정 경계에 나란하고 일정한 거리만큼 떨어져서 마진을 형성
○ 선형 SVM 분류기를 훈련한다는 뜻 =
● 마진 오류를 하나도 발생하지 않기(하드 마진)
● or 제한적인 마진 오류를 가지면서(소프트 마진) 가능한 한 마진을 크게 하는 w와 b를 찾는 것
[그림 생성 코드]
iris = datasets.load_iris()
X = iris["data"][:, (2, 3)] # 꽃잎 길이, 꽃잎 너비
y = (iris["target"] == 2).astype(np.float64) # Iris virginica
from mpl_toolkits.mplot3d import Axes3D
def plot_3D_decision_function(ax, w, b, x1_lim=[4, 6], x2_lim=[0.8, 2.8]):
x1_in_bounds = (X[:, 0] > x1_lim[0]) & (X[:, 0] < x1_lim[1])
X_crop = X[x1_in_bounds]
y_crop = y[x1_in_bounds]
x1s = np.linspace(x1_lim[0], x1_lim[1], 20)
x2s = np.linspace(x2_lim[0], x2_lim[1], 20)
x1, x2 = np.meshgrid(x1s, x2s)
xs = np.c_[x1.ravel(), x2.ravel()]
df = (xs.dot(w) + b).reshape(x1.shape)
m = 1 / np.linalg.norm(w)
boundary_x2s = -x1s*(w[0]/w[1])-b/w[1]
margin_x2s_1 = -x1s*(w[0]/w[1])-(b-1)/w[1]
margin_x2s_2 = -x1s*(w[0]/w[1])-(b+1)/w[1]
ax.plot_surface(x1s, x2, np.zeros_like(x1),
color="b", alpha=0.2, cstride=100, rstride=100)
ax.plot(x1s, boundary_x2s, 0, "k-", linewidth=2, label=r"$h=0$")
ax.plot(x1s, margin_x2s_1, 0, "k--", linewidth=2, label=r"$h=\pm 1$")
ax.plot(x1s, margin_x2s_2, 0, "k--", linewidth=2)
ax.plot(X_crop[:, 0][y_crop==1], X_crop[:, 1][y_crop==1], 0, "g^")
ax.plot_wireframe(x1, x2, df, alpha=0.3, color="k")
ax.plot(X_crop[:, 0][y_crop==0], X_crop[:, 1][y_crop==0], 0, "bs")
ax.axis(x1_lim + x2_lim)
ax.text(4.5, 2.5, 3.8, "Decision function $h$", fontsize=16)
ax.set_xlabel(r"Petal length", fontsize=16, labelpad=10)
ax.set_ylabel(r"Petal width", fontsize=16, labelpad=10)
ax.set_zlabel(r"$h = \mathbf{w}^T \mathbf{x} + b$", fontsize=18, labelpad=5)
ax.legend(loc="upper left", fontsize=16)
fig = plt.figure(figsize=(11, 6))
ax1 = fig.add_subplot(111, projection='3d')
plot_3D_decision_function(ax1, w=svm_clf2.coef_[0], b=svm_clf2.intercept_[0])
plt.show()
4-2-2 목적 함수
○ 결정 함수의 기울기 : 가중치 벡터의 노름 ||w||
e.g. 기울기를 2로 나누면 결정 함수의 값이 ±1이 되는 점들이 결정 경계로부터 2배만큼 멀어짐
즉, 기울기/2 = 마진 * 2
○ 마진을 크게 하기 위해 ||w||를 최소화하려고 한다.
○ 마진 오류를 하나도 만들지 않으려면(즉, 하드 마진하려면)?
● 결정 함수가 모든 양성 훈련 샘플에서는 1보다 커야 하고, 음성 훈련 샘플에서는 -1보다 작아야 함.
● 음성 샘플(y^{(i)}=0) 일 때 t^{(i)} = -1, 양성 샘플(y^{(i)}=1) 일때 t^{(i)} = 1로 정의
● + 제약 조건을 모든 샘플에서 아래와 같이 표현 가능
[표현식]
$$ t^{(i)}(w^{T}x^{i}+b)\geq 1 $$
∴ 하드 마진 선형 SVM 분류기의 목적 함수를 아래와 같이 제약 있는 최적화(Constrained Optimization) 문제로 표현 가능
[하드 마진 선형 SVM 분류기의 목적 함수]
$$ \underset{w, b}{minimize}\frac{1}{2}w^{T}w $$
$$ \textup{[condition]} i = 1,2,..., m일때 t^{(i)}(w^{T}x^{i}+b)\geq 1 $$
○ 소프트 마진 분류기의 목적 함수를 구성하려면 각 샘플에 대해 슬랙 변수(제타^{(i)} ≥ 0)를 도입해야 함.
● 제타^{(i)} : i 번째 샘플이 얼마나 마진을 위반할지 결정
● 이 문제의 경우 두 개의 상충되는 목표를 가지고 있음
1) 마진 오류를 최소화하기 위해 가능한 한 슬랙 변수의 값을 작게 만드는 것
2) 마진을 크게 하기 위해 하드마진 선형 SVM 분류기의 목적함수의 윗 식을 가능한 한 작게 만드는 것
● 여기에 하이퍼파라미터 C가 등장
- C : 두 목표 사이의 트레이드오프를 정의
∴ 아래에 있는 제약을 가진 최적화 문제가 됨
[소프트 마진 선형 SVM 분류기의 목적 함수]
$$ \underset{w, b,\zeta}{minimize}\frac{1}{2}w^{T}w + C\sum_{i=1}^{m}\zeta^{(i)} $$
$$ \textup{[condition]} i = 1,2,..., m t^{(i)}(w^{T}x^{i}+b)\geq 1-\zeta^{(i)}, \zeta^{(i)} \geq 0 $$
4-2 콰드라틱 프로그래밍(QP)
○ 하드 마진과 소프트 마진 문제 모두 선형적인 제약 조건이 있는 볼록 함수의 이차 최적화 문제
○ 이러한 문제들을 QP(Quadratic Programming) 문제라고 함.
○ QP 문제를 푸는 여러 가지 알고리즘이 존재하지만, 일반적인 문제 공식은 아래와 같음
○ 아래와 같이 QP 파라미터를 지정하면 하드 마진을 갖는 선형 SVM 분류기의 목적 함수를 간단히 검증 가능
● n_p = n+1, n = 특성 수(편향으로 인해 +1)
● n_c = m, m = 훈련 샘플 수
● H : n_p * n_p 크기이고 왼쪽 맨 위의 원소가 0(편향 제외를 위함)인 것을 제외하고는 단위행렬
● f = 0, 0으로 채워진 n_p 차원의 벡터
● b=1, 모두 1로 채워진 n_c 차원의 벡터
[QP 문제]
$$ \underset{p}{minimize}\frac{1}{2}p^{T}Hp+f^{T}p $$
조건 : Ap ≤ b
● p : n_p 차원의 벡터(n_p : 모델 파라미터)
● H : n_p * n_p 크기의 행렬
● f : n_p 차원의 벡터
● A : n_c * n_p 크기의 행렬(n_c : 제약 수)
● b : n_c 차원의 벡터
4-3 쌍대 문제
○ 원 문제(Primal Problem)라는 제약이 있는 최적화 문제가 주어지면 쌍대 문제(dual problem)라고 하는 깊게 관련된 다른 문제로 표현이 가능
○ 쌍대 문제 해 : 원 문제 해의 하한값(일반적인 경우) but 어떤 조건하에서는 원 문제와 똑같은 해를 제공
※ SVM 문제는 이 조건을 만족 : 목적함수가 볼록함수이고, 부등식 제약 조건이 연속 미분 가능하면서 볼록 함수
○ 따라서 원 문제 | 쌍대 문제 중 하나를 선택하여 풀 수 있음
※ LinearSVC, LinearSVR은 매개변수 dual의 기본값(True)을 false로 바꾸면 원 문제 선택
※ SVC, SVR은 쌍대 문제만을 품
○ 아래 식이 선형 SVM 목적 함수의 쌍대 형식
[선형 SVM 목적 함수의 쌍대 형식]
$$ \underset{\alpha}{minimize}\frac{1}{2}\sum_{i=1}^{m}\sum_{j=1}^{m}\alpha^{(i)}\alpha^{(j)}t^{(i)}t^{(j)}x^{(i)^T}x^{(j)}- \sum_{i=1}^{m}\alpha^{(i)} $$
조건 : i = 1, 2, ..., m 일때 ∝^{(i)} ≥ 0
○ 위 식을 최소화 하는 \hat{∝}를 찾으면 아래의 식을 사용해 식을 최소화하는 \hat{w}와 \hat{b}를 계산 가능
[쌍대 문제에서 구한 해로 원 문제 해 계산하기]
$$ \hat{w} = \sum_{i=1}^{m}\hat{\alpha}^{(i)}t^{(i)}x^{(i)} $$
$$ \hat{b}=\frac{1}{n_s}\underset{\hat{\alpha}^{(i)}>0}{\sum_{i=1}^{m}}(t^{(i)}-\hat{w}^Tx^{(i)}) $$
4-4 커널 SVM/온라인 SVM
(이 내용의 경우 너무 수학적으로 딥하게 들어가는 경향이 있어 따로 포스팅하도록 하겠습니다.)
'Networks > Hands-On 머신러닝 정리' 카테고리의 다른 글
Hands-On Machine Learning 정리 - 머신러닝(Chapter 7: 앙상블 학습과 랜덤 포레스트) (0) | 2024.10.21 |
---|---|
Hands-On Machine Learning 정리 - 머신러닝(Chapter 6: 결정 트리) (0) | 2024.10.21 |
Hands-On Machine Learning 정리 - 머신러닝(Chapter 4: 모델 훈련) (1) | 2024.10.19 |
Hands-On Machine Learning 정리 - 머신러닝(Chapter 3: 분류) (0) | 2024.10.19 |
Hands-On Machine Learning 정리 - 머신러닝(Chapter 2: 프로젝트) (3) | 2024.10.18 |