引言
聚类是一种无监督的学习,它将相似的对象归到同一个簇中。簇内的对象越相似,聚类的效果越好。
K-均值(K-means
K-均值(K-means)聚类的算法,之所以称之为K-均值是因为它可以发现K个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。
簇识别(cluster identification)
簇识别给出聚类结果的含义。假定有一些数据,现在将相似数据归到一起,簇识别会告诉我们这些簇到底都是些什么。
聚类与分类的最大不同在于,分类的目标事先巳知,而聚类则不一样。因为其产生的结果与分类相同,而只是类别没有预先定义,聚类有时也被称为无监督分类(unsupervised classification )。
10.1k-均值聚类算法
K-均值聚类优缺点
优点:容易实现。
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。
适用数据类型:数值型数据
K-均值聚类的一般流程
1 收集数据:使用任意方法。
2 准备数据:需要数值型数据来计算距离,也可以将标称型数据映射为二值型数据再用 于距离计算。
3 分析数据:使用任意方法。
4 训练算法:不适用于无监督学习,即无监督学习没有训练过程。
5 测试算法:应用聚类算法、观察结果。可以使用量化的误差指标如误差平方和来评价算法的结果。
6 使用算法:可以用于所希望的任何应用。通常情况下,簇质心可以代表整个簇的数据 来做出决策。
K-均值是发现给定数据集的k个簇的算法。簇个数k是用户给定的,每一个簇通过其质心,即簇中所有点的中心来描述。
K-均值算法的工作流程:首先,确定随机k个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值。伪代码如下:
“最近”质心意味着需要进行某种距离计算。数据集上K-均值算法的性能会受到所选距离计算方法的影响。下面给出K-均值算法的代码实现。首先创建一个名为kMeans.py的文件,然后将下面的代码加入其中:
from numpy import *
#导入数据
def loadDataSet(fileName):
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):
#power是求得次方,两个点差值的平方。
return sqrt(sum(power(vecA - vecB, 2)))
#产生质心向量
def randCent(dataSet, k):
#得到数据特征个数
n = shape(dataSet)[1]
#创建一个k x n 的矩阵
centroids = mat(zeros((k,n)))
#遍历数据集的每一特征
for j in range(n):
minJ = min(dataSet[:,j])
#得到特征最大差值
rangeJ = float(max(dataSet[:,j]) - minJ)
#k个质心向量的第j维数据值随机为位于(最小值,最大值)内的某一值
# rand(k,1)表示产生k行1列在0-1之间的随机数
centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1))
#返回初始化得到的k个质心向量
return centroids
#实现k均值算法,当所有中心不再改变时退出
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
#得到样本数
m = shape(dataSet)[0]
#创建一个m x 2 的矩阵
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):
#求取到各个聚簇中心的距离
distJI = distMeas(centroids[j,:],dataSet[i,:])
if distJI < minDist:
#记录最小距离值与聚簇索引
minDist = distJI; minIndex = j
# 样本类型是否需要改变,需要改变就将标识符修改为true
if clusterAssment[i,0] != minIndex: clusterChanged = True
#更新这个点的聚簇索引及误差
clusterAssment[i,:] = minIndex,minDist**2
print (centroids)
#遍历所有的簇,重新找质心
for cent in range(k):
# nonzero(a):返回数组a中非零元素的索引值数组
# 矩阵.A是将矩阵转换为数组numpy
#查找这个 cent 簇所有的点
ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
#对于这个簇中每个点的列取均值,更新中心点centers[cent]
centroids[cent,:] = mean(ptsInClust, axis=0)
return centroids, clusterAssment
dataMat=mat(loadDataSet(r'F:/machinelearning/machinelearninginaction/Ch10/testSet.txt'))
centers,clusterAssment=kMeans(dataMat,4)
print(centers)
#第一个列为簇的编号,第二列是当前点到这个簇的质心的距离
print(clusterAssment)
实验输出结果如下
上图第一个列为簇的编号,第二列是当前点到这个簇的质心的距离。
plt.figure()
#打印原始数据
plt.scatter(dataMat[:,0].tolist(),dataMat[:,1].tolist(),c="b",marker="o")
#打印聚类输出结果
plt.scatter(centers[:,0].tolist(),centers[:,1].tolist(),c='r',marker="+")
plt.show()
下面输出散点图
上图可知数据集散点分布大致可以化为四部分,所以程序中使k=4.输出聚类中心。
10.2使用后处理来提高聚类性能
K-均值聚类中簇的数目k是一个用户预先定义的参数,那么用户如何才能知道k的选择是否正确?如何才能知道生成的簇比较好?在包含簇分配结果的矩阵中保存着每个点的误差,即该点到簇质心的距离平方值。
考虑下图中的聚类结果,这是在一个包含三个簇的数据集上运行K-均值算法之后的结果,但是点的簇分配结果值没有那么准确。K-均值算法收敛但聚类效果较差的原因是,K-均值算法收敛到了局部最小值,而非全局最小值(局部最小值指结果还可以但非最好结果,全局最小值是可能的最好结果)。
一种用于度量聚类效果的指标是SSE(误差平方和),对应程序中clusterAssment矩阵的第一列之和。SSE值越小表示数据点越接近于它们的质心,聚类效果也越好。因为对误差取了平方,因此更加重视那些远离中心的点。一种肯定可以降低SSE值的方法是增加簇的个数,但这违背了聚类的目标。聚类的目标是在保持簇数目不变的情况下提高簇的质量。
那么如何对下图结果进行改进?可以对生成的簇进行后处理,一种方法是将具有最大SSE值的簇划分成两个簇。具体实现时可以将最大簇包含的点过滤出来并在这些点上运行K-均值算法,其中的k设为2。
为了保持簇总数不变,可以将某两个簇进行合并。图中可以明显看出,应该将下部两个出错的簇质心进行合并。可以很容易对二位数据上的聚类进行可视化。
10.3二分k-均值算法
为克服K-均值算法收敛于局部最小值的问题,有人提出另一个称为二分K-均值的算法。该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇机芯划分取决于对其划分是否可以最大程度降低SSE的值。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止
二分K-均值算法伪代码形式如下:
将所有点看成一个襄
当簇数目小于灸时
对于每一个簇
计算总误差
在给定的簇上面进行艮-均 值 聚 类 (^^2)
计算将该簇一分为二之后的总误差
选择使得误差最小的那个族进行划分操作
打开kMeans.py加入下面的代码:
def biKmeans(dataSet, k, distMeas=distEclud):
m = shape(dataSet)[0]
clusterAssment = mat(zeros((m,2)))
centroid0 = mean(dataSet, axis=0).tolist()[0]
centList =[centroid0] #create a list with one centroid
for j in range(m):#calc initial Error
clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2
while (len(centList) < k):
lowestSSE = inf
for i in range(len(centList)):
ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]#get the data points currently in cluster i
centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)
sseSplit = sum(splitClustAss[:,1])#compare the SSE to the currrent minimum
sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])
print("sseSplit, and notSplit: ",sseSplit,sseNotSplit)
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
上述程序中的函数与 kMeans() 的参数相同。在给定数据集、所期望的簇数目和计算方法的条件下,函数返回聚类结果。同 kMeans() 一样,用户可以改变所使用的距离计算方法。
该函数首先创建一个矩阵来存储集中在每个点的簇分配结果及平方误差,然后计算整个数据集的质心,并使用一个列表来保留所有的质心。得到上述质心之后,可以遍历数据集中所有点来计算每个点到质心的误差值。
接下来程序进入while循环,该循环会不停对簇进行划分,直到得到想要的簇数目为止。可以通过考察簇列表中的值来获得当前簇的数目。然后遍历所有的簇来决定最佳的簇进行划分。为此需要比较划分前后的SSE。一开始将最小SSE设置成无穷大,然后遍历簇列表cenList中的每一个簇。对每个簇,将该簇中的所有点砍成一个小的数据集ptsInCurrCluster。将ptsInCurrCluster输入到函数 kMeans() 中进行处理(k=2)。K-均值算法会生成两个质心(簇),同时给出每个簇的误差值。这些误差与剩余数据集的误差之和作为本次划分的误差。如果该划分的SSE值最小,则本次划分被保存。一旦决定了要划分的簇,接下来就要实际执行划分操作。划分操作很容易,只需要将要划分的簇中的所有点的簇分配结果进行修改即可。当使用 kMeans() 函数并且指定簇树为2时,会得到两个编号分别为0和1的结果簇。需要将这些簇编号修改为划分簇及新加簇的编号,该过程可以通过两个数组过滤器来完成。最后,新的簇分配结果被更新,新的质心会被添加到cenList中。
当while循环结束时,同 kMeans() 函数一样,函数返回质心列表与簇分配结果。
import kMeans
from numpy import *
datMat3 = mat(kMeans.loadDataSet('testSet2.txt'))
cenList,myNewAssments = kMeans.biKmeans(datMat3,3)
上述函数可以运行多次,聚类会收敛到全局最小值。
而原始的 kMeans() 函数偶尔会陷入局部最小值。
10.4小结
聚类是一种无监督的学习方法,是第一个接触到的无监督方法。无监督学习是指事先并不知道要寻找的内容,即没有目标变量。K -均值聚类算法以K个随机质心开始。算法会计算每个点到质心的距离。每个点会被分配到距其最近的簇质心,然后紧接着基于新分配到簇的点更新簇质心。以上过程重复数次,直到簇质心不再改变。这个简单的算法非常有效但是也容易受到初始簇质心的影响。
二分K-均值算法首先将所有点作为一个簇,然后使用K-均值算法 (K = 2 ) 对其划分。下一次迭代时,选择有最大误差的簇进行划分。该过程重复直到K个簇创建成功为止。二分K-均值的聚类效果要好于K-均值算法