机器学习实战 第十章 利用k-均值聚类算法对未标注数据分组

第十章 利用K-均值聚类算法对未标注数据分组


    聚类是一种无监督学习,它将相似的对象归到同一簇中。它有点像全自动分类。聚类方法几乎可以应用于所有对象,簇内的对象越相似,聚类的效果越好。本章要学习一种称为K-均值(K-means)聚类的算法。之所以称之为K-均值是因为它可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。
    聚类与分类最大不同在于,分类的目标事先已知,而聚类则不一样。因为其产生的结果与分类相同,而只是类别没有预先定义,聚类有时也被称为无监督分类。

10.1K-均值聚类算法

K-均值聚类的优缺点:

优点:容易实现。
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。
适用数据类型:数值型数据。

    K-均值是发现给定数据集的k个簇的算法。簇个数k是用户给定的,每一个簇通过其质心(centroid),即簇中所有点的中心来描述。
    K-均值算法的工作流程是这样的。首先,随机确定k个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成后,每个簇的质心更新为该簇所有点的平均值。
伪代码如下:

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

K-均值聚类的一般流程:

收集数据:任意方法。
准备数据:需要数值型数据来计算距离,也可以将标称型数据映射为二值型数据再用于距离计算。
分析数据:使用任意方法。
训练算法:不适用于无监督学习,即无监督学习没有训练过程。
测试算法:应用聚类算法、观察结果。可以使用量化的误差指标如误差平方和来评价算法的结果。
使用算法:可以用于所希望的任何应用。通常情况下,簇质心可以代表整个簇的数据来作出决策。

创建KMeans.py文件,并添加下列代码:

from numpy import *


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')#去掉首位的空格,并且以‘\t’分割数据
        fltLine = list(map(float, curLine))  # map all elements to float()使用map把函数转化为float类型
        dataMat.append(fltLine)
    return dataMat


def distEclud(vecA, vecB):#计算向量A和向量B之间的距离
    return sqrt(sum(power(vecA - vecB, 2)))  # la.norm(vecA-vecB)

#随机生成中心
def randCent(dataSet, k):
    n = shape(dataSet)[1]#得到数据列的数量,即数据的维度
    centroids = mat(zeros((k, n)))  # create centroid mat创建一个由k个质心组成的零矩阵
    for j in range(n):  # create random cluster centers, within bounds of each dimension
        minJ = min(dataSet[:, j])#得到第j个维度的最小值
        rangeJ = float(max(dataSet[:, j]) - minJ)#得到第j个维度的取值范围
        centroids[:, j] = mat(minJ + rangeJ * random.rand(k, 1))#生成k*1的随机数(在数据该维度的取值范围内)
    return centroids


def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):#输入变量有4个,数据集,聚类中心的个数,计算距离函数和随机生成聚类中心函数
    m = shape(dataSet)[0]#得到数据的个数
    clusterAssment = mat(zeros((m, 2)))  # create mat to assign data points生成m*2的零矩阵
    # to a centroid, also holds SE of each point
    centroids = createCent(dataSet, k)#随机创建k个中心
    clusterChanged = True#中心是否改变的标志
    while clusterChanged:
        clusterChanged = False
        for i in range(m):  # for each data point assign it to the closest centroid循环每个数据点
            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#该数据的最近的聚类中心不会发生变化,当所有的数据最近的聚类中心不会变化时,停止迭代
            clusterAssment[i, :] = minIndex, minDist ** 2#给clusterAssment每行赋值,第一个值是那个聚类中心距离该数据点距离最小,第二个值是最小距离的平方是多少
        print (centroids)#输出聚类中心
        for cent in range(k):  # recalculate centroids重新计算聚类中心
            ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]]  # get all the point in this cluster得到属于该聚类中心的所有点
            centroids[cent, :] = mean(ptsInClust, axis=0)  # assign centroid to mean计算平均值
    return centroids, clusterAssment#返回聚类中心和数据属于哪个聚类中心的矩阵

测试代码:

import KMeans
from numpy import *
import matplotlib.pyplot as plt

dataMat = mat(KMeans.loadDataSet('testSet.txt')) #载入数据
myCentroids,clustAssing = KMeans.kMeans(dataMat,4) #K-均值算法
#进行绘图 数据可视化
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(dataMat[:,0].tolist(),dataMat[:,1].tolist(),20,15.0*clustAssing[:,0].reshape(1,80).A[0])
ax.scatter(myCentroids[:,0].tolist(),myCentroids[:,1].tolist(),marker='x',color='r')
plt.show()

结果:
在这里插入图片描述
在这里插入图片描述
分析:
    可以看到经过三次迭代后,给出了四个质心。

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

    前面提到在K-均值聚类中簇的数目k是一个用户预先定义的参数,但这可能会使K-均值算法收敛到了局部最小值,而非全局最小值(局部最小值指结果还可以但并非最好结果,全局最小值是可能的最好结果)。
    一种用于度量聚类效果的指标是SSE(Sum of Squared Error,误差平方和)。SSE值越小表示数据点越接近于它们的质心,聚类的效果也越好。聚类的目标是在保持簇数目不变的情况下提高簇的质量。
    当质心随机初始化导致K-均值算法效果不好时,可以对生成的簇进行后处理。一种方法是将具有最大SSE值的簇划分成两个簇。另一种方法是将某两个簇进行合并:合并最近的质心或者合并两个使得SSE增幅最小的质心。

