赞
踩
K均值聚类算法首先是聚类算法。
聚类是一种无监督的学习,将相似的对象归到同一个簇中。聚类与分类的最大不同在于分类的目标事先已知,而聚类则不知道。
K均值聚类算法是发现给定数据集k个簇的算法。如下图所示,如果设定的聚类簇数是4,那么理想的K均值算法可以按照两条黑线将所有数据分为4类:
K均值算法需要输入待聚类的数据和欲聚类的簇数k,主要的聚类过程有3步:
举一个例子,现在有若干可见光需要分成2类,先随机在其波长范围内选择2个波长作为质心,然后若干个可见光根据距离质心的远近分成两个簇,随后在每个簇内分别求波长的平均值作为质心。不断的迭代上面的过程:若干可见光根据距离质心的远近分成两个簇,随后在每个簇内分别求波长的平均值作为质心,直到所有可见光的类别不再改变。如果可见光均匀,K均值聚类理想,一般情况下会将光谱中绿色左边的聚为一类,绿色右边聚为一类。
- from numpy import *
-
- def loadDataSet(fileName):
- dataSet = []
- f = open(fileName)
- for line in f.readlines():
- curLine = line.strip().split('\t')
- fltLine = map(float, curLine)
- dataSet.append(fltLine)
- return mat(dataSet)
K均值聚类中需要计算数据和质心的距离,常见的距离有欧氏距离(Euclidean distance)和曼哈顿距离(Manhattan distance),本处采用欧式距离。
- def distEclud(vecA, vecB):
- return sqrt(sum(power(vecA - vecB, 2)))
这里需要说明的是初始质心的坐标有一个随机生成的范围,即必须在最大值和最小值之间,详见代码注释。
- def randCent(dataSet, k):
- n = shape(dataSet)[1] #n是列数
- centroids = mat(zeros((k, n)))
- for j in range(n):
- minJ = min(dataSet[:, j]) #找到第j列最小值
- rangeJ = float(max(dataSet[:, j]) - minJ) #求第j列最大值与最小值的差
- centroids[:, j] = minJ + rangeJ * random.rand(k, 1) #生成k行1列的在(0, 1)之间的随机数矩阵
- return centroids
矩阵clusterAssment
为m
行2
列,该矩阵和dataSet
的行数相同,行行对应,第一列表示与对应行距离最近的质心下标,第二列表示欧式距离的平方。
矩阵centroids
为k
行2
列,表示k
个质心的坐标。
- def KMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
- m = shape(dataSet)[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): #寻找最近质心
- distJI = distMeas(centroids[j, :], dataSet[i, :])
- if distJI < minDist: #更新最小距离和质心下标
- minDist = distJI; minIndex = j
- if clusterAssment[i, 0] != minIndex:
- clusterChanged = True
- clusterAssment[i, :] = minIndex, minDist**2 #记录最小距离质心下标,最小距离的平方
- print centroids
- for cent in range(k): #更新质心位置
- ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] #获得距离同一个质心最近的所有点的下标,即同一簇的坐标
- centroids[cent,:] = mean(ptsInClust, axis=0) #求同一簇的坐标平均值,axis=0表示按列求均值
- return centroids, clusterAssment
将所有数据使用matplotlib
画出来,方便观察。
- import matplotlib.pyplot as plt
- def showCluster(dataSet, k, clusterAssment, centroids):
- fig = plt.figure()
- plt.title("K-means")
- ax = fig.add_subplot(111)
- data = []
- for cent in range(k): #提取出每个簇的数据
- ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] #获得属于cent簇的数据
- data.append(ptsInClust)
- for cent, c, marker in zip( range(k), ['r', 'g', 'b', 'y'], ['^', 'o', '*', 's'] ): #画出数据点散点图
- ax.scatter(data[cent][:, 0], data[cent][:, 1], s=80, c=c, marker=marker)
- ax.scatter(centroids[:, 0], centroids[:, 1], s=1000, c='black', marker='+', alpha=1) #画出质心点
- ax.set_xlabel('X Label')
- ax.set_ylabel('Y Label')
- plt.show()
使用K均值算法对测试数据进行聚类,一般情况下效果不错,各个簇区别明显,如图所示:
但是有时候效果就不好了,因为K均值聚类收敛的是局部最小值,而不是全局最小值,如图所示:
下面的两类几乎分到了一起,左上的一类又被分成了两类,如何解决这个问题呢?二分K均值算法。
二分K均值算法是K均值算法的改进版。该算法首先将所有的点作为一个簇,然后将该簇一分为二,之后选择其中的一个簇继续划分。选择哪一个簇进行划分取决于对其划分是否可以最大程度的降低误差平方和。重复上述基于误差平方和的划分过程,直到簇数目和指定的k值相等。
- def biKmeans(dataSet, k, distMeas=distEclud):
- m = shape(dataSet)[0]
- clusterAssment = mat(zeros((m,2)))
- centroid0 = mean(dataSet, axis=0).tolist()[0] #求所有数据的平均值,创建一个初始簇质心
- centList =[centroid0]
- for j in range(m): #计算初始误差平方和
- clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2
- while (len(centList) < k): #只要聚类的个数小于等于k
- lowestSSE = inf
- for i in range(len(centList)):
- ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:] #获得属于cent簇的数据
- centroidMat, splitClustAss = KMeans(ptsInCurrCluster, 2, distMeas) #对一个簇中的数据进行kMeans
- sseSplit = sum(splitClustAss[:,1]) #计算本次划分误差平方和
- sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) #计算不属于cent簇的误差平方和
- 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) #将划分簇的编号转为新加簇的编号
- 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]#将一个质心更新为两个质心
- centList.append(bestNewCents[1,:].tolist()[0])
- clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss #更新数据的编号以及误差平方和
- return mat(centList), clusterAssment
一些说明:
MachineLearningAction
仓库里面有常见的机器学习算法处理常见数据集的各种实例,希望能够得到你的STARCopyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。