1. 程式人生 > >機器學習-簡單的K最近鄰演算法及python實現

機器學習-簡單的K最近鄰演算法及python實現

根據前人的成果進行了學習 https://www.cnblogs.com/ahu-lichang/p/7161613.html#commentform

1、演算法介紹

其實k最近鄰演算法算是聚類演算法中最淺顯易懂的一種了,考慮你有一堆二維資料,你想很簡單的把它分開,像下圖這樣分成四類


你當然可以選擇肉眼辨別,拿個鉛筆給它們分開,但是如果這些資料多到你一天都看不完呢?又或者在你辛苦了一天分類完畢後,突然發現你其實應該分成三類而不是四類的,得,重來吧。

雖然人工分類在我們看起來挺弱智的,但實際上k最近鄰演算法的核心思想就跟我們日常分類差不多。我們會自然而然的把“扎堆”的點分成一類,術語可以講,按照歐氏距離來分類,當我們考慮把這種想法用程式實現的時候,那就變成了k最近鄰演算法了。

2、演算法步驟

大概有以下幾點

1. 先按照你想分的類別數,隨機找幾個點作為每一類的中心點

2. 根據你採用的距離計算方式(歐氏距離、曼哈頓距離等),將所有點按照最近鄰的方式歸到這幾個中心點的類別中去

3. 分類完畢後,根據每一類的所有點,來重新計算一個新的中心點來替代上一次使用的中心點

4. 重複2,3,直到中心點不再變化,稱之為收斂的時候,就完成了分類

3、具體程式碼

下面按照2中的演算法步驟,採用python進行程式設計

1. 首先是生成k個隨機中心點,此次取4

def randCent(dataSet,k):
    ndim = array(dataSet).shape[1]
    # 初始化中心點陣列
    centsArray = zeros((k,ndim))
    # 這一步的操作是將初始隨機中心點的每個維度的值限定在資料點的維度值域之間,二維的話就是說中心點不會處在
    # 資料點組成的“域”之外
    for i in range(ndim):
        minIDim = min(array(dataSet)[:,i])
        maxIDim = max(array(dataSet)[:,i])
        rangeIDim = maxIDim-minIDim
        centsArray[:,i] = (minIDim + rangeIDim * random.rand(k, 1)).reshape(centsArray[:, i].shape)
    return centsArray

(我這裡由於個人對python及numpy包的運用不熟練,導致。。。很多操作可能看起來很麻煩,經過這次之後要去系統性的學習一下相關知識)

2. 這裡將步驟中的2,3,4合併為一個函式實現

採用歐氏距離作為判別標準,根據資料點與中心點的歐氏距離大小進行分類

計算歐氏距離的函式

def distEclud(vecA,vecB):
    return sqrt(sum(power(vecA-vecB,2)))

然後是主要的函式

def kMeans(dataSet,k):
    # 資料總量
    num = dataSet.shape[0]
    # 建立一個數組儲存每個點的類別和與對應中心點的歐氏距離
    clusterAssignmentArray = zeros((num,2))
    centsArray = randCent(dataSet,k)
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(num):
            minIndex = -1
            minDist = inf
            for j in range(k):
                diEclud = distEclud(dataSet[i],centsArray[j])
                if diEclud<minDist:
                    minDist = diEclud
                    minIndex = j
            if clusterAssignmentArray[i][0]!=minIndex:
                clusterAssignmentArray[i] = minIndex,minDist
                clusterChanged = True
        # 根據新的分類結果中每一類的資料點,重新計算每一類的中心點
        for centIndex in range(k):
            # 根據minIndex取出每一類的資料點進行計算
            ptrInClust = []
            for j in range(num):
                if clusterAssignmentArray[j][0]==centIndex:
                    ptrInClust.append(dataSet[j])
            centsArray[centIndex, :] = mean(ptrInClust, axis=0)
return centsArray,clusterAssignmentArray
3. 測試程式碼
if __name__ == "__main__":
    k = 4
    datMat = mat(loadDataSet('1.txt'))
    myCentroids, clustAssing = kMeans(datMat, k)

    x = []
    y = []
    x.append(((myCentroids[:, 0]).tolist()))
    y.append((myCentroids[:, 1]).tolist())
    # plt.plot(x[0], y[0], 'b*')
    plt.plot(((myCentroids[:, 0]).tolist()), (myCentroids[:, 1]).tolist(), 'k*')

    colourList = ['bo', 'ro', 'yo', 'co','ko']
    for i in range(k):
        centX = []
        centY = []
        for j in range(19):
            if clustAssing[j].tolist()[0] == i:
                centX.append(datMat[j].tolist()[0][0])
                centY.append(datMat[j].tolist()[0][1])
        plt.plot(centX, centY, colourList[i])
    plt.show()

最後,可以看到已經完成了分類,其中黑星代表中心點。當然,如果你多跑幾次,會發現。。。每次的結果未必一樣,這也是一個坑


4. 完整程式碼

https://github.com/ShenYuhan/ml-python/blob/master/knn_eclud.py

5. 資料,空格分割

1.658985 4.285136
-3.453687 3.424321
4.838138 -1.151539
-5.379713 -3.362104
0.972564 2.924086
-3.567919 1.531611
0.450614 -3.302219
-3.487105 -1.724432
2.668759 1.594842
-3.156485 3.191137
3.165506 -3.999838
-2.786837 -3.099354
4.208187 2.984927
-2.123337 2.943366
0.704199 -0.479481
-0.392370 -3.963704
2.831667 1.574018
-0.790153 3.343144
2.943496 -3.357075