Seluruh model yang kita temui sejauh ini — regresi, pohon, ensemble — punya kesamaan tersembunyi: semuanya menjalankan proses pelatihan yang merangkum data menjadi koefisien, aturan, atau rangkaian pohon, lalu membuang data mentahnya. K-Nearest Neighbors (KNN) menolak jalan itu sama sekali: model ini tidak melatih apa pun, cukup menyimpan seluruh data latih, lalu menyelesaikan setiap prediksi dengan bertanya kepada k tetangga terdekat di sekitar titik baru. Kesederhanaannya membuat KNN menjadi model klasifikasi pertama yang wajib dicoba, sekaligus contoh sempurna betapa pentingnya skala fitur. Pada chapter ini kita memakainya untuk memperkirakan pelanggan mana yang mau berlangganan paket kopi bulanan di “Toko Kopi Kita”, lalu membedah dua poros keputusannya: penskalaan fitur dan pemilihan k.
Intuisi: Bertanya pada Tetangga Terdekat #
Cara kerja KNN bisa dirumuskan dalam satu kalimat: sebuah titik baru diberi label yang paling banyak muncul di antara k titik latih terdekatnya. Kedekatan diukur dengan jarak Euclidean — akar dari jumlah kuadrat selisih tiap fitur — persis seperti mengukur jarak lurus dua titik di peta, hanya saja dimensinya sebanyak fitur kita. Model ini disebut lazy learner karena tidak ada tahap pelatihan: seluruh “belajar” terjadi saat prediksi, saat algoritma mencari tetangga terdekat di antara seluruh data latih yang disimpan.
Jarak Euclidean memperlakukan semua fitur pada derajat yang sama, dan di sinilah letak jebakannya. Misalkan kita membandingkan tiga pelanggan kafe lewat tiga fitur: umur (tahun), frekuensi mampir per bulan (kali), dan jarak rumah ke kafe (meter):
import numpy as np
A = np.array([25, 14, 800]) # umur, kunjungan per bulan, jarak rumah (m)
B = np.array([52, 2, 1500])
C = np.array([26, 15, 1400])
def jarak(p, q):
return np.sqrt(np.sum((p - q) ** 2))
print("Jarak mentah (satuan asli):")
print(" A-B:", round(jarak(A, B), 1))
print(" A-C:", round(jarak(A, C), 1))
rata_rata = np.array([37.0, 8.0, 2400.0])
simpangan = np.array([10.0, 5.5, 1250.0])
def skala(titik):
return (titik - rata_rata) / simpangan
print("Jarak setelah penskalaan (z-score):")
print(" A-B:", round(jarak(skala(A), skala(B)), 2))
print(" A-C:", round(jarak(skala(A), skala(C)), 2))Output yang Dihasilkan:
Jarak mentah (satuan asli):
A-B: 700.6
A-C: 600.0
Jarak setelah penskalaan (z-score):
A-B: 3.52
A-C: 0.52Mari kita telaah baris-baris kode di atas:
- Baris 3-5 (
A,B,C): tiga pelanggan dalam vektor tiga fitur. Sec perilaku nyaris kembar dengan A — sama-sama muda dan rajin mampir — sedangkan B jauh berbeda: 27 tahun lebih tua dan hampir tidak pernah datang. jarak(p, q): rumus jarak Euclidean dalam satu baris NumPy — kuadratkan selisih, jumlahkan, lalu akarkan.- Jarak mentah menipu: selisih A-B dan A-C tampak seimbang (700,6 lawan 600,0) padahal A dan C nyaris kembar. Angka 600-an itu hampir seluruhnya berasal dari selisih jarak rumah dalam meter; selisih 27 tahun umur dan 12 kunjungan hanya menyumbang 27,9 dari ratusan.
skala(titik): mengubah tiap fitur menjadi z-score — selisih dari rata-rata populasi dibagi simpangan bakunya — sehingga ketiga fitur berbicara dalam satuan yang setara. Hasilnya jujur: A-C berjarak 0,52, jauh lebih dekat daripada A-B yang berjarak 3,52, persis intuisi kita.
Kesimpulan dari contoh kecil ini berlaku umum: KNN hanyalah sebaik skala fiturnya. Model berbasis pohon kebal terhadap satuan karena hanya membandingkan titik belah satu fitur dengan fitur itu sendiri; KNN membandingkan lintas fitur, jadi satuan yang timpang langsung menciptakan fitur “raja” yang menenggelamkan sisanya.
Melatih KNN pada Data Pelanggan #
Kasus kita: “Toko Kopi Kita” menguji paket langganan kopi bulanan pada 30 pelanggan dan mencatat hasilnya. Fiturnya umur (tahun), kunjungan_per_bulan (kali), dan jarak_meter (jarak rumah ke kafe dalam meter); target berlangganan bernilai 1 bila pelanggan mendaftar. Pola umumnya: yang berlangganan cenderung muda dan sering mampir, sedangkan jarak_meter nyaris tidak berhubungan dengan keputusan — tetapi justru fitur berskala raksasa inilah yang mempermainkan KNN bila kita lengah:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
pelanggan = pd.DataFrame({
"umur": [24, 27, 22, 29, 25, 31, 34, 26, 30, 23, 28, 33, 35, 24, 45, 52,
38, 26, 42, 55, 36, 40, 41, 44, 58, 46, 39, 34, 43, 37],
"kunjungan_per_bulan": [16, 12, 20, 9, 14, 11, 10, 18, 13, 15, 22, 8, 12,
17, 3, 2, 5, 14, 4, 1, 6, 8, 12, 2, 4, 7, 3, 4, 5, 7],
"jarak_meter": [2200, 3400, 1300, 1200, 2400, 1700, 600, 2700, 1800, 4400,
3100, 1500, 1900, 2800, 2100, 4100, 4600, 3600, 3800, 1100,
3900, 800, 4200, 700, 2000, 500, 900, 2600, 2500, 2900],
"berlangganan": [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
})
fitur = ["umur", "kunjungan_per_bulan", "jarak_meter"]
X = pelanggan[fitur]
y = pelanggan["berlangganan"]
X_latih, X_uji, y_latih, y_uji = train_test_split(X, y, test_size=0.3,
stratify=y, random_state=42)
knn_mentah = KNeighborsClassifier(n_neighbors=5)
knn_mentah.fit(X_latih, y_latih)
skala = StandardScaler()
X_latih_skala = skala.fit_transform(X_latih)
X_uji_skala = skala.transform(X_uji)
knn_skala = KNeighborsClassifier(n_neighbors=5)
knn_skala.fit(X_latih_skala, y_latih)
print("Tanpa penskalaan : akurasi uji %.2f" % knn_mentah.score(X_uji, y_uji))
print("Dengan penskalaan: akurasi uji %.2f" % knn_skala.score(X_uji_skala, y_uji))Output yang Dihasilkan:
Tanpa penskalaan : akurasi uji 0.44
Dengan penskalaan: akurasi uji 1.00Mari kita telaah baris-baris kode di atas:
KNeighborsClassifier(n_neighbors=5): k diberi lewatn_neighbors. Perhatikan bahwa “melatih” KNN sebenarnya hanya menyimpan data —fit-nya nyaris seketika.skala.fit_transform(X_latih)laluskala.transform(X_uji): urutan ini bukan formalitas.fit_transformmempelajari rata-rata dan simpangan baku dari data latih saja, lalutransformmemakai statistik yang sama untuk data uji. Bila kita menghitung statistik dari seluruh data, informasi data uji bocor ke proses persiapan — penyakit yang dinamai data leakage.- 0,44 lawan 1,00: tanpa penskalaan, pencarian tetangga nyaris sepenuhnya dikendalikan
jarak_meteryang bernilai ratusan hingga ribuan; karena jarak rumah tidak berhubungan dengan keputusan berlangganan, hasilnya lebih buruk daripada menebak kelas terbanyak. Setelah ketiga fitur setara, umur dan frekuensi mampir langsung bekerja dan akurasi melompat ke 1,00 pada 9 pelanggan uji.
Memilih Jumlah Tetangga k #
Parameter k mengatur seberapa halus batas keputusan kita. k kecil berarti prediksi ditentukan segelintir tetangga: model sangat lentur, mengikuti setiap lekukan data latih, dan mudah terombang-ambing oleh satu-dua pencilan. k besar meratakan keputusan ke wilayah yang lebih luas: model lebih tahan derau, tetapi batasnya makin kaku hingga akhirnya meredam pola yang sebenarnya. Kedua ujungnya sama-sama bermasalah — yang kita cari adalah wilayah tengah. Kita petakan perilakunya dengan mengulang pelatihan pada data yang telah diskalakan:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
pelanggan = pd.DataFrame({
"umur": [24, 27, 22, 29, 25, 31, 34, 26, 30, 23, 28, 33, 35, 24, 45, 52,
38, 26, 42, 55, 36, 40, 41, 44, 58, 46, 39, 34, 43, 37],
"kunjungan_per_bulan": [16, 12, 20, 9, 14, 11, 10, 18, 13, 15, 22, 8, 12,
17, 3, 2, 5, 14, 4, 1, 6, 8, 12, 2, 4, 7, 3, 4, 5, 7],
"jarak_meter": [2200, 3400, 1300, 1200, 2400, 1700, 600, 2700, 1800, 4400,
3100, 1500, 1900, 2800, 2100, 4100, 4600, 3600, 3800, 1100,
3900, 800, 4200, 700, 2000, 500, 900, 2600, 2500, 2900],
"berlangganan": [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
})
X = pelanggan[["umur", "kunjungan_per_bulan", "jarak_meter"]]
y = pelanggan["berlangganan"]
X_latih, X_uji, y_latih, y_uji = train_test_split(X, y, test_size=0.3,
stratify=y, random_state=42)
skala = StandardScaler()
X_latih_skala = skala.fit_transform(X_latih)
X_uji_skala = skala.transform(X_uji)
print("k | akurasi latih | akurasi uji")
for k in [1, 3, 5, 9, 15, 21]:
model = KNeighborsClassifier(n_neighbors=k)
model.fit(X_latih_skala, y_latih)
print("%2d | %.2f | %.2f"
% (k, model.score(X_latih_skala, y_latih),
model.score(X_uji_skala, y_uji)))Output yang Dihasilkan:
k | akurasi latih | akurasi uji
1 | 1.00 | 0.89
3 | 0.95 | 0.89
5 | 0.86 | 1.00
9 | 0.86 | 1.00
15 | 0.81 | 1.00
21 | 0.52 | 0.56Tabel ini memperlihatkan kedua ujung spektrum secara jujur:
- k=1: menghafal sempurna, prediksi goyah. Akurasi latih pasti 1,00 — setiap titik adalah tetangga terdekat dirinya sendiri — tetapi akurasi uji 0,89; dua pelanggan latih yang menyalahi pola (muda dan rajin mampir tetapi tidak berlangganan, serta sebaliknya) ikut menarik prediksi di sekitarnya.
- k=5 hingga 15: wilayah stabil. Voting lima sampai lima belas tetangga meniadakan satu-dua titik janggal, dan akurasi uji mentok di 1,00.
- k=21: kandas menjadi tebakan mayoritas. Dengan 21 baris data latih, memakai seluruhnya sebagai tetangga berarti setiap prediksi adalah kelas terbanyak data latih — model berhenti melihat titik baru sama sekali, dan akurasinya anjlok ke tingkat tebakan buta.
Kekuatan dan Kelemahan KNN #
| Aspek | Keterangan |
|---|---|
| Tanpa tahap pelatihan | fit sekadar menyimpan data; tak ada koefisien atau pohon untuk ditafsir |
| Penjelasan mudah | “Prediksi 1 karena 4 dari 5 tetangga terdekatnya berlangganan” — bukti yang bisa ditunjukkan |
| Wajib penskalaan fitur | Jarak lintas fitur sensitif terhadap satuan; 0,44 vs 1,00 pada data kita |
| Prediksi melambat pada data besar | Setiap prediksi memindai seluruh data latih; perlu struktur khusus (KD-Tree, BallTree) |
| Fitur banyak melemahkan jarak | Fenomena curse of dimensionality: di dimensi tinggi semua titik terasa berjarak mirip; KNN paling kuat pada sedikit fitur bermakna |
| Memori penuh | Seluruh data latih harus tersimpan di saat prediksi |
Kapan Kita Memakainya #
KNN bersinar sebagai baseline cepat pada dataset berukuran kecil hingga menengah dengan sedikit fitur: sistem rekomendasi sederhana, penapisan awal, atau pemeriksaan cepat apakah pola ketetanggaan ada di balik label. Ketika data membengkak atau fiturnya menjamur, model yang benar-benar belajar — seperti ensemble pohon kita — menjadi pilihan yang lebih efisien. Ada satu keluarga model lain yang sama sensitifnya dengan penskalaan tetapi menawarkan kekuatan berbeda: menarik batas pemisah selebar mungkin di antara dua kelas. Mari kita temui Support Vector Machine.
Rangkuman #
- KNN adalah lazy learner: tanpa pelatihan, ia menyimpan data latih dan memutuskan lewat voting mayoritas k tetangga terdekat berdasarkan jarak Euclidean.
- Penskalaan fitur adalah prasyarat: jarak mentah kita menipu (A-B 700,6 lawan A-C 600,0), dan pada data pelanggan akurasi uji melompat dari 0,44 ke 1,00 setelah
StandardScalermenyeterakan ketiga fitur. StandardScalerhanya di-fit pada data latih (fit_transform) lalu diterapkan ke data uji (transform) untuk mencegah data leakage.- k kecil lentur dan mudah menghafal (k=1: latih 1,00, uji 0,89); k besar terlalu halus hingga menjadi tebakan mayoritas (k=21: 0,52/0,56); wilayah tengah k=5-15 paling stabil pada data kita.
- Model berbasis jarak melemah di dimensi tinggi dan melambat pada data besar — KNN paling cocok untuk dataset kecil dengan fitur sedikit.
Prediksi KNN ditentukan kepadatan tetangga di sekitar titik baru. Support Vector Machine melihat geometri dengan cara yang lebih ambisius: bukan sekadar menghitung tetangga, melainkan mencari garis pemisah dengan ruang aman selebar mungkin — kita bahas pada chapter Support Vector Machine .