Supervised learning_KNN

지도 학습

지도 학습에는 분류회귀가 있다.

분류

  • 분류는 미리 정의된, 가능성 있는 여러 클래스 레이블 중 하나를 예측하는 것.
  • 분류는 딱 두개의 클래스로 분류하는 이진 분류와 셋 이상의 클래스로 분류하는 다중 분류로 나뉩니다.

회귀

  • 회귀는 연속적인 숫자, 또는 프로그래밍 요어로 말하면 부동소수점수를 예측하는 것.
  • 연간 소득을 예측하는 것, 수확량을 예측하는 예들이 회귀 문제이다.


일반화, 과대적합, 과소적합


모델이 처음 보는 데이터에 대해 정확하게 예측할 수 있으면 이를 훈련 세트에서 테스트 세트로 일반화(generalization)되었다고 합니다. 그래서 모델을 만들 때는 가능한 한 정확하게 일반화되도록 해야 합니다. 예를 들어 아주 복잡한 모델을 만든다면 훈련 세트에만 정확한 모델이 되어버릴 수 있습니다. 이것은 분석가가 가진 정보를 모두 사용해서 너무 복잡한 모델을 만드는 것을 과대적합(overfitting)이라고 합니다. 과대적합은 모델이 훈련 세트의 각 샘플에 너무 가깝게 맞춰져서 새로운 데이터에 일반화되기 어려울 때 일어납니다. 반대로 모델이 너무 간단하면, 데이터의 면면과 다양성을 잡아내지 못할 것이고 훈련 세트에도 잘 맞지 않을 것입니다. 너무 간단한 모델이 선택되는 것을 과소적합(underfitting)이라고 합니다.

우리가 찾으려는 모델은 일반화 성능이 최대가 되는 최적점에 있는 모델입니다.


지도 학습 알고리즘


모델들의 장단점을 평가하고 어떤 데이터가 잘 들어맞을지 볼 것입니다. 가장 중요한 매개변수와 옵션의 의미도 살펴 볼 것입니다. 분류회귀 모델을 모두 가지고 있는 알고리즘도 많은데, 이런 경우 둘 다 살펴보겠습니다.


그래프에서 한글 나타내기

먼저 시작하기 앞서서 그래프에서 한글을 쓰게 되면 깨지게 되는데 이를 나타내기 위한 작업을 거쳐야 한다. 아래 방법과 같이 하면 된다. 여기서 font인자는 자신이 사용하고 싶은 font를 사용하면 된다.

In [18]:
import seaborn as sns
from matplotlib import font_manager, rc
sns.set(font="THEGaeideuk",rc={"axes.unicode_minus":False},style='white')

예제에 사용할 데이터셋

In [19]:
import mglearn
import matplotlib.pyplot as plt

# 사용할 데이터 셋
X, y= mglearn.datasets.make_forge()

#  X:
#  [[ 9.96346605  4.59676542]
#  [11.0329545  -0.16816717] ... ]
#  y:
#  [1 0 1 0 0 1 1 ...]


# 산점도를 그려서 데이터를 확인해 본다.
plt.figure(figsize=(8, 6))
mglearn.discrete_scatter(X[:,0],X[:,1],y)
plt.legend(['클래스 0', '클래스 1'],loc=4)
plt.xlabel('첫 번째 특성',fontdict={'fontsize':15})
plt.ylabel('두 번째 특성',fontdict={'fontsize':15})
# 제목의 크기와 위치는 fontdict와 pad인자로 조정한다. 
plt.title('forge 데이터셋의 산점도',fontdict={'fontsize':20},pad=20)
print('X.shape:',X.shape)
C:\Users\won\anaconda3\envs\pydatavenv\lib\site-packages\sklearn\utils\deprecation.py:86: FutureWarning: Function make_blobs is deprecated; Please import make_blobs directly from scikit-learn
  warnings.warn(msg, category=FutureWarning)
X.shape: (26, 2)


회귀 알고리즘 설명에는 인위적으로 만든 wave 데이터셋을 사용합니다.
wave 데이터셋은 입력 특성 하나와 모델링할 타깃 변수를 가집니다.특성을 x축에 놓고 회귀의 타깃(출력)을 y축에 놓았습니다.

In [20]:
X, y= mglearn.datasets.make_wave(n_samples=40)
plt.figure(figsize=(8, 6))
plt.plot(X,y,'o')
plt.ylim(-3,3)
plt.xlabel('특성',fontdict={'fontsize':15})
plt.ylabel('타깃',fontdict={'fontsize':15})
plt.title('wave 데이터셋의 그래프',fontdict={'fontsize':20},pad=20)
Out[20]:
Text(0.5, 1.0, 'wave 데이터셋의 그래프')


