쉽게 머신 러닝 학습에 도전하기 #2

in #kr8 years ago (edited)

혹시 파이썬이 처음이라면 미리 공부해야 할 내용들이 조금 있습니다.

아무래도 통계에 관련된 용어들을 미리 학습하면 도움이 됩니다. 같이 있는 강사님들 중에 통계책을 추천하신 분들이 있어서 2권 추가 합니다.

그림으로 설명하는 쏙쏙 통계학

2018-03-02_15-02-27.png

[세상에서 가장 재미있는 통계학] (http://www.yes24.com/24/goods/2509960?scode=032&OzSrank=1)
2018-03-02_15-03-13.png

첫번째: 파이썬을 공부하기 쉬운 사이트를 하나 알려드립니다. 아래의 사이트에서 파이썬 언어의 기본 문법을 공부하면 됩니다.
위키 독스의 점프투파이썬

2018-03-02_12-32-06.png

두번째: Pandas나 NumPy에 대한 기본 지식을 가지고 있어야 머신 러닝의 기초 데이터를 이해할 수 있습니다.
파이썬 문법이 어느정도 눈에 익으면 보면 좋은 중급책입니다.
"엔지니어를 위한 파이썬"(제이법, 나카쿠키 켄지 지음)
엔지니어를 위한 파이썬

2018-03-02_12-40-16.png

세번째: 머신러닝에 학습을 시작할 수 있는 단계입니다. 두권을 추천 드립니다.

"파이썬을 이용한 머신러닝, 딥러닝 실전 개발 입문"(위키북스, 쿠지라 히코우즈쿠애 지음, 윤인성 옮김)
파이썬을 이용한 머신러닝, 딥러닝 실전 개발 입문

그리고 제가 최근에 보고 있는 "Introduction to Machine Learning with Python"을 추천합니다.
Introduction to Machine Learning with Python
2018-03-02_12-44-07.png

"Introduction to Machine Learning with Python"책을 보면서 공부하고 있습니다.
제가 본 책중에 가장 쉽게 설명되어 있는 책입니다. 강추합니다~~
번역도 상당히 매끄럽고 저처럼머신러닝에 관심있는 분들이 공부해 보시면 좋을 것 같습니다.
개발툴은 아나콘다만 설치하시면 특별히 문제 없네요. 윈도우 10에 아나콘다를 설치했고
추가로 pip install mglearn을 했습니다.

2장 지도 학습

분류와 회귀
지도 학습에는 분류(Classification)과 회귀(Regression)이 있습니다. 분류는 미리 정의된 가능성 있는 여러 클래스 레이블 중 하나를 예측하는 것입니다. 회귀는 연속적인 숫자, 또는 프로그래밍 용어로 말하면 부동소수점수(실수)를 예측하는 것입니다. 어떤 사람의 교육수준, 나이, 주거지를 바탕으로 연간 소득을 예측하는 것이 회귀 문제의 한 예입니다.

일반화, 과대적합, 과소적합
모델이 처음보는 데이터에 대해 정확하게 예측할 수 있으면 이를 훈련 세트에서 테스트 세트로 일반화 되었다고 합니다.
보통 훈련 세트에 대해 정확히 예측하도록 모델을 구축합니다. 훈련 세트와 테스트 세트가 매우 비슷하다면 그 모델이 테스트 세트에서도 정확히 예측하리라 기대할 수 있습니다.
초보 데이터 과학자가 했던 것처럼 가진 정보를 모두 사용해서 너무 복잡한 모델을 만드는 것을 과대적합(overfitting)이라고 합니다.
너무 간단한 모델이 선택되는 것을 과소적합(underfitting)이라고 합니다.
모델을 복잡하게 할수록 훈련 데이터에 대해서는 더 정확히 예측할 수 있습니다. 그러나 너무 복잡해지면 훈련 세트의 각 데이터 포인트에 너무 민감해져 새로운 데이터에 잘 일반화되지 못합니다. 우리가 찾으려는 모델은 일반화 성능이 최대가 되는 최적점에 있는 모델입니다.

#지도학습알고리즘01.py
import mglearn
import numpy as np
import pandas as pd 
import matplotlib.pyplot as plt
%pylab 

#데이터셋을 만듭니다.
X, y = mglearn.datasets.make_forge()

mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
Out[21]: 
[<matplotlib.lines.Line2D at 0x2da0aa89be0>,
 <matplotlib.lines.Line2D at 0x2da0aa89d30>]

plt.xlabel("first attribute")
Out[22]: Text(0.5,23.4122,'first attribute')

plt.ylabel("second attribute")
Out[23]: Text(44.2222,0.5,'second attribute')

print("X.shape: {}".format(X.shape))
X.shape: (26, 2)

2018-02-26_15-05-29.png

X.shape값에서 알 수 있듯이 이 데이터셋은 데이터 포인트 26개와 특성 2개를 가집니다.
회귀 알고리즘 설명에는 인위적으로 만든 wave데이터셋을 사용하겠습니다.

X, y = mglearn.datasets.make_wave(n_samples=40)

plt.plot(X, y, 'o')
Out[30]: [<matplotlib.lines.Line2D at 0x2da0a10e048>]

plt.ylim(-3, 3)
Out[31]: (-3, 3)

plt.xlabel('attribute')
Out[32]: Text(0.5,23.4122,'attribute')

plt.ylabel('target')
Out[33]: Text(44.2222,0.5,'target')

20아8-02-26_15-20-36.png

유방암 종양의 임상 데이터를 기록해놓은 위스콘신 유방암(Wisconsin Breast Cancer)데이터 셋. 각 종양은 양성(benign:해롭지 않은 종양)과 악성(malignana:암종양)으로 레이블되어 있고 조직 데이터를 기반으로 종양이 악성인지를 예측할 수 있도록 학습하는 것이 과제입니다. scikit-learn에 있는 load_breast_cancer함수를 사용해서 불러올 수 있습니다.

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
print("cancer.keys(): \n{}".format(cancer.keys()))
cancer.keys(): 
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])

