Python k-means 練習:分配、更新與評估
把分配、更新和評估拆成練習,k-means 就沒那麼神秘。這些例子只使用二維小資料,每個質心都能手工核對。
練習:計算平方歐氏距離
平方距離保留最近鄰排序,還能省去開方。只有當實際單位對報告很重要時,才需要歐氏距離。
def squared_distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b))
print(squared_distance((0, 0), (3, 4)))25 個平方單位
練習:給每個點分配簇
分配就是對所有點做列表推導。每個點選擇最近質心的索引。把索引也放進排序鍵,平局時結果才確定。
def distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
points = [(0, 0), (0, 1), (5, 5), (5, 6)]
centroids = [(0, 0), (5, 5)]
labels = [min(range(len(centroids)), key=lambda i: (distance(point, centroids[i]), i)) for point in points]
print(labels)每個簇兩個點
練習:更新一個質心
質心是成員在每個坐標上的平均值。必須等所有分配完成後才能更新,否則會用只完成一半的聚類結果。
points = [(0, 0), (0, 1), (5, 5), (5, 6)]
labels = [0, 0, 1, 1]
members = [point for point, label in zip(points, labels) if label == 0]
centroid = (
sum(x for x, _ in members) / len(members),
sum(y for _, y in members) / len(members),
)
print(tuple(round(value, 1) for value in centroid))簇 0 的平均值
練習:計算簇內平方和
簇內平方和是每個點到所屬質心的平方距離之和。數值越小簇越緊,但 k 增大時它一定下降,所以不能只看它選 k。
def squared_distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b))
points = [(0, 0), (0, 1), (5, 5), (5, 6)]
labels = [0, 0, 1, 1]
centroids = [(0, 0.5), (5, 5.5)]
inertia = sum(squared_distance(point, centroids[label]) for point, label in zip(points, labels))
print(round(inertia, 1))聚類有多緊?
練習:迭代並評估
反覆分配和更新,直到標籤不再變化,然後同時報告標籤與簇內平方和。多試幾種初始化,因為迴圈只保證局部最優。
def distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
points = [(0, 0), (0, 1), (5, 5), (5, 6)]
centroids = [(0, 0), (5, 5)]
labels = None
for _ in range(10):
new_labels = [min(range(len(centroids)), key=lambda i: (distance(point, centroids[i]), i)) for point in points]
new_centroids = []
for index in range(len(centroids)):
members = [point for point, label in zip(points, new_labels) if label == index]
new_centroids.append((
sum(x for x, _ in members) / len(members),
sum(y for _, y in members) / len(members),
) if members else centroids[index])
if new_labels == labels:
break
labels = new_labels
centroids = new_centroids
inertia = sum(distance(point, centroids[label]) ** 2 for point, label in zip(points, labels))
print(labels)
print(round(inertia, 1))標籤穩定後停止
你的練習清單
- 計算距離前先縮放特徵。
- 先完成所有點的分配,再更新任何質心。
- 用確定性規則處理距離平局。
- 顯式處理空叢集。
- 報告 k 與簇內平方和,並比較多種初始化。
常見問題
k 怎麼選?
沒有通用規則。可以結合領域知識、觀察不同 k 的簇內平方和,並判斷聚類對實際任務是否有用。
為什麼每次執行聚類都不同?
k-means 找到的是依賴初始化的局部最優。固定隨機種子,或執行多次並保留簇內平方和最小的結果。
縮放會改變 k-means 嗎?
會。距離把每個坐標同等看待,數值範圍更大的特徵可能主導分配。
練習 k-means 迴圈
12 道題,覆蓋距離、分配、質心更新、簇內平方和、縮放與收斂。
繼續閱讀: