機器學習回顧篇(9):K-means聚類算法

 

注:本系列全部博客將持續更新併發布在githubgitee上,您能夠經過github、gitee下載本系列全部文章筆記文件。javascript

1 聚類

本文咱們來總結K-means算法。css

與以前介紹過的諸多分類算法不一樣,K-means算法屬於聚類算法的範疇。說到這裏,必須得因此說分類算法與聚類算法的區別。html

分類算法與聚類算法最本質的區別在於分類算法是一種有監督學習方法,這一類算法在真正用於實踐前,必須經過已知樣本標籤的數據進行訓練,以創建樣本特徵屬性到樣本標籤的最佳擬合模型;與之相反,無監督學習方法能夠在沒有任何先驗知識的狀況下進行分類,這種方法根據類似性原則,將具備較高類似度的數據對象劃分至同一類簇,將具備較高相異度的數據對象劃分至不一樣類簇,最終實現對數據集的分類。html5

2 K-means算法

2.1 算法思想

K-means算法是一種基於距離的聚類算法,這類聚類算法以距離來度量對象間的類似性,兩樣本對象間距離越大,類似性越小。關於K-means算法,有一個很是經典的故事:java

有4個牧師去郊區村莊授課,剛開始,4個牧師在村莊裏分別隨機選了一個位置,而後將位置公佈給全村村民,村民收到消息後,紛紛選擇最近的一個牧師那裏去聽課。牧師授課時,衆多村民反饋路途太遠,因而牧師記錄了來聽本身授課的全部村民的居住地址,第二次授課時,牧師選擇本身記錄的村民的中心位置做爲新的授課位置,而後將位置公佈給全村村民,村民收到4位牧師新的授課位置後,一樣根據距離選擇最近的牧師去聽課。以後4位牧師的每次一次授課都根據來聽本身講課的村民登記的居住地址來更新下一次授課的位置,而村民也更新4位牧師更新的位置來選擇授課牧師,直到村民的選擇再也不發生變化,則牧師授課的位置也完全穩定下來。node

K-means算法思想與上面故事中牧師選位所表現出來的原理是十分類似的,最終的目的都是實現全部樣本數據(村民)到聚類中心(牧師)的距離之和最小化。K-means算法實現步驟以下:python

輸入:數據集$D = \{ {x_1},{x_2}, \cdots ,{x_n}\} $,聚類個數$k$jquery

輸出:聚類結果類簇linux

(1)隨機初始化$k$個樣本做爲聚類中心$\{ {\mu _1},{\mu _2}, \cdots ,{\mu_k}\} $;android

(2)計算數據集中全部樣本$x_i$到各個聚類中心$\mu_j$的距離$dist({x_i},{\mu _j})$,並將$x_i$劃分到距離最小的聚類中心所在類簇中;

(3)對於每個類簇,更新其聚類中心:${\mu _i} = \frac{1}{{|{c_i}|}}\sum\limits_{x \in {c_i}} x $

(4)重複(2)(3)步驟,直到聚類中心再也不有明顯變化或知足迭代次數。

總結而言,K-means算法整個流程可總結爲一個優化問題,經過不斷迭代使得目標函數收斂,K-means算法目標函數爲: $$J = \sum\nolimits_{j = 1}^k {\sum\nolimits_{i = 1}^n {dist({x_i},{\mu _j})} } $$

從目標函數中能夠看出,有兩個因素對聚類結果有着相當重要的影響:$k$值、距離度量方式。

對於$k$值,這是K-means算法一個繞不開的問題,直接影響着最終聚類結果的準確性,在如何肯定$k$值問題上,傳統的的K-means算法在對數據分佈未知的狀況下只能經過屢次嘗試不一樣的$k$值來探索最優取值。值得一說的是,衆多專家學者針對K-means算法中如何肯定$k$值、甚至避開$k$值的的問題對K-means算法進行優化改進,設計了許多改進的K-means算法,這又是一個大話題了,本文不在深究。
下面在說說距離度量的問題。

2.2 距離度量

