Iris flowers data를 이용하여 퍼셉트론 classification을 위한 학습을 실시하면 위와 같이 직선형의 decision boundary 가 결정되며 W∙X = 0 으로 표현할 수 있다. 아울러 청색 영역은 “+1” 라벨 값으로 학습된 영역이므로 decision boundary 에 평행한 직선을 긋게 되면 W∙X = +1 이 되며 붉은 색 영역에서는 “-1”라벨 값으로 학습된 영역이므로 decision boundary 에 평행한 직선을 긋게 되면 W∙X = -1 이 된다. 평행 조건이 지켜지지 못할 경우에는 decision boundary를 벗어나게 되므로 유의한다.
물론 위 결과는 학습 후 세부적인 눈금을 그어 생성된 좌표에 대해서 테스트 결과 얻어진 라벨 값으로 작도한 결과이다. 이 그림에서 decision boundary 양 옆으로 평행하게 support vector를 통과하는 2개의 hyperplane 을 그을 수 있다. support vector 란 서로 다른 종류의 데이터 군들이 인접해 있을 때 가장 가까이 위치한 소수의 데이터들을 의미한다. 위 그림의 사례에서는 입력 데이터들이 2개의 좌표 성분에 의해 기술되므로 벡터로 볼 수 있는 것이다.
퍼셉트론 학습에 의해 결정된 decision boundary에 의해 분할되는 청색 영역과 붉은 색 영역에서 decision boundary 에 평행하면서 support vector를 통과하는 직선을 다음과 같이 표현할 수 있다.
위 관계식에 대해서 뺄셈을 하면 다음 관계식이 얻어질 것이다.
hyperplane은 서로 다른 데이터를 구분하는 경계인데 여기서는 decision boundary 로부터 support vector를 통과하게 되는 범위까지를 지칭하기로 한다. 따라서 벡터
이러한 경우에 SVM(Support Vector Machine) 기법을 적용하기 위해서는 위 식의 우변을 대상으로 즉 웨이트 W를 변수로 하는 함수 즉 objective function을 도입하여 최대화(maximization) 하도록 하자. 아울러 이 최대화 작업에 있어서 동시에 부가되어야 할 제약 조건(constraint)는 다음과 같다.
(i)는 개개의 N개의 data 샘플을 뜻한다.
한편 서로 다른 2가지 데이터들의 일부 데이터들이 상대방 영역에 게릴라처럼 깊히 위치하게 되는 경우가 문제다. 그림에서 보면 녹색원이 청색지역에 일부 위치하고 아울러 청색 x가 녹색영역에 위치하기도 한다. 퍼셉트론 계산으로 decision boundary 영역을 나타낼 수는 있으나 classification을 위한 의미 설정이 문제가 되며 서로 영역이 겹치고 있으므로 support vector 설정도 문제가 될 것이다. 그래도 SVM을 적용하려면 상대방 영역에 속한 데이터는 포기한 상태에서 decision boundary를 중심으로 hyperplane을 설정한다.
한편 포기한 데이터를 어떤 방식으로 조금이라도 구제할 것인가 하는 문제를 양의 값을 가지는 slack(느슨한) 변수를 도입하여 다루어 보자. decision boundary 를 경계로 녹색 영역의 라벨 값이 “+1”, 청색 영역이 “-1”이라면 다음과 같이 slack 변수 ξ에 의해 그 사이의 어중간한 값들을 표현해 보자.
퍼셉트론 라벨링 단계에서 그 값들이 각각 스텝 함수에 해당하는 “+1” 과 “-1”이므로 그 사이의 어중간한 값들이 정의 되지 않으며 그림에서도 hyperplane 도 명확하게 서로 분리되어 있다. 하지만 양의 값을 가지는 slack 변수 ξ를 도입 조절함으로 인해 물론 서로를 넘어가 버리지 않는 범위 내에서 hyperplane들이 더욱 가까워질 수 있다.
이 비선형적으로만 분리가 가능한 아니면 잘못 분류될 수밖에 없는 데이터들에 대해서 엄격한 선형 제약조건을 완화시켜 최적화(Optimization)과정이 잘 수렴할 수 있도록 1995년에 Vladimir Vapnik가 slack 변수를 도입하였다. Optimization은 머신 러닝에서 cost 함수의 최소화 과정도 포함하는 광의의 수학적 기법이다.
이러한 조건들 하에서 아래의 새로운 objective function을 위에 기술된 제약조건들(constraints)에 관해서 최소화 하도록 하자.
다음의 그림에서처럼 학습에 따라서 상당히 차이가 날 수도 있는 C값의 영향을 살펴보자.
C 값이 충분히 클 경우 정상적인 결과를 주고 있으나 C 값이 아주 작을 경우 어느 정도 classification이 되고는 있으나 Margin 영역 밖에 포기하는 데이터가 포함되어 있음에 유의하자.
scikit-learn 라이브러리 모듈의 SVM 기법 적용을 통해 Regularization 효과를 시험해 보자. kernel 명이 “linear’ 이며 파라메터 C 값을 부여하면 된다.
C=100.0 일때 최선의 결과를 보여준다. 반면에 C=0.01에서는 청색 classification 영역이 포함해야 할 데이터 영역을 아예 벗어나고 있다.
#ch03_data_regularization_1_3.py
from sklearn import datasets
import numpy as np
iris = datasets.load_iris()
X = iris.data[:, [2, 3]]
y = iris.target
print('Class labels:', np.unique(y))
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=1, stratify=y)
print('Labels counts in y:', np.bincount(y))
print('Labels counts in y_train:', np.bincount(y_train))
print('Labels counts in y_test:', np.bincount(y_test))
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)
from sklearn.linear_model import Perceptron
from sklearn.metrics import accuracy_score
ppn = Perceptron(n_iter=40, eta0=0.1, random_state=1)
ppn.fit(X_train_std, y_train)
y_pred = ppn.predict(X_test_std)
print('Misclassified samples: %d' % (y_test != y_pred).sum())
print('Accuracy: %.2f' % accuracy_score(y_test, y_pred))
print('Accuracy: %.2f' % ppn.score(X_test_std, y_test))
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0],
y=X[y == cl, 1],
alpha=0.8,
c=colors[idx],
marker=markers[idx],
label=cl,
edgecolor='black')
if test_idx:
X_test, y_test = X[test_idx, :], y[test_idx]
plt.scatter(X_test[:, 0],
X_test[:, 1],
c='',
edgecolor='black',
alpha=1.0,
linewidth=1,
marker='o',
s=100,
label='test set')
class LogisticRegressionGD(object):
"""Logistic Regression Classifier using gradient descent.
Parameters
------------
eta : float
Learning rate (between 0.0 and 1.0)
n_iter : int
Passes over the training dataset.
random_state : int
Random number generator seed for random weight
initialization.
Attributes
-----------
w_ : 1d-array
Weights after fitting.
cost_ : list
Logistic cost function value in each epoch.
"""
def __init__(self, eta=0.05, n_iter=100, random_state=1):
self.eta = eta
self.n_iter = n_iter
self.random_state = random_state
def fit(self, X, y):
""" Fit training data.
Parameters
----------
X : {array-like}, shape = [n_samples, n_features]
Training vectors, where n_samples is the number of samples and
n_features is the number of features.
y : array-like, shape = [n_samples]
Target values.
Returns
-------
self : object
"""
rgen = np.random.RandomState(self.random_state)
self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
self.cost_ = []
for i in range(self.n_iter):
net_input = self.net_input(X)
output = self.activation(net_input)
errors = (y - output)
self.w_[1:] += self.eta * X.T.dot(errors)
self.w_[0] += self.eta * errors.sum()
# note that we compute the logistic `cost` now
# instead of the sum of squared errors cost
cost = -y.dot(np.log(output)) - ((1 - y).dot(np.log(1 - output)))
self.cost_.append(cost)
return self
def net_input(self, X):
"""Calculate net input"""
return np.dot(X, self.w_[1:]) + self.w_[0]
def activation(self, z):
"""Compute logistic sigmoid activation"""
return 1. / (1. + np.exp(-np.clip(z, -250, 250)))
def predict(self, X):
"""Return class label after unit step"""
return np.where(self.net_input(X) >= 0.0, 1, 0)
X_train_01_subset = X_train[(y_train == 0) | (y_train == 1)]
y_train_01_subset = y_train[(y_train == 0) | (y_train == 1)]
print('Class labels:', np.unique(y_train_01_subset))
#Training a logistic regression model with scikit-learn
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(C=100.0, random_state=1)
lr.fit(X_train_std, y_train)
plot_decision_regions(X_combined_std, y_combined,
classifier=lr, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()