Kmeans 算法理論和Python實現(一)——簡單K-Means實現

1. Kmeans算法原理

1.1 Kmeans算法介紹

聚類算法是一種無監督學習方法,將相似的對象歸到同一個簇中。在K-Means算法中,我們將所有的數據劃分到K個不同的簇中並且每一個簇的中心是該簇中所有的節點的均值計算得到,因此稱爲K—均值算法。

在機器學習中聚類和分類算法最大的不同在於,分類的目標事先是已知的,而聚類則不同,聚類的目標是未知的。分類產生的結果是分類前已經知曉的結果,而聚類產生的結果是未知的,因此聚類別稱爲無監督的學習方式。

1.2 Kmeans算法原理

K-Means算法是發現給定數據集中的K個簇而簇的個數K是用戶給定的數據信息,每一個簇通過簇的質心來描述,質心是通過該類中的所有點求平均值得到。
在K-Means算法中,算法的流程如下:

(1)隨機選擇K個點作爲初始質心

(2)對所有的點計算尋找最近的質心爲該點所屬的質心,並計算到該質心的距離。

(3)對各個節點劃分好的點通過求該簇所有節點的均值計算該簇新的質心。

(4)重複步驟(2)對所有的節點重新進行劃分,直到所有的簇的質心不再改變。

在計算節點與質心之間的距離時,我們通常採用歐式距離進行計算計算公式如下:

dist=sqrt((x1y1)2+(x2y2)2+...+(xiyi)2)

根據上述描述的過程Kmeans算法執行的過程主要爲:計算質心——劃分節點——重新計算的過程,接下來我們考慮如何實現K-Means算法

2. Kmeans算法Python實現

2.1 初始化質心

def Random_initCentroids(dataSet, k):
    numSamples, dim = dataSet.shape
    centroids = zeros((k, dim))
    #隨機生成聚類中心
    for i in range(k):
        index = int(random.uniform(0, numSamples))
        centroids[i, :] = dataSet[index, :]
    return centroids

在上述代碼中,在所有的數據節點中隨機選擇一個節點作爲聚類中心,在Random_initCentroids()中通過隨機的選擇所有的點中的任意K個作爲聚類中心。其中Random_initCentroids()傳入的參數分別爲:
dataSet和k值,在隨機選擇時同樣同樣的可以在每一個特徵的最小值和最大值之間隨機選取一個值作爲初始的質心。

2.2 計算節點距離

def euclDistance(vector1, vector2):
    return sqrt(sum(power(vector2 - vector1, 2)))

上述代碼用於計算兩個向量之間的距離,也就是節點vector1和節點vector2之間的距離在這裏的距離表示的歐式距離。

2.3 Kmeans聚類

def kmeans(dataSet, k,medthod):
    numSamples = dataSet.shape[0]
    # first column stores which cluster this sample belongs to,
    # second column stores the error between this sample and its centroid
    clusterAssment = mat(zeros((numSamples, 2)))
    clusterChanged = True
    ## 初始化聚類中心
    if medthod == 0:
        centroids = Random_initCentroids(dataSet, k)
    else:
        centroids = maxDis_initCentroids(dataSet, k)
    while clusterChanged:
        clusterChanged = False
        ## 對每一個點計算聚類中心
        for i in range(numSamples):
            minDist = 100000.0
            minIndex = 0
            ## 尋找最近的聚類中心
            for j in range(k):
                distance = euclDistance(centroids[j, :], dataSet[i, :])
                if distance < minDist:
                    minDist = distance
                    minIndex = j
            ##更新節點所屬的簇判斷迭代是否繼續
            if clusterAssment[i, 0] != minIndex:
                clusterChanged = True
            clusterAssment[i, :] = minIndex, minDist ** 2
        ## 更新聚類中心
        for j in range(k):
            pointsInCluster = dataSet[nonzero(clusterAssment[:, 0].A == j)[0]]
            centroids[j, :] = mean(pointsInCluster, axis=0)
    print('Congratulations, cluster complete!')
    return centroids, clusterAssment

在上述的Kmeans聚類算法中主要的執行過程如下:

(1)初始化聚類中心

根據下面的代碼可知,在我們這裏定義了兩種初始化聚類中心的方式一種爲上面講述的Random_initCentroids()隨機選取聚類中心的方式,另外一種爲maxDis_initCentroids()方式,第二種聚類方式我們在下面做詳細的介紹。

clusterAssment = mat(zeros((numSamples, 2)))
clusterChanged = True
## 初始化聚類中心
if medthod == 0:
    centroids = Random_initCentroids(dataSet, k)
else:
    centroids = maxDis_initCentroids(dataSet, k)

(2)計算每一個結點所有的簇

對數據集中的所有節點計算出距離最小的質心,並將該節點劃分到質心所屬的簇。

while clusterChanged:
    clusterChanged = False
    ## 對每一個點計算聚類中心
    for i in range(numSamples):
        minDist = 100000.0
        minIndex = 0
        ## 尋找最近的聚類中心
        for j in range(k):
            distance = euclDistance(centroids[j, :], dataSet[i, :])
            if distance < minDist:
                minDist = distance
                minIndex = j
        ##更新節點所屬的簇判斷迭代是否繼續
        if clusterAssment[i, 0] != minIndex:
            clusterChanged = True
        clusterAssment[i, :] = minIndex, minDist ** 2

