[데이터 분석] 13. 최근접 이웃(k Nearest Neighbor)

1. 분류
본격적으로 분류와 관련된 알고리즘을 알아보기에 앞서 분류라는 것에 대한 정의를 간략하게 짚고 넘어가고자 한다. 분류란 새롭게 나타난 현상에 대해서 기존이 나눠둔, 혹은 정의된 집합에 배정하는 것을 의미한다. 주로 반응변수(종속변수)가 알려진 다변량 자료를 이용해 모형을 구축하고 이를 통해 새로운 자료에 대한 예측 및 분류를 수행하는 것이 목적이다. 반응 변수가 범주형인 경우에는 분류, 연속형인 경우에는 예측이라 한다. 대표적인 알고리즘으로는 앞쪽에서 살펴본 로지스틱 회귀 부터, 의사결정나무, 서포트벡터, 랜덤 포레스트 등이 있다.
로지스틱 회귀의 경우 회귀 부분에서 언급했기 때문에 이번에는 넘어가도록 하겠다.
2. kNN(k Nearest Neighbor)
이번 장에서는 분류와 관련된 머신러닝 알고리즘들 중에서 가장 단순한 모델인 kNN(k Nearest Neighbor, 최근접 이웃)에 대해서 다뤄볼 것이다. 이름에서도 알 수 있듯이, kNN 은 임의의 데이터를 기준으로 주변에 분포하는 이웃 데이터 k 개의 속성을 참조해 데이터를 분류 예측하는 방식을 사용한다.
2.1 kNN의 특징
다음으로 kNN 알고리즘의 장단점, 동작 방식 등을 통해 kNN 이 갖는 특징에 대해 살펴보도록 하자. 앞서 간략하게 이야기 했지만, kNN 알고리즘은 주변에 분포하는 데이터들의 속성을 이용하며, 만약 새로운 데이터가 들어오면, 해당 데이터를 중심으로 주변 데이터의 레이블(혹은 클래스) 의 빈도를 측정해 가장 빈도가 높은 레이블로 분류하는 방식이다. 때문에 알고리즘이 다른 분류 모델들에 비해 동작 원리가 단순하고, 간단하기에 훈련 속도가 빠른 편에 속하고, 적은 데이터를 사용해도 분류 정확도가 높다는 장점을 가진다.
하지만, 만약 주변에 위치한 데이터가 이상치이거나, 노이즈가 심한 경우라면 분류 정확도에 영향을 줄 수 있고, 모든 데이터에 대해 기억을 해야되기 때문에 데이터가 많을 수록 메모리 사용률이 높으며, 계산복잡도가 높아질 수 있다는 단점이 존재한다.
위와 같은 이유로, 보통은 적은 데이터를 이용하여 분류 예측하는 경우에 효과적이며, 사용하기에 따라 데이터 내의 이상치 유무나 노이즈 존재를 파악하는 용도로도 활용할 수 있다.

또 다른 특징으로는 거리 기반의 분류모델이라는 점이다. 위의 그림을 통해서 알 수 있듯이, 이웃의 개수를 파악하는 방법은 특정 갯수별 원이 그려진 것을 알 수 있다. 즉, 중심이 되는 데이터로부터 다른 데이터들 간의 거리를 측정해 k 개에 맞는 데이터를 파악하는 것이라고 할 수 있다. 이처럼 거리를 기반으로 하는 알고리즘 이기 때문에 상대적으로 짧은 거리에 있는 데이터를 이웃으로 판단하며, 이들의 레이블 빈도를 파악해 가장 빈도가 높은 레이블(클래스)로 분류하는 알고리즘이다.
2.2 거리 측정 방법
그렇다면 거리를 측정을 하기 위한 방식들을 알아보도록 하자. 거리를 측정하는 방식에는 여러가지 방법론 및 함수들이 존재하지만, 이번 장에서는 가장 많이 사용되는 측정 방법 4가지에 대해서 다뤄볼 예정이다.
2.2.1 유클리드 거리
첫 번째로 알아볼 거리측정법은 우리에게 가장 친숙한 방법인 유클리드 거리(Euclidean Distance) 이다. 주로 두 점 사이의 거리를 측정할 때 사용하는 방법으로 수식으로 표현하면 다음과 같다.
$$d(p, q) = d(q, p) = \sqrt{(q_1 - p_1)^2 + (q_2 - p_2)^2 + ... + (q_n - p_n)^2}$$
$$ = \sqrt{\sum_{i=1}{n}(q_i - p_i)^2}$$

