机器学习笔记8-K-means及其改进

k-means算法:

聚类是一种无监督的学习,它将相似的对象归到同一个簇中,有点像全自动分类。

本章讲的K-means算法之所以称之为K-均值是因为它可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算。

算法原理: K-均值算法的工作流程是这样的。首先,随机确定k个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值。 

算法缺点:K-menas算法收敛但是聚类效果较差的原因是K-means算法收敛到了局部最小值,而非全局最小值。

下面介绍k-均值算法的一种改进

二分K-means算法:

伪代码形式如下:

将所有点看成一个簇

当簇数目小于k时

对每一个簇:

       计算总误差

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

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

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

Python代码:

import numpy as np

# 读取数据
def loadDataSet(fileName):
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = [float(example) for example in line.strip().split('\t')]
        dataMat.append(curLine)
    return np.array(dataMat)

# 计算欧氏距离
def distEclud(vecA, vecB):
    return np.sqrt(sum(np.power(vecA - vecB, 2)))

# 为数据集中每个特征随机创建k个聚簇中心
def randCent(dataSet, k):
    n = dataSet.shape[1]
    centroids = np.zeros((k, n))  # k个聚簇中心,每个簇中心特征数为n
    for j in range(n):
        minJ = min(dataSet[:, j])
        rangeJ = float(max(dataSet[:, j]) - minJ)
        # 为第j个特征创建k个聚簇中心
        centroids[:, j] = minJ + rangeJ * np.random.rand(k)
    return centroids

# kMeans算法
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
    m = dataSet.shape[0]
    clusterAssment = np.zeros((m, 2))  # 存储每个点的簇分配结果,第一列记录簇索引值,第二列存储误差(距离的平方)
    centroids = createCent(dataSet, k)  # k个聚簇中心矩阵(k,n)
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):
            minDist = np.inf
            minIndex = -1
            for j in range(k):
                distJI = distMeas(dataSet[i,:], centroids[j,:])
                if distJI < minDist:
                    minDist = distJI
                    minIndex = j
            if clusterAssment[i, 0] != minIndex: clusterChanged=True
            clusterAssment[i, :] = [minIndex, minDist**2]
        for cent in range(k):
            ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0] == cent)[0]]  # 得到在这个簇中心的所有数据点
            centroids[cent,:] = np.mean(ptsInClust, axis=0)
    return centroids, clusterAssment

# 测试kMeans算法
# datMat = loadDataSet('testSet.txt')
# myCentroids, clustAssing = kMeans(datMat,4)
# print(myCentroids) # 经过数次迭代后会收敛
# print(clustAssing) # 第1列为簇索引,第2列为每个点到簇中心的误差

# 二分K均值算法:克服K-均值算法收敛于局部最小值的问题
def biKmeans(dataSet, k, distMeas=distEclud):
    m = dataSet.shape[0]
    clusterAssment = np.zeros((m, 2))
    centroid0 = np.mean(dataSet, axis=0) # 创建一个初始簇,只有一个簇中心,并转化为列表
    centList =[centroid0]  # 加一层列表包含初始簇列表,以后会越来越多,直到等于k个
    for j in range(m):  # 计算初始误差
        clusterAssment[j,1] = distMeas(centroid0, dataSet[j,:])**2
    while (len(centList) < k):  # 簇中心的数目小于k个
        lowestSSE = np.inf
        for i in range(len(centList)):  # 对每一个簇划分
            ptsInCurrCluster = dataSet[np.nonzero(clusterAssment[:,0] == i)[0],:] # 得到在簇i中的数据点
            centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) # 使用kMeans算法将簇一分为二
            sseSplit = sum(splitClustAss[:,1]) # 计算新划分的簇中数据的SSE(误差平方和)
            # 计算剩余簇中数据的SSE值,如果是第一次,该值为0,因为所有数据都用来划分新簇,故没有剩余簇的数据
            sseNotSplit = sum(clusterAssment[np.nonzero(clusterAssment[:,0] != i)[0],1])
            print("sseSplit, and notSplit: ",sseSplit,sseNotSplit)
            if (sseSplit + sseNotSplit) < lowestSSE:  # 两部分SSE作为总误差
                bestCentToSplit = i   # 最好的簇划分,即按照第i个簇划分
                bestNewCents = centroidMat.copy()  # 新簇,在原来基础上+1个簇中心,要用copy方法,防止矩阵传引用同时更改bestNewCents的值
                bestClustAss = splitClustAss.copy()  # 新的划分结果,要用copy方法,防止矩阵传引用同时更改bestClustAss的值
                lowestSSE = sseSplit + sseNotSplit
        bestClustAss[np.nonzero(bestClustAss[:,0] == 1)[0],0] = len(centList) # 为新划分出的簇赋一个新的编号,如1,2,3,...
        bestClustAss[np.nonzero(bestClustAss[:,0] == 0)[0],0] = bestCentToSplit
        print('the bestCentToSplit is: ',bestCentToSplit)
        print('the len of bestClustAss is: ', len(bestClustAss))
        centList[bestCentToSplit] = bestNewCents[0,:] # 更新原来的旧簇的值
        centList.append(bestNewCents[1,:]) # 将新划分出的簇加入centList中
        clusterAssment[np.nonzero(clusterAssment[:,0] == bestCentToSplit)[0],:] = bestClustAss # 更新clusterAssment
    return centList, clusterAssment  # 返回簇中心、簇分配结果矩阵

# 测试二分K均值算法
datMat3 = loadDataSet('testSet2.txt')
centList, myNewAssment= biKmeans(datMat3,3)
print(centList)
print(myNewAssment)

 

K均值的时间复杂度为NKTD,其中,N代表样本个数,K代表k值,即聚类中心点个数,T代表循环次数,D代表样本数据的维度。 本算法改进主要在以下方面: 一, 初始聚类中心点,传统的初始中心点是随机选择,由于K均值算法受初始中心点影响较大,为获得更好的效果,在本方法中,先将数据采用层次聚类的方法预处理,得到的k个中心点作为K均值算法的中心点。 二, 传统的聚类中心点更新是在结束一次循环后,本方法的聚类中心采用实时更新策略,即每次将一个模式归于一个新的聚类中心时,即立刻更新新的所属中心和原属聚类中心的中心值,增强算法的收敛性。 三, 为达到类内方差最小化,类类方差最大化这一原则,考虑到往往设定的K值不一定能很好实现聚类效果,故将以往的固定聚类中心改为一浮动区间。原有K为最小聚类中心个数,另设一聚类中心个数上限maxK。其具体实现如下: 1) 当一待聚类的模式得到其最近中心时,计算该聚类中心类内方差和将此模式归于该中心之后的类内方差,如果两者差别大于某设定阈值,则以该模式数据为基础,得到一新的聚类中心。 2) 当当前聚类中心个数等于设定的最大聚类中心时,合并最相邻的两个聚类。为使得到的聚类效果更为均衡,应该优先合并维度较小的聚类类别。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值