目录
聚类算法
K-means均值算法
工作流程
缺陷
二分k-均值算法
实例:对地图上的点进行聚类
聚类与分类最大的不同在于,分类的目标事先已知,而聚类则不知道。聚类是一种无监督学习,事先并不知道要寻找的内容,没有目标变量,只是将相似的对象归到一个簇中。簇内的对象越相似,聚类的效果越好。
K-means聚类算法中的K表示可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。
优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。
适用数据类型:数值型数据
一般流程:
收集数据:任意方法
准备数据:需要数值型数据来计算距离,也可以将标称型数据映射为二值型数据再计算距离
分析数据:任意方法
训练算法:不适用于无监督学习
测试算法:应用聚类算法、观察结果。可以使用量化的误差指标如误差平方和来评价算法的结果。
使用算法:可以用于所希望的任何应用。通常情况下,簇质心可以代表整个簇的数据来做出决策。
首先,随机确定k个初始点作为质心,然后将数据集中的每个点分配到一个簇中,具体来说,为每个点找距离其最近的质心,并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值。
流程图来自:
机器学习实战 第十章 K-均值聚类算法 Python3代码_Jeremy_Ren的博客-CSDN博客
#生成数据集
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 np.sqrt(np.sum(np.power((VecA-VecB),2)))
#随机生成k个质心的集合
def randCent(dataset,k):
n = dataset.shape[1]
centroids = np.mat(np.zeros((k,n))) #k个簇
for j in range(n): #生成簇 不过要在所有数据的区域内
rangeJ = float(max(dataset[:,j]) - min(dataset[:,j])) #最大值与最小值的差距
centroids[:,j] = rangeJ * np.random.rand(k,1) + min(dataset[:,j])
return centroids
datMat = np.mat(LoadDataSet('Ch10/testSet.txt'))
#创建k个质心,将每个点分配到最近的质心,再重新计算质心
def kMeans(dataMat,k,distMeans=distEclud,createCent=randCent):
m = np.shape(dataMat)[0]
centroids = createCent(dataMat,k)
clusterAssment = np.mat(np.zeros((m,2))) #0列记录簇索引值,1列存储误差
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m):
minDist = float('inf'); minIndex = -1
for j in range(k):
distJI = distMeans(dataMat[i],centroids[j]) #计算每个点到质心的距离
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 = dataMat[np.nonzero(clusterAssment[:,0]==cent)[0]] #找到同一个簇中的所有点
centroids[cent,:] = np.mean(ptsInClust,axis=0) #重新计算簇的质心
return centroids,clusterAssment #返回质心和点的分配结果
k是用户自定的参数,难以准确确定。
生成簇可能不是最优簇,因为最开始的质心是随机出来的,只能保证从这个起点出发得到的距离最小值(局部最小值),而无法保证是全局的距离最小值。
可以采用SSE(Sum of Squared Error,误差平方和)来度量聚类的效果。SSE值越小表示数据点越接近于它们的质心,聚类效果也越好。因为对误差取了平方,因此更重视那些远离中心的点。
一种方法是可以将具有最大SSE的簇划分成2个簇,然后再将某两个离得近的簇合并。
为克服k均值算法收敛于局部最小值的问题,有人提出了二分k均值算法。该算法首先将所有点作为一个簇,然后将该簇一分为二,之后选择其中一个簇继续划分,选择的依据是划分之后可以最大程度降低SSE的值。上述代码不断重复,直到得到用户指定的簇数目为止。
def biKmeans(dataSet, k, distMeas=distEclud):
m = np.shape(dataSet)[0]
clusterAssment = np.mat(np.zeros((m,2)))
centroid0 = np.mean(dataSet, axis=0).tolist()[0]
centList =[centroid0]
for j in range(m):
clusterAssment[j,1] = distMeas(np.mat(centroid0), dataSet[j,:])**2
while (len(centList) < k):
lowestSSE = float('inf')
for i in range(len(centList)):
ptsInCurrCluster =dataSet[np.nonzero(clusterAssment[:,0].A==i)[0],:] #每个簇看成一个小数据集,
centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2 , distMeas) #输入到kmeans算法中,计算k=2的质心簇和误差值
sseSplit = sum(splitClustAss[:,1])
sseNotSplit = sum(clusterAssment[np.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[np.nonzero(bestClustAss[:,0].A == 1)[0],0] =len(centList)
bestClustAss[np.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[np.nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss
return np.mat(centList), clusterAssment
datMat = np.mat(LoadDataSet('Ch10/testSet2.txt'))
centList,mynewassments = biKmeans(datMat,3)
print(centList)
输出质心:
从网站扒经纬度的过程就不做了,书中给的文件也有palce.txt,最后两列分别为经纬度数据,直接用就好了。
def distSLC(vecA, vecB):#根据经纬度算地球表面距离
a = np.sin(vecA[0,1]*math.pi/180) * np.sin(vecB[0,1]*math.pi/180)
b = np.cos(vecA[0,1]*math.pi/180) * np.cos(vecB[0,1]*math.pi/180) * np.cos(math.pi * (vecB[0,0]-vecA[0,0]) /180)
return np.arccos(a + b)*6371.0
def clusterClubs(numClust=5):
datList = []
for line in open('Ch10/places.txt').readlines():
lineArr = line.split('\t')
datList.append([float(lineArr[4]), float(lineArr[3])])
datMat = np.mat(datList)
myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC)
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('Ch10/Portland.png')
ax0.imshow(imgP)
ax1=fig.add_axes(rect, label='ax1', frameon=False)
for i in range(numClust):
ptsInCurrCluster = datMat[np.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)
print(myCentroids)
ax1.scatter(myCentroids[:,0].flatten().A[0], myCentroids[:,1].flatten().A[0], marker='+', s=300)
plt.show()
clusterClubs()
5个簇时:
4个时:
3个时: