분류 및 회귀를 위한 K-최근접 이웃(KNN) 이해하기
목차
- K-최근접 이웃 소개
- KNN의 작동 방식
- ‘K’ 값 선택하기
- 분류를 위한 KNN
- 회귀를 위한 KNN
- KNN의 장점과 단점
- Python에서 KNN 구현하기
- 실용적인 예제
- 결론
- 참고문헌
1. K-최근접 이웃 소개
K-최근접 이웃(KNN)은(는) 단순하지만 강력한 지도 학습 기계 학습 알고리즘으로, 분류 및 회귀 작업 모두에 사용됩니다. 핵심 아이디어는 특징 공간에서 ‘K’개의 가장 가까운 이웃의 레이블을 기반으로 새로운 데이터 포인트의 레이블을 예측하는 것입니다.
KNN을 선택하는 이유
- 단순성: 이해하고 구현하기 쉽습니다.
- 학습 단계 없음: KNN은 ‘게으른 학습자’로, 명시적으로 모델을 학습하지 않고 전체 데이터셋을 기반으로 결정을 내립니다.
- 다재다능성: 분류, 회귀, 심지어 이상치 탐지 등 다양한 유형의 문제에 적용할 수 있습니다.
2. KNN의 작동 방식
KNN은 유사한 데이터 포인트가 유사한 결과를 가질 가능성이 있다는 원칙에 따라 작동합니다. 다음은 알고리즘이 작동하는 방식을 단계별로 설명한 것입니다:
데이터 표현
각 데이터 포인트가 두 가지 특성을 기반으로 자동차를 나타내는 2차원 공간을 상상해보세요:
- 제조 시간 (X축)
- 제조 비용 (Y축)
데이터 포인트는 색상으로 구분됩니다:
- 빨간 점: 휘발유 자동차
- 파란 점: 전기 자동차
거리 측정 방법
데이터 포인트의 “가까움”을 판단하기 위해 KNN은 거리 측정 방법을 사용합니다. 가장 일반적으로 사용되는 측정 방법은 다음과 같습니다:
- 유클리드 거리
\[ d(p, q) = \sqrt{\sum_{i=1}^{n} (q_i – p_i)^2} \]
- 사용 시기: 데이터가 연속 공간에 있을 때.
- 전문가 팁: 유클리드 거리는 scikit-learn을 포함한 많은 KNN 구현에서 기본 거리 측정 방법입니다.
- 맨해튼 거리
\[ d(p, q) = \sum_{i=1}^{n} |q_i – p_i| \]
- 사용 시기: 데이터가 격자 형태일 때 및 이동이 수평과 수직 경로로 제한될 때.
- 민코프스키 거리
유클리드 거리와 맨해튼 거리의 일반화입니다.
\[ d(p, q) = \left( \sum_{i=1}^{n} |q_i – p_i|^p \right)^{1/p} \]
- p = 1일 때: 맨해튼 거리와 동일합니다.
- p = 2일 때: 유클리드 거리와 동일합니다.
3. 적절한 ‘K’ 값 선택하기
매개변수 ‘K’는 예측을 할 때 고려할 이웃의 수를 결정합니다. 최적의 ‘K’ 값을 선택하는 것은 KNN 알고리즘의 성능에 매우 중요합니다.
‘K’의 영향
- 작은 ‘K’ (예: K=1):
- 노이즈에 더 민감합니다.
- 과적합을 유발할 수 있습니다.
- 큰 ‘K’ (예: K=20):
- 더 부드러운 결정 경계를 가집니다.
- 데이터를 과도하게 단순화하여 과소적합을 유발할 수 있습니다.
모범 사례
- 교차 검증: 교차 검증과 같은 기법을 사용하여 최상의 정확도를 제공하는 ‘K’ 값을 찾습니다.
- 홀수: 이진 분류를 다룰 때, 홀수 ‘K’ 값을 사용하면 동점 결과를 피하는 데 도움이 됩니다.
4. 분류를 위한 KNN
분류에서 KNN은 ‘K’개의 가장 가까운 이웃 중 가장 흔한 클래스를 새로운 데이터 포인트에 할당합니다.
예제 시나리오
특정 제조 시간과 비용을 가진 새로운 자동차 데이터 포인트를 고려해보겠습니다. KNN 알고리즘은 다음과 같이 작동합니다:
- 계산: 이 지점에서 데이터셋 내 모든 지점까지의 거리를 계산합니다.
- 식별: ‘K’개의 가장 가까운 이웃을 식별합니다.
- 할당: 이러한 이웃 중 다수 표에 기반하여 클래스(전기 또는 휘발유)를 할당합니다.
‘K’에 대한 민감도
전사에서 입증된 바와 같이, ‘K’를 다양하게 조정하면 분류 결과가 변경될 수 있습니다. 예를 들어:
- K=1: 새로운 포인트는 단일 가장 가까운 이웃을 기준으로 분류됩니다.
- K=5: 다섯 이웃 간의 다수 투표가 분류를 결정합니다.
5. 회귀를 위한 KNN
KNN은 주로 분류에 사용되지만, ‘K’개의 가장 가까운 이웃의 평균 값을 예측하여 회귀 작업도 수행할 수 있습니다.
회귀의 도전 과제
- 과적합: 작은 ‘K’ 값은 과적합을 초래할 수 있습니다.
- 과소적합: 큰 ‘K’ 값은 모델을 지나치게 단순화할 수 있습니다.
구현 통찰
제공된 Jupyter Notebook에서는 KNN 회귀를 사용하여 다이아몬드 가격을 예측했습니다. 간략한 개요는 다음과 같습니다:
- 데이터 전처리:
- 범주형 변수를 숫자 값으로 매핑했습니다.
- 표준화를 사용하여 특성을 스케일링했습니다.
- 모델 훈련:
- 최적의 성능을 결정하기 위해 다양한 ‘K’ 값을 사용하여 KNN 회귀 모델을 훈련했습니다.
- 평가:
- K=4일 때 최대 약 98.05%의 정확도 점수를 달성했습니다.
- 더 나은 해석을 위해 Plotly를 사용하여 실제 가격과 예측 가격을 시각화했습니다.
6. KNN의 장점과 단점
장점
- 단순하고 직관적: 이해하고 구현하기 쉽습니다.
- 학습 단계 없음: 훈련 중 계산 비용을 줄입니다.
- 적응 가능: 분류와 회귀 모두에 적합합니다.
단점
- 계산 비용이 많이 듦: 전체 데이터셋을 사용하여 예측을 하기 때문에 대규모 데이터셋에서는 느릴 수 있습니다.
- 무관한 특성에 민감: 무관하거나 중복된 특성은 성능을 저하시킬 수 있습니다.
- ‘K’ 선택: 최적의 ‘K’ 값을 선택하는 것이 어려울 수 있습니다.
7. Python에서 KNN 구현하기
Python의 scikit-learn 라이브러리를 활용하면 KNN 구현이 간단해집니다. 아래에서는 데이터 전처리부터 모델 평가까지의 주요 단계를 설명합니다.
데이터 전처리
KNN을 적용하기 전에 데이터를 준비하는 것이 필수적입니다:
- 범주형 변수 처리:
- 매핑 사전을 사용하여 범주형 텍스트 데이터를 숫자 값으로 변환합니다.
1 2 3 4 5 6 7 8 |
cut_dict = {'Fair': 1, 'Good': 2, 'Very Good': 3, 'Premium': 4, 'Ideal': 5} clarity_dict = {'I1': 1, 'SI2': 2, 'SI1': 3, 'VS2': 4, 'VS1': 5, 'VVS2': 6, 'VVS1': 7, 'IF': 8} color_dict = {'D':7, 'E':6, 'F':5, 'G':4, 'H':3, 'I':2, 'J':1} df['cut'] = df['cut'].map(cut_dict) df['clarity'] = df['clarity'].map(clarity_dict) df['color'] = df['color'].map(color_dict) df = df.drop('Unnamed: 0', axis=1) |
- 특성 스케일링:
- 모든 특성이 거리 계산에 동등하게 기여하도록 특성 세트를 정규화합니다.
1 2 3 4 5 6 |
from sklearn import preprocessing X = df.drop(['price'], axis=1).values X = preprocessing.scale(X) y = df['price'].values y = preprocessing.scale(y) |
모델 훈련 및 평가
- 데이터셋 분할:
1 2 3 4 5 6 7 8 |
from sklearn.utils import shuffle df = shuffle(df, random_state=42) test_size = 200 X_train = X[:-test_size] y_train = y[:-test_size] X_test = X[-test_size:] y_test = y[-test_size:] |
- KNN 회귀 모델 훈련:
1 2 3 4 5 6 7 |
from sklearn.neighbors import KNeighborsRegressor score = [] for k in range(1, 20): clf = KNeighborsRegressor(n_neighbors=k, weights='distance', p=1) clf.fit(X_train, y_train) score.append(clf.score(X_test, y_test)) |
- 성능 시각화:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import plotly.graph_objs as go from plotly.offline import iplot trace0 = go.Scatter( y=score, x=np.arange(1, len(score)+1), mode='lines+markers', marker=dict(color='rgb(100, 200, 150)') ) layout = go.Layout( title='K Value vs. Accuracy Score', xaxis=dict(title='K Value', tickmode='linear'), yaxis=dict(title='Score') ) fig = go.Figure(data=[trace0], layout=layout) iplot(fig, filename='basic-line') |
- 최적의 ‘K’ 찾기:
1 2 |
k_max = score.index(max(score)) + 1 print(f"At K = {k_max}, Max Accuracy = {max(score) * 100:.2f}%") |
출력:
1 |
At K = 4, Max Accuracy = 98.05% |
- 최종 모델 평가:
1 2 3 4 |
clf = KNeighborsRegressor(n_neighbors=50) clf.fit(X_train, y_train) print(clf.score(X_test, y_test)) y_pred = clf.predict(X_test) |
출력:
1 |
0.9543611406331687 |
- 실제 가격과 예측 가격 비교:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import plotly.graph_objs as go from plotly.offline import iplot trace0 = go.Scatter( y=y_test, x=np.arange(200), mode='lines+markers', name='Actual Price', marker=dict(color='rgb(110, 10, 150)') ) trace1 = go.Scatter( y=y_pred, x=np.arange(200), mode='lines+markers', name='Predicted Price', line=dict(color='rgb(200, 50, 10)', dash='dot') ) layout = go.Layout( xaxis=dict(title='Index'), yaxis=dict(title='Normalized Price') ) figure = go.Figure(data=[trace0, trace1], layout=layout) iplot(figure) |
이 시각화는 실제 가격과 예측 가격 값을 오버레이하여 모델의 예측 정확도를 평가하는 데 도움이 됩니다.
8. 실용적인 예제
제공된 Jupyter Notebook에 설명된 대로 Python의 scikit-learn 라이브러리를 사용한 실용적인 구현을 살펴보겠습니다.
단계 1: 필요한 라이브러리 가져오기
1 2 3 4 5 6 7 8 |
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from sklearn import preprocessing, utils from sklearn.neighbors import KNeighborsRegressor import plotly.graph_objs as go from plotly.offline import iplot |
단계 2: 데이터셋 로드 및 탐색
1 2 3 4 |
df = pd.read_csv('diamonds.csv') print(df.head()) sns.FacetGrid(df, hue='cut', height=6).map(sns.distplot, 'price').add_legend() plt.show() |
단계 3: 데이터 전처리
범주형 변수를 숫자형으로 변환하고 특성을 스케일링합니다.
1 2 3 4 5 6 7 8 |
cut_dict = {'Fair': 1, 'Good': 2, 'Very Good': 3, 'Premium': 4, 'Ideal': 5} clarity_dict = {'I1': 1, 'SI2': 2, 'SI1': 3, 'VS2': 4, 'VS1': 5, 'VVS2': 6, 'VVS1': 7, 'IF': 8} color_dict = {'D':7, 'E':6, 'F':5, 'G':4, 'H':3, 'I':2, 'J':1} df['cut'] = df['cut'].map(cut_dict) df['clarity'] = df['clarity'].map(clarity_dict) df['color'] = df['color'].map(color_dict) df = df.drop('Unnamed: 0', axis=1) |
단계 4: 특성 스케일링 및 데이터 섞기
1 2 3 4 5 |
df = utils.shuffle(df, random_state=42) X = df.drop(['price'], axis=1).values X = preprocessing.scale(X) y = df['price'].values y = preprocessing.scale(y) |
단계 5: 데이터셋 분할
1 2 3 4 5 |
test_size = 200 X_train = X[:-test_size] y_train = y[:-test_size] X_test = X[-test_size:] y_test = y[-test_size:] |
단계 6: KNN 회귀 모델 훈련 및 성능 평가
1 2 3 4 5 |
score = [] for k in range(1, 20): clf = KNeighborsRegressor(n_neighbors=k, weights='distance', p=1) clf.fit(X_train, y_train) score.append(clf.score(X_test, y_test)) |
단계 7: 정확도 점수 시각화
1 2 3 4 5 6 7 8 9 10 11 12 13 |
trace0 = go.Scatter( y=score, x=np.arange(1, len(score)+1), mode='lines+markers', marker=dict(color='rgb(100, 200, 150)') ) layout = go.Layout( title='K Value vs. Accuracy Score', xaxis=dict(title='K Value', tickmode='linear'), yaxis=dict(title='Score') ) fig = go.Figure(data=[trace0], layout=layout) iplot(fig, filename='basic-line') |
단계 8: 최적의 ‘K’ 값 결정하기
1 2 |
k_max = score.index(max(score)) + 1 print(f"At K = {k_max}, Max Accuracy = {max(score) * 100:.2f}%") |
단계 9: 최종 모델 훈련 및 예측
1 2 3 4 |
clf = KNeighborsRegressor(n_neighbors=50) clf.fit(X_train, y_train) print(clf.score(X_test, y_test)) y_pred = clf.predict(X_test) |
단계 10: 실제 값과 예측 값 비교
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
trace0 = go.Scatter( y=y_test, x=np.arange(200), mode='lines+markers', name='Actual Price', marker=dict(color='rgb(110, 10, 150)') ) trace1 = go.Scatter( y=y_pred, x=np.arange(200), mode='lines+markers', name='Predicted Price', line=dict(color='rgb(200, 50, 10)', dash='dot') ) layout = go.Layout( xaxis=dict(title='Index'), yaxis=dict(title='Normalized Price') ) figure = go.Figure(data=[trace0, trace1], layout=layout) iplot(figure) |
생성된 플롯은 선택한 ‘K’ 값을 기반으로 KNN 모델이 다이아몬드 가격을 얼마나 잘 예측하는지 시각적으로 보여줍니다.
9. 결론
K-최근접 이웃 알고리즘은 분류와 회귀의 다양한 응용 분야에 적합한 다재다능하고 간단한 기계 학습 도구입니다. 그 효과는 주로 ‘K’ 값 선택과 사용된 거리 측정 방법에 달려 있습니다. 적절한 데이터 전처리와 특성 스케일링은 모델 성능을 향상시키는 중요한 단계입니다. KNN은 대규모 데이터셋에서 계산 비용이 많이 들지만, 그 단순성은 기계 학습 실무자들에게 훌륭한 출발점을 제공합니다.
10. 참고문헌
이 가이드가 K-최근접 이웃 알고리즘에 대한 명확한 이해를 제공했기를 바랍니다. 계속해서 심도 있는 튜토리얼과 기계 학습 기술에 대한 통찰을 기대해 주세요.