聚类算法——k-means(K均值)法

聚类分析:K-Means

软件及版本:Jupyter Notebook (Anaconda3)
作者:落寞红颜玉玫瑰
编程语言:python


1. 关于聚类

        “物以类聚,人以群分”,聚类(Clustering)是人类认识大自然的一种重要方法。聚类就是按照事物的某些属性,把事物聚成簇,使簇类元素具有较高的相似性,使不同簇间的相似较差。
        聚类属于无监督学习。它与分类的根本区别在于:分类是已知对象特征,根据特征来整理对象,而聚类则是找到对象特征。
        聚类分析通常被作为数据预处理,是进一步分析和处理数据的基础。在生物学及商业分析上具有广泛的应用。在商业上,其可以帮助市场业务人员分析客户,并对客户进行分群;在互联网应用上,聚类分析被用来在网上进行文档归类,等等。
        聚类分析算法取决于数据的类型。聚类的目的和应用。按照其主要思路的不同,可分为:划分方法,层次方法,基于密度的算法,基于网格的算法,基于模型的算法。
        基于聚类分析的数据挖掘已经取得了很好的效果,但由于要处理巨大的,复杂的数据集,对数据分析也提出了特殊的挑战,根据其要求不同,主要要求有:可伸缩,可处理高维数据,可理解,发现任意形状的簇,可处理噪声数据,对数据顺序不敏感等。

2. K-Means 简介

        此方法属于划分方法,同属于划分方法的还有:K中心点,PAM(Parting Around Medoid,围绕中心点的划分),CLARA(Clustering LARge Applications,大型应用中的聚类方法),CLARRANS(Clustering LARge Applications based upRANdomized Search,用于空间数据库的聚类算法)等。

        其是一种古老的,最广泛使用的聚类方法。k均值用质心来表示一个簇,其中质心是一组数据的平均值。此算法以k为输入参数,将n个数据分为k个簇。

算法思想:

  1. 输入目标簇个数值:k;
  2. 随机在包含n个数据的数据集中选择k个对象,以此作为初始组,开始迭代计算;
  3. 对剩余对象:计算其与各个初始组中心点的距离(本文为欧式距离),将其分配到最近的簇,计算其判别函数。
  4. 重复上述第二步,直至簇不在发生变化。

判别函数(目标函数):是评判分组是否是当前数据集最佳的重要表达式。由k均值的定义及实现步骤可以得出其判别函数,不至一个,下文使用的为簇间距离最小模型,还有加法模型,乘法模型等。

依据1:簇内距离最小,dis_in=sum(x(i)-x)^2,其中i ~ {1-m},所有簇内点到中心点的欧式距离和。

依据2:簇间距离最大,dis_out = sum(k(j)-X)^2,其中j~{1,k},各个簇到总数据中心点的距离和,簇的代表元素为其簇中心点。

构建判别函数:1.min=sum(dis_in(j)),j~{1,k},k个组单独簇类距离和最小。
2.max=dis_out,k个组,簇间距离最大。
3.sum(dis_in)/dis_out,簇内距离越小效果越好,簇间距离越大越优,故此值越小越好,可使其等于某个收敛值。

3. 算法实现

## k-means 聚类算法。
# 组内距离最小,组间距离最大化。
#定义函数。写出伪代码:
#设分为 m 组,第一个组第一个元素为X11,平均数means为X1,以此类推;
#则公式为:min=E{1,m}(X1i-X1)^2,其中X1=(X11+X12+...+X1n)/n
#上面伪代码看出,这是一个迭代算法,故在复杂度方面需注意。
#引入需要的模块
import numpy
import time
import math
import json
#根据自己需要,可引进其他模块,比如:
import peewee
from concurrent.futures import ThreadingPoolExecutor
import matlibplot.pyplot as plt

自己定义一个平均值函数,更加适合本算法。

