數學推導+純Python實現機器學習算法25:kmeans聚類

Python機器學習算法實現

Author:louwill

Machine Learning Lab

     

聚類分析(Cluster Analysis)是一類經典的無監督學習算法。在給定樣本的情況下,聚類分析通過特徵相似性或者距離的度量方法,將其自動劃分到若干個類別中。常用的聚類分析方法包括層次聚類法(Hierarchical Clustering)、k均值聚類(K-means Clustering)、模糊聚類(Fuzzy Clustering)以及密度聚類(Density Clustering)等。本節我們僅對最常用的kmeans算法進行講解。

相似度度量

相似度或距離度量是聚類分析的核心概念。常用的距離度量方式包括閔氏距離和馬氏距離,常用的相似度度量方式包括相關係數和夾角餘弦等。

  • 閔氏距離
    閔氏距離即閔可夫斯基距離(Minkowski Distance),定義如下。給定維向量樣本集合,對於,樣本與樣本之間的閔氏距離可定義爲:

    時,閔氏距離就可以表達爲歐式距離(Euclidean Distance):

    時,閔氏距離也稱爲曼哈頓距離(Manhatan Distance):

    時,閔氏距離也稱爲切比雪夫距離(Chebyshev Distance):

  • 馬氏距離
    馬氏距離全稱爲馬哈拉諾比斯距離(Mahalanobis Distance),即一種考慮各個特徵之間相關性的聚類度量方式。給定一個樣本集合,其協方差矩陣爲,樣本與樣本之間的馬氏距離可定義爲:

    爲單位矩陣時,即樣本的各特徵之間相互獨立且方差爲1時,馬氏距離就是歐式距離。

  • 相關係數
    相關係數(Correlation Coefficent)是度量相似度最常用的方式。相關係數越接近於1表示兩個樣本越相似,相關係數越接近於0,表示兩個樣本越不相似。樣本之間相關係數可定義爲:

  • 夾角餘弦
    夾角餘弦也是度量兩個樣本相似度的方式之一。夾角餘弦越接近於1表示兩個樣本越相似,夾角餘弦越接近於0,表示兩個樣本越不相似。樣本之間夾角餘弦可定義爲:

kmeans聚類

kmeans即k均值聚類算法。給定維樣本集合均值聚類是要將個樣本劃分到個不同的類別區域,通常而言。所以均值聚類可以總結爲對樣本集合的劃分,其學習策略主要是通過損失函數最小化來選取最優的劃分。

我們使用歐式距離作爲樣本間距離的度量方式。則樣本間的距離可定義爲:

定義樣本與其所屬類中心之間的距離總和爲最終損失函數:

其中爲第個類的質心(即中心點),表示指示函數,取值爲1或0。函數表示相同類中樣本的相似程度。所以均值聚類可以規約爲一個優化問題求解:



該問題是一個NP hard的組合優化問題,實際求解時我們採用迭代的方法進行求解。

根據以上定義,我們可以梳理均值聚類算法的主要流程如下:

  • 初始化質心。即在第0次迭代時隨機選擇個樣本點作爲初始化的聚類質心點

  • 按照樣本與中心的距離對樣本進行聚類。對固定的類中心,其中爲類的中心點,計算每個樣本到類中心的距離,將每個樣本指派到與其最近的中心點所在的類,構成初步的聚類結果

  • 計算上一步聚類結果的新的類中心。對聚類結果計算當前各個類中樣本均值,並作爲新的類中心

  • 如果迭代收斂或者滿足迭代停止條件,則輸出最後聚類結果,否則令,返回第二步重新計算。

kmeans算法實現

下面我們基於numpy按照前述算法流程來實現一個kmeans算法。回顧上述過程,我們可以先思考一下對算法每個流程該如何定義。首先要定義歐式距離計算函數,然後類中心初始化、根據樣本與類中心的歐式距離劃分類別並獲取聚類結果、根據新的聚類結果重新計算類中心點、重新聚類直到滿足停止條件。

下面我們先定義兩個向量之間的歐式距離函數如下:

