728x90
반응형
SMALL
728x90
반응형
LIST
728x90
반응형
SMALL

이번 포스팅은 다중 선형 분류에 대하여 알아보도록 하겠습니다

선형 이진 분류 또는 선형 회귀에 대해 알고 싶은 분들은 이전 포스팅들을 참고해 주세요~!

2023.05.09 - [Programming/특성 공학] - [Machine Learning] 선형 이진 분류

 

[Machine Learning] 선형 이진 분류

지난 선형 회귀 포스팅에 이어 이번 포스팅에서는 선형 이진 분류에 대하여 알아보도록 하겠습니다. 2023.05.02 - [Programming/특성 공학] - [Machine Learning] 선형 회귀 [Machine Learning] 선형 회귀 이번 블로

yuja-k.tistory.com

2023.05.02 - [Programming/특성 공학] - [Machine Learning] 선형 회귀

 

[Machine Learning] 선형 회귀

이번 블로그는 선형 모델에 대하여 알아보도록 합시다. 선형 모델은 매우 오래전 개발된 모델입니다. 선형 모델은 입력 특성에 대한 선형 함수를 만들어 예측을 수행합니다. 먼저 회귀의 선형

yuja-k.tistory.com

다중 클래스 분류란?

로지스틱 회귀는 기본적으로 softmax 함수를 이용하여 다중 분류를 지원하나, 다른 많은 모델들은 태생적으로 이진 분류만을 지원합니다. 즉 다중 클래스(Multi-class)를 지원하지 않습니다.

이진 분류 알고리즘을 다중 클래스 알고리즘으로 확장하는 방법은 일대다(One vs Rest 또는 One vs All) 방법입니다. 일대다 방식은 각 클래스를 다른 모든 클래스와 구분하도록 이진 분류 모델을 학습시킵니다.

결국 클래스의 개수만큼 이진 분류 모델이 만들어지며, 예측할 때 이렇게 만들어진 모든 이진 분류기가 작동하여 가장 높은 점수를 내는 분류기의 클래스를 예측값으로 선택하게 됩니다.

지금까지 해왔던 것과 마찬가지로 다중 클래스 분류도 어쨌든 이진 분류를 기반으로 생각해야 하기 때문에 방정식 자체가 똑같습니다.

다중 클래스 로지스틱 회귀의 수학은 다른 모델들의 위의 일대다 방식과는 조금 다릅니다. 하지만 여기서도 클래스마다 계수 벡터와 절편을 만들며, 예측 방법도 같습니다.

 

𝑖번째 데이터 포인트 𝑋𝑖의 출력 𝑌𝑖가 클래스 𝑐일 확률 𝑃𝑟(𝑌𝑖=𝑐)는 𝐾개의 클래스에 대한 각각의 계수를 데이터 포인트에 곱하여 자연상수(𝑒)에 지수함수를 적용한 합으로써 클래스에 대한 값을 나누어 계산합니다.

위의 함수를 소프트맥스 함수 표현식이라 하며, 수식의 간소화를 위해 계수벡터에 절편 𝑏가 포함 되어 있는 것으로 나타냅니다.

따라서 다중 클래스 로지스틱 회귀에서도 계수 벡터와 절편이 존재합니다.

세 개의 클래스를 가진 간단한 데이터셋에 일대다 방식을 적용시켜 보겠습니다. 이 데이터셋은 2차원이며 각 클래스의 데이터는 정규분포(가우시안 분포)를 따릅니다.

#필요 라이브러리 임포트
from IPython.display import display
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn
from sklearn.model_selection import train_test_split
import platform
plt.rcParams['axes.unicode_minus'] = False

path = 'c:/Windows/Fonts/malgun.ttf'
from matplotlib import font_manager, rc
if platform.system() == 'Darwin':
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
else:
    print('Unknown system... sorry!')
from sklearn.datasets import make_blobs

X, y = make_blobs(random_state=42)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel("특성 0")
plt.ylabel("특성 1")
plt.legend(["클래스 0", "클래스 1", "클래스 2"])

이 데이터셋으로 먼저 LinearSVC 분류기를 훈련해 보겠습니다.

