Networks/Hands-On 머신러닝 정리

Hands-On Machine Learning 정리 - 머신러닝(Chapter 5: SVM)

코딩하는 Español되기 2024. 10. 20. 17:30

SVM(Support Vector Machine)은 선형, 비선형 분류, 회귀, 이상치 탐색에도 사용이 가능한 다목적 머신러닝 모델입니다.

SVM은 특히 복잡한 분류 문제에 잘 맞고 작거나 중간 크기의 데이터셋에 적합합니다.

이번 Chapter에서는 SVM의 핵심 개념, 사용방법과 작동원리에 대하여 알아보겠습니다.

 


 

Chap5 목차

 

1. 선형 SVM 분류

    - 소프트 마진 분류

2. 비선형 커널 분류

    - 다항식 커널

    - 유사도 특성

    - 가우시안 RBF 커널

    - 계산 복잡도

3. SVM 회귀

4. SVM 이론

    - 결정 함수와 목적 함수

    - 콰드라틱 프로그래밍(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() 메서드를 제공

[return 5-4. SVM 이론]

[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

(이 내용의 경우 너무 수학적으로 딥하게 들어가는 경향이 있어 따로 포스팅하도록 하겠습니다.)