参与拿奖:本文已参与「新人创作礼」活动,一起开启掘金创作之路
ps:代码文末自取
1.相关概念
聚类是一种无监督学习方法,几乎适用于所有对象
K均值聚类:将数据划分成K个簇,每个簇的中心采用该簇内均值计算而成
K均值聚类优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据上收敛较慢
适用数据类型:数值型数据
K均值伪代码
创建K个点为起始质心(通常随机选择)
对数据集中的每个数据点:
对于每个质心:
计算质心与数据点之间的距离
将数据点分配到距离其最近的簇中
对于每一个簇,计算簇中所有点的均值,并将均值作为质心
2.聚类案例
2.1 加载数据
# 加载数据集
def loadDataSet(fileName): #general function to parse tab -delimited floats
dataMat = [] #assume last column is target value
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = list(map(float,curLine)) #map all elements to float()
dataMat.append(fltLine)
return dataMat
2.2 计算误差平方和
# 计算误差平方和
def distEclud(vecA,vecB):
return sqrt(sum(power((vecA-vecB),2)))
2.3 构建初始质心
# 构建初始簇质心
def randCent(dataSet,k):
# 特征个数
n=shape(dataSet)[1]
# 构建划分为k*n的矩阵
centroids=mat(zeros((k,n)))
for j in range(n):
# 获取特征J对应属性值取值步长(max-min)
minJ=min(dataSet[:,j])
rangJ=float(max(dataSet[:,j])-minJ)
centroids[:,j]=minJ+rangJ*random.rand(k,1)
return centroids
2.4 k-means聚类
# 输入参数分别为:数据集、K值、计算距离、创建初始质心
def KMeans(dataSet,k,distMeas=distEclud,createCent=randCent):
# 获取数据点个数
m=shape(dataSet)[0]
# 记录每个点的分配结果,第一列存储簇索引、第二列存储误差(当前点到簇质心的距离)
clusterAssment=mat(zeros((m,2)))
# 初始质心
centroids=createCent(dataSet,k)
# 显示簇质心是否改变
clusterChanged=True
# 存储划分的结果
clusterR=mat(zeros((m,3)))
while clusterChanged:
clusterChanged=False
for i in range(m):
# 初始化最小距离
minDist=inf
# 初始化最小索引
minIndex=-1
for j in range(k):
# 计算第j行质心与第i行的数据距离,求出最近质心
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
clusterR[i,:]=dataSet[i,0],dataSet[i,1],minIndex
# print(centroids)
for cent in range(k):
ptsInClust=dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
centroids[cent,:]=mean(ptsInClust,axis=0)
# 返回所有的类质心与点分配结果
return centroids,clusterAssment,clusterR
2.5 测试聚类效果
def kMeasPlot():
dataMat=loadDataSet(r'data/testSet.txt')
dataMat=mat(dataMat)
# print(KMeans(dataMat,4))
centeroids,clustrAssment,clusterR=KMeans(dataMat,4)
fig=plt.figure()
ax=fig.add_subplot(111)
plt.scatter(centeroids[:,0].tolist(),centeroids[:,1].tolist(),color='red',marker='x')
markers=['o','v','s','d']
ms=[]
for i in range(shape(dataMat)[0]):
ms.append(clusterR[i,2])
print(ms)
plt.scatter(clusterR[:,0].tolist(),clusterR[:,1].tolist(),color="blue",marker=markers[int(ms[2])])
plt.xlim(-6,6)
plt.ylim(-6,6)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
2.6 运行结果
由图可见数据比较分散,聚类效果并没有那么的好。
3.改进方法
使用后处理来提高聚类性能
K均值算法收敛但聚类效果差,是因为K均值算法收敛到了局部最小值,而非全局最小值
一种度量聚类效果的指标是SSE(误差平方和),SSE值越小说明其越接近质心,聚类效果越好
在簇数一定的限制条件下,可以通过后处理方法提高聚类性能
一种后处理方法:将SSE值较大的簇划分为两个簇,为了使得簇数不变可以将簇质心相距较小的两个簇进行合并
3.1 二分K均值算法步骤
将所有点看成一个簇
当簇数小于K时:
对于每一个簇:
计算总误差
在给定的簇上面进行K均值聚类(K=2)
计算该簇划分为二之后的总误差
选择使得误差最小的那个簇进行划分
3.2 代码
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,clusterR= 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
3.3 测试代码与结果
def kBiMeasPlot():
dataMat=loadDataSet(r'data/testSet2.txt')
dataMat=mat(dataMat)
# print(KMeans(dataMat,4))
centList,myNewAssement=biKmeans(dataMat,3)
fig=plt.figure()
ax=fig.add_subplot(111)
plt.scatter(centList[:,0].tolist(),centList[:,1].tolist(),color='red',marker='x')
plt.scatter(dataMat[:,0].tolist(),dataMat[:,1].tolist(),color="blue",marker='v')
plt.xlim(-6,6)
plt.ylim(-6,6)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
4.小结
聚类是一种无监督学习方法,无监督含义就是事先不知道需要寻找的内容,没有目标标量
聚类将数据点归类到多个簇中,其中相似数据在同一簇,可以使用多种方法计算相似度
K均值聚类:需要用户事先指定簇数,该方法以K个随机质心开始,计算每个点到质心的距离,每个点会被分到其距离最近的簇中,同时更新簇质心
二分K均值聚类:每次将目标数据划分为两个簇(直达划分K个簇为止),该方法基于贪心策略,是否划分依据为(SSE平方误差)
5.参考资料
[1] 机器学习实战
[2] 书籍源码
[3] jupyter版本
[4] 本节代码