手写机器学习算法系列05——k-means

引言

k-means是一种聚类算法。聚类与之前讲过的分类很相似但实质不一样,分类是已有了明确的类别,然后将样本分到不同类别中去;聚类是事先没有明确的类别,将特征各自相近的样本分别聚在一起。k-means则是原理比较简单的聚类算法。

k-means原理

k-means的原理可以用一个拟人化的场景来理解:

有三个传教士来到了一个小镇,各自选了个地点布道。小镇的居民们都很想听传教士的布道,但三个传教士有的近有的远,于是居民们都不约而同选了离自己最近的传教士听布道,没多久每家每户的居民都有了自己的传教士。

没过多久,传教士们发现有的居民每天要跑来大老远才能到自己这,很累。于是每个传教士都移动了自己的位置,让听自己布道的居民们的家离自己位置的总距离最短。

但是传教士们如此一移动,让有些部分居民不高兴了,因为自己的传教士照顾了其他居民,但却离自己远了,还不如去另一个传教士那才是离自己最近的。

于是这部分居民不再听原来的传教士布道了,而选择了目前离自己最近的另一个。

传教士们也发现听自己布道的居民发生了变化,为了照顾到这些新听众,传教士们再一次移动了自己的位置。部分居民的选择传教士也随之再次改变。

经过几次更迭之后,居民们不再更换自己的传教士,传教士们也不再移动自己的位置,各自形成了三个稳定聚落,达成完美的平衡。

以上就是k-means聚类过程的拟人化叙述。接下来我们用图来解释:

我们有一堆样本,特征值形成的散点图如下:
在这里插入图片描述

此时我们欲将样本聚类,比如聚成三个簇,那么我们就在样本中随机选取三个初始点。样本点会选择离自己最近的初始点,就分别形成了不同颜色的集合:

在这里插入图片描述

三个初始点移动至各自簇的质心:
在这里插入图片描述

达成稳态,中心点不再移动,形成稳定的三个簇。
在这里插入图片描述

手写k-means算法

k-means聚类过程首先要根据簇的数量随机选取初始点,于是先实现初始点选取的函数:

 def __pick_start_point(self,ndarray,cluster_num):
       '''选取初始点
       '''
        if cluster_num <0 or cluster_num > ndarray.shape[0]:
            raise Exception("簇数设置有误")
     
        # 随机点的下标
        indexes=random.sample(np.arange(0,ndarray.shape[0],step=1).tolist(),cluster_num)
        points=[]
        for index in indexes:
            points.append(ndarray[index].tolist())
        return np.array(points)

k-means中涉及到的距离度量一般采用欧氏距离,因此可以先写出坐标点之间欧氏距离的计算函数:

 def __distance(self,p1,p2):
        '''计算两点间距
        '''
        tmp=0
        for i in range(len(p1)):
            tmp += pow(p1[i]-p2[i],2)
        return pow(tmp,0.5)

中心点的坐标会随着加入自己的样本点的变化而发生变化,于是我们实现计算一组坐标的中心点的函数:

def __center(self,list):
        '''计算一组坐标的中心点
        '''
        # 计算每一列的平均值
        return np.array(list).mean(axis=0)

接下来是最核心的函数,迭代计算中心点和簇的形成。

    def cluster(self):
        result = []
        for i in range(self.cluster_num):
            result.append([])

        for item in self.ndarray:
            distance_min = sys.maxsize
            index=-1
            # 计算离自己最近的中心点
            for i in range(len(self.points)):                
                distance = self.__distance(item,self.points[i])
                if distance < distance_min:
                    distance_min = distance
                    index = i
		    # 根据中心点形成不同的簇
            result[index] = result[index] + [item.tolist()]

		# 新的中心点
        new_center=[]
        for item in result:
            new_center.append(self.__center(item).tolist())

        # 中心点未改变,说明达到稳态,结束递归
        if (self.points==new_center).all():
            return result
        # 新中心点覆盖旧中心点
        self.points=np.array(new_center)
        # 继续迭代
        return self.cluster()

封装好的完整算法代码:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import random
import sys
import time

class KMeansClusterer:

    def __init__(self,ndarray,cluster_num):
        self.ndarray = ndarray
        self.cluster_num = cluster_num
        self.points=self.__pick_start_point(ndarray,cluster_num)
        print("初始点"+str(self.points))

    def cluster(self):
        result = []
        for i in range(self.cluster_num):
            result.append([])

        for item in self.ndarray:
            distance_min = sys.maxsize
            index=-1
            for i in range(len(self.points)):                
                distance = self.__distance(item,self.points[i])
                if distance < distance_min:
                    distance_min = distance
                    index = i

            result[index] = result[index] + [item.tolist()]


        new_center=[]
        for item in result:
            new_center.append(self.__center(item).tolist())

        print("中心点:"+str(new_center))

        for item in result:
            plt.scatter([x[0] for x in item],[x[1] for x in item])
        plt.scatter([x[0] for x in self.points],[x[1] for x in self.points],marker="*",s=100)
        plt.show()
        # 中心点未改变,说明达到稳态,结束递归
        if (self.points==new_center).all():
            return result
        
        self.points=np.array(new_center)
        # print(self.points)
        return self.cluster()
            

    def __center(self,list):
        '''计算一组坐标的中心点
        '''
        # 计算每一列的平均值
        return np.array(list).mean(axis=0)

    def __distance(self,p1,p2):
        '''计算两点间距
        '''
        tmp=0
        for i in range(len(p1)):
            tmp += pow(p1[i]-p2[i],2)
        return pow(tmp,0.5)

    def __pick_start_point(self,ndarray,cluster_num):
       
        if cluster_num <0 or cluster_num > ndarray.shape[0]:
            raise Exception("簇数设置有误")
     
        # 随机点的下标
        indexes=random.sample(np.arange(0,ndarray.shape[0],step=1).tolist(),cluster_num)
        points=[]
        for index in indexes:
            points.append(ndarray[index].tolist())
        return np.array(points)

调用方式

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from K_means import KMeansClusterer

if __name__ == "__main__":
    pdData=pd.read_csv("datas.csv")
    data=pdData[["density","sugercontent"]].values
    clusterer=KMeansClusterer(data,3)
    result=clusterer.cluster()

    for item in result:
        plt.scatter([x[0] for x in item],[x[1] for x in item])

    plt.show()

输出结果:

在这里插入图片描述

发布了39 篇原创文章 · 获赞 61 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_33829547/article/details/99747664