[Tech Blog #18 | Data Science] K-Nearest Neighbors (KNN): Triết Lý "Chọn Bạn Mà Chơi" & Vai Trò Sống Còn Của Feature Scaling
![[Tech Blog #18 | Data Science] K-Nearest Neighbors (KNN): Triết Lý "Chọn Bạn Mà Chơi" & Vai Trò Sống Còn Của Feature Scaling](/uploads/ai-images/cover-knn-feature-scaling.png)
1. Đặt vấn đề: Bạn là trung bình cộng của những người bạn chơi thân
Dân gian có câu tục ngữ nổi tiếng: "Hãy cho tôi biết 5 người bạn thân nhất của bạn là ai, tôi sẽ nói cho bạn biết bạn là người như thế nào".
Trong Machine Learning, thuật toán K-Nearest Neighbors (KNN - K người hàng xóm gần nhất) vận hành y hệt như vậy:
- Khi có một điểm dữ liệu mới tinh xuất hiện (ví dụ: một khách hàng mới đăng ký).
- Để dự đoán xem người này có mua gói VIP hay không, mô hình không cần học một công thức toán học cao siêu nào cả.
- Nó chỉ đơn giản là đi tìm người khách hàng cũ có hành vi tương đồng (nằm gần nhất) với người này trong không gian dữ liệu.
- Sau đó, mô hình tổ chức một cuộc "bỏ phiếu biểu quyết" (Majority Voting): Nếu trong 5 người hàng xóm gần nhất có 4 người mua VIP Dự đoán khách hàng mới cũng sẽ mua VIP!
KNN là một thuật toán thuộc nhóm Lazy Learning (Học lười): Nó không hề tính toán hay tối ưu trọng số trong quá trình huấn luyện (fit), mà ghi nhớ toàn bộ tập dữ liệu và chỉ bắt đầu tính toán khoảng cách khi có yêu cầu dự đoán (predict).
2. Bản chất hình học & Nghệ thuật chọn con số K
Thu nhập
▲
│ (B) (B)
│ ? <--- Điểm mới cần dự đoán
│ (A) (B)
│ (A) (A)
│
└────────────────────────► Tuổi tác
2.1. Đo khoảng cách bằng công thức Euclid
Để biết ai là "hàng xóm gần nhất", máy tính đo khoảng cách đường chim bay giữa điểm mới và điểm cũ bằng công thức khoảng cách Euclid:
2.2. Chọn bao nhiêu là vừa?
- Nếu chọn quá nhỏ (ví dụ ): Mô hình chỉ nhìn duy nhất 1 người hàng xóm sát cạnh. Rất nguy hiểm vì nếu người hàng xóm đó là một điểm nhiễu (Outlier / Noise), mô hình sẽ đoán sai ngay lập tức Overfitting (Quá khớp).
- Nếu chọn quá lớn (ví dụ trên tập 120 mẫu): Mô hình sẽ lấy ý kiến của hầu như toàn bộ tập dữ liệu. Khi đó, nhãn chiếm đa số trong tập dữ liệu sẽ luôn luôn thắng cuộc bỏ phiếu Underfitting (Thiếu khớp).
- Mẹo thực chiến:
- Luôn chọn là một số lẻ (3, 5, 7, 9...) đối với bài toán phân loại 2 lớp (Binary Classification) để không bao giờ xảy ra tình trạng "hòa phiếu" 50-50.
- Dùng kỹ thuật kiểm thử chéo (Cross-Validation) để thử nghiệm các giá trị từ 3 đến 21 và chọn con số cho độ chính xác cao nhất.
3. Tử huyệt của KNN: Bắt buộc phải Chuẩn Hóa Dữ Liệu (Feature Scaling)
Hãy xem xét kịch bản dự đoán khách hàng với 2 đặc trưng:
- Đặc trưng 1 - Độ tuổi: Dao động từ 20 đến 60 tuổi (Chênh lệch tối đa khoảng 40 đơn vị).
- Đặc trưng 2 - Thu nhập hàng tháng: Dao động từ 10.000.000 đến 100.000.000 VNĐ (Chênh lệch tới 90.000.000 đơn vị).
Khi tính khoảng cách Euclid:
Chênh lệch 5.000.000 VNĐ tiền lương khi bình phương lên sẽ là , hoàn toàn "đè bẹp" chênh lệch 20 năm tuổi tác (chỉ là 400). Khi đó, mô hình hoàn toàn bị mù quáng trước cột Tuổi tác, và chỉ còn xem xét duy nhất cột Thu nhập!
Do đó, với bất kỳ thuật toán nào dựa trên khoảng cách (KNN, K-Means, SVM, PCA), chuẩn hóa dữ liệu về cùng một thang đo là bước sống còn.
4. Bảng so sánh: StandardScaler vs. MinMaxScaler
| Tiêu chí | StandardScaler (Standardization - Z-Score) | MinMaxScaler (Normalization) |
|---|---|---|
| Công thức | (Trừ trung bình, chia độ lệch chuẩn) | |
| Phạm vi giá trị | Không bị giới hạn cứng (thường tập trung trong ). | Ép nghiêm ngặt vào đoạn . |
| Độ nhạy với Outliers | 🟢 Tốt hơn: Outlier không làm co cụm các điểm bình thường. | 🔴 Kém: Nếu có 1 Outlier cực lớn, 99% dữ liệu còn lại sẽ bị nén nghẹt về sát số 0. |
| Khi nào nên dùng? | Dữ liệu dạng số thực có phân phối tương đối chuẩn hoặc có chứa Outlier. | Dữ liệu không có Outlier, hoặc khi thuật toán đòi hỏi khoảng giá trị (như xử lý ảnh, pixel 0–255). |
5. Thực hành thực chiến: Huấn luyện KNN với Pipeline chuẩn bằng Scikit-Learn
Đoạn mã Python dưới đây minh họa bài toán phân loại khách hàng, chứng minh sức mạnh của việc kết hợp StandardScaler và KNeighborsClassifier thông qua một quy trình khép kín (Pipeline):
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, accuracy_score
# 1. Tạo tập dữ liệu mô phỏng: Tuổi (20-60) và Thu nhập (10M - 80M)
np.random.seed(42)
n_samples = 400
age = np.random.randint(20, 60, size=n_samples)
income = np.random.uniform(10_000_000, 80_000_000, size=n_samples)
# Quy luật mua gói cao cấp: Người trên 35 tuổi VÀ có thu nhập trên 40 triệu
is_vip = ((age > 35) & (income > 40_000_000)).astype(int)
df = pd.DataFrame({"age": age, "income": income, "is_vip": is_vip})
X = df[["age", "income"]]
y = df["is_vip"]
# 2. Chia tập Train (75%) và Test (25%)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
# ==============================================================================
# TRƯỜNG HỢP 1: CHẠY KNN KHI CHƯA CHUẨN HÓA DỮ LIỆU (SAI LẦM PHỔ BIẾN)
# ==============================================================================
raw_knn = KNeighborsClassifier(n_neighbors=5)
raw_knn.fit(X_train, y_train)
raw_pred = raw_knn.predict(X_test)
raw_acc = accuracy_score(y_test, raw_pred)
print(f"1. Độ chính xác khi KHÔNG chuẩn hóa dữ liệu: {raw_acc * 100:.2f}%")
print(" -> Cột 'age' bị cột 'income' áp đảo hoàn toàn do chênh lệch thang đo!")
# ==============================================================================
# TRƯỜNG HỢP 2: DÙNG PIPELINE KẾT HỢP STANDARDSCALER + KNN (CHUẨN CHỈNH)
# ==============================================================================
# Pipeline đảm bảo scaler fit trên tập train và chỉ transform trên tập test (Tránh Data Leakage)
scaled_pipeline = Pipeline([
("scaler", StandardScaler()), # Bước 1: Đưa age và income về cùng thang đo z-score
("knn", KNeighborsClassifier(n_neighbors=5)) # Bước 2: Bỏ phiếu 5 người hàng xóm
])
scaled_pipeline.fit(X_train, y_train)
scaled_pred = scaled_pipeline.predict(X_test)
scaled_acc = accuracy_score(y_test, scaled_pred)
print(f"\n2. Độ chính xác sau khi dùng StandardScaler: {scaled_acc * 100:.2f}%")
print("\n--- BÁO CÁO PHÂN LOẠI CHI TIẾT (MÔ HÌNH CHUẨN HÓA) ---")
print(classification_report(y_test, scaled_pred, target_names=["Thường (0)", "VIP (1)"]))
# 3. Thử nghiệm dự đoán cho khách hàng mới tinh
new_customer = pd.DataFrame({
"age": [42], # 42 tuổi
"income": [55_000_000] # 55 triệu VNĐ
})
prediction = scaled_pipeline.predict(new_customer)
status = "VIP" if prediction[0] == 1 else "Thường"
print(f"Dự đoán cho khách hàng mới (42 tuổi, 55M): {status}")
6. Câu hỏi phỏng vấn thực tế (Interview Q&A)
Câu 1 (Mức độ: Entry / Junior)
Câu hỏi: "Tại sao KNN được gọi là một thuật toán Lazy Learning (Học lười biếng)? Điểm yếu lớn nhất của nó khi đưa vào môi trường sản xuất (Production) là gì?"
Gợi ý trả lời:
"KNN được gọi là Lazy Learner vì trong giai đoạn huấn luyện (training), nó không thực sự học bất kỳ trọng số mô hình nào (như các hệ số trong hồi quy) mà chỉ đơn thuần lưu trữ toàn bộ dữ liệu vào bộ nhớ.
Điểm yếu lớn nhất trong Production: Tốc độ dự đoán (inference / prediction) rất chậm và tốn tài nguyên. Mỗi khi có một người dùng mới gửi yêu cầu, thuật toán phải tính toán lại khoảng cách từ điểm mới đó tới HÀNG TRIỆU bản ghi có sẵn trong Database để tìm ra Top K láng giềng. Do đó, KNN chỉ phù hợp với các tập dữ liệu nhỏ đến vừa; với dữ liệu hàng chục triệu dòng, người ta sẽ dùng các thuật toán như ANN (Approximate Nearest Neighbors) hoặc các mô hình cây."
Câu 2 (Mức độ: Junior / Mid-level)
Câu hỏi: "Nếu dữ liệu của bạn có chứa các điểm ngoại lệ (Outliers) cực đoan, bạn sẽ chọn StandardScaler hay MinMaxScaler trước khi nạp vào KNN? Tại sao?"
Gợi ý trả lời:
"Em sẽ kiên quyết chọn StandardScaler.
Lý do là vì công thức của MinMaxScaler phụ thuộc trực tiếp vào hai giá trị cực biên: và . Nếu có một Outlier cực lớn xuất hiện, khoảng cách sẽ bị kéo giãn khủng khiếp, khiến cho 99% các điểm dữ liệu bình thường còn lại bị nén nghẹt lại trong một khoảng rất hẹp sát số 0 (ví dụ từ 0.001 đến 0.01), làm mất đi tính phân biệt của đặc trưng.
Trong khi đó, StandardScaler sử dụng giá trị trung bình () và độ lệch chuẩn (), nên nó không bị ràng buộc biên cứng và vẫn giữ được hình thái phân phối tương đối của các điểm dữ liệu bình thường xung quanh điểm 0."
7. Key Takeaways (Ghi nhớ nhanh)
- KNN vận hành theo nguyên lý đa số: Tìm người hàng xóm gần nhất theo khoảng cách Euclid để bỏ phiếu quyết định.
- Luôn chọn số lẻ: Đối với bài toán phân loại 2 lớp để tránh tình huống hòa phiếu bế tắc.
- Không scale = Mô hình hỏng: Với các thuật toán dựa trên khoảng cách, luôn chuẩn hóa các cột đặc trưng (
StandardScaler) trước khi đưa vào mô hình. - Luôn dùng Scikit-Learn
Pipeline: Để đóng gói bước Scaling và Model thành một thể thống nhất, ngăn chặn tuyệt đối lỗi rò rỉ dữ liệu (Data Leakage).