k均值算法——无监督学习以及地图点分类

k均值算法是无监督学习的第一章,看完以后认识到k近邻不算是无监督学习,而k均值算法是。主要目的是将待分类样本进行按簇的分类,簇与簇之间的差别尽量大,簇内的差别尽量小,每个簇的中心使用簇中所含值的均值计算而成。 本篇的内容是先简要介绍k均值聚类算法,然后介绍对聚类后得到的簇进行后处理,作为改进,简介二分k均值聚类算法,最后使用对地理位置进行聚类作为应用典例。
分类的原理依据簇识别,假定一些数据,现在将相似数据归到一起,簇识别会告诉我们这些簇是什么。聚类和分类的最大不同在于分类的目标事先已经确定,而聚类则是不一样,其类别并没有预先定义。聚类有时也被称为无监督分类,适用于数值型数据。

1.k均值聚类算法

k均值是发现数据k个簇的算法,簇的个数k是由用户给定的。.每一个簇通过其质心来描述。工作流程为:随机确定k个初始点作为质心。然后将数据集中的每个点分配到每一个簇中,为每一个簇找距离最近的质心,然后将簇的中心店更新为该簇所有点的平均值。
上述过程的伪代码描述如下:

创建k个点作为质心(随机选择的)
当任意一个点簇分配结果发生变化时
    对数据集中的每一个数据点
        对数据集中的每一个质心
            计算质心与数据点之间的距离
        将数据点分配到距其最近的簇
    对每一个簇计算所有点的均值并将其作为质心

测试算法,运用量化的误差指标如误差平方和来评价算法的结果。同时,计算出的簇质心可以代表整个簇的数据进行决策。
书中有程序用来支持k均值聚类算法,第一个函数是将文本文档导入到列表当中,第二个是计算两个向量之间的欧氏距离。第三个函数是构建簇质心的函数(随机分配)
对于k均值算法的计算流程为:该算法创造k个质心,价格每个点分配到距离最近的质心中,然后再重新计算质心。该过程重复数次,直到数据点的簇不会再改变为止。代码如下:

def kmeans(dataSet,k,distMeas=ditEclud,createCent=randCent)
    m=shape(dataSet)[0]
    clusterAssment=mat(zeros((m,2)))
    centroids=creatCent(dataSet,k)
    clusterChanged=True
    while clusterChanged:
        clusterChanged=Flase
        for i in range(m):
            minDist=inf;minIndex=-1
            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
            clusterAssement[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

按照上述方式(计算质心-分配-重新计算) 反复迭代,直到数据点簇分配结果不会再改变为止。

使用后处理来提高聚类性能

利用误差平方和(sse)来评价聚类质量。sse值越小表示数据点越接近他们的质心。后处理提出将sse值最大的那个簇一分为二,而合并最近的质心。第一种是合并距离最近的质心,第二种方式合并两个簇然后计算总sse值。通过这种思路,提出了二分k均值算法。

二分K均值算法

该算法的思路是将所有的点作为一个簇,然后将该簇一分为二,之后选择其中一个点簇进行划分,依据是能否最大程度降低sse值。上述基于sse划分过程不断重复直到得到用户指定的簇数目为止。伪代码如下:

将所有点看作是一个簇
当簇的数目小于k时
    对于每一个簇
        计算总误差
        在给定的簇上面进行k俊辉聚类
        计算将该簇一分为二之后的总误差
    选择使误差最小的那个簇进行划分操作

也可以划分sse最大的簇
代码:

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)
            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: ',bestCentToSplit
        print 'the len of bestClustAss is: ', len(bestClustAss)
        centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]
        centList.append(bestNewCents[1,:].tolist()[0])
        clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss
    return mat(centList), clusterAssment                    

该函数首先创建一个矩阵来存储数据集中每个点的簇分配结果及平方误差,然后计算整个数据集的质心,并用一个列表来保留所有质心。为方便计算,遍历所有点计算各点到质心的误差值,后面备用。
接下来进行while循环,对簇进行划分,直到得到k个簇为止。划分前后需要对比sse值。将每个簇进行k均值划分,并计算最小sse。

对地图上的点进行分类

分类的目的是能够寻找到最佳停车点。这个例子用到了网络爬虫这个功能,利用了urllib这个库进行网络爬虫。
使用球面余弦定理来进行AB之间距离的推算。具体的推导过程见:https://blog.csdn.net/yu412346928/article/details/42966001
故,首先定义经纬度一致的两个点之间距离的函数。第二个函数将各个点进行聚类并画出结果。代码如下:

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)
    lt.show()                

第二个函数首先创建一个画布和矩形,接下来使用一些标记形状的列表来绘制散点图,下一步用imread基于图像创建矩阵,再用imshow来绘制该矩阵。最后遍历每一个簇来将他们画出来,用十字标记簇心。

猜你喜欢

转载自blog.csdn.net/weixin_42320441/article/details/82149980