1. 程式人生 > >機器學習——聚類(clustering):K-means演算法(非監督學習)

機器學習——聚類(clustering):K-means演算法(非監督學習)

1、歸類

聚類(clustering):屬於非監督學習(unsupervised learning),是無類別標記(class label)

2、舉例

3、K-means演算法

(1)K-means演算法是聚類(clustering)中的經典演算法,資料探勘的十大經典演算法之一

(2)演算法接收引數K,然後將事先輸入的n個數據劃分為K個聚類以便使得所獲得的聚類滿足:

同一聚類中的物件相似度較高,而不同聚類中的物件相似度較低

(3)演算法思想:以空間中K個點作為中心進行聚類,對最靠近它們的物件進行歸類;

  通過迭代的方法,逐漸更新各聚類中心的值,直至得到最好的聚類結果。

(4)演算法的描述:

a、適當選擇c個類的初始中心

b、在第K次迭代中,對任意一個樣本,求其到c各中心的距離,將該樣本歸類到距離最短的中心所在的類。

c、利用均值等方法更新該類的中心值

d、對所有的c個聚類中心,如果利用b、c的迭代更新後,值保持不變,則迭代結束;否則繼續迭代。

(5)演算法的流程:

輸入K,data[n]

a、選擇K個初始中心,例如:c[0] = data[0],...,c[k-1] = data[k-1];

b、對於data[0]......data[n]分別c[0].....c[k-1]進行比較,假定與c[i]距離最小,就標記為i;

c、對於所有標記為i點,重新計算中心點c[i] = {所有標記為i的data[i]之和}/標記為i的個數;

d、重複b、c,直到所有的c[i]的值的變化小於給定的閾值。

4、舉例

舉例:將下面的4顆藥分成兩類,K值即為:2。


這裡給出的特徵值是2維的,可以表示在一個平面上;不論是幾維的向量,都可以在一個超平面上表示出來。


(1)初始兩類中心點分別為A(1,1)、B(2,1),對4個點分別計算到2箇中心點的歐式距離,距離哪個類的距離小,就歸為那一類。


矩陣D的第一行表示:4個點到第一個類中心(1,1)的距離,第二行表示:4個點到第二個類中心(2,1)的距離。

比較每個點到兩類的距離,歸為距離小的類,得到歸類的矩陣如下:1表示:歸為此類;0表示:不歸為此類。

第一次歸類結果為:A歸為:第一類;B、C、D歸為第二類。


(2)利用均值,重新計算每個聚類中心點座標

c1=(1,1);c2 = ((2+4+5/3,(1+3+4)/3)=(11/3,8/3)

更新每個聚類中心點座標之後如下圖:


(3)再次計算4個點到新的聚類中心點的距離,第二次歸類結果為:AB歸為:第一類;C、D歸為第二類。



(4)繼續迭代,求新的聚類中心的座標,繼續分類,直到分類結果不再發生變化。





(5)最終的分類結果:AB歸為:第一類;C、D歸為第二類。

5、K-means演算法的優缺點:

優點:速度快、簡單

缺點:最終結果跟初始點選擇相關,容易陷入區域性最優,需要知道K值,有可能在分類之前不知道要分成幾類。

6、在Python中實現K-means演算法的上述例項

(1)隨機選取初始化中心點的情況

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:ZhengzhengLiu

import numpy as np

#x:資料集是一個numpy陣列(每行代表一個數據點,每列代表一個特徵值),k:分類數,maxIt:迭代次數
def kmeans(X,k,maxIt):
    numPoints, numDim = X.shape        #返回資料集X的行數和列數
    dataSet = np.zeros((numPoints,numDim+1))       #初始化新的資料集dataSet比X多一列,用來存放分標籤
    dataSet[:,:-1] = X          #dataSet的所有行和除去最後一列的所有列的數值與X相同

    #隨機初始化k箇中心點(利用randint函式從所有行例項中隨機選出k個例項作為中心點)
    centroids = dataSet[np.random.randint(numPoints,size=k),:]
    #centroids = dataSet[0:2,:]       #初始化前兩個例項作為中心點(也可以隨機選取初始化中心點)

    #為中心點最後一列初始化分類標記1到k
    centroids[:,-1] = range(1,k+1)

    iterations = 0            #迴圈次數
    oldCentroids = None       #舊的中心點
    while not shouldStop(oldCentroids,centroids,iterations,maxIt):
        print("iterations:\n",iterations)      #列印當前迴圈迭代次數
        print("dataSet:\n",dataSet)            #列印當前資料集
        print("centroids:\n",centroids)        #列印當前的中心

        oldCentroids = np.copy(centroids)       #將當前中心點賦值到舊中心點中
        iterations += 1                         #迭代次數加1

        #依照中心點為每個例項歸類
        updateLabels(dataSet,centroids)

        #根據歸類後資料集和k值,計算新的中心點
        centroids = getCentroids(dataSet,k)

    return dataSet

#迭代停止函式
def shouldStop(oldCentroids,centroids,iterations,maxIt):
    if iterations > maxIt:
        return True        #迭代次數比最大迭代次數大,則停止迭代
    return np.array_equal(oldCentroids,centroids)       #比較新舊中心點的值是否相等,相等返回True,迭代終止

#依照中心點為每個例項歸類
def updateLabels(dataSet,centroids):
    numPoints, numDim = dataSet.shape       #獲取當前資料集的行列數
    for i in range(0,numPoints):
        #當前行例項與中心點的距離最近的標記作為該例項的標記
        dataSet[i,-1] = getLabelFromClosesCentroid(dataSet[i,:-1],centroids)

def getLabelFromClosesCentroid(dataSetRow,centroids):
    label = centroids[0,-1]      #初始化當前標記賦值為第一個中心點的標記(第一行最後一列)
    minDist = np.linalg.norm(dataSetRow - centroids[0,:-1])       #初始化計算當前行例項與第一個中心點例項的歐氏距離
    for i in range(1,centroids.shape[0]):      #遍歷第二個到最後一箇中心點
        dist = np.linalg.norm(dataSetRow - centroids[i,:-1])      #計算當前行例項與每一箇中心點例項的歐氏距離
        if dist < minDist:
            minDist = dist
            label = centroids[i,-1]        #若當前的歐氏距離比初始化的小,則取當前中心點的標記作為該例項標記
    print("minDist:",minDist)
    return label

##根據歸類後資料集和k值,計算新的中心點
def getCentroids(dataSet,k):
    #最後返回的新的中心點的值有k行,列數與dataSet相同
    result = np.zeros((k,dataSet.shape[1]))
    for i in range(1,k+1):
        #將所有標記是當前同一類的例項的資料組成一個類
        oneCluster = dataSet[dataSet[:,-1] == i,:-1]
        result[i-1,:-1] = np.mean(oneCluster,axis = 0)     #對同一類的例項求均值找出新的中心點
        result[i-1,-1] = i

    return result

x1 = np.array([1,1])
x2 = np.array([2,1])
x3 = np.array([4,3])
x4 = np.array([5,4])
testX = np.vstack((x1,x2,x3,x4))       #垂直方向合併numpy陣列

result = kmeans(testX,2,10)
print("final result:",result)
執行結果:
iterations:
 0
dataSet:
 [[ 1.  1.  0.]
 [ 2.  1.  0.]
 [ 4.  3.  0.]
 [ 5.  4.  0.]]
centroids:
 [[ 5.  4.  1.]
 [ 2.  1.  2.]]
minDist: 1.0
minDist: 0.0
minDist: 1.41421356237
minDist: 0.0
iterations:
 1
dataSet:
 [[ 1.  1.  2.]
 [ 2.  1.  2.]
 [ 4.  3.  1.]
 [ 5.  4.  1.]]
centroids:
 [[ 4.5  3.5  1. ]
 [ 1.5  1.   2. ]]
minDist: 0.5
minDist: 0.5
minDist: 0.707106781187
minDist: 0.707106781187
final result: [[ 1.  1.  2.]
 [ 2.  1.  2.]
 [ 4.  3.  1.]
 [ 5.  4.  1.]]

(2)初始化前兩個例項作為中心點的情況
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:ZhengzhengLiu

import numpy as np

#x:資料集是一個numpy陣列(每行代表一個數據點,每列代表一個特徵值),k:分類數,maxIt:迭代次數
def kmeans(X,k,maxIt):
    numPoints, numDim = X.shape        #返回資料集X的行數和列數
    dataSet = np.zeros((numPoints,numDim+1))       #初始化新的資料集dataSet比X多一列,用來存放分標籤
    dataSet[:,:-1] = X          #dataSet的所有行和除去最後一列的所有列的數值與X相同

    #隨機初始化k箇中心點(利用randint函式從所有行例項中隨機選出k個例項作為中心點)
    centroids = dataSet[np.random.randint(numPoints,size=k),:]
    centroids = dataSet[0:2,:]       #初始化前兩個例項作為中心點(也可以隨機選取初始化中心點)

    #為中心點最後一列初始化分類標記1到k
    centroids[:,-1] = range(1,k+1)

    iterations = 0            #迴圈次數
    oldCentroids = None       #舊的中心點
    while not shouldStop(oldCentroids,centroids,iterations,maxIt):
        print("iterations:\n",iterations)      #列印當前迴圈迭代次數
        print("dataSet:\n",dataSet)            #列印當前資料集
        print("centroids:\n",centroids)        #列印當前的中心

        oldCentroids = np.copy(centroids)       #將當前中心點賦值到舊中心點中
        iterations += 1                         #迭代次數加1

        #依照中心點為每個例項歸類
        updateLabels(dataSet,centroids)

        #根據歸類後資料集和k值,計算新的中心點
        centroids = getCentroids(dataSet,k)

    return dataSet

#迭代停止函式
def shouldStop(oldCentroids,centroids,iterations,maxIt):
    if iterations > maxIt:
        return True        #迭代次數比最大迭代次數大,則停止迭代
    return np.array_equal(oldCentroids,centroids)       #比較新舊中心點的值是否相等,相等返回True,迭代終止

#依照中心點為每個例項歸類
def updateLabels(dataSet,centroids):
    numPoints, numDim = dataSet.shape       #獲取當前資料集的行列數
    for i in range(0,numPoints):
        #當前行例項與中心點的距離最近的標記作為該例項的標記
        dataSet[i,-1] = getLabelFromClosesCentroid(dataSet[i,:-1],centroids)

def getLabelFromClosesCentroid(dataSetRow,centroids):
    label = centroids[0,-1]      #初始化當前標記賦值為第一個中心點的標記(第一行最後一列)
    minDist = np.linalg.norm(dataSetRow - centroids[0,:-1])       #初始化計算當前行例項與第一個中心點例項的歐氏距離
    for i in range(1,centroids.shape[0]):      #遍歷第二個到最後一箇中心點
        dist = np.linalg.norm(dataSetRow - centroids[i,:-1])      #計算當前行例項與每一箇中心點例項的歐氏距離
        if dist < minDist:
            minDist = dist
            label = centroids[i,-1]        #若當前的歐氏距離比初始化的小,則取當前中心點的標記作為該例項標記
    print("minDist:",minDist)
    return label

##根據歸類後資料集和k值,計算新的中心點
def getCentroids(dataSet,k):
    #最後返回的新的中心點的值有k行,列數與dataSet相同
    result = np.zeros((k,dataSet.shape[1]))
    for i in range(1,k+1):
        #將所有標記是當前同一類的例項的資料組成一個類
        oneCluster = dataSet[dataSet[:,-1] == i,:-1]
        result[i-1,:-1] = np.mean(oneCluster,axis = 0)     #對同一類的例項求均值找出新的中心點
        result[i-1,-1] = i

    return result

x1 = np.array([1,1])
x2 = np.array([2,1])
x3 = np.array([4,3])
x4 = np.array([5,4])
testX = np.vstack((x1,x2,x3,x4))       #垂直方向合併numpy陣列

result = kmeans(testX,2,10)
print("final result:",result)
執行結果:
iterations:
 0
dataSet:
 [[ 1.  1.  1.]
 [ 2.  1.  2.]
 [ 4.  3.  0.]
 [ 5.  4.  0.]]
centroids:
 [[ 1.  1.  1.]
 [ 2.  1.  2.]]
minDist: 0.0
minDist: 0.0
minDist: 2.82842712475
minDist: 4.24264068712
iterations:
 1
dataSet:
 [[ 1.  1.  1.]
 [ 2.  1.  2.]
 [ 4.  3.  2.]
 [ 5.  4.  2.]]
centroids:
 [[ 1.          1.          1.        ]
 [ 3.66666667  2.66666667  2.        ]]
minDist: 0.0
minDist: 1.0
minDist: 0.471404520791
minDist: 1.88561808316
iterations:
 2
dataSet:
 [[ 1.  1.  1.]
 [ 2.  1.  1.]
 [ 4.  3.  2.]
 [ 5.  4.  2.]]
centroids:
 [[ 1.5  1.   1. ]
 [ 4.5  3.5  2. ]]
minDist: 0.5
minDist: 0.5
minDist: 0.707106781187
minDist: 0.707106781187
final result: [[ 1.  1.  1.]
 [ 2.  1.  1.]
 [ 4.  3.  2.]
 [ 5.  4.  2.]]

對比兩種情況的聚類,結果一致。