Scikit-Learn을 이용한 머신러닝 분류기 - 2

ggob_2·2022년 9월 18일
0

machine_learning

목록 보기
3/20
post-thumbnail

Tackling overfitting via regularization

overfitting은 모델이 학습 데이터에 너무 치중한 나머지, 처음 보는 데이터에 대해 성능이 좋지 않은 경우를 말하며, 이는 머신러닝에서 매우 흔한 일이다. 반대로 underfitting은 모델이 충분히 복잡하지 않아, 학습 데이터의 특정 패턴 등을 파악하지 못하는 경우를 말하며 좋지 못한 성능의 원인이 된다.

regularization을 통해 bias-variancetradeoff를 잘 찾는 것은 모델 튜닝 기법 중 하나로, 굉장히 유용한 방법중 하나다. 가장 대중적으로 사용하는 것은 L2 regularization으로, 다음과 같이 나타낼 수 있다.

regularization을 추가한 partial dericative는 다음과 같이 표현한다.

여기서 regularization parameter λ를 통해 w와 관계없이, 학습 데이터에 얼마나 영향을 받을 지 조절할 수 있다. 값이 올라가면, regularization이 강해지는 것이다.

Logistic regression의 파라미터 Cλinversely propotional으로, 이를 통해 regularization 정도를 조절한다.

weights, params = [], []

for c in np.arange(-5, 5):
	lr = LogisticRegression(C=10.**c, multi_class='ovr')
	lr.fit(X_train_std, y_train)
	weights.append(lr.coef_[1])
	params.append(10.**c)

weights = np.array(weights)

plt.plot(params, weights[:, 0], label='Petal length')
plt.plot(params, weights[:, 1], linestyle='--', label='Petal width')

plt.ylabel('Weight coefficient')
plt.xlabel('C')
plt.legend(loc='upper left')
plt.xscale('log')
plt.show()

위 코드를 통해 10개의 다른 C를 가진 logistic regression 모델을 만들었다.

regularization strength를 키우면 overfitting을 줄일 수 있지만, 왜 모든 모델에 적용하지 않는지 생각해봐야 한다. 그 이유는 바로 underfitting이 발생할 수 있기 때문이다.

Maximum margin classification with support vector machines

SVMmisclassification을 최소화한 퍼셉트론과 달리, margin을 최대화 하는 알고리즘으로, margin은 나누어져 있는 클래스의 간격을 의미한다. support vectors는 클래스를 구분하는 decision boundary에 가장 가까이 붙어있는 학습 데이터를 말한다.

Maximum margin intuition

margin이 작은 경우에는 overfitting이 발생할 가능성이 높고, 반대로 큰 경우에는 generalization error가 줄어든다.

Dealing with a nonlinearly separable case using slack variables

SVM의 학습에서 사용되는 파라미터 Cmisclassification에 대한 패널티로 생각할 수 있다. 이를 통해 앞서 설명한 bias-variance trade-off를 조절한다.

SVM을 학습하여 성능을 확인한다.

from sklearn.svm import SVC

svm = SVC(kernel='linear', C=1.0, random_state=1)
svm.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=svm, 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()

SVMnonlinear classification problem을 해결하기 위해 kernel SVM 개념을 사용한다.

Kernal methods for linearly inseparable data

import matplotlib.pyplot as plt
import numpy as np

np.random.seed(1)

X_xor = np.random.randn(200, 2)
y_xor = np.logical_xor(X_xor[:, 0] > 0, X_xor[:, 1] > 0)
y_xor = np.where(y_xor, 1, 0)

plt.scatter(X_xor[y_xor == 1, 0], X_xor[y_xor == 1, 1], c='royalblue', marker='s', label='Class 1')
plt.scatter(X_xor[y_xor == 0, 0], X_xor[y_xor == 0, 1], c='tomato', marker='o', label='Class 0')

plt.xlim([-3, 3])
plt.ylim([-3, 3])
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend(loc='best')
plt.tight_layout()
plt.show()

위 코드를 통해, random noise를 갖는 XOR dataset을 만들었다.

그림에서 보는 것 처럼, lineardecision boundary를 이용해서는 두 클래스를 명확하게 구분할 수 없다. 이를 해결하기 위해 kernel method를 도입한다.

kernal method는 이러한 문제를 해결하기 위해 더 높은 차원 공간에 해당 데이터들을 매핑하여 linearly separable하게 한다.


Using the kernel trick to find separating hyperplanes in a high-dimensional space