#自己定义一个平均值函数。
# 输入列表,输出中心点元组
def get_mean(*arges):
    
    if len(*arges) == 0:
        
        return "list is empty,please sure list is true,or not"
    
    elif len(*arges) == 1:
        
        return list(*arges)[0]
    
    else:
        
        temp_tuple=list(list(*arges)[0])
        
        num_weight = len(*arges)
        
        for i in range(len(list(*arges)[0])):
            
            temp_single_mean=0
        
            for item in list(*arges):
                
                temp_single_mean += item[i]
            
            temp_tuple[i]=round(temp_single_mean/num_weight,2)
            
        return tuple(temp_tuple)
        #or can writer code like this: return sum(*arges)/len(*arges)

定义一个类,方便储存,和查看各项参数。

import json
# 定义一个类,用来输出分类的组的各项属性
class KMeansGroup():
    
    def __init__(self,row_list = None,group_num = None,convergence_val = None,iter_frequency = None,group_class = None):
        
        self.rowList = row_list
        
        self.groupClass = group_class
        
        self.groupNum = group_num
        
        self.iterFrequency = iter_frequency
        
        self.convergenceVal = convergence_val
        
    
    #重新构建str函数数,使输出的信息是分类信息
    def __str__(self):
        
        return json.dumps(self.groupClass,ensure_ascii=False)
    
    def __repr__(self):
        
        return str(self.groupClass)
    
    #内置一个分类函数,调用时可对当前类重新再次分类
    def group_class_again(self,convergence_val = 10**(-3),iter_frequency = 10):
        
        iter_fun(self.groupClass)
    

定义一个迭代函数,为核心函数

#定义一个迭代函数,方便使用
def iter_fun(group_class):
    #基本组分好了,我们进行迭代。
    
    #1.先求出所有组的平均值
    mean_list=[get_mean(group_class[item]) for item in group_class.keys()]
    #print(mean_list)
    
    #对每个组循环。重新分配变量
    for i in range(len(group_class.keys())):
        
        #长度为1的组不需要参与此次重新分配
        if len(group_class[list(group_class.keys())[i]])>1:
                
            #拿到当前组的中心点平均变量
            item_mean = mean_list[i]
                            
            #当前组中的所有元素,单独进行比较    
            for item in group_class[list(group_class.keys())[i]]:
                     
                #距离列表,方便计算总距离和单个组的距离,方便分组。    
                distince_list=[]
                
                #计算当前元素到各个组的距离
                for ele in mean_list:
                    
                    #单个双元素的距离:欧式距离。        
                    both_distince=0
                    
                    #计算欧式距离,每个方向上。
                    for j in range(len(item)):
                
                        both_distince += round((float(item[j])-ele[j])**2,2)
                    
                    #将元素加入列表
                    distince_list.append(both_distince)
                        
                #重新分配值,得到最短距离所在的位置,并于当前所在位置作比较。
                index=distince_list.index(min(distince_list))
                
                #如果不等于当前所在位置,说明不是最合适的组,移动位置
                if index != i:
                    
                    #先添加到最短距离组,然后在当前组删除元素。
                    group_class["第【{}】组".format(index+1)].append(item)
                    group_class["第【{}】组".format(i+1)].remove(item)
                            
                else:
                            
                    continue
    
    return group_class                    
       

计算簇内距离和。

def groupMidDistinS(init_group):
    
    temp_sum = 0
    
    for item in init_group.keys():
        
        temp_mean = get_mean(init_group[item])
        
        for ele in init_group[item]:
            
            both_distince=0
                    
            #计算欧式距离,每个方向上。
            for j in range(len(ele)):
                
                both_distince += round((float(ele[j])-temp_mean[j])**2,2)
                    
            temp_sum += both_distince
            
            #print(temp_mean,ele,both_distince,temp_sum)
            
    return temp_sum
            

主函数:进行分簇。

