/usr/bin/env python3


Kernelized Support Vector Machine


데이터가 단순한 초평면으로 구분되지 않는 더 복잡한 model을 만들 수 있도록 확장한 것이

Kernelized Support Vector Machine입니다.


선형적으로 구분되지 않는 클래스를 가진 binary data 생성하여 시각화해보겠습니다.

from mglearn.datasets import make_blobs

from sklearn.svm import LinearSVC

from mglearn.plots import plot_2d_separator

import matplotlib.pyplot as plt

import numpy as np


x, y = make_blobs(centers=4, random_state=8)

y = y % 2 # y를 binary화


linear_svm = LinearSVC().fit(x, y) 


plot_2d_separator(linear_svm, x, fill=False)


marker_set = ['o', '^']

for i, m in zip(np.unique(y), marker_set):

    plt.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m,

                label='class {}'.format(i), edgecolors='k')

plt.legend(bbox_to_anchor=(0.1, 1.1), fancybox=True)

plt.xlabel('feature 0', size=15)

plt.ylabel('feature 1', size=15)

plt.show()

선형적으로 구분되지 않는 클래스를 가진 binary data

이런 경우에는 Kernel Support Vector Machine를 사용할 수 있습니다.

support vector machine에서 데이터를 고차원 공간에 매핑하는 방법은 크게 2가지가 있으며


1. 원래 특성의 가능한 조합을 모두 계산((특성1)^2, (특성2)^5) 하거나

2. (radial basis function), RBF커널-- 가우시안 커널을 사용하는 것입니다.

RBF 커널의 경우 특성의 중요도는 테일러급수를 이용하기때문에 고차원일수록 줄어듭니다.



1번 방법으로 고차원 공간에 매핑하겠습니다.

# 선 하나로 구분되지 않는 데이터를 분석하기 위해 수학적방법으로 차수를 하나 늘려 비선형 데이터축 추가합니다.


# x[:, 1:]

# x[:, 1].reshape(-1, 1)

# x[:, 1][:,np.newaxis]

# 위의 3개는 모두 같은 결과입니다.

x_new = np.hstack([x, x[:, 1:]**2])


linear_svm_3d = LinearSVC().fit(x_new, y)


xx = np.linspace(x[:, 0].min()-2, x[:, 0].max()+2, num=50)

yy = np.linspace(x[:, 1].min()-2, x[:, 1].max()+2, num=50)


# xx, yy의 좌표를 이용하여 교차 좌표 만듬 ex) x=[1, 2], y=[3, 4]라면 (1, 3), (1, 4), (2, 3), (2, 4) 좌표 생성합니다.

XX, YY = np.meshgrid(xx, yy)

ZZ = YY ** 2 


params = np.c_[XX.ravel(), YY.ravel(), ZZ.ravel()] # np.c_ : XX, YY, ZZ의 원소를 하나씩 출력하여 한개의 행으로 묶기

dec = linear_svm_3d.decision_function(params)

plt.contourf(XX, YY, dec.reshape(XX.shape), # XX.shape = (50, 50) 

             levels=[dec.min(), 0, dec.max()],

                 alpha=0.5)


for i, m in zip(np.unique(y), marker_set):

    plt.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m, edgecolors='k')


plt.xlabel('feature 0', size=15)

plt.ylabel('feature 1', size= 15)

plt.show() 

고차원 특성을 이용해 구분한 Support vector machine decision boundary



Support Vector는 학습이 진행되는 동안 SVM은 각 train 데이터 포인트가 두 class 사이의 decision boundary를 구분하는 데 얼마나 중요한지를 배우게 됩니다.

일반적으로 train 데이터의 일부만 decision boundary를 만드는데 영향을 줍니다.


class 두 개의 경계에 위치한 데이터 포인트를 support vector라 합니다.


suppor vector의 중요도는 train 과정에서 학습하며 SVC객체의 dual_coef_에 저장되며

radial basis function, RBF커널, gaussian  : 특성의 중요도는 테일러급수를 이용하기때문에 고차원일수록 줄어듭니다.

이번에는 원래 2개 특성의 투영한 decision boundary를 그려보겠습니다.

from sklearn.svm import SVC

from mglearn.tools import make_handcrafted_dataset

from mglearn.plots import plot_2d_separator


x, y = make_handcrafted_dataset()


svm = SVC(kernel='rbf', C=10, gamma=0.1) # kernel, 규제정도, 커널폭의 역수

