【機(jī)器學(xué)習(xí)】無監(jiān)督學(xué)習(xí) K-means(K-均值聚類算法)

任務(wù)

  1. 利用K-均值聚類算法對(duì)未標(biāo)注數(shù)據(jù)分組
  2. 對(duì)聚類得到的簇進(jìn)行后處理
  3. 二分K-均值聚類算法

思想

  • 聚類算法幾乎可以用于任何對(duì)象侦啸,簇內(nèi)對(duì)象越相似,聚類效果越好偎痛。
  • 相似度取決于相似度的計(jì)算方法衅鹿,根據(jù)具體應(yīng)用選擇相似度的計(jì)算方法
  • K-均值可以發(fā)現(xiàn)k個(gè)不同的簇,且每個(gè)簇的中心都采用簇中所含值的均值計(jì)算而成
  • 簇識(shí)別給出簇類結(jié)果的含義偷俭。
  • 聚類與分類最大區(qū)別在于浪讳,分類的目標(biāo)事先知道缰盏,而聚類是未知的。聚類產(chǎn)生的結(jié)果與分類相同,只是類別沒有預(yù)先定義口猜。所以聚類算法也叫無監(jiān)督分類负溪。

缺點(diǎn)與改進(jìn)

  • 簡(jiǎn)單K-均值算法的一些缺陷,可以用二分K-均值算法(bisecting k-means)解決

一济炎、K-均值聚類算法的構(gòu)建

優(yōu)缺點(diǎn)

優(yōu)點(diǎn):容易實(shí)現(xiàn)
缺點(diǎn):可能收斂與局部最小值川抡,在大規(guī)模數(shù)據(jù)集上收斂較慢

使用范圍

數(shù)值型數(shù)據(jù)

構(gòu)建步驟

給定數(shù)據(jù)集k個(gè)簇,每個(gè)簇通過其質(zhì)心(centroid),即簇中所有點(diǎn)的中心來描述

偽代碼:

創(chuàng)建k個(gè)點(diǎn)作為起始質(zhì)心(經(jīng)常是隨機(jī)選擇)
當(dāng)任意一個(gè)點(diǎn)的簇分配結(jié)果發(fā)生變化時(shí):
    對(duì)數(shù)據(jù)集中的每個(gè)數(shù)據(jù)點(diǎn):
        對(duì)每個(gè)質(zhì)心:
            計(jì)算質(zhì)心與數(shù)據(jù)點(diǎn)之間的距離
        將數(shù)據(jù)點(diǎn)分配到距離其最近的簇
    對(duì)每一個(gè)簇须尚,計(jì)算簇中所有點(diǎn)的均值崖堤,并將均值作為該簇的中心

編寫輔助函數(shù):

from numpy import *
"""
創(chuàng)建k個(gè)點(diǎn)作為起始質(zhì)心(經(jīng)常是隨機(jī)選擇)
當(dāng)任意一個(gè)點(diǎn)的簇分配結(jié)果發(fā)生變化時(shí):
    對(duì)數(shù)據(jù)集中的每個(gè)數(shù)據(jù)點(diǎn):
        對(duì)每個(gè)質(zhì)心:
            計(jì)算質(zhì)心與數(shù)據(jù)點(diǎn)之間的距離
        將數(shù)據(jù)點(diǎn)分配到距離其最近的簇
    對(duì)每一個(gè)簇,計(jì)算簇中所有點(diǎn)的均值耐床,并將均值作為該簇的中心
"""
#輔助函數(shù)
def loadDataSet(fileName): #將文本文件轉(zhuǎn)換成列表的列表格式
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = list(map(float, curLine))
        dataMat.append(fltLine)
    return dataMat

def distEclud(vecA, vecB): #計(jì)算兩個(gè)向量之間的歐氏距離
    return sqrt(sum(power(vecA - vecB, 2)))

