haileyjpark

[머신러닝] KNN(K-Nearest Neighbors) 분류기 본문

소프트웨어 공학

[머신러닝] KNN(K-Nearest Neighbors) 분류기

개발하는 헤일리 2024. 11. 10. 21:03

 

머신러닝 수업을 듣고 KNN 분류기에 대해 학습한 내용을 바탕으로 KNN 분류기를 구현하고, K값에 따른 성능과 거리측정방식에 따른 성능을 비교해보았습니다.

 

KNN(K-Nearest Neighbors) 분류기란?

KNN(K-Nearest Neighbors) 분류기는 머신러닝의 지도 학습 기법 중 하나로, 가장 간단하고 직관적인 분류 알고리즘으로 알려져 있습니다. KNN 분류기의 주요 개념은 새로운 데이터를 분류할 때, 그 데이터와 가장 가까운 K개의 이웃 데이터의 클래스(라벨)를 참고하여 결정하는 방식입니다.

 

KNN의 원리

KNN의 핵심 원리는 유사한 데이터가 가깝게 위치한다는 가정에 기반하고 있습니다. 즉, 새로운 데이터가 주어졌을 때, 그 데이터와 기존 데이터 간의 거리를 계산하여 가장 가까운 K개의 이웃 데이터를 찾아 이웃 데이터의 클래스 중에서 다수결로 새로운 데이터의 클래스를 결정합니다.

이때 중요한 요소는 K 값거리 측정 방법입니다.

 

  • K 값: K는 이웃의 개수를 의미하며, K의 값에 따라 분류기의 성능이 달라질 수 있습니다. 너무 작은 K를 선택하면 노이즈에 민감해질 수 있고, 너무 큰 K를 선택하면 모델이 데이터에 덜 민감해져 구분 능력이 떨어질 수 있습니다. 일반적으로 여러 K 값을 테스트해보며 최적의 K 값을 찾습니다.
  • 거리 측정 방법: KNN에서는 데이터 포인트 간 거리를 측정하여 가장 가까운 이웃을 찾습니다. 일반적으로 유클리드 거리(Euclidean Distance)가 많이 사용되지만, 문제에 따라 맨해튼 거리(Manhattan Distance), 코사인 유사도(Cosine Similarity) 등 다양한 거리 측정 방법을 사용할 수 있습니다. 거리 측정 방법에 따라 분류 결과가 달라질 수 있기 때문에, 적절한 거리를 선택하는 것도 중요한 요소입니다.

 

KNN 알고리즘의 작동 방식

1. 데이터 준비: 학습 데이터와 테스트 데이터를 준비합니다. 학습 데이터는 이미 클래스 라벨이 부여된 데이터이며, 테스트 데이터는 분류하고자 하는 새로운 데이터입니다.

2. 거리 계산: 테스트 데이터와 학습 데이터의 각 포인트 간의 거리를 계산합니다.

3. 가장 가까운 이웃 찾기: 계산한 거리 값 중에서 가장 가까운 K개의 이웃을 선택합니다.

4. 다수결 투표: K개의 이웃 데이터의 라벨을 확인하고, 그 중 가장 빈도가 높은 라벨을 테스트 데이터의 예측 라벨로 결정합니다.

5. 결과 출력: 테스트 데이터에 대해 예측된 라벨을 출력하여 분류 결과를 확인합니다.

 

 

KNN의 장점과 단점

장점

  • 직관적이고 간단함: 이해하고 구현하기 쉽습니다. 데이터의 분포나 특성을 크게 가정하지 않기 때문에 유연하게 사용할 수 있습니다.
  • 모델 학습 시간이 없음: KNN은 학습 단계에서 모델을 생성하지 않고, 테스트 단계에서 거리 계산과 다수결 투표만 수행하기 때문에 Lazy Learning(게으른 학습)이라고도 불립니다. 학습 시간이 거의 들지 않는다는 장점이 있습니다.

단점

  • 계산 비용이 큼: 모든 데이터와 거리를 계산해야 하므로, 데이터가 많아질수록 계산 비용이 커집니다. 특히, 실시간으로 예측해야 하는 환경에서는 비효율적일 수 있습니다.
  • 저장공간이 많이 필요함: KNN은 모든 학습 데이터를 저장하고 있어야 하므로, 메모리 사용량이 많아질 수 있습니다.
  • 데이터의 특성에 따라 성능이 영향을 받음: 데이터의 특성에 따라 K 값이나 거리 측정 방법을 잘 선택하지 않으면 성능이 떨어질 수 있으며, 특히 고차원 데이터에서는 거리 기반 측정이 잘 작동하지 않는 차원의 저주(Curse of Dimensionality) 문제가 발생할 수 있습니다.

 

KNN의 활용 사례

KNN은 그 직관성 덕분에 간단한 분류 문제나 회귀 문제에 자주 사용됩니다. 예를 들어, 이미지 분류, 텍스트 분류, 추천 시스템, 패턴 인식 등에서 활용됩니다. 특히, 소규모 데이터셋을 다룰 때 성능이 우수하며, 데이터 분포를 파악하거나 다른 복잡한 모델의 성능과 비교할 때 기준으로 사용하기도 합니다.

 