2.2.2 멘하튼 거리
두 번째로 알아볼 방법은 멘하튼 거리(Manhatten Distance) 이다. 한 지점에서 다른 지점으로 이동할 때, x 축과 y 축을 아래 그림과 같이 격자에 맞춰서 이동하는 거리를 측정하는 방식이며, 어떤 경로로 이동하든 결과적으로는 동일하다는 것을 알 수 있다. 수식으로 표현하자면, 다음과 같이 할 수 있다.
$$d(X, Y) = \sum_{i=1}{n}|x_i - y_i|$$

2.2.3 마할라노비스 거리
다음으로는 마할라노비스 거리(Mahalanobis Distance) 인데, 각 변수의 분산과 공분산을 고려하여 상관관계를 고려해 거리를 측정하는 방식이다. 주로 이상탐지를 하는 경우에 활용된다. 수식으로 표현하면 다음과 같이 나타낼 수 있다.
$$d(X, Y) = \sqrt{(\vec{X} - \vec{Y})^T \sum{}{-1}(\vec{X} - \vec{Y})}$$
2.2.4 해밍 거리
마지막으로 해밍 거리(Hamming Distance) 에 대해 살펴보도록 하자. 이는 같은 크기를 갖는 데이터들을 놓고, 같은 위치에 있는 값들끼리 비교해 서로 다른 값이 있을 경우 1씩 증가시킨다. 주로 유사도를 측정할 때 활용되며, 위에서 말한 같은 위치라 함은 아래 예시에서처럼 순서상의 위치를 의미한다.
ex1. '1011101'과 '1001001'사이의 해밍 거리는 2이다. (1011101, 1001001)
ex2. '2143896'과 '2233796'사이의 해밍 거리는 3이다. (2143896, 2233796)
ex3. "toned"와 "roses"사이의 해밍 거리는 3이다. (toned, roses)
3. 실습: 유방암 진찰 데이터
마지막으로 유방암 진찰 데이터를 통해 kNN 알고리즘으로 분류하는 실습을 해보자. 코드는 Python, R 순서로 배치하니, 참고하기 바란다. 시작에 앞서, 이번 실습에서 사용되는 유방암 진찰 데이터는 아래 링크에서 다운로드 받을 수 있으며, 데이터 셋의 구성은 다음과 같다.
https://archive.ics.uci.edu/dataset/17/breast+cancer+wisconsin+diagnostic
UCI Machine Learning Repository
This dataset is licensed under a Creative Commons Attribution 4.0 International (CC BY 4.0) license. This allows for the sharing and adaptation of the datasets for any purpose, provided that the appropriate credit is given.
archive.ics.uci.edu
변수명 | 설명 |
ID | 식별자 |
Diagnosis | 진단여부 (양성: B / 악성: M) |
radius | 반지름 |
texture | 텍스처 |
perimeter | 둘레 |
area | 면적 |
smoothness | 평활도 |
compactness | 다짐도 |
concavity | 요면 |
concave points | 요면점 |
symmetry | 대칭 |
fractal dimension | 프렉탈 차원 |
먼저 실습에 필요한 데이터를 불러오고, 모델을 사용하기 전에 필요한 준비(데이터 전처리, 학습/테스트 데이터 분리 등)를 진행해보자.
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
import scipy.stats as stats
# 1. 사용 데이터 로드
cancer_data = pd.read_csv("~/workspace/Python3/DataAnalysis/Dataset/breast_cancer/wdbc.data", header=None)
cancer = cancer_data.iloc[:, 0:12]
cancer.columns = ["id", "diagnosis", "radius", "texture", "perimeter", \
"area", "smoothness", "compactness", "concavity", \
"concave_points", "symmetry", "fractal_dimension"]
print(cancer['diagnosis'].value_counts())
# 2. 원본 전처리
cancer['diagnosis'] = cancer['diagnosis'].map({'B': 0, 'M': 1})
print(round(cancer['diagnosis'].value_counts(normalize=True) * 100, 1))
print(cancer.describe())
[실행결과]
diagnosis
B 357
M 212
Name: count, dtype: int64
diagnosis
0 62.7
1 37.3
Name: proportion, dtype: float64
id diagnosis ... symmetry fractal_dimension
count 5.690000e+02 569.000000 ... 569.000000 569.000000
mean 3.037183e+07 0.372583 ... 0.181162 0.062798
std 1.250206e+08 0.483918 ... 0.027414 0.007060
min 8.670000e+03 0.000000 ... 0.106000 0.049960
25% 8.692180e+05 0.000000 ... 0.161900 0.057700
50% 9.060240e+05 0.000000 ... 0.179200 0.061540
75% 8.813129e+06 1.000000 ... 0.195700 0.066120
max 9.113205e+08 1.000000 ... 0.304000 0.097440
[8 rows x 12 columns]
setwd("D:/workspace/R/workspace")
cancer_data <- read.csv("Data/breast_cancer/wdbc.data", header=FALSE, sep=",", stringsAsFactors=FALSE)
cancer <- cancer_data[, 1:12]
colnames(cancer) <- c("id", "diagnosis", "radius", "texture", "perimeter",
"area", "smoothness", "compactness", "concavity",
"concave_points", "symmetry", "fractal_dimension"
)
table(cancer$diagnosis)
cancer$diagnosis <- factor(cancer$diagnosis,
levels = c("B","M"),
labels = c("Benign", "Malignant")
)
round(prop.table(table(cancer$diagnosis)) * 100, digits = 1)
summary(cancer)
[실행결과]
B M
357 212
Benign Malignant
62.7 37.3
id diagnosis radius texture perimeter
Min. : 8670 Benign :357 Min. : 6.981 Min. : 9.71 Min. : 43.79
1st Qu.: 869218 Malignant:212 1st Qu.:11.700 1st Qu.:16.17 1st Qu.: 75.17
Median : 906024 Median :13.370 Median :18.84 Median : 86.24
Mean : 30371831 Mean :14.127 Mean :19.29 Mean : 91.97
3rd Qu.: 8813129 3rd Qu.:15.780 3rd Qu.:21.80 3rd Qu.:104.10
Max. :911320502 Max. :28.110 Max. :39.28 Max. :188.50
area smoothness compactness concavity concave_points
Min. : 143.5 Min. :0.05263 Min. :0.01938 Min. :0.00000 Min. :0.00000
1st Qu.: 420.3 1st Qu.:0.08637 1st Qu.:0.06492 1st Qu.:0.02956 1st Qu.:0.02031
Median : 551.1 Median :0.09587 Median :0.09263 Median :0.06154 Median :0.03350
Mean : 654.9 Mean :0.09636 Mean :0.10434 Mean :0.08880 Mean :0.04892
3rd Qu.: 782.7 3rd Qu.:0.10530 3rd Qu.:0.13040 3rd Qu.:0.13070 3rd Qu.:0.07400
Max. :2501.0 Max. :0.16340 Max. :0.34540 Max. :0.42680 Max. :0.20120
symmetry fractal_dimension
Min. :0.1060 Min. :0.04996
1st Qu.:0.1619 1st Qu.:0.05770
Median :0.1792 Median :0.06154
Mean :0.1812 Mean :0.06280
3rd Qu.:0.1957 3rd Qu.:0.06612
Max. :0.3040 Max. :0.09744
위의 코드를 실행하면 알 수 있듯이, 각 변수별로 측정범위가 다르기 때문에 값을 재조정하여 일정 범위의 값으로 변경해준다. 다음으로 모델이 원활히 학습이 진행되도록 학습 데이터와 테스트 데이터를 분할한다. 비율은 약 8:2 정도로 설정했다. 분류결과 역시 동일한 비율로 학습용과 테스트용으로 분류한다.
# 데이터셋 분리
cancer_data = cancer[["id", "radius", "texture", "perimeter", "area", "smoothness", "compactness", "concavity", "concave_points", "symmetry", "fractal_dimension"]]
cancer_label = cancer[["diagnosis"]]
x_train, x_test, y_train, y_test = train_test_split(cancer_data, cancer_label, test_size=0.2, random_state=1234)
train <- cancer[1:469, c(1, 3:12)]
test <- cancer[470:569, c(1, 3:12)]
train_label <- cancer[1:469, 2]
test_label <- cancer[470:569, 2]
데이터 분할까지 완료했으니, 이제 모델링을 진행해보도록 하자. R의 경우에는 class 패키지에 포함되어있는 knn() 함수를 사용하면 되고, 매개변수는 아래 설명과 같다.
model <- knn(train, test, class, k)
* train : 수치형 데이터로 구성된 학습용 데이터 셋
* test : 수치형 데이터로 구성된 테스트용 데이터 셋
* class : 훈련 데이터의 각 행에 대응되는 범주형 데이터로 구성된 라벨
* k : 최근접 이웃의 개수 , 정수형으로 명시
- 선정 : k^2 이 학습 데이터의 행의 개수에 근접하도록 설정한다.
위의 데이터로 모델을 학습하는 과정은 다음과 같다.
knn = KNeighborsClassifier(n_neighbors=21)
knn.fit(x_train, y_train)
y_pred = knn.predict(x_test)
print(y_pred)
[실행결과]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
install.packages("class")
library(class)
y_pred <- knn(train = train, test = test, cl = train_label, k = 21)
print(y_pred)
[실행결과]
[1] Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign
[14] Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign
[27] Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign
[40] Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign
[53] Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign
[66] Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign
[79] Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign Benign
[92] Benign Benign Benign Benign Benign Benign Benign Benign Benign
Levels: Benign Malignant
다음으로 생성한 분류 모델에 대한 성능평가방법은 여러가지가 있는데 이번 장에서는 교차표(CrossTable)을 사용한 성능평가를 진행해볼 것이다. R의 경우에는 gmodels 패키지에 있는 CrossTable() 함수를 사용하면 되며, 사용방법은 아래 예시와 같이 사용하면 된다. 참고로 교차표에 관한 내용은 추후 모델 성능평가와 관련된 내용에서 설명할 예정이니, 이번 장에서는 간단하게 이런 기법이 있다 정도로만 이해하면 될 것이다.
conf_matrix = confusion_matrix(y_test, y_pred)
print(conf_matrix)
[실행결과]
[[65 4]
[37 8]]
install.packages("gmodels")
library(gmodels)
CrossTable(x = test_label, y = test_pred, prop.chisq = F)
[실행결과]
Cell Contents
|-------------------------|
| N |
| N / Table Total |
|-------------------------|
Total Observations in Table: 100
| y_pred
test_label | Benign | Row Total |
-------------|-----------|-----------|
Benign | 77 | 77 |
| 0.770 | |
-------------|-----------|-----------|
Malignant | 23 | 23 |
| 0.230 | |
-------------|-----------|-----------|
Column Total | 100 | 100 |
-------------|-----------|-----------|
코드를 실행하면 아래 그림과 유사한 형태의 결과가 나올 것이다. 뒤에서 자세히 설명하겠지만, 현재 우리가 사용 중인 분류는 이진분류에 해당하며, 클래스가 2개이기 때문에 교차표에서 모델이 정확하게 분류한 부분은 TP(True Positive), TN(True Negative) 에 해당한다. 이를 그림으로 표현한 것이 아래의 그림이다.

