机器学习实战第10章_无监督学习_k-means聚类

终于进入无监督学习的部分了,首先介绍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)

运行结果如下:

 

posted @ 2018-09-19 14:10  weiququ  阅读(1638)  评论(0编辑  收藏  举报