K값에 따른 성능 분석 구현

KNN 알고리즘에서 K 값은 예측의 정확도에 중요한 역할을 합니다. K 값이 너무 작으면 모델이 데이터의 노이즈에 민감해져 과적합될 가능성이 있으며, K 값이 너무 클 경우 주어진 데이터 영역이 아닌 전체 데이터 영역에서 각 클래스가 차지하는 비율(선험확률)에 의존하게 됩니다.

 

K 값을 3, 5, 7, 15, 20, 100으로 설정하고 각 K 값에 대해 5번씩 무작위로 데이터를 분할하여 정확도를 측정한 후, 평균 정확도를 계산하여 각각의 성능 비교를 구현해 보았습니다.

import numpy as np
import pandas as pd
from collections import Counter
from sklearn.model_selection import train_test_split

# CSV 파일 불러오기
data = pd.read_csv('./knn_assignment_data.csv')
X = data[['Feature1', 'Feature2']].values
y = data['Label'].values

# 거리 계산 함수 (유클리드 거리)
def euclidean_distance(point1, point2):
    return np.sqrt(np.sum((point1 - point2) ** 2))

# KNN 분류기 클래스 정의
class KNNClassifier:
    def __init__(self, k=3, distance_metric='euclidean'):
        self.k = k
        self.distance_metric = distance_metric

    def fit(self, X_train, y_train):
        self.X_train = X_train
        self.y_train = y_train

    def _calculate_distance(self, x1, x2):
        if self.distance_metric == 'euclidean':
            return euclidean_distance(x1, x2)

    def predict(self, X_test):
        predictions = [self._predict(x) for x in X_test]
        return np.array(predictions)

    def _predict(self, x):
        distances = [self._calculate_distance(x, x_train) for x_train in self.X_train]
        k_indices = np.argsort(distances)[:self.k]
        k_nearest_labels = [self.y_train[i] for i in k_indices]
        most_common = Counter(k_nearest_labels).most_common(1)
        return most_common[0][0]

# K 값에 따른 성능 비교 (유클리드 거리 고정)
k_values = [3, 5, 7, 15, 20, 100]
n_repeats = 5
results = {}

for k in k_values:
    accuracies = []
    for _ in range(n_repeats):
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
        knn = KNNClassifier(k=k, distance_metric='euclidean')
        knn.fit(X_train, y_train)
        y_pred = knn.predict(X_test)
        accuracy = np.sum(y_pred == y_test) / len(y_test)
        accuracies.append(accuracy)

    avg_accuracy = np.mean(accuracies)
    results[k] = avg_accuracy
    print(f"K={k}, Average Accuracy={avg_accuracy:.2f}")

# 최적 K 값 출력
best_k = max(results, key=results.get)
print(f"\n최적 K 값: {best_k}, 평균 정확도: {results[best_k]:.2f}")

 

 

K 값 선택 근거

처음에 K의 값을 3, 5, 7로 비교해보았으나, 성능에 큰 차이가 보이지 않아서 점점 더 큰 값으로 비교해보려고 15, 20, 100을 추가하여 다시 비교해보았습니다.

 

결과 분석

  • K=3: 모델이 세부사항에 민감하게 반응하여 각 클래스의 경계를 잘 구분할 것으로 예상했지만, 정확도가 0.77로 나타나 작은 K 값에서 다소 불안정한 성능을 보였습니다.
  • K=5, K=7: 값들에서는 정확도가 0.79 약간 개선되었으며, 모델이 과도한 세부 사항을 반영하지 않으면서도, 데이터의 전반적인 경향을 반영하는 균형을 이룹니다.
  • K=15: 정확도가 가장 높은 0.80 기록했습니다. 구간에서 모델이 데이터의 노이즈에 민감해지며, 과적합되지 않으면서도 일반화 성능이 향상된 것으로 보입니다.
  • K=20, K=100: K=20, K=100에서 정확도가 0.79 약간 낮아졌습니다. 이는 K 값에서는 모델이 클래스의 전반적인 분포를 반영하여, 안정적이지만 세부사항을 반영하지 못하는 경향이 있음을 시사합니다.

 

최적 K값과 결론

최적의 K 값은 K=15로 나타났으며, 이때 평균 정확도가 0.80으로 가장 높았습니다.

이는 K=15일 때 모델이 각 클래스의 전반적인 분포와 개별 데이터 포인트 간의 관계를 적절하게 반영할 수 있었음을 의미합니다.

 

K 값에 따라 성능 차이가 크게 나타나지 않은 이유는 다음과 같이 분석할 수 있습니다.

  • 데이터의 균등한 분포: 각 클래스가 균일하게 분포되어 있어, K 값이 작아도 과적합 문제가 심각하지 않았습니다.
  • 명확한 클래스 경계: 클래스 간 경계가 명확하여 작은 K 값이나 큰 K 값 모두 안정적인 성능을 보였습니다.
  • K 값이 큰 경우: 큰 K 값에서는 모델이 모든 클래스의 평균적인 분포를 반영하며 안정성을 유지하지만, 개별 데이터의 세부 사항 반영이 부족해 정확도가 약간 감소했습니다.
반응형