Algoritma K-Nearest Neighbors (KNN) dengan R

Cahya Alkahfi | sainsdata.id

Konsep K-Nearest Neighbors (KNN)

K-Nearest Neighbors (KNN) merupakan metode populer dalam bidang machine learning. Konsep dasar algoritma KNN adalah mencari k-nearest neighbors atau k tetangga terdekat dari suatu observasi berdasarkan jarak euclidean ataupun ukuran jarak lainnya. KNN umumnya digunakan untuk pemodelan klasifikasi namun dapat digunakan juga pada pemodelan regresi.

Dalam model klasifikasi, algoritma KNN menghitung jarak antara amatan yang akan diklasifikasikan dengan dataset yang sudah memiliki label kelas, dan memilih k tetangga terdekat dengan amatan tersebut berdasarkan jarak tersebut. Setelah itu, dari k tetangga tersebut, dilihat kelas apa yang paling banyak (mayoritas) menjadi tetangga terdekatnya. Hasil akhir yaitu mengklasifikasikan amatan tersebut masuk ke dalam kelas mayoritas yang diperoleh.

Prinsip Dasar dan Proses KNN

  • Jarak: KNN mengukur kesamaan antara data dengan menghitung jarak antara titik data dalam ruang fitur. Jarak ini dapat diukur menggunakan berbagai metrik, seperti Euclidean, Manhattan, atau Minkowski distance.

  • K-Neighbors: “K” dalam KNN menunjukkan jumlah tetangga terdekat yang akan digunakan untuk membuat prediksi. Algoritma ini memeriksa label k tetangga terdekat suatu titik data dan menentukan label mayoritas sebagai prediksi.

Langkah-Langkah KNN

  1. Menentukan Parameter k: Pemilihan nilai k adalah bagian kritis dari algoritma KNN. Nilai k yang terlalu kecil dapat menyebabkan model rentan terhadap noise, sedangkan nilai k yang besar dapat mengaburkan batas keputusan yang sebenarnya.

  2. Menghitung Jarak: Algoritma menghitung jarak antara titik data yang akan diprediksi dengan semua titik data dalam set pelatihan.

  3. Menentukan Tetangga Terdekat: Algoritma memilih k tetangga terdekat berdasarkan jarak yang dihitung sebelumnya.

  4. Menentukan Kelas Prediksi: kelas mayoritas dari tetangga terdekat digunakan sebagai prediksi untuk titik data yang dipertanyakan.

Ilustrasi K Nearest Neighbors (KNN)

Ilustrasi di atas menunjukkan data historis mengenai karakteristik anak ayam yang baru menetas menurut dua kriteria yaitu tinggi dan bobot. Warna biru menunjukkan anak ayam betina dan orange anak ayam jantan. Secara visual dapat dilihat bahwa kecenderungan anak ayam jantan memiliki bobot dan tinggi yang lebih besar dibandingkan anak ayam betina. Pertanyaannya adalah jika kita memiliki 3 anak ayam yang baru menetas (misal amatan denganw arna merah), dan belum diketahui jenis kelaminnya bagaimana kita dapat memprediksi jenis kelamin dari ketiga anak ayam tersebut.

Penentuan kelas prediksi pada algoritma KNN diukur dari kedekatan jarak data baru tersebut dengan tetangga terdekatnya. Sebagai contoh, berdasarkan k=3 tetangga terdekat, anak ayam a kemungkinan akan diprediksi sebagai pejantan karena tiga tetangga terdekatnya adalah pejantan. Begitu pula anak ayam b akan diprediksi sebagai betina. Untuk anak ayam c, 2 dari 3 tetangga terdekatnya adalah pejantan maka akan diprediksi sebagai pejantan.

Untuk memperoleh model KNN yang baik, parameter penting yang perlu ditentukan adalah nilai k, atau jumlah tetangga terdekat yang akan digunakan. Nilai k tentu saja akan berbeda tergantung dari permasalahan dan data yang dihadapi. Penentuan nilai k optimal ini dapat dilakukan melalui proses tuning hyperparameter.

Penyiapan Data

Dataset yang digunakan memiliki format csv sehingga kita dapat membacanya menggunakan fungsi read.csv. Peubah respon atau kelas pada dataset tersimpan pada kolom quality dengan nilai LOW atau HIGH. Sementara kolom lainnya seluruhnya bertipe numerik.

