参考上一篇文章:深度学习基础课程1笔记-Kmeans算法(聚类)
优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据集上收敛速度较慢
适用数据类型:数值型数据。
from numpy import *
import numpy as np
import matplotlib.pyplot as plt
#K-means算法支持函数
#文本数据解析函数
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)) #将每一行的数据映射成float型
dataMat.append(fltLine)
return dataMat
#计算两个向量的欧氏距离
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA - vecB, 2))) #la.norm(vecA-vecB)
#生成k个随机质心(质心满足数据边界之内)
def randCent(dataSet, k):
# 得到数据样本的维度
n = shape(dataSet)[1]
# 初始化为一个(k,n)的矩阵
centroids = mat(zeros((k,n)))
# 遍历数据集的每一维度
for j in range(n):
# 得到该列数据的最小值
minJ = min(np.array(dataSet)[:,j])
# 得到该列数据的范围(最大值-最小值)
rangeJ = float(max(np.array(dataSet)[:,j]) - minJ)
# k个质心向量的第j维数据值随机为位于(最小值,最大值)内的某一值
centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1))
return centroids
#测试
datMat=loadDataSet('testSet.txt') #加载数据
centroids=randCent(datMat, 3) #随机产生三个质心
print(centroids) #打印质心
代码结果:
所有支持函数正常运行之后,就可以准备实现完整的K-均值算法了。该算法会创建k个质心,然后将每个点分配到最近的质心,再重新计算质心。这个过程重复数次,直到数据点的簇分配结果不再改变为止。
#k-均值聚类算法
#@dataSet:聚类数据集
#@k:用户指定的k个类
#@distMeas:距离计算方法,默认欧氏距离distEclud()
#@createCent:获得k个质心的方法,默认随机获取randCent()
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
# 获取数据集样本数
m = shape(dataSet)[0]
# 初始化一个(m,2)的矩阵
clusterAssment = mat(zeros((m,2)))
# 创建初始的k个质心向量
centroids = createCent(dataSet, k)
# 聚类结果是否发生变化的布尔类型
clusterChanged = True
# 只要聚类结果一直发生变化,就一直执行聚类算法,直至所有数据点聚类结果不变化
while clusterChanged:
# 聚类结果变化布尔类型置为false
clusterChanged = False
# 遍历数据集每一个样本向量
for i in range(m):
# 初始化最小距离最正无穷;最小距离对应索引为-1
minDist = inf; minIndex = -1
# 循环k个类的质心
for j in range(k):
# 计算数据点到质心的欧氏距离
distJI = distMeas(np.array(centroids)[j,:],np.array(dataSet)[i,:])
# 如果距离小于当前最小距离
if distJI < minDist:
# 当前距离定为当前最小距离;最小距离对应索引对应为j(第j个类)
minDist = distJI; minIndex = j
# 当前聚类结果中第i个样本的聚类结果发生变化:布尔类型置为true,继续聚类算法
if clusterAssment[i,0] != minIndex: clusterChanged = True
# 更新当前变化样本的聚类结果和平方误差
clusterAssment[i,:] = minIndex,minDist**2
# 打印k-均值聚类的质心
print (centroids)
for cent in range(k):
# 将数据集中所有属于当前质心类的样本通过条件过滤筛选出来
ptsInClust = np.array(dataSet)[nonzero(clusterAssment[:,0].A==cent)[0]]
# 计算这些数据的均值(axis=0:求列的均值),作为该类质心向量
centroids[cent,:] = mean(ptsInClust, axis=0)
# 返回k个聚类,聚类结果及误差
return centroids, clusterAssment
#测试
datMat=loadDataSet('testSet.txt')
myCentroids, clustAssing =kMeans(datMat, 4)
#画图
fig = plt.figure()
ax1 = fig.add_subplot(111)
#画出四个质心
for data in myCentroids:
x = np.array(data)[0, 0]
y = np.array(data)[0, 1]
ax1.scatter(x, y, c='r', marker='x')
#按类别画出点
for i in range(len(datMat)):
x = datMat[i][0]
y = datMat[i][1]
label=np.array(clustAssing[i])[0, 0]
if (label == 0):
ax1.scatter(x, y, c='r', marker='^')
if (label == 1):
ax1.scatter(x, y, c='g', marker='s')
if (label == 2):
ax1.scatter(x, y, c='b', marker='o')
if (label == 3):
ax1.scatter(x, y, c='y', marker='D')
#显示所画的图
plt.show()
代码结果:
二分K-means算法首先将所有点作为一个簇,然后将簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇取决于对其进行划分是否能够最大程度的降低SSE的值。上述划分过程不断重复,直至划分的簇的数目达到用户指定的值为止。
二分K-means算法的伪代码如下:
将所有点看成一个簇
当簇数目小于k时
对于每一个簇
计算总误差
在给定的簇上面进行k-均值聚类(k=2)
计算将该簇一分为二之后的总误差
选择使得总误差最小的簇进行划分
代码:
#二分K-均值聚类算法
#@dataSet:待聚类数据集
#@k:用户指定的聚类个数
#@distMeas:用户指定的距离计算方法,默认为欧式距离计算
def biKmeans(dataSet,k,distMeas=distEclud):
# 获得数据集的样本数
m = shape(dataSet)[0]
# 将所有的点看成是一个簇
# clusterAssment 存储 (所属的中心编号,距中心的距离)的列表
clusterAssment = mat(zeros((m,2)))
centroid0 = mean(dataSet,axis=0).tolist()[0]
# centList 存储聚类中心
centList = [centroid0]
# 遍历每个数据集样本
for j in range(m):
clusterAssment[j,1] = distMeas(mat(centroid0),np.array(dataSet)[j,:]) ** 2
# 当簇小于数目k时
while len(centList) < k:
lowestSSE = inf
for i in range(len(centList)):
# 得到dataSet中行号与clusterAssment中所属的中心编号为i的行号对应的子集数据。
ptsInCurrCluster = np.array(dataSet)[nonzero(clusterAssment[:,0].A == i)[0],:]
# 在给定的簇上进行K-均值聚类,k值为2
centroidMat,splitClustAss = kMeans(ptsInCurrCluster,2,distMeas)
# 计算将该簇划分成两个簇后总误差
sseSplit = sum(splitClustAss[:,1])
sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A != i)[0],1])
# 选择使得误差最小的那个簇进行划分
if sseSplit + sseNotSplit < lowestSSE:
bestCentToSplit = i
bestNewCents = centroidMat.copy()
bestClustAss = splitClustAss.copy()
lowestSSE = sseSplit + sseNotSplit
# 将需要分割的聚类中心下的点进行1划分
# 新增的聚类中心编号为len(centList)
bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList)
bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:] = bestClustAss
# 更新被分割的聚类中心的坐标
centList[bestCentToSplit] = bestNewCents[0,:]
# 增加聚类中心
centList.append(bestNewCents[1,:])
return centList,clusterAssment
#测试
datMat=loadDataSet('testSet2.txt')
myCentroids, clustAssing=biKmeans(datMat,3)
print(myCentroids)
print(clustAssing)
# myCentroids, clustAssing =kMeans(datMat, 4)
#
#画图
fig = plt.figure()
ax1 = fig.add_subplot(111)
#画出四个质心
for data in myCentroids:
x = np.array(data)[0, 0]
y = np.array(data)[0, 1]
ax1.scatter(x, y, c='r', marker='x')
#按类别画出点
for i in range(len(datMat)):
x = datMat[i][0]
y = datMat[i][1]
label=np.array(clustAssing[i])[0, 0]
if (label == 0):
ax1.scatter(x, y, c='r', marker='^')
if (label == 1):
ax1.scatter(x, y, c='g', marker='s')
if (label == 2):
ax1.scatter(x, y, c='b', marker='o')
if (label == 3):
ax1.scatter(x, y, c='y', marker='D')
#显示所画的图
plt.show()
代码结果:
现在有一个存有70个地址和城市名的文本,而没有这些地点的距离信息。而我们想要对这些地点进行聚类,找到每个簇的质心地点,从而可以安排合理的行程,即质心之间选择交通工具抵达,而位于每个质心附近的地点就可以采取步行的方法抵达。显然,K-means算法可以为我们找到一种更加经济而且高效的出行方式。
书上给出的yahooAPI的baseurl已经改变,github上有oauth2供python使用,但是yahoo的BOOS GEO好像OAuth2验证出了问题,虽然写了新的placeFinder调用api的代码,仍然会有403错误。
随书代码中已经给出place.txt,所以直接调用,这里略过获取数据的步骤。
#draw function
import matplotlib
import matplotlib.pyplot as plt
def clusterClubs(numClust=3):#参数:希望得到的簇数目
datList = []
for line in open('places.txt').readlines():#获取地图数据
lineArr = line.split('\t')
datList.append([float(lineArr[4]), float(lineArr[3])])#逐个获取第四列和第五列的经纬度信息
datMat = mat(datList)
myCentroids, clustAssing = biKmeans(datMat, numClust)
#draw
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('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)
#画质心
for data in myCentroids:
x = np.array(data)[0, 0]
y = np.array(data)[0, 1]
ax1.scatter(x, y, c='r', marker='+', s=300)
plt.show()
#测试
clusterClubs(3)
代码结果:
聚类是一种无监督聚类算法,无监督指的是事先不知道所需要查找的内容(无目标变量)。聚类将数据点归入多个簇中,相似的数据点归入到同一个簇。有很多不同的方法来计算相似性。广泛使用的是K-均值算法:通过指定k值,随机分配k个质心,然后计算每个数据点到各个质心的距离,将点分配到距离最近的质心,重新计算每个簇的均值更新质心,反复迭代直到质心不在变化。(算法有效但初始k值不容易确定)
另一种是二分K-均值算法:首先将所有点作为一个簇,然后采用k=2的K-均值算法进行划分,下一次迭代时选择两个簇中 SSE(平方误差)最大的簇进行再次划分,直到簇数目达到给定的k值。二分K-均值的算法要优于K-均值算法,不容易收敛到局部最小。