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

目录

一、K-均值聚类算法

二、 使用后处理提高聚类性能

三、二分K-均值算法


一、K-均值聚类算法

        k-均值聚类

        优点:容易实现

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

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

       K-均值是发现给定数据集的k个簇的算法。簇个数是用户给定的,每一个簇通过其质心(即簇中所有的中心)来描述

        K-均值算法的伪代码:

        创建k个点作为起始质心(经常是随机选择)

        当任意一个点的簇分配结果 发生改变时

                对数据集中的每个数据点

                        对每个质心

                                计算质心与数据点之间的距离

                        将数据点分配到距其最近的簇

                对每一个簇,计算簇中所有点的均值并将其均值作为质心

python实现K-均值算法,创建kMeans.py文件:

import numpy as np


# 生成数据集
def LoadDataSet(fileName):
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = list(map(float, curLine))
        dataMat.append(fltLine)
    return dataMat


# 距离计算函数
def distEclud(VecA, VecB):
    return np.sqrt(np.sum(np.power((VecA - VecB), 2)))


# 随机生成k个质心的集合
def randCent(dataset, k):
    n = dataset.shape[1]
    centroids = np.mat(np.zeros((k, n)))  # k个簇
    for j in range(n):  # 生成簇 不过要在所有数据的区域内
        rangeJ = float(max(dataset[:, j]) - min(dataset[:, j]))  # 最大值与最小值的差距
        centroids[:, j] = rangeJ * np.random.rand(k, 1) + min(dataset[:, j])
    return centroids


datMat = np.mat(LoadDataSet('Ch10/testSet.txt'))


函数randCent()用于为给定数据集构建一个包含k个随机质心的集合。随机质心必须要在整个数据集的边界之内,可以通过找到数据集每一维的最小和最大值来完成。生成0到1.0之间的随机数并通过取值范围和最小值,以确保随机点在数据的边界之内。 

import kMeans
from numpy import *

datMat = mat(kMeans.LoadDataSet('D:\\learning\\testSet.txt'))
result1 = min(datMat[:, 0])
result2 = min(datMat[:, 1])
result3 = max(datMat[:, 1])
result4 = max(datMat[:, 0])

result5 = kMeans.randCent(datMat, 2)
print(result1)
print(result2)
print(result3)
print(result4)

print(result5)

检验randCent()函数是否生成min到max之间的值,首先提出最小值与最大值,在输出经由randCent()函数处理的数据,可以看出函数可以按照预想的方式运行 。  

K-均值聚类算法:

# 创建k个质心,将每个点分配到最近的质心,再重新计算质心
def kMeans(dataMat, k, distMeans=distEclud, createCent=randCent):
    m = np.shape(dataMat)[0]
    centroids = createCent(dataMat, k)
    clusterAssment = np.mat(np.zeros((m, 2)))  # 0列记录簇索引值,1列存储误差
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):
            minDist = float('inf');
            minIndex = -1
            for j in range(k):
                distJI = distMeans(dataMat[i], centroids[j])  # 计算每个点到质心的距离
                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 = dataMat[np.nonzero(clusterAssment[:, 0] == cent)[0]]  # 找到同一个簇中的所有点
            centroids[cent, :] = np.mean(ptsInClust, axis=0)  # 重新计算簇的质心
    return centroids, clusterAssment  # 返回质心和点的分配结果

        函数有4个输入参数。只有数据集及簇的数目是必选参数,用来计算距离和创建初始质心的函数都是可选的。kMeans()函数一开始确定数据集中数据点的总数,并创建一个矩阵来存储每个点的簇分配结果。矩阵clusterAssment一列记录簇索引值,第二列存储误差。误差为当前点到簇质心的距离。

        通过计算质心-分配-重新计算反复迭代,直到所有数据点的簇分配结果不再改变为止。 clusterChanged用于判断是否继续迭代。

        测试k-means的效果;

datMat = mat(kMeans.LoadDataSet('D:\\learning\\testSet.txt'))
myCentroids, clustAssing = kMeans.kMeans(datMat, 4)

 结果如下:

缺点,K-均值聚类中簇的数目k是用户预先定义的,而用户并不能提前预知k的选择是否正确。同时点的簇分配结果值也没有那么准确,因为K-均值算法收敛到了局部最小值,而非全局最小值。 

二、 使用后处理提高聚类性能

        一种用于度量聚类效果的指标是SSE(误差平方和),SSE值越小表示数据点越接近它们的质心,同时聚类效果也越好。因为对误差取平方,因此更重视那些远离中心的点,一种降低SSE值方法是增加簇的个数,但这个违背聚类的目标。

        可以通过对生成的簇进行后处理,一种是将具有最大SSE值的簇划分成两个簇,具体实现时将最大簇包含的点过滤出来并在这些点上运行K-均值算法,记k=2.

        我们可以很容易的对二维数据上的聚类进行可视化,但面对40维数据时,就不是那么容易了,两种量化方法:合并最近的质心,或者合并两个使得SSE增幅最小的质心。

三、二分K-均值算法

        二分K-均值算法主要是为了克服K-均值算法收敛于局部最小值的问题。将所有点作为一个簇,将簇一分为二。选择其中一个继续划分,选择哪一 个簇进行划分取决于对其划分是否可以最大程度降低SSE的值。

        二分K-均值算法的伪代码:

        将所有点看成一个簇

        当簇数目小于k时

        对于每一个簇

                计算总误差

                在给定的簇上进行K-均值聚类(k=2)

                计算将该簇一分为二之后的总误差

        选择使得误差最小的那个簇进行划分操作

另一种做法是选择SSE最大的簇进行划分,直到簇数目达到用户指定的数目为止。

python实现二分K-均值聚类算法

def biKmeans(dataSet, k, distMeas=distEclud):
    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 = float('inf')
        for i in range(len(centList)):
            ptsInCurrCluster = dataSet[np.nonzero(clusterAssment[:, 0].A == i)[0], :]  # 每个簇看成一个小数据集,
            centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)  # 输入到kmeans算法中,计算k=2的质心簇和误差值

            sseSplit = sum(splitClustAss[:, 1])
            sseNotSplit = sum(clusterAssment[np.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[np.nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)
        bestClustAss[np.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[np.nonzero(clusterAssment[:, 0].A == bestCentToSplit)[0], :] = bestClustAss
    return np.mat(centList), clusterAssment

        创建矩阵存储数据集中的每一个点的簇分配结果以及平方误差,然后计算整个数据集的质心,使用列表保留所有的质心。得到质心后遍历数据集中所有的点以计算点到质心的误差值。

        while循环用于划分簇,直到得到想要的簇数目。可以通过考察簇列表中的值得到当前簇的数目。通过遍历所有簇来决定最佳的簇的划分。对于每个簇将簇中的所有点看成是一个小的数据集,将其输入到kMeans()函数中进行处理,得到两个质心以及每个簇的误差值。误差与剩余数据的的误差之和作为本次划分的误差,当划分的SSE值很小保留本次划分。划分操作只需将划分的簇中所有点的簇分配结果进行修改就行了。新的簇分配后,新的质点会被添加到centLIst中。

实际效果如下:

datMat3 = mat(kMeans.LoadDataSet('D:\\learning\\testSet2.txt'))
centList, myNewAssment = kMeans.biKmeans(datMat3, 3)
print(centList)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值