K-means聚类算法一文详解+Python代码实例

简介: K-means聚类算法一文详解+Python代码实例

前言


博主共参与了数十场数学建模,其中对于未给出标签的数据进行分析时一般第一个想到的就是聚类算法。聚类算法分析又称群分析,它是研究(样品或指标)分类问题的一种统计分析方法,同时也是数据挖掘的一个重要算法。聚类(Cluster)分析是由若干模式(Pattern)组成的,通常,模式是一个度量(Measurement)的向量,或者是多维空间中的一个点。聚类分析以相似性为基础,在一个聚类中的模式之间比不在同一聚类中的模式之间具有更多的相似性。


K-means均值聚类算法作为最经典也是最基础的无标签分类学习算法,根据不断的迭代优化衍生出许多十分好用的算法,例如K-mean++、K-MEDOIDS等。因此学习K-means的底层原理和计算方法是十分有必要。


本篇博客的愿景是希望我或者读者通过阅读这篇博客能够学会方法并能实际运用,而且能够记录到你的思想之中。希望读者看完能够提出错误或者看法,博主会长期维护博客做及时更新。


39e28ec7f5af404dbae0bf251fed1c50.jpg

一、聚类分析



我们知道我们是使用聚类算法的目的就是从大量数据中将他们具有相关性的特征输入,然后通过算法返回标签类型。也就是说该算法的目的就是将具有相同特性的数据归纳为一类。当然我们的算法是贪心的,尽可能将所有相同类型的数据归为一类,本质还在站在分类的角度上,只不过没有标签需要我们进行运算得出。


那么既然是找到具有相同性质的数据,那么回到原始的方法,例如KNN算法,我们只需要去根据两个数据点的距离去判断他们是否属于一类,其实聚类的思想也类似,只不过我们选择圆心的点不再是判断数据类型的点,而是划分为一类标签的最大范围半径的点,类似画一个最大的圆


570c188e66224c25a242655b25a32c19.png


二、K-means原理


既然前面我们谈到了聚类分析也就是根据彼此的相关性来划分聚类,那么他们的相关性又以什么来衡量呢?这点和KNN算法类似,这设计到了距离度量算法。


1.距离度量算法


在不同情况维度下,我们计算两点之间的距离也不同,不过我们在初中高中学的两点之间的距离计算公式,为欧式距离:


欧几里得距离(欧氏距离)

f7415fe78be0483e9b4bf057d0798aa7.png


衡量多维空间中的两点间距离,也是最常用的距离度量方法。


曼哈顿距离


对于曼哈顿距离大家可能会比较陌生,这是种使用在几何度量空间的几何学用语,用以标明两个点在标准坐标系上的绝对轴距总和。

a5ec01546a554421ad8739e97bd43fcc.png


图中红线代表曼哈顿距离,绿色代表欧氏距离,也就是直线距离,而蓝色和黄色代表等价的曼哈顿距离。曼哈顿距离——两点在南北方向上的距离加上在东西方向上的距离,即:

f67a7447d3984b67b710de97f8ed412e.png


对于一个具有正南正北、正东正西方向规则布局的城镇街道,从一点到达另一点的距离正是在南北方向上旅行的距离加上在东西方向上旅行的距离,因此,曼哈顿距离又称为出租车距离。曼哈顿距离不是距离不变量,当坐标轴变动时,点间的距离就会不同。


切比雪夫距离


相信大家对于距离的了解,了解多的知道除了欧式距离以外还有个曼哈顿距离,但是除去曼哈顿距离之外还有个切比雪夫距离。在数学中,切比雪夫距离或是L∞度量,是向量空间中的一种度量,二个点之间的距离定义是其各坐标数值差绝对值的最大值。

3d2e615d9e8944379d40d50c305571e8.png


国际象棋棋盘上二个位置间的切比雪夫距离是指王要从一个位子移至另一个位子需要走的步数。由于王可以往斜前或斜后方向移动一格,因此可以较有效率的到达目的的格子。图是棋盘上所有位置距f6位置的切比雪夫距离。一维空间中,所有的Lp度量都是一样的,即为二座标差的绝对值。


平面上两点image.png的切比雪夫距离为:


fe63217c0b7b41a4ae851d00a5894d62.png

n维空间上的切比雪夫距离:

n 维空间则有两点image.png

c9bb96ec80a4467596bd52e538d9f42d.png

2.K-means算法思想