#输入项:类数目,收敛值,
#输出项:分类结果,组内距离和,各类中心点,及类元素数目
def class_by_kmeans(init_list,group_num,iter_frequency=0,convergence_val=10):
    
    #引入一个类,让所有信息储存在类中
    group = KMeansGroup(row_list=init_list,group_num=group_num,iter_frequency=iter_frequency,convergence_val=convergence_val)
    
    if group_num > len(init_list):
        
        return "类数目多余列表元素数目相等,不符合逻辑,输入有误,请重新输入。"
    
    else:
        start_num = 0
        
        group_class={}
        
        square_list = []
        
        for i in range(1,group_num+1):
            
                group_class["第【{}】组".format(i)]=[]
                group_class["第【{}】组".format(i)].append(init_list[i-1])

    
        if group_num == len(init_list):
            
             group.groupClass=group_class
            
             return group 
            
        elif group_num == len(init_list)-1:

            for ele in init_list[:-1]:
                
                square_sum = 0
                
                for i in range(len(init_list[-1])):
                    
                    square_sum += (float(list(ele)[i])-init_list[-1][i])**2
                    
                square_list.append(square_sum)
                
            index=square_list.index(min(square_list))
            
            group_class["第【{}】组".format(index+1)].append(init_list[-1])
            
            group.groupClass = group_class
            
            return group
       
        else:
            
            #随机分配。先确定大组,然后进行迭代
            for item in init_list[group_num:]:
                
                distince_list=[]
                    
                for ele in init_list[:group_num]:
                    
                    both_distince=0
                    
                    for i in range(len(item)):
                
                        both_distince += (float(item[i])-ele[i])**2
                    
                    distince_list.append(both_distince)
                    
                #print(min(distince_list))
                    
                index=distince_list.index(min(distince_list))
                
                group_class["第【{}】组".format(index+1)].append(item)
                
            #print(group_class)
            
            front_sub=groupMidDistinS(group_class)
            
            convergence_count=0
            
            flag = True
            #迭代函数,传入可接受迭代次数,当达到迭代次数后,无论是否是最佳分组,都将返回分组
            while flag:
                
                #接受返回参数,相当于更新group_class。
                group_class=iter_fun(group_class)
            
                now_sub=groupMidDistinS(group_class)
                
                if front_sub-now_sub < convergence_val:
                    
                    convergence_count += 1
                                    
                    if convergence_count > iter_frequency:
                    
                        flag = False
                        
                        group.groupClass = group_class
            
                        return group
                
                front_sub = now_sub
            

以上便是算法的雏形,完善及优化在下面开始。

4. 算法调教

先输入一个一维数据,来测试一下各种条件。

#一维数据输入,应该遵循下面格式,当然可以加判别,数据的元素,
lst=[(2,),(4,),(10,),(12,),(3,),(20,),(30,),(11,),(25,)]
"""
init_list:初始数据集,应该按照所规定的格式输入:列表输入,单个元素为元组;
group_num:期望的簇数,不应该超过列表长度;
iter_frewuency:迭代次数,当判别函数小于收敛值的次数,大于此值时,认为当前为最优分组,停止迭代;
convergence_val:收敛值,当判别函数小于此值时,认为目前分组暂时最优,有待进一步判断。(1太大了,不过目前不影响)
"""
print(class_by_kmeans(lst,group_num=9,iter_frequency=10,convergence_val=1).groupClass)
"""
output:
{"第【1】组": [[2]], "第【2】组": [[4]], "第【3】组": [[10]], "第【4】组": [[12]], "第【5】组": [[3]], "第【6】组": [[20]], "第【7】组": [[30]], "第【8】组": [[11]], "第【9】组": [[25]]}
"""

验证正确,我们输入一个长于列表长度的值。

a=class_by_kmeans(lst,group_num=12,iter_frequency=10,convergence_val=1)
print(a)
#output:类数目多余列表元素数目相等,不符合逻辑,输入有误,请重新输入。

验证正确,将其簇数改为2,看看。

a=class_by_kmeans(lst,group_num=2,iter_frequency=10,convergence_val=1)
print(a)
#output:{"第【1】组": [[2], [3], [4], [10], [12], [11]], "第【2】组": [[20], [30], [25]]}

接下来升级一下难度,输入二维数组。

