Look for the interactive boxes: Ask the Neighbours (Section 01), Distance Metrics (03), Hand Calculator (04), KNN Regression (05), Choosing K (06), Scaling Matters (07), The Curse of Dimensionality (08) and How a KD-Tree Finds Neighbours (11). Every neighbour search in them runs live in your browser.
The Story That Explains KNN
You do not read economic theory. You do not build a complex model. You simply ask: “What are the five nearest flats paying right now?” If four of them pay £2,000 a month and one pays £1,200, you estimate the average: £1,840 — and you are probably close.
Now a doctor wants to know if a new patient has diabetes. She looks up the five most similar patients in the hospital records — similar age, similar BMI, similar glucose. Four of the five have diabetes. Her prediction: diabetes. A majority vote. No formula needed.
That is K-Nearest Neighbors (KNN) in full: find the K most similar examples you have already seen, then let them vote (classification) or take their average (regression).
Most algorithms learn a compact model during training. KNN does the opposite: it stores the whole training set and does all the real work at prediction time. “Training” is almost instant (scikit-learn may just build a search tree, see Section 11), but predictions can be slow on big data — every new point must be compared with the stored points.
Try It — Ask the Neighbours
Click anywhere to place a new point. Watch KNN find its neighbours one by one, then count the votes. Switch to distance-weighted votes and see how close neighbours get louder votes.
इन interactive boxes को देखिए: Ask the Neighbours (सेक्शन 01), Distance Metrics (03), Hand Calculator (04), KNN Regression (05), Choosing K (06), Scaling Matters (07), The Curse of Dimensionality (08) और How a KD-Tree Finds Neighbours (11)। इनमें हर neighbour search आपके browser में live चलती है।
वह कहानी जो KNN को समझाती है
आप economics की theory नहीं पढ़ते। आप कोई complex model नहीं बनाते। आप बस पूछते हैं: “सबसे पास वाले पाँच flats अभी कितना किराया दे रहे हैं?” अगर उनमें से चार £2,000 महीना देते हैं और एक £1,200, तो आप औसत निकालते हैं: £1,840 — और शायद आप सही के क़रीब हैं।
अब एक doctor जानना चाहती है कि नए मरीज़ को diabetes है या नहीं। वह hospital records में सबसे मिलते-जुलते पाँच मरीज़ ढूँढती है — मिलती-जुलती उम्र, मिलता-जुलता BMI, मिलता-जुलता glucose। पाँच में से चार को diabetes है। उसकी prediction: diabetes। एक majority vote। किसी formula की ज़रूरत नहीं।
पूरा K-Nearest Neighbors (KNN) बस यही है: पहले से देखे गए examples में से K सबसे मिलते-जुलते examples ढूँढिए, फिर उन्हें vote करने दीजिए (classification) या उनका औसत लीजिए (regression)।
ज़्यादातर algorithms training के दौरान एक छोटा model सीखते हैं। KNN इसका उल्टा करता है: यह पूरा training set store कर लेता है और असली काम prediction के समय करता है। “Training” लगभग तुरंत हो जाती है (scikit-learn शायद बस एक search tree बनाए, सेक्शन 11 देखिए), पर बड़े data पर predictions धीमी हो सकती हैं — हर नए point की तुलना store किए गए points से करनी पड़ती है।
ख़ुद आज़माइए — Ask the Neighbours
कहीं भी click करके एक नया point रखिए। देखिए कि KNN उसके neighbours को एक-एक करके कैसे ढूँढता है, फिर votes गिनता है। distance-weighted votes पर जाइए और देखिए कि पास वाले neighbours के votes कैसे ज़्यादा भारी हो जाते हैं।
请留意这些可以互动的方框:Ask the Neighbours(第 01 节)、Distance Metrics(03)、Hand Calculator(04)、KNN Regression(05)、Choosing K(06)、Scaling Matters(07)、The Curse of Dimensionality(08)和 How a KD-Tree Finds Neighbours(11)。其中每一次 neighbour 搜索都在你的浏览器里实时运行。
一个讲清楚 KNN 的故事
你不会去读经济学理论,也不会去建一个复杂的 model。 你只会问:“离这里最近的五套公寓,现在每月付多少?” 如果其中四套每月付 £2,000,一套付 £1,200, 你就算出平均值:£1,840 —— 而且多半八九不离十。
现在,一位医生想知道一位新病人有没有糖尿病。 她在医院的记录里找出五个最相似的病人 —— 年龄相近、BMI 相近、血糖相近。 五个人中有四个有糖尿病。她的预测:糖尿病。这就是 majority vote,不需要公式。
这就是 K-Nearest Neighbors(KNN)的全部: 找出你已经见过的 K 个最相似的例子, 然后让它们 vote(classification),或者取它们的平均值(regression)。
大多数算法在 training 时学出一个紧凑的 model。KNN 正好相反: 它把整个 training set 存起来,所有真正的工作都留到预测的时候再做。 “training”几乎是一瞬间的事(scikit-learn 可能只是建一棵搜索树,见第 11 节), 但在大数据上预测可能很慢 —— 每个新点都要和存下来的点比较。
动手试试 —— Ask the Neighbours
点击任意位置,放一个新点。看 KNN 一个一个地找到它的 neighbours,然后数 vote。切换到 distance-weighted vote,看看近的 neighbours 的 vote 怎样变得更有分量。
How KNN Works — Four Steps
Distance Metrics — How “Closeness” Is Measured
KNN depends completely on its idea of similarity. Different distance metrics can pick different neighbours — and give different predictions.
| Metric | Best For | Effect of One Large Difference | scikit-learn Parameter |
|---|---|---|---|
| Euclidean | Continuous features, low dimensions — the default | Strong — it is squared | metric='euclidean' |
| Manhattan | Continuous features, many dimensions, noisy data | Weaker | metric='manhattan' |
| Minkowski | General — tune p | Grows with p | metric='minkowski', p=2 |
| Hamming | Categorical or binary features | Each position counts 1 | metric='hamming' |
| Cosine | Text vectors — direction matters more than length | Ignores vector length | metric='cosine' |
Try It — Distance Metrics
Slide p from 1 to ∞. The shaded shape — all points at the same distance — changes from a diamond to a circle to a square. Red-ringed points are neighbours that Euclidean distance would not have chosen.
KNN कैसे काम करता है — चार कदम
Distance Metrics — “नज़दीकी” कैसे मापी जाती है
KNN पूरी तरह इस पर टिका है कि वह similarity को कैसे समझता है। अलग-अलग distance metrics अलग neighbours चुन सकते हैं — और अलग predictions दे सकते हैं।
| Metric | किसके लिए सबसे अच्छा | एक बड़े फ़र्क़ का असर | scikit-learn Parameter |
|---|---|---|---|
| Euclidean | Continuous features, कम dimensions — यही default है | ज़्यादा — यह square होता है | metric='euclidean' |
| Manhattan | Continuous features, बहुत सारे dimensions, noisy data | कम | metric='manhattan' |
| Minkowski | सामान्य — p को tune कीजिए | p के साथ बढ़ता है | metric='minkowski', p=2 |
| Hamming | Categorical या binary features | हर position 1 गिनी जाती है | metric='hamming' |
| Cosine | Text vectors — लंबाई से ज़्यादा दिशा मायने रखती है | Vector की लंबाई को नज़रअंदाज़ करता है | metric='cosine' |
ख़ुद आज़माइए — Distance Metrics
p को 1 से ∞ तक खिसकाइए। छाया वाला आकार — वे सारे points जो एक ही distance पर हैं — diamond से circle और फिर square में बदल जाता है। लाल ring वाले points ऐसे neighbours हैं जिन्हें Euclidean distance नहीं चुनती।
KNN 怎样工作 —— 四个步骤
Distance Metrics —— 怎样衡量“近”
KNN 完全依赖它对“相似”的理解。 不同的 distance metric 可能选出不同的 neighbours —— 并给出不同的预测。
| Metric | 最适合 | 一个大差别的影响 | scikit-learn 参数 |
|---|---|---|---|
| Euclidean | 连续的 features,dimension 少 —— 默认选择 | 强 —— 因为要平方 | metric='euclidean' |
| Manhattan | 连续的 features、dimension 多、有噪声的数据 | 较弱 | metric='manhattan' |
| Minkowski | 通用 —— 调 p | 随 p 变大而增强 | metric='minkowski', p=2 |
| Hamming | 类别型或二值的 features | 每个位置算 1 | metric='hamming' |
| Cosine | 文本向量 —— 方向比长度更重要 | 不看向量长度 | metric='cosine' |
动手试试 —— Distance Metrics
把 p 从 1 滑到 ∞。阴影形状 —— 所有 distance 相同的点 —— 会从菱形变成圆形,再变成正方形。带红圈的点,是 Euclidean distance 不会选中的 neighbours。
Step-by-Step Manual Calculation
Let us classify one new patient by hand, with K = 3 and Euclidean distance. We have five patients with two features — Age and Glucose — and a known diabetes label.
| Patient | Age | Glucose | Diabetes? |
|---|---|---|---|
| P1 | 25 | 80 | No |
| P2 | 35 | 150 | Yes |
| P3 | 45 | 180 | Yes |
| P4 | 20 | 70 | No |
| P5 | 50 | 200 | Yes |
| NEW | 40 | 160 | ? |
Glucose ranges over 130 units (70–200), age over only 30 (20–50). After squaring, glucose differences are often dozens of times larger: for P1, 6400 comes from glucose and only 225 from age. Age barely affects which neighbours are chosen. In a real project you must scale the features first — or glucose silently overrules everything else.
Try It — Hand Calculator
Move the new patient’s age and glucose, change K, and switch the scaling. Watch the glucose share of distance² — and how the ranking changes when both features are scaled.
कदम-दर-कदम हाथ से हिसाब
आइए, K = 3 और Euclidean distance के साथ एक नए मरीज़ को हाथ से classify करें। हमारे पास पाँच मरीज़ हैं, दो features के साथ — Age और Glucose — और diabetes का पता label भी।
| मरीज़ | Age | Glucose | Diabetes? |
|---|---|---|---|
| P1 | 25 | 80 | No |
| P2 | 35 | 150 | Yes |
| P3 | 45 | 180 | Yes |
| P4 | 20 | 70 | No |
| P5 | 50 | 200 | Yes |
| NEW | 40 | 160 | ? |
Glucose 130 units (70–200) में फैला है, जबकि age सिर्फ़ 30 (20–50) में। Square करने के बाद glucose के फ़र्क़ अक्सर दर्जनों गुना बड़े होते हैं: P1 के लिए 6400 glucose से आता है और सिर्फ़ 225 age से। Neighbours चुनने पर age का असर लगभग न के बराबर है। असली project में आपको पहले features को scale करना ही होगा — वरना glucose चुपचाप बाक़ी सब पर हावी हो जाता है।
ख़ुद आज़माइए — Hand Calculator
नए मरीज़ की age और glucose बदलिए, K बदलिए, और scaling बदलिए। distance² में glucose का हिस्सा देखिए — और देखिए कि दोनों features scale होने पर ranking कैसे बदलती है।
一步一步手工计算
我们用 K = 3 和 Euclidean distance,手工给一位新病人做 classification。 我们有五个病人,每人有两个 features —— 年龄和血糖 —— 还有已知的糖尿病 label。
| 病人 | 年龄 | 血糖 | 糖尿病? |
|---|---|---|---|
| P1 | 25 | 80 | No |
| P2 | 35 | 150 | Yes |
| P3 | 45 | 180 | Yes |
| P4 | 20 | 70 | No |
| P5 | 50 | 200 | Yes |
| NEW | 40 | 160 | ? |
血糖的范围跨越 130 个单位(70–200),年龄只跨越 30(20–50)。平方之后,血糖的差常常要大几十倍: 以 P1 为例,6400 来自血糖,只有 225 来自年龄。年龄几乎不影响选出哪些 neighbours。 在真实的项目中,你必须先对 features 做 scaling —— 否则血糖会悄悄压过其他一切。
动手试试 —— Hand Calculator
移动新病人的年龄和血糖,改变 K,并切换 scaling 方式。注意看血糖在 distance² 中的占比 —— 以及两个 features 都做了 scaling 之后,排名怎样变化。
Classification vs Regression
| Neighbour | Label | Distance |
|---|---|---|
| Neighbour 1 | Spam | 0.12 |
| Neighbour 2 | Spam | 0.18 |
| Neighbour 3 | Ham | 0.25 |
| Neighbour 4 | Spam | 0.31 |
| Neighbour 5 | Ham | 0.40 |
| Prediction | Spam (3 vs 2 votes) | |
| Neighbour | Price (£k) | Distance |
|---|---|---|
| Neighbour 1 | 295 | 0.08 |
| Neighbour 2 | 310 | 0.15 |
| Neighbour 3 | 280 | 0.22 |
| Neighbour 4 | 325 | 0.30 |
| Neighbour 5 | 300 | 0.38 |
| Prediction | £302,000 (mean) | |
predict_proba in a KNN classifier is simply the share of the K neighbours in each class — here Spam 3/5 = 60%, Ham 2/5 = 40%.
Try It — KNN Regression
Each prediction is the average of the K nearest training values. Click to move the query line. Small K follows the noise; large K flattens the curve.
Classification बनाम Regression
| Neighbour | Label | Distance |
|---|---|---|
| Neighbour 1 | Spam | 0.12 |
| Neighbour 2 | Spam | 0.18 |
| Neighbour 3 | Ham | 0.25 |
| Neighbour 4 | Spam | 0.31 |
| Neighbour 5 | Ham | 0.40 |
| Prediction | Spam (3 बनाम 2 votes) | |
| Neighbour | क़ीमत (£k) | Distance |
|---|---|---|
| Neighbour 1 | 295 | 0.08 |
| Neighbour 2 | 310 | 0.15 |
| Neighbour 3 | 280 | 0.22 |
| Neighbour 4 | 325 | 0.30 |
| Neighbour 5 | 300 | 0.38 |
| Prediction | £302,000 (mean) | |
KNN classifier में predict_proba बस हर class में K neighbours का हिस्सा है — यहाँ Spam 3/5 = 60%, Ham 2/5 = 40%।
ख़ुद आज़माइए — KNN Regression
हर prediction K सबसे पास वाली training values का औसत है। Query line को खिसकाने के लिए click कीजिए। छोटा K noise के पीछे चलता है; बड़ा K curve को सपाट कर देता है।
Classification 与 Regression
| Neighbour | Label | Distance |
|---|---|---|
| Neighbour 1 | Spam | 0.12 |
| Neighbour 2 | Spam | 0.18 |
| Neighbour 3 | Ham | 0.25 |
| Neighbour 4 | Spam | 0.31 |
| Neighbour 5 | Ham | 0.40 |
| 预测 | Spam(3 比 2 个 vote) | |
| Neighbour | 价格(£k) | Distance |
|---|---|---|
| Neighbour 1 | 295 | 0.08 |
| Neighbour 2 | 310 | 0.15 |
| Neighbour 3 | 280 | 0.22 |
| Neighbour 4 | 325 | 0.30 |
| Neighbour 5 | 300 | 0.38 |
| 预测 | £302,000(平均值) | |
KNN classifier 里的 predict_proba,就是 K 个 neighbours 中每个 class 所占的比例 —— 这里 Spam 3/5 = 60%,Ham 2/5 = 40%。
动手试试 —— KNN Regression
每个预测都是 K 个最近的 training 值的平均。点击来移动 query 线。K 小时,曲线跟着噪声走;K 大时,曲线变平。
Choosing K — The Most Important Decision
K = N (all training points): every query gets the majority class of the whole dataset — the position of the new point is ignored. This is the most extreme underfitting.
The right K sits in between: small enough to keep the local shape, large enough to smooth out noise. Find it with cross-validation.
| K Value | Decision Boundary | Bias | Variance | Risk |
|---|---|---|---|---|
| K = 1 | Very jagged | Very low | Very high | Overfitting — follows noise |
| Small K (3–7) | Jagged but smoother | Low | Moderate–high | Often good for small, clean datasets |
| Medium K | Smooth | Balanced ✅ | Moderate | Often best for noisy data — check with CV |
| Large K | Very smooth | High | Low | Underfitting — local shape lost |
| K = N | None — one class everywhere | Maximum | Zero | Always predicts the majority class |
Try It — Choosing K
Move K from 1 to 159 and watch the border change from jagged to smooth to flat. The small chart shows train and test accuracy for every K at once.
K चुनना — सबसे ज़रूरी फ़ैसला
K = N (सारे training points): हर query को पूरे dataset की majority class मिलती है — नए point की जगह को नज़रअंदाज़ कर दिया जाता है। यह सबसे ज़्यादा underfitting है।
सही K बीच में होता है: इतना छोटा कि local आकार बना रहे, इतना बड़ा कि noise smooth हो जाए। इसे cross-validation से ढूँढिए।
| K की Value | Decision Boundary | Bias | Variance | ख़तरा |
|---|---|---|---|---|
| K = 1 | बहुत टेढ़ी-मेढ़ी | बहुत कम | बहुत ज़्यादा | Overfitting — noise के पीछे चलती है |
| छोटा K (3–7) | टेढ़ी-मेढ़ी, पर ज़्यादा smooth | कम | मध्यम–ज़्यादा | छोटे, साफ़ datasets के लिए अक्सर अच्छा |
| मध्यम K | Smooth | संतुलित ✅ | मध्यम | Noisy data के लिए अक्सर सबसे अच्छा — CV से जाँचिए |
| बड़ा K | बहुत smooth | ज़्यादा | कम | Underfitting — local आकार खो जाता है |
| K = N | कोई नहीं — हर जगह एक ही class | सबसे ज़्यादा | शून्य | हमेशा majority class की prediction |
ख़ुद आज़माइए — Choosing K
K को 1 से 159 तक ले जाइए और देखिए कि border टेढ़ी-मेढ़ी से smooth और फिर सपाट कैसे होती है। छोटा chart हर K के लिए train और test accuracy एक साथ दिखाता है।
选择 K —— 最重要的决定
K = N(所有 training 点):每个 query 得到的都是整个 dataset 中的多数 class —— 新点的位置完全被忽略。 这是最极端的 underfitting。
合适的 K 在两者之间:小到能保留局部的形状,大到能抹平噪声。 用 cross-validation 来找到它。
| K 值 | 决策边界 | Bias | Variance | 风险 |
|---|---|---|---|---|
| K = 1 | 非常锯齿 | 很低 | 很高 | Overfitting —— 跟着噪声走 |
| 小 K(3–7) | 锯齿,但更平滑 | 低 | 中到高 | 对小而干净的 dataset 常常不错 |
| 中等 K | 平滑 | 平衡 ✅ | 中等 | 对有噪声的数据常常最好 —— 用 CV 检查 |
| 大 K | 非常平滑 | 高 | 低 | Underfitting —— 局部形状丢失 |
| K = N | 没有 —— 到处都是同一个 class | 最大 | 零 | 永远预测多数 class |
动手试试 —— Choosing K
把 K 从 1 移到 159,看边界从锯齿变平滑,再变平。小图同时显示每一个 K 的 train accuracy 和 test accuracy。
Feature Scaling — Essential for KNN
KNN’s core operation is measuring distance. A feature with large numbers will dominate every distance and make the other features almost invisible.
| Feature | Patient A | Patient B | Diff² |
|---|---|---|---|
| Age (years) | 30 | 31 | 1 |
| Income (£) | 30,000 | 70,000 | 1,600,000,000 |
| BMI | 22.5 | 35.0 | 156.25 |
| Euclidean d | 40,000.00 — almost entirely income | ||
| Feature | Patient A | Patient B | Diff² |
|---|---|---|---|
| Age (z-score) | −0.85 | −0.71 | 0.0196 |
| Income (z-score) | −0.92 | 1.15 | 4.2849 |
| BMI (z-score) | −1.20 | 0.95 | 4.6225 |
| Euclidean d | 2.99 — income and BMI both contribute | ||
How Much Does Scaling Matter? A Real Test
Feature Scaling — KNN के लिए ज़रूरी
KNN का मुख्य काम distance मापना है। बड़ी संख्याओं वाला feature हर distance पर हावी हो जाता है और बाक़ी features को लगभग ग़ायब कर देता है।
| Feature | मरीज़ A | मरीज़ B | Diff² |
|---|---|---|---|
| Age (साल) | 30 | 31 | 1 |
| Income (£) | 30,000 | 70,000 | 1,600,000,000 |
| BMI | 22.5 | 35.0 | 156.25 |
| Euclidean d | 40,000.00 — लगभग पूरी income से | ||
| Feature | मरीज़ A | मरीज़ B | Diff² |
|---|---|---|---|
| Age (z-score) | −0.85 | −0.71 | 0.0196 |
| Income (z-score) | −0.92 | 1.15 | 4.2849 |
| BMI (z-score) | −1.20 | 0.95 | 4.6225 |
| Euclidean d | 2.99 — income और BMI दोनों का योगदान | ||
Scaling कितना मायने रखता है? एक असली Test
Feature Scaling —— 对 KNN 必不可少
KNN 的核心操作就是测量 distance。数值很大的 feature 会主导每一个 distance, 让其他 features 几乎看不见。
| Feature | 病人 A | 病人 B | 差² |
|---|---|---|---|
| 年龄(岁) | 30 | 31 | 1 |
| 收入(£) | 30,000 | 70,000 | 1,600,000,000 |
| BMI | 22.5 | 35.0 | 156.25 |
| Euclidean d | 40,000.00 —— 几乎全部来自收入 | ||
| Feature | 病人 A | 病人 B | 差² |
|---|---|---|---|
| 年龄(z-score) | −0.85 | −0.71 | 0.0196 |
| 收入(z-score) | −0.92 | 1.15 | 4.2849 |
| BMI(z-score) | −1.20 | 0.95 | 4.6225 |
| Euclidean d | 2.99 —— 收入和 BMI 都有贡献 | ||
Scaling 到底有多重要?一个真实的测试
from sklearn.datasets import load_wine
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
from sklearn.pipeline import make_pipeline
X, y = load_wine(return_X_y=True) # 13 chemical features of 178 wines
print(f"Feature ranges: from {X.max(axis=0).min():.2f} (largest value of the smallest feature)"
f" to {X.max(axis=0).max():.0f} (proline)")
models = {
"No scaling": KNeighborsClassifier(n_neighbors=5),
"StandardScaler": make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5)),
"MinMaxScaler": make_pipeline(MinMaxScaler(), KNeighborsClassifier(n_neighbors=5)),
"RobustScaler": make_pipeline(RobustScaler(), KNeighborsClassifier(n_neighbors=5)),
}
for name, model in models.items():
print(f"{name:15s} CV accuracy = {cross_val_score(model, X, y, cv=5).mean():.4f}")
Without scaling, KNN gets only 69% on the Wine data: the “proline” feature (values up to 1,680) controls every distance. Any of the three scalers lifts accuracy to about 94–95%.
Try It — Scaling Matters
Without scaling, the 7 “nearest” neighbours of the ★ form a thin vertical strip — near in income, but spread over many ages. Tick StandardScaler first.
Scaling के बिना KNN को Wine data पर सिर्फ़ 69% मिलता है: “proline” feature (values 1,680 तक) हर distance को control करता है। तीनों में से कोई भी scaler accuracy को लगभग 94–95% तक ले जाता है।
ख़ुद आज़माइए — Scaling Matters
Scaling के बिना ★ के 7 “nearest” neighbours एक पतली खड़ी पट्टी बनाते हैं — income में पास, पर कई उम्रों में फैले हुए। पहले StandardScaler पर tick कीजिए।
不做 scaling 时,KNN 在 Wine 数据上只有 69%:“proline”这个 feature(值最高到 1,680)控制了每一个 distance。三种 scaler 中任何一种,都能把 accuracy 提高到大约 94–95%。
动手试试 —— Scaling Matters
不做 scaling 时,★ 的 7 个“最近的” neighbours 排成一条细细的竖条 —— 收入上很近,年龄却分散得很开。勾选先做 StandardScaler。
The Curse of Dimensionality
In 100 dimensions, your “nearest” neighbour is almost as far away as your farthest one. When all distances are nearly the same, “nearest” carries little information and KNN loses its signal. This is the curse of dimensionality — a main reason KNN struggles on high-dimensional data.
| Dimensions | Points to fill a grid with 10 cells per feature | Nearest vs farthest distance | KNN usefulness |
|---|---|---|---|
| 1D | 10 | Very different | Excellent |
| 2D | 100 | Clearly different | Very good |
| 10D | 10¹⁰ | Getting similar | Declining |
| 100D | 10¹⁰⁰ | Nearly the same | Weak — unless the data really lives in fewer dimensions |
| 1000D | 10¹⁰⁰⁰ | Almost identical (random data) | Needs dimensionality reduction |
Here is the effect measured on 1,000 random points:
Curse of Dimensionality
100 dimensions में आपका “nearest” neighbour लगभग उतना ही दूर है जितना सबसे दूर वाला। जब सारी distances लगभग बराबर हों, तो “nearest” से बहुत कम जानकारी मिलती है और KNN अपना signal खो देता है। यही curse of dimensionality है — ज़्यादा dimensions वाले data पर KNN की मुश्किल की एक बड़ी वजह।
| Dimensions | हर feature में 10 cells वाला grid भरने के लिए points | सबसे पास बनाम सबसे दूर की distance | KNN कितना काम का |
|---|---|---|---|
| 1D | 10 | बहुत अलग | बहुत बढ़िया |
| 2D | 100 | साफ़ तौर पर अलग | बहुत अच्छा |
| 10D | 10¹⁰ | मिलती-जुलती होने लगती हैं | घटता हुआ |
| 100D | 10¹⁰⁰ | लगभग एक जैसी | कमज़ोर — जब तक data असल में कम dimensions में न हो |
| 1000D | 10¹⁰⁰⁰ | लगभग बिल्कुल एक जैसी (random data) | Dimensionality reduction ज़रूरी |
1,000 random points पर मापा गया असर यह है:
Curse of Dimensionality
在 100 个 dimension 中,你“最近的” neighbour 几乎和最远的那个一样远。 当所有 distance 都差不多时,“最近”就没有多少信息量了,KNN 也就失去了信号。 这就是 curse of dimensionality —— KNN 在高 dimension 数据上吃力的一个主要原因。
| Dimension 数 | 每个 feature 分 10 格时,填满网格需要的点数 | 最近与最远的 distance | KNN 的用处 |
|---|---|---|---|
| 1D | 10 | 差别非常大 | 极好 |
| 2D | 100 | 差别明显 | 很好 |
| 10D | 10¹⁰ | 越来越接近 | 在下降 |
| 100D | 10¹⁰⁰ | 几乎一样 | 弱 —— 除非数据其实只分布在更少的 dimension 中 |
| 1000D | 10¹⁰⁰⁰ | 几乎完全相同(随机数据) | 需要 dimensionality reduction |
下面是在 1,000 个随机点上测量到的效果:
import numpy as np
rng = np.random.default_rng(0)
print(f"{'dims':>5} {'nearest':>9} {'farthest':>9} {'farthest/nearest':>17}")
for d in [1, 2, 10, 100, 1000]:
X = rng.random((1000, d)) # 1,000 random points in a unit cube
q = rng.random(d) # one random query point
dist = np.sqrt(((X - q) ** 2).sum(axis=1))
print(f"{d:>5} {dist.min():>9.3f} {dist.max():>9.3f} {dist.max() / dist.min():>17.1f}")
In 2D the farthest point is 67 times further away than the nearest. In 1,000 dimensions it is only 1.1 times further — almost every point is “equally near”. (Real data is less extreme because its features are related, but the trend is the same.)
Useless features cause the same problem. Each one adds random noise to every distance:
2D में सबसे दूर वाला point सबसे पास वाले से 67 गुना दूर है। 1,000 dimensions में यह सिर्फ़ 1.1 गुना दूर है — लगभग हर point “बराबर पास” है। (असली data इतना extreme नहीं होता, क्योंकि उसके features आपस में जुड़े होते हैं, पर रुझान वही रहता है।)
बेकार features भी यही समस्या पैदा करते हैं। हर एक feature हर distance में random noise जोड़ता है:
在 2D 中,最远的点的 distance 是最近的点的 67 倍。在 1,000 个 dimension 中,只有 1.1 倍 —— 几乎每个点都“一样近”。(真实数据没有这么极端,因为它的 features 之间互相有关联,但趋势是一样的。)
无用的 features 会造成同样的问题。每一个都会给每个 distance 加上随机噪声:
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
rng = np.random.default_rng(0)
X, y = make_classification(n_samples=600, n_features=5, n_informative=5, n_redundant=0, random_state=0)
for n_noise in [0, 5, 20, 50, 200]:
Xn = np.hstack([X, rng.normal(size=(len(X), n_noise))]) # add useless random columns
acc = cross_val_score(make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=7)),
Xn, y, cv=5).mean()
print(f"5 useful + {n_noise:3d} useless features -> CV accuracy {acc:.3f}")
Try It — The Curse of Dimensionality
Increase the number of dimensions and watch the distance histogram squeeze to the right. Then switch to useless features and watch KNN accuracy fall.
ख़ुद आज़माइए — The Curse of Dimensionality
Dimensions की संख्या बढ़ाइए और देखिए कि distance histogram दाईं ओर सिमटता जाता है। फिर बेकार features पर जाइए और KNN accuracy को गिरते देखिए।
动手试试 —— The Curse of Dimensionality
增加 dimension 的数量,看 distance 直方图向右挤成一团。然后切换到无用的 features,看 KNN 的 accuracy 下降。
1. Remove useless features (feature selection) — often the biggest win.
2. Reduce dimensions with PCA inside the pipeline, and tune the number of components with cross-validation together with K.
UMAP can also be used (it can transform new data); t-SNE cannot, because it has no way to map new points, so it is only for visualisation.
3. For text, use cosine distance on TF-IDF or embedding vectors.
Python Implementation
KNN Classification — Iris Dataset
1. बेकार features हटाइए (feature selection) — अक्सर सबसे बड़ा फ़ायदा यहीं होता है।
2. Pipeline के अंदर PCA से dimensions घटाइए, और components की संख्या को K के साथ cross-validation से tune कीजिए।
UMAP भी इस्तेमाल हो सकता है (यह नए data को transform कर सकता है); t-SNE नहीं, क्योंकि उसके पास नए points को map करने का कोई तरीक़ा नहीं, इसलिए वह सिर्फ़ visualisation के लिए है।
3. Text के लिए TF-IDF या embedding vectors पर cosine distance इस्तेमाल कीजिए।
Python Implementation
KNN Classification — Iris Dataset
1. 去掉无用的 features(feature selection)—— 往往收获最大。
2. 降低 dimension:在 pipeline 里用 PCA,并用 cross-validation 把 component 的数量和 K 一起调。
也可以用 UMAP(它能变换新数据);t-SNE 不行,因为它没有办法映射新的点,所以只能用来做可视化。
3. 对于文本,在 TF-IDF 或 embedding 向量上使用 cosine distance。
Python 实现
KNN Classification —— Iris Dataset
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
# StandardScaler inside the Pipeline: the scaler only ever sees training data
pipe = Pipeline([
("scaler", StandardScaler()),
("knn", KNeighborsClassifier(
n_neighbors=5, # K - tune with cross-validation
metric="euclidean", # distance function
weights="uniform", # every neighbour gets one vote
algorithm="auto", # let scikit-learn pick brute / kd_tree / ball_tree
)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(f"Accuracy: {pipe.score(X_test, y_test):.4f}\n")
print(classification_report(y_test, y_pred, target_names=iris.target_names, digits=3))
# predict_proba = share of the K neighbours in each class
y_prob = pipe.predict_proba(X_test)
for i in range(5):
print(f" Sample {i+1}: {iris.target_names[y_pred[i]]:12s} ({y_prob[i].max()*100:3.0f}% of neighbours agree)")
93.3% on the 30 test flowers (two versicolor/virginica mix-ups). Sample 2 shows the uncertainty well: only 3 of its 5 neighbours (60%) agree.
Finding the Best K with Cross-Validation
30 test फूलों पर 93.3% (दो बार versicolor/virginica में गड़बड़)। Sample 2 अनिश्चितता को अच्छे से दिखाता है: उसके 5 में से सिर्फ़ 3 neighbours (60%) सहमत हैं।
Cross-Validation से सबसे अच्छा K ढूँढना
在 30 朵 test 花上达到 93.3%(有两次把 versicolor 和 virginica 弄混了)。样本 2 很好地显示了不确定性:它的 5 个 neighbours 中只有 3 个(60%)意见一致。
用 Cross-Validation 找到最好的 K
from sklearn.model_selection import cross_val_score
k_scores = []
for k in range(1, 32, 2): # odd K: 1, 3, 5, ... 31
pipe_k = Pipeline([("scaler", StandardScaler()),
("knn", KNeighborsClassifier(n_neighbors=k))])
scores = cross_val_score(pipe_k, X_train, y_train, cv=5, scoring="accuracy")
k_scores.append((k, scores.mean(), scores.std()))
best = max(s[1] for s in k_scores)
print(f"{'K':>4} {'CV accuracy':>12} {'Std':>8}")
print("-" * 28)
for k, mean, std in k_scores:
print(f"{k:>4} {mean:>12.4f} {std:>8.4f}{' <- best' if mean == best else ''}")
best_k = min(k for k, m, s in k_scores if m == best) # on ties, take the smallest K
print(f"\nSmallest K with the best CV score: {best_k}")
K = 5 is best (96.7%), and K between 3 and 17 is almost as good. Above K ≈ 21 the score drops — with only about 40 flowers per class, large K starts mixing in the other species.
KNN Regression — House Prices
K = 5 सबसे अच्छा है (96.7%), और 3 से 17 के बीच का K लगभग उतना ही अच्छा है। K ≈ 21 के ऊपर score गिरता है — हर class में सिर्फ़ लगभग 40 फूल हैं, इसलिए बड़ा K दूसरी species को भी मिलाने लगता है।
KNN Regression — मकानों की क़ीमतें
K = 5 最好(96.7%),K 在 3 到 17 之间也差不多一样好。K 超过大约 21 之后,分数下降 —— 每个 class 只有大约 40 朵花,K 太大就会混进别的品种。
KNN Regression —— 房价
import pandas as pd
from sklearn.neighbors import KNeighborsRegressor
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import r2_score, mean_absolute_error
# California housing (1990 census), 20,640 districts
url = "https://raw.githubusercontent.com/ageron/handson-ml2/master/datasets/housing/housing.csv"
df = pd.read_csv(url)
X = df.drop(columns=["median_house_value", "ocean_proximity"])
y = df["median_house_value"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
for weights in ["uniform", "distance"]:
knn_reg = Pipeline([
("impute", SimpleImputer(strategy="median")), # KNN cannot handle missing values
("scaler", StandardScaler()),
("knn", KNeighborsRegressor(n_neighbors=10, weights=weights)),
])
knn_reg.fit(X_train, y_train)
y_pred = knn_reg.predict(X_test)
print(f"weights={weights:8s} R² = {r2_score(y_test, y_pred):.4f} "
f"MAE = ${mean_absolute_error(y_test, y_pred):,.0f}")
Distance weighting helps a little here (R² 0.708 → 0.715). Note the SimpleImputer: KNN cannot work with missing values,
and this dataset has 207 missing total_bedrooms. A Random Forest on the same data reaches R² ≈ 0.81, so KNN is not the best model for this table.
Implementing KNN from Scratch
यहाँ distance weighting थोड़ी मदद करती है (R² 0.708 → 0.715)। SimpleImputer पर ध्यान दीजिए: KNN missing values के साथ काम नहीं कर सकता,
और इस dataset में 207 total_bedrooms missing हैं। इसी data पर एक Random Forest R² ≈ 0.81 तक पहुँचता है, इसलिए इस table के लिए KNN सबसे अच्छा model नहीं है।
KNN को शुरू से ख़ुद बनाना
这里 distance weighting 稍微有一点帮助(R² 0.708 → 0.715)。注意 SimpleImputer:KNN 不能处理缺失值,
而这个 dataset 有 207 个缺失的 total_bedrooms。在同样的数据上,Random Forest 能达到 R² ≈ 0.81,所以 KNN 不是这张表最好的 model。
从零开始实现 KNN
import numpy as np
from collections import Counter
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
def euclidean_distance(x1, x2):
"""Straight-line distance between two points."""
return np.sqrt(np.sum((x1 - x2) ** 2))
class KNNClassifier:
def __init__(self, k=5):
self.k = k
def fit(self, X, y):
"""No real training - just remember the data."""
self.X_train, self.y_train = X, y
return self
def predict(self, X):
return np.array([self._predict_one(x) for x in X])
def _predict_one(self, x):
distances = [euclidean_distance(x, x_tr) for x_tr in self.X_train] # 1. all distances
k_idx = np.argsort(distances)[:self.k] # 2. K smallest
k_labels = self.y_train[k_idx] # 3. their labels
return Counter(k_labels).most_common(1)[0][0] # 4. majority vote
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.2, random_state=42, stratify=iris.target)
sc = StandardScaler().fit(X_train)
X_train_sc, X_test_sc = sc.transform(X_train), sc.transform(X_test)
ours = KNNClassifier(k=5).fit(X_train_sc, y_train).predict(X_test_sc)
skl = KNeighborsClassifier(n_neighbors=5).fit(X_train_sc, y_train).predict(X_test_sc)
print(f"Scratch KNN accuracy: {np.mean(ours == y_test):.4f}")
print(f"scikit-learn KNN accuracy: {np.mean(skl == y_test):.4f}")
print(f"Same predictions: {np.array_equal(ours, skl)}")
Our 15-line version makes exactly the same predictions as scikit-learn. The algorithm really is that simple.
Weighted KNN — Closer Neighbours Vote Louder
Standard KNN gives every neighbour one equal vote, whatever its distance. A neighbour at distance 0.01 counts as much as one at distance 5. Weighted KNN gives each neighbour a vote proportional to 1 / distance — closer means more influence.
weights='distance'. Side effect: train accuracy is always 100%, because each training point is its own neighbour at distance 0 — so judge it only with cross-validation.हमारा 15 lines वाला version ठीक वही predictions देता है जो scikit-learn देता है। Algorithm सच में इतना ही simple है।
Weighted KNN — पास वाले Neighbours का Vote ज़्यादा भारी
Standard KNN हर neighbour को एक बराबर vote देता है, उसकी distance चाहे जो हो। 0.01 distance वाला neighbour उतना ही गिना जाता है जितना 5 distance वाला। Weighted KNN हर neighbour को 1 / distance के अनुपात में vote देता है — जितना पास, उतना ज़्यादा असर।
weights='distance' इस्तेमाल कीजिए। Side effect: train accuracy हमेशा 100% होती है, क्योंकि हर training point distance 0 पर अपना ही neighbour है — इसलिए इसे सिर्फ़ cross-validation से परखिए।我们这个 15 行的版本,和 scikit-learn 给出的预测完全相同。这个算法真的就是这么简单。
Weighted KNN —— 越近的 Neighbour,Vote 越有分量
标准的 KNN 给每个 neighbour 同样的一个 vote,不管它的 distance 是多少。 distance 为 0.01 的 neighbour,和 distance 为 5 的 neighbour 分量一样。 Weighted KNN 给每个 neighbour 的 vote 和 1 / distance 成正比 —— 越近,影响越大。
weights='distance'。副作用:train accuracy 永远是 100%,因为每个 training 点都是它自己的 neighbour,distance 为 0 —— 所以只能用 cross-validation 来评判它。from sklearn.model_selection import GridSearchCV
pipe = Pipeline([("scaler", StandardScaler()), ("knn", KNeighborsClassifier())])
param_grid = {
"knn__n_neighbors": [3, 5, 7, 9, 11, 15, 21],
"knn__weights": ["uniform", "distance"],
"knn__metric": ["euclidean", "manhattan"],
}
grid = GridSearchCV(pipe, param_grid, cv=5, scoring="accuracy", n_jobs=-1)
grid.fit(X_train, y_train)
print("Best parameters: ", grid.best_params_)
print(f"Best CV accuracy: {grid.best_score_:.4f}")
print(f"Test accuracy: {grid.score(X_test, y_test):.4f}")
n_best = (grid.cv_results_["rank_test_score"] == 1).sum()
print(f"Settings tied for first place: {n_best} of {len(grid.cv_results_['params'])}")
On Iris, the simple setting wins: Euclidean, K = 5, uniform weights — and 7 of the 28 settings tie for first place. Distance weighting is worth trying, but it is not automatically better.
Search Algorithms — How scikit-learn Finds Neighbours Fast
The default 'auto' uses brute force when the data is sparse, the metric is not supported by the trees,
K is large compared with n, or there are more than 15 features. Otherwise it uses a KD-tree (or a ball-tree for metrics the KD-tree does not support).
Override it only after a benchmark.
Iris पर simple setting जीतती है: Euclidean, K = 5, uniform weights — और 28 में से 7 settings पहले नंबर पर बराबर हैं। Distance weighting आज़माने लायक है, पर यह अपने-आप बेहतर नहीं होती।
Search Algorithms — scikit-learn Neighbours को तेज़ी से कैसे ढूँढता है
Default 'auto' brute force इस्तेमाल करता है जब data sparse हो, metric trees में support न हो,
K, n के मुक़ाबले बड़ा हो, या 15 से ज़्यादा features हों। वरना यह KD-tree इस्तेमाल करता है (या उन metrics के लिए ball-tree जिन्हें KD-tree support नहीं करता)।
इसे सिर्फ़ benchmark करने के बाद ही बदलिए।
在 Iris 上,简单的设置赢了:Euclidean、K = 5、uniform weights —— 而且 28 种设置中有 7 种并列第一。 distance weighting 值得试一试,但它不会自动就更好。
搜索算法 —— scikit-learn 怎样快速找到 Neighbours
默认的 'auto' 在这些情况下使用 brute force:数据是稀疏的、树不支持这个 metric、
K 相对 n 很大,或者 features 超过 15 个。其他情况下,它使用 KD-tree(对 KD-tree 不支持的 metric,就用 ball-tree)。
只有做过 benchmark 之后,才去改它。
import time
import numpy as np
from sklearn.neighbors import NearestNeighbors
rng = np.random.default_rng(0)
for d in [3, 50]:
X = rng.normal(size=(100_000, d))
Q = rng.normal(size=(2_000, d))
for algo in ["brute", "kd_tree", "ball_tree"]:
nn = NearestNeighbors(n_neighbors=5, algorithm=algo)
t0 = time.perf_counter(); nn.fit(X); t_fit = time.perf_counter() - t0
t0 = time.perf_counter(); nn.kneighbors(Q); t_q = time.perf_counter() - t0
print(f"d={d:>2} {algo:9s} build: {t_fit:6.3f}s 2,000 queries: {t_q:6.3f}s")
auto = NearestNeighbors(n_neighbors=5).fit(X)
print(f"d={d:>2} 'auto' chose: {auto._fit_method}\n")
In 3 dimensions the KD-tree answers 2,000 queries about 30× faster than brute force.
In 50 dimensions it is about 30× slower — the boxes no longer help — and 'auto' correctly chooses brute force.
Try It — How a KD-Tree Finds Neighbours
Press Build the tree to watch the space being split. Then click anywhere: the yellow boxes are the only ones the search had to open.
3 dimensions में KD-tree 2,000 queries का जवाब brute force से लगभग 30× तेज़ देता है।
50 dimensions में यह लगभग 30× धीमा है — boxes अब मदद नहीं करते — और 'auto' सही तरह से brute force चुनता है।
ख़ुद आज़माइए — How a KD-Tree Finds Neighbours
Tree बनाएँ दबाइए और space को बँटते हुए देखिए। फिर कहीं भी click कीजिए: पीले boxes ही वे boxes हैं जिन्हें search को खोलना पड़ा।
在 3 个 dimension 中,KD-tree 回答 2,000 个 query,比 brute force 大约快 30 倍。
在 50 个 dimension 中,它大约慢 30 倍 —— 盒子不再有帮助 —— 而 'auto' 正确地选择了 brute force。
动手试试 —— How a KD-Tree Finds Neighbours
按构建树,看空间被一步步切开。然后点击任意位置:黄色的盒子,就是搜索唯一需要打开的盒子。
Full Pipeline — Breast Cancer Diagnosis
A complete, honest pipeline: scaling, optional PCA, and K — all tuned together with cross-validation, then tested once.
पूरी Pipeline — Breast Cancer Diagnosis
एक पूरी, ईमानदार pipeline: scaling, ज़रूरत हो तो PCA, और K — सब cross-validation से एक साथ tune, फिर सिर्फ़ एक बार test।
完整的 Pipeline —— 乳腺癌诊断
一个完整而诚实的 pipeline:scaling、可选的 PCA 和 K —— 全部用 cross-validation 一起调,然后只 test 一次。
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report
cancer = load_breast_cancer()
X, y = cancer.data, cancer.target # 30 features; 0 = malignant, 1 = benign
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
pipe = Pipeline([
("scaler", StandardScaler()),
("pca", PCA(random_state=42)),
("knn", KNeighborsClassifier()),
])
# Tune the number of PCA components together with K ('passthrough' = no PCA)
param_grid = {
"pca": ["passthrough", PCA(n_components=5), PCA(n_components=10)],
"knn__n_neighbors": [3, 5, 7, 9, 11],
"knn__weights": ["uniform", "distance"],
}
grid = GridSearchCV(pipe, param_grid, cv=5, scoring="f1", n_jobs=-1)
grid.fit(X_train, y_train)
print("Best:", {k: str(v) for k, v in grid.best_params_.items()})
print(f"Best 5-fold CV F1: {grid.best_score_:.4f}")
print(f"Test accuracy: {grid.best_estimator_.score(X_test, y_test):.4f}\n")
print(classification_report(y_test, grid.predict(X_test), target_names=cancer.target_names, digits=3))
The best setting reduces 30 features to 5 PCA components with K = 3 (CV F1 0.978). On the 114 test patients it reaches 94.7% accuracy, but it misses 4 of 42 malignant tumours (recall 0.905). In a medical setting that recall matters more than accuracy — you might lower the decision threshold, or prefer a model with higher recall.
When to Use KNN
KNN vs Other Classifiers
| Property | KNN | Random Forest | SVM (RBF) | Logistic Reg. |
|---|---|---|---|---|
| Training time | Almost none | Moderate | Slow on large data | Fast |
| Prediction time | Slow — up to O(n × d) per query | Moderate | Moderate | Fastest |
| Memory usage | Stores all data | Stores trees | Stores support vectors | Just coefficients |
| Feature scaling needed | Essential | No | Essential | Recommended |
| Non-linear boundaries | Naturally | Excellent | Via kernels | Only with added features |
| High-dimensional data | Struggles — curse of dimensionality | Moderate | Good | Good |
| Interpretability | High — show the neighbours | Feature importance | Low with RBF | High — coefficients |
| Adding new data | Easy — just add points (search tree is rebuilt) | Retrain | Retrain | Possible (SGD, partial_fit) |
Strengths vs Weaknesses
| Almost no training time |
| Very simple to understand and explain |
| Handles many classes naturally |
| Few assumptions — only that similar points have similar labels |
| Non-parametric — can learn any boundary shape |
| New data can be added without retraining a model |
| Predictions are explainable — show the actual neighbours |
| Same idea for classification and regression |
| Slow predictions on big data |
| Stores the entire training set — high memory use |
| Feature scaling is essential — forgetting it is costly |
| Struggles in high dimensions — curse of dimensionality |
| Sensitive to irrelevant features — all features count equally |
| Sensitive to class imbalance — the majority class wins more votes |
| No built-in feature importance |
| Cannot handle missing values without imputation |
Real-World Applications
- User-based collaborative filtering
- “Users like you also watched…”
- Item-to-item similarity
- Classic approach in early recommenders
- Find similar patient records
- Spot unusual vital signs (anomaly detection)
- Case-based reasoning
- Explainable — show real cases
- MNIST digits: about 97% with plain KNN
- Reverse image search on embeddings
- Semantic search in vector databases
- Face verification on face embeddings
Golden Rules
StandardScaler() inside a Pipeline to avoid leakage.
On the Wine data this one step raised accuracy from 69% to 95%.
weights='uniform' and weights='distance'.
Distance weights often help when neighbours are at very different distances, but not always (on Iris, uniform won).
Let cross-validation decide — and ignore the 100% training accuracy that distance weights always give.
PCA in the pipeline (scaler → PCA → KNN) and tune n_components together with K.
On the breast cancer data, 5 components beat all 30 features.
SelectKBest, or feature importance from another model.
class_weight option,
so oversample the minority class (e.g. SMOTE from imbalanced-learn, applied only to training data),
try distance weights, or tune the decision threshold on predict_proba. Evaluate with F1, recall or AUC — not accuracy.
सबसे अच्छी setting 30 features को K = 3 के साथ 5 PCA components तक घटा देती है (CV F1 0.978)। 114 test मरीज़ों पर यह 94.7% accuracy पाती है, पर 42 में से 4 malignant tumours छोड़ देती है (recall 0.905)। Medical setting में accuracy से ज़्यादा यह recall मायने रखता है — आप decision threshold घटा सकते हैं, या ज़्यादा recall वाला model चुन सकते हैं।
KNN कब इस्तेमाल करें
KNN बनाम दूसरे Classifiers
| ख़ासियत | KNN | Random Forest | SVM (RBF) | Logistic Reg. |
|---|---|---|---|---|
| Training का समय | लगभग शून्य | मध्यम | बड़े data पर धीमा | तेज़ |
| Prediction का समय | धीमा — हर query पर O(n × d) तक | मध्यम | मध्यम | सबसे तेज़ |
| Memory का इस्तेमाल | सारा data store करता है | Trees store करता है | Support vectors store करता है | बस coefficients |
| Feature scaling की ज़रूरत | ज़रूरी | नहीं | ज़रूरी | सलाह दी जाती है |
| Non-linear boundaries | स्वाभाविक रूप से | बहुत बढ़िया | Kernels के ज़रिए | सिर्फ़ अतिरिक्त features के साथ |
| ज़्यादा dimensions वाला data | मुश्किल — curse of dimensionality | मध्यम | अच्छा | अच्छा |
| समझने में आसानी | ज़्यादा — neighbours दिखा दीजिए | Feature importance | RBF के साथ कम | ज़्यादा — coefficients |
| नया data जोड़ना | आसान — बस points जोड़िए (search tree दोबारा बनता है) | दोबारा train | दोबारा train | संभव (SGD, partial_fit) |
ख़ूबियाँ बनाम कमज़ोरियाँ
| Training में लगभग कोई समय नहीं |
| समझने और समझाने में बहुत आसान |
| बहुत सारी classes को स्वाभाविक रूप से संभालता है |
| कम मान्यताएँ — बस यह कि मिलते-जुलते points के labels भी मिलते-जुलते होते हैं |
| Non-parametric — किसी भी आकार की boundary सीख सकता है |
| Model को दोबारा train किए बिना नया data जोड़ा जा सकता है |
| Predictions समझाई जा सकती हैं — असली neighbours दिखा दीजिए |
| Classification और regression के लिए एक ही विचार |
| बड़े data पर धीमी predictions |
| पूरा training set store करता है — ज़्यादा memory |
| Feature scaling ज़रूरी है — भूलना महँगा पड़ता है |
| ज़्यादा dimensions में मुश्किल — curse of dimensionality |
| बेमतलब features से प्रभावित — सारे features बराबर गिने जाते हैं |
| Class imbalance से प्रभावित — majority class ज़्यादा votes जीतती है |
| कोई built-in feature importance नहीं |
| Imputation के बिना missing values नहीं संभाल सकता |
असल ज़िंदगी में इस्तेमाल
- User-based collaborative filtering
- “आप जैसे users ने यह भी देखा…”
- Item-to-item similarity
- शुरुआती recommenders का पुराना तरीक़ा
- मिलते-जुलते मरीज़ों के records ढूँढना
- असामान्य vital signs पकड़ना (anomaly detection)
- Case-based reasoning
- समझाने लायक — असली cases दिखाइए
- MNIST digits: सादे KNN से लगभग 97%
- Embeddings पर reverse image search
- Vector databases में semantic search
- Face embeddings पर face verification
सुनहरे नियम
StandardScaler() को Pipeline के अंदर रखिए।
Wine data पर इस एक कदम ने accuracy 69% से 95% कर दी।
weights='uniform' और weights='distance' दोनों आज़माइए।
जब neighbours बहुत अलग-अलग distances पर हों, तो distance weights अक्सर मदद करते हैं, पर हमेशा नहीं (Iris पर uniform जीता)।
Cross-validation को तय करने दीजिए — और उस 100% training accuracy को नज़रअंदाज़ कीजिए जो distance weights हमेशा देते हैं।
PCA रखिए (scaler → PCA → KNN) और n_components को K के साथ tune कीजिए।
Breast cancer data पर 5 components ने सारे 30 features को पीछे छोड़ दिया।
SelectKBest, या किसी दूसरे model की feature importance इस्तेमाल कीजिए।
class_weight option नहीं है,
इसलिए minority class को oversample कीजिए (जैसे imbalanced-learn से SMOTE, सिर्फ़ training data पर),
distance weights आज़माइए, या predict_proba पर decision threshold tune कीजिए। F1, recall या AUC से जाँचिए — accuracy से नहीं।
最好的设置把 30 个 features 降到 5 个 PCA component,K = 3(CV F1 0.978)。 在 114 个 test 病人上,它达到 94.7% 的 accuracy,但在 42 个恶性肿瘤中漏掉了 4 个(recall 0.905)。 在医疗场景中,这个 recall 比 accuracy 更重要 —— 你可以降低决策阈值,或者选一个 recall 更高的 model。
什么时候用 KNN
KNN 与其他 Classifier 的比较
| 比较项 | KNN | Random Forest | SVM (RBF) | Logistic Reg. |
|---|---|---|---|---|
| Training 时间 | 几乎为零 | 中等 | 大数据上很慢 | 快 |
| 预测时间 | 慢 —— 每个 query 最多 O(n × d) | 中等 | 中等 | 最快 |
| 内存占用 | 存下所有数据 | 存下所有树 | 存下 support vectors | 只存系数 |
| 需要 feature scaling 吗 | 必不可少 | 不需要 | 必不可少 | 建议做 |
| 非线性边界 | 天然支持 | 极好 | 通过 kernel | 只能靠额外加的 features |
| 高 dimension 数据 | 吃力 —— curse of dimensionality | 中等 | 好 | 好 |
| 可解释性 | 高 —— 展示 neighbours | feature importance | 用 RBF 时低 | 高 —— 看系数 |
| 加入新数据 | 容易 —— 直接加点(搜索树会重建) | 重新训练 | 重新训练 | 可以(SGD、partial_fit) |
优点与缺点
| 几乎不需要 training 时间 |
| 非常容易理解和解释 |
| 天然能处理很多个 class |
| 假设很少 —— 只假设相似的点有相似的 label |
| Non-parametric —— 能学会任何形状的边界 |
| 加入新数据,不需要重新训练 model |
| 预测可以解释 —— 展示真实的 neighbours |
| classification 和 regression 用的是同一个思路 |
| 在大数据上预测很慢 |
| 要存下整个 training set —— 内存占用高 |
| feature scaling 必不可少 —— 忘了代价很大 |
| 在高 dimension 中吃力 —— curse of dimensionality |
| 对无关的 features 敏感 —— 所有 features 分量相同 |
| 对 class 不平衡敏感 —— 多数 class 能得到更多 vote |
| 没有内置的 feature importance |
| 不做 imputation 就无法处理缺失值 |
实际应用
- 基于用户的 collaborative filtering
- “和你相似的用户还看了……”
- 物品与物品之间的相似度
- 早期推荐系统的经典方法
- 查找相似的病人记录
- 发现异常的生命体征(anomaly detection)
- 基于案例的推理
- 可以解释 —— 展示真实的病例
- MNIST 数字:普通 KNN 大约 97%
- 基于 embedding 的以图搜图
- 向量数据库中的语义搜索
- 基于人脸 embedding 的人脸验证
黄金法则
StandardScaler() 放进 Pipeline 里,避免数据泄漏。
在 Wine 数据上,仅这一步就把 accuracy 从 69% 提高到 95%。
weights='uniform' 和 weights='distance'。
当 neighbours 的 distance 差别很大时,distance weights 常常有帮助,但不总是这样(在 Iris 上,uniform 赢了)。
让 cross-validation 来决定 —— 并且不要理会 distance weights 总会给出的 100% training accuracy。
PCA 放进 pipeline(scaler → PCA → KNN),并把 n_components 和 K 一起调。
在乳腺癌数据上,5 个 component 胜过全部 30 个 features。
SelectKBest,或者另一个 model 给出的 feature importance。
class_weight 选项,
所以可以对少数 class 做过采样(例如用 imbalanced-learn 里的 SMOTE,只用在 training 数据上),
试试 distance weights,或者在 predict_proba 上调决策阈值。用 F1、recall 或 AUC 来评估 —— 不要用 accuracy。