[머신러닝] 1. KNN (K-Nearest Neighbors)
1. KNN (K-Nearest Neighbors)
1.1. KNN의 작동원리
1.2. KNN의 하이퍼 파라미터
2. KNN의 주요 특징
2.1. KNN의 장점
2.2. KNN의 단점
3. KNN 코드 구현
3.1. scikit-learn에서 KNN
3.2. KNN 모델 실험
A. 데이터 정규화의 영향
B. 근접 이웃(K)의 영향
C. 가중치 영향
D. 거리 측정 방식의 영향
1. KNN (K-Nearest Neighbors)
KNN은 별다른 학습이 필요 없는 이해하기 가장 쉬운 알고리즘입니다. KNN에 대해서 먼저 알아 보도록 하겠습니다.
1.1. KNN의 작동 원리
1. 학습에 필요한 데이터들을 저장한다.
2. 예측하고자 하는 데이터를 기존 데이터들과 비교한다.
- 비교란, 예측하고자 하는 데이터와 저장되어 있는 데이터들 사이의 거리를 측정하는 것입니다.
3. 거리를 측정한 뒤, 가장 거리가 가까운 K개의 데이터에 따라 새로운 데이터의 라벨, 값을 예측합니다.
- 분류 문제의 경우, K개의 데이터 라벨의 다수결을 통해 라벨을 결정합니다.
- 회귀 문제의 경우, K개의 데이터 결과값의 평균을 계산하여 결정합니다.
1.2. KNN의 하이퍼 파라미터
위에서 봤듯이, KNN에서 가장 중요한 것은 몇 개(K)의 데이터를 볼 것인가, 거리는 어떻게 측정할 것인가가 가장 중요합니다.
하이퍼 파라미터 K의 경우, 커질수록 underfitting의 위험이 있으며, 작을수록 overfitting의 위험이 있습니다.
적절한 K를 결정하는 방법은, 일반적으로 전체 데이터의 수(N)의 제곱근을 초기값으로 설정하고, 교차 검증을 하여 최적의 K를 탐색합니다.
K개의 가까운 거리의 데이터를 찾기 위해선 모든 데이터를 탐색해야 하기 때문에 데이터 수가 많아질수록 시간이 오래 걸리는 문제가 발생할 수 있습니다. 이럴 땐 K-D 트리, 볼츠만 트리 등의 자료 구조를 사용해 데이터를 저장하여 가까운 데이터를 찾는 데 걸리는 시간을 단축시킬 수 있습니다.
인접한 K개의 데이터를 찾기 위해선 데이터 사이의 거리를 측정해야 합니다. 거리를 측정하는 방법은 여러가지가 있습니다.
1. 유클리드 거리
$$distance=\sqrt{\sum^n_{i=1}(x_i-y_i)^2}$$
가장 흔히 사용되는 방법이며 연속형 데이터에 유용한 방법입니다.
2. 맨해튼 거리
$$distance=\sum^n_{i=1}|x_i-y_i|$$
직선 이동이 불가능한 경우 (연속적이지 않은 데이터를 처리해야 할 경우)에 사용되는 거리 측정법입니다.
3. 민코프스키 거리
$$distance=[\sum^n_{i=1}|x_i-y_i|^p]^{1\over p}$$
유클리드 거리와 맨해튼 거리를 절충한 방법으로, p=1일 경우 맨해튼 거리가 되며, p=2일 경우 유클리드 거리가 됩니다.
4. 코사인 거리
$$distance={A\cdot B\over||A||\cdot||B||}={\sum^n_{i=1}A_iB_i\over\sqrt{\sum^n_{i=1}A^2_i}\sqrt{\sum^n_{i=1}B^2_i}}$$
코사인 거리의 경우 고차원 데이터를 다룰 때 유클리드 거리보다 유리하다고 합니다.
거리를 측정해 가장 인접한 K개의 데이터를 선정했다면, 가중치를 설정해야 합니다. 균등한 가중치를 설정하면 거리와 관계없이 K개의 데이터가 모두 동등한 가중치를 갖게 됩니다. 또는 거리에 따라 가중치를 차등 설정하면 더 가까운 거리의 데이터가 더 큰 가중치를 갖게 됩니다.
2. KNN의 주요 특징
KNN은 학습과정이 없다는 것이 가장 큰 특징입니다. 따라서 학습에 필요한 파라미터도 없으며, 학습시간도 소요되지 않습니다. 다만 예측 과정에선 인접한 K개의 데이터를 찾아야 하기 때문에 시간이 소요됩니다. 이런 특징들로 인한 KNN의 장단점을 알아 보도록 하겠습니다.
2.1. KNN의 장점
- 구현이 간단하고 이해하기 쉬습니다.
- 학습 과정이 필요 없습니다.
- 학습이 필요 없기 때문에 새로운 데이터를 추가하는 것도 쉽습니다.
2.2. KNN의 단점
- KNN이 저장한 모든 데이터들과 거리를 비교해야 하기 때문에 예측 과정에서 시간이 오래 걸립니다.
- 그렇기 때문에 데이터가 많아질수록 속도가 급격히 느려집니다.
- K-D트리, 볼츠만 트리 등의 자료구조를 사용해 이 시간을 줄여줄 수 있습니다.
- 모든 데이터를 저장해둬야 하기 때문에 메모리 사용량이 많습니다.
- 데이터 특성(feature)의 스케일에 민감합니다.
- 데이터 간의 거리를 측정하는데 만약 서로 단위가 다르다면 (cm와 km라던가) 거리가 비정상적으로 예측될 수 있다.
- 따라서 정규화를 통해 특성 사이의 단위를 맞춰주는 것이 필수입니다.
- 노이즈에 민감합니다.
- 인접한 거리에 노이즈가 많을 경우 데이터가 잘못 예측될 가능성이 높습니다.
- 고차원 데이터에서 성능이 떨어지기 쉽습니다.
- 이 경우 차원 축소 알고리즘을 적용해 성능을 높여줄 수 있습니다.
이렇게 정리해보면 단점이 훨씬 많아 보이지만, 실제로 사용해보면 장점이 매우 강력하단 걸 알 수 있고, 성능도 상당히 잘 나오는 편이기 때문에 굉장히 괜찮은 알고리즘입니다.
3. KNN 코드 구현
3.1. scikit-learn에서 KNN
머신러닝 알고리즘들은 파이썬의 scikit-learn 라이브러리를 이용해 쉽게 사용할 수 있습니다.
https://scikit-learn.org/dev/modules/generated/sklearn.neighbors.KNeighborsClassifier.html
KNeighborsClassifier
Gallery examples: Release Highlights for scikit-learn 0.24 Classifier comparison Plot the decision boundaries of a VotingClassifier Caching nearest neighbors Comparing Nearest Neighbors with and wi...
scikit-learn.org
KNN은 아래와 같이 사용할 수 있습니다.
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
clf = KNeighborsClassifier(
n_neighbors=5,
weights="distance",
algorithm="auto",
metric="minkowski",
p=2,
n_jobs=4
)
clf.fit(x_train, y_train)
pred = clf.pred(x_test)
상당히 간단하죠? 주요 파라미터들에 대해 살펴보겠습니다.
- n_neighbors : 몇 개(K)의 근접 이웃을 살펴볼지를 결정하는 파라미터입니다.
- weights : 근접한 K개의 이웃에 대해 거리에 따른 가중치를 부여할지 여부입니다. {"uniform", "distance"} 중 하나를 선택합니다.
- algorithm : 데이터를 저장할 때 어떤 자료 구조로 저장할 지를 선택하는 파라미터입니다. {"ball_tree", "kd_tree", "brute", "auto"} 중 하나를 선택할 수 있습니다.
- metric, p : 거리 측정 방법을 선택합니다. {'manhattan', 'cosine', 'euclidean', 'nan_euclidean', 'haversine', 'minkowski'} 중 하나를 선택할 수 있습니다. p는 'minkowski'를 선택했을 때 결정할 수 있는 파라미터입니다. (p=1이면 manhattan, p=2면 euclidean)
- n_jobs : 이웃 데이터를 찾을 때 사용할 cpu 코어의 수를 결정합니다.
3.2. KNN 모델 실험
간단하게 KNeighborsCalssifier를 이용해 breast cancer 데이터셋 분류를 학습해 보도록 하겠습니다.
https://colab.research.google.com/drive/1oMwcNgtZM28Ie39C4fcad4hVsWchDt8E?usp=sharing
K-NN.ipynb
Colab notebook
colab.research.google.com
breast cancer 데이터셋은 환자의 특정 수치 30개를 입력했을 때, 이 환자가 유방암 환자인지 아닌지를 판별하는 분류 문제입니다.
이 데이터를 이용해 KNeighborsClassifier의 파라미터를 다양하게 조정해보면서 모델 성능에 어떤 영향을 미치는지 확인해 보도록 하겠습니다.
A. 데이터 정규화의 영향
우선 데이터를 정규화 하기 전과 후의 성능을 비교해 보도록 하겠습니다.
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
clf = KNeighborsClassifier(
n_neighbors=5,
weights="distance",
metric="minkowski",
p=2,
n_jobs=4
)
clf.fit(train_x, train_y)
print("정규화 하기 전 : ", clf.score(test_x, test_y))
pipeline = Pipeline(
[('scaler', StandardScaler()), ('model', clf)]
)
pipeline.fit(train_x, train_y)
print("정규화 한 후 : ", pipeline.score(test_x, test_y))
'''
정규화 하기 전 : 0.9298245614035088
정규화 한 후 : 0.956140350877193
'''
보면 정확도가 3%나 차이나는 것을 확인할 수 있습니다. 앞서 장단점에서 말했듯이 KNN은 데이터 단위에 영향을 크게 받는 것을 확인할 수 있었습니다.
B. 근접 이웃(K)의 영향
n_neighbors = [10, 15, 20, 25]
for nn in n_neighbors:
clf = KNeighborsClassifier(
n_neighbors=nn,
weights="distance",
metric="minkowski",
p=2,
n_jobs=4
)
pipeline = Pipeline(
[('scaler', StandardScaler()), ('model', clf)]
)
pipeline.fit(train_x, train_y)
train_score = pipeline.score(train_x, train_y)
test_score = pipeline.score(test_x, test_y)
print(f"n_neighbors : {nn}")
print(f" ㄴtrain score : {train_score}")
print(f" ㄴtest score : {test_score}")
'''
n_neighbors : 10
ㄴtrain score : 1.0
ㄴtest score : 0.956140350877193
n_neighbors : 15
ㄴtrain score : 1.0
ㄴtest score : 0.9736842105263158
n_neighbors : 20
ㄴtrain score : 1.0
ㄴtest score : 0.9736842105263158
n_neighbors : 25
ㄴtrain score : 1.0
ㄴtest score : 0.9649122807017544
'''
모두 훈련셋에 대해서는 100%의 정확도를 달성한 것을 확인할 수 있습니다.
하지만 K=10인 경우 테스트셋의 점수가 가장 낮습니다. 앞서 공부한대로 overfitting된 것으로 보입니다.
K=25인 경우에도 점수가 낮은 것을 볼 수 있습니다. 이는 underfitting되었다고 생각할 수 있습니다.
C. 가중치 영향
weights = ["uniform", "distance"]
for w in weights:
clf = KNeighborsClassifier(
n_neighbors=20,
weights=w,
metric="minkowski",
p=2,
n_jobs=4
)
pipeline = Pipeline(
[('scaler', StandardScaler()), ('model', clf)]
)
pipeline.fit(train_x, train_y)
print(f"weights : {w} / score : {pipeline.score(test_x, test_y)}")
'''
weights : uniform / score : 0.9649122807017544
weights : distance / score : 0.9736842105263158
'''
가중치를 부여한 경우가 점수가 더 높은 것을 확인할 수 있었습니다.
D. 거리 측정 방식의 영향
metric = ["euclidean", "manhattan", "cosine"]
for m in metric:
clf = KNeighborsClassifier(
n_neighbors=20,
weights="distance",
metric=m,
n_jobs=4
)
pipeline = Pipeline(
[('scaler', StandardScaler()), ('model', clf)]
)
pipeline.fit(train_x, train_y)
print(f"metric : {m} / score : {pipeline.score(test_x, test_y)}")
'''
metric : euclidean / score : 0.9736842105263158
metric : manhattan / score : 0.9736842105263158
metric : cosine / score : 0.9649122807017544
'''
실혐 결과 'cosine distance'의 성능이 낮게 나타났고, 'euclidean'과 'manhattan'은 동일한 점수를 받았습니다.
이런 경향은 30개의 feature 중에 특정 feature만 취사선택하거나 이웃 수 K를 조정하면 다르게 나타날 수 있습니다. 다양하게 실험해보면서 결과를 분석해 보는 것이 좋습니다.
예를 들어 K가 작아질 경우, 참고하는 이웃의 수가 줄어드는만큼 거리 측정 방식에 더 민감하게 반응하여 차이가 크게 나타날 수 있습니다.
2. Decision Tree : https://all-the-meaning.tistory.com/69
3. Random Forest : https://all-the-meaning.tistory.com/70
4. Linear/Logistic Regression : https://all-the-meaning.tistory.com/71
5. SVM : https://all-the-meaning.tistory.com/72
6. 머신러닝 총정리 : https://all-the-meaning.tistory.com/73