from sklearn.svm import LinearSVC
linear_svm = LinearSVC().fit(X, y)
print("계수 배열의 크기 : ", linear_svm.coef_.shape)
df_coef = pd.DataFrame(columns=["특성 0","특성 1"] ,data=linear_svm.coef_)
df_coef

print("절편 배열의 크기 : ", linear_svm.intercept_.shape)
df_intercept = pd.DataFrame(columns=["절편"], data = linear_svm.intercept_)
df_intercept

coef_ 배열의 크기는 (3,2)입니다. coef_의 행은 세 개의 클래스가 각각 대응하는 계수 벡터들을 각각 담고 있으며, 열은 각 특성에 따른 계수 값을 가지고 있습니다. intercept_는 각 클래스의 절편을 담아낸 1차원 벡터입니다.

세 개의 이진분류기가 만드는 경계를 시각화해보겠습니다.

mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15) # -15 ~ 15 까지 50개의 수열을 생성

for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_, mglearn.cm3.colors):
    plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color) # 판별 함수 사용

plt.ylim(-10, 15)
plt.xlim(-10, 8)
plt.xlabel("특성 0")
plt.ylabel("특성 1")
plt.legend(['클래스 0', '클래스 1', '클래스 2', '클래스 0 경계','클래스 1 경계', '클래스 2 경계'], loc=(1.01, 0.3))

각 훈련 데이터의 클래스들에 대한 결정 경계를 그려 보았습니다. 파란색 선은 클래스 0으로, 주황색 선은 클래스 1로, 초록색 선은 클래스 2로 경계합니다. 각 경계에서 겹치는 부분들은 조금 더 가까운 쪽으로 예측을 하게 됩니다.

중앙의 삼각형 영역은 모든 데이터 포인트가 나머지로 분류한 곳으로써 분류 공식의 결과가 가장 높은 클래스가 포인트로써 지정되게 됩니다.

조금 더 알아보기 쉽도록 경계면을 그려 보겠습니다.

mglearn.plots.plot_2d_classification(linear_svm, X, fill=True, alpha=.7)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15)

for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_, mglearn.cm3.colors):
    plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)

plt.legend(['클래스 0', '클래스 1', '클래스 2', '클래스 0 경계', '클래스 1 경계', '클래스 2 경계'], 
           loc=(1.01, 0.3))
plt.xlabel('특성 0')
plt.ylabel('특성 1')

선형 모델의 주요 매개변수는 회귀 모델은 alpha, 분류 모델 (LinearSVC, LogisticRegression)에서는 C입니다. alpha의 값이 클수록, C 값이 작아질수록 모델은 단순해집니다.

회귀 모델은 이러한 매개변수를 조절하는 것이 꽤나 중요하며 보통 C와 alpha는 로그 스케일 ( 10배 단위로 조절 하는 것. 0.01, 0.1, 1, 10 등 )로 최적치를 정하게 됩니다.

또한 L1 규제와 L2 규제를 정해야 하는데 어떠한 규제를 사용해야 할지 결정 지어 주는 상황은 다음으로 정리해 볼 수 있겠습니다.

  • 중요한 특성이 많지 않으면 L1 규제를 사용해 예측에 사용하지 않도록 만들 수 있습니다.
  • 모델의 해석이 중요한 요소일 때도 ( 특정 요소가 매우 중요한 요소일 경우 ) L1 규제를 사용할 수 있습니다.
  • 중요한 특성이 많으면 L2 규제를 사용해 약간이라도 예측에 영향을 미치도록 만들 수 있습니다.

선형 모델은 학습 속도가 빠르고 예측도 매우 빠릅니다. 매우 큰 데이터셋이던, 희소한 데이터셋이던 잘 작동합니다.

참고로 수십만 ~ 수백만 개의 샘플로 이뤄진 대용량 데이터 셋이라면 기본 설정보다 더 빨리 처리할 수 있도록 LogisticRegression과 Ridge에 solver='sag' 옵션을 부여해 줄 수 있습니다.

아니면 SGDClassifier 또는 SDGRegressor를 사용해 볼 수도 있습니다.

선형 모델은 샘플에 비해 특성이 많을 때 잘 작동하며 다른 모델로 학습하기 어려운 매우 큰 데이터셋에도 선형 모델을 많이 사용합니다.