example_list=[(1,2),(2,2),(12,13),(5,6),(20,9)]
class_by_kmeans(example_list,group_num=3,convergence_val=1)
#output:{'第【1】组': [(1, 2), (2, 2)], '第【2】组': [(5, 6)], '第【3】组': [(12, 13), (20, 9)]}

多输入几组二维数组,发现分组的可靠度很高。

接下来,输入高维数据,大数据(本例中最多为10000条6维的数据)。

构建一个产生随机高维数组的简单函数。

#可以通过改变j来改变数据维度,i来改变数据条数。
exp_list=[tuple([np.random.randint(1000) for j in range(5)]) for i in range(50000)]

5. 算法优化

5.1 时间复杂度
我们来看看不同长度元素分类所需要的时间。
for z in range(10,200):
    
    exp_list=[tuple([np.random.randint(1000) for j in range(5)]) for i in range(z*10)]
     
    #print(exp_list)
    
    a=class_by_kmeans(exp_list,group_num=3,iter_frequency=10,convergence_val=10**(-2))

在这里插入图片描述

从上图可以看出,其时间复杂度为O(nkt),即随着数据的增大,时间成线性增加。在数据长度为200时,分簇时长为:0.48s,而当数据长度为2000时,时间为:1.152。由此推测当数据长度为50000时,时间为60s,实际运行时长124s。此处可用多线程加网格算法优化内部结构。

我们从维度方向来看,时间复杂度。

#实际应用中维度达到30的很少吧,所以以长度为1000的数据来测试。
for z in range(1,30):
    
    exp_list=[tuple([np.random.randint(1000) for j in range(z)]) for i in range(1000)]
     
    #print(exp_list)
    
    a=class_by_kmeans(exp_list,group_num=3,iter_frequency=10,convergence_val=10**(-2))

在这里插入图片描述

从上图可以看出,当维度为1时,对1000条数据进行分簇所用时长为:0.26s,而当维度达到29时,其时间达到了恐怖的5.84s。

从上面实验可以看出,当数据长度达到上万条时,就已经有了明显的压力,如果再加上维度的处理,那么分簇时长将会成倍叠加,这不符合我们设计算法的初衷。优化其算法可通过多线程,或者网格算法。

最后,我们看看分簇组数的变化与分簇时长的关系。

#对同一个数组进行不同组数的分簇
exp_list=[tuple([np.random.randint(1000) for j in range(5)]) for i in range(1000)]
    
for z in range(1,100):
     
    #print(exp_list)
    
    a=class_by_kmeans(exp_list,group_num=z,iter_frequency=10,convergence_val=10**(-2))

在这里插入图片描述

可以看到,分组的时间复杂度不亚于维度的时间复杂度,分为一个组时,所需时长:0.16s,而当分为1000组时,其所需时间为:16.70s。在此处采取的策略时:当组的元素个数达到一定值时,采取深度优先原则,先大组,后小组,即先将列表分为几个大组,然后对单个大组进行分类,分为几个小组。

空间复杂度目前不需要优化。

以上便是所以内容,欢迎指正错误。优化算法后面有时间就优化了。

源代码详见资源。资源中为未优化的K-means算法,但对于10000条,单个数据拥有7个变量及以下的数据进行分簇是完全够用的。