K-means聚类算法思想可以看它设计诞生的伪代码看出:


e1d3b0a9ae7e4037a9b6e2237ee34734.png

我们发现这是一种迭代求解的聚类分析算法,其步骤是,预将数据分为K组,则随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。


三.K-means算法实现


8aef870c8b9d4c3ca1c55efedeaa8b5d.jpg



我们将算法步骤细化来分析:


step1:选取K值


k 的选择一般是按照实际需求进行决定,或在实现算法时直接给定 k 值。这是基于项目你想要聚类的个数来决定的,但是也有不确定的情况,我们可能需要去一个最优的K值来将数据很好的归类达到最大化区分类别,这时候就需要思考从数据角度出发,应该进行怎么样的计算能够得到最优的K。


1.手肘法


手肘法是最常用的确定K-means算法K值的方法,所用到的衡量标准是SSE(sum of the squared errors,误差平方和)  。


7435306dd7674ee2a40b301c0479b10c.png


SEE各个计算在K-means里含义如下图:


b2dbc71b3b5c4d48a39bf4ec0f2c22b8.png


误差平方和又称残差平方和,根据n个观察值拟合适当的模型后,余下未能拟合部份(ei=yi−y¯ei=yi−y¯)称为残差,其中y平均表示n个观察值的平均值,所有n个残差平方之和称误差平方和。


残差我在最小二乘法已经做了详细解释,想要了解的可以去看我这篇文章:


一文速学-最小二乘法曲线拟合算法详解+项目代码


在回归分析中通常用SSE表示,其大小用来表明函数拟合的好坏。将残差平方和除以自由度n-p-1(其中p为自变量个数)可以作为误差方差σ2的无偏估计,通常用来检验拟合的模型是否显著也用来寻找K值。


主要思想:当k小于真实聚类数时,随着k的增大,会大幅提高类间聚合程度,SSE会大幅下降,当k达到真实聚类数时,随着k的增加,类间的聚合程度不会大幅提高,SSE的下降幅度也不会很大,所以k/SSE的折线图看起来像一个手肘,我们选取肘部的k值进行运算。


这里我们以欧几里德距离来计算两点之间的相关性:


python代码:

import matplotlib.pyplot as plt
import pandas as pd
from sklearn.metrics import silhouette_score
from sklearn.cluster import KMeans
data=pd.read_csv(r'C:\Users\10799\get_info\sklearn_try\series_gpstime_level.csv')
distortions=[]#簇内误差平方和  SSE
for i in range(2,10):
    Kmeans_model=KMeans(n_clusters=i)
    predict_=Kmeans_model.fit_predict(data)
    distortions.append(Kmeans_model.inertia_)
    print("簇内误差平方和:",distortions)
#SSE  手肘法
plt.plot(range(2,10),distortions,marker='x')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.title('distortions')
plt.show()

b34ef383de8549899bbeef0a30600324.png


根据拟合图片我们知道选K为5时能够得到最效率的K值。


2.轮廓系数法


轮廓系数这一指标无需知道数据集的真实标签。取值范围[-1, 1],值越大,聚类效果越好。旨在将某个对象与自己的簇的相似程度和与其他簇的相似程度作比较。轮廓系数最高的簇的数量表示簇的数量的最佳选择。


轮廓系数综合考虑了内聚度和分离度两种因素。


方法:  轮廓系数公式:

image.png


1)计算样本i到同簇其他样本的平均距离a(i)。a(i) 越小,说明样本i越应该被聚类到该簇。将a(i)称为样本i的簇内不相似度。

e32ccd92189c4250b0ffb07518a92ef5.png

最近簇定义如下:


image.png


2)计算样本i到其他某簇Cj的所有样本的平均距离bij,称为样本i与簇Cj 的不相似度。定义为样本i的簇间不相似度:bi =min{bi1, bi2, ..., bik},bi越大,说明样本i越不属于其他簇。


轮廓系数范围在[-1,1]之间。该值越大,越合理。

si接近1,则说明样本i聚类合理;

si接近-1,则说明样本i更应该分类到另外的簇;

若si 近似为0,则说明样本i在两个簇的边界上。

有样本的s i 的均值称为聚类结果的轮廓系数,是该聚类是否合理、有效的度量。

3)根据样本i的簇内不相似度a(i)和簇间不相似度b(i) ,定义样本i的轮廓系数.

8ccd27ea80874b80a6bca371de48714b.png


   求出所有点的轮廓系数再求平均值就得出了平均轮廓系数,平均轮廓系数的取值在[-1,1],显然,由轮廓系数公式可以观察出,凝聚度越小,分离度越大,分类效果越好,平均轮廓系数也越大,所以,取平均轮廓系数最大的点的k值时,分类效果越好.


python代码:

scores=[]  #存放轮廓系数
for i in range(2,10):
    Kmeans_model=KMeans(n_clusters=i)
    predict_=Kmeans_model.fit_predict(data)
    scores.append( silhouette_score(data,predict_))
    print("轮廓系数:",scores)
#轮廓系数法
plt.plot(range(2,10),scores,marker='x')
plt.xlabel('Number of clusters')
plt.ylabel('scores')
plt.title('scores')
plt.show()

eb25fe8448594aa082e3be7b9cd52c22.png


step2:计算初始化K点


初始质心随机选择即可,每一个质心为一个类。对剩余的每个样本点,计算它们到各个质心的欧式距离,并将其归入到相互间距离最小的质心所在的簇。

def euclDistance(x1, x2):
    return np.sqrt(sum((x2 - x1) ** 2))
def initCentroids(data, k):
    numSamples, dim = data.shape
    # k个质心,列数跟样本的列数一样
    centroids = np.zeros((k, dim))
    # 随机选出k个质心
    for i in range(k):
        # 随机选取一个样本的索引
        index = int(np.random.uniform(0, numSamples))
        # 作为初始化的质心
        centroids[i, :] = data[index, :]
    return centroids

step3:迭代计算重新划分


计算各个新簇的质心。


在所有样本点都划分完毕后,根据划分情况重新计算各个簇的质心所在位置,然后迭代计算各个样本点到各簇质心的距离,对所有样本点重新进行划分


重复2. 和 3.,直到质心不在发生变化时或者到达最大迭代次数时


# 传入数据集和k值
def kmeans(data, k):
    # 计算样本个数
    numSamples = data.shape[0]
    # 样本的属性,第一列保存该样本属于哪个簇,第二列保存该样本跟它所属簇的误差
    clusterData = np.array(np.zeros((numSamples, 2)))
    # 决定质心是否要改变的质量
    clusterChanged = True
    # 初始化质心
    centroids = initCentroids(data, k)
    while clusterChanged:
        clusterChanged = False
        # 循环每一个样本
        for i in range(numSamples):
            # 最小距离
            minDist = 100000.0
            # 定义样本所属的簇
            minIndex = 0
            # 循环计算每一个质心与该样本的距离
            for j in range(k):
                # 循环每一个质心和样本,计算距离
                distance = euclDistance(centroids[j, :], data[i, :])
                # 如果计算的距离小于最小距离,则更新最小距离
                if distance < minDist:
                    minDist = distance
                    # 更新最小距离
                    clusterData[i, 1] = minDist
                    # 更新样本所属的簇
                    minIndex = j
            # 如果样本的所属的簇发生了变化
            if clusterData[i, 0] != minIndex:
                # 质心要重新计算
                clusterChanged = True
                # 更新样本的簇
                clusterData[i, 0] = minIndex
        # 更新质心
        for j in range(k):
            # 获取第j个簇所有的样本所在的索引
            cluster_index = np.nonzero(clusterData[:, 0] == j)
            # 第j个簇所有的样本点
            pointsInCluster = data[cluster_index]
            # 计算质心
            centroids[j, :] = np.mean(pointsInCluster, axis=0)
    return centroids, clusterData


step4:可视化展现

def showCluster(data, k, centroids, clusterData):
    numSamples, dim = data.shape
    if dim != 2:
        print('dimension of your data is not 2!')
        return 1
    # 用不同颜色形状来表示各个类别
    mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'dr', '<r', 'pr']
    if k > len(mark):
        print('your k is too large!')
        return 1
    # 画样本点
    for i in range(numSamples):
        markIndex = int(clusterData[i, 0])
        plt.plot(data[i, 0], data[i, 1], mark[markIndex])
    # 用不同颜色形状来表示各个类别
    mark = ['*r', '*b', '*g', '*k', '^b', '+b', 'sb', 'db', '<b', 'pb']
    # 画质心点
    for i in range(k):
        plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize=20)
    plt.show()


最后结果根据 手肘法我们选取k为5:

k = 5
centroids, clusterData = kmeans(data, k)
if np.isnan(centroids).any():
    print('Error')
else:
    print('cluster complete!')
    # 显示结果
showCluster(data, k, centroids, clusterData)


5d2e8087488d44c1b569479057d4d60f.png


四、K-means优缺点


优点:


  • k‐均值算法原理简单,容易实现,且运行效率比较高
  • k‐均值算法聚类结果容易解释,适用于高维数据的聚类


缺点:


  • k‐均值算法采用贪心策略,导致容易局部收敛,在大规模数据集上求解较慢
  • k‐均值算法对离群点和噪声点非常敏感,少量的离群点和噪声点可能对算法求平均值产生极大影响,从而影响聚类结果
目录
相关文章
|
12天前
|
开发框架 数据建模 中间件
Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器是那些静悄悄的幕后英雄。它们不张扬,却能默默地为函数或类增添强大的功能。本文将带你了解装饰器的魅力所在,从基础概念到实际应用,我们一步步揭开装饰器的神秘面纱。准备好了吗?让我们开始这段简洁而富有启发性的旅程吧!
23 6
|
10天前
|
机器学习/深度学习 人工智能 算法
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
宠物识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了37种常见的猫狗宠物种类数据集【'阿比西尼亚猫(Abyssinian)', '孟加拉猫(Bengal)', '暹罗猫(Birman)', '孟买猫(Bombay)', '英国短毛猫(British Shorthair)', '埃及猫(Egyptian Mau)', '缅因猫(Maine Coon)', '波斯猫(Persian)', '布偶猫(Ragdoll)', '俄罗斯蓝猫(Russian Blue)', '暹罗猫(Siamese)', '斯芬克斯猫(Sphynx)', '美国斗牛犬
76 29
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
|
1天前
|
机器学习/深度学习 人工智能 算法
猫狗宠物识别系统Python+TensorFlow+人工智能+深度学习+卷积网络算法
宠物识别系统使用Python和TensorFlow搭建卷积神经网络,基于37种常见猫狗数据集训练高精度模型,并保存为h5格式。通过Django框架搭建Web平台,用户上传宠物图片即可识别其名称,提供便捷的宠物识别服务。
84 53
|
5天前
|
数据可视化 Python
以下是一些常用的图表类型及其Python代码示例,使用Matplotlib和Seaborn库。
通过这些思维导图和分析说明表,您可以更直观地理解和选择适合的数据可视化图表类型,帮助更有效地展示和分析数据。
36 8
|
12天前
|
API Python
【Azure Developer】分享一段Python代码调用Graph API创建用户的示例
分享一段Python代码调用Graph API创建用户的示例
35 11
|
14天前
|
测试技术 Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界中,装饰器是那些能够为我们的代码增添魔力的小精灵。它们不仅让代码看起来更加优雅,还能在不改变原有函数定义的情况下,增加额外的功能。本文将通过生动的例子和易于理解的语言,带你领略装饰器的奥秘,从基础概念到实际应用,一起开启Python装饰器的奇妙旅程。
31 11
|
10天前
|
Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器就像是给函数穿上了一件神奇的外套,让它们拥有了超能力。本文将通过浅显易懂的语言和生动的比喻,带你了解装饰器的基本概念、使用方法以及它们如何让你的代码变得更加简洁高效。让我们一起揭开装饰器的神秘面纱,看看它是如何在不改变函数核心逻辑的情况下,为函数增添新功能的吧!
|
11天前
|
程序员 测试技术 数据安全/隐私保护
深入理解Python装饰器:提升代码重用与可读性
本文旨在为中高级Python开发者提供一份关于装饰器的深度解析。通过探讨装饰器的基本原理、类型以及在实际项目中的应用案例,帮助读者更好地理解并运用这一强大的语言特性。不同于常规摘要,本文将以一个实际的软件开发场景引入,逐步揭示装饰器如何优化代码结构,提高开发效率和代码质量。
35 6
|
10天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
16天前
|
机器学习/深度学习 算法 Serverless
基于WOA-SVM的乳腺癌数据分类识别算法matlab仿真,对比BP神经网络和SVM
本项目利用鲸鱼优化算法(WOA)优化支持向量机(SVM)参数,针对乳腺癌早期诊断问题,通过MATLAB 2022a实现。核心代码包括参数初始化、目标函数计算、位置更新等步骤,并附有详细中文注释及操作视频。实验结果显示,WOA-SVM在提高分类精度和泛化能力方面表现出色,为乳腺癌的早期诊断提供了有效的技术支持。