print("유방암 데이터의 형태:{}".format(cancer.data.shape))
유방암 데이터의 형태:(569, 30)

이 데이터셋은 569개의 데이터 포인트를 가지고 있고 특성은 30개입니다.

print("클래스별 샘플 개수:\n{}".format(cancer.data.shape))
클래스별 샘플 개수:
(569, 30)

569개의 데이터 포인트 중 212개는 악성이고 357개는 양성입니다.

print("클래스별 샘플 갯수:\n{}".format({n: v for n, v in zip(cancer.target_names, np.bincount(cancer.target))}))
클래스별 샘플 갯수:
{'malignant': 212, 'benign': 357}

feature_names속성을 확인하면 각 특성의 의미를 알 수 있습니다.

print("특성 이름:\n{}".format(cancer.feature_names))
특성 이름:
['mean radius' 'mean texture' 'mean perimeter' 'mean area'
 'mean smoothness' 'mean compactness' 'mean concavity'
 'mean concave points' 'mean symmetry' 'mean fractal dimension'
 'radius error' 'texture error' 'perimeter error' 'area error'
 'smoothness error' 'compactness error' 'concavity error'
 'concave points error' 'symmetry error' 'fractal dimension error'
 'worst radius' 'worst texture' 'worst perimeter' 'worst area'
 'worst smoothness' 'worst compactness' 'worst concavity'
 'worst concave points' 'worst symmetry' 'worst fractal dimension']

또 회귀 분석용 실제 데이터셋으로는 보스턴 주택 가격 데이터셋을 사용하겠습니다. 범죄율, 찰스강 인접도, 고속도로 접근성 등의 정보를 이용해 1970년대 보스턴 주변의 주택 평균 가격을 예측하는 것입니다. 데이터 포인트 506개와 특성 13개가 있습니다.

from sklearn.datasets import load_boston
boston = load_boston()
print("data shape: {}".format(boston.data.shape))
data shape: (506, 13)

이 데이터셋에서는 13개의 입력 특성뿐 아니라 특성끼리 곱하여 의도적으로 확정하겠습니다. 다시 말하면 범죄율과 고속도로 접근성의 개별 특성은 물론 범위율과 고속도로 접근성의 곱도 특성으로 생각한다는 뜻입니다. 이처럼 특성을 유도해내는 것을 특성공학(feature engineering)이라고 합니다.

X, y = mglearn.datasets.load_extended_boston()
print("X.shape: {}".format(X.shape))
X.shape: (506, 104)

k-최근접 이웃
새로운 데이터 포인트에 대해 예측할 땐 알고리즘이 훈련 데이터셋에서 가장 가까운 데이터 포인트 즉 ‘최근접 이웃’을 찾습니다.

mglearn.plots.plot_knn_classification(n_neighbors=1)

2018-02-26_16-15-19.png

최근접 이웃 3을 찾는 경우라면

mglearn.plots.plot_knn_classification(n_neighbors=3)

2018-02-26_16-18-53.png

앞에서 한 것처럼 일반화 성능을 평가할 수 있도록 데이터를 훈련 세트와 테스트 세트로 나눕니다.

from sklearn.model_selection import train_test_split
X, y = mglearn.datasets.make_forge()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

다음은 KNeighborsClassifier를 임포트하고 객체를 만듭니다. 이때 이웃의 수 같은 매개변수들을 지정합니다. 이웃의 수를 3으로 지정합니다.

from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)

이제 훈련 세트를 사용하여 분류 모델을 학습시킵니다. KNeighborsClassifier에서의 학습은 예측할 때 이웃을 찾을 수 있도록 데이터를 저장하는 것입니다.

clf.fit(X_train, y_train)
Out[54]: 
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=1, n_neighbors=3, p=2,
           weights='uniform')

테스트 데이터에 대해 predict메서드를 호출해서 예측합니다. 테스트 세트의 각 데이터 포인트에 대해 훈련 세트에서 가장 가까운 이웃을 계산한 다음 가장 많은 클래스를 찾습니다.

print("테스트 세트 예측: {}".format(clf.predict(X_test)))
테스트 세트 예측: [1 0 1 0 1 0 0]

print("테스트 세트 정확도: {:.2f}".format(clf.score(X_test, y_test)))
테스트 세트 정확도: 0.86

KNeighborsClassifier분석
2차원 데이터셋이므로 가능한 모든 테스트 포인트의 예측을 xy평면에 그려볼 수 있습니다. 그리고 각 데이터 포인트가 속한 클래스에 따라 평면에 색을 칠합니다. 이렇게 하면 알고리즘이 클래스 0과 클래스 1로 지정한 영역으로 나뉘는 결정 경계(decision boundary)를 볼 수 있습니다. 다음 코드는 이웃이 하나, 셋, 아홉 개일 때의 결정 경계를 보여줍니다.

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

