최근접이웃 알고리즘(K-NN)

k-NN(Nearest Neighbors) 알고리즘은 가장 간단한 머신러닝 알고리즘입니다. 훈련용 데이터셋을 통해서 모델을 만들고 새로운 데이터가 입력될 때는 훈련 데이터셋에서 가장 가까운 “최근접 이웃”을 찾습니다.

KNN 알고리즘의 좋은 설명을 해주는 사이트의 허민석님의 강의를 올려드리니 참고해보시면 좋겠습니다. 이 외에도 알고리즘을 설명하는 많은 강의 사이트가 있으니 개념이 궁금하신 분들은 찾아보시길 추천해드립니다.

이번 글에서는 개념에 대한 설명보다는 바로 예제 코드를 살펴보도록 하겠습니다. 해당 알고리즘은 sklearn에서 이미 잘 구현했기 때문에 사용자가 별도의 알고리즘을 구현한 필요가 없습니다. 사용자는 단 몇줄의 코드만으로 해당 알고리즘을 사용할 수 있습니다. 예제는 sklearn에서 제공하는 KNeighborsClassifier를 사용하도록 하겠습니다. 사용하는 데이터 역시 sklearn에서 제공하는 load_iris() 데이터를 사용해보겠습니다.

Introduction to Machine Learning with Python, KNeighborsClassifier

sklearn에서 제공하는 다양한 데이터셋이 있습니다. 그중에 이번 예제는 붗꽃 데이터를 사용해보겠습니다. 붗꽃 데이터는 꽃받침과 꽃잎의 넓이와 길이 정보와 붗꽃의 종류(setosa, versicolor, virginica) 데이터의 형식으로 되어 있습니다. 4개의 feature 정보와 label 컬럼이 있고 데이터의 수는 150개 정도이기 때문에 분류 문제를 테스트해보기에 적절한 예제입니다.

from sklearn.datasets import load_iris
iris = load_iris()
iris.keys()
#dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename'])

feature 정보는 아래와 같이 sepal(꽃받침) length, sepal width, petal(꽃잎) length, petal width의 네개 컬럼 정보가 들어있습니다.

iris.feature_names
#['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

해당 데이터를 pandas의 데이터프레임으로 데이터 타입을 바꾸면 좀 더 편하게 데이터의 내용을 확인 할 수 있고 pandas에서 제공하는 다양한 함수를 사용할 수 있습니다.

import pandas as pd
df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
df.head()
df.describe()

sklearn의 KNeighborsClassifier 패키지를 임포트합니다. 그리고 데이터를 훈령용 세트와 테스트용 세트를 8:2로 분리해서 x_train, y_train, x_test, y_test 형태로 데이터를 생성합니다. 데이터 세트를 분리한 후에 shape을 보면 120:30의 형태로 데이터가 나뉜 것을 확인할 수 있습니다.

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2, shuffle=False, random_state=701)
print(x_train.shape, x_test.shape)
#(120, 4) (30, 4)

n_neighbors의 적절한 이웃 값을 찾기 위해서 데이터 셋을 테스트해봅니다. 이웃 값을 너무 적게 하면 모델의 복잡도가 올라가고 너무 많게 하면 모델의 예측력이 떨어지기 때문에 적절한 값을 찾는 것이 중요합니다.

train_accuracy = []
test_accuracy = []
neighbors_set = range(1,11)
for n_neighbors in neighbors_set:    
    clf = KNeighborsClassifier(n_neighbors=n_neighbors)
    clf.fit(x_train, y_train)
    score = clf.score(x_test, y_test)
    train_accuracy.append(clf.score(x_train, y_train))
    test_accuracy.append(clf.score(x_test, y_test))
import matplotlib.pyplot as plt

plt.plot(neighbors_set, train_accuracy, label='train')
plt.plot(neighbors_set, test_accuracy, label='test')
plt.legend()

위의 결과 값과 같이 이웃 값(n_neighbors)이 5일 경우에 가장 높은 예측 정확도를 보여줍니다. 모델을 생성한 후 예측의 정확도를 보기 위해 테스트 데이터를 사용해서 예측을 수행하고 이 값을 테스트 값과 비교해본 결과 80%의 예측 정확도를 얻었습니다.

clf = KNeighborsClassifier(n_neighbors=5)
clf.fit(x_train, y_train)
clf.predict(x_test)
# array([2, 2, 2, 1, 2, 2, 1, 1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1])
(clf.predict(x_test) == y_test).sum()/len(y_test) # 0.8

다만 이 알고리즘을 사용할 경우 이웃을 어떻게 적절히 정의할 것인가에 대한 고민이 필요합니다. 만약 이웃을 적게 사용한다면 모델의 복잡도가 높아지고 많이 사용하면 복잡도는 낮아집니다. 위의 이미지에서 가장 오른쪽에 이미지의 경계면이 가장 부드럽지만 이렇게되면 모델이 지나치게 일반화 되어서 예측하는 값의 정확도가 낮아질 염려가 있습니다.

k-NN 알고리즘의 특징은 이해하기 매우 쉬운 모델이라는 점입니다. 사용이 비교적 간단하지 좋은 성능을 발휘하기 때문에 어떤 높은 난이도의 문제를 해결하기 전에 시도해봄직한 모델이라고 할 수 있습니다. 그러나 쉬운 모델이지만 훈련용 세트가 커지만 예측이 느려지는 특징이 있기 때문에 널리 사용되진 않습니다.

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

class ANN(nn.Module):
    
    def __init__(self, D_in, H, D_out):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(D_in, H),
            nn.ReLU(),
            nn.Linear(H, D_out),
        )
    
    def forward(self, x):
        x = x.float()
        return self.layers(x)

# input dim, hidden size, ont-hot
model = ANN(tx_train.size(dim=1),5,torch.unique(ty_train).size(dim=0))
model

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())

nb_epochs = 5001
for epoch in range(nb_epochs):
    model.train()
    predict = model(tx_train)
    loss = criterion(predict, ty_train.squeeze().long())
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    if epoch % 250 == 0:
        print('{}/{}, cost:{:.5f}'.format(epoch,nb_epochs,loss.item()))

참고로 위의 코드는 pytorch를 사용해서 동일한 예측 코드를 Linear Regression으로 구현한 것입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다