二分k-means算法(Bisecting k-means cluster)python 实现

有用请点赞,没用请差评。

欢迎分享本文,转载请保留出处。

 

 代码:

# -*- coding:utf-8 -*-
# kmeans : Bisecting k-means cluster(二分K-means算法)

import numpy as np
import matplotlib.pyplot as plt

def readfile(filename):
    """
    读取数据集
    W:特征向量数组,只取前两个特征
    label:标签(类别)列表
    :param filename:
    :return:特征向量数组和标签集合列表
    """
    save_path="D:\\python3_anaconda3\\学习\机器学习\\机器学习数据集\\"
    with open(save_path+filename,'r') as f:

        length=len(f.readlines())
        print(filename,"length: %d"%length)
        W = np.zeros((length,2))
        label=[]
        i=0

        f.seek(0,0)
        for line in f.readlines():
            linestr=line.strip()
            linestrlist=line.split(',')
            # print(linestrlist)
            # 鸢尾属植物数据集的特征共有四个,我们这里只取前两个特征作为特征向量,当然这样分类肯定是不准确的。
            number_data=[float(j) for j in linestrlist[0:2]]
            W[i,:]=np.array(number_data)
            label.append(linestrlist[4].strip('\n'))
            i+=1
    return W,label

def createDataset(filename):
    """
    创建待分类数据集
    """
    data_vector,label_str=readfile(filename)
    # print(data_vector,"\n",label)

    # 将原始数据集中非字符串标签改为用数字代表,用户后续画图
    label_num=[]
    for i in label_str:
        if i=="Iris-setosa":
            label_num.append(0)
        elif i=="Iris-versicolor":
            label_num.append(1)
        else:
            label_num.append(2)
    return  data_vector,label_num

# 计算欧式距离
def euclDistance(vector1,vector2):
    return np.sqrt(sum(pow(vector2-vector1,2)))  # pow()是自带函数

# 使用随机样例初始化质心
def initCentroids(dataSet,k):

    numSamples,dim = dataSet.shape
    # numSample - 行,此处代表数据集数量  dim - 列,此处代表维度,例如只有xy轴的,dim=2
    centroids = np.zeros((k, dim))  # 产生k行,dim列零矩阵
    for i in range(k):
        index = int(np.random.uniform(0, numSamples))  # 给出一个服从均匀分布的在0~numSamples之间的整数
        # print("ok",index,numSamples)
        centroids[i, :] = dataSet[index, :]  # 第index行作为簇心
    # print(centroids)
    return centroids

# k均值聚类
def kmeans(dataSet, k):
    numSamples = dataSet.shape[0]
    # print(numSamples)
    # frist column stores which cluster this sample belongs to,
    # second column stores the error between this sample and its centroid

    clusterAssment = np.zeros((numSamples, 2))
    clusterChanged = True

    ## step 1: init centroids
    centroids = initCentroids(dataSet, k)

    while clusterChanged:
            clusterChanged = False
            ## for each sample
            for i in range(numSamples):
                minDist = 1000000.0  # 最小距离
                minIndex = 0  # 最小距离对应的点群
                ## for each centroid
                ## step2: find the centroid who is closest
                for j in range(k):
                    distance = euclDistance(centroids[j, :], dataSet[i, :])  # 计算每个数据到每个簇中心的欧式距离
                    if distance < minDist:  # 如果距离小于当前最小距离
                        minDist = distance  # 则最小距离更新
                        minIndex = j  # 对应的点群也会更新

                ## step 3: update its cluster
                if clusterAssment[i, 0] != minIndex:  # 如当前数据不属于该点群
                    # 此处与书本上算法步骤稍微有点不同:当有一个数据的分类错误时就clusterChanged = True ,便会重新计算簇心。而书本上的终止条件是是新簇心等于上一次迭代后的簇心
                    clusterChanged = True  # 聚类操作需要继续
                    clusterAssment[i, :] = minIndex, minDist**2

     ## step 4: update centroids
            for j in range(k):
                # 提取同一类别的向量
                pointsInCluster = dataSet[np.nonzero(clusterAssment[:, 0] == j)[0]]
                # print("s",pointsInCluster.shape)
                # nonzeros返回的是矩阵中非零的元素的[行号]和[列号]
                # 将所有等于当前点群j的,赋给pointsInCluster,之后计算该点群新的中心
                if len(pointsInCluster)>0:
                    centroids[j, :] = np.mean(pointsInCluster, axis=0)  #  对每列求均值

    # print("center",centroids)
    return centroids, clusterAssment