def randCent(dataSet, k):
    #構(gòu)建一個(gè)包含k個(gè)隨機(jī)質(zhì)心的集合
    #隨機(jī)質(zhì)心必須在整個(gè)數(shù)據(jù)集的邊界之內(nèi)
    n = shape(dataSet)[1] #獲得列數(shù)
    centroids = mat(zeros((k, n))) #構(gòu)建k行n列的質(zhì)心
    for j in range(n):
        minJ = min(dataSet[:,j])  #獲得數(shù)據(jù)集中每一個(gè)維(每列)的最小值
        rangeJ = float(max(dataSet[:,j] - minJ)) #獲得數(shù)據(jù)集中每一個(gè)維(每列)的取值范圍
        centroids[:,j] = minJ + rangeJ * random.rand(k,1) #生成[0, 1.0]之間的隨機(jī)數(shù)
        #通過取值范圍和最小值密幔,確保隨機(jī)點(diǎn)在數(shù)據(jù)的邊界之內(nèi)
    return centroids

測(cè)試運(yùn)行:

In [66]: import kMeans

In [67]: from numpy import *

In [68]: datMat = mat(kMeans.loadDataSet('testSet.txt'))

In [69]: min(datMat[:,0])
Out[69]: matrix([[-5.379713]])

In [70]: min(datMat[:,1])
Out[70]: matrix([[-4.232586]])

In [71]: max(datMat[:,1])
Out[71]: matrix([[5.1904]])

In [72]: max(datMat[:,0])
Out[72]: matrix([[4.838138]])

In [73]: kMeans.randCent(datMat, 2)
Out[73]:
matrix([[ 0.50953466, -3.71200331],
        [ 3.98163501, -2.66656359]])
In [74]: kMeans.distEclud(datMat[0], datMat[1])
Out[74]: 5.184632816681332

實(shí)現(xiàn)完整的K-均值算法

  • 該算法會(huì)構(gòu)建k個(gè)質(zhì)心,然后將每個(gè)點(diǎn)分配到最近的質(zhì)心撩轰,再重新計(jì)算胯甩。
  • 這個(gè)過程重復(fù)無數(shù)次,直到數(shù)據(jù)點(diǎn)的簇分配結(jié)果不再改變?yōu)橹?/li>
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
    m = shape(dataSet)[0]  #獲得數(shù)據(jù)點(diǎn)的總數(shù)
    clusterAssment = mat(zeros((m,2))) #創(chuàng)建一個(gè)矩陣來存儲(chǔ)每個(gè)點(diǎn)的簇分配結(jié)果:
    #第一列記錄簇索引值堪嫂,第二列存儲(chǔ)誤差
    #誤差:當(dāng)前點(diǎn)到簇質(zhì)心的距離偎箫。后面將使用該誤差來評(píng)價(jià)聚類的效果
    centroids = createCent(dataSet, k)  
    
    #循環(huán)迭代,直到所有數(shù)據(jù)點(diǎn)的簇分配不再改變?yōu)橹?    clusterChanged = True
    while clusterChanged: 
        clusterChanged = False
        for i in range(m): #對(duì)dataSet中的每個(gè)數(shù)據(jù)點(diǎn)
            minDist = inf; minIndex = -1
            #遍歷所有質(zhì)心皆串,并計(jì)算當(dāng)前點(diǎn)到每個(gè)質(zhì)心的距離
            for j in range(k): 
                distJI = distMeas(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)
        
        #最后淹办,遍歷所有質(zhì)心并更新它們的取值
        for cent in range(k):
            #通過數(shù)組過濾,獲得給定簇的所有點(diǎn)
            ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
            centroids[cent,:] = mean(ptsInClust, axis=0) #計(jì)算所有點(diǎn)的均值愚战,axis=0
                    
    return centroids, clusterAssment

運(yùn)行:

In [83]: import importlib

In [84]: importlib.reload(kMeans)
Out[84]: <module 'kMeans' from 'D:\\Data\\File\\ML\\kMeans.py'>

In [85]: datMat = mat(kMeans.loadDataSet('testSet.txt'))

In [86]: myCentroids, clustAssing = kMeans.KMeans(datMat, 4)
[[ 1.00280717  0.11770989]
 [-1.73165675 -3.43839335]
 [ 2.55164274 -2.32833514]
 [ 3.63722591  0.00961112]]
[[-0.93031861  2.9279739 ]
 [-3.19984738 -2.96423548]
 [ 2.7475792  -3.14066887]
 [ 3.57953285  1.76429869]]
[[-1.94392522  2.96291883]
 [-3.38237045 -2.9473363 ]
 [ 2.72031426 -2.83200232]
 [ 2.91014783  2.71954072]]