for n_neighbors, ax in zip([1,3,9], axes):
    # fit메서드는 self객체를 반환합니다.
    # 그래서 객체 생성과 fit 메서드를 한줄에 쓸 수 있습니다.
    clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X, y)
    mglearn.plots.plot_2d_separator(clf, X, fill=True, eps=0.5, ax=ax, alpha=.4)
    mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
    ax.set_title("{} 이웃".format(n_neighbors))
    ax.set_xlabel("attribute 0")
    ax.set_ylabel("attribute 1")
    axes[0].legend(loc=3)

2018-02-26_16-39-26.png

위의 그림을 보면 이웃을 하나 선택했을 때는 결정 경계가 훈련 데이터에 가깝게 따라가고 있습니다. 이웃의 수를 늘릴수록 결정 경계는 더 부드러워집니다. 부드러운 경계는 더 단순한 모델을 의미합니다. 다시 말해 이웃을 적게 사용하면 모델의 복잡도는 높아지고 많이 사용하면 복잡도는 낮아집니다.

plt.plot(neighbors_settings, training_accuracy, label='train accuracy')
Out[75]: [<matplotlib.lines.Line2D at 0x2da0bf20518>]

plt.plot(neighbors_settings, test_accuracy, label='test accuracy')
Out[76]: [<matplotlib.lines.Line2D at 0x2da0bf4e198>]

plt.ylabel('accuracy')
Out[77]: Text(33.8472,0.5,'accuracy')

plt.xlabel('n_neighbors')
Out[78]: Text(0.5,23.4122,'n_neighbors')

plt.legend()
Out[79]: <matplotlib.legend.Legend at 0x2da0c094358>

2018-02-26_17-34-00.png

이 그램은 n_neighbors수(x축)에 따른 훈련 세트와 테스트 세트 정확도(y축)을 보여줍니다. 실제 이런 그래프는 매끈하게 나오지 않지만 여기서도 과대적합과 과소적합의 특징을 볼 수 있습니다. 최근접 이웃의 수가 하나일 때는 훈련 데이터에 대한 예측이 완벽합니다. 하지만 이웃의 수가 늘어나면 모델은 단순해지고 훈련 데이터의 정확도는 줄어듭니다. 이웃을 하나 사용한 테스트 세트의 정확도는 이웃을 많이 사용했을 때보다 낮습니다. 반대로 이웃을 10개 사용했을 때는 모델이 너무 단순해서 정확도는 더 나빠집니다. 정확도가 가장 좋을 때는 중간 정도인 여섯개를 사용한 경우입니다. 이 그래프의 범위는 눈여겨보면 가장 나쁜 정확도도 88%여서 수긍할 만 합니다.

k-최근접 이웃 회귀
k-최근접 이웃 알고리즘은 회귀 분석에도 쓰입니다. 이번에는 wave데이터셋을 이용해서 이웃이 하나인 최근접 이웃을 사용해 봅니다. x축에 세개의 테스트 데이터를 흐린 별 모양으로 표시했습니다. 최근접 이웃을 한 개만 이용할 때 예측은 그냥 가장 가까운 이웃의 타깃값입니다. 이 예측은 진한 별 모양으로 표시했습니다.

mglearn.plots.plot_knn_regression(n_neighbors=1)

2018-02-26_17-40-13.png

mglearn.plots.plot_knn_regression(n_neighbors=3)

2018-02-26_17-40-20.png

scikit-learn에서 회귀를 위한 k-최근접 이웃 알고리즘은 KNeighborsRegressor에 구현되어 있습니다.

from sklearn.neighbors import KNeighborsRegressor
X, y = mglearn.datasets.make_wave(n_samples=40)

#wave데이터셋을 훈련 세트와 테스트 세트로 나눈다.
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

#이웃의 수를 3으로 하여 모델의 객체를 만든다. 
reg = KNeighborsRegressor(n_neighbors=3)

reg.fit(X_train, y_train)
Out[88]: 
KNeighborsRegressor(algorithm='auto', leaf_size=30, metric='minkowski',
          metric_params=None, n_jobs=1, n_neighbors=3, p=2,
          weights='uniform')

print("테스트 세트 예측:\n{}".format(reg.predict(X_test)))
테스트 세트 예측:
[-0.05396539  0.35686046  1.13671923 -1.89415682 -1.13881398 -1.63113382
  0.35686046  0.91241374 -0.44680446 -1.13881398]

print("테스트 세트 R^2: {:.2f}".format(reg.score(X_test, y_test)))
테스트 세트 R^2: 0.83

83%의 적중률을 보인다.

KNeighborsRegressor분석
이 1차원 데이터셋에 대해 가능한 모든 특성 값을 만들어 예측해 볼 수 있습니다. 이를 위해 x축을 따라 많은 포인트를 생성해 테스트 데이터셋을 만듭니다.

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

# -3과 3사이에 1000개의 데이터 포인트를 만든다.
line = np.linspace(-3, 3, 1000).reshape(-1, 1)

for n_neighbors, ax in zip([1,3,9], axes):
    #1, 3, 9 이웃을 사용한 예측을 합니다.
    reg = KNeighborsRegressor(n_neighbors=n_neighbors)
    reg.fit(X_train, y_train)
    ax.plot(line, reg.predict(line))
    ax.plot(X_train, y_train, '^', c=mglearn.cm2(0), markersize=8)
    ax.plot(X_test, y_test, 'v', c=mglearn.cm2(1), markersize=8)
    ax.set_title("{} 이웃의 훈련 스코어: {:.2f} 테스트 스코어: {:.2f}".format(n_neighbors, reg.score(X_train, y_train), reg.score(X_test, y_test)))
    ax.set_xlabel("attribute")
    ax.set_ylabel("target")
    

