今天是周天呢~还是逼着自己出来写完...
聚类是一种无监督的学习,它将相似的对象归到同一簇中。聚类的方法几乎可以应用所有对象,簇内的对象越相似,聚类的效果就越好。K-means算法中的k表示的是聚类为k个簇,means代表取每一个聚类中数据值的均值作为该簇的中心,或者称为质心,即用每一个的类的质心对该簇进行描述。
聚类和分类最大的不同在于,分类的目标是事先已知的,而聚类则不一样,聚类事先不知道目标变量是什么,类别没有像分类那样被预先定义出来,所以,聚类有时也叫无监督学习。
聚类分析试图将相似的对象归入同一簇,将不相似的对象归为不同簇,那么,显然需要一种合适的相似度计算方法,我们已知的有很多相似度的计算方法,比如欧氏距离,余弦距离,汉明距离等。事实上,我们应该根据具体的应用来选取合适的相似度计算方法。
当然,任何一种算法都有一定的缺陷,没有一种算法时完美的,有的只是人类不断追求完美,不断创新的意志。K-means算法也有它的缺陷,但是我们可以通过一些后处理来得到更好的聚类结果,这些在后面都会一一降到。
K-means算法虽然比较容易实现,但是其可能收敛到局部最优解,且在大规模数据集上收敛速度相对较慢。
2 K-means算法的工作流程
首先,随机确定k个初始点的质心;然后将数据集中的每一个点分配到一个簇中,即为每一个点找到距其最近的质心,并将其分配给该质心所对应的簇;该步完成后,每一个簇的质心更新为该簇所有点的平均值。伪代码如下:
创建k个点作为起始质心,可以随机选择(位于数据边界内)
当任意一个点的簇分配结果发生改变时
对数据集中每一个点
对每个质心
计算质心与数据点之间的距离
将数据点分配到距其最近的簇
对每一个簇,计算簇中所有点的均值并将均值作为质心
下面是实现代码:
#这是将文件数据导入的函数,需要注意的就是在python3中map的返回值是iterators,不再是列表。
def loadDataSet(filename):
dataSet = []
with open(filename,'r') as f:
lines =f.readlines()
for line in lines:
line = line.strip().split("\t")
#python3中map的返回值是iterators,不再是list
line = list(map(float,line))
dataSet.append(line)
return dataSet
#计算两个向量之间的欧氏距离
def distEclud(vecA,vecB):
return sqrt(sum(power(vecA-vecB,2)))
#随机生成k个质心
def randCent(dataSet,k):
n = dataSet.shape[1]
randMat = mat(zeros((k,n)))
for i in range(n):
mindig = min(dataSet[:,i])
#print(mindig)
#千万不能落下这个float,因为这个数组max和min生成的仍然是一个数组。如果不用float,后面进行实数乘矩阵会变成维度不同的矩阵相乘。
rangedig = float(max(dataSet[:,i])-mindig)
#print(rangedig),不加mat的话输出聚类矩阵时,k值有问题
randMat[:,i] = mat(mindig + rangedig * random.rand(k,1))
return randMat
#k均值算法
def KMeans(dataSet,k,distMeas=distEclud,createCent=randCent):
m = dataSet.shape[0]
clusterAssment = mat(zeros((m,2)))
centroids = createCent(dataSet,k)
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m):
mindist = inf; minIndex=-1
for j in range(k):
temp = distMeas(dataSet[i,:],centroids[j,:])
if(temp
有时候当我们观察聚类的结果图时,发现聚类的效果没有那么好,如上图所示,K-means算法在k值选取为3时的聚类结果,我们发现,算法能够收敛但效果较差。显然,这种情况的原因是,算法收敛到了局部最小值,而并不是全局最小值,局部最小值显然没有全局最小值的结果好。
那么,既然知道了算法已经陷入了局部最小值,如何才能够进一步提升K-means算法的效果呢?
一种用于度量聚类效果的指标是SSE,即误差平方和, 为所有簇中的全部数据点到簇中心的误差距离的平方累加和。SSE的值如果越小,表示数据点越接近于它们的簇中心,即质心,聚类效果也越好。因为,对误差取平方后,就会更加重视那些远离中心的数据点。
显然,我们知道了一种改善聚类效果的做法就是降低SSE,那么如何在保持簇数目不变的情况下提高簇的质量呢?
一种方法是:我们可以将具有最大SSE值得簇划分为两个簇(因为,SSE最大的簇一般情况下,意味着簇内的数据点距离簇中心较远),具体地,可以将最大簇包含的点过滤出来并在这些点上运行K-means算法,其中k设为2.
同时,当把最大的簇(上图中的下半部分)分为两个簇之后,为了保证簇的数目是不变的,我们可以再合并两个簇。具体地:
一方面我们可以合并两个最近的质心所对应的簇,即计算所有质心之间的距离,合并质心距离最近的两个质心所对应的簇。
另一方面,我们可以合并两个使得SSE增幅最小的簇,显然,合并两个簇之后SSE的值会有所上升,那么为了最好的聚类效果,应该尽可能使总的SSE值小,所以就选择合并两个簇后SSE涨幅最小的簇。具体地,就是计算合并任意两个簇之后的总得SSE,选取合并后最小的SSE对应的两个簇进行合并。这样,就可以满足簇的数目不变。
上面,是对已经聚类完成的结果进行改善的方法,在不改变k值的情况下,上述方法能够起到一定的作用,会使得聚类效果得到一定的改善。那么,下面要讲到的是一种克服算法收敛于局部最小值问题的K-means算法。即二分k-均值算法。
三,二分K-means算法
二分K-means算法首先将所有点作为一个簇,然后将簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇取决于对其进行划分是否能够最大程度的降低SSE的值。上述划分过程不断重复,直至划分的簇的数目达到用户指定的值为止。
二分K-means算法的伪代码如下:
将所有点看成一个簇
当簇数目小于k时
对于每一个簇
计算总误差
在给定的簇上面进行k-均值聚类(k=2)
计算将该簇一分为二之后的总误差
选择使得总误差最小的簇进行划分
当然,也可以选择SSE最大的簇进行划分,知道簇数目达到用户指定的数目为止。下面看具体的代码:
def biKMeans(dataSet,k,distMeas=distEclud):
m = dataSet.shape[0]
centroido = mean(dataSet,axis=0).tolist()[0]
clusterAssment = mat(zeros((m,2)))
centList = [centroido]
for j in range(m):
clusterAssment[j,1] = distMeas(dataSet[j,:],mat(centroido))**2
while(len(centList) < k):
lowestSSE = inf
for i in range(len(centList)):
#首先找到属于该类的数据集
IClassIndex = nonzero(clusterAssment[:,0].A == i)[0]
IdataSet = dataSet[IClassIndex,:]
centroidMat,splitClustAss = KMeans(IdataSet,2,distMeas)
sseSplit = sum(splitClustAss[:,1])
NotClassIndex = nonzero(clusterAssment[:,0].A != i)[0]
sseNotSplit = sum(clusterAssment[NotClassIndex,1])
if(sseNotSplit+sseSplit < lowestSSE):
bestClassToSplite = i
bestNewCents = centroidMat
bestClustAss = splitClustAss.copy() #不知道为什么要用copy
lowestSSE = sseSplit+sseNotSplit
#接下来更新簇的分配结果,将新分类的加为两类,类别为1的类改为类的长度,类别为0的类为原被分的类。
bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList)
bestClustAss[nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestClassToSplite
centList[bestClassToSplite] = bestNewCents[0,:].tolist()[0]
centList.append(bestNewCents[1,:].tolist()[0])
clusterAssment[nonzero(clusterAssment[:,0].A == bestClassToSplite)[0],:] = bestClustAss
return mat(centList),clusterAssment
写代码时对于nonzero(clusterAssment[:,0].A == i)[0]不是很理解,下面链接是分步讲该语句的实现的
https://blog.csdn.net/xinjieyuan/article/details/81477120
然后还有 return mat(centList)时报错
将centList[bestClassToSplite] = bestNewCents[0,:];centList.append(bestNewCents[1,:])改成centList[bestClassToSplite] = bestNewCents[0,:].tolist()[0] centList.append(bestNewCents[1,:].tolist()[0])就可以了。