10.3二分K-均值算法

    为克服K-均值算法收敛于局部最小值的问题,有人提出了另一个称为二分K-均值(bisecting K-means)的算法。该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分是否可以最大程度降低SSE的值。上述基于SSE的划分过程不断重复,知道得到用户指定的簇数目为止。
    另一种做法是选择SSE最大的簇进行划分,直到簇数目达到用户指定的数目为止。
二分K-均值算法的伪代码形式如下:

将所有点看成一个簇
当簇数目小于k时
    对于每一个簇
        计算总误差
        在给定的簇上面进行K均值聚类(k=2)
        计算将该簇一分为二后的总误差
    选择使得误差最小的那个簇进行划分操作

在KMeans.py文件中添加下列代码:

def biKmeans(dataSet, k, distMeas=distEclud):
    m = shape(dataSet)[0]#得到数据的数量
    clusterAssment = mat(zeros((m, 2)))#簇分配结果矩阵
    centroid0 = mean(dataSet, axis=0).tolist()[0]#第一个聚类中心,计算所有数据的平均值
    centList = [centroid0]  # create a list with one centroid创建一个列表保存聚类中心
    for j in range(m):  # calc initial Error循环每个数据
        clusterAssment[j, 1] = distMeas(mat(centroid0), dataSet[j, :]) ** 2#计算每个数据和第一个聚类中心的误差
    while (len(centList) < k):#判断聚类中心的数量和自定义的数量
        lowestSSE = inf#初始化最小的SSE
        for i in range(len(centList)):#循环每个聚类中心,range(10)循环的是  0—9
            ptsInCurrCluster = dataSet[nonzero(clusterAssment[:, 0].A == i)[0],:]  # get the data points currently in cluster i找到属于聚类中心i的数据点
            centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)#计算出两个聚类中心时的聚类中心和簇分配结果矩阵
            sseSplit = sum(splitClustAss[:, 1])  # compare the SSE to the currrent minimum计算第i个聚类中心数据点总的SSE之和
            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:, 0].A != i)[0], 1])#计算不属于第i个聚类中心数据点的数据SSE之和
            print("sseSplit, and notSplit: ", sseSplit, sseNotSplit)
            if (sseSplit + sseNotSplit) < lowestSSE:#判断把第i个聚类中心分成两个后,总的误差是否减小
                bestCentToSplit = i#如果减小,进行一系列的赋值
                bestNewCents = centroidMat
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit
        #为什么这儿有两个类别,因为上面的步骤就是每次把第i个分为2个聚类中心
        bestClustAss[nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)  # change 1 to 3,4, or whatever将类别为1的更改为聚类中心的长度
        bestClustAss[nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCentToSplit#将类别为0的变为前面的第i个长度
        print('the bestCentToSplit is: ', bestCentToSplit)
        print('the len of bestClustAss is: ', len(bestClustAss))
        # ----3. 用最优分隔点来重构聚类中心----#
        # 覆盖: bestNewCents[0,:].tolist()[0]附加到原有聚类中心的bestCentToSplit位置
        # 增加: 聚类中心增加一个新的bestNewCents[1,:].tolist()[0]向量
        #例如:目前划分成了0,1两个簇,而要求划分成3个簇,则在算法进行时,假设对1进行划分得到的SSE最小,则将1划分成了2个簇,其返回值为0,1
        #两个簇,将返回为1的簇改成2,返回为0的簇改成1,因此现在就有0,1,2三个簇了。
        centList[bestCentToSplit] = bestNewCents[0, :].tolist()[0]  # replace a centroid with two best centroids
        centList.append(bestNewCents[1, :].tolist()[0])
        clusterAssment[nonzero(clusterAssment[:, 0].A == bestCentToSplit)[0],:] = bestClustAss  # 把原来的属于第i个聚类中心的数据的簇分配结果矩阵换为新的簇分配结果矩阵
        #新的矩阵变成了两类
        # reassign new clusters, and SSE更新新的对应于bestCentToSplit的聚类中心bestClustAss的值
    return mat(centList), clusterAssment

测试代码:

import KMeans
from numpy import *
import matplotlib.pyplot as plt

dataMat = mat(KMeans.loadDataSet('testSet2.txt')) #载入数据
centList,myNewAssments = KMeans.biKmeans(dataMat,3) #K-均值算法
print('质心结果:\n',centList)
#进行绘图 数据可视化
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(dataMat[:,0].tolist(),dataMat[:,1].tolist(),20,15.0*myNewAssments[:,0].reshape(1,60).A[0])
ax.scatter(centList[:,0].tolist(),centList[:,1].tolist(),marker='x',color='r')
plt.show()

结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分析:
    二分K-均值算法的聚类会收敛到全局最小值,相较于单纯的k-均值聚类算法有更好的聚类效果。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值