[카테고리:] <span>머신러닝/딥러닝</span>

안녕하세요. 언제나휴일입니다.

KNN(K-Nearest Neighbor)은 비슷한 특징을 갖는 이웃을 찾아 예측을 하는 머신 러닝 기법입니다.

KNN을 이용한 머신 러닝 모델에는 회귀 모델과 분류 모델이 있는데 여기에서는 회귀 모델을 알아볼 거예요.

먼저 KNN을 알아보기 위해 사용할 데이터를 소개한 후에 KNN을 만들어 보고 사이킷 런에서 제공하는 KNN 모델을 이용할게요.

사용할 데이터
KNN 만들어 보기
사이킷 런의 KNN 사용하기

1. 사용할 데이터

KNN 학습에서 사용할 데이터는 사이킷 런에서 제공하는 붓꽃 데이터입니다.

from sklearn.datasets import load_iris

load_iris 함수를 호출하여 붓꽃 데이터를 로딩한 후 출력해 볼게요.

iris_data = load_iris()
print(iris_data)

출력 결과는 다음과 같습니다.

{'data': array([[5.1, 3.5, 1.4, 0.2],
       [4.9, 3. , 1.4, 0.2],
       [4.7, 3.2, 1.3, 0.2],
       ...중략...
       [5.9, 3. , 5.1, 1.8]]),
 'target': array([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, 0, 0, 0, 0, 0, 0, 0, 0,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]), 
  'frame': None, 
   'target_names': array(['setosa', 'versicolor', 'virginica'], dtype='<U10'), 
   ...중략...
   Number of Instances: 150 (50 in each of three classes)
   Attribute Information:
        - sepal length in cm
        - sepal width in cm
        - petal length in cm
        - petal width in cm
   ...이하 생략...

보시면 3종(setosa, versicolor, virginica) 150개(각 50개)의 붓꽃 데이터입니다.

붓꽃 데이터는 꽃받침 길이, 꽃받침 너비, 꽃잎 길이, 꽃잎 너비, 품종으로 구성하고 있어요.

여기에서는 setosa 품종의 꽃받침 길이와 꽃받침 너비를 사용하여 KNN을 알아볼거예요.

꽃받침 길이를 알고 있을 때 꽃받침 너비를 추측하는 간단한 작업을 해 볼 거예요.

로딩한 데이터에서 붓꽃 속성과 품종을 구분하는 작업을 해 볼게요.

붓꽃 속성은 ‘data’, 품종은 ‘target’, 품종명은 ‘target_name’을 통해 얻어올 수 있어요.

data = iris_data['data']
target = iris_data['target']
tnames = iris_data['target_names']
for i,y in enumerate(target):
  print(f'{i}: {tnames[y]}')

출력 결과

0: setosa
1: setosa
2: setosa
...중략...
47: setosa
48: setosa
49: setosa
50: versicolor
51: versicolor
52: versicolor
...중략...
98: versicolor
99: versicolor
100: virginica
101: virginica
102: virginica
...중략...
148: virginica
149: virginica

이 중에 setosa 품종의 꽃받침 길이와 너비를 얻어옵니다.

*머신 러닝에서 독립 변수는 텐서 구조를 요구합니다. sepal_lengths를 얻어올 때 data[:50,[0]]로 표현한 이유입니다. 

[[독립변수 특성],[독립변수 특성],[독립변수 특성],…]

[종속변수,종속변수,…]

sepal_lengths = data[:50,[0]]
sepal_widths = data[:50,1]

이를 도표로 표시해 봅시다.

import matplotlib.pyplot as plt
plt.plot(sepal_lengths[:,0],sepal_widths,'ro',label='setosa')
plt.xlabel('sepal-length')
plt.ylabel('sepal-width')
plt.yticks([0,max(sepal_widths)])
plt.title('iris - setosa')
plt.show()
setosa 꽃받침 너비와 길이

2. KNN 만들어 보기

머신 러닝 기술을 익히기 위해 모델을 특징을 알면 좋을 거예요.

이를 위해 간단한 모델은 비슷하게 만들어 보는 것도 하나의 방법이겠죠.

먼저 학습할 데이터와 예측에 사용할 데이터를 나누어 봅시다.

train_xs = sepal_lengths[:len(sepal_lengths)*2//3]
train_ys = sepal_widths[:len(sepal_widths)*2//3]
test_xs = sepal_lengths[len(sepal_lengths)*2//3:]
test_ys = sepal_widths[len(sepal_widths)*2//3:]
print(f'학습용 길이:{len(train_xs)}, 테스트용 길이:{len(test_xs)}')

출력 결과

학습용 길이:33, 테스트용 길이:17

KNN에서는 전달받은 값과 비슷한 이웃을 찾는 작업을 수행합니다.

그리고 이웃의 평균값을 예측값으로 반환합니다.

이웃을 찾는 작업을 위해 거리를 측정하는 함수가 필요합니다.

다음은 독립 변수의 거리를 구하는 함수입니다.

import numpy as np
def distance(x1, x2):
  if isinstance(x1,int) and isinstance(x2,int):
    return np.abs(x2-x1)  
  assert len(x1)==len(x2), "두 개의 벡터의 원소 개수는 같아야 합니다."
  return sum((x1-x2)**2)**(1/2)  #각 항목마다 제곱한 후에 루트를 취한 값입니다.

다음은 k개의 이웃을 찾는 함수입니다.

def find_k_nearest_neighbor(xs,ys,tx,k=5):
  sarr=[]
  #xs 목록의 항목과 tx의 거리를 측정하여 거리와 인덱스를 sarr에 보관합니다.
  for i,x in enumerate(xs):
    dis = distance(x,tx)
    sarr.append((dis,i))          
  #거리로 정렬합니다.
  sarr.sort(key=lambda x:x[0])      
  k = min(k,len(sarr))    
  #k개의 이웃 인덱스를 구합니다.
  neighbors = [x[1] for x in sarr[:k]]    
  
  return sum(ys[neighbors])/k #이웃의 평균 값을 반환합니다.

다음은 여러 개의 테스트 독립 변수로 결과를 예측하는 함수입니다.

def find_k_nearest_neighbors(xs,ys,t_xs,k=5):  
  return [find_k_nearest_neighbor(xs,ys,tx,k) for tx in t_xs]

이제 학습용 데이터와 테스트 용 데이터를 통해 예측해 봅시다.

*실제 모델에서는 클래스로 정의하여 학습 과정과 예측 과정은 별도의 메서드로 정의하고 있습니다.

pred_val = find_k_nearest_neighbors(train_xs,train_ys,test_xs)

실제 값과 예측 값을 도표로 표시해 볼게요.

plt.plot(pred_val,'ro',label='predict')
plt.plot(test_ys,'b.',label='actual')
plt.xlabel('test xs index')
plt.ylabel('sepal-width')
plt.yticks([0,max(max(pred_val),max(test_ys))])
plt.legend()
plt.title('iris - setosa')
plt.show()
iris-setosa KNN 예측 결과
iris-setosa KNN 예측 결과

오차를 확인해 볼게요.

‘abs(예측 값-실제 값)/실제 값’의 평균

print(np.mean(np.abs(pred_val-test_ys)/test_ys))

출력 결과

0.07213767474452305

3. 사이킷 런의 KNN 사용하기

사이킷 런의 KNN 중에 여기에서는 회귀 모델을 사용합니다.

from sklearn.neighbors import KNeighborsRegressor
knr_model = KNeighborsRegressor(n_neighbors=5)

학습할 때는 fit 메서드를 호출합니다.

학습 후에 예측할 때는 predict 메서드를 호출합니다.

knr_model.fit(train_xs,train_ys)
pred_val2 = knr_model.predict(test_xs)

앞에서 작성한 함수를 이용한 결과와 사이킷 런의 KNN을 이용한 결과를 도표로 나타내어 비교해 봅시다.

plt.plot(pred_val,'ro',label='use find_k_nearest_neighbors')
plt.plot(pred_val2,'b.',label='use KNeighborsRegressor')
plt.xlabel('test xs index')
plt.ylabel('sepal-width')
plt.yticks([0,max(max(pred_val),max(test_ys))])
plt.legend()
plt.title('iris - setosa')
plt.show()
KNN직접 만든 것과 사이킷 런의 KNN 비교
KNN직접 만든 것과 사이킷 런의 KNN 비교

큰 차이가 없다는 것을 확인할 수 있어요.

사이킷 런의 KNN으로 예측했을 때 오차도 확인해 볼게요.

print(np.mean(np.abs(pred_val2-test_ys)/test_ys))

출력 결과

0.07322719531933776

직접 만든 함수를 이용할 때의 오차는 0.07213767474452305

사이킷 런의 KNN을 이용할 때 오차는 0.07322719531933776

이상으로 KNN 회귀 모델이 어떠한 원리로 동작하는지 알아보았습니다.

머신러닝/딥러닝