svm.fit(x, y)


plot_2d_separator(svm, x, eps=0.5)  # epsilon


marker_set = ['o', '^']

color_set = ['skyblue', 'orange']

class_set = np.unique(y)

for i, m, color in zip(class_set, marker_set, color_set):

    plt.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m, 

label='class {}'.format(i), edgecolors='k', c=color)


sv = svm.support_vectors_

print('{}'.format(sv))

# [[ 8.1062269   4.28695977]

#  [ 9.50169345  1.93824624]

#  [11.563957    1.3389402 ]

#  [10.24028948  2.45544401]

#  [ 7.99815287  4.8525051 ]]


# dual_coef_의 부호에 의해 서포트 벡터의 클래스 레이블이 결정됩니다.

sv_labels = svm.dual_coef_.ravel() > 0 # 중요도가 0보다 높은  것, 클래스 구분

idx_set = [~sv_labels, sv_labels]


for idx,m, color in zip(idx_set, marker_set, color_set):

    plt.scatter(sv[idx][:, 0], sv[idx][:, 1],  s=100, 

marker=m, edgecolors='k', linewidths=2, c=color)


plt.xlabel('feature 0', size=15)

plt.ylabel('feature 1', size=15) 

plt.show() 

RBF Kernel을 사용한 SVM으로 만든 decision boundary와 support vector




SVM에서 model의 complexity를 제어하는 주요 parmeter

gamma: kernel폭의 역수입니다.

gamma가 작을 수록 knernal폭이 넓어져 train 데이터의 영향 범위도 넓어집니다.


다음은 C와 gamma에 따른 decision area를 보여줍니다.

fig, axes = plt.subplots(3, 3) 


C_args = np.array([-1, 0, 3])

C_set = 10**C_args.astype(float)


gamma_args = np.arange(-1, 2)

gamma_set = 10**gamma_args.astype(float)


marker_set = ['o', '^']

for axe, C in zip(axes, C_set):

    for ax, gamma in zip(axe, gamma_set):

        params = {'C':C, 'gamma':gamma}


        svm = SVC(**params).fit(x, y)


        plot_2d_separator(svm, x, eps=0.5, ax=ax)

        ax.set_title('C={}, gamma={}'.format(C, gamma))


        color_set = ['skyblue', 'orange']

        for i, m, color in zip(np.unique(y), marker_set, color_set):

            ax.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m,

                       label='class {}'.format(i), c=color, edgecolors='k')


        sv = svm.support_vectors_

        idx = svm.dual_coef_.ravel() < 0

        idx_set = np.array([idx, ~idx])


        for i, idx, color, m in zip(np.unique(y), idx_set, color_set, marker_set):

            ax.scatter(sv[idx][:, 0], sv[idx][:, 1], marker=m,

                       c=color, label='class {} support vector'.format(i), s=100,

                       edgecolors='k', linewidths=2)

        

axes[0, 1].legend(loc='upper center', bbox_to_anchor=(0.5, 1.4), fancybox=True, shadow=True, ncol=4)

plt.show()

C와 gamma 에 따른 decision boundary와 support vector

왼쪽에서 오른쪽으로 가면서 gamma 0.1 -> 10 으로 증가합니다.

작은 gamma는 gaussia kernel의 반경을 크게 하여 많은 포인트들이 가까이 있는 것으로 고려됩니다.

따라서 왼쪽 그림의 decision boundary는 부드럽지만 오른쪽 그림의 decision boundary는 데이터 포인트 하나에 민감합니다.

gamma의 값이 클수록 model의 복잡도가 올라가는 것을 확인할 수 있습니다.


위에서 아래로는 C 값을 0.1 -> 1000으로 증가합니다.

linear model과 마찬가지로 작은 C값은 제약이 큰 model을 만듭니다. ==> 일반화 성능이 증가


C값을 증가시키면 제약이 거의 없어져 포인트 하나 값에 영향이 커집니다. ==> 복잡도 증가



SVM으로 Wine Data를 분석해보겠습니다. 

from sklearn.model_selection import train_test_split

from sklearn.datasets import load_wine

from sklearn.svm import SVC


wine = load_wine()


x_train, x_test, y_train, y_test = train_test_split(

      wine.data, wine.target, stratify=wine.target,

      random_state=0, test_size=0.25)


svc = SVC(random_state=0) # default: C=1, kernel='rbf', gamme='auto'

