摘要:
前面所实现的机器学习算法,都是为了对一定目标的分类或者预测。但是如果目标变量事先不知道?那么问题就是转换成从数据X中能发现什么,或者构成X的最佳6个数据簇有哪些。或者X中哪3个特征最频繁出现。本章是将聚类算法在无监督学习中的应用。
聚类是一种无监督学习,它将相似的对象归为同一个簇中。它有点像自动全分类。聚类方法几乎可以应用到所有对象,簇内对象越相似,聚类的效果就越好。
之所以称作K-均值,是因为它可以发现K个不同的簇,而且每个簇的中心采用簇中所含有的值的均值继续计算。聚类和分类最大不彤是,分类的目标事先已经知道
而聚类则不知道,因为产生的结果与分类相同,而只是类别没有开始定义。至于聚类分析中相似对象归到一个类中,如何归类取决相似度的计算方法。实际应用将不同。
K-均值聚类算法
优点:容易实现
缺点:容易收敛到局部最小值,大规模数据集上收敛比较慢
使用类型:数值型类型
算法流程如下:
创建K个点作为起始的质心
当任何一点点的簇分配结果发生改变的时候
队数据集中每个数据点
对每个质心
计算质心和数据点的距离
将数据点分配道距离最近的簇
对每个簇重新计算所有点的均值作为质心
一般流程如下:
1),收集数据
2)准备数据,需要数值型数据计算距离,也可以将标称形数据映射为二维数据在计算距离
3)分析数据
4)训练算法:无监督学习
5)测试算法。应用聚类观察结果,用量化误差评估
6)使用算法。质心代表整个簇数据来做出决策
def loadDataSet(fileName):
dataMat=[]
fr = open(fileName)
for line in fr.readlines():
cur = line.strip().split('\t')
fltLine = map(float,cur)
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)))#初始化k个n维质心
for j in range(n):
minJ = min(dataSet[:,j])
rangeJ = float(max(dataSet[:,j])-minJ)
#而这里是因为有K个质心所以需要初始化K行每行一个(0,1)之间
centroids[:,j] = minJ + rangeJ*random.rand(k,1)
return centroids
我们可以对上面的过程进行测试。测试代码如下:
dataMat = mat(kMeans.loadDataSet('testSet.txt'))
#可以先了解一下这个二维矩阵
min(dataMat[:,0])
min(dataMat[:,1])
max(dataMat[:,1])
#查看是否生成道min-max之间
kMeans.randCent(dataMat,2)
#测试一下距离
kMeans.distEclud(dataMat[0],dataMat[1])
当所有函数正常运行后,我们就可以实现完整的K均值算法了,
def kMeans(dataSet,k,distMeas=distEclud,createCent=randCent):
m = shape(dataSet)[0]
clusterAssment = mat(zeros((m,2)))#包含2列其中1列是簇索引另外一列是距离质心误差
centroids = createCent(dataSet,k)
clusterChanged = True
while clusterChanged:
clusterChanged = False;
for i in range(m):
minDist = inf;minIndex = -1
for j in range(k):#遍历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)#重新计算均值
return centroids,clusterAssment
根据上面的函数我们就可以进行聚类的实现了
myCentroids,clustAssing = kMeans.kMeans(dataMat,4)
得到如下的结果:
[[ 2.15952982 4.4456886 ]
[-1.06833165 -3.04522139]
[ 3.20572127 -0.52747895]
[ 4.35551202 4.85071971]]
[[-0.15502569 3.16899634]
[-2.83958877 -2.51353146]
[ 3.12503825 -1.9916332 ]
[ 4.000154 4.0603475 ]]
[[-1.74330087 2.99077065]
[-3.34887714 -2.76960033]
[ 2.80293085 -2.7315146 ]
[ 2.87966663 3.02176687]]
[[-2.46154315 2.78737555]
[-3.38237045 -2.9473363 ]
[ 2.80293085 -2.7315146 ]
[ 2.6265299 3.10868015]]
经过了4轮迭代过程。最后一组就是最后的质心的位置
使用后处理提高聚类的性能
前面提到,在K-均值聚类的簇的数目K是用户预先定义的一个参数,那么用户如何知道这个参数的设置是正确的呢。如何才能知道生成的簇比较好呢
在包含簇的分配结果的矩阵中保存着点误差,就是每个点到簇质心的距离的平方。
我们考察上图,这是一个包含3个簇的数据集上面运行K-均值的算法结果。但是点到簇结果没有十分准确。聚类效果差的原因是因为收敛到了局部最小值,而不是全局的最小值。
一种用于度量聚类效果的指标是SSE(误差平方和)也就是我们上面保存的矩阵的第一列的和。SSE越小说明数据点越靠近质心,聚类效果越好。取平方说明更加重视远离中心的点,一种降低SSE的方法就是增加簇的个数,但是违背了聚类的目标,目标是维持簇数目不变的情况下提高簇的质量。
那么如何进行改进,可以队生成的簇进行后处理,一种方法是对最大SSE的簇划分成两个簇。具体实现是将最大簇包含的点过滤出来,并在这些点上运行K-均值其中K设置为2.。同时为了保持簇的个数不变,我们可以将两个簇进行合并。如何合并呢,有两种方法一种是合并最近的两个质心,另外一种是先计算所有两两簇组合后的SSE,算出其中最小的就进行合并。
二分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):
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 not split",sseSplit,sseNotSplit
if (sseNotSplit+sseNotSplit)<lowestSSE:
bestCentToSplit = i
bestNewCents = centroidMat
bestClustAss = splitClustAss.copy()
lowestSSE = sseNotSplit + sseSplit
#重新计算划分的新簇的中心以及全局的划分表
bestClustAss[nonzero(bestClustAss[:,0].A==1)[0],0]=\
len(centList)#将新加入的簇是centlist长度
bestClustAss[nonzero(bestClustAss[:,0].A==0)[0],0] = bestCentToSplit
print "the bestCentToSplit is :",bestCentToSplit
centList[bestCentToSplit] = bestNewCents[0,:]
centList.append(bestNewCents[1,:])
clusterAssment[nonzero(clusterAssment[:,0].A==\
bestCentToSplit)[0],:] = bestClustAss
return mat(centList),clusterAssment
首先创建一个矩阵存储数据集的所有簇以及平方误差。遍历centList中每一个簇,犹豫Kmeans后得到的是编号0,1我们需要重新
datMat3 = mat(kMeans.loadDataSet('testSet2.txt')) centList,myNewAssments = kMeans.biKmeans(datMat3,3) print centList
[[-0.45965615 -2.7782156 ]
[-2.94737575 3.3263781 ]
[ 2.93386365 3.12782785]]
地图聚类
import urllib
import json
def geoGrab(stAddress,city):
apiStem = 'http://where.yahooapis.com/geocode?'
params = {}
params['flags']='J'
params['appid'] = 'ppp68N8t'
params['location'] = '%s %s' %(stAddress,city)
url_params = urllib.urlencode(params)
yahooApi = apiStem + url_params
print yahooApi
c = urllib.urlopen(yahooApi)
return json.loads(c.read())
from time import sleep
def massPlaceFind(fileName):
fw = open('places.txt', 'w')
for line in open(fileName).readlines():
line = line.strip()
lineArr = line.split('\t')
retDict = geoGrab(lineArr[1], lineArr[2])
if retDict['ResultSet']['Error'] == 0:
lat = float(retDict['ResultSet']['Results'][0]['latitude'])
lng = float(retDict['ResultSet']['Results'][0]['longitude'])
print "%s\t%f\t%f" % (lineArr[0], lat, lng)
fw.write('%s\t%f\t%f\n' % (line, lat, lng))
else: print "error fetching"
sleep(1)
fw.close()
有用的数据就是places.txt我们利用这个文本数据进行下面的聚类分析
#球面距离
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
得到距离公式后,我们利用上面的文本我们就可以聚类了,利用二分-均值聚类
import matplotlib
import matplotlib.pyplot as plt
def clusterClubs(numClust=5):
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, 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('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()
最后我们可以得到如下的实验结果: