對於有監督學習,我們知道其訓練數據形式為\(T=\left \{ (x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),\cdots ,(x^{(n)},y^{(n)})\right \}\),其中,\(x\)表示樣本實例,\(y\)表示樣本所屬類別。而對於無監督學習,訓練數據不提供對應的類別,訓練數據形式為\(T=\left \{ (x^{(1)}),(x^{(2)}),\cdots ,(x^{(n)})\right \}\)。這里,對無監督的聚類算法K-Means進行總結。
1 K-Means原理
K-Means作為聚類算法是如何完成聚類的呢?正如其算法名稱,\(K\)表示簇的個數,Means表示均值。(注: 在聚類中,把數據所在的集合稱為簇)。K-Means算法的基本思想是將訓練數據集按照各個樣本到聚類中心的距離分配到距離較近的\(K\)個簇中,然后計算每個簇中樣本的平均位置,將聚類中心移動到該位置。
根據上面K-Means算法的基本思想,K-Means算法可以總結成兩步:
- 簇分配:樣本分配到距離較近的簇
- 移動聚類中心:將聚類中心移動到簇中樣本平均值的位置
假設有\(K\)個簇\((C_{1},C_{2},\cdots ,C_{k})\),樣本距離簇類中心的距離的表達式為:
其中\(C_{i}\)是第1到\(K\)個最接近樣本\(x\)的聚類中心,\(\mu _{i}\)是該簇\(C_{i}\)中所有樣本點的均值組成的向量。\(\mu _{i}\)的表達式為:
舉例,如何計算\(\mu _{2}\)的聚類中心?假設被分配到了聚類中心2的4個樣本有:\(x^{(1)}\),\(x^{(5)}\),\(x^{(6)}\),\(x^{(10)}\),也就是\(C_{(1)}=2\),\(C_{(5)}=2\),\(C_{(6)}=2\),
\(C_{(10)}=2\),
\(n\)表示樣本的特征個數。
2 K-Means算法
K-Means算法: 簇分配和移動聚類中心
輸入: \(T=\left \{ x^{(1)},x^{(2)},\cdots ,x^{(m)} \right \}\),\(K\)
step1 隨機初始化\(K\)個聚類中心\(\mu _{1},\mu _{2},\cdots ,\mu _{k}\epsilon \mathbb{R}^{n}\)
step2 計算各個樣本點到聚類中心的距離,如果樣本距離第\(i\)個聚類中心更近,將其分配到第\(i\)簇
step3 計算每個簇中所有樣本點的平均位置,將聚類中心移動到該位置
step4 重復2-3直至各個聚類中心的位置不再發生變化
3 K-Means代碼實現
簇分配+移動聚類中心
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
#簇分配 將每一個訓練樣本分配給最接近的簇中心
def findClosestCentroids(X, centroids):
"""
output a one-dimensional array idx that holds the
index of the closest centroid to every training example.
"""
idx = []
max_dist = 1000000 # 限制一下最大距離
for i in range(len(X)):
minus = X[i] - centroids # here use numpy's broadcasting
dist = minus[:,0]**2 + minus[:,1]**2
if dist.min() < max_dist:
ci = np.argmin(dist)
idx.append(ci)
return np.array(idx)
mat = loadmat('D:/Python/Andrew-NG-Meachine-Learning/machine-learning-ex7/ex7/ex7data2.mat')
X = mat['X']
#print(X.shape) #(300, 2)
init_centroids = np.array([[3, 3], [6, 2], [8, 5]]) #自定義一個centroids,[3, 3], [6, 2], [8, 5]
idx = findClosestCentroids(X, init_centroids)
#print(idx) #[0 2 1 ..., 1 1 0]
#print(idx.shape) #(300,)
print(idx[0:3]) #[0 2 1] 計算idx[0:3] 也就是前三個樣本所屬簇的索引或者序號
#移動聚類中心 重新計算每個簇中心—這個簇中所有點位置的平均值
def computeCentroids(X, idx):
centroids = []
for i in range(len(np.unique(idx))): # np.unique() means K
u_k = X[idx==i].mean(axis=0) # 求每列的平均值
centroids.append(u_k)
return np.array(centroids)
print(computeCentroids(X, idx))
#輸出
[[ 2.42830111 3.15792418]
[ 5.81350331 2.63365645]
[ 7.11938687 3.6166844 ]]
#kmeans on an example dataset
def plotData(X, centroids, idx=None):
"""
可視化數據,並自動分開着色。
idx: 最后一次迭代生成的idx向量,存儲每個樣本分配的簇中心點的值
centroids: 包含每次中心點歷史記錄
"""
colors = ['b','g','gold','darkorange','salmon','olivedrab',
'maroon', 'navy', 'sienna', 'tomato', 'lightgray', 'gainsboro'
'coral', 'aliceblue', 'dimgray', 'mintcream', 'mintcream']
assert len(centroids[0]) <= len(colors), 'colors not enough '
subX = [] # 分號類的樣本點
if idx is not None:
for i in range(centroids[0].shape[0]):
x_i = X[idx == i]
subX.append(x_i)
else:
subX = [X] # 將X轉化為一個元素的列表,每個元素為每個簇的樣本集,方便下方繪圖
# 分別畫出每個簇的點,並着不同的顏色
plt.figure(figsize=(8,5))
for i in range(len(subX)):
xx = subX[i]
plt.scatter(xx[:,0], xx[:,1], c=colors[i], label='Cluster %d'%i)
plt.legend()
plt.grid(True)
plt.xlabel('x1',fontsize=14)
plt.ylabel('x2',fontsize=14)
plt.title('Plot of X Points',fontsize=16)
# 畫出簇中心點的移動軌跡
xx, yy = [], []
for centroid in centroids:
xx.append(centroid[:,0])
yy.append(centroid[:,1])
plt.plot(xx, yy, 'rx--', markersize=8)
plotData(X, [init_centroids])
運行效果:

查看聚類中心移動效果
def runKmeans(X, centroids, max_iters):
K = len(centroids)
centroids_all = []
centroids_all.append(centroids)
centroid_i = centroids
for i in range(max_iters):
idx = findClosestCentroids(X, centroid_i)
centroid_i = computeCentroids(X, idx)
centroids_all.append(centroid_i)
return idx, centroids_all
idx, centroids_all = runKmeans(X, init_centroids, 20)
plotData(X, centroids_all, idx)

對簇中心點進行初始化的一個好的策略就是從訓練集中選擇隨機的例子。
隨機初始化簇中心
def initCentroids(X, K):
"""隨機初始化"""
m, n = X.shape
idx = np.random.choice(m, K)
centroids = X[idx]
return centroids
#進行三次隨機初始化,看下各自的效果。會發現第三次的效果並不理想,這是正常的,落入了局部最優
for i in range(3):
centroids = initCentroids(X, 3)
idx, centroids_all = runKmeans(X, centroids, 10)
plotData(X, centroids_all, idx)
進行三次隨機初始化,看下各自的效果。看到第三次的效果並不理想,都落入了局部最優。



用K-Means進行圖像壓縮, 通過減少圖像中出現的顏色的數量 ,只剩下那些在圖像中最常見的顏色。
K-Means圖像壓縮
from skimage import io
import matplotlib.pyplot as plt
A = io.imread('D:/Python/Andrew-NG-Meachine-Learning/machine-learning-ex7/ex7/bird_small.png')
print(A.shape) #(128, 128, 3)
plt.imshow(A)
A = A/255 #Divide by 255 so that all values are in the range 0-1
X = A.reshape(-1, 3)
K = 16
centroids = initCentroids(X, K)
idx, centroids_all = runKmeans(X, centroids, 10)
img = np.zeros(X.shape)
centroids = centroids_all[-1]
for i in range(len(centroids)):
img[idx == i] = centroids[i]
img = img.reshape(128, 128, 3)
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
axes[0].imshow(A)
axes[1].imshow(img)


參考:吳恩達機器學習 吳恩達機器學習作業Python實現