axes[0].legend(["Model predict", "Train data/target", "test data/target"], loc="best")
Out[96]: <matplotlib.legend.Legend at 0x2da0a800ef0>

이 그래프에서 볼 수 있듯이 이웃을 하나만 사용할 때는 훈련 세트의 각 데이터 포인트가 예측에 주는 영향이 커서 예측값이 훈련 데이터 포인트를 모두 지나갑니다. 이는 매우 불안정한 예측을 만들어 냅니다. 이웃을 많이 사용하면 훈련 데이터에는 잘 안 맞을 수 있지만 더 안정된 예측을 얻게 됩니다.

장단점과 매개변수
k-NN의 장점은 이해하기 쉬운 모델이라는 점입니다. 그리고 많이 조정하지 않아도 자주 좋은 성능을 발휘합니다. 더 복잡한 알고리즘을 적용해보기 전에 시도해볼 수 있는 좋은 시작점입니다.
k-최근접 이웃 알고리즘이 이해하긴 쉽지만 예측이 느리고 많은 특성을 처리하는 능력이 부족해 현업에서는 잘 쓰지 안습니다.

선형 모델
회귀의 경우 선형 모델을 위한 일반화된 예측 함수는
y = w[0] * x[0] + w[1] * x[1] + … + w[p] * x[p] + b
이 식에서 x[0]부터 x[p]까지는 하나의 데이터 포인트에 대한 특성을 나타내며 w와 b는 모델이 학습할 파라메터입니다. 그리고 y는 모델이 만들어낸 예측값입니다.

mglearn.plots.plot_linear_regression_wave()
w[0]: 0.393906  b: -0.031804

2018-02-26_18-55-04.png

선형 회귀(최소제곱법)
선형 회구 또는 최소제곱법(Ordinary least squares)는 가장 간단하고 오래된 회귀용 선형 알고리즘입니다.
다음은 선형 모델을 만드는 코드입니다.

from sklearn.linear_model import LinearRegression
X, y = mglearn.datasets.make_wave(n_samples=60)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
lr = LinearRegression().fit(X_train, y_train)

기울기 파라메터(w)는 가중치(weight) 또는 계수(coefficient)라고 하며 lr객체의 coef_속성에 저장되어 있고 편향(offset) 또는 절편(intercept) 파라미터(b)는 intercept_속성에 저장되어 있습니다.

print("lr.coef_: {}".format(lr.coef_))
lr.coef_: [ 0.39390555]

print("lr.intercept_: {}".format(lr.intercept_))
lr.intercept_: -0.031804343026759746

intercept_속성은 항상 실수값 하나지만, coef_속성은 각 입력 특성에 하나씩 대응되는 NumPy배열입니다. wave데이터셋에는 입력 특성이 하나뿐이므로 lr.coef_도 원소를 하나만 가지고 있습니다.

print("훈련 세트 점수: {:.2f}".format(lr.score(X_train, y_train)))
훈련 세트 점수: 0.67

print("테스트 세트 점수: {:.2f}".format(lr.score(X_test, y_test)))
테스트 세트 점수: 0.66

R2의 값이 0.66인 것은 그리 좋은 결과는 아닙니다. 하지만 훈련 세트와 테스트 세트의 점수가 매우 비슷한 것을 알 수 있습니다. 이는 과대적합이 아니라 과소적합인 상태를 의미합니다. LinearRegression모델이 보스턴 주택가격 데이터셋 같은 복잡한 데이터셋에서 어떻게 동작하는지 살펴봅니다. 이 데이터셋은 샘플이 506개가 있고 특성은 유도된 것을 합쳐 104개 입니다. 먼저 데이터셋을 읽고 훈련 세트와 테스트 세트로 나눕니다. 그런 다음 이전과 같은 방식으로 선형 모델을 만듭니다.

X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
lr = LinearRegression().fit(X_train, y_train)

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

훈련 데이터와 테스트 데이터 사이의 이런 성능 차이는 모델이 과대적합되었다는 확실한 신호이므로 복잡도를 제어할 수 있는 모델을 사용해야 합니다. 기본 선형 회귀 방식 대신 가장 널리 쓰이는 모델은 다음에 볼 릿지 회귀입니다.

릿지 회귀
릿지(Ridge)도 회귀를 위한 선형 모델이므로 최소적합버에서 사용한 것과 같은 예측 함수를 사용하빈다. 하지만 릿지 회귀에서의 가중치(w)선택은 훈련 데이터를 잘 예측하기 위해서 뿐만 아니라 추가 제약 조건을 만족시키기 위한 목적도 있습니다. 가중치의 절대값을 가능한 한 작게 만드는 것입니다. 다시 말해서 w의 모든 원소가 0에 가깝게 되길 원합니다. 직관적으로 생각하면 이는 모든 특성이 출력에 주는 영향을 최소한으로 만듭니다. 기울기를 작게 만든다. 이런 제약을 규제(regularization)이라고 합니다.
릿지 회귀는 linear_model.Ridge에 구현되어 있습니다. 릿지 회귀가 확장된 보스턴 주택가격 데이터셋에 어떻게 적용되는지 살펴봅니다.

from sklearn.linear_model import Ridge
ridge = Ridge().fit(X_train, y_train)

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

결과를 보니 훈련 세트에의 점수는 LinearRegression보다 낮지만 테스트 세트에 대한 점수는 더 높습니다. 선형 회귀는 이 데이터셋에 과대적합되지만 Ridge는 덜 자유로운 모델이기 때문에 과대적합이 적어집니다. 모델의 복잡도가 낮아지면 훈련 세트에서의 성능이 나빠지지만 더 일반화된 모델이 됩니다. 관심 있는 것은 테스트 세트에 대한 성능이기 때문에 LinearRegression보다 Ridge모델을 선택해야 합니다.
Ridge는 모델을 단순하게(계수를 0에 가깝게) 해주고 훈련 세트에 대한 성능 사이를 절충할 수 있는 방법을 제공합니다. 사용자는 alpha매개변수로 훈련 세트의 성능 대비 모델을 얼마나 단순화할지를 지정할 수 있습니다. 앞의 예제에서는 매개변수의 기본값인 alpha=1.0을 사용했습니다. 하지만 이 값이 최적이라고 생각할 이유는 없습니다. alpha값을 높이면 계수를 0에 더 가깝게 만들어서 훈련 세트의 성능은 나빠지지만 일반화에는 도움을 줄 수 있습니다.

ridge10 = Ridge(alpha=10).fit(X_train, y_train)
print("훈련 세트 점수: {:.2f}".format(ridge10.score(X_train, y_train)))
훈련 세트 점수: 0.79
print("테스트 세트 점수: {:.2f}".format(ridge10.score(X_test, y_test)))
테스트 세트 점수: 0.64

아래의 코드에서 alpha=0.1 은 꽤 좋은 성능을 냅니다. 테스트 세트에 대한 성능이 높아질 때까지 alpha값을 줄일 수 있을 것입니다.

ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)
print("훈련 세트 점수: {:.2f}".format(ridge01.score(X_train, y_train)))
훈련 세트 점수: 0.93
print("테스트 세트 점수: {:.2f}".format(ridge01.score(X_test, y_test)))
테스트 세트 점수: 0.77

또한 alpha값에 따라 모델의 coef_속성이 어떻게 달라지는지를 조사해보면 alpha매개변수가 모델을 어떻게 변경시키는지 더 깊게 이해할 수 있습니다. 높은 alpha값은 제약이 더 많은 모델이므로 작은 alpha값일 때보다 coef_의 절대값 크기가 작을 것이라고 예상할 수 있습니다.

plt.plot(ridge10.coef_, '^', label="Ridge alpha=10")
Out[122]: [<matplotlib.lines.Line2D at 0x2da0c0c6048>]
plt.plot(ridge.coef_, 's', label="Ridge alpha=1")
Out[123]: [<matplotlib.lines.Line2D at 0x2da0c0c6240>]
plt.plot(ridge01.coef_, 'v', label="Ridge alpha=0.1")
Out[124]: [<matplotlib.lines.Line2D at 0x2da0d2af320>]
plt.plot(lr.coef_, 'o', label="LinearRegression")
Out[127]: [<matplotlib.lines.Line2D at 0x2da0bae41d0>]

plt.xlabel("계수 목록")
Out[128]: Text(0.5,366.227,'계수 목록')

plt.ylabel("계수 크기")
Out[129]: Text(361.097,0.5,'계수 크기')

plt.hlines(0, 0, len(lr.coef_))
Out[130]: <matplotlib.collections.LineCollection at 0x2da0baee2b0>

plt.ylim(-25, 25)
Out[131]: (-25, 25)

plt.legend()
Out[132]: <matplotlib.legend.Legend at 0x2da0baeee48>

규제의 효과를 이해하는 또 다른 방법은 alpha값을 고정하고 훈련 데이터의 크기를 변화시켜보는 것입니다. 보스턴 주택 가격 데이터셋에서 여러 가지 크기로 샘플릿하여 LinearRegression과 Ridge(alpha=1)을 적용한 것입니다. 데이터셋의 크기에 따른 모델의 성능 변화를 나타낸 그래프를 학습 곡선이라고 합니다.

mglearn.plots.plot_ridge_n_samples()

2018-02-26_19-31-37.png

예상대로 모든 데이터셋에 대해 릿지와 선형 회귀 모두 훈련 세트의 점수가 테스트 세트의 점수보다 높습니다. 릿지에는 규제가 적용되므로 릿지의 훈련 데이터 점수가 전체적으로 선형 회귀의 훈련 데이터 점수보다 낮습니다. 그러나 테스트 데이터에서는 릿지의 점수가 더 높으며 특별히 작은 데이터셋에서는 더 그렇습니다. 데이터셋 크기가 400 미만에서는 선형 회귀는 어떤 것도 학습하지 못하고 있습니다.

라쏘
선형 회귀에 규제를 적용하는데 Ridge의 대안으로 Lasso가 있습니다. 릿지 회귀에서와 같이 라쏘(Lasso)도 계수를 0에 가깝게 만들려고 합니다. 하지만 방식이 조금 다르며 이를 L1규제라고 합니다. L1규제의 결과로 라쏘를 사용할 때 어떤 계수는 정말 0이 됩니다.

from sklearn.linear_model import Lasso
lasso = Lasso().fit(X_train, y_train)

print("훈련 세트 점수: {:.2f}".format(lasso.score(X_train, y_train)))
훈련 세트 점수: 0.29
print("테스트 세트 점수: {:.2f}".format(lasso.score(X_test, y_test)))
테스트 세트 점수: 0.21
print("사용한 특성의 수: {}".format(np.sum(lasso.coef_ != 0)))
사용한 특성의 수: 4

결과에서 볼 수 있듯이 Lasso는 훈련 세트와 테스트 세트 모두에서 결과가 좋지 않습니다. 이는 과소적합이며 104개의 특성 중 4개만 사용한 것을 볼 수 있습니다. 과소적합을 줄이기 위해서 alpha값을 줄여보겠습니다. 이렇게 하려면 max_iter(반복 실행하는 최대 횟수)의 기본값을 늘려야 합니다.

lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)
print("훈련 세트 점수: {:.2f}".format(lasso001.score(X_train, y_train)))
훈련 세트 점수: 0.90
print("테스트 세트 점수: {:.2f}".format(lasso001.score(X_test, y_test)))
테스트 세트 점수: 0.77
print("사용한 특성의 수: {}".format(np.sum(lasso001.coef_ != 0)))
사용한 특성의 수: 33

alpha값을 낮추면 모델의 복잡도는 증가하여 훈련 세트와 테스트 세트에서의 성능이 좋아집니다. 성능은 Ridge보다 조금 나은데 사용된 특성은 104개 중 33개뿐이어서 아마도 모델을 분석하기가 조금 더 쉽습니다.
그러나 alpha값을 너무 낮추면 규제의 효과가 없어져 과대적합이 되므로 LinearRegression의 결과과 비슷해집니다.

lasso00001 = Lasso(alpha=0.0001, max_iter=100000).fit(X_train, y_train)
print("훈련 세트 점수: {:.2f}".format(lasso00001.score(X_train, y_train)))
훈련 세트 점수: 0.95
print("테스트 세트 점수: {:.2f}".format(lasso00001.score(X_test, y_test)))
테스트 세트 점수: 0.64
print("사용한 특성의 수: {}".format(np.sum(lasso00001.coef_ != 0)))
사용한 특성의 수: 94

그래프로 그리면 다음과 같다.

plt.plot(lasso.coef_, 's', label='Lasso alpha=1')
Out[147]: [<matplotlib.lines.Line2D at 0x2da0bbcfd68>]

plt.plot(lasso001.coef_, '^', label='Lasso alpha=0.01')
Out[149]: [<matplotlib.lines.Line2D at 0x2da0bbf7b70>]

plt.plot(lasso00001.coef_, 'v', label='Lasso alpha=0.0001')
Out[150]: [<matplotlib.lines.Line2D at 0x2da0bc111d0>]

plt.plot(ridge01.coef_, 'o', label='Ridge alpha=0.1')
Out[152]: [<matplotlib.lines.Line2D at 0x2da0bc1abe0>]

plt.legend(ncol=2, loc=(0, 1.05))
Out[154]: <matplotlib.legend.Legend at 0x2da0bc2f278>

2018-02-26_19-58-30.png

alpha가 1일 때 계수 대부분이 0일 뿐만 아니라 나머지 계수들도 크기가 작다는 것을 알 수 있습니다. alpha를 0.01로 줄이면 대부분의 특성이 0이 되는 분포를 얻게 됩니다. alpha=0.0001이 되면 계수 대부분이 0이 아니고 값도 커져 꽤 규제받지 않는 모델을 얻게 됩니다. 비교를 위해 릿지 회귀를 원 모양으로 나타냈습니다.
실제로 이 두 모델 중 보통은 릿지 회귀를 선호합니다. 하지만 특성이 많고 그중 일부분만 중요하다면 Lasso가 더 좋은 선택일 수 있습니다.

분류형 선형 모델
선형 모델은 분류에도 널리 사용합니다. 먼저 이진분류(Binary classification)을 살펴봅니다.
y = w[0] * x[0] + w[1] * x[1] + … + w[p] * x[p] + b > 0

분류형 선형 모델에서는 결정 경계가 입력의 선형 함수입니다. 많은 애플리케이션에서 앞 목록의 첫번째 항목(손실 함수 loss function)에 대한 차이는 크게 중요하지 않습니다. 가장 널리 알려진 두 개의 선형 분류 알고리즘은 로지스틱 회귀(logistic regression)와 SVM(Suport vector classifier)의 약자입니다.

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("attribute 0")
    ax.set_ylabel("attribute 1")
    
axes[0].legend()
Out[166]: <matplotlib.legend.Legend at 0x2da0e4d7d68>

2018-02-27_10-30-42.png

새로운 데이터가 이 직선 위쪽에 놓이면 클래스 1로 분류될 것이고 반대로 직선 아래쪽에 놓이면 클래스 0으로 분류될 것입니다. 이 두 모델은 비슷한 결정 경계를 만들었습니다. 그리고 똑같이 포인트 두개를 잘못 분류했습니다.
LogitsticRegression과 LinearSVC에서 규제의 강도를 결정하는 매개변수는 C입니다. C의 값이 높아지면 규제가 감소합니다. 다시 말해 매개변수로 높은 C값을 지정하면 LogisticRegression과 LinearSVC 는 훈련 세트에 가능한 최대로 맞추려 하고 반면에 C값을 낮추면 모델은 계수 벡터(w)가 0에 가까워지도록 만듭니다.

mglearn.plots.plot_linear_svc_regularization()

2018-02-27_10-48-25.png

