KMeans算法的Python实现

理论介绍
点击此处

结果展示

代码中自带例子中的一个二维散点聚类。
在这里插入图片描述
图片压缩:
在这里插入图片描述

在这里插入图片描述


#### Python代码 在你的项目里建两个python文件,分别取名为KMeans_jia和 XX(随便你,主要是第二个文件实现图片压缩需要导入第一个文件),然后分别复制两段代码进去就行了。

KMeans和KMeans++就只有在初始化簇中心的步骤不同而已,在代码里我已经两种全部选进去,只需要在调用时自己选择想用的就可以了。

  1. KMeans_jia.py
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
from sklearn.datasets import load_iris
import random


class K_Means():

    def __init__(self,X_data,K):

        self.X_data=X_data
        self.K=K  # 簇中心数

        self.datanum=X_data.shape[0]  # 数据的数目
        print(self.datanum)

        self.dim=X_data.shape[-1]  # 数据维度

        self.AllCenter=[]  # 存储迭代过程中所有的簇中心
        self.ALLCluster=[]  # 存储迭代过程中所有的聚类结果
        self.AllJ=[]       # 存储迭代过程在所有的误差函数

        self.oldCenter=np.zeros((self.K, self.dim)) # 存储前一次迭代的簇中心

        self.newCenter=self.InitCenter_jia()  # 将簇中心初始化,这个newCenter属性用来存储本次迭代的簇中心
        self.AllCenter.append(self.newCenter)

        self.iternum=0 # 记录迭代次数

        while not (self.oldCenter == self.newCenter).all(): # 如果前后两次簇中心没变化,则迭代结束
            if self.iternum > 12:  # 如果迭代次数大于十二,结束
                break
            print("itter  ",self.iternum)
            self.distance=self.calculate_distance()
            self.oldCenter=self.newCenter
            self.newCenter,self.cluster=self.update_Center()
            self.J=self.calculate_J()
            if (self.oldCenter == self.newCenter).all():
                print("迭代结束")
                break
            self.iternum += 1

            self.AllCenter.append(self.newCenter)  # 往存储簇中心的列表中添加迭代后得到的簇中心
            self.ALLCluster.append(self.cluster)
            self.AllJ.append(self.J)
            print("newCenter", self.newCenter)
            print()

    #  初始化簇中心KMeans
    def InitCenter(self):
        Center = np.zeros((self.K, self.dim))
        rand_list= random.sample(list(range(0, self.datanum)), self.K) # 从所有样本中挑出K个作为簇中心
        for i in range(self.K):
            print( self.X_data[rand_list[i]])
            Center[i] = self.X_data[rand_list[i]]
            print(Center[i])
        print(Center)
        return Center

    # 初始化簇中心KMeans++
    def InitCenter_jia(self):
        Center=np.zeros([self.K,self.dim])
        num=0  # 用于记录已知簇中心个数
        centerx=self.X_data[random.randint(0,self.datanum-1)] # 先随机初始化一个簇中心
        Center[num]=centerx
        num+=1

        while num<self.K:
            dis = np.zeros([self.datanum,num])
            for i in range(0,self.datanum): # 计算所有样本到所有已知簇中心的距离
               for j in range(0,num):
                   dis[i][j]=np.linalg.norm(self.X_data[i]-Center[j])
            min_dis_For_Data=np.min(dis,axis=1) # 取每个样本到与其最近的簇中心的距离
            All_dis=np.sum(min_dis_For_Data)  # 计算总距离
            ranDis=All_dis*random.random()    # 产生一个0~All_dis之间的数

            for i in range(0,self.datanum):
                ranDis-=min_dis_For_Data[i]
                if ranDis<=0:
                    Center[num]=self.X_data[i]
                    num+=1
                    break
        return Center

    # 数据到簇中心距离的矩阵
    def calculate_distance(self):
        distance=np.zeros([self.datanum, self.K])
        for i in range(0,self.datanum):
            for k in range(0,self.K):
                distance[i, k] = np.linalg.norm(self.X_data[i, :]-self.newCenter[k])
        return distance

    # 更新簇中心
    def update_Center(self):
        center = np.zeros([self.K,self.dim])
        cluster = np.argmin(self.distance, axis=1)  # 数据点所属簇的矩阵
        KongCu_List = [] # 存储空簇序号的列表
        for i in range(0,self.K):
            data = self.X_data[cluster==i]  # 属于第i个聚类中心的数据
            print("第%d个簇的点数 "%i,data.shape[0])
            if data.shape[0] != 0:  # 如果不是空簇
                center_i=np.mean(data,axis=0) # 寻找属于同一个簇中的样本点的中心作为新的簇中心
                center[i]=center_i
            else:
                print("第%d个簇是空簇!!!"%i)
                KongCu_List.append(i)

        # 寻找离当前已知簇中心最远的点作为空簇簇中心
        while len(KongCu_List) != 0: # 循环直至不存在空簇
            Centernum = list(range(0,self.K))
            NotnullCenter_index =[i for i in Centernum if i not in KongCu_List]  # 挑出非空簇的索引
            NotnullCenter = []  # 存储非空簇的簇中心

            for i in NotnullCenter_index:
                NotnullCenter.append(center[i])
            dis = np.zeros([self.datanum,len(NotnullCenter)])
            # 计算所有样本到非空簇的距离
            for i in range(0, self.datanum):
                for k in range(0, len(NotnullCenter)):
                    dis[i][k] = np.linalg.norm(self.X_data[i]-NotnullCenter[k])

            maxdis=np.sum(dis, axis=1)  # 求距离和
            maxdisarg=np.argmax(maxdis)  # 找最大距离的样本的索引
            center[KongCu_List.pop()]=self.X_data[maxdisarg]  # 将其对应的样本作为空簇中心

        return center, cluster

    # 计算SSE
    def calculate_J(self):
        J=0
        for i in range(0,self.datanum):
            J+=np.linalg.norm((self.X_data[i]-self.oldCenter[self.cluster[i]]))**2
        return J

    def Visual(self):
        mark = ['or', 'ob', 'og', 'om', 'oy', 'oc']  # 簇中点的颜色及形状
        center =['Dr', 'Db', 'Dg', 'Dm', 'Dy', 'Dc'] # 簇中心颜色及形状
        figure=plt.figure(figsize=(8,7))

        if self.dim == 1:  # 如果数据维度是1
            ax1 = figure.add_subplot(221)
            ax1.scatter([0]*self.datanum,self.X_data.tolist(), s=3)
            plt.title("未聚类前散点图")

            plt.ion()
            ax3 = figure.add_subplot(212)
            plt.title("误差函数图")

            ax2 = figure.add_subplot(222)
            x = list(range(self.iternum))
            for t in range(0, self.iternum):
                ax2.cla()
                j = 0
                ax3.plot(x[t], self.AllJ[t], "b.")  # 打印每次迭代的SSE
                for i in range(0,self.K):  # 打印簇中心
                    ax2.plot([0], self.AllCenter[t][i].tolist(), center[i], markersize=5, zorder=2)

                if t == self.iternum - 1:
                    plt.title("最终聚类结果散点图")
                else:
                    plt.title("第%d次迭代的聚类结果" % t)

                for i in self.ALLCluster[t]:
                    ax2.plot(self.X_data[j:j + 1, 0].tolist(), mark[i], markersize=3, zorder=1)
                    j += 1
                plt.pause(1.5)  # 停1.5s再打印下一次迭代结果
            ax3.plot(self.AllJ, "g-")
            plt.ioff()
            plt.show()

        if self.dim == 2:
            ax1 = figure.add_subplot(221)
            ax1.scatter(self.X_data[:,0].tolist(), self.X_data[:,1].tolist(),s=3)
            plt.title("未聚类前散点图")

            plt.ion()
            ax3 = figure.add_subplot(212)
            plt.title("误差函数图")

            ax2 = figure.add_subplot(222)
            x=list(range(self.iternum))
            for t in range(0,self.iternum):
                ax2.cla()
                j = 0
                ax3.plot(x[t], self.AllJ[t],"b.")
                for i in range(0,self.K):

                    ax2.plot(self.AllCenter[t][i,0],self.AllCenter[t][i,1],center[i],markersize=5,zorder=2)  # zorder越大,越在上层显示

                if t==self.iternum-1:
                    plt.title("最终聚类结果散点图")
                else:
                    plt.title("第%d次迭代的聚类结果"  % t)

                for i in self.ALLCluster[t]:

                    ax2.plot(self.X_data[j:j+1,0].tolist(),self.X_data[j:j+1,1].tolist(),mark[i],markersize=3,zorder=1)
                    j+=1
                plt.pause(1.5)
            ax3.plot(self.AllJ, "g-")
            plt.ioff()
            plt.show()

        if self.dim==3:
            ax1 = figure.add_subplot(221,projection='3d')
            ax1.scatter(self.X_data[:, 0].tolist(), self.X_data[:, 1].tolist(),self.X_data[:,2].tolist(),s=3)
            plt.title("未聚类前散点图")

            plt.ion()
            ax3 = figure.add_subplot(212)
            plt.title("误差函数图")

            ax2 = figure.add_subplot(222,projection='3d')
            x = list(range(self.iternum))

            for t in range(0, self.iternum):
                ax2.cla()
                j = 0
                ax3.plot(x[t], self.AllJ[t], "b.")

                ax2.plot(self.AllCenter[t][:, 0].tolist(), self.AllCenter[t][:, 1].tolist(),
                         self.AllCenter[t][:, 2].tolist(), "k*", label='聚类中心', markersize=5, zorder=2)
                plt.legend()
                if t == self.iternum - 1:
                    plt.title("最终聚类结果散点图")
                else:
                    plt.title("第%d次迭代的聚类结果" % t)

                for i in self.ALLCluster[t]:
                    ax2.plot(self.X_data[j:j + 1, 0].tolist(), self.X_data[j:j + 1, 1].tolist(),self.X_data[j:j + 1, 2].tolist(), mark[i], markersize=3, zorder=1)
                    j += 1
                plt.pause(1.5)
            ax3.plot(self.AllJ, "g-")
            plt.ioff()
            plt.show()


