K-mean均值算法原理講解和代碼實戰

K-mean均值算法原理講解和代碼實戰

前言

最近在學習吳恩達機器學習課程,剛剛學完第一個無監督學習算法,搭配着機器學習實戰裏面的k-mean實戰,成功的將理論和實際結合了起來。接下來,咱們簡單的分析算法原理之後,着重講解一下源代碼。

項目github地址:K-mean算法實戰

K-mean算法原理解析

K表示的是組的個數,也就是你想把這些數據分成幾類。

算法主要思路:

  1. 隨機選擇k個點,作爲聚類的中心點
  2. 將所有數據進行歸類,歸類標準是按照歐幾里得距離,數據離哪個中心點近,就屬於哪一類
  3. 移動聚類點。計算屬於該中心點的數據的平均值,將聚類點移動到平均值位置
  4. 不斷重複2、3,直到數據的歸類不再發生變化

圖形過程演示:

我使用了數據量是50,然後K值設定的是2,也就是分爲兩類

下面咱們看一下這個動圖(文末有生成動圖python代碼地址):

在這裏插入圖片描述

接下來給大家解析一下:

  1. 下圖是隨機初始化的兩個K點,當然這個初始化是的範圍是在數據點的範圍之內

在這裏插入圖片描述

  1. 下圖開始進行2、3步的循環:

在這裏插入圖片描述

  1. 直到數據歸屬不發生改變,則判定爲當前K點爲最佳值。數據歸屬是根據各個數據點距K點的距離判定的,使用的方法是歐幾里得距離公式。
    在這裏插入圖片描述

代碼解析

一、生成數據

def CreatData():
    x1 = np.random.rand(50)*3#0-3
    y1 = [i+np.random.rand()*2-1 for i in x1]
    with open('data.txt','w') as f:
        for i in range(len(x1)):
            f.write(str(x1[i])+'\t'+str(y1[i])+'\n')

二、讀取數據

def loadDateSet(fileName):
    dataMat=[]
    fr = open(fileName)
    for line in fr.readlines():
        curline = line.strip().split('\t')
        #map函數 對指定的序列做映射,第一個參數是function 第二個是序列
        #此方法可以理解爲進行字符串格式轉換.這個函數可以深究
        fltLine = map(float,curline)
        dataMat.append(list(fltLine))
    return dataMat

這裏主要是說一下map這個方法

map(function,list) 此方法的作用是將第二個參數(列表或者迭代器)對前面的方法進行一一映射。

舉個例子:

>>>def square(x) :            # 計算平方數
...     return x ** 2
... 
>>> map(square, [1,2,3,4,5])   # 計算列表各個元素的平方
[1, 4, 9, 16, 25]

map(float,curline) 的作用可以理解爲將curline 列表中的數字格式轉換成float類型的。

三、歐幾里得距離計算

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

四、顯示變化數據變化過程

def showProcess(clusterAssment,centroids):
    #顯示過程
    Index1 = np.nonzero(clusterAssment[:,0]==0)[0]
    Index2 = []
    for i in range(len(clusterAssment)):
        if i not in Index1:
            Index2.append(i)
    plt.plot(datamat[Index1,0],datamat[Index1,1],'ro')
    plt.plot(datamat[Index2,0],datamat[Index2,1],'go')
    plt.scatter([centroids[0][0]],[centroids[0][1]],color='',marker='o',edgecolors='red',linewidths=3)
    plt.scatter([centroids[1][0]],[centroids[1][1]],color='',marker='o',edgecolors='green',linewidths=3)
    plt.show()

五、初始化K點

def randCent(dataSet,k):
    n = np.shape(dataSet)[1]#獲取維度數
    centroids = np.array(np.zeros((n,2)))#創建一個k*n的矩陣,初始值爲0
    for j in range(n):
        minJ = np.min(dataSet[:,j])#獲取每一維度的最小值
        rangeJ = float(np.max(dataSet[:,j])-minJ)#獲得最大間隔,最大值➖最小值
        centroids[:,j] = minJ+rangeJ*np.random.rand(k,1)#最小值加上間隔*[0,1]範圍的數
        #每進行一次循環,給每一整列賦值。
    return centroids