위의 그림은 아주 작은 C값 때문에 규제가 많이 적용되었습니다. 클래스 0의 대부분은 아래에 있고 클래스 1의 대부분은 위에 있습니다. 클래스 0의 대부분은 아래에 있고 클래스 1의 대부분은 위에 있습니다. 규제가 강해진 모델은 비교적 수평에 가까운 결정경계를 만들었고 잘못 분류한 데이터 포인트는 두개입니다. 중간 그림은 C값이 조금 더 크며 잘못 분류한 두 샘플에 민감해져 결정 경계가 기울어졌습니다. 오른쪽 그림에서 C값을 아주 크게 하였더니 결정 경계는 더 기울었고 마침내 클래스 0의 모든 데이터 포인트를 올바로 분류했습니다.
회귀와 비슷하게 분류에서의 선형 모델은 낮은 차원의 데이터에서는 결정 경계가 직선이거나 평면이어서 매우 제한적인 것처럼 보입니다. 하지만 고차원에서는 분류에 대한 선형 모델이 매우 강력해지며 특성이 많아지면 과대적합되지 않도록 하는 것이 매우 중요해집니다.
유방암 데이터셋을 사용해서 LogisticRegression을 좀 더 분석해 봅니다.

mglearn.plots.plot_linear_svc_regularization()
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)))
훈련 세트 점수: 0.955
print("테스트 세트 점수: {:.3f}".format(logreg.score(X_test, y_test)))
테스트 세트 점수: 0.958

기본값 C=1이 훈련 세트와 테스트 세트 양쪽에 95%정확도로 꽤 훌륭한 성능을 내고 있습니다. 하지만 훈련 세트와 테스트 세트의 성능이 매우 비슷하므로 과소적합인 것 같습니다. 모델의 제약을 더 풀어주기 위해 C를 증가시켜보겠습니다.

logreg100 = LogisticRegression(C=100).fit(X_train, y_train)
print("훈련 세트 점수: {:.3f}".format(logreg100.score(X_train, y_train)))
훈련 세트 점수: 0.972
print("테스트 세트 점수: {:.3f}".format(logreg100.score(X_test, y_test)))
테스트 세트 점수: 0.965

C=100을 사용하니 훈련 세트의 정확도가 높아졌고 테스트 세트의 정확도도 조금 증가했습니다. 이는 복잡도가 높은 모델일수록 성능이 좋음을 말해줍니다.
이번에는 규제를 더 강하게 하기 위해 기본값(C=1)이 아니라 C=0.01을 사용하면 어떻게 되는지 살펴보겠습니다.

logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)
print("훈련 세트 점수: {:.3f}".format(logreg001.score(X_train, y_train)))
훈련 세트 점수: 0.934
print("테스트 세트 점수: {:.3f}".format(logreg001.score(X_test, y_test)))
테스트 세트 점수: 0.930

예상대로 이미 과소적합된 모델에서 왼쪽으로 더 이동하게 되므로 훈련 세트와 테스트 세트의 정확도는 기본 매개변수일 때보다 낮아집니다.

plt.plot(logreg.coef_.T, 'o', label="C=1")
Out[180]: [<matplotlib.lines.Line2D at 0x2da0e4f5cc0>]
plt.plot(logreg100.coef_.T, '^', label="C=100")
Out[181]: [<matplotlib.lines.Line2D at 0x2da0e4fd2b0>]
plt.plot(logreg001.coef_.T, 'v', label="C=0.001")
Out[182]: [<matplotlib.lines.Line2D at 0x2da116d20f0>]
plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
Out[184]: 
([<matplotlib.axis.XTick at 0x2da0e4e1710>,
  <matplotlib.axis.XTick at 0x2da0e3bf048>,
  <matplotlib.axis.XTick at 0x2da0e33f6a0>,
…
<a list of 30 Text xticklabel objects>)

plt.hlines(0, 0, cancer.data.shape[1])
Out[185]: <matplotlib.collections.LineCollection at 0x2da0e517390>

plt.ylim(-5, 5)
Out[186]: (-5, 5)

plt.xlabel("attribute")
Out[187]: Text(0.5,-127.463,'attribute')

plt.ylabel("계수 size")
Out[188]: Text(44.3472,0.5,'계수 size')

plt.legend()
Out[189]: <matplotlib.legend.Legend at 0x2da0e5062b0>

2018-02-27_12-10-58.png

더 이해하기 쉬운 모델을 원한다면 L1규제를 사용하는 것이 좋습니다. 다음은 L1규제를 사용할 때의 분류 정확도와 계수 그래프입니다.

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}인 l1 로지스틱 회귀의 훈련 정확도: {:.2f}".format(C, lr_l1.score(X_train, y_train)))
    print("C={:.3f}인 l1 로지스틱 회구의 테스트 정확도:{:.2f}".format(C, lr_l1.score(X_test, y_test)))
    plt.plot(lr_l1.coef_.T, marker, label="C={:.3f}".format(C))
    
C=0.001인 l1 로지스틱 회귀의 훈련 정확도: 0.91
C=0.001인 l1 로지스틱 회구의 테스트 정확도:0.92
C=1.000인 l1 로지스틱 회귀의 훈련 정확도: 0.96
C=1.000인 l1 로지스틱 회구의 테스트 정확도:0.96
C=100.000인 l1 로지스틱 회귀의 훈련 정확도: 0.99
C=100.000인 l1 로지스틱 회구의 테스트 정확도:0.98

plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
Out[199]: 
([<matplotlib.axis.XTick at 0x2da119c7898>,
  <matplotlib.axis.XTick at 0x2da0e346e10>,
  <matplotlib.axis.XTick at 0x2da116d2470>,
…
<a list of 30 Text xticklabel objects>)
plt.hlines(0, 0, cancer.data.shape[1])
Out[200]: <matplotlib.collections.LineCollection at 0x2da0bb74860>
plt.xlabel("attribute")
Out[201]: Text(0.5,-107.443,'attribute')
plt.ylabel("계수 size")
Out[202]: Text(71.5972,0.5,'계수 size')
plt.ylim(-5, 5)
Out[203]: (-5, 5)
plt.legend(loc=3)
Out[204]: <matplotlib.legend.Legend at 0x2da0bb73470>

2018-02-27_12-10-58.png

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

다중 클래스 분류용 선형 모델
많은 선형 분류 모델은 태생적으로 이진 분류만을 지원합니다. 즉 다중 클래스를 지원하지 않습니다.
세개의 클래스를 가진 간단한 데이터셋에 일대다 방식을 적용합니다. 이 데이터셋은 2차원이며 각 클래스의 데이터는 정규분포(가우시안 분포)를 따릅니다.

from sklearn.datasets import make_blobs
X, y = make_blobs(random_state=42)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
Out[207]: 
[<matplotlib.lines.Line2D at 0x2da0bb58470>,
 <matplotlib.lines.Line2D at 0x2da1170eac8>,
 <matplotlib.lines.Line2D at 0x2da0e380a58>]
plt.xlabel("attribute 0")
Out[209]: Text(0.5,23.4122,'attribute 0')
plt.ylabel("attribute 1")
Out[210]: Text(31.0972,0.5,'attribute 1')
plt.legend(["class 0", "class 1", "class 2"])
Out[212]: <matplotlib.legend.Legend at 0x2da0bb485c0>

이 데이터셋으로 LinearSVC분류기를 훈련해 봅니다.

linear_svm = LinearSVC().fit(X, y)
print("계수 배열의 크기: ", linear_svm.coef_.shape)
계수 배열의 크기:  (3, 2)
print("절편 배열의 크기: ", linear_svm.intercept_.shape)
절편 배열의 크기:  (3,)

coef_ 배열의 크기는 (3,2)입니다. coef_의 행은 세 개의 클래스에 각각 대응하는 계수 벡터를 담고 있으며 열은 각 특성에 따른 계수 값을 가지고 있습니다. intercept_는 각 클래스의 절편을 담은 1차원 벡터입니다.
세 개의 이진 분류기가 만드는 경계를 시작화

mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
Out[216]: 
[<matplotlib.lines.Line2D at 0x2da0e397d68>,
 <matplotlib.lines.Line2D at 0x2da0e397e80>,
 <matplotlib.lines.Line2D at 0x2da0e4242b0>]
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.ylim(-10, 15)
Out[221]: (-10, 15)

plt.xlim(-10, 8)
Out[222]: (-10, 8)

plt.xlabel("attribute 0")
Out[223]: Text(0.5,24.6222,'attribute 0')

plt.ylabel("attribute 1")
Out[224]: Text(44.5972,0.5,'attribute 1')

plt.legend(['class 0', 'class 1', 'class 2', 'class 0 경계', 'class 1 경계', 'class 2 경계'], loc=(1.01, 0.3))
Out[225]: <matplotlib.legend.Legend at 0x2da0e42c128>

2018-02-27_14-29-05.png

이 데이터셋으로 LinearSVC분류기를 훈련

mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
Out[233]: 
[<matplotlib.lines.Line2D at 0x2da118bd710>,
 <matplotlib.lines.Line2D at 0x2da118bd860>,
 <matplotlib.lines.Line2D at 0x2da118bdda0>]

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.ylim(-10, 15)
Out[236]: (-10, 15)

plt.xlim(-10, 8)
Out[237]: (-10, 8)

plt.xlabel('attribute 0')
Out[238]: Text(0.5,23.4122,'attribute 0')

plt.ylabel('attribute 1')
Out[239]: Text(35.4722,0.5,'attribute 1')

plt.legend(['class 0', 'class 1', 'class 2', 'class 0 경계', 'class 1 경계', 'class 2 경계'], loc=(0.01, 0.3))
Out[240]: <matplotlib.legend.Legend at 0x2da11902748>

2018-02-27_15-21-29.png

훈련 데이터의 클래스 0에 속한 모든 포인트는 클래스 0을 구분하는 직선 위에 즉 이진 분류기가 만든 클래스 0 지역에 있습니다. 그런데 클래스 0에 속한 포인트는 클래스 2를 구분하는 직선 위 즉 클래스 2의 이진 분류기에 의해 나머지로 분류됩니다. 또한 클래스 0에 속한 포인트는 클래스 1을 구분하는 직선 왼쪽, 즉 클래스 1의 이진 분류기에 의해서도 나머지로 분류되었습니다.

다음 예는 2차원 평면의 모든 표인트에 대한 예측 결과를 보여줍니다.

n [241]: mglearn.plots.plot_2d_classification(linear_svm, X, fill=True, alpha=.7)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
Out[242]: 
[<matplotlib.lines.Line2D at 0x2da1184bb38>,
 <matplotlib.lines.Line2D at 0x2da1184bc88>,
 <matplotlib.lines.Line2D at 0x2da11856160>]

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(['class 0','class 1','class 2','class 0 경계','class 1 경계','class 2 경계'], loc=(1.01, 0.3))
Out[246]: <matplotlib.legend.Legend at 0x2da1186e898>
plt.xlabel('attribute 0')
Out[247]: Text(0.5,47.1344,'attribute 0')
plt.ylabel('attribute 1')
Out[248]: Text(74.4444,0.5,'attribute 1')

2018-02-27_15-21-29.png

Sort:  

Hey @papasmf1, great post! I enjoyed your content. Keep up the good work! It's always nice to see good content here on Steemit! Cheers :)

Coin Marketplace

STEEM 0.04
TRX 0.32
JST 0.081
BTC 62294.13
ETH 1679.15
USDT 1.00
SBD 0.41