svc.fit(x_train, y_train) 


train_score = svc.score(x_train, y_train)

test_score = svc.score(x_test, y_test)


print('{:.3f}'.format(train_score))

# 1.000


print('{:.3f}'.format(test_score))

# 0.422

gamma의 기본값은 auto로 : 1/wine.data.shape[1]입니다.

train set는 완벽하지만 일반화는 42.2%의 정확도로 overfitting된 것을 알 수가 있습니다.



SVM은 parameter설정과 data scale에 매우 민감합니다. 이를 해결하기 위해 data preprocessing이 존재합니다.

preprocessing을 하기 전에 wine data의 특성을 시각화하여 알아보겠습니다.

plt.boxplot(x_train, manage_xticks=False) # manage_xticks: x축 눈금 작은 눈금 표시

plt.yscale('symlog') # y축 로그 스케일

plt.xlabel('feature list', size =15

plt.ylabel('feature size', size =15)

plt.show()

wine 데이터셋의 특성 값 범위(y축은 logscale)

그래프를 보면 wine 데이터셋의 특성은 자릿 수 자체가 달라서 SVM에서는 영향이 아주 큼을 알 수 있습니다.



이제는 SVM을 위한 데이터 전처리를 해보겠습니다.

전처리는 특성 값의 범위가 비슷해지도록 조정하는 것입니다.


모든 특성의 값을 0과 1사이로 맞추는 방법을 많이 사용합니다.

train_min = x_train.min(axis=0) 

train_range = (x_train - train_min).max(axis=0)

x_train_scaled = (x_train - train_min) / train_range

x_test_scaled = (x_test - train_min) / train_range

    

x_min_ori = x_train_scaled.min(axis=0)

print('{}'.format(x_min_ori))

# [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]


x_max_ori = x_train_scaled.max(axis=0)

print('{}'.format(x_max_ori))

# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]



위의 코드는 아래와 같이 전처리 메소드를 로드하여 사용할 수 있으며 앞으로는 이 방법을 사용하겠습니다.

from sklearn.preprocessing import MinMaxScaler


mms_scaler = MinMaxScaler()

mms_scaler.fit(x_train)


x_train_scaled_mms = mms_scaler.transform(x_train)

x_test_scaled_mms = mms_scaler.transform(x_test)


x_min = x_train_scaled.min(axis=0)

print('{}'.format(x_min))

# [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]


x_max = x_train_scaled.max(axis=0)

print('{}'.format(x_max))

# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]



이제 전처리한 데이터로 머신러닝을 해보겠습니다.

svc = SVC(random_state=0)

svc.fit(x_train_scaled_mms, y_train)


train_score = svc.score(x_train_scaled_mms, y_train)

test_score = svc.score(x_test_scaled_mms, y_test)


print('{:.3f}'.format(train_score))

# 0.970


print('{:.3f}'.format(test_score))

# 0.978

스케일을 조정하니 성능이 매우 좋아졌습니다.



이번에는 C값을 매우 크게 하여 규제를 완화 해보겠습니다.

svc = SVC(C=1000) 

svc.fit(x_train_scaled, y_train) 


train_score = svc.score(x_train_scaled_mms, y_train) 

test_score = svc.score(x_test_scaled_mms, y_test)


print('{:.3f}'.format(train_score))

# 1.000


print('{:.3f}'.format(test_score))

# 1.000

규제를 매우 완화 하니 train_score와 test_score모두 결과가 1이 나와 과소적합된 형태입니다.



SVM은 데이터의 특성이 몇개 안되도 복잡한 decision boundary를 만들 수 있습니다.

SVM은 parameter와 pre-procession(전처리)에 신경을 많이 써야합니다.


random_forest나 gradient boosting 같은 pre-processing이 거의 없는 모델을 많이 사용합니다.

그러나 예측이 어떻게 결정되었는지 이해하기 어렵고 비전문가에게 설명하기또한 난해합니다.




참고 자료:

[1]Introduction to Machine Learning with Python, Sarah Guido

'python 머신러닝 -- 지도학습 > Classifier' 카테고리의 다른 글

Neural Network(Deep Learning)  (0) 2018.03.17
Gradient Boosting Model  (0) 2018.03.15
Random Forest  (0) 2018.03.15
Decision Tree  (0) 2018.03.14
Multi Linear Classification  (0) 2018.03.14

+ Recent posts