data <- read.csv("https://raw.githubusercontent.com/sainsdataid/dataset/main/wine-quality-binary.csv")

str(data)
## 'data.frame':    1143 obs. of  13 variables:
##  $ id                  : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ fixed.acidity       : num  7.4 7.8 7.8 11.2 7.4 7.4 7.9 7.3 7.8 6.7 ...
##  $ volatile.acidity    : num  0.7 0.88 0.76 0.28 0.7 0.66 0.6 0.65 0.58 0.58 ...
##  $ citric.acid         : num  0 0 0.04 0.56 0 0 0.06 0 0.02 0.08 ...
##  $ residual.sugar      : num  1.9 2.6 2.3 1.9 1.9 1.8 1.6 1.2 2 1.8 ...
##  $ chlorides           : num  0.076 0.098 0.092 0.075 0.076 0.075 0.069 0.065 0.073 0.097 ...
##  $ free.sulfur.dioxide : num  11 25 15 17 11 13 15 15 9 15 ...
##  $ total.sulfur.dioxide: num  34 67 54 60 34 40 59 21 18 65 ...
##  $ density             : num  0.998 0.997 0.997 0.998 0.998 ...
##  $ pH                  : num  3.51 3.2 3.26 3.16 3.51 3.51 3.3 3.39 3.36 3.28 ...
##  $ sulphates           : num  0.56 0.68 0.65 0.58 0.56 0.56 0.46 0.47 0.57 0.54 ...
##  $ alcohol             : num  9.4 9.8 9.8 9.8 9.4 9.4 9.4 10 9.5 9.2 ...
##  $ quality             : chr  "LOW" "LOW" "LOW" "HIGH" ...

Dari struktur di atas dapat pula kita lihat terdapat kolom id. kolom ini hanya berisi urutan data dari 1, 2… sampai 1143 sesuai banyaknya data dan bukanlah peubah yang berkaitan dengan kelas data sehingga bisa kita hapus saja. Kita juga sebaiknya merubah tipe dari peubah respon menjadi factor. Hal ini nantinya dapat mempermudah pengukuran pada proses-proses selanjutnya.

Pada kode di bawah ini, kita juga menampilkan banyaknya data serta komposisinya menurut peubah quality. Dari total 1143 observasi, terdapat 621 yang masuk sebagai kelas "HIGH" dan 522 sebagai kelas "LOW". Berdasarkan proporsinya kedua kelas relatif seimbang yaitu dengan komposisi sekitar 54% data dengan kelas "HIGH" berbanding 46% data dengan kelas "LOW".

# menghapus kolom `id`
data$id <- NULL

# mengubah tipe kolom `quality` menjadi factor
data$quality <- as.factor(data$quality)

# melihat jumlah dan komposisi masing-masing kategori
quality <- data.frame(table(data$quality))

quality$Prop <- round(quality$Freq/sum(quality$Freq), 3)

print(quality)
##   Var1 Freq  Prop
## 1 HIGH  621 0.543
## 2  LOW  522 0.457

Pembagian Data Latih dan Data Uji

Membagi data menjadi data latih dan data uji merupakan tahapan penting dalam algoritma machine learning termasuk KNN. Pembagian ini diperlukan agar algoritma dapat diuji menggunakan data yang belum pernah dilihat selama proses pelatihan. Pada saat pelatihan, data yang digunakan hanya data latih saja. Adapun data uji nantinya digunakan untuk mengukur performa algoritma. Hal ini penting agar pengukuran performa lebih “fair” karena menggunakan data baru yang belum pernah digunakan untuk proses pelatihan.

Pembagian data pada bahasa R dapat dilakukan menggunakan fungsi createDataPartition dari pustaka caret. Proporsi banyaknya data untuk pelatihan dan pengujian dapat ditentukan dengan mengatur parameter p. Pada contoh ini, kita akan menggunakan 70% dataset sebagai data latih dan 30% sisanya menjadi data uji.

# Membagi data sebagai data Latih dan data uji

library(caret)
## Warning: package 'caret' was built under R version 4.4.1
## Loading required package: ggplot2
## Loading required package: lattice
set.seed(100)  # mengatur seed tertentu untuk hasil yang dapat direproduksi

# data latih = 70%, data uji = 30%
trainIndex <- createDataPartition(data$quality, p = 0.70, list = FALSE, times = 1)

# Buat data latih dan data uji berdasarkan indeks yang dihasilkan
data.train <- data[trainIndex, ]
data.test <- data[-trainIndex, ]