六、執行算法邏輯主題,即上述步驟 2、3

def kMeans(dataSet,k,distMeans=distEclud,createCent = randCent):
    m = np.shape(dataSet)[0]#獲取數據的個數
    clusterAssment = np.array(np.zeros((m,2)))#創建一個m行2列的矩陣用於存儲索引值和距離
    centroids = createCent(dataSet,k)#隨機選取兩個點
    plt.scatter([centroids[0][0]],[centroids[0][1]],color='',marker='o',edgecolors='red',linewidths=3)
    plt.scatter([centroids[1][0]],[centroids[1][1]],color='',marker='o',edgecolors='green',linewidths=3)
    plt.plot(dataSet[:,0],dataSet[:,1],'o',color='yellow')
    plt.show()
    clusterChanged = True#標誌符,判定數據點的所屬關係有沒有發生變化
    flag=1
    while clusterChanged:
        print("當前迭代次數爲:{}".format(flag))
        flag+=1
        clusterChanged=False
        for i in range(m):#m爲數據量的個數
            minDist = 10000#設置一個最大值
            minIndex = -1#初始化索引
            for j in range(k):#k爲劃分的種類數 此for循環給數據點分配所屬關係
                distJI = distMeans(centroids[j,:],dataSet[i,:])#距離值
                if distJI<minDist:
                    minDist = distJI
                    minIndex = j
            if clusterAssment[i,0]!=minIndex:#判斷所屬關係是否發生改變
                clusterChanged=True
            clusterAssment[i,:] = minIndex,minDist**2#這裏面存儲的是所屬關係和序列號
        #print(centroids)
        for cent in range(k):#這個for循環是用來移動分類點的位置,將其移動到所屬點的平均值位置
            # print("輸出1:",clusterAssment[:,0])
            # print("輸出2:",np.nonzero(clusterAssment[:,0]==cent))
            #.A 是將矩陣轉化爲數組
            ptsInClust = dataSet[np.nonzero(clusterAssment[:,0]==cent)[0]]#取出相同簇的點進行取平均,這裏[0]是因爲參數的形狀爲(n,1)
            #np.nonzero 取值不爲0的索引值
            centroids[cent,:] = np.mean(ptsInClust,axis=0)#取平均
        showProcess(clusterAssment,centroids)
    return centroids,clusterAssment

全部代碼

import numpy as np
import matplotlib.pyplot as plt

def CreatData():
    x1 = np.random.rand(50)*3#0-3
    y1 = [i+np.random.rand()*2-1 for i in x1]
    with open('data.txt','w') as f:
        for i in range(len(x1)):
            f.write(str(x1[i])+'\t'+str(y1[i])+'\n')
def loadDateSet(fileName):
    dataMat=[]
    fr = open(fileName)
    for line in fr.readlines():
        curline = line.strip().split('\t')
        #map函數 對指定的序列做映射,第一個參數是function 第二個是序列
        #此方法可以理解爲進行字符串格式轉換.這個函數可以深究
        #print(curline)
        #fltLine = float(curline)
        fltLine = map(float,curline)
        dataMat.append(list(fltLine))
    return dataMat

def distEclud(vecA,vecB):
    return np.sqrt(np.sum(np.power((vecA-vecB),2)))
def showProcess(clusterAssment,centroids):
    #顯示過程
    Index1 = np.nonzero(clusterAssment[:,0]==0)[0]
    Index2 = []
    for i in range(len(clusterAssment)):
        if i not in Index1:
            Index2.append(i)
    plt.plot(datamat[Index1,0],datamat[Index1,1],'ro')
    plt.plot(datamat[Index2,0],datamat[Index2,1],'go')

    plt.scatter([centroids[0][0]],[centroids[0][1]],color='',marker='o',edgecolors='red',linewidths=3)
    plt.scatter([centroids[1][0]],[centroids[1][1]],color='',marker='o',edgecolors='green',linewidths=3)
    plt.show()
