手寫演算法-python程式碼實現Kmeans

Dream-YH發表於2020-12-17

手寫演算法-python程式碼實現Kmeans

原理解析

今天,我們來講一下Kmeans,一種無監督聚類演算法,也是最為經典的基於劃分的聚類方法,它的思想是:對於給定的樣本集,按照樣本之間的距離大小,將樣本集劃分為K個簇。讓簇內的點儘量緊密的連在一起,而讓簇間的距離儘量的大。

實現流程如下:
1、先確定資料集聚類個數k;
2、在資料集中隨機選取k個資料,作為初始質心;
3、計算資料集中每個樣本到每個質心的距離,把樣本劃分到
距離最小的質心所屬的類別;
4、根據聚類結果,重新計算質心,當本次計算的質心與上一次質心完全一樣(或者收斂)時,停止迭代;
否則更新質心,繼續執行步驟2、3、4。

流程比較簡單,這裡要說的是注意幾個問題:
1、步驟1確定資料集聚類個數k,很顯然,當資料集D是高維資料時,沒辦法很快確定合適的k;
2、步驟2隨機選取資料作為初始質心,可以想象,當質心之間距離很近的時候,將需要迭代很多次,會影響收斂速度,甚至不能收斂;
3、步驟3中計算距離,我們應該想到,距離受到量綱的影響,在使用時,需要考慮一下標準化;
然後就是基於所有樣本,因此會對異常資料比較敏感;
4、當資料集很大時,計算的時間複雜度很高。

可見優化的點很多(優化部分我們明天再寫,今天只實現最基礎的Kmeans)。

程式碼實現

基於上面描述的過程,我們來寫python實現程式碼:

import numpy as np
from sklearn.datasets import make_blobs
from matplotlib import pyplot as plt

#無監督演算法,學習過程就是訓練質心的位置,進行聚類
class Kmeans:
    def __init__(self,k):
        self.k = k
    
    def calc_distance(self,x1,x2):
        diff = x1 - x2
        distances = np.sqrt(np.square(diff).sum(axis=1))
        return distances
    
    def fit(self,x):
        self.x = x
        m,n = self.x.shape
        #隨機選定k個資料作為初始質心,不重複選取
        self.original_ = np.random.choice(m,self.k,replace=False)
        #預設類別是從0到k-1
        self.original_center = x[self.original_]
        while True:
            #初始化一個字典,以類別作為key,賦值一個空陣列
            dict_y = {}
            for j in range(self.k):
                dict_y[j] = np.empty((0,n))
            for i in range(m):
                distances =self.calc_distance(x[i],self.original_center)
                #把第i個資料分配到距離最近的質心,存放在字典中
                label = np.argsort(distances)[0]
                dict_y[label] = np.r_[dict_y[label],x[i].reshape(1,-1)]
            centers = np.empty((0,n))
            #對每個類別的樣本重新求質心
            for i in range(self.k):
                center = np.mean(dict_y[i],axis=0).reshape(1,-1)
                centers = np.r_[centers,center]
            #與上一次迭代的質心比較,如果沒有發生變化,則停止迭代(也可考慮收斂時停止)
            result = np.all(centers == self.original_center)
            if result == True:
                break
            else:
                #繼續更新質心
                self.original_center = centers

    def predict(self,x):
        y_preds = []
        m,n = x.shape
        for i in range(m):
            distances =self.calc_distance(x[i],self.original_center)
            y_pred = np.argsort(distances)[0]
            y_preds.append(y_pred)
        return y_preds

例項演示

下面我們來生成一些資料集,來看看我們寫的Kmeans聚類效果:

#下面是我今天找到的一個很有代表性的資料集,哈哈,為什麼這麼說,後面會用到
x,y = make_blobs(centers=5,random_state=20,cluster_std=1)
plt.scatter(x[:,0],x[:,1])
plt.show()

在這裡插入圖片描述

從影像上看,我們適合把這些資料分成5個類別,跑一下程式碼看一下結果:

model = Kmeans(k=5)
model.fit(x)
y_preds = model.predict(x)
plt.scatter(x[:,0],x[:,1],c=y_preds)
plt.show()

在這裡插入圖片描述
,聚類得挺完美的,等一下,有人跑出不一樣的結果:

在這裡插入圖片描述
在這裡插入圖片描述
哈哈,要的就是這個效果(大家多重複執行一下上面這段程式碼,就會發現結果各種各樣),為什麼呢?
答案就在我們剛才說的幾個問題上,也就是步驟的問題,
具體怎麼優化我們明天寫。

sklearn對比

上面的問題,sklearn中的Kmeans演算法,會不會也出現呢?
我們來驗證一下:

from sklearn.cluster import KMeans
model_1 = KMeans(n_clusters=5)
y_pred_1 = model_1.fit_predict(x)
plt.scatter(x[:,0],x[:,1],c=y_pred_1)
plt.show()

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
可以發現,不管執行多少次,聚類效果都很穩定,都很好。

總結

初步實現了Kmeans演算法,但是有待優化,下一篇,我們來介紹優化的Kmeans。

相關文章