Nhóm thuật toán instance-based (hay memory-based) không cố gắng xây một mô hình tường minh từ dữ liệu huấn luyện. Thay vào đó, chúng ghi nhớ chính các mẫu đã thấy và đưa ra dự đoán cho điểm mới dựa trên độ tương đồng với những mẫu đã lưu. Triết lý cốt lõi: "những thứ giống nhau thì có xu hướng gần nhau".
Ý tưởng. Để dự đoán nhãn cho một điểm mới \(x\), kNN tìm \(k\) điểm trong tập huấn luyện gần \(x\) nhất (theo một độ đo khoảng cách), rồi cho chúng bầu chọn: nhãn nào chiếm đa số trong \(k\) láng giềng thì gán cho \(x\). Với bài toán hồi quy, ta lấy trung bình giá trị của \(k\) láng giềng.
Lazy learning. kNN gần như không có giai đoạn "huấn luyện" — nó chỉ lưu lại toàn bộ dữ liệu. Mọi tính toán bị dồn sang lúc dự đoán (phải đo khoảng cách từ điểm mới tới mọi điểm đã lưu). Vì vậy kNN được gọi là lazy learner (học lười), trái với các mô hình eager như hồi quy hay cây quyết định vốn tốn công lúc huấn luyện nhưng dự đoán rất nhanh.
"Gần nhất" phụ thuộc vào cách ta định nghĩa khoảng cách giữa hai vector đặc trưng \(x=(x_1,\dots,x_n)\) và \(x'=(x_1',\dots,x_n')\):
| Độ đo | Công thức | Đặc điểm |
|---|---|---|
| Euclid (L2) | \(d=\sqrt{\sum_{i=1}^{n}(x_i-x_i')^2}\) | Khoảng cách "đường chim bay"; phổ biến nhất, nhạy với scale |
| Manhattan (L1) | \(d=\sum_{i=1}^{n}|x_i-x_i'|\) | Tổng chênh lệch theo từng trục; bền hơn với ngoại lai |
| Minkowski | \(d=\left(\sum_{i=1}^{n}|x_i-x_i'|^{p}\right)^{1/p}\) | Tổng quát hoá: \(p=1\) là Manhattan, \(p=2\) là Euclid |
| Cosine | \(d=1-\dfrac{x\cdot x'}{\lVert x\rVert\,\lVert x'\rVert}\) | Đo góc giữa hai vector, bỏ qua độ dài; hợp với văn bản / vector thưa |
Giá trị \(k\) là siêu tham số quan trọng nhất:
Trong bản kNN cơ bản, mọi láng giềng có phiếu bầu như nhau. Weighted kNN cho láng giềng ở gần có trọng số lớn hơn, thường theo nghịch đảo khoảng cách:
$$ w_i=\frac{1}{d(x,x_i)+\varepsilon} \qquad \hat{y}=\frac{\sum_i w_i\,y_i}{\sum_i w_i} $$
Nhờ vậy điểm sát ngay cạnh \(x\) ảnh hưởng mạnh hơn điểm ở rìa của \(k\) láng giềng, giúp dự đoán ổn định hơn khi \(k\) lớn (\(\varepsilon\) là số nhỏ để tránh chia cho 0).
| Ưu điểm | Nhược điểm |
|---|---|
| Đơn giản, dễ hiểu, dễ cài đặt | Chậm khi dự đoán: mỗi truy vấn quét toàn bộ dữ liệu, độ phức tạp \(O(n)\) |
| Không giả định về phân phối dữ liệu (non-parametric) | Tốn bộ nhớ: phải lưu toàn bộ tập huấn luyện |
| Không có giai đoạn huấn luyện riêng | Curse of dimensionality: nhiều chiều thì mọi điểm "gần như cách đều nhau", khoảng cách mất ý nghĩa |
| Dùng được cho cả phân loại lẫn hồi quy | Nhạy với scale: đặc trưng giá trị lớn lấn át → cần chuẩn hoá |
Ý tưởng prototype-based. Điểm yếu lớn nhất của kNN là phải giữ toàn bộ dữ liệu. LVQ giải quyết điều này bằng cách học một tập nhỏ các vector đại diện — gọi là codebook hoặc prototype — mỗi vector được gắn sẵn một nhãn. Sau khi huấn luyện, ta chỉ cần giữ vài prototype thay vì hàng nghìn điểm dữ liệu.
Luật cập nhật. LVQ học các prototype theo kiểu lặp. Với mỗi mẫu huấn luyện \(x\) (có nhãn thật), ta tìm prototype gần nó nhất — gọi là BMU (Best Matching Unit), ký hiệu \(w\) — rồi điều chỉnh:
Trong đó \(\eta\) (\(0\lt\eta\lt 1\)) là tốc độ học, thường giảm dần qua các vòng lặp. Sau nhiều lần lặp, các prototype "trôi" tới những vị trí phản ánh đúng vùng của từng lớp, tạo nên biên quyết định gọn gàng. Khi dự đoán: gán cho điểm mới nhãn của prototype gần nó nhất.
LVQ thực chất nén dữ liệu thành một số ít prototype. Nhờ đó dự đoán nhanh hơn nhiều (chỉ so với vài prototype thay vì toàn bộ dữ liệu) và nhẹ bộ nhớ. Cái giá phải trả là cần một giai đoạn huấn luyện thật sự và kết quả phụ thuộc vào số prototype, cách khởi tạo, và \(\eta\).
| Tiêu chí | kNN | LVQ |
|---|---|---|
| Huấn luyện | Hầu như không (chỉ lưu dữ liệu) | Có: học vị trí các prototype qua nhiều vòng lặp |
| Bộ nhớ | Cao — giữ toàn bộ tập huấn luyện | Thấp — chỉ giữ vài prototype |
| Tốc độ dự đoán | Chậm, \(O(n)\) theo số mẫu | Nhanh, tỉ lệ với số prototype (\(\ll n\)) |
scikit-learn cung cấp sẵn KNeighborsClassifier. Lưu ý đặt scaler và mô hình trong một Pipeline để việc chuẩn hoá được áp dụng nhất quán:
from sklearn.neighbors import KNeighborsClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) # chuẩn hoá rồi mới kNN — rất quan trọng vì kNN dựa trên khoảng cách clf = make_pipeline( StandardScaler(), KNeighborsClassifier( n_neighbors=5, # k = 5 (số lẻ cho bài 2 lớp) weights="distance", # weighted kNN theo nghịch đảo khoảng cách metric="minkowski", p=2 # p=2 -> Euclid ) ) clf.fit(X_train, y_train) print(clf.score(X_test, y_test))
StandardScaler, đặc trưng có thang đo lớn sẽ chi phối khoảng cách và làm hỏng kết quả. Quy tắc: fit scaler trên tập train rồi áp lại cho test (Pipeline tự lo việc này).