[[-2.46154315  2.78737555]
 [-3.38237045 -2.9473363 ]
 [ 2.80293085 -2.7315146 ]
 [ 2.6265299   3.10868015]]

使用后處理來提高聚類性能

由于K-均值算法可能只收斂到局部最小值娇唯,而非全局最小值

聚類的目標(biāo)是在保持簇?cái)?shù)目不變的情況下,提高簇的質(zhì)量

將具有最大SSE值的簇寂玲,劃分為兩個(gè)簇

可以將最大簇包含的點(diǎn)過濾出來塔插,并且在這些點(diǎn)上再運(yùn)行K-均值算法

為了保持簇總數(shù)不變,將某兩個(gè)分錯(cuò)的簇的質(zhì)心進(jìn)行合并(兩種量化的方法)

  • ①合并最近的質(zhì)心
    通過計(jì)算所有質(zhì)心之間的距離拓哟,然后合并距離最近的兩個(gè)點(diǎn)
  • ②合并兩個(gè)使得SSE增幅最小的質(zhì)心
    合并兩個(gè)簇想许,然后計(jì)算總SSE值。在所有可能的兩個(gè)簇上重復(fù)這個(gè)處理過程断序,直到找到合并最佳的兩個(gè)簇為止流纹。

二、二分K-均值算法(bisecting K-means)

思想

  • 將所有點(diǎn)看成一個(gè)簇
  • 對(duì)該簇一分為二
  • 選擇可以最大程度降低SSE值的其中一個(gè)簇繼續(xù)進(jìn)行劃分
  • 上述基于SSE的劃分過程不斷重復(fù)违诗,直到得到用戶指定的簇?cái)?shù)目為止

偽代碼

將所有點(diǎn)看成一個(gè)簇
當(dāng)簇?cái)?shù)目小于k時(shí):
    對(duì)于每一個(gè)簇:
        計(jì)算總誤差
        在給定的簇上面進(jìn)行K-均值聚類(k=2)
        計(jì)算該簇一分為二之后的總誤差
    選擇使得誤差最小的那個(gè)簇進(jìn)行劃分操作

另一種做法是選擇SSE最大的簇進(jìn)行劃分操作漱凝,直到簇?cái)?shù)目達(dá)到用戶指定的數(shù)目為止

代碼

def biKmeans(dataSet, k, distMeas=distEclud):
    m = shape(dataSet)[0]
    clusterAssment = mat(zeros((m,2))) 
    centroid0 = mean(dataSet, axis=0).tolist()[0]  #計(jì)算整個(gè)數(shù)據(jù)集的質(zhì)心
    centList = [centroid0] #創(chuàng)建一個(gè)初始化簇,并使用一個(gè)列表來保存所有質(zhì)心
    
    for j in range(m): #遍歷數(shù)據(jù)集诸迟,計(jì)算每個(gè)點(diǎn)到質(zhì)心的誤差值(距離的平方)
        clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2
    # 嘗試劃分已有的每一個(gè)簇,尋找使得SSE降幅最大的那個(gè)簇,然后對(duì)其進(jìn)行2-Means聚類劃分
    while (len(centList) < k):
        lowestSSE = inf
        for i in range(len(centList)):
            #嘗試劃分每一個(gè)簇
            ptsInCurrCluster = \
                dataSet[nonzero(clusterAssment[:,0].A==i)[0],:] #選擇每一個(gè)簇中的所有點(diǎn)茸炒,作為一個(gè)小的數(shù)據(jù)集
            centroidMat, splitClustAss = \
                kMeans(ptsInCurrCluster, 2, distMeas)  #將該簇用kMeans一分為二愕乎,給出質(zhì)心,分配的質(zhì)心和誤差值
            sseSplit = sum(splitClustAss[:,1]) #計(jì)算這個(gè)簇劃分成兩個(gè)簇以后的誤差的和
            sseNotSplit = \
                sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) #計(jì)算剩余未被劃分的簇(剩余數(shù)據(jù)集)的誤差的和
            print("sseSplit, and sseNotSplit: ", sseSplit, sseNotSplit)
            if (sseSplit + sseNotSplit) < lowestSSE: #如何該劃分的SSE值最小壁公,則本次劃分被保存
                bestCentToSplit = i
                bestNewCents = centroidMat.copy()
                bestClustAss = splitClustAss.copy()  #該簇的劃分情況
                lowestSSE = sseSplit + sseNotSplit
                #實(shí)際執(zhí)行劃分操作:把要?jiǎng)澐值拇刂械乃悬c(diǎn)的簇分配結(jié)果進(jìn)行修正
            bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] =\
                                 len(centList) #新加簇的編號(hào)
            bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] =\
                                 bestCentToSplit #劃分簇的編號(hào)
            print('the bestCentToSplit is: ', bestCentToSplit)
            print('the len of bestClustss is: ', len(bestClustAss))
            #新的質(zhì)心被添加到centList
            centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0] #將cluster0的簇的質(zhì)心分配到原本簇的質(zhì)心
            centList.append(bestNewCents[1,:].tolist()[0])  #將cluster1的簇質(zhì)心添加到centList
            clusterAssment[nonzero(clusterAssment[:,0].A ==\
                                  bestCentToSplit)[0], :] = bestClustAss #將原本被劃分的簇感论,更新簇分配的結(jié)果
    return mat(centList), clusterAssment

