k-최근접 이웃(k-Nearest Neighbors, k-NN)은 가장 널리 쓰이는 머신러닝 분류 알고리즘 중 하나로, 새로운 데이터를 주변 k개 이웃의 다수결로 분류합니다. 학습된 모델이 클래스 간 영역을 어떻게 나누는지 보여주는 결정 경계(decision boundary)를 시각화하면 모델의 동작 방식을 직관적으로 파악할 수 있습니다. 이 글에서는 Matplotlib과 scikit-learn을 활용해 k-NN 결정 경계 그래프를 그리는 방법을 단계별로 살펴보겠습니다.
구현 단계
그림(figure) 크기를 설정하고 서브플롯 주변과 사이의 여백을 조정합니다.
이웃의 개수를 지정하는 n_neighbors 변수를 초기화합니다.
scikit-learn에서 iris(붓꽃) 분류 데이터셋을 불러옵니다.
x, y에 사용할 데이터 포인트를 생성합니다. 여기서는 꽃받침(sepal) 길이와 너비 두 가지 특성만 사용합니다.
결정 영역 배경에 사용할 연한 색상 팔레트와 데이터 포인트에 사용할 진한 색상 팔레트를 각각 준비합니다.
k-최근접 이웃 투표 방식을 구현한
KNeighborsClassifier분류기를 생성하고 학습 데이터에 적합(fit)시킵니다.x축과 y축의 최솟값(x_min, y_min)과 최댓값(x_max, y_max)을 구해 그래프의 표시 범위를 정합니다.
새 figure를 생성하거나 기존 figure를 활성화합니다.
contourf플롯으로 예측 결과를 배경에 채워 결정 영역을 표현합니다.X 데이터셋으로 산점도(scatter plot)를 그립니다.
x축·y축 레이블, 그래프 제목, 축 범위를 설정합니다.
show()메서드를 호출해 그림을 화면에 출력합니다.
전체 예제 코드
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.colors import ListedColormap
from sklearn import neighbors, datasets
# 그림 크기와 레이아웃 설정
plt.rcParams['figure.figsize'] = [7.00, 3.50]
plt.rcParams['figure.autolayout'] = True
# 이웃의 개수
n_neighbors = 15
# iris 데이터셋 로드 (처음 2개 특성: 꽃받침 길이, 꽃받침 너비)
iris = datasets.load_iris()
X = iris.data[:, :2]
y = iris.target
h = .02 # 격자 간격
# 연한 색(결정 영역용) / 진한 색(데이터 포인트용) 팔레트
cmap_light = ListedColormap(['orange', 'cyan', 'cornflowerblue'])
cmap_bold = ['darkorange', 'c', 'darkblue']
# k-NN 분류기 생성 및 학습
clf = neighbors.KNeighborsClassifier(n_neighbors, weights='uniform')
clf.fit(X, y)
# 그래프 표시 범위 계산
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
# 격자의 모든 좌표에 대해 클래스 예측
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure()
# 결정 경계를 배경에 채우기
plt.contourf(xx, yy, Z, cmap=cmap_light)
# 실제 데이터 포인트 산점도
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=iris.target_names[y],
palette=cmap_bold, alpha=1.0, edgecolor='black')
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title("3-Class classification (k = %i, weights = 'uniform')" % n_neighbors)
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])
plt.show()
코드 핵심 포인트
np.meshgrid: 결정 경계를 그리려면 그래프 영역 전체를 h=0.02 간격의 촘촘한 격자로 나눈 뒤, 모든 격자점에 대해 클래스를 예측해야 합니다. meshgrid는 이러한 2차원 격자 좌표를 손쉽게 만들어 줍니다.
plt.contourf: 격자점별 예측 결과(Z)를 등고선 형태로 배경에 채워, 클래스별 결정 영역을 색으로 구분해 보여줍니다.
sns.scatterplot: hue 옵션에 클래스 이름을 지정하면 클래스마다 다른 색으로 실제 데이터 포인트를 표시할 수 있어, 배경의 결정 영역과 함께 비교하기 좋습니다.
weights='uniform': 모든 이웃에 동일한 가중치를 부여하는 방식입니다. 'distance'로 바꾸면 가까운 이웃일수록 더 큰 영향력을 가지며, 결정 경계의 모양도 달라집니다.
n_neighbors 값 조절: k 값을 작게 하면 경계가 데이터에 민감하게 구부러져 과적합(overfitting) 위험이 커지고, 크게 하면 경계는 매끄러워지지만 과소적합(underfitting)될 수 있습니다.
실행 결과
위 코드를 실행하면 아래와 같이 3개 클래스의 결정 경계가 색으로 구분된 그래프가 출력됩니다.
