본문 바로가기
스터디/인공지능, 딥러닝, 머신러닝

[Machine Learning] SVM in Python (1) Decision Boundary

by 궁금한 준이 2023. 5. 10.
728x90
반응형

 

 

 

Synopsis

sklearn의 iris dataset으로 간단히 binary classification을 해보자.

import numpy as np

import matplotlib.pyplot as plt

from sklearn.svm import SVC
from sklearn import datasets
from sklearn.metrics import confusion_matrix

 

SVC의 defalut kernel은 'rbf'이지만 linear로 먼저 살펴보자

# Load Iris dataset
iris = datasets.load_iris()
X = iris["data"][:, (2, 3)]  # petal length, petal width
y = iris["target"]

# setosa 와 versicolor 를 분류하는 데이터만 선택
setosa_or_versicolor = (y == 0) | (y == 1)
X = X[setosa_or_versicolor]
y = y[setosa_or_versicolor]

# build SVM-Classifier and fit
svm_clf = SVC(kernel="linear", C=1e6) # Constraint 강하게
svm_clf.fit(X, y)

# predict
y_pred = svm_clf.predict(X)
confusion_matrix(y, y_pred) 

'''
array([[50,  0],
       [ 0, 50]])
'''

confusion matrix를 보아, 모두 TF, TN으로 분류하였다.

 

Visualization

kernel이 linear이고, 사용한 feature가 2개이므로, 2차원 공간에서 hyperplane이 직선으로 나타날 것이다.

특별히 kernel='linear'인 경우, coef_ 와 intercept_ 를 이용하여 hyperplane의 계수들을 구할 수 있다.

Decision Boundary

# set range of X and Y
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1

# create meshgrid
h = 0.02 # step size in the mesh
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                     np.arange(y_min, y_max, h))

# predict by meshgrid
z = svm_clf.predict(np.c_[xx.ravel(), yy.ravel()])
z = z.reshape(xx.shape)

# plot the decision boundary
plt.gca().set_aspect('equal') # equal-ratio
plt.contourf(xx, yy, z, cmap=plt.cm.coolwarm, alpha=0.6)

Decision Boundary (1)
Decision Boundary (1)

 

SVM 수식은 다음과 같다. (사이킷런에서는 $b$가 양수로 구현되어있다.)

\[ \mathbf{w}^\top \mathbf{x} + b = 0 \]

여기서 $\mathbf{w}$는 coefficient, $b$는 intercept이다. 

사이킷런에서는 다음과 같이 멤버변수로 접근할 수 있다.

svm_clf.coef_ 
# array([[1.29411744, 0.82352928]])

svm_clf.intercept_
array([-3.78823471])

2차원 벡터이므로 직선의 방정식을 이용하여 기울기를 구할 수 있다.

$\mathbf{w} = [w_0,. w_1]^\top$이고 $\mathbf{x} = [x_0, x_1]^\top$이므로

$w_0 x_0 + w_1 x_1 + b = 0 \Longleftrightarrow x_1 = -\cfrac{w_0}{w_1}x_0 -\cfrac{w_0}{w_1}b$ 를 이용하여 구할 수 있다.

 

해당 코드를 추가한 후 시각화하면 다음과 같다.

# set range of X and Y
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1

# create meshgrid
h = 0.02 # step size in the mesh
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                     np.arange(y_min, y_max, h))

# predict by meshgrid
z = svm_clf.predict(np.c_[xx.ravel(), yy.ravel()])
z = z.reshape(xx.shape)

# plot the decision boundary
plt.gca().set_aspect('equal') # equal-ratio
plt.contourf(xx, yy, z, cmap=plt.cm.coolwarm, alpha=0.6)

# get hyperplane
w = svm_clf.coef_[0]
b = svm_clf.intercept_[0]

# decision boundary
x0 = np.linspace(x_min, x_max, 200)
a = -w[0] / w[1]
y0 = a * x0 - (svm_clf.intercept_[0]) / w[1]
plt.plot(x0, y0, "k-", linewidth=2)
plt.axis([0, 5.5, 0, 2])
plt.show()

Decision Boundary (2)
Decision Boundary (2)

이제 support vector를 지나는 평행한 두 hyperplane(여기서는 직선)을 구해보자.

support vector는 다음과 같이 구할 수 있다.

 

svs = svm_clf.support_vectors_
'''
array([[1.9, 0.4],
       [3. , 1.1]])
'''