하지만 저 차원의 데이터셋에서는 ( 특성이 많이 없는 ) 데이터셋에서는 다른 모델들의 일반화 성능이 더 좋습니다. 

 

다음 블로그에서는 의사결정 트리에 대하여 다뤄보도록 하겠습니다!

728x90
반응형
LIST
728x90
반응형
SMALL

지난 선형 회귀 포스팅에 이어 이번 포스팅에서는 선형 이진 분류에 대하여 알아보도록 하겠습니다.

2023.05.02 - [Programming/특성 공학] - [Machine Learning] 선형 회귀

 

[Machine Learning] 선형 회귀

이번 블로그는 선형 모델에 대하여 알아보도록 합시다. 선형 모델은 매우 오래전 개발된 모델입니다. 선형 모델은 입력 특성에 대한 선형 함수를 만들어 예측을 수행합니다. 먼저 회귀의 선형

yuja-k.tistory.com

선형 모델은 '분류'에서도 사용됩니다.

먼저 이진 분류부터 확인해 보겠습니다! 이진 분류를 위한 방정식은 다음과 같습니다.

위의 방정식은 선형 회귀와 매우 비슷한데, 이러한 특성들의 가중치 합을 그냥 사용하는 대신, 그 결과가 임계치라고 할 수 있는 0과 비교하여 0보다 작으면 클래스를 -1로 분류하고, 0보다 크면 +1로 예측합니다.

위의 규칙은 모든 선형 모델에서 동일하게 작동하며, 여기에서도 𝑤와 𝑏를 찾기 위한 여러 가지 방법들이 있습니다.

먼저 회귀에서는 𝑦̂ 이 특성의 선형 함수가 되었었습니다. 분류용 선형 모델에서는 결정 경계가 입력 특성의 선형 함수입니다.

즉 이진 선형 분류기는 선, 평면, 초평면을 사용하여 두 개의 클래스를 구분하는 분류기라고 생각하면 됩니다.

선형 모델을 학습시키기 위한 알고리즘은 다양한데, 다음의 두 방법으로 구분합니다.

.

  • 특정 계수(𝑤)와 절편(𝑏)의 조합이 훈련 데이터에 얼마나 잘 맞는지 측정하는 방법
  • 사용할 수 있는 규제가 있는지, 있다면 어떤 방식인지

각 알고리즘마다 훈련세트를 잘 학습하는지를 측정하는 방법은 각기 다릅니다. 불행하게도 수학적이고 기술적인 이유로, 알고리즘이 만드는 잘못된 분류의 수를 최소화하도록 𝑤와 𝑏를 조정하는 것은 불가능합니다. 손실이 일어난다고 할 수 있고, 완벽히는 아니지만 손실을 최소화하는 방법이 여럿 존재 합니다.

 

위에서 이야기한 첫 번째 목록( 손실 함수 loss function 라 합니다. )에 대한 차이는 별로 중요하지는 않습니다. 직접 잘못된 결과를 나타내는 0-1 손실 함수는 완전한 계단 함수입니다. 따라서 대신 사용할 수 있는 함수( surrogate loss function )를 사용하여 대신 최적화 합니다.

우리가 가장 널리 사용 할 수 있는 함수는 두 가지가 있습니다.

  • 로지스틱 회귀 (logistic Regression) - linear_model.LogisticRegression에 구현되어 있음 - 이진 분류에서 logistic 손실 함수 사용, 다중 분류에서 교차 엔트로피( cross-entropy ) 손실 함수 사용
  • 서포트 벡터 머신 ( Support Vector Machine - SVM ) - 제곱 힌지 ( squared hinge ) 손실 함수 사용

LogisticRegression은 회귀(Regression)가 들어가 있기는 하지만 회귀 알고리즘이 아닌 분류 알고리즘 이기 때문에 LinearRegression(선형 회귀)와 혼동하면 안 됩니다.

이진 분류용 모델인 forge 데이터 세트를 이용하여 LogisticRegression과 LinearSVC 모델을 각각 만들어 이 선형 모델들이 만들어낸 결정 경계를 확인해 보겠습니다.

참고로 두 모델 전부다 L2 규제를 사용하며, 차이점은 사용하는 손실 함수가 다르다는 것뿐입니다.

