k-means學習筆記

        最近看了吳恩達老師的機器學習教程(能夠在Coursera,或者網易雲課堂上找到)中講解的k-means聚類算法,k-means是一種應用很是普遍的無監督學習算法,使用比較簡單,但其背後的思想是EM算法(看李航老師統計學習方法看了半天仍是沒太明白,後面找了一篇博客,博主對EM算法講解很是通俗易懂)。這裏對k-means算法和應用作一個小筆記,腦殼記不住那麼多hh。本文用的數據和代碼見github.python

1、k-means算法

        在介紹k-means算法以前,先看一個課程中使用k-means對二維數據進行聚類的小例子。下圖中(a)是原始樣本點,在(b)圖中隨機選取兩個點做爲質心,即k-means中的k取2,而後計算各樣本到質心的距離(通常用歐式距離),選擇距離小的一個質心做爲該樣本的一個類,如(c);以後再計算分好類的樣本的中心點。重複以上過程能夠看到效果如圖(f)。git

        從上面的例子能夠看出k-means的工做流程是首先隨機選取k個初始點做爲質心,而後將數據中的每一個樣本點按照距離分配到一個簇中,以後再計算各簇中樣本點的中心,將其做爲質心,而後重複以上過程。k-means算法以下:github

 

將數據集clip_image004分紅k個簇。算法

一、 隨機選取k個聚類質心點(cluster centroids)爲clip_image008[6]app

二、 重複下面過程直到收斂 {dom

               對於每個樣例i,計算其應該屬於的類機器學習

               clip_image009(1)函數

               對於每個類j,從新計算該類的質心學習

               clip_image010[6](2)測試

}

 K是咱們事先給定的聚類數,clip_image012[6]表明樣例i與k個類中距離最近的那個類,clip_image012[7]的值是1到k中的一個。質心clip_image014[6]是屬於同一個類的樣本中心點。

 

        k-means算法中要保證其是收斂的,定義損失函數如(3)式,表示每一個樣本點到其質心的平方和,k-means的優化目標是使最小化如(4)式。假設當前目標沒有達到最小值,那麼首先能夠固定每一個類的質心 clip_image014[8],調整每一個樣本的所屬的類別 clip_image012[9] 來讓目標函數減小,一樣,固定 clip_image012[10],調整每一個類的質心 clip_image014[9] 也可使減少。這兩個過程就是算法中循環使目標單調遞減的過程。當目標遞減到最小時,clip_image018[6]和c也同時收斂。但(3)是非凸函數,因此k-means有可能不會達到全局最小值,而是收斂到局部最小值,這時咱們能夠屢次隨機選取質心初始值,而後對結果進行比較,選擇使目標最小的聚類和質心。

 

(3)

            (4)

 

2、k的選擇(僅供參考)

  一、肘部法則

  選擇不一樣的k值,而後分別計算目標函數(4)式的值,而後畫出目標函數值隨聚類k的變化狀況,若是圖像以下圖左邊的圖像所示,則選擇拐點即k=3(拐點能夠視爲手的肘部,稱爲肘部法則 hh)。可是若是變化狀況像右圖同樣,則沒有出現明顯的拐點,這時候肘部法則就不適用了(肘部法則不適用於全部狀況)。

                     

 

  二、根據實際應用的目的選擇K

  能夠根據聚類的目的選擇相應的K值,好比T恤的大小與型號設置,若是選擇k=3,則能夠分爲S/M/L三種型號,若是k=5,則可將T恤分爲XS/S/M/L/XL。

 

3、k-means算法應用

        課程中還留了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()

 

4、k-means總結

優勢:容易實現

缺點:可能收斂到局部最小值,在大規模數據集上的收斂速度較慢。

適用數據類型:數值型數據

 

 

相關文章
相關標籤/搜索