한양과학기술고등학교

에듀테크 프로젝트 대시보드

k-최근접 이웃 (KNN)

k-최근접 이웃 (k-Nearest Neighbors)

새 데이터가 어느 그룹인지 궁금하면 제일 가까운 이웃 k명에게 물어보고 다수결로 정한다. 수식도, 학습 단계도 없다. 그냥 모든 훈련 데이터를 통째로 외워 두었다가, 질문이 들어오면 그때 거리를 재서 답한다 — 그래서 게으른 학습(lazy learning)이라 부른다. 아래 판을 눌러(또는 드래그해) ? 점을 옮기면서, k에 따라 판정이 어떻게 바뀌는지, 그리고 결정 경계(decision boundary)가 어떤 모양이 되는지 살펴보자.

1. 이웃에게 물어보기

색칠된 점은 이미 정답을 아는 훈련 데이터, 흰 테두리 ? 는 분류하려는 새 점이다. ? 에서 가까운 순서로 k개를 잇는 선이 그어지고, 그 k개의 색 중 가장 많은 색으로 ? 가 판정된다. 판 아무 곳이나 누르면 ? 가 그리로 이동한다.

A 클래스 B 클래스 C 클래스 분류 대상 ?
…
경계가 울퉁불퉁하면 k가 작다는 신호(노이즈에 민감·과적합). k를 키우면 매끈해지지만 너무 키우면 작은 그룹이 큰 그룹에 먹힌다.

2. 알고리즘: 딱 세 단계

거리 재기

새 점과 모든 훈련 데이터 사이 거리를 계산한다. 보통 유클리드 거리 √(Δx²+Δy²) 를 쓴다.

k개 고르기

거리를 오름차순 정렬해 가장 가까운 k개의 이웃만 남긴다.

투표

그 k개의 라벨을 세어 최다 득표 클래스로 판정. 동점을 피하려고 k는 보통 홀수로 둔다.

변형

가까운 이웃에 더 큰 표(1/거리)를 주는 가중 KNN, 라벨 대신 평균값을 내는 KNN 회귀도 있다.

학습(fit) 시간이 사실상 0이다 — 데이터를 저장만 한다. 대신 예측 한 번마다 전체 데이터와 거리를 재야 해서 데이터가 많으면 느리다. 그래서 k-d 트리·볼 트리· 근사 최근접 이웃(ANN) 같은 색인으로 이웃 탐색을 가속한다.

3. 20줄이면 끝 (JavaScript)

// train: [{x, y, label}], q: {x, y}, k: 홀수 function knnPredict(train, q, k) { // ① 모든 훈련점까지 거리 계산 const scored = train.map(p => ({ label: p.label, dist: Math.hypot(p.x - q.x, p.y - q.y) })); // ② 거리순 정렬 후 앞에서 k개 scored.sort((a, b) => a.dist - b.dist); const neighbors = scored.slice(0, k); // ③ 라벨 다수결 const tally = {}; for (const n of neighbors) tally[n.label] = (tally[n.label] || 0) + 1; return Object.keys(tally) .reduce((a, b) => tally[a] >= tally[b] ? a : b); }

4. 놓치기 쉬운 함정

k = 1 은 위험

이웃 딱 하나만 보면 이상치(노이즈) 한 점에 통째로 휘둘린다. 훈련 정확도는 100%지만 새 데이터에는 약한 과적합.

특성 스케일 정규화

키(㎝)와 몸무게(㎏)처럼 단위가 다르면 숫자 큰 축이 거리를 독점한다. 각 특성을 0~1 또는 표준점수로 맞춰야 공정하다.

차원의 저주

특성이 수십 개면 모든 점이 고만고만하게 멀어져 "가까운 이웃"이 의미를 잃는다. 차원 축소나 특성 선택이 필요.

불균형 데이터

A가 900개, B가 100개면 큰 k에서 어디를 찍어도 A로 쏠린다. 거리 가중 투표나 표본 균형이 해법.

적당한 k는 보통 교차 검증으로 여러 값을 시험해 고른다. 흔한 출발점은 k ≈ √n (n = 훈련 데이터 수).

5. k-평균(k-means)과 다르다

 KNNk-평균
학습 종류지도 학습 (라벨 필요)비지도 학습 (라벨 없음)
k의 의미참고할 이웃 수만들 군집(중심점) 수
하는 일새 점 분류데이터 그룹핑
반복없음 (한 번에 계산)할당↔이동 반복 수렴
이름에 똑같이 "k"가 들어가고 "거리"를 쓴다는 점 때문에 자주 혼동되지만, 목적도 입력도 완전히 다른 알고리즘이다.