(3)更新質心

對統計簇中每一個點的每一維特徵的均值作爲新的質心,並重覆上述步驟(2)直到所有簇的質心都不再發生改變爲止。

for j in range(k):
    pointsInCluster = dataSet[nonzero(clusterAssment[:, 0].A == j)[0]]
    centroids[j, :] = mean(pointsInCluster, axis=0)

2.4 數據可視化

在Python中我們可以通過matplotlib對二維的數據進行可視化。

def showCluster(dataSet, k, centroids, clusterAssment):
    numSamples, dim = dataSet.shape
    printCluster(dataSet, centroids, clusterAssment)
    if dim != 2:
        print("Sorry! I can not draw because the dimension of your data is not 2!")
        return 1

    mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
    if k > len(mark):
        print
        "Sorry! Your k is too large! please contact Zouxy"
        return 1

        # draw all samples
    for i in range(numSamples):
        markIndex = int(clusterAssment[i, 0])
        plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])

    mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', '<b', 'pb']
    # draw the centroids
    for i in range(k):
        plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize=12)
    plt.show()

2.5 數據輸出

輸出整個數據集的所有簇的質心以及每一個每一個數據項對應的簇和該簇的質心。

def printCluster(dataSet, centroids, clusterAssment):
    numSample,dim = dataSet.shape
    i = 0;
    for a in centroids:
        print("Cluster ",i,"centroids is ",a);
        i = i + 1
    for i in range(numSample):
        print("the node ",dataSet[i]," belong to the cluster",clusterAssment[i],"the centroids is ",centroids[int(clusterAssment[i,0])]);

2.6 測試代碼

from numpy import *
import time
import matplotlib.pyplot as plt
## step 1: load data
from src.Kmeans import kmeans, showCluster
print("step 1: load data...")
dataSet = []
fileIn = open('../data/Iris.txt')
#read each line in the file
for line in fileIn.readlines():
    linedata = []
    lineArr = line.strip().split(',')
    for data in lineArr:
        linedata.append(float(data));
    dataSet.append(linedata)

print("step 2: clustering...")
dataSet = mat(dataSet)
k = 3
centroids, clusterAssment = kmeans(dataSet,k,1)
## step 3: show the result  
print("step 3: show the result...")
showCluster(dataSet, k, centroids, clusterAssment) 

3. 運行結果

在測試算法的聚類效果時,我們分別選取了兩個數據集進行聚類,一個是在參考的博客中給出的數據集,是一個二維的數據集,一個失鳶尾花數據集。

3.1 二維數據集

下面如下圖1爲matplotlib繪製出的二維圖像,我們將所有的數據一共聚成4個類,根據下述圖中的顯示的結果可知,Kmean算法具有較好的聚類效果。

這裏寫圖片描述

對於聚類後的輸出數據如下圖2所示

這裏寫圖片描述

3.2鳶尾花數據集

對於鳶尾花數據集聚類的結果輸出如下所示,我們將數據結果一共聚類成3個類。
這裏寫圖片描述

4. Kmeans算法的優缺點及改進方法

4.1 Kmens算法的優點

(1)實現簡單,易於理解

4.2 Kmeans算法的缺點

(1)可能收斂到局部最小值,在大規模數據集上收斂較慢

(2)只適用於數值型的數據

(3)對初始質心的比較敏感。

(4)無法聚類非球狀數據

(5)在較大數據集上收斂速度慢

4.3 Kmeans聚類性能提高

爲了解決聚類算法對處置敏感性的問題,防止Kmeans算法收斂到最小值的可能收斂到局部的最小值,我們通過改變質心的初始化方式來解決這一問題,根據上述描述的方式,我們在初始化聚類中心時採用隨機初始化聚類中心的方式。爲了減小聚類中心對初始值的敏感性,我們對聚類中心的初始化方式有以下兩種:

(1)通過最大距離的方式初始化聚類中心。

根據下述的代碼可知,通過maxDistance初始化聚類中心時,第一個質心通過隨機選取,在此之後所有質心的選擇都選擇與當前已經選擇作爲質心的點最遠的點作爲新的質心。

def maxDistance(dataSet,centroids,numCentroids):
    numSamples,dim = dataSet.shape;
    maxDis = 0;
    maxindex = 0;
    for i in range(numSamples):
        distance = 0;
        for j in range(numCentroids):
            distance = euclDistance(dataSet[i,:],centroids[j,:]) + distance;
        if distance > maxDis:
            maxDis = distance;
            maxindex = i;
    centroids[numCentroids,:] = dataSet[maxindex,:]

(2)通過分層聚類初始化聚類中心
通過Canopy進行分層聚類,在每一個Canopy中選擇一個點作爲質心進行聚類,得到最終的聚類結果。

此外還有一些聚類的後處理方式以及二分K-均值聚類算法在之後進行描述
博客源代碼下載地址

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