# melihat komposisi setiap kelas pada data train dan test
cbind("train" = table(data.train$quality), "test" = table(data.test$quality))
##      train test
## HIGH   435  186
## LOW    366  156

Features Scaling

KNN merupakan model berbasis jarak sehingga ukuran jarak merupakan hal penting. Perbedaan skala data antar fitur akan mempengaruhi performa model yang dihasilkan. Untuk menghindari hal tersebut, disarankan untuk selalu melakukan scaling terhadap fitur, khususnya ketika perbedaan skala sangat besar. Metode scaling yang dapat digunakan salah satunya adalah normalisasi (MinMax Scaling), yaitu membuat setiap fitur memiliki nilai minimum 0 dan maksimum 1.

Proses scaling pada R dapat menggunakan fungsi preProcess dari pustaka caret. Parameter method="range" menunjukkan scaling dengan metode normalisasi. Dapat dilihat dari output di bawah ini, bahwa fitur hasil scaling seluruhnya memiliki nilai minimum 0 dan maksimum 1.

# Menerapkan scaling menggunakan preProcess untuk semua fitur numerik
preproc.params <- preProcess(data.train[, -12], method = "range")

scaled.data.train <- predict(preproc.params, data.train[, -12])
scaled.data.test <- predict(preproc.params, data.test[, -12])

summary(scaled.data.train)
##  fixed.acidity    volatile.acidity  citric.acid     residual.sugar   
##  Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.00000  
##  1st Qu.:0.2212   1st Qu.:0.2231   1st Qu.:0.1139   1st Qu.:0.07752  
##  Median :0.3009   Median :0.3306   Median :0.3165   Median :0.10078  
##  Mean   :0.3318   Mean   :0.3377   Mean   :0.3445   Mean   :0.12456  
##  3rd Qu.:0.3982   3rd Qu.:0.4298   3rd Qu.:0.5443   3rd Qu.:0.13178  
##  Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.00000  
##    chlorides       free.sulfur.dioxide total.sulfur.dioxide    density      
##  Min.   :0.00000   Min.   :0.0000      Min.   :0.0000       Min.   :0.0000  
##  1st Qu.:0.08488   1st Qu.:0.1154      1st Qu.:0.0530       1st Qu.:0.4212  
##  Median :0.10875   Median :0.2308      Median :0.1166       Median :0.5034  
##  Mean   :0.12409   Mean   :0.2780      Mean   :0.1406       Mean   :0.5092  
##  3rd Qu.:0.13793   3rd Qu.:0.3846      3rd Qu.:0.1908       3rd Qu.:0.5933  
##  Max.   :1.00000   Max.   :1.0000      Max.   :1.0000       Max.   :1.0000  
##        pH           sulphates         alcohol      
##  Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
##  1st Qu.:0.2920   1st Qu.:0.1026   1st Qu.:0.1719  
##  Median :0.3805   Median :0.1474   Median :0.2656  
##  Mean   :0.3841   Mean   :0.1712   Mean   :0.3093  
##  3rd Qu.:0.4690   3rd Qu.:0.2115   3rd Qu.:0.4219  
##  Max.   :1.0000   Max.   :1.0000   Max.   :1.0000

KNN dengan paket ‘class’

Untuk membuat prediksi berdasarkan algoritma KNN kita dapat menggunakan fungsi knn dari pustaka class. Fungsi knn langsung menghasilkan output berupa prediksi dari kelas pada data uji yang diberikan. Pada contoh berikut, kita melakukan prediksi data uji berdasarkan data latih dengan mengambil nilai k=5.

library(class)

pred.knn <- knn(train=scaled.data.train, test=scaled.data.test, 
             cl=data.train$quality, k=5)