另附工作中的数据对其分类结果,以及其准确性的报告。作为算法实际应用的实验。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: K-Means 聚类算法是一种常用的无监督学习算,它可以将数据集划分为 K 个不同的类别,其中 K 是预先设定的。在 K-Means中,我们需要指定 K 值和距离计算方,然后通过迭代的方式不断调整聚类中心,直到达到某个停止准则为止。 下面我们以鸢尾花数据集为例,来实现 K-Means 聚类算法。 首先,我们需要导入数据集并进行预处理。这里我们使用 sklearn 中的 load_iris 函数来加载数据集,并使用 MinMaxScaler 对数据进行归一化处理: ``` python from sklearn.datasets import load_iris from sklearn.preprocessing import MinMaxScaler # 加载数据集 iris = load_iris() X = iris.data # 数据归一化 scaler = MinMaxScaler() X = scaler.fit_transform(X) ``` 接下来,我们需要实现 K-Means。这里我们使用 scikit-learn 中的 KMeans 类来实现: ``` python from sklearn.cluster import KMeans # 设置 K 值 k = 3 # 初始化 KMeans 模型 kmeans = KMeans(n_clusters=k) # 训练模型并预测结果 y_pred = kmeans.fit_predict(X) ``` 最后,我们可以使用 Matplotlib 来可视化聚类结果: ``` python import matplotlib.pyplot as plt # 绘制聚类结果 plt.scatter(X[:, 0], X[:, 1], c=y_pred) plt.title("K-Means Clustering") plt.show() ``` 运行以上代码,即可得到鸢尾花数据的聚类结果。 ### 回答2: K-Means聚类算法是一种常用的无监督学习方,能够对数据进行聚类。在K-Means中,通过计算数据点与聚类中心的距离,将数据点归类到距离最近的聚类中心,从而实现数据的聚类。 鸢尾花数据是机器学习中常用的数据集之一,包含了150个样本,每个样本有4个特征,分别是花萼长度、花萼宽度、花瓣长度和花瓣宽度。这些样本被分为三个类别,分别是山鸢尾、变色鸢尾和维吉尼亚鸢尾。 使用K-Means聚类算法对鸢尾花数据进行聚类的过程如下: 1. 随机选择K个初始聚类中心。K代表要将数据聚成的类别数,这里我们选择K=3,即将鸢尾花数据聚成3个类别。 2. 对每个数据点,计算其与各个聚类中心的距离,并将其归类到距离最近的聚类中心。 3. 更新每个聚类中心的位置,将其移动到所归类数据点的平均位置。 4. 重复步骤2和3,直到聚类中心不再发生变化或达到预定的迭代次数。 通过上述步骤,可以将鸢尾花数据聚类成3个类别。每个类别中的数据点具有相似的特征,并且与其他类别中的数据点的特征有较大的区别。 K-Means聚类算法的优点是简单易实现,计算效率高。然而,这种算对初始聚类中心的选择较为敏感,可能会收敛到局部最优解。因此,在应用K-Means时,需要进行多次实验,以避免得到不理想的聚类结果。同时,K-Means对于离群点比较敏感,离群点可能会影响聚类结果的准确性。 ### 回答3: K-Means 聚类算法是一种常用的无监督学习算,主要用于将数据集中的样本划分成不同的簇。下面以实现鸢尾花数据的聚类为例进行解释。 首先,我们需要加载鸢尾花数据集,该数据集包含了150个样本,每个样本有4个特征,分别是花萼长度、花萼宽度、花瓣长度和花瓣宽度。我们将这些样本表示为一个150x4的矩阵。 然后,我们需要确定簇的数量 k,即要将数据集划分成几个簇。在这里,我们可以根据经验或者领域知识来选择一个合适的值。 接下来,我们需要初始化 k 个簇的中心点。可以随机从数据集中选取 k 个样本作为初始的簇中心点。 然后,对于每个样本,我们计算其与各个簇中心点的距离,并将其分配给距离最近的簇中心点所在的簇。 接着,我们更新每个簇的中心点,即将每个簇中的样本的特征均值作为新的簇中心点。 最后,我们重复执行以上两个步骤,直到簇中心点不再发生变化,或者到达预定的迭代次数。 完成聚类后,我们可以根据簇的中心点和每个样本所属的簇来进行结果的分析和可视化。例如,可以绘制不同簇中心点的特征值分布图,以及将样本点按簇的标签进行颜色分类的散点图等。 K-Means 聚类算法能够有效地将数据集划分为不同的簇,实现了对样本的聚类。在鸢尾花数据集这个例子中,我们可以根据花萼和花瓣的特征值将鸢尾花分为不同的类别,从而更好地了解这些花的分类情况。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落寞红颜玉玫瑰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值