對於K-means算法這類基於距離(其餘還有基於密度、網格、層次、模型)的聚類算法,距離度量貫穿了算法的整個流程,因此選擇一種合適的距離度量方式尤其重要。
這裏列舉幾種常見的距離度量方式。

(1)閔可夫斯基距離(Minkowski Distance) 對於兩個給定的$d$維數據樣本$X = ({x_1},{x_2}, \cdots ,{x_p})$,$Y = ({y_1},{y_2}, \cdots ,{y_p})$,閔可夫斯基距離定義爲: $$dist(X,Y) = \root p \of {\sum\limits_{i = 1}^d {|{x_i} - {y_i}{|^p}} } $$

當$p=1$時,閔可夫斯基距離又被稱爲曼哈頓距離(Manhattan Distance): $$dist(X,Y) = \sum\limits_{i = 1}^d {|{x_i} - {y_i}|} $$ 曼哈頓距離能夠看作是數據樣本在各維度差值的絕對值之和,又被稱爲折線距離、街區距離。相比於歐氏距離,曼哈頓距離沒進行平方運算,因此對離羣點不敏感。

當$p=2$時,閔可夫斯基距離就是咱們熟悉的歐氏距離: $$dist(X,Y) = \sqrt {\sum\limits_{i = 1}^d {|{x_i} - {y_i}{|^2}} } $$

(2)餘弦距離(Cosine)

餘弦距離以兩向量夾角餘弦值來反映類似度,取值在$[0,1]$之間,值越大,類似度越大。
$$dist(X,Y) = \cos (X,Y) = \frac{{\sum\nolimits_{i = 1}^d {{x_i}{y_i}} }}{{\sqrt {\sum\nolimits_{i = 1}^d {{{({x_i})}^2}} } \sqrt {\sum\nolimits_{i = 1}^d {{{({y_i})}^2}} } }}$$

餘弦距離在文本識別中應用比較廣泛。

(3)切比雪夫距離 (Chebyshev Distance )

切比雪夫距離是以各維度差值的最大值做爲最終的類似度: $$dist(X,Y) = \mathop {\max }\limits_i (|{x_i} - {y_i}|)$$

除了這三個經常使用距離外,還有馬氏距離、皮爾遜相關係數、KL散度等等距離度量方法。

2.3 算法總結

K-means算法是一個應用十分普遍、出場率極高的一個聚類算法,思想簡單,解釋性強,設定好$k$值後便可輸出指定數量的類簇。不過,在實際應用中,也須要注意K-means算法的不足之處:

  • K-means算法的$k$值必須在聚類前肯定,在缺少對數據集分佈認知的狀況下這每每是很難估計的,只能經過屢次的嘗試探索最佳的$k$值。
  • K-means算法第一次迭代時的$k$個聚類中心對算法最終結果有很大影響,但在K-means算法中,第一次迭代的$k$各聚類中心是隨機選定的,這給算法聚類結果帶來了不肯定性。
  • K-means算法對非球狀分佈的數據集上表現不佳。K-means算法這類基於距離的聚類算法基本假設是同一類簇內部對象間距離遠小於不一樣類簇間對象距離,這種假設至關於將類簇看做是一個球狀,因此對非球狀分佈的數據集,K-means算法表現可能並不佳。
  • K-means算法在不斷迭代過程當中使得算法逐漸優化,在每一次迭代中,都必須計算每個對象與聚類中心的距離,因此當數據量很是大時,時間開銷比較大。

天下沒有免費的偏差,也沒有適合全部場景的算法,想要享受算法有點,就必須承受算法的不足,根據實際應用選擇合適的算法纔是最佳選擇。

3 python實現K-means算法

In [1]:
import numpy as np
import matplotlib.pyplot as plt 
import copy
 

先建立一個包含3個類簇的數據集:

In [2]:
a = np.random.normal(20,5,300)
b = np.random.normal(15,5,300)
cluster1 = np.array([[x, y] for x, y in zip(a,b)])
In [3]:
a = np.random.normal(20,5,300)
b = np.random.normal(45,5,300)
cluster2 = np.array([[x, y] for x, y in zip(a,b)])
In [4]:
a = np.random.normal(55,5,300)
b = np.random.normal(30,5,300)
cluster3 = np.array([[x, y] for x, y in zip(a,b)])
In [5]:
dataset = np.append(np.append(cluster1,cluster2, axis=0),cluster3, axis=0)
 

展現一下剛建立的數據集:

In [6]:
for i in dataset:
    plt.scatter(i[0], i[1],c='black',s=6)
plt.show()
 
 

定義一個方法用於計算歐氏距離:

In [7]:
def calc_dist(simple1, simple2):
    """計算兩數據對象間的歐氏距離"""
    return np.linalg.norm(simple1-simple2) 
 

定義一個方法用於算法第一次迭代時隨機出世後聚類中心:

In [8]:
def init_centers(k, dataset):
    """隨機獲取k個初始化聚類中心"""
    shuffle_array = np.arange(dataset.shape[0])
    np.random.shuffle(shuffle_array)
    center_index = shuffle_array[:k]  # 獲取k個隨機索引
    center_dict = {}
    for i in range(k):
        center = dataset[center_index[i]]  # 聚類中心
        center_dict[i] = center
    return center_dict
In [9]:
def k_means(k,dataset):
    """實現K-means算法"""
    ds = copy.deepcopy(dataset)  # 複製一份數據
    
    epoch = 0 # 迭代次數
    center_dict = init_centers(k, ds)  # 第一次迭代時,隨機初始化k個聚類中心
    ds = np.insert(ds, 2, values=-1, axis=1)  # 插入一列做爲類標籤,默認爲0
    total_last = np.inf  # 上一次迭代距離總和
    while epoch<=20:  # 迭代次數少於20次時繼續迭代,也能夠直接設爲True,當目標函數收斂時自動結束迭代
        cluster_dist = {i:0 for i in range(k)}  # 記錄每個類簇距離總和
        
        for simple in ds:
            min_dist = np.inf   # simple 到最近的聚類中心的距離
            min_label = -1    # 最近的聚類中心類標籤
            for label in center_dict.keys():
                dist = calc_dist(simple[:2], center_dict[label])
                if dist < min_dist:
                    min_dist = dist
                    min_label = label
            simple[2] = min_label  # 將當前樣本點劃分到最近的聚類中心所在聚類中
            cluster_dist[int(min_label)] = cluster_dist[int(min_label)] + min_dist  # 更新類簇內部距離總和
        loss_now = sum(cluster_dist.values())  # 全部類簇內部距離總和
        
        print("epoch:{}, tatal distance: {}".format(epoch,loss_now))
        for i in ds:
            if i[2] == 0:
                plt.scatter(i[0], i[1],c='red',s=6)
            elif i[2] == 1:
                plt.scatter(i[0], i[1],c='green',s=6)
            else:
                        plt.scatter(i[0], i[1],c='blue',s=6)
        for center in center_dict.values():
            plt.scatter(center[0], center[1],c='black')
        plt.show()
        if total_last == loss_now:  # 若是兩次迭代距離總和都不變,證實已收斂
            break
        total_last = loss_now
        for label in center_dict.keys():  # 更新聚類中心
            simple_list = ds[ds[:,2]==label]  # 挑選出類標籤爲k的全部樣本
            x = np.mean(simple_list[:, 0])
            y = np.mean(simple_list[:, 1])
            center_dict[label] = [x, y]
        epoch += 1
    
    return ds, center_dict
In [12]:
ds,cluster_label = k_means(3,dataset)
 
epoch:0, tatal distance: 12934.408559284844
 
 
epoch:1, tatal distance: 7964.838789624616
 
 
epoch:2, tatal distance: 5778.574439702343
 
 
epoch:3, tatal distance: 5628.049794639323
 
 
epoch:4, tatal distance: 5628.049794639323
 
 

好了,K-means算法就算實現好了,從上多幅圖中能夠看到每一次迭代聚類中心是如何變化的。

相關文章
相關標籤/搜索