def example0():
    N=1000
    C=[[N/4,N/2,0,N/2],[N/2,N,0,N/2],[N/4,N/2,N/2,N],[N/2,N,N/2,N]]
    data=[]
    for i in range(4):
        center_datanum=random.randint(20,50)
        for j in range(center_datanum):
            change=random.randint(20,100)
            x=random.randint(C[i][0]+change,C[i][1]-change)
            y=random.randint(C[i][2]+change,C[i][3]-change)
            data.append([x,y])
    data=np.mat(data)
    test=K_Means(data,4)
    test.Visual()


def example1():
    x1 = np.zeros((10, 1))
    x2 = np.zeros((10, 1))
    for i in range(0, 10):
        x1[i] = np.random.rand() * 4
        x2[i] = np.random.rand() * 5 + 5
        x = np.append(x1, x2, axis=0)
    test = K_Means(x, 2)
    test.Visual()


def example2():
    data=load_iris().data[: , 2:]  # 取iris数据集后两列
    test=K_Means(data,3)
    test.Visual()


def example3():
    data=load_iris().data[:,1:]  # 取iris数据集后三列
    test=K_Means(data,3)
    test.Visual()



if __name__ == '__main__':
    example0()
    example1()
    example2()
    example3()

2.图片压缩的代码

import matplotlib.pyplot as plt
import cv2
from KMeans_jia import K_Means
import numpy as np