運(yùn)行:

> importlib.reload(kMeans)
> datMat3=mat(kMeans.loadDataSet('testSet2.txt'))
> centList,myNewAssments = kMeans.biKmeans(datMat3,3)
sseSplit, and sseNotSplit:  570.7227574246755 0.0
the bestCentToSplit is:  0
the len of bestClustss is:  60
sseSplit, and sseNotSplit:  68.68654812621844 38.06295063565756
the bestCentToSplit is:  0
the len of bestClustss is:  40
sseSplit, and sseNotSplit:  22.971771896318412 68.68654812621844
the bestCentToSplit is:  1
the len of bestClustss is:  20

質(zhì)心:

In [96]: centList
Out[96]:
matrix([[ 2.02712544,  3.50141167],
        [-2.94737575,  3.3263781 ],
        [ 3.67574036,  2.82216836],
        [-0.45965615, -2.7782156 ]])

質(zhì)心示意圖:

import matplotlib.pyplot as plt
centroids_x = centList[:,0]
centroids_y = centList[:,1]
plt.scatter(centroids_x.tolist(),centroids_y.tolist(), 
            marker = 'x', 
            s=150, linewidths = 5,
            zorder = 10, 
            c=['green', 'red','blue','black'])

聚類會(huì)收斂到全局最小值,原始的kMeans()函數(shù)偶爾會(huì)陷入·局部最小值

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末紊册,一起剝皮案震驚了整個(gè)濱河市比肄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌囊陡,老刑警劉巖芳绩,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異撞反,居然都是意外死亡示括,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門痢畜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垛膝,“玉大人,你說我怎么就攤上這事丁稀『鹩担” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵线衫,是天一觀的道長(zhǎng)凿可。 經(jīng)常有香客問我,道長(zhǎng)授账,這世上最難降的妖魔是什么枯跑? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮白热,結(jié)果婚禮上敛助,老公的妹妹穿的比我還像新娘。我一直安慰自己屋确,他們只是感情好纳击,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著攻臀,像睡著了一般焕数。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刨啸,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天堡赔,我揣著相機(jī)與錄音,去河邊找鬼设联。 笑死善已,一個(gè)胖子當(dāng)著我的面吹牛苦蒿,可吹牛的內(nèi)容都是我干的胚吁。 我是一名探鬼主播昙衅,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼煞檩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼粘招!你這毒婦竟也來了啥寇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤洒扎,失蹤者是張志新(化名)和其女友劉穎辑甜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袍冷,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磷醋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胡诗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邓线。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖煌恢,靈堂內(nèi)的尸體忽然破棺而出骇陈,到底是詐尸還是另有隱情,我是刑警寧澤瑰抵,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布你雌,位于F島的核電站,受9級(jí)特大地震影響二汛,放射性物質(zhì)發(fā)生泄漏婿崭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一肴颊、第九天 我趴在偏房一處隱蔽的房頂上張望氓栈。 院中可真熱鬧,春花似錦婿着、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至袜硫,卻和暖如春氯葬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背婉陷。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工帚称, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留官研,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓闯睹,卻偏偏與公主長(zhǎng)得像戏羽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子楼吃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354