# Bisecting k-means cluster(二分k-means算法)
def bisect_kmeans(dataSet,k):
    numSamples = dataSet.shape[0]
    clusterAssment = np.zeros((numSamples, 2))      #保存数据点信息,所属类别和误差
    centrol_address=[]          #簇心坐标,注意是列表形式

    # step1 将所有点看做一个簇
    # print(np.mean(dataSet,axis=0).shape,np.mean(dataSet,axis=0))
    centrol_address.append(np.mean(dataSet,axis=0).tolist())   #numpy.ndarray.tolist()将数组变为列表形式

    for j in range(numSamples):
        clusterAssment[j,1]=euclDistance(dataSet[j,:],centrol_address[0])**2     #计算SSE
    classes = len(centrol_address)

    while classes<k:
        lowestSSE = float("inf")         #python 使用float("inf")表示正无穷大,float("-inf")表示负无穷大

        for i in range(classes):
            # 获得属于某一类的数据
            second_kmeans_verctor=dataSet[np.nonzero(clusterAssment[:, 0] == i)[0],:]
            # 调用上一节写的k-means方法,此时k=2,二分法。输出簇心坐标和数据聚类信息
            second_centroids, second_clusterAssment=kmeans(second_kmeans_verctor,k=2)
            # 划分数据的SSE,加上未划分数据的SSE,得到总的SSE
            SSE=sum(second_clusterAssment[:,1])+sum(clusterAssment[np.nonzero(clusterAssment[:, 0] != i)[0],1])
            # 找出最小的SSE以及相关数据信息
            if SSE<lowestSSE:
                lowestSSE=SSE
                bestCentToSplit = i  #当前最适合继续二分法划分的类别
                bestNewCentrols = second_centroids    #二分法划分后的两个簇心向量
                bestClustAss = second_clusterAssment.copy()   #二分法划分后的数据信息

        # 由于是二分法划分,只有0,1两个类别,将属于1类别的所属信息转为下一个新的中心点的的类别号,即等于len(centrol_address)=i+1
        # 注意这里两次类别信息的替换顺序不能错位!!!!
        bestClustAss[np.nonzero(bestClustAss[:, 0] == 1)[0], 0] = len(centrol_address)
        #将属于0类别的类别信息替换为本次被划分的数据信息,bestCentToSplit=i
        bestClustAss[np.nonzero(bestClustAss[:, 0] == 0)[0], 0] = bestCentToSplit

        # 这里替换中心点信息
        centrol_address[bestCentToSplit]=bestNewCentrols[0,:].tolist()
        centrol_address.append(bestNewCentrols[1,:].tolist())
        # 替换本次被用来重新划分数据的所属类别和误差信息
        clusterAssment[np.nonzero(clusterAssment[:, 0] == bestCentToSplit)[0],:]=bestClustAss
        classes=len(centrol_address)
    # 返回中心点信息和数据类别和误差信息
    return  np.array(centrol_address),clusterAssment


def showCluster(dataSet, k, centroids, clusterAssment,old_label):
    numSamples, dim = dataSet.shape  # numSample - 样例数量  dim - 数据的维度
    if dim != 2:
        print (" not two-dimensional data")
        return 1

    mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
    if k > len(mark):
        print ("the k is too large! the max k is 10")
        return 1

    # draw all samples     对k-means聚类后的结果对数据进行绘图
    for i in range(numSamples):
        markIndex = int(clusterAssment[i, 0])
        plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])
    plt.title(" The classification results of Bisecting k-means cluster")

    mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', '<b', 'pb']

    # draw the centroids
    for i in range(k):
        plt.plot(centroids[i, 0], centroids[i, 1], mark[i], ms=12.0)


    # 按照原始数据集自带的类别画图,用于与新分类后的数据进行对比
    plt.figure()     #打开第二个窗口显示图片,而不是分屏显示
    for i in range(numSamples):
        markIndex = int(old_label[i])
        plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])
    plt.title("Original classification result")
    plt.show()


'''
函数功能: 二分 k-means聚类  
采用UCI的数据集:鸢尾属植物数据库,Link:http://archive.ics.uci.edu/ml/machine-learning-databases/iris/
为了绘图方便,只取特征空间中的前两个维度,即萼片长度、萼片宽度两个特征,当然只采用这两个特征进行分类肯定是不准确滴!!

'''
if __name__=="__main__":
    # 数据集名称
    filename="iris_all.data"

    # data_vector, label分别为特征向量和原始标签
    data_vector, label=createDataset(filename)
    # initCentroids(data_vector,3)

    k=3
    # centroids, clusterAssment=kmeans(data_vector,k)
    centroids, clusterAssment=bisect_kmeans(data_vector,k)

    # 按照原始标签和k-means聚类后的分类分别对数据进行绘图
    showCluster(data_vector,k,centroids,clusterAssment,label)

 原始数据自带标签分类结果:

 

 二分k-means分类后结果

 传统k-means方法分类结果,代码参考上一节博客https://blog.csdn.net/Big_Pai/article/details/88607856

 通过结果可以看出二分k-means分类后的结果和传统k-means分类后的结果还是有差别的。

二分k-means算法不再随机选取簇中心,而是从一个簇出发,根据聚类效果度量指标SSE来判断下一步应该对哪一个簇进行划分,因此该方法不会收敛到局部最小值,而是收敛到全局最小值。

  • 7
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值