k-means學習筆記


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

一、k-means算法

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

        從上面的例子可以看出k-means的工作流程是首先隨機選取k個初始點作為質心,然后將數據中的每個樣本點按照距離分配到一個簇中,之后再計算各簇中樣本點的中心,將其作為質心,然后重復以上過程。k-means算法如下:

 

將數據集clip_image004分成k個簇。

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

2、 重復下面過程直到收斂 {

               對於每一個樣例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)

 

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

  1、肘部法則

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

                     

 

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

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

 

三、k-means算法應用

        課程中還留了k-means的練習,但里面是使用MATLAB/Octave編寫的,一直用的python,這里就利用python來完成這個練習算了。該練習有兩個題目,第一個題目是利用k-means對二維數據進行聚類,第二個題目是利用k-means對圖片進行壓縮。

 1、第一題  二維數據聚類

第一步 數據存在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()

 

四、k-means總結

優點:容易實現

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

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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM