最近看了吳恩達老師的機器學習教程(能夠在Coursera,或者網易雲課堂上找到)中講解的k-means聚類算法,k-means是一種應用很是普遍的無監督學習算法,使用比較簡單,但其背後的思想是EM算法(看李航老師統計學習方法看了半天仍是沒太明白,後面找了一篇博客,博主對EM算法講解很是通俗易懂)。這裏對k-means算法和應用作一個小筆記,腦殼記不住那麼多hh。本文用的數據和代碼見github.python
在介紹k-means算法以前,先看一個課程中使用k-means對二維數據進行聚類的小例子。下圖中(a)是原始樣本點,在(b)圖中隨機選取兩個點做爲質心,即k-means中的k取2,而後計算各樣本到質心的距離(通常用歐式距離),選擇距離小的一個質心做爲該樣本的一個類,如(c);以後再計算分好類的樣本的中心點。重複以上過程能夠看到效果如圖(f)。git
從上面的例子能夠看出k-means的工做流程是首先隨機選取k個初始點做爲質心,而後將數據中的每一個樣本點按照距離分配到一個簇中,以後再計算各簇中樣本點的中心,將其做爲質心,而後重複以上過程。k-means算法以下:github
一、 隨機選取k個聚類質心點(cluster centroids)爲。app
二、 重複下面過程直到收斂 {dom
對於每個樣例i,計算其應該屬於的類機器學習
對於每個類j,從新計算該類的質心學習
}
K是咱們事先給定的聚類數,表明樣例i與k個類中距離最近的那個類,
的值是1到k中的一個。質心
是屬於同一個類的樣本中心點。
k-means算法中要保證其是收斂的,定義損失函數如(3)式,表示每一個樣本點到其質心的平方和,k-means的優化目標是使
最小化如(4)式。假設當前目標沒有達到最小值,那麼首先能夠固定每一個類的質心
,調整每一個樣本的所屬的類別
來讓目標函數減小,一樣,固定
,調整每一個類的質心
也可使
減少。這兩個過程就是算法中循環使目標單調遞減的過程。當目標遞減到最小時,
和c也同時收斂。但(3)是非凸函數,因此k-means有可能不會達到全局最小值,而是收斂到局部最小值,這時咱們能夠屢次隨機選取質心初始值,而後對結果進行比較,選擇使目標最小的聚類和質心。
(3)
(4)
選擇不一樣的k值,而後分別計算目標函數(4)式的值,而後畫出目標函數值隨聚類k的變化狀況,若是圖像以下圖左邊的圖像所示,則選擇拐點即k=3(拐點能夠視爲手的肘部,稱爲肘部法則 hh)。可是若是變化狀況像右圖同樣,則沒有出現明顯的拐點,這時候肘部法則就不適用了(肘部法則不適用於全部狀況)。
能夠根據聚類的目的選擇相應的K值,好比T恤的大小與型號設置,若是選擇k=3,則能夠分爲S/M/L三種型號,若是k=5,則可將T恤分爲XS/S/M/L/XL。
課程中還留了k-means的練習,但裏面是使用MATLAB/Octave編寫的,一直用的python,這裏就利用python來完成這個練習算了。該練習有兩個題目,第一個題目是利用k-means對二維數據進行聚類,第二個題目是利用k-means對圖片進行壓縮。
第一步 數據存在ex7data2.mat文件中,這裏先引入相關庫,而後提取數據。
import pandas as pd import numpy as np from scipy.io import loadmat import matplotlib.pyplot as plt
mat = loadmat('./ex7/ex7data2.mat') print(mat)
第二步 根據(1)式定義根據質心對樣本聚類的函數findClosestCentroids。
def findClosestCentroids(Datas, centroids): # Datas:array, centroids:array max_dist = np.inf # 定義最大距離 clustering = [] # 儲存聚類結果 # 遍歷每一個樣本點 for i in range(len(Datas)): data = Datas[i] diff = data - centroids # 數據類型都爲np.array dist = 0 for j in range(len(diff[0])): dist += diff[:,j]**2 # 求歐式距離 min_index = np.argmin(dist) # 找出距離最小的下標 clustering.append(min_index) return np.array(clustering)
X = mat['X'] # get data centroids = np.array([[3,3], [6,2], [8,5]]) # 測試 clusted = findClosestCentroids(X, centroids) clusted[:5]
這裏k取3,定義質心爲[[3,3], [6,2], [8,5]],對數據進行測試,對應的聚類爲[0,2,1,0,0].
第三步 根據(2)式定義根據分類從新計算中心點的函數computMeans。
def computMeans(Datas, clustering): centroids = [] for i in range(len(np.unique(clustering))): # np.unique計算聚類個數 u_k = np.mean(Datas[clustering==i], axis=0) # 求每列的平均值 centroids.append(u_k) return np.array(centroids)
用以上的聚類結果對其進行測驗
centroids = computMeans(X, clusted)
centroids
第四步 定義展現最終聚類結果和中心點變化的函數plotdata。
# 定義可視化函數 def plotdata(data, centroids, clusted=None): # data:數據, centroids:迭代後全部中心點, clusted:最後一次聚類結果 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 are not enough ' # 檢查顏色和中心點維度 clust_data = [] # 存儲聚好類的數據,同一個類放在同一個列表中 if clusted is not None: for i in range(centroids[0].shape[0]): x_i = data[clusted==i] clust_data.append(x_i) # x_i is np.array else: clust_data = [data] # 未進行聚類,默認將其做爲一個類 # 用不一樣顏色繪製數據點 plt.figure(figsize=(8,5)) for i in range(len(clust_data)): plt.scatter(clust_data[i][:, 0], clust_data[i][:, 1], color=colors[i], label='cluster %d'%(i+1)) plt.legend() plt.xlabel('x', size=14) plt.ylabel('y', size=14) # 繪製中心點 centroid_x = [] centroid_y = [] for centroid in centroids: centroid_x.append(centroid[:,0]) centroid_y.append(centroid[:,1]) plt.plot(centroid_x, centroid_y, 'r*--', markersize=14) plt.show()
將數據集和初始質心帶入plotdata函數進行測試,畫出的是原始樣本點。
plotdata(X, [centroids])
第五步 進行訓練,迭代30次。
# 進行訓練 def run_k_means(Datas, centroids, iters): all_centroids = [centroids] for i in range(iters): clusted = findClosestCentroids(Datas, centroids) centroids = computMeans(Datas, clusted) all_centroids.append(centroids) return clusted, all_centroids clusted, all_centroids = run_k_means(X, np.array([[3,3], [6,2], [8,5]]), 30) plotdata(X, all_centroids, clusted)
以上過程選取的質心是本身給定的,實際應用中通常是隨機給定的。隨機給定方法中能夠先找出樣本在每一維度的最小值和最大值,而後每一維度選取最小值到最大值之間的數,不一樣維度合併成初始質心點。也能夠從樣本點中隨機選取k個質心。
# 方案一 先找出數據集每一列的最大值和最小值,而後在最大和最小之間隨機生成 def randCent(Datas, k): n = np.shape(Datas)[1] # 數據集維度 centroids = np.mat(np.zeros((k, n))) # 給質心賦0值 for i in range(n): min_i = min(Datas[:, i]) range_i = float(max(Datas[:, i]) - min_i) centroids[:, i] = min_i + range_i*np.random.rand(k, 1) return np.array(centroids)
randCent(X, 3)
# 方案二 從數據集去隨機選取K個樣本做爲初始質心 def randCent(Datas, k): n = Datas.shape[0] random_index = np.random.choice(n, k) centroids = Datas[random_index] return centroids randCent(X, 3)
在這個題目中看,用一個簡單的24位顏色表示圖像。每一個像素被表示爲三個8位無符號整數(從0到255),指定了紅、綠和藍色的強度值。這種編碼一般被稱爲RGB編碼。咱們的圖像包含數千種顏色,在這一部分的練習中,你將把顏色的數量減小到16種顏色,這能夠有效地壓縮照片。具體地說,您只須要存儲16個選中顏色的RGB值,而對於圖中的每一個像素,如今只須要將該顏色的索引存儲在該位置(只須要4 bits就能表示16種可能性)。 若是圖像是128×128的,那麼圖像通過壓縮後由原來的128×128×24 = 393,216 位變爲了 16 × 24 + 128 × 128 × 4 = 65,920 位。
接下來咱們要用K-means算法選16種顏色,用於圖片壓縮。你將把原始圖片的每一個像素看做一個數據樣本,而後利用K-means算法去找分組最好的16種顏色。
第一步 引入圖片(bird_small.png)
from skimage import io sample_image = io.imread('./ex7/bird_small.png') sample_image.shape
plt.imshow(sample_image)
plt.show()
第二步 隨機初始化質心
sample_image = sample_image/255 # 將數據歸一化到0-1 data = sample_image.reshape(-1, 3) # 將圖片像素大小重置,每個像素點表明一個樣本 print(data[:3]) print(data.shape) k = 16 # 聚類個數 centroids = randCent(data, k) # 隨機初始化質心 centroids
第三步 訓練
# 對其進行聚類, 迭代次數爲30次 clusted, all_centroids = run_k_means(data, centroids, 30)
第四步 重構圖片
img = np.zeros(data.shape) # 初始化圖片 last_centroids = all_centroids[-1] # 最後一聚類質心 for i in range(len(last_centroids)): # 利用聚類質心替換圖片中元素 img[clusted==i] = last_centroids[i] img = img.reshape(128, 128, 3) # 轉換大小
第五步 對比先後效果
# 繪製圖片 fig, axs = plt.subplots(1, 2, figsize=(10,6)) axs[0].imshow(sample_image) axs[1].imshow(img) plt.show()
優勢:容易實現
缺點:可能收斂到局部最小值,在大規模數據集上的收斂速度較慢。
適用數據類型:數值型數據