#필요 라이브러리 임포트
from IPython.display import display
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn
from sklearn.model_selection import train_test_split
plt.rcParams['axes.unicode_minus'] = False

import platform
path = 'c:/Windows/Fonts/malgun.ttf'
from matplotlib import font_manager, rc
if platform.system() == 'Darwin':
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
else:
    print('Unknown system... sorry')
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC

X, y = mglearn.datasets.make_forge()

fig, axes = plt.subplots(1, 2, figsize=(10, 3))

for model, ax in zip([LinearSVC(), LogisticRegression()], axes):
    clf = model.fit(X, y)
    mglearn.plots.plot_2d_separator(clf, X, fill=False, eps=0.5, ax=ax, alpha=.7)
    mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
    ax.set_title("{}".format(clf.__class__.__name__))
    ax.set_xlabel("특성 0")
    ax.set_ylabel("특성 1")
    
axes[0].legend()

먼저 forge 데이터셋의 첫 번째 특성을 x축에, 두 번째 특성을 y축에 배치시켰습니다.

LinearSVC와 LogisticRegression으로 만들어낸 결정 경계가 직선으로 표현되어 아래쪽은 특성 0으로, 위쪽은 특성 1로 구분한 것이 확인됩니다.
즉, 새롭게 추가될 데이터가 직선을 기준으로 위로 놓이게 되면 특성 1을 의미하고, 아래쪽으로 놓이면 특성 0으로 분류할 것이라고 분류될 것입니다. 두 모델은 비슷한 결정 경계를 만들었는데, 각각 똑같이 두 개의 포인트를 잘못 구분한 것이 확인됩니다.


각 모델의 규제의 강도를 줄이는 매개변수는 C 값을 활용합니다. 여기서 C 값이 높아지면 규제가 감소하고, C값이 낮아지면 규제가 증가합니다.

즉, C값을 높이 지정하면 훈련 세트에 최대한 가깝게 맞추기 위해 노력하고, C값을 낮추면 계수(𝑤)를 0에 가깝게 맞추도록 노력하게 됩니다.

다르게 설명할 수도 있는데, C값이 낮아지면 데이터 포인트 중 다수에 맞추려고 노력하고, C 값을 높이면 개개의 데이터 포인트를 정확히 분류하려고 노력합니다.

 

다음은 C값에 따라서 LinearSVC의 결정경계가 달라지는 모양을 알아봅니다.

mglearn.plots.plot_linear_svc_regularization()

왼쪽 그림은 너무 낮은 C값 때문에 규제가 많이 적용되었습니다. 잘 보면 다수의 데이터 포인트에 몰린 쪽으로 직선이 그어져 있는 것이 확인됩니다. 즉 다수의 포인트에 맞추려고 노력했다는 증거가 될 수 있겠네요

중간 그림은 C값이 조금 커져서 잘못 분류한 샘플에 굉장히 민감해진 것이 확인됩니다. 잘못 분류된 데이터 쪽으로 결정 경계가 그어져 있는 것이 확인되나요?

오른쪽 그림에서 C값을 아주 크게 설정했더니 결정 경계가 엄청나게 기울어졌고 클래스 0의 모든 데이터를 완벽하게 구분한 것이 확인됩니다.

forge 데이터셋의 모든 포인트를 직선으로 완벽히 구분하는 것은 불가능하기에 클래스 1의 포인트 한 개는 여전히 잘못 구분하였습니다.

오른쪽 그림의 모델은 모든 데이터 포인트를 정확하게 분류하려고 애썼지만 클래스의 전체적인 배치를 잘 파악하지 못한 것 같습니다. 즉 과대적합 되었다고 볼 수 있겠네요

회귀와 비슷하게 선형 모델은 낮은 차원의 데이터에서는 결정 경계가 직선 또는 평면이어서 매우 제한적인 것처럼 보입니다. 하지만 고차원에서는 분류에 대한 선형 모델이 매우 강력해지며 특성이 많아지면 과대적합 되지 않도록 규제를 조절하는 것이 중요합니다.

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

X_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, stratify=cancer.target, random_state=42)
logreg = LogisticRegression().fit(X_train, y_train)

print("훈련 세트 점수 : {:.3f}".format(logreg.score(X_train, y_train)))
print("테스트 세트 점수 : {:.3f}".format(logreg.score(X_test, y_test)))

기본값 C=1이 훈련 세트와 테스트 세트 양쪽에 95% 정도로 매우 좋은 정확도를 보이긴 하지만 두 세트의 성능이 매우 비슷합니다. 따라서 과소적합인 것 같으니 제약을 더 풀어주기 위해 C를 증가시켜 보겠습니다.

logreg100 = LogisticRegression(C=100).fit(X_train, y_train)

print("훈련 세트 점수 : {:.3f}".format(logreg100.score(X_train, y_train)))
print("테스트 세트 점수 : {:.3f}".format(logreg100.score(X_test, y_test)))

C=100을 사용하니 훈련 세트의 정확도가 높아졌고 테스트 세트의 정확도도 조금 증가하였습니다. 이는 복잡도가 높은 모델일수록 성능이 좋아진다는 것을 의미합니다.

반대로 규제를 더 강하게 하기 위해 C=0.01을 사용해 보겠습니다.

logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)

print("훈련 세트 점수 : {:.3f}".format(logreg001.score(X_train, y_train)))
print("테스트 세트 점수 : {:.3f}".format(logreg001.score(X_test, y_test)))

C=1 인 상태에서 과소 적합이었는데, 여기서 더 규제가 강해져 계수가 미치는 영향도가 줄어들었기 때문에 더욱더 과소적합 되었다고 볼 수 있겠습니다.

규제 매개변수 C 설정을 세 가지로 다르게 하여 계수를 시각화해서 확인해 보겠습니다.

plt.plot(logreg.coef_.T, 'o', label="C=1")
plt.plot(logreg100.coef_.T, '^', label="C=100")
plt.plot(logreg001.coef_.T, 'v', label="C=0.01")
plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
plt.hlines(0, 0, cancer.data.shape[1])
plt.ylim(-5, 5)
plt.xlabel("특성")
plt.ylabel("계수 크기")
plt.legend()

위의 그림을 잘 살펴보면 C 매개변수가 특성에 미치는 영향을 파악할 수 있는데, 릿지 회귀와 비슷한 모양새를 보이고 있습니다.

규제를 강하게 할수록( C 값이 감소할수록 ) 계수들을 0에 가깝게 만들지만 완전한 0으로 만들지는 않는 것이 보이고 있습니다.

그리고 세 번째 계수인 mean perimeter를 보면 C=100, C = 1 일 때는 음수지만 C=0.0001 이면 양수가 되며, C = 1일 때보다 절댓값이 더 큰 것을 확인할 수 있습니다.

이와 같이 모델을 해석하면 계수가 클래스와 특성의 연관성을 알려줄 수도 있습니다.

다른 예로 texture error 특성은 악성인 샘플과 영향이 깊다는 것도 알 수 있습니다. ( 음수 ) 하지만 mean perimeter는 계수의 부호가 바뀜에 따라서 양성이나 악성의 신호 모두가 될 수 있다고도 판단할 수 있습니다.

다음은 L1 규제를 사용한 LogisticRegression입니다.

for C, marker in zip([0.001, 1, 100], ['o', '^', 'v']):
    lr_l1 = LogisticRegression(C=C, penalty="l1").fit(X_train, y_train)
    print("C={:.3f} 인 로지스틱 회귀의 훈련 정확도 : {:.2f}".format(C, lr_l1.score(X_train, y_train)))
    print("C={:.3f} 인 로지스틱 회귀의 테스트 정확도 : {:.2f}".format(C, lr_l1.score(X_test, y_test)))
    plt.plot(lr_l1.coef_.T, marker, label="C={:.3f}".format(C))

plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
plt.hlines(0, 0, cancer.data.shape[1])
plt.xlabel("특성")
plt.ylabel("계수 크기")

plt.ylim(-5, 5)
plt.legend(loc=3)

이진 분류에서의 선형 모델과 회귀에서의 선형 모델 사이에는 유사점이 굉장히 많습니다.

penalty 매개변수를 활용하여 전체 특성을 사용할지( L2 규제 ), 일부 특성만을 사용할지 ( L1 규제 )를 사용할지를 결정할 수 있습니다.

penalty="l2" 또는 penalty="l1"을 설정할 수 있습니다.

728x90
반응형
LIST

+ Recent posts