def KMeans_pic_cut0(img_path,gray=False,clustercenternum=5):  #图片路径,是否要转化为灰度图,聚类中心数
    if gray:
        img=cv2.imread(img_path,0)  #灰度图
        data=img.reshape(img.shape[0]*img.shape[1],1) #将图片拉成一列

    else:
        img=cv2.imread(img_path)
        img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #转化为RGB,不然plt时图片会怪怪的
        data=img.reshape(-1,3)  # 将三维降成二维

    print("开始聚类")
    test=K_Means(data,clustercenternum)
    # test.Visual()  # 最好不要打印样本聚类过程图,图片像素点多会非常非常卡
    cluster=test.cluster  # 聚类结果
    center=test.newCenter # 聚类中心

    print("聚类完成,开始生成图片")
    new_img=center[cluster] # 根据聚类结果和聚类中心构建新图像
    new_img=np.reshape(new_img,img.shape) #矩阵转成原来图片的形状
    new_img=new_img.astype('uint8')  # 要变成图像得数据得转换成uint8
    if gray:
        plt.subplot(121), plt.imshow(img, cmap="gray"), plt.title("原图") # plt默认显示三通道,灰度图要加个cmap="gray",不然图片绿绿的。。
        plt.subplot(122), plt.imshow(new_img, cmap="gray"), plt.title("KMeans,%d个聚类中心"%clustercenternum)
    else :
        plt.subplot(121), plt.imshow(img), plt.title("原图")
        plt.subplot(122), plt.imshow(new_img), plt.title("KMeans,%d个聚类中心"%clustercenternum)
    plt.show()
    #plt.imsave("cutgray.jpg",new_img) # 保存图片

if __name__ == '__main__':

    KMeans_pic_cut0("Mai_sakurajima.jpg",gray=False,clustercenternum=15)  #第一个参数选自己当前目录下的图片,
                                                                          # 图片越大,聚类中心数越大越慢




评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值