# Menampilkan hasil prediksi
print(pred.knn)
##   [1] LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  HIGH HIGH LOW 
##  [16] LOW  LOW  LOW  LOW  HIGH LOW  LOW  LOW  HIGH LOW  LOW  LOW  HIGH LOW  HIGH
##  [31] LOW  LOW  HIGH HIGH LOW  LOW  LOW  LOW  LOW  LOW  LOW  HIGH LOW  LOW  HIGH
##  [46] HIGH HIGH LOW  LOW  LOW  HIGH LOW  LOW  LOW  HIGH LOW  HIGH HIGH LOW  HIGH
##  [61] LOW  LOW  HIGH LOW  HIGH HIGH HIGH HIGH HIGH LOW  LOW  HIGH HIGH HIGH HIGH
##  [76] HIGH HIGH HIGH HIGH HIGH HIGH HIGH LOW  LOW  HIGH LOW  LOW  HIGH HIGH LOW 
##  [91] HIGH LOW  LOW  HIGH HIGH HIGH HIGH HIGH HIGH HIGH LOW  HIGH HIGH LOW  LOW 
## [106] HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH LOW  HIGH HIGH LOW  HIGH LOW 
## [121] HIGH HIGH LOW  HIGH HIGH HIGH HIGH LOW  LOW  HIGH LOW  HIGH HIGH LOW  LOW 
## [136] LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  HIGH HIGH HIGH LOW  LOW 
## [151] LOW  HIGH LOW  LOW  LOW  LOW  HIGH LOW  HIGH LOW  LOW  LOW  HIGH HIGH LOW 
## [166] LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  LOW  HIGH LOW  LOW  LOW  HIGH LOW 
## [181] LOW  LOW  HIGH HIGH HIGH HIGH HIGH HIGH HIGH LOW  LOW  LOW  HIGH HIGH LOW 
## [196] LOW  LOW  LOW  HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH LOW 
## [211] HIGH LOW  LOW  HIGH HIGH HIGH HIGH HIGH LOW  LOW  HIGH HIGH LOW  HIGH HIGH
## [226] HIGH HIGH HIGH HIGH HIGH HIGH HIGH LOW  LOW  HIGH HIGH HIGH HIGH LOW  HIGH
## [241] HIGH HIGH HIGH LOW  HIGH HIGH HIGH HIGH LOW  HIGH LOW  HIGH HIGH LOW  HIGH
## [256] HIGH HIGH HIGH HIGH LOW  LOW  HIGH LOW  HIGH HIGH LOW  HIGH HIGH LOW  LOW 
## [271] LOW  HIGH LOW  HIGH HIGH LOW  HIGH HIGH LOW  HIGH HIGH LOW  LOW  HIGH HIGH
## [286] LOW  LOW  HIGH LOW  LOW  HIGH HIGH HIGH LOW  LOW  LOW  LOW  HIGH HIGH HIGH
## [301] LOW  LOW  HIGH LOW  HIGH HIGH HIGH HIGH HIGH HIGH LOW  HIGH HIGH HIGH HIGH
## [316] HIGH LOW  HIGH HIGH LOW  HIGH HIGH LOW  HIGH LOW  LOW  HIGH HIGH HIGH HIGH
## [331] LOW  HIGH LOW  HIGH LOW  HIGH LOW  HIGH HIGH HIGH LOW  HIGH
## Levels: HIGH LOW
# Evaluasi hasil prediksi pada data uji
conf_matrix <- confusionMatrix(pred.knn, data.test$quality)
print(conf_matrix)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction HIGH LOW
##       HIGH  136  48
##       LOW    50 108
##                                           
##                Accuracy : 0.7135          
##                  95% CI : (0.6624, 0.7608)
##     No Information Rate : 0.5439          
##     P-Value [Acc > NIR] : 9.525e-11       
##                                           
##                   Kappa : 0.4231          
##                                           
##  Mcnemar's Test P-Value : 0.9195          
##                                           
##             Sensitivity : 0.7312          
##             Specificity : 0.6923          
##          Pos Pred Value : 0.7391          
##          Neg Pred Value : 0.6835          
##              Prevalence : 0.5439          
##          Detection Rate : 0.3977          
##    Detection Prevalence : 0.5380          
##       Balanced Accuracy : 0.7117          
##                                           
##        'Positive' Class : HIGH            
## 

Berdasarkan output di atas, disajikan prediksi kelas dari data uji. Misalkan pada data uji yang pertama diprediksi masuk kelas LOW, data uji kedua masuk kelas LOW sampai dengan data uji terakhir diprediksi masuk sebagai kelas HIGH. Hasil ini selanjutnya dapat dibandingkan dengan kelas sebenarnya pada data uji melalui confusion matrix.

Secara keseluruhan, nilai akurasi prediksi sebesar 0.7135 atau algoritma KNN berhasil memprediksi benar 71,35% dari data uji. Jika dilihat menurut masing-masing kelas, dari 186 data uji dengan kelas HIGH, 136 diprediksi benar, sementara 50 salah. Hasil ini memberikan nilai sensitivity (prediksi benar kelas positif) sebesar 0.7312. Selanjutnya, dari 156 data uji dengan kelas LOW, 108 diprediksi benar dan 48 salah atau memiliki nilai spesifisitas sebesar 0.6923.