SVM을 이용해 nonlinear한 문제를 풀기 위해서는, mapping funciton을 이용해 더 높은 차원 공간 데이터로 변환해야한다. 하지만, 차원을 강제로 올리는만큼 연산이 매우 복잡하다.

kernel function은 다음과 같이 정의한다.

주로 많이 사용하는 kernel functionradial basis function(RBF)로, Gaussian kernel이라고도 부른다.

데이터 간의 거리를 통해 유사도를 측정하여 1(매우 비슷), 0(전혀 안비슷)으로 구분한다.

다음은 SVM을 이용해 nonlinear한 문제를 풀 수 있도록 하는 코드를 알아본다.

svm = SVC(kernel='rbf', random_state=1, gamma=0.10, C=10.0)
svm.fit(X_xor, y_xor)

plot_decision_regions(X_xor, y_xor, classifier=svm)

plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

nonlinear한 데이터에 대해서도 잘 구분하는 모습을 확인할 수 있다.

아래 코드는 hyperparameter를 조금 변경한 뒤, iris-dataset에 적용하는 모습이다.

svm = SVC(kernel='rbf', random_state=1, gamma=0.2, C=1.0)
svm.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=svm, 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()

아래는 극단적인 경우를 보여주는 예시다. γ를 100.0으로 설정한 뒤 모습이다.

svm = SVC(kernel='rbf', random_state=1, gamma=100.0, C=1.0)
svm.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=svm, 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()

얼핏보면 모델이 굉장히 잘 학습된 것 같지만, 모르는 데이터 (새로운 데이터)에 대해 높은
generalization error를 보인다. 이러한 과정을 통해 γ를 적절한 값으로 설정하는 것이 중요함을 알 수 있다.

Decision tree learning

decision tree는 이름에서 나와있는 것 처럼, 여러 질문에 대한 답을 하며 학습을 진행한다.

위 그림은 decision tree의 기본 개념을 보여주며, 이와 비슷한 개념으로 sepal width >= 2.8cm?과 같은 질문을 통해 Iris-dataset에 있는 클래스를 구분할 수 있다.

decision tree의 깊이가 깊고, 노드가 많으면 많을 수록 overfitting의 위험이 있어, 적당한 maximum depth를 설정하는 것이 좋다.

Maximizing IG - getting the most bang for your buck

decision tree node에 가장 알맞은 feature를 넣으려면, tree learning 알고리즘을 최적화하기 위한 objective function을 정의해야한다.

binary decision tree에서 주로 사용하는 impurity measure, splitting criteria는 다음과 같다.

  • Gini impurity
  • Entropy
  • Classification error

entropy의 시각화를 위한 코드는 다음과 같다.

def entropy(p):
	... return - p * np.log2(p) - (1 - p) * np.log2((1 - p))

x = np.arange(0.0, 1.0, 0.01)
ent = [entropy(p) if p != 0 else None for p in x]

plt.ylabel('Entropy')
plt.xlabel('Class-membership probability p(i=1)')
plt.plot(x, ent)
plt.show()

Gini impuritymisclassification 확률을 최소화하기 위한 기준을 나타낸다.

classification error는 다음과 같다.

3가지 방식을 비교하기 위한 코드는 다음과 같다.

>>> import matplotlib.pyplot as plt
>>> import numpy as np

>>> def gini(p):
... return p*(1 - p) + (1 - p)*(1 - (1-p))

>>> def entropy(p):
... return - p*np.log2(p) - (1 - p)*np.log2((1 - p))

>>> def error(p):
... return 1 - np.max([p, 1 - p])

x = np.arange(0.0, 1.0, 0.01)
ent = [entropy(p) if p != 0 else None for p in x]
sc_ent = [e*0.5 if e else None for e in ent]
err = [error(i) for i in x]
fig = plt.figure()
ax = plt.subplot(111)

for i, lab, ls, c, in zip([ent, sc_ent, gini(x), err],
                     	 ['Entropy', 'Entropy (scaled)',
                      	'Gini impurity',
                      	'Misclassification error'],
                      	['-', '-', '--', '-.'],
                      	['black', 'lightgray',
                      	'red', 'green', 'cyan']):
                      	line = ax.plot(x, i, label=lab,
                      	linestyle=ls, lw=2, color=c)

ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=5, fancybox=True, shadow=False)
ax.axhline(y=0.5, linewidth=1, color='k', linestyle='--')
ax.axhline(y=1.0, linewidth=1, color='k', linestyle='--')

plt.ylim([0, 1.1])
plt.xlabel('p(i=1)')
plt.ylabel('impurity index')
plt.show()

