
深入理解K-Means聚類算法
什么是聚類分析
聚類分析是在數(shù)據(jù)中發(fā)現(xiàn)數(shù)據(jù)對象之間的關(guān)系,將數(shù)據(jù)進行分組,組內(nèi)的相似性越大,組間的差別越大,則聚類效果越好。
不同的簇類型
聚類旨在發(fā)現(xiàn)有用的對象簇,在現(xiàn)實中我們用到很多的簇的類型,使用不同的簇類型劃分?jǐn)?shù)據(jù)的結(jié)果是不同的,如下的幾種簇類型。
明顯分離的
可以看到(a)中不同組中任意兩點之間的距離都大于組內(nèi)任意兩點之間的距離,明顯分離的簇不一定是球形的,可以具有任意的形狀。
基于原型的
簇是對象的集合,其中每個對象到定義該簇的原型的距離比其他簇的原型距離更近,如(b)所示的原型即為中心點,在一個簇中的數(shù)據(jù)到其中心點比到另一個簇的中心點更近。這是一種常見的基于中心的簇,最常用的K-Means就是這樣的一種簇類型。
這樣的簇趨向于球形。
基于密度的
簇是對象的密度區(qū)域,(d)所示的是基于密度的簇,當(dāng)簇不規(guī)則或相互盤繞,并且有早上和離群點事,常常使用基于密度的簇定義。
關(guān)于更多的簇介紹參考《數(shù)據(jù)挖掘導(dǎo)論》。
基本的聚類分析算法
1. K均值:
基于原型的、劃分的距離技術(shù),它試圖發(fā)現(xiàn)用戶指定個數(shù)(K)的簇。
2. 凝聚的層次距離:
思想是開始時,每個點都作為一個單點簇,然后,重復(fù)的合并兩個最靠近的簇,直到嘗試單個、包含所有點的簇。
3. DBSCAN:
一種基于密度的劃分距離的算法,簇的個數(shù)有算法自動的確定,低密度中的點被視為噪聲而忽略,因此其不產(chǎn)生完全聚類。
距離量度
不同的距離量度會對距離的結(jié)果產(chǎn)生影響,常見的距離量度如下所示:
K-Means算法
下面介紹K均值算法:
優(yōu)點:易于實現(xiàn)
缺點:可能收斂于局部最小值,在大規(guī)模數(shù)據(jù)收斂慢
算法思想較為簡單如下所示:
選擇K個點作為初始質(zhì)心
repeat
將每個點指派到最近的質(zhì)心,形成K個簇
重新計算每個簇的質(zhì)心
until 簇不發(fā)生變化或達到最大迭代次數(shù)
這里的重新計算每個簇的質(zhì)心,如何計算的是根據(jù)目標(biāo)函數(shù)得來的,因此在開始時我們要考慮距離度量和目標(biāo)函數(shù)。
考慮歐幾里得距離的數(shù)據(jù),使用誤差平方和(Sum of the Squared Error,SSE)作為聚類的目標(biāo)函數(shù),兩次運行K均值產(chǎn)生的兩個不同的簇集,我們更喜歡SSE最小的那個。
k表示k個聚類中心,ci表示第幾個中心,dist表示的是歐幾里得距離。
這里有一個問題就是為什么,我們更新質(zhì)心是讓所有的點的平均值,這里就是SSE所決定的。
下面用Python進行實現(xiàn)
# dataSet樣本點,k 簇的個數(shù)
# disMeas距離量度,默認(rèn)為歐幾里得距離
# createCent,初始點的選取
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
m = shape(dataSet)[0] #樣本數(shù)
clusterAssment = mat(zeros((m,2))) #m*2的矩陣
centroids = createCent(dataSet, k) #初始化k個中心
clusterChanged = True
while clusterChanged: #當(dāng)聚類不再變化
clusterChanged = False
for i in range(m):
minDist = inf; minIndex = -1
for j in range(k): #找到最近的質(zhì)心
distJI = distMeas(centroids[j,:],dataSet[i,:])
if distJI < minDist:
minDist = distJI; minIndex = j
if clusterAssment[i,0] != minIndex: clusterChanged = True
# 第1列為所屬質(zhì)心,第2列為距離
clusterAssment[i,:] = minIndex,minDist**2
print centroids
# 更改質(zhì)心位置
for cent in range(k):
ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
centroids[cent,:] = mean(ptsInClust, axis=0)
return centroids, clusterAssment
重點理解一下:
for cent in range(k):
ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
centroids[cent,:] = mean(ptsInClust, axis=0)
循環(huán)每一個質(zhì)心,找到屬于當(dāng)前質(zhì)心的所有點,然后根據(jù)這些點去更新當(dāng)前的質(zhì)心。
nonzero()返回的是一個二維的數(shù)組,其表示非0的元素位置。
>>> from numpy import *
>>> a=array([[1,0,0],[0,1,2],[2,0,0]])
>>> a
array([[1, 0, 0],
[0, 1, 2],
[2, 0, 0]])
>>> nonzero(a)
(array([0, 1, 1, 2]), array([0, 1, 2, 0]))
表示第[0,0],[1,1] … 位非零元素。第一個數(shù)組為行,第二個數(shù)組為列,兩者進行組合得到的。
ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
因此首先先比較clusterAssment[:,0].A==cent的真假,如果為真則記錄了他所在的行,因此在用切片進行取值。
一些輔助的函數(shù):
def loadDataSet(fileName): #general function to parse tab -delimited floats
dataMat = [] #assume last column is target value
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = map(float,curLine) #map all elements to float()
dataMat.append(fltLine)
return dataMat
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA - vecB, 2))) #la.norm(vecA-vecB)
def randCent(dataSet, k):
n = shape(dataSet)[1]
centroids = mat(zeros((k,n)))#create centroid mat
for j in range(n):#create random cluster centers, within bounds of each dimension
minJ = min(dataSet[:,j])
rangeJ = float(max(dataSet[:,j]) - minJ)
centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1))
return centroids
運行和結(jié)果
將上述代碼寫到kMeans.py中,然后打開python交互端。
>>> from numpy import *
>>> import kMeans
>>> dat=mat(kMeans.loadDataSet('testSet.txt')) #讀入數(shù)據(jù)
>>> center,clust=kMeans.kMeans(dat,4)
[[ 0.90796996 5.05836784]
[-2.88425582 0.01687006]
[-3.3447423 -1.01730512]
[-0.32810867 0.48063528]]
[[ 1.90508653 3.530091 ]
[-3.00984169 2.66771831]
[-3.38237045 -2.9473363 ]
[ 2.22463036 -1.37361589]]
[[ 2.54391447 3.21299611]
[-2.46154315 2.78737555]
[-3.38237045 -2.9473363 ]
[ 2.8692781 -2.54779119]]
[[ 2.6265299 3.10868015]
[-2.46154315 2.78737555]
[-3.38237045 -2.9473363 ]
[ 2.80293085 -2.7315146 ]]
# 作圖
>>>kMeans(dat,center)
繪圖的程序如下:
defdraw(data,center):length=len(center) fig=plt.figure# 繪制原始數(shù)據(jù)的散點圖plt.scatter(data[:,0],data[:,1],s=25,alpha=0.4)# 繪制簇的質(zhì)心點foriinrange(length): plt.annotate('center',xy=(center[i,0],center[i,1]),xytext=\ (center[i,0]+1,center[i,1]+1),arrowprops=dict(facecolor='red')) plt.show()
k均值算法非常簡單且使用廣泛,但是其有主要的兩個缺陷:
1. K值需要預(yù)先給定,屬于預(yù)先知識,很多情況下K值的估計是非常困難的,對于像計算全部微信用戶的交往圈這樣的場景就完全的沒辦法用K-Means進行。對于可以確定K值不會太大但不明確精確的K值的場景,可以進行迭代運算,然后找出Cost
Function最小時所對應(yīng)的K值,這個值往往能較好的描述有多少個簇類。
2.K-Means算法對初始選取的聚類中心點是敏感的,不同的隨機種子點得到的聚類結(jié)果完全不同
3.K均值算法并不是很所有的數(shù)據(jù)類型。它不能處理非球形簇、不同尺寸和不同密度的簇,銀冠指定足夠大的簇的個數(shù)是他通常可以發(fā)現(xiàn)純子簇。
4.對離群點的數(shù)據(jù)進行聚類時,K均值也有問題,這種情況下,離群點檢測和刪除有很大的幫助。
下面對初始質(zhì)心的選擇進行討論:
當(dāng)初始質(zhì)心是隨機的進行初始化的時候,K均值的每次運行將會產(chǎn)生不同的SSE,而且隨機的選擇初始質(zhì)心結(jié)果可能很糟糕,可能只能得到局部的最優(yōu)解,而無法得到全局的最優(yōu)解。如下圖所示:
可以看到程序迭代了4次終止,其得到了局部的最優(yōu)解,顯然我們可以看到其不是全局最優(yōu)的,我們?nèi)匀豢梢哉业揭粋€更小的SSE的聚類。
隨機初始化的局限
你可能會想到:多次運行,每次使用一組不同的隨機初始質(zhì)心,然后選擇一個具有最小的SSE的簇集。該策略非常的簡單,但是效果可能不是很好,這取決于數(shù)據(jù)集合尋找的簇的個數(shù)。
關(guān)于更多,參考《數(shù)據(jù)挖掘導(dǎo)論》
K-Means優(yōu)化算法
為了克服K-Means算法收斂于局部最小值的問題,提出了一種二分K-均值(bisecting K-means)
bisecting K-means
算法的偽代碼如下:
將所有的點看成是一個簇
當(dāng)簇小于數(shù)目k時
對于每一個簇
計算總誤差
在給定的簇上進行K-均值聚類,k值為2
計算將該簇劃分成兩個簇后總誤差
選擇是的誤差最小的那個簇進行劃分
完整的Python代碼如下:
def biKmeans(dataSet, k, distMeas=distEclud):
m = shape(dataSet)[0]
# 這里第一列為類別,第二列為SSE
clusterAssment = mat(zeros((m,2)))
# 看成一個簇是的質(zhì)心
centroid0 = mean(dataSet, axis=0).tolist()[0]
centList =[centroid0] #create a list with one centroid
for j in range(m): #計算只有一個簇是的誤差
clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2
# 核心代碼
while (len(centList) < k):
lowestSSE = inf
# 對于每一個質(zhì)心,嘗試的進行劃分
for i in range(len(centList)):
# 得到屬于該質(zhì)心的數(shù)據(jù)
ptsInCurrCluster =\ dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]
# 對該質(zhì)心劃分成兩類
centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)
# 計算該簇劃分后的SSE
sseSplit = sum(splitClustAss[:,1])
# 沒有參與劃分的簇的SSE
sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])
print "sseSplit, and notSplit: ",sseSplit,sseNotSplit
# 尋找最小的SSE進行劃分
# 即對哪一個簇進行劃分后SSE最小
if (sseSplit + sseNotSplit) < lowestSSE:
bestCentToSplit = i
bestNewCents = centroidMat
bestClustAss = splitClustAss.copy()
lowestSSE = sseSplit + sseNotSplit
# 較難理解的部分
bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) #change 1 to 3,4, or whatever
bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
print 'the bestCentToSplit is: ',bestCentToSplit
print 'the len of bestClustAss is: ', len(bestClustAss)
centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]#replace a centroid with two best centroids
centList.append(bestNewCents[1,:].tolist()[0])
clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss#reassign new clusters, and SSE
return mat(centList), clusterAssment
下面對最后的代碼進行解析:
bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) #change 1 to 3,4, or whatever
bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
這里是更改其所屬的類別,其中bestClustAss = splitClustAss.copy()是進行k-means后所返回的矩陣,其中第一列為類別,第二列為SSE值,因為當(dāng)k=2是k-means返回的是類別0,1兩類,因此這里講類別為1的更改為其質(zhì)心的長度,而類別為0的返回的是該簇原先的類別。
舉個例子:
例如:目前劃分成了0,1兩個簇,而要求劃分成3個簇,則在算法進行時,假設(shè)對1進行劃分得到的SSE最小,則將1劃分成了2個簇,其返回值為0,1兩個簇,將返回為1的簇改成2,返回為0的簇改成1,因此現(xiàn)在就有0,1,2三個簇了。
centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]#replace a centroid with two best centroids
centList.append(bestNewCents[1,:].tolist()[0])
clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss#reassign new clusters, and SSE
其中bestNewCents是k-means的返回簇中心的值,其有兩個值,分別是第一個簇,和第二個簇的坐標(biāo)(k=2),這里將第一個坐標(biāo)賦值給 centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0],將另一個坐標(biāo)添加到centList中 centList.append(bestNewCents[1,:].tolist()[0])
運行與結(jié)果
>>> from numpy import *
>>> import kMeans
>>> dat = mat(kMeans.loadDataSet('testSet2.txt'))
>>> cent,assment=kMeans.biKmeans(dat,3)
sseSplit, and notSplit: 570.722757425 0.0
the bestCentToSplit is: 0
the len of bestClustAss is: 60
sseSplit, and notSplit: 68.6865481262 38.0629506357
sseSplit, and notSplit: 22.9717718963 532.659806789
the bestCentToSplit is: 0
the len of bestClustAss is: 40
可以看到進行了兩次的劃分,第一次最好的劃分是在0簇,第二次劃分是在1簇。
可視化如下圖所示:
在原始的K-means算法中,每一次的劃分所有的樣本都要參與運算,如果數(shù)據(jù)量非常大的話,這個時間是非常高的,因此有了一種分批處理的改進算法。
使用Mini Batch(分批處理)的方法對數(shù)據(jù)點之間的距離進行計算。
Mini Batch的好處:不必使用所有的數(shù)據(jù)樣本,而是從不同類別的樣本中抽取一部分樣本來代表各自類型進行計算。n 由于計算樣本量少,所以會相應(yīng)的減少運行時間n 但另一方面抽樣也必然會帶來準(zhǔn)確度的下降。
數(shù)據(jù)分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
訓(xùn)練與驗證損失驟升:機器學(xué)習(xí)訓(xùn)練中的異常診斷與解決方案 在機器學(xué)習(xí)模型訓(xùn)練過程中,“損失曲線” 是反映模型學(xué)習(xí)狀態(tài)的核心指 ...
2025-09-19解析 DataHub 與 Kafka:數(shù)據(jù)生態(tài)中兩類核心工具的差異與協(xié)同 在數(shù)字化轉(zhuǎn)型加速的今天,企業(yè)對數(shù)據(jù)的需求已從 “存儲” 轉(zhuǎn)向 “ ...
2025-09-19CDA 數(shù)據(jù)分析師:讓統(tǒng)計基本概念成為業(yè)務(wù)決策的底層邏輯 統(tǒng)計基本概念是商業(yè)數(shù)據(jù)分析的 “基礎(chǔ)語言”—— 從描述數(shù)據(jù)分布的 “均 ...
2025-09-19CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-19SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎(chǔ)用法到實戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無論 ...
2025-09-18MySQL 大表拆分與關(guān)聯(lián)查詢效率:打破 “拆分必慢” 的認(rèn)知誤區(qū) 在 MySQL 數(shù)據(jù)庫管理中,“大表” 始終是性能優(yōu)化繞不開的話題。 ...
2025-09-18DSGE 模型中的 Et:理性預(yù)期算子的內(nèi)涵、作用與應(yīng)用解析 動態(tài)隨機一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開始提取前,需先判斷 TIF 文件的類型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價值的專業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫表、Excel 表、 ...
2025-09-17Excel 導(dǎo)入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實戰(zhàn)應(yīng)用 在用 Python(如 pandas 庫)處理 Excel 數(shù)據(jù)時,“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗與 t 檢驗:差異、適用場景與實踐應(yīng)用 在數(shù)據(jù)分析與統(tǒng)計學(xué)領(lǐng)域,假設(shè)檢驗是驗證研究假設(shè)、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專業(yè)操盤手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計劃中 rows 數(shù)量的準(zhǔn)確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對象的 text 與 content:區(qū)別、場景與實踐指南 在 Python 進行 HTTP 網(wǎng)絡(luò)請求開發(fā)時(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價值的核心操盤手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫表)是企業(yè)最基礎(chǔ)、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請求工具對比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長浮點數(shù)據(jù)的科學(xué)計數(shù)法問題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長浮點數(shù)據(jù)時的科學(xué)計數(shù)法問題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務(wù)數(shù)據(jù)分析步驟的落地者與價值優(yōu)化者 業(yè)務(wù)數(shù)據(jù)分析是企業(yè)解決日常運營問題、提升執(zhí)行效率的核心手段,其價值 ...
2025-09-12用 SQL 驗證業(yè)務(wù)邏輯:從規(guī)則拆解到數(shù)據(jù)把關(guān)的實戰(zhàn)指南 在業(yè)務(wù)系統(tǒng)落地過程中,“業(yè)務(wù)邏輯” 是連接 “需求設(shè)計” 與 “用戶體驗 ...
2025-09-11塔吉特百貨孕婦營銷案例:數(shù)據(jù)驅(qū)動下的精準(zhǔn)零售革命與啟示 在零售行業(yè) “流量紅利見頂” 的當(dāng)下,精準(zhǔn)營銷成為企業(yè)突圍的核心方 ...
2025-09-11