import numpy as np
# 定義歐式距離
def euclidean_distance(x1, x2):
    distance = 0
    # 距離的平方項再開根號
    for i in range(len(x1)):
        distance += pow((x1[i] - x2[i]), 2)
    return np.sqrt(distance)

然後爲每個類別隨機選擇樣本進行類中心初始化:

# 定義中心初始化函數
def centroids_init(k, X):
    n_samples, n_features = X.shape
    centroids = np.zeros((k, n_features))
    for i in range(k):
        # 每一次循環隨機選擇一個類別中心
        centroid = X[np.random.choice(range(n_samples))]
        centroids[i] = centroid
    return centroids

根據歐式距離計算每個樣本所屬最近類中心點的索引:

# 定義樣本的最近質心點所屬的類別索引
def closest_centroid(sample, centroids):
    closest_i = 0
    closest_dist = float('inf')
    for i, centroid in enumerate(centroids):
        # 根據歐式距離判斷,選擇最小距離的中心點所屬類別
        distance = euclidean_distance(sample, centroid)
        if distance < closest_dist:
            closest_i = i
            closest_dist = distance
    return closest_i

定義構建每個樣本所屬類別過程如下:

# 定義構建類別過程
def create_clusters(centroids, k, X):
    n_samples = np.shape(X)[0]
    clusters = [[] for _ in range(k)]
    for sample_i, sample in enumerate(X):
        # 將樣本劃分到最近的類別區域
        centroid_i = closest_centroid(sample, centroids)
        clusters[centroid_i].append(sample_i)
    return clusters

根據上一步聚類結果重新計算每個類別的均值中心點:

# 根據上一步聚類結果計算新的中心點
def calculate_centroids(clusters, k, X):
    n_features = np.shape(X)[1]
    centroids = np.zeros((k, n_features))
    # 以當前每個類樣本的均值爲新的中心點
    for i, cluster in enumerate(clusters):
        centroid = np.mean(X[cluster], axis=0)
        centroids[i] = centroid
    return centroids

然後簡單定義一下如何獲取每個樣本所屬的類別標籤:

# 獲取每個樣本所屬的聚類類別
def get_cluster_labels(clusters, X):
    y_pred = np.zeros(np.shape(X)[0])
    for cluster_i, cluster in enumerate(clusters):
        for sample_i in cluster:
            y_pred[sample_i] = cluster_i
    return y_pred

最後我們將上述過程進行封裝,定義一個完整的kmeans算法流程:

# 根據上述各流程定義kmeans算法流程
def kmeans(X, k, max_iterations):
    # 1.初始化中心點
    centroids = centroids_init(k, X)
    # 遍歷迭代求解
    for _ in range(max_iterations):
        # 2.根據當前中心點進行聚類
        clusters = create_clusters(centroids, k, X)
        # 保存當前中心點
        prev_centroids = centroids
        # 3.根據聚類結果計算新的中心點
        centroids = calculate_centroids(clusters, k, X)
        # 4.設定收斂條件爲中心點是否發生變化
        diff = centroids - prev_centroids
        if not diff.any():
            break
    # 返回最終的聚類標籤
    return get_cluster_labels(clusters, X)

我們來簡單測試一下上述實現的kmeans算法:

# 測試數據
X = np.array([[0,2],[0,0],[1,0],[5,0],[5,2]])
# 設定聚類類別爲2個,最大迭代次數爲10次
labels = kmeans(X, 2, 10)
# 打印每個樣本所屬的類別標籤
print(labels)
[0. 0. 0. 1. 1.]

可以看到,kmeans算法將第1~3個樣本聚爲一類,第4~5個樣本聚爲一類。sklearn中也爲我們提供了kmeans算法的接口,嘗試用sklearn的kmeans接口來測試一下該數據:

from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
print(kmeans.labels_)
[0. 0. 0. 1. 1.]

可以看到sklearn的聚類結果和我們自定義的kmeans算法是一樣的。但是這裏有必要說明的一點是,不同的初始化中心點的選擇對最終結果有較大影響,自定義的kmeans算法和sklearn算法計算出來的結果一致本身也有一定的偶然性。另外聚類類別k的選擇也需要通過一定程度上的實驗才能確定。

參考資料:

李航 統計學習方法 第二版

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章