본문 바로가기
## 오래된 게시글 (미관리) ##/Python (Linux)

37. Python - 서포트 벡터 머신(소프트 마진 분류)

by #Glacier 2019. 2. 2.
반응형

저번에 라지 마진 분류를 보았다면 오늘 볼 것은 소프트 마진 분류입니다.


모든 샘플이 도로 바깥쪽에 올바르게 분류되어 있다면 이를 하드 마진 분류(hard margin classification)라고 합니다.

하지만 하드 마진 분류에는 2가지 문제점이 있습니다. 

데이터가 선형적으로 구분될 수 있어야 제대로 작동하며, 이상치에 민감합니다.


아래의 그래프에서는 붓꽃 데이터에 이상치가 있어 하드 마진을 찾을 수 없습니다. 

따라서 일반화가 잘 될 것 같지 않습니다.

아래의 코드를 통해 보면 알 수 있습니다.


X_outliers = np.array([[3.4, 1.3], [3.2, 0.8]])

y_outliers = np.array([0, 0])


#numpy.concatenate는 아래로 합쳐줍니다.

#따라서 X와 X_outliers의 0:1까지 값인 3.4, 1.3을 Xo1에 추가하여 넣습니다.(axis=0)

Xo1 = np.concatenate([X, X_outliers[:1]], axis=0)

yo1 = np.concatenate([y, y_outliers[:1]], axis=0)

Xo2 = np.concatenate([X, X_outliers[1:]], axis=0)

yo2 = np.concatenate([y, y_outliers[1:]], axis=0)


svm_clf2 = SVC(kernel='linear', C=10**9)

svm_clf2.fit(Xo2, yo2)


plt.figure(figsize=(12,2.7))

plt.subplot(121)

plt.plot(Xo1[:, 0][yo1==1], Xo1[:, 1][yo1==1], 'bs')

plt.plot(Xo1[:, 0][yo1==0], Xo1[:, 1][yo1==0], 'yo')

plt.text(0.3, 1.0, '불가능!', fontsize=24, color='red')

plt.xlabel('꽃잎 길이', fontsize=14)

plt.ylabel('꽃잎 너비', fontsize=14)

plt.annotate('이상치', 

                            xy=(X_outliers[0][0], X_outliers[0][1]),

                            xytext=(2.5,1.7),

                            ha='center',

                            arrowprops = dict(facecolor='black', shrink=0.1),

                            fontsize=16,

                            )

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


plt.subplot(122)

plt.plot(Xo2[:, 0][yo2==1], Xo2[:, 1][yo2==1], 'bs')

plt.plot(Xo2[:, 0][yo2==0], Xo2[:, 1][yo2==0], 'yo')

plot_svc_decision_boundary(svm_clf2, 0, 5.5)

plt.xlabel('꽃잎 길이', fontsize=14)

plt.annotate('이상치', 

                            xy=(X_outliers[1][0], X_outliers[1][1]),

                            xytext=(3.2, 0.08),

                            ha='center',

                            arrowprops = dict(facecolor='black', shrink=0.1),

                            fontsize=16,

                            )

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

plt.show()



왼쪽 그래프에서는 하드 마진을 찾을 수 없고, 오른쪽 그래프의 결정 경계는 이상치가 없던 것과 매우 다르고, 

일반화가 잘 될 것 같지 않습니다.


이런 문제를 피하려면, 좀 더 유연한 모델이 필요합니다. 도로의 폭을 가능한 넓게 유지하는 것과, 

마진 오류(margin violation, 즉 샘플이 도로 중간이나 심지어 반대쪽에 있는 경우) 사이에 적절한 균형을 잡아야 합니다.

이를 소프트 마진 분류(Soft Margin Classification)라고 합니다.


사이킷런의 SVM모델에서는 C 하이퍼파라미터를 사용해 이 균형을 조절할 수 있습니다.

C값을 줄이면 도로의 폭이 넓어지지만, 마진 오류도 커집니다. 

아래의 그림은 예시입니다.


#C 하이퍼파라미터 조절 예시

# SVM모델이 과대적합이라면, C를 감소시켜 모델 규제가 가능하다.


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)] #Petal length, petal width

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)

Pipeline(memory=None,
     steps=[('sclaer', StandardScaler(copy=True, with_mean=True, with_std=True)), ('linear_svc', LinearSVC(C=1, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss='hinge', max_iter=1000, multi_class='ovr',
     penalty='l2', random_state=42, tol=0.0001, verbose=0))])

svm_clf.predict([[5.5, 1.7]])

#SVM 분류기는 로지스틱 회귀 분류기와는 다르게 클래스에 대한 확률을 제공하지 않습니다.

#사이킷런의 LinearSVC는 predict_proba() 메서드를 제공하지 않지만,

#SVC 모델은 probability = True로 매개변수를 지정하면 predict_proba() 메서드를 제공합니다.

#default = False

array([1.])

#여러가지 규제 설명을 비교하는 그래프

#svm_clf1과 2의 차이는 C=1이냐 C=100이냐의 차이입니다.


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([

                                ('sclaer', scaler),

                                ('linear_svc', svm_clf2), 

                                ])


scaled_svm_clf1.fit(X,y)

scaled_svm_clf2.fit(X,y)


Pipeline(memory=None,
     steps=[('sclaer', StandardScaler(copy=True, with_mean=True, with_std=True)), ('linear_svc', LinearSVC(C=100, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss='hinge', max_iter=1000, multi_class='ovr',
     penalty='l2', random_state=42, tol=0.0001, verbose=0))])

#스케일 되지 않은 파라미터로 변경


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]


plt.figure(figsize=(12,3.2))

plt.subplot(121)

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, 6)

plt.xlabel("꽃잎 길이", fontsize=14)

plt.ylabel("꽃잎 너비", fontsize=14)

plt.legend(loc="upper left", fontsize=14)

plt.title("$C = {}$".format(svm_clf1.C), fontsize=16)

plt.axis([4, 6, 0.8, 2.8])


plt.subplot(122)

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, 6)

plt.xlabel("꽃잎 길이", fontsize=14)

plt.title("$C = {}$".format(svm_clf2.C), fontsize=16)

plt.axis([4, 6, 0.8, 2.8])



위의 왼쪽 그래프는 C=1, 오른쪽 그래프는 C=100입니다.

C=100의 그래프가 Decision Boundary가 좁음을 알 수 있죠.

따라서 C 값이 크다면, 전체 마진도 커지기 때문에 오분류율이 줄어든다는 의미이고,

C값이 작다면 전체 마진이 작아져 오분류율을 어느 정도 허용하겠다는 의미가 됩니다.


#이 내용은 SVC에 상응하는 LinearSVC의 설정을 설명합니다.

#LinearSVC는 보통의 SVM구현과 달리 규제에 편향을 포함하고 있어서, 데이터의 스케일을 맞추지 않고 SVC모델과 비교하면

#큰 차이가 납니다. LinearSVC 모델의 loss 매개변수 default 값은 'squared_hinge' 입니다. 

#위 그래프는 StandardScaler로 데이터 스케일을 변경하고, LinearSVC로 결정 경계를 찾은 후,

#원본 좌표 공간으로 가중치와 편향의 스케일을 복원하여 나타낸 것입니다.



LinearSVC는 규제에 편향을 포함시킵니다.

그래서 훈련 세트에서 평균을 빼서, 중앙에 맞춰야 합니다.

StandardScaler를 사용하여 데이터 스케일을 맞추면, 자동으로 이렇게 됩니다.

그리고 loss 매개변수를 'hinge'로 지정해야 합니다. 

마지막으로, 훈련 샘플보다 특성이 많지 않다면 성능을 높이기 위해 dual 매개변수를 False로 지정해야 합니다.

(이 장 뒷부분에서 쌍대, duality 문제에 대해 설명합니다.)


 블로그 

출처


이 글의 상당 부분은  [핸즈온 머신러닝, 한빛미디어/오렐리앙 제롱/박해선] 서적을 참고하였습니다.


나머지는 부수적인 함수나 메서드에 대해 부족한 설명을 적어두었습니다.

학습용으로 포스팅 하는 것이기 때문에 복제보다는 머신러닝에 관심이 있다면 구매해보시길 추천합니다.


도움이 되셨다면 로그인 없이 가능한

아래 하트♥공감 버튼을 꾹 눌러주세요! 

반응형