• 机器学习实战第10章_无监督学习_kmeans聚类


    终于进入无监督学习的部分了,首先介绍k-means聚类和二分k-means聚类

    1. k-means聚类

    k-means聚类将相似的对象归到同一个簇中,每个簇的中心采用簇中所含值的均值计算而成。

    优点:容易实现

    缺点:可能收敛到局部最小值,在大规模数据上收敛较慢

    适用数据类型:数值型数据

    伪代码:

    创建k个点作为起始质心(随机选择)
    当任意一个点的簇分配结果发生改变时:
        对数据集中的每个数据点:
         对每个质心:
           计算数据点到质心的距离
       将数据点分配到距其最近的簇
       对每一个簇重新计算其均值作为质心

    实现代码如下:

    #!/usr/bin/env python
    # encoding: utf-8
    '''
    @author: shuhan Wei 
    @software: pycharm
    @file: kMeans.py
    @time: 18-9-18 下午3:32
    @desc:
    '''
    import numpy as np
    import matplotlib.pyplot as plt
    
    def loadDataSet(fileName):
        dataMat = []
        fr = open(fileName)
        for line in fr.readlines():
            curline = line.strip().split('\t')
            fltLine = map(float, curline)
            dataMat.append(list(fltLine))
        return dataMat
    
    
    def distEclud(vecA, vecB):
        """
        函数说明:计算两个向量的欧式距离
        :param vecA:
        :param vecB:
        :return:
        """
        return np.sqrt(np.sum(np.power(vecA - vecB, 2))) #la.norm(vecA-vecB)
    
    
    def randCent(dataSet, k):
        """
        函数说明:随机选取k个聚类质心
        :param dataSet: 数据集
        :param k: 质心个数
        :return: 质心
        """
        n = np.shape(dataSet)[1]
        centroids = np.mat(np.zeros((k,n))) #创建质心向量
        for j in range(n):#create random cluster centers, within bounds of each dimension
            minJ = np.min(dataSet[:,j])
            rangeJ = float(np.max(dataSet[:,j]) - minJ)
            centroids[:,j] = np.mat(minJ + rangeJ * np.random.rand(k,1))    #np.random.rand(k,1)随机生成k*1的[0,1)的数
        return centroids
    
    
    def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
        """
        函数说明:k-均值聚类函数
        :param dataSet: 数据集
        :param k: 分为k类
        :param distMeas: 计算数据到质心距离
        :param createCent: 创建质心函数
        :return:
            centroids: 质心位置
            clusterAssment: 第一列是所属分类下标,第二列是点到质心距离
        """
        """
        伪代码:
            创建k个点作为起始质心(随机选择)
            当任意一个点的簇分类结果发生改变时:
                对数据集中的每个数据点:
                    对每个质心:
                        计算点到质心的距离
                    将数据点分配到距其最近的簇
                根据每个簇的均值重新计算每个质心
        """
        m = np.shape(dataSet)[0]
        clusterAssment = np.mat(np.zeros((m,2)))    #第一列存放簇类index,第二列存放误差值
        centroids = createCent(dataSet, k)
        clusterChanged = True
        while clusterChanged:
            clusterChanged = False
            for i in range(m):  #遍历每一行数据,将数据划分到最近的质心
                minDist = np.inf; minIndex = -1
                for j in range(k):  #计算第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
            print(centroids)
            for cent in range(k):   #重新计算质心
                ptsInClust = dataSet[np.nonzero(clusterAssment[:,0].A==cent)[0]]#获取某个簇类的所有点
                centroids[cent,:] = np.mean(ptsInClust, axis=0) #计算均值作为新的簇类中心
        return centroids, clusterAssment
    
    
    def plot(dataSet):
        """
        函数说明:绘制原数据集
        :param dataSet:
        :return:
        """
        x = dataSet[:, 0].tolist()
        y = dataSet[:, 1].tolist()
        plt.scatter(x, y)
        plt.show()
    
    
    def plotKMeans(dataSet, clusterAssment, cenroids):
        """
        函数说明:绘制聚类后情况
        :param dataSet: 数据集
        :param clusterAssment: 聚类结果
        :param cenroids: 质心坐标
        :return:
        """
        m = np.shape(dataSet)[0]
        x0 = dataSet[np.nonzero(clusterAssment[:, 0] == 0), 0][0].tolist()
        y0 = dataSet[np.nonzero(clusterAssment[:, 0] == 0), 1][0].tolist()
        x1 = dataSet[np.nonzero(clusterAssment[:, 0] == 1), 0][0].tolist()
        y1 = dataSet[np.nonzero(clusterAssment[:, 0] == 1), 1][0].tolist()
        x2 = dataSet[np.nonzero(clusterAssment[:, 0] == 2), 0][0].tolist()
        y2 = dataSet[np.nonzero(clusterAssment[:, 0] == 2), 1][0].tolist()
        x3 = dataSet[np.nonzero(clusterAssment[:, 0] == 3), 0][0].tolist()
        y3 = dataSet[np.nonzero(clusterAssment[:, 0] == 3), 1][0].tolist()
        plt.scatter(x0, y0, color = 'red', marker='*')
        plt.scatter(x1, y1, color = 'yellow', marker='o')
        plt.scatter(x2, y2, color = 'blue', marker='s')
        plt.scatter(x3, y3, color = 'green', marker='^')
        for i in range(np.shape(cenroids)[0]):
            plt.scatter(cenroids[i, 0], cenroids[i, 1], color='k', marker='+', s=200)
        # plt.plot(cenroids[0,0], cenroids[0,1], 'k+', cenroids[1,0], cenroids[1,1], 'k+',cenroids[2,0],
        #          cenroids[2,1], 'k+',cenroids[3,0], cenroids[3,1], 'k+',)
        plt.show()
    if __name__ == '__main__':
        dataSet = loadDataSet('testSet.txt')
        dataMat = np.mat(dataSet)
        plot(dataMat)
        cenroids, clusterAssment = kMeans(dataMat, 4)
        print(cenroids, clusterAssment)
        plotKMeans(dataMat, clusterAssment, cenroids)
    

    选择k=4进行聚类,结果如下:    

                   

    2. 二分k-means聚类

      k-means的缺点是可能收敛于局部最优解,使用二分k-means聚类算法来解决这个问题。

      可以使用SSE(sum of squared error误差平方和)来度量聚类的效果,SSE值越小表示数据点接近于它的质心,聚类效果越好。因为对误差取了平方,因此更加重视那些远离质心的点。

      二分k-means聚类算法的思想是先将所有数据点当作一个簇,然后将该簇一分为二。之后在现有的簇中选择一个簇进行划分,选择哪个簇取决于划分哪个簇后能使SSE值最小。不断重复上述过程,直到达到用户要求的簇的个数。

     伪代码:
            将所有数据点都看成一个簇
            当簇的数目小于k时:
                初始化lowestSSE = inf
                对于每一个簇:
                    对该簇进行k-means聚类(k=2)
                    计算聚类后的总误差
                    如果小于lowestSSE,则保存聚类后的参数,更新lowestSEE
                选择划分后使得误差值最小的那个簇进行划分

    具体实现代码如下:

    def biKmeans(dataSet, k, distMeas=distEclud):
        """
        函数说明:二分K-均值算法
        :param dataSet:
        :param k:
        :param distMeas:
        :return:
        """
        """
        伪代码:
            将所有数据点都看成一个簇
            当簇的数目小于k时:
                初始化lowestSSE = inf
                对于每一个簇:
                    对该簇进行k-means聚类(k=2)
                    计算聚类后的总误差
                    如果小于lowestSSE,则保存聚类后的参数,更新lowestSEE
                选择划分后使得误差值最小的那个簇进行划分
        """
        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 = np.inf
            for i in range(len(centList)):
                ptsInCurrCluster = dataSet[np.nonzero(clusterAssment[:,0].A==i)[0],:] #获取属于第i个簇类的所有数据点
                centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)  #对属于第i个簇类的所有数据点进行k=2的聚类
                sseSplit = np.sum(splitClustAss[:,1])   #计算对第i个簇类进行聚类后的sse值
                sseNotSplit = np.sum(clusterAssment[np.nonzero(clusterAssment[:,0].A!=i)[0],1]) #计算不属于第i类的所有数据点的sse值
                print("sseSplit, and notSplit: ",sseSplit,sseNotSplit)
                if (sseSplit + sseNotSplit) < lowestSSE:    #将聚类后的sse值与最低sse值进行比较
                    bestCentToSplit = i
                    bestNewCents = centroidMat
                    bestClustAss = splitClustAss.copy()
                    lowestSSE = sseSplit + sseNotSplit
            bestClustAss[np.nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) #将1改变为新增簇的编号
            bestClustAss[np.nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit   #将0改变为划分簇的编号
            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
    
    
    if __name__ == '__main__':
        dataSet2 = loadDataSet('testSet2.txt')
        dataMat2 = np.mat(dataSet2)
    
        cenroids2, clusterAssment2 = biKmeans(dataMat2, 3)
        plotKMeans(dataMat2, clusterAssment2, cenroids2)
        print('the error of biKmeans:', sum(clusterAssment2[:, 1]))

    3. 实例——对地图上的点进行聚类

    问题是:你的朋友Drew希望你带他去城里庆祝生日,他想要一个晚上去70个地方,给了你这些地方的坐标,你需要将这些地方进行聚类,之后坐车抵达簇的中心,然后步行到簇的其他地方。

    实现代码如下:

    #!/usr/bin/env python
    # encoding: utf-8
    '''
    @author: shuhan Wei 
    @software: pycharm
    @file: placeFinder.py
    @time: 18-9-18 下午9:04
    @desc:
    '''
    import numpy as np
    from math import radians, cos, sin, asin, sqrt
    import kMeans
    import matplotlib.pyplot as plt
    
    def distSLC(vecA, vecB):
        """
        函数说明:计算根据经纬度计算两点之间的距离
        :param vecA: 一个点坐标向量
        :param vecB: 另一个点坐标向量
        :return: 距离
        """
        a = sin(vecA[0,1] * np.pi/180) * sin(vecB[0,1] * np.pi/180)
        b = cos(vecA[0,1]* np.pi/180) * cos(vecB[0,1]* np.pi/180) * \
                          cos(np.pi * (vecB[0,0]-vecA[0,0]) /180)
        return np.arccos(a + b)*6371.0
    
    
    def clusterClubs(numClust = 5):
        """
        函数说明:对地图坐标进行聚类,并在地图图片上显示聚类结果
        :param numClust: 聚类数目
        :return:
        """
        clubsCoordinate = []
        fr = open('places.txt')
        for line in fr.readlines():
            lineCur = line.strip().split('\t')
            # lineMat = np.mat(lineCur)[0, -2:]   #获取最后两列经纬度
            # fltLinr = map(float, lineMat.tolist()[0])
            # clubsCoordinate.append(list(fltLinr))
            clubsCoordinate.append([float(lineCur[-1]), float(lineCur[-2])])    #获取最后两列经纬度
        clubsCoordinateMat = np.mat(clubsCoordinate)
        cenroids, clusterAssment = kMeans.biKmeans(clubsCoordinateMat, numClust,distMeas=distSLC)
        kMeans.plotKMeans(clubsCoordinateMat, clusterAssment, cenroids)
        fig = plt.figure()
        rect = [0.1, 0.1, 0.8, 0.8] #使用矩阵来设置图片占绘制面板的位置,左下角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 = clubsCoordinateMat[np.nonzero(clusterAssment[:, 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) #flatten()将m*n的矩阵转化为1*(m×n)的矩阵,.A[0]矩阵转化为数组后获取数组第一维数据
        ax1.scatter(cenroids[:, 0].flatten().A[0], cenroids[:, 1].flatten().A[0], marker='+', s=300)
        print(sum(clusterAssment[:,1]))
        plt.show()
    
    
    if __name__ == '__main__':
        clusterClubs(4)

    运行结果如下:

  • 相关阅读:
    PBRT笔记(3)——KD树
    PBRT笔记(2)——BVH
    PBRT笔记(1)——主循环、浮点误差
    《Ray Tracing in One Weekend》、《Ray Tracing from the Ground Up》读后感以及光线追踪学习推荐
    在Node.js中使用ffi调用dll
    Node.js c++ 扩展之HelloWorld
    在Qt中配置TBB以及简单实用
    对《将Unreal4打包后的工程嵌入到Qt或者桌面中》一文的补充
    QtQuick大坑笔记之Http的Get与Post操作(带cookie)
    QtQuick自定义主题以及控件样式指引
  • 原文地址:https://www.cnblogs.com/weiququ/p/9674068.html
Copyright © 2020-2023  润新知