유방암 종양의 임상 데이터를 기록해놓은 위스콘신 유방암 데이터셋

In [21]:
from sklearn.datasets import load_breast_cancer
import pandas as pd

cancer=load_breast_cancer()
print("cancer.keys():\n",cancer.keys())
cancer.keys():
 dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename'])
  • sckit-learn에 포함된 데이터셋은 실제 데이터와 데이터셋 관련 정보를 담고있는 Bunch 객체에 저장되어있다.
  • Bunch 객체는 파이썬 딕셔너리와 비슷하지만 점 표기법을 사용할 수 있다. (bunch['key']대신 bunch.key사용가능)
In [22]:
print('유방암 데이터 형태:',cancer.data.shape)
유방암 데이터 형태: (569, 30)

np.bincount

  • 1차원 array를 input값으로 가진다.
  • weights인자는 입력과 같은 크기의 array이어야 하면서 해당 인덱스의 수를 weights인자의 가중치를 더한다.
  • minlength는 결과값의 array의 최소길이를 설정한다. 없는 인덱스에 대해서는 0으로 처리한다.
  • 입력은 모두 양의 값이어야 하면서 array의 해당값은 인덱스에 값이 카운트된다.
  • 나중 라벨링 개수 카운트에서 사용가능.

[예시]