Building a decision tree

너무 복잡할 경우 overfitting의 문제가 있는 decision tree는, scikit-learn을 이용해 최대 4 깊이, Gini impurity를 사용해 구현한다.

from sklearn.tree import DecisionTreeClassifier

tree_model = DecisionTreeClassifier(criterion='gini', max_depth=4, random_state=1)
tree_model.fit(X_train, y_train)

X_combined = np.vstack((X_train, X_test))
y_combined = np.hstack((y_train, y_test))

plot_decision_regions(X_combined, y_combined, classifier=tree_model, test_idx=range(105, 150))

plt.xlabel('Petal length [cm]')
plt.ylabel('Petal width [cm]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

scikit-learn에서는 학습이 종료된 후, visualize 도구도 제공한다.

from sklearn import tree

feature_names = ['Sepal length', 'Sepal width', 'Petal length', 'Petal width']

tree.plot_tree(tree_model, feature_names=feature_names, filled=True)

plt.show()

위 그림을 통해 모델이 어떤 기준으로 클래스를 구분했는지 알 수 있다. 부모노드에서 자식노드로 가지칠 때,
left -> true right -> false를 의미한다. 아쉽게도 scikit-learn에서는 더 이상 수동으로 기준을 설정하는 기능을 제공하지 않는다.

Combining multiple decision tree via random forests

Ensemble 학습 기법은 좋은 성능 및 overfitting에 대한 견고함 때문에 인기가 많다. 본 장에서는 random forest 기법을 통해 decision tree들의 ensemble에 대해 알아본다.

random forest의 순서는 다음과 같다.

  1. random bootstrap sample을 선택한다.
  2. 1번 데이터를 이용해 decision tree를 그린다.
    2-1. replacement 없이 feature를 랜덤으로 선택한다. (중복 없이)
    2-2. information gain을 극대화하는 objective function을 이용해 구분한다.
  3. 1-2 번을 k번 반복한다.
  4. decision treeprediction을 집계하는 majority vote를 진행한다.

random forest를 사용할 경우, hyperparameter 세팅에 관해 신경쓰지 않아도 되고, noise에 꽤나 견고하기 때문에 overfitting에도 강력한 성능을 보인다.

더 많은 tree를 사용할 경우, 컴퓨팅 연산이 필요하긴 하지만 훨씬 더 좋은 성능을 보여준다. 나머지 부분은 scikit-learn에서 알아서 하지만, 신경써야 할 부분은 사용할 tree의 수다.

작은 bootstrap samplerandom forest에서 randomness를 충족할 수 있고 overfitting을 방지할 수 있는 수단이지만, 좋지 못한 성능을 낼 수 있고 큰 bootstrap sampleoverfitting의 가능성을 키우지만 성능이 좋아질 수 있다.

최근 구현된 random forest에서는 적당한 bias-variacne trade-off를 갖는 bootstrap sample을 선택한다.

from sklearn.ensemble import RandomForestClassifier

forest = RandomForestClassifier(n_estimators=25, random_state=1, n_jobs=2)
forest.fit(X_train, y_train)

plot_decision_regions(X_combined, y_combined, classifier=forest, test_idx=range(105,150))

plt.xlabel('Petal length [cm]')
plt.ylabel('Petal width [cm]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

decision tree의 형상과 비슷한 plot을 확인할 수 있고, 좋은 성능을 보인다.
위 코드에서는 n_estimators=25의 옵션을 부여해 25개의 decision tree를 사용했다.

K-nearest neighbors - a lazy learning algorithm

KNNlazy learner의 예로, discriminative function을 학습하지 않고, training dataset을 기억한다.

KNN의 절차는 다음과 같다.

  1. distance metrick 선택
  2. 분류하고자 하는 데이터에 해당하는 k-nearest neighbor를 탐색
  3. majority vote를 통해 class label 할당

majority votek-nearest neighbor가 진행한다.

이를 위한 코드는 다음과 같다.

from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=5, p=2, metric='minkowski')
knn.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=knn, 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()

이론 상 별로 좋지 않을 것 같은 알고리즘이였는데도, 성능이 매우 괜찮았다. 하지만 KNNcurse of dimensionality 문제로 인해 overfitting에 매우 취약하다. 이는 평면 상에서는 옆에 있는 것 처럼 보이지만 고차원의 환경에서는 매우 멀리 떨어져있는 문제가 발생할 수도 있기 때문이다.

profile
고양이 모시는 집사

0개의 댓글