plt.scatter(svs[:, 0], svs[:, 1], s=180, facecolors='#FFAAAA')

 

이제 margin을 구해보자.

margin = $\cfrac{1}{\Vert \mathbf{w} \Vert}$ 이고, $\mathbf{w}$는 정규화(normalized)되지 않았으므로, normal vector를 unit vector로 만들어 크기를 $1$로 만들고 margin만큼 곱하여 간격을 만든다.

# w = svm_clf.coef_[0]

margin = 1 / np.sqrt(np.sum(w **2))
unit_normal = w / np.sqrt(np.sum(w **2))

boundary_points = np.array(list(zip(x0, y0)))
lower_points = boundary_points - unit_normal * margin
upper_points = boundary_points + unit_normal * margin

support vector를 지나는 직선은 이중선으로 하여 그리면 다음과 같다.

# set range of X and Y
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1

# create meshgrid
h = 0.02 # step size in the mesh
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                     np.arange(y_min, y_max, h))

# predict by meshgrid
z = svm_clf.predict(np.c_[xx.ravel(), yy.ravel()])
z = z.reshape(xx.shape)

# plot the decision boundary
plt.gca().set_aspect('equal') # equal-ratio
plt.contourf(xx, yy, z, cmap=plt.cm.coolwarm, alpha=0.6)

# get hyperplane
w = svm_clf.coef_[0]
b = svm_clf.intercept_[0]

# decision boundary
x0 = np.linspace(x_min, x_max, 200)
a = -w[0] / w[1]
y0 = a * x0 - (svm_clf.intercept_[0]) / w[1]
plt.plot(x0, y0, "k-", linewidth=2)

# calculate margin
# get paralles that pass through the support vectors
margin = 1 / np.sqrt(np.sum(w **2))
unit_normal = w / np.sqrt(np.sum(w **2))

boundary_points = np.array(list(zip(x0, y0)))
lower_points = boundary_points - unit_normal * margin
upper_points = boundary_points + unit_normal * margin

# plot the margin lines
plt.plot(x0, y0, "k-", linewidth=2)
plt.plot(lower_points[:, 0], lower_points[:, 1], "k--", linewidth=2)
plt.plot(upper_points[:, 0], upper_points[:, 1], "k--", linewidth=2)

# get support vectors and plot as pink (#FFAAAA)
svs = svm_clf.support_vectors_
plt.scatter(svs[:, 0], svs[:, 1], s=180, facecolors='#FFAAAA')

plt.axis([0, 5.5, 0, 2])
plt.show()

Decision Boundary (3)
Decision Boundary (3)

마지막으로 iris 데이터와 범례를 추가하면 다음과 같다.

# set range of X and Y
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1

# create meshgrid
h = 0.02 # step size in the mesh
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                     np.arange(y_min, y_max, h))

# predict by meshgrid
z = svm_clf.predict(np.c_[xx.ravel(), yy.ravel()])
z = z.reshape(xx.shape)

# plot the decision boundary
plt.gca().set_aspect('equal') # equal-ratio
plt.contourf(xx, yy, z, cmap=plt.cm.coolwarm, alpha=0.6)

# get hyperplane
w = svm_clf.coef_[0]
b = svm_clf.intercept_[0]

# decision boundary
x0 = np.linspace(x_min, x_max, 200)
a = -w[0] / w[1]
y0 = a * x0 - (svm_clf.intercept_[0]) / w[1]
plt.plot(x0, y0, "k-", linewidth=2)

# calculate margin
# get paralles that pass through the support vectors
margin = 1 / np.sqrt(np.sum(w **2))
unit_normal = w / np.sqrt(np.sum(w **2))

boundary_points = np.array(list(zip(x0, y0)))
lower_points = boundary_points - unit_normal * margin
upper_points = boundary_points + unit_normal * margin

# plot the margin lines
plt.plot(x0, y0, "k-", linewidth=2)
plt.plot(lower_points[:, 0], lower_points[:, 1], "k--", linewidth=2)
plt.plot(upper_points[:, 0], upper_points[:, 1], "k--", linewidth=2)

# get support vectors and plot as pink (#FFAAAA)
svs = svm_clf.support_vectors_
plt.scatter(svs[:, 0], svs[:, 1], s=180, facecolors='#FFAAAA')

# plot iris data
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.show()

Decision Boundary (4)
Decision Boundary (4)

728x90
반응형