一條新評論,聽起來很像 Maya 已經標過的三條舊評論。k-NN 讓最近的鄰居投票。幾個鄰居才算合適,距離又該在什麼尺度上量?
最近鄰分類器通過找出離新點最近的訓練樣本,並對它們的標籤投票來預測。它沒有學習到的權重,卻仍然涉及核心問題:怎樣度量距離、用幾個鄰居,以及為什麼特徵尺度很重要。 你還會比較距離度量,並用留一驗證選擇 k。
度量點之間的距離
歐氏距離是各坐標差平方和的開方。zip 把坐標配對,這個表達式可以處理任意維數。函式要短,距離規則才容易檢查和替換。
def distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
print(distance((1, 1), (4, 5)))兩個維度,一個公式
給訓練點排序
為每個訓練點計算距離,再按距離排序。儲存 (距離, 標籤) 對可以讓標籤跟隨點移動。小資料集這樣就夠了;大規模集合在生產環境通常使用更快的索引結構。
def distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
points = [((1, 1), "a"), ((4, 4), "b"), ((1, 2), "a")]
target = (1, 1.5)
ranked = sorted(points, key=lambda item: distance(target, item[0]))
print([label for _, label in ranked])最近的標籤排在前面
進行多數投票
取 k 個鄰居,統計標籤並選擇出現最多的。Counter 讓投票過程更易讀。平票需要明確規則,例如選擇距離最近的平票鄰居,避免結果隨字典順序變化。
from collections import Counter
labels = ["a", "a", "b"]
winner = Counter(labels).most_common(1)[0][0]
print(winner)出現最多的標籤
度量前先縮放特徵
以千為單位的特徵會壓倒個位數特徵。計算距離前,應對每個特徵做標準化或歸一化。縮放參數只能在訓練資料上配適,再用同樣的變換處理新點。
def distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
print(round(distance((30, 30000), (31, 30000)), 1))
print(round(distance((30, 30000), (30, 31000)), 1))原始單位不可直接比較
距離度量會改變排序
歐氏距離對坐標差取平方;曼哈頓距離取絕對值之和。它們代表不同的「接近」含義,因此同樣一組點可能得到不同的鄰居順序。選擇與資料比對的度量,並用驗證結果判斷,而不是假定任何單一度量都放之四海皆準。
def euclidean(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
def manhattan(a, b):
return sum(abs(x - y) for x, y in zip(a, b))
points = [((0, 0), "a"), ((3, 3), "b"), ((1, 4), "a")]
target = (0, 2)
for name, metric in [("euclidean", euclidean), ("manhattan", manhattan)]:
ranked = sorted((metric(point, target), label) for point, label in points)
print(name, [(round(d, 2), label) for d, label in ranked])同一組點,不同的鄰居
用留一驗證選擇 k
較小的 k 容易追隨雜訊;較大的 k 可能抹平局部結構。留一法每次取出一個訓練點,用其餘點預測它,再重複。先在驗證訊號上比較 k,再接觸最終測試集。
def distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
points = [((0, 0), "a"), ((1, 1), "a"), ((5, 5), "b"), ((6, 5), "b")]
def predict(train, target, k):
ranked = sorted((distance(point, target), label) for point, label in train)
labels = [label for _, label in ranked[:k]]
return max(sorted(set(labels)), key=labels.count)
for k in [1, 3]:
correct = 0
for i, (point, label) in enumerate(points):
train = points[:i] + points[i + 1:]
correct += predict(train, point, k) == label
print(k, correct, "/", len(points))在最終測試前驗證 k
本課要點
- 最近鄰預測依賴到訓練樣本的距離。
- 儲存 (距離, 標籤) 對,預測才不會和點分離。
- k-NN 中的 k 決定參與投票的鄰居數量。
- 平票需要明確且確定的處理規則。
- 度量距離前要縮放特徵,否則大單位會佔主導。
- 距離度量包含假設;k 應通過驗證選擇,而不是只憑直覺。
常見錯誤
- 特徵單位不同卻不縮放,就直接計算距離。
- 讓平票結果依賴任意的字典或集合順序。
- 用訓練集本身評估分類器,卻當成未見資料。
- 在測試集上調 k,又把同一個分數當作無偏結果報告。
給定三個帶標籤的點和目標點,按距離排序,取最近標籤並列印。
動手試試
改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。
練習本課
12 道題,作答後立即看到解釋。