K-근접이웃(KNN, K-Nearest Neighber)
K-근접이웃(K-nearest neighber)기법은 분류 및 회귀분석을 위한 대표적인 비모수 학습기법의 일종으로, 소수의 근접이웃들만을 이용하여 예측하는 알고리즘이기 때문에 매우 기초적이고 단순하며, 데이터에 대한 사전 정보가 부족할 때 유용하게 사용될 수 있다. K-근접이웃은 예측하고자 하는 대상 데이터와 특징 벡터 공간에서 가장 유사한 K-개의 표본 학습 데이터를 지칭하고 k-개의 표본 학습 데이터로부터 공통의 성질 또는 패턴을 분석함으로써 대상 데이터에 대한 예측을 수행한다. K-근접이웃에서 사용자가 주의해야할 유일한 부분은 바로 K값을 어떻게 설정하냐는 것이다. K값이 커질수록 많은 근접이웃을 포함하기 때문에 예측 정확도는 향상될 수 있지만 k값을 지나치게 높게 잡게 되면 불필요한 잡음 데이터까지도 포함시킬 수 있다.즉, K-근접이웃기법은 K값에 결과가 매우 민감하게 반응하기 때문에 k값을 신중하게 결정하여야 한다.
1. K-근접이웃의 정의
KNN(K-근접이웃기법)은 특징 벡터 공간에서 대상 데이터와 가장 근접한 특성을 지니는 K개의 근접이웃들을 기반으로 한다. 때문에 대상 데이터와 이웃 데이터 간의 유사도를 계산하는 것이 이 기법의 핵심이라고 할 수 있다. 데이터 간의 유사도를 측정 하기 위해서 유클리드 거리(Euclidean distance)를 가장 대중적으로 사용하며, 그 외에도 두 데이터 간의 유사도를 측정하기 위한 측도로서 코사인 거리, 맨하탄 거리 등을 사용할 수 있다.
- KNN 모형의 분류 예
그림[10.1] KNN 모형분류 (출처: https://upload.wikimedia.org/wikipedia/commons/e/e7/KnnClassification.svg)
위의 그림은 분류를 위한 KNN 모형을 그림으로 표시한 것이다. 네모그룹과 세모그룹으로 분류하는 상황에서 중앙의 초록색 원은 어떤 그룹으로 분류되어야 할까? 결과는 K값에 따라 결정된다. 만약 k=3(실선)이라면 세모 2개, 네모 1개로 세모그룹으로 분류될 것이고, k=5(점선)라면 세모 2개, 네모 3개로 네모그룹으로 분류될 것이다. 만약 분류가 아닌 회귀문제라면 이웃들의 종속변수 평균이 예측값이 된다.
KNN은 다른 학습기법 모델들과 비교했을 때 학습이라고 표현할 절차가 없다. 그저 초록색 원처럼 새로운 데이터가 들어왔을 때, 기존 데이터들과의 거리를 재서 이웃을 찾아내는게 전부이기 때문이다. KNN의 이러한 특성때문에 학습모델을 별도로 구축하지 않는다고 하여 게으른 모델(Lazy model), 혹은 관측치 기반 모델(Instance-based Learning)이라고도 한다. 별도의 학습모델 생성없이 관측치만으로 분류/회귀 작업을 진행한다.
KNN의 장점
- 알고리즘이 매우 간단하여 구현이 쉽다.
- 데이터가 수치형 데이터일 경우, 분류작업의 성능이 좋다.
KNN의 단점
- 학습 데이터의 양이 많으면 분류 속도가 느려진다.
- K값에 매우 민감하며, 최적의 K값을 정하기 위한 명확한 방법이 존재하지 않는다.
2. K-근접이웃 실습
2-1. 데이터 분할 및 정규화
KNN기법은 특징 벡터 공간에서 두 데이터 간에 유사도를 기반으로 예측을 수행하기 때문에 분석에 이용할 변수 중에 범주형 데이터가 있다면 이를 사전에 수치형으로 변형해주어야 하며, 분석변수를 정규화하는 과정이 필요하다. 또한 학습기법의 일종이기 때문에 학습용, 검증용 데이터를 분할하는 사전 과정이 필요하다. 다음 예제를 풀어보자
예제 (1)
백화점 데이터에서 총 매출액, 방문빈도, 구매 카테고리수, 구매유형을 변수로 하여 분할 및 정규화를 진행해보자.
In [74]:
#데이터 불러오기 import pandas as pd data= pd.read_csv('C:\\python\\백화점.csv', engine='python') data.head(7)
Out[74]:
고객ID | 이탈여부 | 총 매출액 | 방문빈도 | 1회 평균매출액 | 할인권 사용 횟수 | 총 할인 금액 | 고객등급 | 구매유형 | 클레임접수여부 | … | 음향 적절성 | 안내 표지판 설명 | 친절성 | 신속성 | 책임성 | 정확성 | 전문성 | D1 | D2 | D3 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 4007080 | 17 | 235711 | 1 | 5445 | 1 | 4 | 0 | … | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 | 0 | 1 |
1 | 2 | 1 | 3168400 | 14 | 226314 | 22 | 350995 | 2 | 4 | 0 | … | 6 | 6 | 5 | 3 | 6 | 6 | 6 | 0 | 0 | 1 |
2 | 3 | 0 | 2680780 | 18 | 148932 | 6 | 186045 | 1 | 4 | 1 | … | 6 | 6 | 7 | 7 | 6 | 6 | 7 | 0 | 0 | 1 |
3 | 4 | 0 | 5946600 | 17 | 349800 | 1 | 5195 | 1 | 4 | 1 | … | 6 | 6 | 6 | 6 | 6 | 5 | 6 | 0 | 0 | 1 |
4 | 5 | 0 | 13745950 | 73 | 188301 | 9 | 246350 | 1 | 2 | 0 | … | 6 | 5 | 5 | 6 | 6 | 5 | 6 | 1 | 0 | 0 |
5 | 6 | 0 | 3323610 | 26 | 127831 | 20 | 348145 | 1 | 4 | 0 | … | 6 | 5 | 5 | 5 | 6 | 6 | 5 | 0 | 0 | 1 |
6 | 7 | 0 | 2369340 | 6 | 394890 | 30 | 380945 | 1 | 1 | 0 | … | 4 | 6 | 5 | 4 | 5 | 5 | 4 | 0 | 0 | 0 |
7 rows × 42 columns
In [88]:
a=data[['총 매출액','방문빈도','구매 카테고리 수','구매유형']] X = a.iloc[:, :-1].values # X: 총 매출액, 방문빈도, 구매 카테고리 수 y = a.iloc[:, 3].values # y: 이탈여부 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30) # 학습용, 검증용 데이터 분할, 학습용 데이터의 크기는 0.3(임의 설정 가능) #변수의 정규화 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() scaler.fit(X_train) # .fit(): 정규화에 사용될 변수의 평균 및 표준편차를 계산한다. X_train = scaler.transform(X_train) # .transform(): 센터링 및 확장을 통해 정규화를 수행한다. X_test = scaler.transform(X_test)
C:\Users\gksmf\anaconda3\lib\site-packages\sklearn\utils\validation.py:475: DataConversionWarning: Data with input dtype int64 was converted to float64 by StandardScaler. warnings.warn(msg, DataConversionWarning)
iloc[컬럼번호, 컬럼번호]
순서를 나타내는 정수 기반의 2차원 인덱싱, 데이터프레임에서 numpy행렬과 같이 쉼표를 사용한 2차원 인덱싱을 지원한다.
.value를 넣음으로써 인덱싱한 값들만 추출한다.
sklearn.model_selection.train_test_split 모듈
데이터 분석 시 학습용, 검증용 등의 데이터 분할을 위해 사용되는 모듈
sklearn.preprocessing.StandardScaler 모듈
범위가 서로 다른 값들을 일정한 분산에 맞게 정규화시켜주는 모듈
예제 (1) 해설
KNN기법을 쓰기에 앞서, 분석변수X와 타겟변수y를 설정해 주어야 한다. 분석에 사용할 변수들을 a로 따로 뽑아준 후, iloc를 이용해 각각의 변수를 인덱싱해준다. 그 다음 학습용 데이터와 검증용데이터를 각각 나누어 준 후, 분석변수의 정규화 작업을 진행한다. 변수값의 크기가 모두 일정하지 않기 때문에 정규화를 하지 않을 경우, 거리를 기반으로 분석을 진행하는 KNN은 정확한 결과값을 낼 수 없다. 때문에 분석을 시작하기 전, 전처리 단계에서 정규화를 필수로 진행해 주어야 한다. 먼저 모듈을 불러온 후, fit(X_train)로 평균 및 표준편차를 계산한다. 그 후에 transform()으로 모두 정규화시킨다.
2-2. K-근접이웃 학습 및 평가
KNN은 학습을 통해 튜닝하는 변수가 아닌, 사용자가 지정해야 하는 세팅이 존재하는데, 이러한 튜닝 옵션을 하이퍼파라미터(Hyperparameter)라고 부른다. KNN의 하이퍼파라미터는 탐색할 이웃의 수(K)와 이웃간의 거리 측정 방식 2가지가 존재하며, 앞서 언급했던 유클리드 거리, 코사인 거리, 맨하탄 거리 등이 거리 측정 방식에 포함된다. 다음 예제를 풀어보자.
예제 (2)
전처리한 백화점 데이터로 KNN분석을 진행한 후 알고리즘을 평가해보자(K=5)
In [168]:
#교육 및 예측 from sklearn.neighbors import KNeighborsClassifier clf = KNeighborsClassifier(n_neighbors=5, metric='minkowski') #n_neighpors K값, 이상적인 값 없음 clf.fit(X_train, y_train) y_pred = clf.predict(X_test)#예측
sklearn.neighbors.KNeighborsClassifier 모듈
K-근접이웃 기법을 구현한 모듈로 n_neighbors, metric, weights 등으로 파라미터를 설정할 수 있다.
code | 세부 내용 |
---|---|
n_neighbors = | K값, 숫자로 표현한다. (기본값=5) |
metric= | 거리측정방식, (기본값=minkowski) minkowski(유클리드와 맨해튼 혼합) euclidean manhattan |
weights= | 가중치 ‘uniform’: 거리에 따라 가중치 부여하지 않음 ‘distance’: 거리에 따라 가중치 부여 |
In [169]:
#알고리즘 평가 from sklearn.metrics import confusion_matrix, classification_report print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred))
[[ 0 0 0 11] [ 0 84 1 14] [ 2 7 31 12] [ 0 10 0 128]] precision recall f1-score support 1 0.00 0.00 0.00 11 2 0.83 0.85 0.84 99 3 0.97 0.60 0.74 52 4 0.78 0.93 0.84 138 avg / total 0.80 0.81 0.79 300
skleart.metrics.confusion_matrix 모듈
'분류결과표'라고도 하며 타겟의 기존 값과 모형의 결과값이 일치하는 지를 갯수로 표현한 것이다.
예측값 0 | 예측값 1 | |
---|---|---|
기존값 0 | 기존값이 0, 예측값이 0인 표본의 수 | 기존값이 0, 예측값이 1인 표본의 수 |
기존값 1 | 기존값이 1, 예측값이 0인 표본의 수 | 기존값이 1, 예측값이 1인 표본의 수 |
sklearn.metrics.classfication_report 모듈
모형의 성능을 평가하는 모듈로 정밀도(precision), 재현율(recall), F1-score을 구해준다.
In [170]:
print("학습용 데이터셋 정확도: {:.3f}".format(clf.score(X_train, y_train))) print("검증용 데이터셋 정확도: {:.3f}".format(clf.score(X_test, y_test)))
학습용 데이터셋 정확도: 0.876 검증용 데이터셋 정확도: 0.810
예제 (2) 해설
n_neighbor=5로 지정하여 k값을 5로 설정하였고, clf.fit(X_train, y_train)로 데이터를 학습시킨 후, 학습된 알고리즘을 기반으로 X_test데이터를 예측하였다. 결과적으로 정밀도 80%, 재현율 81% f1-score 79% 정확도 81%로 이상적인 알고리즘으로 평가되었다.
예제 (2-1)
전처리한 백화점 데이터로 KNN분석을 진행한 후 알고리즘을 평가해보자(K=9, metric=’euclidean’)
In [153]:
e_clf = KNeighborsClassifier(n_neighbors=3, metric='euclidean') #n_neighpors K값, 이상적인 값 없음 e_clf.fit(X_train, y_train) e_y_pred = e_clf.predict(X_test)#예측
In [154]:
print(confusion_matrix(y_test, e_y_pred)) print(classification_report(y_test, e_y_pred))
[[ 0 0 1 10] [ 0 85 0 14] [ 1 7 37 7] [ 1 11 0 126]] precision recall f1-score support 1 0.00 0.00 0.00 11 2 0.83 0.86 0.84 99 3 0.97 0.71 0.82 52 4 0.80 0.91 0.85 138 avg / total 0.81 0.83 0.81 300
In [155]:
print("학습용 데이터셋 정확도: {:.3f}".format(e_clf.score(X_train, y_train))) print("검증용 데이터셋 정확도: {:.3f}".format(e_clf.score(X_test, y_test)))
학습용 데이터셋 정확도: 0.889 검증용 데이터셋 정확도: 0.827
예제 (2-1) 해설
n_neighbor=3로 지정하여 k값을 3로 설정하였고, metric=’euclidean’으로 거리측정방식을 유클리드 거리로 선택했다. e_clf.fit(X_train, y_train)로 데이터를 학습시킨 후, 학습된 알고리즘을 기반으로 X_test데이터를 예측하였다. 결과적으로 정밀도 81%, 재현율 83% f1-score 81% 정확도 82.7%로 평가되었다. 역시나 이상적인 평가수치임을 확인할 수 있다
3. K-근접이웃의 이상적인 K값 찾기
KNN은 K값 설정에 따라 분석 결과가 상이하게 변하기 때문에 적합한 K값을 선택하는 것이 매우 중요하다. 그러나 아쉽게도 어느 데이터에나 적합하게 활용되는 K값은 존재하지 않는다. 다만 K값을 비교하면서 상대적으로 나은 K값을 선택하는 방법론은 존재한다. 바로 K값에 따른 평균 오류를 계산해서 오류가 적은 K값을 찾아내는 것이다. 그러나 이 방법 또한 K값 설정에 대한 절대적인 지표가 되지는 않는다. 그저 K값을 비교함에 따라 사용자의 선택에 도움을 주기 위함임을 염두해야 한다. 다음 예제를 풀어보자.
예제(3)
KNN분석에 대해 이상적인 K값을 찾아보자.
In [87]:
#모델에 적합한 K값 구하기 import matplotlib.pyplot as plt import numpy as np error = [] # k값 1-40까지 반복 실행, 평균 오류를 계산해서 error목록에 출력 for i in range(1, 40): knn = KNeighborsClassifier(n_neighbors=i) knn.fit(X_train, y_train) pred_i = knn.predict(X_test) error.append(np.mean(pred_i != y_test)) # x축: K값, y축: 평균 오류인 k값에 대한 오류비율표 작성 plt.figure(figsize=(10, 5)) plt.plot(range(1, 40), error, color='green', marker='.', markerfacecolor='red', markersize=15) plt.xlabel('K') plt.ylabel('Mean Error') plt.show
Out[87]:
<function matplotlib.pyplot.show(*args, **kw)>
![](https://it-bite.com/wp-content/uploads/2023/07/image-19.png)
예제 (3) 해설
for문을 이용해서 K값이 1~40일 때의 KNN을 반복실행한 후, 반복실행한 결과에서 오류값의 평균을 구해 그래프를 작성했다. K값이 4일 때 평균 오류가 가장 낮았으며, 그 이후로는 평균 오류가 계속 증가하는 것을 확인할 수 있다. 평균 오류를 기준으로 보았을 때 이상적인 K값은 4이다.
예제 (3-1)
KNN분석에 대해 이상적인 K값을 찾아보자.(metric=’euclidean’)
In [171]:
error = [] for i in range(1, 40): knn = KNeighborsClassifier(n_neighbors=i,metric='euclidean') knn.fit(X_train, y_train) pred_i = knn.predict(X_test) error.append(np.mean(pred_i != y_test)) plt.figure(figsize=(10, 5)) plt.plot(range(1, 40), error, color='green', marker='.', markerfacecolor='red', markersize=15) plt.xlabel('K') plt.ylabel('Mean Error') plt.show
Out[171]:
<function matplotlib.pyplot.show(*args, **kw)>
![](https://it-bite.com/wp-content/uploads/2023/07/image-18.png)
예제 (3-1) 해설
K값이 3일 때 평균 오류가 가장 낮았으며, 그 이후로는 평균 오류가 계속 증가하는 것을 확인할 수 있다. 평균 오류를 기준으로 보았을 때 이상적인 K값은 3이다.