위의 내용을 통해 결과를 해석해보자면, 전체 100개 중 77개를 정확히 골라냈다는 것까지 확인할 수 있다. 추가적으로 위의 실행결과는 1개 클래스로만 분류했다는 것을 통해 학습이 원활히 진행되지 않았다는 것도 알 수 있다.
끝으로 모델의 개선방법에 대해서 살펴보고 마무리 하겠다. 위의 모델이 77%의 정확도를 갖는다고 할 수 있지만, 1개 클래스로만 분류가 되었으며, 실무에서 위와 같은 상황 나오면 안된다. 그렇다면 모델에 변화를 주거나 데이터에 변화를 주는 것이 일반적이며, 먼저 데이터에 대한 변화를 살펴보려고 한다. 데이터에 대해 변화를 주는 경우는 데이터의 크기나 전처리를 추가로 하는 방법 등이 있으며, 이번 장에서는 Z-score 표준화라는 작업을 진행할 것이다. Z-score 는 표준화된 값은 최소와 최대를 미리 정하지 않았기 때문에 극단적으로 값이 중앙에 모이지 않으며, 때문에 거리 계산에서 이상치에 좀 더 큰 가중치를 주는 것이 합리적이라고 판단한다.
# 성능개선
# 1. 데이터 전처리 추가
scaler = StandardScaler()
cancer_z = scaler.fit_transform(cancer[['radius', 'texture', 'perimeter', 'area', 'smoothness', 'compactness', 'concavity', 'concave_points', 'symmetry', 'fractal_dimension']])
cancer_z = pd.DataFrame(cancer_z, columns=['radius', 'texture', 'perimeter', 'area', 'smoothness', 'compactness', 'concavity', 'concave_points', 'symmetry', 'fractal_dimension'])
cancer_z = pd.concat([cancer["id"], cancer_z], axis=1)
x_train, x_test, y_train, y_test = train_test_split(cancer_z, cancer_label, test_size=0.2, random_state=1234)
knn.fit(x_train, y_train)
y_pred = knn.predict(x_test)
print(confusion_matrix(y_test, y_pred))
[실행결과]
[[65 4]
[37 8]]
cancer_z <- as.data.frame(scale(cancer[, c(1, 3:12)], center=TRUE, scale=TRUE))
summary(cancer_z)
train_z <- cancer_z[1:469,]
test_z <- cancer_z[470:569,]
test_z_pred <- knn(train_z, test_z, cl = train_label, k=21)
CrossTable(x = test_label, y = test_z_pred, prop.chisq = F)
[실행결과]
id radius texture perimeter area
Min. :-0.2429 Min. :-2.0279 Min. :-2.2273 Min. :-1.9828 Min. :-1.4532
1st Qu.:-0.2360 1st Qu.:-0.6888 1st Qu.:-0.7253 1st Qu.:-0.6913 1st Qu.:-0.6666
Median :-0.2357 Median :-0.2149 Median :-0.1045 Median :-0.2358 Median :-0.2949
Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000
3rd Qu.:-0.1724 3rd Qu.: 0.4690 3rd Qu.: 0.5837 3rd Qu.: 0.4992 3rd Qu.: 0.3632
Max. : 7.0464 Max. : 3.9678 Max. : 4.6478 Max. : 3.9726 Max. : 5.2459
smoothness compactness concavity concave_points symmetry
Min. :-3.10935 Min. :-1.6087 Min. :-1.1139 Min. :-1.2607 Min. :-2.74171
1st Qu.:-0.71034 1st Qu.:-0.7464 1st Qu.:-0.7431 1st Qu.:-0.7373 1st Qu.:-0.70262
Median :-0.03486 Median :-0.2217 Median :-0.3419 Median :-0.3974 Median :-0.07156
Mean : 0.00000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.00000
3rd Qu.: 0.63564 3rd Qu.: 0.4934 3rd Qu.: 0.5256 3rd Qu.: 0.6464 3rd Qu.: 0.53031
Max. : 4.76672 Max. : 4.5644 Max. : 4.2399 Max. : 3.9245 Max. : 4.48081
fractal_dimension
Min. :-1.8183
1st Qu.:-0.7220
Median :-0.1781
Mean : 0.0000
3rd Qu.: 0.4706
Max. : 4.9066
Cell Contents
|-------------------------|
| N |
| N / Row Total |
| N / Col Total |
| N / Table Total |
|-------------------------|
Total Observations in Table: 100
| test_z_pred
test_label | Benign | Malignant | Row Total |
-------------|-----------|-----------|-----------|
Benign | 71 | 6 | 77 |
| 0.922 | 0.078 | 0.770 |
| 0.986 | 0.214 | |
| 0.710 | 0.060 | |
-------------|-----------|-----------|-----------|
Malignant | 1 | 22 | 23 |
| 0.043 | 0.957 | 0.230 |
| 0.014 | 0.786 | |
| 0.010 | 0.220 | |
-------------|-----------|-----------|-----------|
Column Total | 72 | 28 | 100 |
| 0.720 | 0.280 | |
-------------|-----------|-----------|-----------|
데이터에 대한 변화를 줌으로써, 실제 결과에서 나타나듯이, 93%로 정확도가 더 증가했다는 것을 확인할 수 있다. 이처럼 데이터를 변화시켰음에도 성능 개선이 안된다면, 모델의 파라미터를 적절한 값으로 변경해주는 작업이 필요하다. kNN 의 경우 모델을 생성하는 과정에서 가장 중요한 값인 k 값을 설정할 수 있고, 이 때 최적의 k 값을 찾아내는 것이 중요하다. 일반적으로는 적절한 k 값이라고 평가되는 범위는 3 ~10 사이의 값으로 지정된다고 한다.
아래 예시에서는 1, 5, 11, 15, 21, 27 일 경우에 각각의 모델에 대한 성능을 표시한 것이다.
k=1
[[49 20]
[27 18]]
k=5
[[58 11]
[34 11]]
k=11
[[59 10]
[34 11]]
k=15
[[63 6]
[36 9]]
k=21
[[65 4]
[37 8]]
k=27
[[65 4]
[37 8]]
# 모델 내 파라미터(K 값) 변경
# k = 1
Total Observations in Table: 100
| y_pred
test_label | Benign | Malignant | Row Total |
-------------|-----------|-----------|-----------|
Benign | 69 | 8 | 77 |
| 0.896 | 0.104 | 0.770 |
| 0.986 | 0.267 | |
| 0.690 | 0.080 | |
-------------|-----------|-----------|-----------|
Malignant | 1 | 22 | 23 |
| 0.043 | 0.957 | 0.230 |
| 0.014 | 0.733 | |
| 0.010 | 0.220 | |
-------------|-----------|-----------|-----------|
Column Total | 70 | 30 | 100 |
| 0.700 | 0.300 | |
-------------|-----------|-----------|-----------|
# k = 5
| y_pred
test_label | Benign | Malignant | Row Total |
-------------|-----------|-----------|-----------|
Benign | 70 | 7 | 77 |
| 0.909 | 0.091 | 0.770 |
| 1.000 | 0.233 | |
| 0.700 | 0.070 | |
-------------|-----------|-----------|-----------|
Malignant | 0 | 23 | 23 |
| 0.000 | 1.000 | 0.230 |
| 0.000 | 0.767 | |
| 0.000 | 0.230 | |
-------------|-----------|-----------|-----------|
Column Total | 70 | 30 | 100 |
| 0.700 | 0.300 | |
-------------|-----------|-----------|-----------|
# k = 11
| y_pred
test_label | Benign | Malignant | Row Total |
-------------|-----------|-----------|-----------|
Benign | 69 | 8 | 77 |
| 0.896 | 0.104 | 0.770 |
| 0.986 | 0.267 | |
| 0.690 | 0.080 | |
-------------|-----------|-----------|-----------|
Malignant | 1 | 22 | 23 |
| 0.043 | 0.957 | 0.230 |
| 0.014 | 0.733 | |
| 0.010 | 0.220 | |
-------------|-----------|-----------|-----------|
Column Total | 70 | 30 | 100 |
| 0.700 | 0.300 | |
-------------|-----------|-----------|-----------|
# k = 15
| y_pred
test_label | Benign | Malignant | Row Total |
-------------|-----------|-----------|-----------|
Benign | 72 | 5 | 77 |
| 0.935 | 0.065 | 0.770 |
| 0.986 | 0.185 | |
| 0.720 | 0.050 | |
-------------|-----------|-----------|-----------|
Malignant | 1 | 22 | 23 |
| 0.043 | 0.957 | 0.230 |
| 0.014 | 0.815 | |
| 0.010 | 0.220 | |
-------------|-----------|-----------|-----------|
Column Total | 73 | 27 | 100 |
| 0.730 | 0.270 | |
-------------|-----------|-----------|-----------|
# k = 21
| y_pred
test_label | Benign | Malignant | Row Total |
-------------|-----------|-----------|-----------|
Benign | 71 | 6 | 77 |
| 0.922 | 0.078 | 0.770 |
| 0.986 | 0.214 | |
| 0.710 | 0.060 | |
-------------|-----------|-----------|-----------|
Malignant | 1 | 22 | 23 |
| 0.043 | 0.957 | 0.230 |
| 0.014 | 0.786 | |
| 0.010 | 0.220 | |
-------------|-----------|-----------|-----------|
Column Total | 72 | 28 | 100 |
| 0.720 | 0.280 | |
-------------|-----------|-----------|-----------|
# k = 27
| y_pred
test_label | Benign | Malignant | Row Total |
-------------|-----------|-----------|-----------|
Benign | 73 | 4 | 77 |
| 0.948 | 0.052 | 0.770 |
| 0.973 | 0.160 | |
| 0.730 | 0.040 | |
-------------|-----------|-----------|-----------|
Malignant | 2 | 21 | 23 |
| 0.087 | 0.913 | 0.230 |
| 0.027 | 0.840 | |
| 0.020 | 0.210 | |
-------------|-----------|-----------|-----------|
Column Total | 75 | 25 | 100 |
| 0.750 | 0.250 | |
-------------|-----------|-----------|-----------|
위의 결과를 확인해보면, 실험한 모델들 중 k=15 인 경우와 k=27 인 경우에 분류 정확도가 높은 것을 확인할 수 있다. 전체적인 숫자는 동일하지만 세부적으로 비교해보자면, 일부 분류하기 어려운 데이터가 있다는 것도 확인이 가능하다.