KNN dengan paket ‘caret’

Pustaka caret memiliki fungsi train yang dapat dimanfaatkan untuk melakukan iterasi berbagai nilai k. Selain melakukan iterasi, fungsi train juga memungkinkan kita melakukan validasi silang sehingga hasil yang diperoleh bisa lebih dapat dipercaya dan dapat mengurangi kecenderungan overfitting.

Kode berikut ini menunjukkan bagaimana penggunaan fungsi train untuk mencari nilai k terbaik pada algoritma KNN.

set.seed(100)  # mengatur seed tertentu untuk hasil yang dapat direproduksi

# proses pengukuran menggunakan k-fold cv dengan 5 fold
control = trainControl(method = "cv", number = 5)

# pencarian dilakukan untuk k=1,2,...30
grid = expand.grid(k = 1:30)

knn.grid <- train(x=scaled.data.train, 
                   y=data.train$quality, 
                   method = "knn",
                   trControl = control,
                   tuneGrid = grid)

# Menampilkan hasil model
print(knn.grid)
## k-Nearest Neighbors 
## 
## 801 samples
##  11 predictor
##   2 classes: 'HIGH', 'LOW' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 641, 641, 641, 641, 640 
## Resampling results across tuning parameters:
## 
##   k   Accuracy   Kappa    
##    1  0.7091227  0.4126780
##    2  0.6529503  0.2973395
##    3  0.6966770  0.3874821
##    4  0.7029503  0.4015506
##    5  0.7204425  0.4333700
##    6  0.7129581  0.4194087
##    7  0.7191693  0.4314914
##    8  0.7266848  0.4464642
##    9  0.7129658  0.4194162
##   10  0.7117314  0.4166171
##   11  0.7092236  0.4119548
##   12  0.7017236  0.3965814
##   13  0.7029658  0.3999190
##   14  0.6904969  0.3724202
##   15  0.6942081  0.3807460
##   16  0.6967158  0.3877130
##   17  0.7029658  0.3997832
##   18  0.7029891  0.4004082
##   19  0.7092314  0.4128985
##   20  0.7017081  0.3980471
##   21  0.7142158  0.4224141
##   22  0.7117391  0.4176401
##   23  0.6967624  0.3868755
##   24  0.7054658  0.4041047
##   25  0.7042081  0.4011766
##   26  0.7054814  0.4041193
##   27  0.7141925  0.4216292
##   28  0.7104581  0.4141541
##   29  0.7142003  0.4211373
##   30  0.7216925  0.4369388
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was k = 8.

Hasil di atas menunjukkan bahwa nilai k yang menghasilkan akurasi tertinggi berdasarkan validasi silang adalah k=8 yaitu sebesar 0.7266. Namun, perlu diingat bahwa nilai tersebut adalah berdasarkan data latih. Kita perlu mengukur performa sebenarnya menggunakan data uji seperti sebelumnya. Evaluasi pada data uji menunjukkan nilai akurasi sebesar 0,7047 sementara nilai sensitivity sebesar 0,7366 dan specificity sebesar 0,6667.

set.seed(100)

# Membuat prediksi untuk data uji
predictions <- predict(knn.grid, newdata = scaled.data.test)

# Evaluasi model
conf_matrix <- confusionMatrix(predictions, data.test$quality)
print(conf_matrix)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction HIGH LOW
##       HIGH  137  52
##       LOW    49 104
##                                           
##                Accuracy : 0.7047          
##                  95% CI : (0.6532, 0.7525)
##     No Information Rate : 0.5439          
##     P-Value [Acc > NIR] : 8.382e-10       
##                                           
##                   Kappa : 0.4039          
##                                           
##  Mcnemar's Test P-Value : 0.8423          
##                                           
##             Sensitivity : 0.7366          
##             Specificity : 0.6667          
##          Pos Pred Value : 0.7249          
##          Neg Pred Value : 0.6797          
##              Prevalence : 0.5439          
##          Detection Rate : 0.4006          
##    Detection Prevalence : 0.5526          
##       Balanced Accuracy : 0.7016          
##                                           
##        'Positive' Class : HIGH            
## 

Selamat mencoba!

Sumber: Algoritma K-Nearest Neighbors (KNN) dengan R