1. 程式人生 > >Python機器學習演算法實踐——k均值聚類(k-means)

Python機器學習演算法實踐——k均值聚類(k-means)

一開始的目的是學習十大挖掘演算法(機器學習演算法),並用編碼實現一遍,但越往後學習,越往後實現編碼,越發現自己的編碼水平低下,學習能力低。這一個k-means演算法用Python實現竟用了三天時間,可見編碼水平之低,而且在編碼的過程中看了別人的編碼,才發現自己對numpy認識和運用的不足,在自己的程式碼中有很多可以優化的地方,比如求均值的地方可以用mean直接對陣列求均值,再比如去最小值的下標,我用的是argsort排序再取列表第一個,但是有argmin可以直接用啊。下面的程式碼中這些可以優化的並沒有改,這麼做的原因是希望做到拋磚引玉,歡迎大家丟玉,如果能給出優化方法就更好了

一.k-means演算法

人以類聚,物以群分,k-means聚類演算法就是體現。數學公式不要,直接用白話描述的步驟就是:

  • 1.隨機選取k個質心(k值取決於你想聚成幾類)
  • 2.計算樣本到質心的距離,距離質心距離近的歸為一類,分為k類
  • 3.求出分類後的每類的新質心
  • 4.判斷新舊質心是否相同,如果相同就代表已經聚類成功,如果沒有就迴圈2-3直到相同

用程式的語言描述就是:

  • 1.輸入樣本
  • 2.隨機去k個質心
  • 3.重複下面過程知道演算法收斂:
    計算樣本到質心距離(歐幾里得距離)
    樣本距離哪個質心近,就記為那一類
    計算每個類別的新質心(平均值)

二。需求分析

資料來源:從國際統計局down的資料,資料為城鄉居民家庭人均收入及恩格爾係數(

點選這裡下載
資料描述:

  • 1.橫軸:城鎮居民家庭人均可支配收入和農村居民家庭人均純收入,
  • 2.縱軸:1996-2012年。
  • 3.資料為年度資料

需求說明:我想把這資料做個聚類分析,看人民的收入大概經歷幾個階段(感覺我好高大上啊)
需求分析:

  • 1.由於樣本資料有限,就兩列,用k-means聚類有很大的準確性
  • 2.用文字的形式匯入資料,結果輸出聚類後的質心,這樣就能看出人民的收入經歷了哪幾個階段

二.Python實現

引入numpy模組,借用其中的一些方法進行資料處理,上程式碼:

# -*- coding=utf-8 -*-

"""
authon:xuwf
created:2017-02-07
purpose:實現k-means演算法
"""
import numpy as np import random '''裝載資料''' def load(): data=np.loadtxt('data\k-means.csv',delimiter=',') return data '''計算距離''' def calcDis(data,clu,k): clalist=[] #存放計算距離後的list data=data.tolist() #轉化為列表 clu=clu.tolist() for i in range(len(data)): clalist.append([]) for j in range(k): dist=round(((data[i][1]-clu[j][0])**2+(data[i][2]-clu[j][1])**2)*0.05,1) clalist[i].append(dist) clalist=np.array(clalist) #轉化為陣列 return clalist '''分組''' def group(data,clalist,k): grouplist=[] #存放分組後的叢集 claList=clalist.tolist() data=data.tolist() for i in range(k): #確定要分組的個數,以空列表的形式,方便下面進行資料的插入 grouplist.append([]) for j in range(len(clalist)): sortNum=np.argsort(clalist[j]) grouplist[sortNum[0]].append(data[j][1:]) grouplist=np.array(grouplist) return grouplist '''計算質心''' def calcCen(data,grouplist,k): clunew=[] data=data.tolist() grouplist=grouplist.tolist() templist=[] #templist=np.array(templist) for i in range(k): #計算每個組的新質心 sumx=0 sumy=0 for j in range(len(grouplist[i])): sumx+=grouplist[i][j][0] sumy+=grouplist[i][j][1] clunew.append([round(sumx/len(grouplist[i]),1),round(sumy/len(grouplist[i]),1)]) clunew=np.array(clunew) #clunew=np.mean(grouplist,axis=1) return clunew '''優化質心''' def classify(data,clu,k): clalist=calcDis(data,clu,k) #計算樣本到質心的距離 grouplist=group(data,clalist,k) #分組 for i in range(k): #替換空值 if grouplist[i]==[]: grouplist[i]=[4838.9,1926.1] clunew=calcCen(data,grouplist,k) sse=clunew-clu #print "the clu is :%r\nthe group is :%r\nthe clunew is :%r\nthe sse is :%r" %(clu,grouplist,clunew,sse) return sse,clunew,data,k if __name__=='__main__': k=3 #給出要分類的個數的k值 data=load() #裝載資料 clu=random.sample(data[:,1:].tolist(),k) #隨機取質心 clu=np.array(clu) sse,clunew,data,k=classify(data,clu,k) while np.any(sse!=0): sse,clunew,data,k=classify(data,clunew,k) clunew=np.sort(clunew,axis=0) print "the best cluster is %r" %clunew

三.測試

直接執行程式就可以,k值可以自己設定,會發現k=3的時候結果資料是最穩定的,這裡我就不貼圖了
需要注意的是上面的程式碼裡面主函式裡的資料結構都是array,但是在每個小函式裡就有可能轉化成了list,主要原因是需要進行array的一下方法進行計算,而轉化為list的原因是需要向陣列中插入資料,但是array做不到啊(至少我沒找到怎麼做)。於是這裡就出現了一個問題,那就是資料結構混亂,到最後我除錯了半天,乾脆將主函式的資料結構都轉化成array,在小函式中輸入的array,輸出的時候也轉化成了array,這樣就清晰多了

四.演算法分析

單看這個演算法還是較好理解的,但是演算法的目的是聚類,那就要考慮到聚類的準確性,這裡聚類的準確性取決於k值、初始質心和距離的計算方式。

  • k值就要看個人經驗和多次試驗了,演算法結果在哪個k值的時候更穩定就證明這個分類更加具有可信度,其中演算法結果的穩定也取決於初始質心的選擇
  • 初始質心一般都是隨機選取的,怎麼更準確的選擇初始質心呢?有種較難實現的方法是將樣本中所有點組合起來都取一遍,然後計算演算法收斂後的所有質心到樣本的距離之和,哪個距離最小,哪個的聚類就最為成功,相對應的初始質心就選取的最為準確。但是這種方法有很大的計算量,如果樣本很大,維度很多,那就是讓電腦幹到死的節奏
  • 距離的計算方式取決於樣本的特徵,有很多的選擇,入歐式距離,夾角餘弦距離,曼哈頓距離等,具體的資料特性用具體的距離計算方式

五.專案評測

1.專案總結資料來源的資料很乾淨,不需要進行過多的資料清洗和資料降噪,資料預處理的工作成本接近為0。需求基本實現
2.還能做什麼:可以用計算最小距離之和的方法求出最佳k值,這樣就可以得到穩定的收入階梯;可以引入畫圖模組,將資料結果進行資料視覺化,顯得更加直觀;如果可能應該引入更多的維度或更多的資料,這樣得到的聚類才更有說服力