k-Means 算法
聚類是一種無監督的學習,它將相似的對像歸到一個簇中,將不相似對象歸到不同類中,相似這一概念取決於所選擇的相似度計算方法。
k-Means 是發現給定數據集的k個簇的聚類算法,之所以稱之爲k-均值,是因爲它可以發現K個不同的簇,且每個簇的中心所含值的均值計算而成。簇的個數k是用戶指定,每一個簇通過質心(centroid),即簇中所有點的中心來描述,聚類與分類的算法的最大區別在於,分類的目標類別是已知的,而聚類的目標類別是未知的。
k-Means 術語
簇:所有數據點的集合,簇中的對象是相似的。
質心:簇中所有的中心(計算所有值的均值而來)
SSE:Sum of Square Error (平方誤差和),SSE 值越小,表示越接近他們的質心,由於對誤差取了平方,因此更加註重那些遠離中心的點。
K -Means 工作流程
- 首先,隨機確定k個初始點作爲質心(通常是隨機選擇)
- 然後將數據集中的每一個點分配到一個簇中,具體來講,就是爲每個點找到距離其最近的質心,並將其分配該質心所對應的簇中,之後每個簇的質心更新爲該簇中所有點的均值。
從文件加載數據集
def loadDataSet(fileName):#通用函數,用來解析以tab鍵分隔的floats
dataMat = []
fr=open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine =map(float,curLine)#映射所有元素爲float類型
dataMat.append(fltLine)
return dataMat
計算兩個向量的歐式距離
def distEclud(vecA,vecB):
return sqrt(sum(power(vecA-vecB,2)))
構建一個包含k個隨機質心的集合
爲給定數據集構造一個包含k個隨機質心的集合,隨機質心必須在整個數據集邊界之內,這可以通過找到數據集每一維的最小值和最大值來完成,然後生成0~1.0之間的隨機數通過取值範圍和最小值,以便確保隨機點在數據的邊界之內。
def randCent(dataSet,k):
n=shape(dataSet)[1]
centroids = mat(zeros((k,n))) #創建k個質心矩陣
for j in range(n): #創建 隨機質心,並且保證在每一維的邊界內
minJ=min(dataSet[:,j])
rangeJ = float(max(dataSet[:,j])-minJ)
centroids[:,j] = mat(minJ+rangeJ*random.rand(k,1))
return centroids
注:numpy.random.rand(d0, d1, …, dn)
的隨機樣本位於[0, 1)中
arr = np.random.rand(2,4)
print(arr)
[[ 0.19947349 0.05282713 0.56704222 0.45479972]
[ 0.28827103 0.1643551 0.30486786 0.56386943]]
k-Means聚類算法
該算法會創建k個質心,然後將每個點分配到最近的質心,再重新計算質心。這個過程會重複數次,直到數據點的簇分配結果不再改變位置,
def kMeans(dataSet,k,distMeas=distEculd,createCent=randCent):
m=shape(dataSet)[0]
clusterAssment=mat(zeros((m,2)))#保存簇的分配結果
centroids =createCent(dataSet,k)
clusterChange=True
while clusterChange:
clusterChange = False
for i in range(m): #循環每一個數據點並分配到最近的質心中去
minDist = inf;minIndex=-1
for j in range(k):
dist =distMeas(dataSet[i,:],centroids[j,:])#計算每一個數據點到質心的距離
if dist<minDist: #更新minDist 和 minIndex
minDist =dist;minIndex =j
if clusterAssment[i,:]!=minIndex:
clusterAssment[i,:]=minIndex,minDist
print(centroids)
for cent in range(k):#更新質點
ptsInClust =dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] #獲取該簇中的所有點
centroids[cent,:]=mean(ptsInClust,axis=0)#將質心修改爲簇中所有點的平均值
return centroids,clusterAssment
k-Means 聚類算法的缺陷
在k-Means的函數測試中可能偶爾會陷入局部最小值(局部最優的結果,但不是全局最優的結果),爲了克服這種情況,提出二分k-均值算法(bisecting K-Means)
二分k -Means 聚類算法
該算法首先將所有點作爲一個簇,然後將該簇一分爲二,之後選擇其中一個簇繼續進行劃分,選擇哪一個簇進行劃分取決於對其劃分時可以
最大程度降低SSE(平方和誤差)的值。劃分過程不斷重複,直到得到用戶指定的簇數目爲止。第二種方法,選擇SSE最大的簇進行劃分。
二分k-Means 聚類算法
def biKMeans(dataSet,k,distMeas=distEclud):
m=shape(dataSet)[0]
clusterAssment=mat(zeros((m,2)))#保存每一個數據點的簇分配結果和最小平方誤差和
centroid0=mean(dataSet,axis =0).tolist()[0] #質心初始化爲所有數據點的均值
centList = [centroid0] #初始化只有1個質心的list
for j in range(m):
clusterAssment[j,1]=distMeas(mat(centroid0),dataSet[j,:])**2
while (len(centList)<k): #當質心數量小於k
lowestSSE=inf
for i in rang(len(centList)): #遍歷每一個質心
ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]#獲取當前數據集下屬於簇i的所有數據點
centroidMat ,splitClustAss =kMeans(ptsInCurrCluster,2,distMeas) # 將當前簇 i 進行二分 kMeans 處理
sseSplit = sum(splitClustAss[:,1]) #將二分的kmeans結果中的平方和的距離進行求和
sseNoSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])
if(sseSplit+sseNoSplit)<lowestSSE: #總的誤差和越小,越相似,效果越優越,劃分的結果越好
bestNewCents=centroidMat
bestClustAss=splitClustAss.copy
lowestSSE=sseSplit+sseNoSplit
#找出最好的簇分配結果
bestClustAss[nonzero(bestClustAss[:,0].A==1)[0],0]=len(centList) # 調用二分 kMeans 的結果,默認簇是 0,1.
# 當然也可以改成其它的數字
bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0], 0] = bestCentToSplit # 更新爲最佳質心
#更新質心列表
centList[bestCentToSplit]=bestNewCents[0,:].tolist()[0]# 更新原質心 list 中的第 i個質心爲使用二分 kMeans
# 後 bestNewCents 的第一個質心
centList.append(bestNewCents[1,:].tolist()[0]) # 添加 bestNewCents 的第二個質心
clusterAssment[nonzero(clusterAssment[:,0].A==bestCentToSplit)[0],:]=bestClustAss
return mat(centList),clusterAssment