def randCent(dataSet,k):
    n = np.shape(dataSet)[1]#獲取維度數
    centroids = np.array(np.zeros((n,2)))#創建一個k*n的矩陣,初始值爲0
    print(centroids)
    for j in range(n):
        minJ = np.min(dataSet[:,j])#獲取每一維度的最小值
        rangeJ = float(np.max(dataSet[:,j])-minJ)#獲得最大間隔,最大值➖最小值
        #print("test2:",centroids[:,j])
        centroids[:,j] = np.array(minJ+rangeJ*np.random.rand(k,1)).reshape(2)#最小值加上間隔*[0,1]範圍的數
        #print("test3:",centroids[:,j])
        #每進行一次循環,給每一整列賦值。
    return centroids

def kMeans(dataSet,k,distMeans=distEclud,createCent = randCent):
    m = np.shape(dataSet)[0]#獲取數據的個數
    clusterAssment = np.array(np.zeros((m,2)))#創建一個m行2列的數組用於存儲索引值和距離
    centroids = createCent(dataSet,k)#隨機選取兩個點
    print("初始化的矩陣",centroids)
    plt.scatter([centroids[0][0]],[centroids[0][1]],color='',marker='o',edgecolors='red',linewidths=3)
    plt.scatter([centroids[1][0]],[centroids[1][1]],color='',marker='o',edgecolors='green',linewidths=3)
    plt.plot(dataSet[:,0],dataSet[:,1],'o',color='yellow')
    plt.show()
    clusterChanged = True#標誌符,判定數據點的所屬關係有沒有發生變化
    flag=1
    while clusterChanged:
        print("當前迭代次數爲:{}".format(flag))
        flag+=1
        clusterChanged=False
        for i in range(m):#m爲數據量的個數
            minDist = 10000#設置一個最大值
            minIndex = -1#初始化索引
            for j in range(k):#k爲劃分的種類數 此for循環給數據點分配所屬關係
                distJI = distMeans(centroids[j,:],dataSet[i,:])#距離值
                if distJI<minDist:
                    minDist = distJI
                    minIndex = j
            if clusterAssment[i,0]!=minIndex:#判斷所屬關係是否發生改變
                clusterChanged=True
            clusterAssment[i,:] = minIndex,minDist**2#這裏面存儲的是所屬關係和序列號
        #print(centroids)
        for cent in range(k):#這個for循環是用來移動分類點的位置,將其移動到所屬點的平均值位置
            # print("輸出1:",clusterAssment[:,0])
            # print("輸出2:",np.nonzero(clusterAssment[:,0].A==cent))
            #.A 是將矩陣轉化爲數組
            ptsInClust = dataSet[np.nonzero(clusterAssment[:,0]==cent)[0]]#取出相同簇的點進行取平均,這裏[0]是因爲參數的形狀爲(n,1)
            #np.nonzero 取值不爲0的索引值
            centroids[cent,:] = np.mean(ptsInClust,axis=0)#取平均
        #showProcess(clusterAssment,centroids)
    return centroids,clusterAssment


if __name__ == '__main__':
    CreatData()#生成數據
    datamat =  np.array(loadDateSet('data.txt'))
    centroids,clusterAssment = kMeans(datamat,2)

總結

K-mean均值算法的原理非常簡單,就是通過計算數據點距分類點的距離大小來判斷所屬關係,臨界條件就是數據的所屬關係不再發生改變。但是這種方式也存在一定的問題,很容易陷入到局部最優解。爲了更好的解決這個問題,我們下一章說一下改進後的算法,也就是二分K-均值法。

直達鏈接:二分K-mean均值算法

另附 python小程序-生成GIF圖和分解GIF圖:小程序地址

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