二分K-均值算法 bisecting K-means in Python

时间:2022-02-23 22:06:16

下面的连续几篇博文将介绍无监督学习中的基于k均值算法的聚类法、基于Apriori算法的关联分析法,和更高效的基于FP-growth的关联分析方法。

需要注意的是,无监督学习不存在训练过程。



聚类法概念很好理解,但传统的K-means法存在较大的缺陷,我们首先介绍K-means法后着重介绍二分K-means法。


1、K-means法


1.1 实现伪代码

创建K个点作为起始簇心(一般随机选)
当数据集中任意点的簇分配结果发生变化:
对数据集中的每个数据点:
对每个簇心:
计算该数据点与该簇心之间的距离
将该数据点分配到距其最近的簇
对每个簇:
计算簇中所有数据点的均值作为该簇的簇心计算簇中所有数据点的均值作为该簇的簇心

具体实现上述伪代码中的任一点是否改变,可采用

flag = True
while flag:
flag = False
计算,分配
for 每个数据点:
if 该数据点此轮分配结果与之前不同:
flag = True


1.2 分析

(1)K-means的显著缺陷在于算法可能收敛到局部最小值,由于每轮循环都要遍历所有数据点,在大规模数据集上收敛较慢。

(2)K-means的另一个缺点在于,难以正确选择由用户预先设定的参数K。

(3)利用SSE——度量聚类效果的指标,即误差平方和,由于对误差取了平方,因此更加注重那些远离中心的点——改进K-means聚类性能,即对生成的簇进行后处理。首先,将具有最大SSE值的簇划分为两个簇;其次,为了保持簇总数不变,需要将某两个簇进行合并;一种合并法为合并最近的两个簇心,或者两个使得SSE增幅最小的簇心。

(4)而为了根本上克服K-means算法收敛于局部最小值的问题,关键在于二分K-means算法。


2、二分K-means算法


2.1 伪代码


将所有数据点看成一个簇
当簇的数目小于K时:
对于每一个簇:
在该簇上进行K-means聚类(k=2)
计算将该簇一分为二后的总误差1
计算除该簇以外的剩余数据集的总误差2
选择使得上述(误差1+误差2)最小的那个簇进行划分操作


2.2 分析

(1)上述伪代码中选择切分的簇的标准为“是否最大程度降低SSE值”,另一种方法也可以“选择SSE最大的簇进行划分”
(2)通常情况下,簇心可以代表整个簇的数据来做出决策,当然这也适用于K-means法
(3)除K-means算法、二分K-means算法,层次聚类的方法也广泛应用。


3、Python实现


from numpy import *

def loadDataSet(fileName):
dataMat = []
fr =open(fileName)
for line in fr.readlines():
currLine = line.strip().split('\t')
fltLine = map(float, currLine)
dataMat.append(fltLine)
return dataMat

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

def randCent(dataSet, k):
n = shape(dataSet)[1]
centroids = mat(zeros((k,n)))
for ii in range(n):
minI = min(dataSet[:,ii])
rangeI = float(max(dataSet[:,ii]) - minI)
centroids[:,ii] = minI + rangeI*random.random(size=(k,1))
return centroids

### k-Means
def kMeans(dataSet, k, distMeas=distEclud, createRandCent=randCent):
N = shape(dataSet)[0]
clusterAssment = mat(zeros((N,2)))
centroids = createRandCent(dataSet, k)
clusterChanged = True
while clusterChanged:
clusterChanged = False
for ii in range(N):
minDist = inf
minIndex = -1
for jj in range(k):
distIJ = distMeas(centroids[jj,:], dataSet[ii,:])
if distIJ < minDist:
minDist = distIJ
minIndex = jj
if clusterAssment[ii,0] != minIndex:
clusterChanged = True
clusterAssment[ii,:] = minIndex, minDist**2
print centroids
for cent in range(k):
ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
centroids[cent,:] = mean(ptsInClust, axis=0)
return centroids, clusterAssment


### bisecting K-means
def biKmeans(dataSet, k, distMeas=distEclud):
N = shape(dataSet)[0]
clusterAssment = mat(zeros((N,2)))
centroid0 = mean(dataSet, axis=0).tolist()[0]
centList = [centroid0]
for ii in range(N):
clusterAssment[ii,1] = distMeas(mat(centroid0), dataSet[ii,:]) **2
while (len(centList)<k):
lowestSSE = inf
for ii in range(len(centList)):
ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==ii)[0],:]
centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)
sseSplit = sum(splitClustAss[:,1])
sseNotSplit = sum(clusterAssment[nonzeros(clusterAssment[:,0].A!=ii)[0],1])
if (sseSplit+sseNotSplit) < lowestSSE:
bestCentToSplit = ii
bestNewCents = centroidMat
bestClustAss = splitClustAss.copy()
lowestSSE = sseSplit + sseNotSplit
bestClusterAss[nonzero(bestClustAss[:,0].A==1)[0],0] = len(centList)
bestClusterAss[nonzero(bestClustAss[:,0].A==0)[0],0] = bestCentToSplit
centList[bestCentToSplit] = bestNewCents[0,:]
centList.append(bestNewCents[1,:])
clusterAssment[nonzero(clusterAssment[:,0].A==bestCentToSplit)[0],:] = bestClustAss
return mat(centList), clusterAssment