- K-均值(K-means)算法:它可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。
- 聚类与分类最大的不同在于,分类的目标实现已知,而聚类则不一样。因为其产生的结果与分类相同,而只是类别没有预先定义。
- 聚类分析试图将相似对象归入同一簇,将不相似对象归到不同簇。相似这一概念取决于所选择的相似度计算方法。
如何影响选民,进行拉票?
首先,收集用户的信息,可以同时收集用户满意或不满意的信息,这是因为任何对用户重要的内容都可能影响用户的投票结果。然后,将这些信息输入到某个聚类算法中。接着,对聚类结果中的每一个簇(最好选择最大簇),精心构造能够吸引该簇玄冥的消息。最后,开展竞选活动并观察上述做法是否有效。
10.1 K-均值聚类算法
- 优缺点:
优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢 - 流程:
随机确定k个初始点作为质心,然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值。(KNN计算要归类点到所有点的距离,然后排序选择最近K个点中所属分类) - 实现的伪代码:
创建k个点作为起始质心(经常是随机选择)
当任意一个点的簇分配结果发生改变时:
对数据集中的每个数据点
对每个质心
计算质心与数据点之间的距离
将数据点分配到距其最近的簇
对每一个簇,计算簇中所有点的均值并将均值作为质心 - 最近质心
进行距离的计算。数据集上K-means算法的性能会受到所选距离计算方法的影响
from numpy import *
#K-means聚类支持函数
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):
"""计算两个向量之间的欧几里得距离"""
return sqrt(sum(power(vecA-vecB,2)))
def randCent(dataSet,k):
"""随机选择簇的质心,确定初始质心"""
n = shape(dataSet)[1] #特征数量
centroids = mat(zeros((k,n)))
#计算每个特征的质心,进而得到整个数据的质心
for j in range(n):
#构建簇质心
minJ = min(dataSet[:,j])
rangeJ = float(max(dataSet[:,j]) - minJ) #最大值与最小值的差值
centroids[:,j] = minJ + rangeJ*random.rand(k,1) #rand()生成一个或一组服从"0-1"均匀分布的随机样本值
return centroids
#K-均值聚类算法
def kMeans(dataSet,k,distMeas=distEclud,createCent=randCent):
"""K-means算法
dataSet:数据集
k:簇的数量
distMeas=distEclud:计算距离的函数类型,通过传递引用使函数更具通用性.默认使用欧几里得计算距离
createCent=randCent:随机选择质心
return centroids,clusterAssment:质心和相应的误差"""
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
#寻得离第i个数据点最近的质心
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
#遍历所有质心并更新它们的取值
for cent in range(k):
ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
centroids[cent,:] = mean(ptsInClust,axis=0) #axis=0表示沿矩阵的列方向进行均值计算
return centroids,clusterAssment
10.2 使用后处理来提高聚类性能
- K-均值聚类方法收敛到局部最小,而非全局最小。
- 一种用于度量聚类效果的指标是SSE(sum of squared error,误差平方和clusterAssment中的第二列)。SSE值越小表示数据点越接近于它们的质心,聚类效果也越好。因为对误差取了平方,因此更加重视那些远离中心的点。
- 聚类的目标是在保持簇数目不变的情况下提高簇的质量。
可以对误差最大的簇进行分割,然后再对最近的质心进行合并,或合并两个使得SSE增幅最小的质心。
10.3 二分K-均值算法
- 过程
为克服K-均值算法收敛于局部最小值的问题,使用二分K-均值算法。
该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分是否可以最大程度降低SSE值。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止。
伪代码
将所有点看成一个簇
当簇数目小于k时
对于每一个簇
计算总误差
在给定的簇上面进行K-均值聚类(k=2)
计算将该簇一分为二之后的总误差
选择使得误差最小的那个簇进行划分操作
#二分K-均值聚类算法
def biKmeans(dataSet,k,distMeas=distEclud):
"""二分K-均值聚类算法"""
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):
lowestSSE = inf
#尝试划分每一个簇
for i in range(len(centList)): #遍历簇列表
ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A == i)[0],:] #获取每个簇中包含的数据集
centroidMat,splitClustAss = kMeans(ptsInCurrCluster,2,distMeas)
sseSplit = sum(splitClustAss[:,1])
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)
bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
print('the bestCentToSplit is:',len(bestClustAss))
centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]
# print("========Numpy.matrix>>>list============")
# print(bestNewCents[0,:])
# print(bestNewCents[0,:].tolist())
# print(bestNewCents[0,:].tolist()[0])
# print("========Numpy.matrix>>>list============")
centList.append(bestNewCents[1,:].tolist()[0])
clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:] = bestClustAss
return mat(centList),clusterAssment
if __name__ == '__main__':
dataMat = mat(loadDataSet('./M_10_Kmeans/testSet.txt'))
myCentroids,clustAssing = biKmeans(dataMat,4)
print(myCentroids)
10.4 示例:对地图上的点进行聚类
"""地图点聚类"""
import M_1002_Kmeans as Kmeans
from numpy import *
import matplotlib
import matplotlib.pyplot as plt
def distSLC(vecA,vecB):
"""球面距离计算"""
a = sin(vecA[0,1]*pi/180) * sin(vecB[0,1]*pi/180)
b = cos(vecA[0,1]*pi/180) * cos(vecB[0,1]*pi/180) * cos(pi*(vecB[0,0] - vecA[0,0])/180)
return arccos(a+b)*6371.0
def clusterClubs(numClust=5):
datList = []
for line in open('./M_10_Kmeans/places.txt').readlines():
lineArr = line.split('\t')
datList.append([float(lineArr[4]),float(lineArr[3])])
datMat = mat(datList)
print(datMat)
myCentroids,clustAssing = Kmeans.biKmeans(datMat,numClust,distMeas=distSLC)
myCentroids = mat(myCentroids)
fig = plt.figure()
rect = [0.1,0.1,0.8,0.8]
scatterMarkers = ['s','o','^','8','p','d','v','h','>','<']
axprops = dict(xticks=[],yticks=[])
ax0 = fig.add_axes(rect,label='ax0',**axprops)
imgP = plt.imread('./M_10_Kmeans/Portland.png')
ax0.imshow(imgP)
ax1 = fig.add_axes(rect,label='ax1',frameon=False)
for i in range(numClust):
ptsInCurrCluster = datMat[nonzero(clustAssing[:,0].A == i)[0],:]
markerStyle = scatterMarkers[i % len(scatterMarkers)]
ax1.scatter(ptsInCurrCluster[:,0].flatten().A[0],ptsInCurrCluster[:,1].flatten().A[0],marker=markerStyle,s=90)
ax1.scatter(myCentroids[:,0].flatten().A[0],myCentroids[:,1].flatten().A[0],marker='+',s=300)
plt.show()
clusterClubs()