In [23]:
import numpy as np
np.bincount?
In [24]:
tmp=np.array([1,2,3,4,3,5,2,3,42,3,5,2,4])
In [25]:
np.bincount(tmp)
Out[25]:
array([0, 1, 3, 4, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
      dtype=int64)
In [26]:
np.bincount(tmp,minlength=50)
Out[26]:
array([0, 1, 3, 4, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
       0, 0, 0, 0, 0, 0], dtype=int64)
In [27]:
import numpy as np

print('클래스별 샘플 개수:\n',
     {n: v for n,v in zip(cancer.target_names,np.bincount(cancer.target))})
클래스별 샘플 개수:
 {'malignant': 212, 'benign': 357}
In [28]:
print('특성 이름:\n:',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']


보스턴 주택가격 데이터셋

In [29]:
from sklearn.datasets import load_boston
boston=load_boston()
print('데이터의 형태:',boston.data.shape)
데이터의 형태: (506, 13)

위 보스턴의 13개의 입력 특성뿐 아니라 특성끼리 곱하여(상호작용) 의도적으로 확장하겠습니다.
다시 말하면 범죄율과 고속도로 접근성의 개별 특성은 물론, 범죄율과 고속도로 접근성의 곱도 특성으로 생각한다는 뜻입니다.
이처럼 특성을 유도해내는 것을 특성 공학이라고 합니다.

In [30]:
X, y=mglearn.datasets.load_extended_boston()
print('X.shape:',X.shape)
X.shape: (506, 104)
  • 13개의 원래 특성에 13개에서 2개씩(중복을 포함해) 짝지은 91개의 특성을 더해 총 104개가 됩니다.


k-최근접 이웃

가장 간단한 머신런이 알고리즘입니다. 훈련 데이터셋을 그냥 저장하는 것이 모델을 만드는 과정의 전부입니다. 새로운 데이터 포인트에 대해 예측할 땐 알고리즘이 훈련 데이터셋에서 가장 가까운 데이터 포인트, 즉 '최근접 이웃'을 찾습니다.

In [31]:
plt.figure(figsize=(8, 6))
mglearn.plots.plot_knn_classification(n_neighbors=1)
plt.legend(['훈련 클래스0','훈련 클래스1','테스트 예측0','테스트 예측1'],fontsize=15)
C:\Users\won\anaconda3\envs\pydatavenv\lib\site-packages\sklearn\utils\deprecation.py:86: FutureWarning: Function make_blobs is deprecated; Please import make_blobs directly from scikit-learn
  warnings.warn(msg, category=FutureWarning)
Out[31]:
<matplotlib.legend.Legend at 0x20b4c94b848>

데이터 포인트를 3개를 추가하여 다시 그립니다.
테스트 포인트 하나에 대해 클래스 0에 속한 이웃이 몇 개인지, 그리고 클래스 1에 속한 이웃이 몇 개인지를 셉니다.
그리고 이웃이 더 많은 클래스를 레이블로 지정합니다. 다시 말해 k-최근접 이웃 중 다수의 클래스가 레이블이 됩니다.

In [32]:
# 이웃을 3개로 다시 그린다.
plt.figure(figsize=(8, 6))
mglearn.plots.plot_knn_classification(n_neighbors=3)
plt.legend(['훈련 클래스0','훈련 클래스1','테스트 예측0','테스트 예측1'],fontsize=15)
C:\Users\won\anaconda3\envs\pydatavenv\lib\site-packages\sklearn\utils\deprecation.py:86: FutureWarning: Function make_blobs is deprecated; Please import make_blobs directly from scikit-learn
  warnings.warn(msg, category=FutureWarning)
Out[32]:
<matplotlib.legend.Legend at 0x20b4c94b808>

이제 scikit-learn을 사용해서 k-최근접 이웃 알고리즘을 어떻게 적용하는지 살펴보겠습니다.


먼저 일반화 성능을 평가할 수 있도록 데이터를 훈련 세트와 테스트 세트로 나눕니다.

In [33]:
from sklearn.model_selection import train_test_split
X, y=mglearn.datasets.make_forge()

# 75%의 train, 25%의 test로 나눈다.
X_train, X_test, y_train, y_test= train_test_split(X,y,random_state=0)
print(X_train.shape)
print(y_train.shape)
print(X_test.shape)
print(y_test.shape)
(19, 2)
(19,)
(7, 2)
(7,)
C:\Users\won\anaconda3\envs\pydatavenv\lib\site-packages\sklearn\utils\deprecation.py:86: FutureWarning: Function make_blobs is deprecated; Please import make_blobs directly from scikit-learn
  warnings.warn(msg, category=FutureWarning)


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

In [34]:
from sklearn.neighbors import KNeighborsClassifier
clf=KNeighborsClassifier(n_neighbors=3)


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

In [35]:
clf.fit(X_train,y_train)
Out[35]:
KNeighborsClassifier(n_neighbors=3)


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

In [36]:
print('테스트 세트 예측:',clf.predict(X_test))
테스트 세트 예측: [1 0 1 0 1 0 0]


모델이 얼마나 잘 일반화되었는지 평가하기 위해 score 메서드에 테스트 데이터와 테스트 레이블을 넣어 호출합니다.

In [37]:
print('테스트 세트 정확도:{:.2f}'.format(clf.score(X_test,y_test)))
테스트 세트 정확도:0.86
  • 이 모델의 정확도는 86%로 나왔습니다. 즉 모델이 테스트 데이터셋에 있는 샘플 중 86%를 정확히 예측하였습니다.


KNeighborsClassifier 분석

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

In [38]:
fig, axes= plt.subplots(1,3,figsize=(14,4))

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('특성 0')
    ax.set_ylabel('특성 1')

# 첫번째 그림에만 범례를 나타낸다.
axes[0].legend(loc=3)
Out[38]:
<matplotlib.legend.Legend at 0x20b4cb3d848>

앞서 이야기한 모델의 복잡도와 일반화 사이의 관계를 입증할 수 있는지 살펴 보겠습니다.
이를 위해 실제 데이터인 유방암 데이터셋을 사용하겠습니다. 먼저 훈련 세트와 테스트 세트로 나눕니다. 그럼 다음 이웃의 수를 달리 하여 훈련 세트와 테스트 세트의 성능을 평가합니다.

  • 여기서 데이터셋을 나눌때 stratify인자를 사용한다. 인자 설명은 다음과 같다.

계층적 데이터 추출 옵션 (분류 모델에서 추천!)

  • 여러 층으로 분할후 각 층별로 렌덤 데이터 추출, 원래 데이터의 분포와 유사하게 데이터 추출
In [40]:
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=66)

training_accuracy=[]
test_accuracy=[]

# 1에서 10까지 n_neighbors를 적용
neighbors_settings=range(1,11)

for n_neighbors in neighbors_settings:
    # 모델 생성
    clf=KNeighborsClassifier(n_neighbors=n_neighbors)
    clf.fit(X_train,y_train)
    
    # 훈련 세트 정확도 저장
    training_accuracy.append(clf.score(X_train,y_train))
    # 일반화 정확도 저장
    test_accuracy.append(clf.score(X_test,y_test))

plt.figure(figsize=(10,6))
plt.plot(neighbors_settings,training_accuracy,label='훈련 정확도')
plt.plot(neighbors_settings,test_accuracy,label='테스트 정확도')
plt.ylabel('정확도')
plt.xlabel('n_neighbors')
plt.legend()
Out[40]:
<matplotlib.legend.Legend at 0x20b4cbbef48>
  • 정확도가 가장 좋을 때는 중간 정도인 여섯 개를 사용한 경우입니다.
  • 이 그래프의 범위를 눈여겨보면 가장 나쁜 정확도도 88%여서 수긍할만합니다.


k-최근접 이웃 회귀

k-최근접 이웃 알고리즘은 회귀 분석에도 쓰입니다. 이번에는 wave 데이터셋을 이용해서 이웃이 하나인 최근접 이웃을 사용해보겠습니다. 최근접 이웃을 한 개만 이용할 때 예측은 그냥 가장 가까운 이웃의 타깃값입니다.

In [41]:
mglearn.plots.plot_knn_regression(n_neighbors=1)
plt.legend(['훈련 데이터/타깃','테스트 데이터','테스트 예측'],fontsize=15)
Out[41]:
<matplotlib.legend.Legend at 0x20b4cb9fe08>

이웃을 둘 이상 사용하여 회귀 분석을 할 수 있습니다. 여러 개의 최근접 이웃을 사용할 땐 이웃 간의 평균이 예측이 됩니다.

In [42]:
mglearn.plots.plot_knn_regression(n_neighbors=3)
plt.legend(['훈련 데이터/타깃','테스트 데이터','테스트 예측'],fontsize=12)
Out[42]:
<matplotlib.legend.Legend at 0x20b4ccc27c8>

scikit-learn에서 회귀를 위한 k-최근접 이웃 알고리즘은 KNeighborsRegressor에 구현되어 있습니다.
사용법은 KNeighborsClassifier와 비슷합니다.

In [43]:
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[43]:
KNeighborsRegressor(n_neighbors=3)
In [45]:
print('테스트 세트 예측:\n',reg.predict(X_test))
테스트 세트 예측:
 [-0.05396539  0.35686046  1.13671923 -1.89415682 -1.13881398 -1.63113382
  0.35686046  0.91241374 -0.44680446 -1.13881398]

score 메서드를 사용해 모델을 평가할 수 있습니다. 이 메서드는 회귀일 땐 R^2값을 반환합니다. 결정 계수라고도 하는 R^2값은 회귀 모델에서 예측의 적합도를 측정한 것으로 보통 0과 1사이의 값이 됩니다. 1은 예측이 완벽한 경우이고, 0은 훈련 세트의 출력값인 y_train의 평균으로만 예측하는 모델의 경우입니다. R^2은 음수가 될 수도 있습니다. 이 때는 예측과 타깃이 상반된 경향을 가지는 경우입니다.

In [46]:
print('테스트 세트 R^2:{:.2f}'.format(reg.score(X_test,y_test)))
테스트 세트 R^2:0.83


KNeighborsRegressor 분석

In [49]:
fig, axes= plt.subplots(1,3,figsize=(15,4))

# -3과 3 사이에 1000개의 데이터 포인트를 만든다.
# reshape에서 -1값은 array크기에 맞게 알아서 조정해주다.
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('특성')
    ax.set_ylabel('타깃')
axes[0].legend(['모델 예측','훈련 데이터/타깃','테스트 데이터/타깃'],loc='best')
Out[49]:
<matplotlib.legend.Legend at 0x20b493b5fc8>
  • 그림에서 볼 수 있듯이 이웃을 하나만 사용할 때는 훈련 세트의 각 데이터 포인트가 예측에 주는 영향이 커서 예측값이 훈련 데이터 포인트를 모두 지나갑니다. 이는 매우 불안정한 예측을 만들어 냅니다.
  • 이웃을 많이 사용하면 훈련 데이터에느 잘 안맞을 수 있지만 더 안정된 예측을 얻게 됩니다.


장단점과 매개변수

일반적으로 KNeighbors 분류기에 중요한 매개변수는 두개입니다. 데이터 포인트 사이의 거리를 재는 방법과 이웃의 수입니다. 거리를 재는 방법은 기본적으로 유클리디안 거리 방식을 사용하지만, KNeighborsClassifierKNeighborsRegressor의 객체를 생성할 때 metric 매개변수를 사용하여 거리 측정 방식을 변경할 수 있습니다.metric 매개변수의 기본값은 민코프스키 거리를 의미하는 'minkowski'이며 거듭제곱의 크기를 정하는 매개변수인 p가 기본값 2일 때 유클리디안 거리와 같습니다.

k-NN의 장점은 보통 최근접 이웃 모델은 매우 빠르게 만들 수 있지만, 훈련 세트가 매우 크면(특성의 수나 샘플의 수가 클 경우) 예측이 느려집니다.k-NN 알고리즘을 사용할 땐 데이터를 전처리하는 과정이 중요합니다. 그리고 (수백 개 이상의) 많은 특성을 가진 데이터셋에는 잘 동작하지 않으며, 특성 값 대부분이 0인(즉 희소한) 데이터셋과는 특히 잘 작동하지 않습니다. 이런 단점이 없는 알고리즘이 다음에 설명할 선형 모델...

참고자료

  • 파이선 라이브러리를 활용한 머신러닝/안드레아스 뮐러,세라가이도 지음/ 박해선 옮김/ 한빛미디어

'데이터 분석 > Machine Learning' 카테고리의 다른 글

머신러닝 소개  (0) 2020.12.03

+ Recent posts