机器学习(六):K-means聚类原理及案例分析

一、算法简介

1、监督学习和非监督学习

监督学习

监督学习是根据已有的数据集,知道输入和输出结果之间的关系。根据这种已知的关系,训练得到一个最优的模型。也就是说,在监督学习中训练数据既有特征又有标签,通过训练,让机器可以自己找到特征和标签之间的联系,在面对只有特征没有标签的数据时,可以判断出标签。监督学习中的常用算法包括线性回归、逻辑回归、朴素贝叶斯、支持向量机、人工神经网络和随机森林。在回归和分类中,目标都是找到输入数据中的特定关系或结构,以便我们有效地生成正确的输出数据。

非监督学习

非监督学习是我们不知道数据集中数据和特征之间的关系,而是要根据聚类或一定的模型得到数据之间的关系。在非监督学习中,我们知识给定了一组数据,我们的目标是发现这组数据中的特殊结构。例如我们使用非监督学习算法会将这组数据分成两个不同的簇,这样的算法就叫聚类算法。

2、K-means算法

K均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,是非监督学习算法的一种,其算法思想大致为:先从样本集中随机选取K个样本作为簇中心,并计算所有样本与这k个"簇中心"的距离,对于每一个样本,将其划分到与其距离最近的"簇中心"所在的簇中,对于新的簇计算各个簇的新的"簇中心"。
根据以上描述,我们大致可以猜测到实现kmeans算法的主要三点:

  • 簇个数k的选择
  • 各个样本点到"簇中心"的距离
  • 根据新划分的簇,更新"簇中心"

二、算法原理

1、原理

  • 随机设置K个特征空间内的点作为初始的聚类中心
  • 对于其他每个点计算到k个中心点的距离,选择最近的一个聚类中心点作为标记类别
  • 接着对着标记的聚类中心,重新计算出每个聚类点的平均值
  • 如果计算得出的新中心点与原中心点一样,那么结束,否则把新的平均值点作为新的中心,重新进行第二步过程

2、优化目标
min\sum_{i=1}^K\sum_{x\in C_{i}}dist(C_{i},x)^2
注:K是中心点,即簇数目;C_{i}表示每个簇内的中心点,x属于每个簇内的样本点;我们的目的是优化每个簇的点使得它到样本中心点的距离是最短的。
3、算法的性能评估指标
轮廓系数:
SC_{i}=\frac{b_{i}-a_{i}}{max(b_{i},a_{i})} 注:对于每个点i为已聚类数据中的样本,b_{i}i到其他簇群的所有样本的距离最小值,a_{i}为i到本身簇的距离平均值,最终计算出所有的样本点的轮廓系数平均值。
这里我们举个例子说明:


上图我们按照评估指标看一下,以蓝色样本为例,
1、计算蓝1到自身类别的点距离的平均值记为
2、计算蓝1分别到紅色类别,绿色类别所有点的距离,求出平均值,取其中最下的值记为
我们讨论一下极端的情况:

  • 好情况(b_{i} >> a_{i}
    SC_{i} =1
  • 坏情况(b_{i} << a_{i}
    SC_{i} =-1
    所以轮廓系数的取值范围应该在[-1,1]之间,越靠近1说明聚类的效果越好。
    关于轮廓系数的求解,sklearn算法包中有封装好的轮廓系数API,我们可以直接调用:
    sklearn.metric.sihouette_score(X,labels)
    计算所有样本的平均轮廓系数
    X:特征值
    labels:被聚类标记的目标值

4、算法的使用
关于k-means算法的使用,由于k-means比较简单,我们这里仍然使用sklearn包中封装好的k-means的API,
sklearn.cluster.KMeans(n_clusters=8,init='k-means++')

  • n_clusters:开始的聚类中心数量
  • init:初始化方法,默认为'k-means++'
  • labels_:默认标记的类别,可以和真实值比较(不是值的比较)

接下来,我们用实例来看一下API的使用。

三、案例讲解

案例用到的数据集是给出啤酒的一些成分,将这些啤酒进行聚类,首先我们先看一下数据集,这个数据集非常小,只有19个样本,我们进行简单的演示,

首先我们先用k-means算法进行聚类,

import pandas as pd
from pandas.plotting import scatter_matrix
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
def kmeans():
    """
    Kmeams聚类分析啤酒
    :return:
    """
    #读取数据
    data = pd.read_table('../../../数据集/机器学习/聚类算法/啤酒成分和价格/beer.txt',sep=' ',error_bad_lines=False)
    print(data)

    #提取特征值
    X = data.loc[:,['calories','sodium','alcohol','cost']]
    # print(x)

    #使用Kmeans聚类
    kmean = KMeans(n_clusters=3)
    km = kmean.fit(X)
    data['cluster'] = km.labels_
    centers = data.groupby("cluster").mean().reset_index()
    # print(centers)
    #画图,四个特征量量比较
    plt.rcParams['font.size'] = 14
    colors = np.array(['red','green','blue','yellow'])
    plt.scatter(data['calories'],data['alcohol'],c=colors[data["cluster"]])
    plt.scatter(centers.calories,centers.alcohol,linewidths=3,marker='+',s=300,c='black')#中心点
    plt.xlabel("Calories")
    plt.ylabel("Alcohol")
    plt.show()
    return None

if __name__ == "__main__":
    kmeans()

我们顺便用图像画一下聚类的效果,



我们还可以将四个特征两两比较,

import pandas as pd
from pandas.plotting import scatter_matrix
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
def kmeans():
    """
    Kmeams聚类分析啤酒
    :return:
    """
    #读取数据
    data = pd.read_table('../../../数据集/机器学习/聚类算法/啤酒成分和价格/beer.txt',sep=' ',error_bad_lines=False)
    print(data)

    #提取特征值
    X = data.loc[:,['calories','sodium','alcohol','cost']]
    # print(x)

    #使用Kmeans聚类
    kmean = KMeans(n_clusters=3)
    km = kmean.fit(X)
    data['cluster'] = km.labels_
centers = data.groupby("cluster").mean().reset_index()
    # print(centers)
    # 画图,四个特征量量比较
    plt.rcParams['font.size'] = 14
    colors = np.array(['red','green','blue','yellow'])
    plt.scatter(data['calories'],data['alcohol'],c=colors[data["cluster"]])
    plt.scatter(centers.calories,centers.alcohol,linewidths=3,marker='+',s=300,c='black')#中心点
    plt.xlabel("Calories")
    plt.ylabel("Alcohol")
    #
    scatter_matrix(data[['calories','sodium','alcohol','cost']],s=100,alpha=1,c = colors[data["cluster"]],figsize=(15,15))
    plt.suptitle("初始化成3类")
    plt.show()
    return None

if __name__ == "__main__":
    kmeans()

我们可以通过横纵坐标进行两两特征的比较。
我们还可以对数据进行标准化,然后对比没有进行标准化后的效果。
我们先看一下标准化后的数据(标准化的代码在后面有),



接下来我们对标准化后的数据进行聚类看一下轮廓系数评估结果,

import pandas as pd
from pandas.plotting import scatter_matrix
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
def kmeans():
    """
    Kmeams聚类分析啤酒
    :return:
    """
    #读取数据
    data = pd.read_table('../../../数据集/机器学习/聚类算法/啤酒成分和价格/beer.txt',sep=' ',error_bad_lines=False)
    #print(data)

    #提取特征值
    X = data.loc[:,['calories','sodium','alcohol','cost']]
    # print(x)

    #使用Kmeans聚类
    kmean = KMeans(n_clusters=3)
    km = kmean.fit(X)
    data['cluster'] = km.labels_
    #我们进行标准化看看效果
    std = StandardScaler()
    x = std.fit_transform(X)
    # print(x)
    kms = kmean.fit(x)
    data["cluster_std"] = kms.labels_
    #print(data)
    #使用轮廓系数进行评估
    sht = silhouette_score(X,data.cluster)  #没有进行标准化的评估值
    sht2 = silhouette_score(x,data.cluster_std)
    print(sht)
    print(sht2)
    return None

if __name__ == "__main__":
    kmeans()


第一个是没有标准化的数据聚类后的效果0.67,第二个是标准化后的数据聚类结果0.44,通过结果来看,数据标准化不一定会使结果更优。
我们还可以比较一下将K值设置的更大的时候的效果,举例将k设置为[2,19]并将效果图画出来看一下,

import pandas as pd
from pandas.plotting import scatter_matrix
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
def kmeans():
    """
    Kmeams聚类分析啤酒
    :return:
    """
    #读取数据
    data = pd.read_table('../../../数据集/机器学习/聚类算法/啤酒成分和价格/beer.txt',sep=' ',error_bad_lines=False)
    # print(data)

    #提取特征值
    X = data.loc[:,['calories','sodium','alcohol','cost']]
    # print(x)

    #使用Kmeans聚类
    kmean = KMeans(n_clusters=3)
    km = kmean.fit(X)
    data['cluster'] = km.labels_
    
    scores = []
    for i in range(2,19):
        labels = KMeans(n_clusters=i).fit(X).labels_
        score = silhouette_score(X,labels)
        scores.append(score)
    print(scores)

    plt.plot(list(range(2,19)),scores)
    plt.xlabel("Number of Clusters Initialized")
    plt.ylabel("Sihouette Score")
    plt.show()
    return None

if __name__ == "__main__":
    kmeans()

通过图像可以看出来,随着k值的增大,效果反而并没有变得更好,所以k值的选取是一个很重要的点。

总结

这里我们对k-means聚类进行总结一下,

  • 优点:简单,快速,适合常规数据集,迭代式算法非常实用
  • 缺点:k值难确定,复杂度与样本呈线性关系,很难发现任意形状的簇,而且容易收敛到局部最优解(可以多次聚类)

四、补充(DBSCAN聚类算法)

1、简介

这里我们补充一种聚类算法,DBSCAN(Density-Based Spatial Clustering of Applications with Noise,具有噪声的基于密度的聚类方法)是一种基于密度的空间聚类算法。 该算法将具有足够密度的区域划分为簇,并在具有噪声的空间数据库中发现任意形状的簇,它将簇定义为密度相连的点的最大集合。比如下图,我们希望将下面的圆环分成两个部分,一个是里面的内核,一个是外面的环状,但是我们用k-means聚类之后,只能分成下面的形状,而且通过理解k-means聚类原理,我们知道k-means是做不到我们想要的结果的,这时我们就可以使用DBSCAN算法了。


DBSCAN效果图

2、算法原理

DBSCAN使用的方法很简单,它任意选择一个没有类别的核心对象作为种子,然后找到所有这个核心对象能够密度可达的样本集合,即为一个聚类簇。接着继续选择另一个没有类别的核心对象去寻找密度可达的样本集合,这样就得到另一个聚类簇。一直运行到所有核心对象都有类别为止。
基本上这就是DBSCAN算法的主要内容了,是不是很简单?但是我们还是有三个问题没有考虑。
1、第一个是一些异常样本点或者说少量游离于簇外的样本点,这些点不在任何一个核心对象在周围,在DBSCAN中,我们一般将这些样本点标记为噪音点。
2、第二个是距离的度量问题,即如何计算某样本和核心对象样本的距离。在DBSCAN中,一般采用最近邻思想,采用某一种距离度量来衡量样本距离,比如欧式距离。这和KNN分类算法的最近邻思想完全相同。
3、第三种问题比较特殊,某些样本可能到两个核心对象的距离都小于ϵϵ,但是这两个核心对象由于不是密度直达,又不属于同一个聚类簇,那么如果界定这个样本的类别呢?一般来说,此时DBSCAN采用先来后到,先进行聚类的类别簇会标记这个样本为它的类别。也就是说BDSCAN的算法不是完全稳定的算法。

这里还有几个重要概念需要了解一下:

核心对象:若某个点的密度达到算法设定的阈值则其为核心点(即r领域内点的数量不小于minPts)

领域的距离阈值:设定的半径r

直接密度可达:若某点p在点q的r领域内,且q是核心点则p-q直接密度可达

密度可达:若有一个点的序列q0,q1,...,qk,对任意qi-qi-1是直接密度可达的,则称从q0到qk密度可达,这实际上是直接密度可达的"传播"

聚类算法对比

通过上图对比我们发现,DBSCAN聚类比Kmeans还要强大,这也是我们讲的主要原因。

3、工作流程

参数D:输入数据集
参数\epsilon:制定半径
MinPts:密度阈值
1、标记所有对象为unvisited;
2、Do
3、随机选择一个unvisited对象p;
4、标记p为visited;
5、if p 的\epsilon-领域至少有MinPts个对象
6、    创建一个新簇C,并把p添加到C;
7、    令N为p的\epsilon-领域中的对象集合;
8、    For N 中每个点p
9、          if p 为unvisited
10、            标记p为visited
11、             if p的\epsilon-领域至少有MinPts个对象,把这些对象添加到N;
12、             如果p还不是任何簇的成员,把p添加到C;
13、   End For;
14、   输出C;
15、Else 标记p为噪声;
16、Until没有标记为unvisited的对象;

4、API

sklearn包中有封装好的DBSCAN算法from sklearn.cluster import DBSCAN
DBSCAN算法适合检测异常点和离群点
参数选择:
1)eps: DBSCAN算法参数,即我们的ϵ-邻域的距离阈值,和样本距离超过ϵ的样本点不在ϵ-邻域内。默认值是0.5.一般需要通过在多组值里面选择一个合适的阈值。eps过大,则更多的点会落在核心对象的ϵ-邻域,此时我们的类别数可能会减少, 本来不应该是一类的样本也会被划为一类。反之则类别数可能会增大,本来是一类的样本却被划分开。
2)min_samples: DBSCAN算法参数,即样本点要成为核心对象所需要的ϵ-邻域的样本数阈值。默认值是5. 一般需要通过在多组值里面选择一个合适的阈值。通常和eps一起调参。在eps一定的情况下,min_samples过大,则核心对象会过少,此时簇内部分本来是一类的样本可能会被标为噪音点,类别数也会变多。反之min_samples过小的话,则会产生大量的核心对象,可能会导致类别数过少。

5、案例分析

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.cluster import KMeans
from sklearn.cluster import DBSCAN

def dbs():
    """
    用一组随机数来验证DBSCAN算法与Kmeans算法
    :return:
    """
    X1, y1=datasets.make_circles(n_samples=5000, factor=.6,
                                          noise=.05)
    X2, y2 = datasets.make_blobs(n_samples=1000, n_features=2, centers=[[1.2,1.2]], cluster_std=[[.1]],
                   random_state=9)

    X = np.concatenate((X1, X2))
    plt.scatter(X[:, 0], X[:, 1], marker='o')
    plt.show()
    #使用K-means算法
    x_pred = KMeans(n_clusters=3, random_state=9).fit_predict(X)
    plt.scatter(X[:, 0], X[:, 1], c=x_pred)
    plt.show()
    #使用DBSCAN算法
    y_pred = DBSCAN().fit_predict(X)
    plt.scatter(X[:, 0], X[:, 1], c=y_pred)
    plt.show()
    return None

if __name__ == "__main__":
    dbs()

我们看一下输出结果,


原始数据

Kmeans聚类

DBSCAN聚类

通过上面与原始数据对比发现,K-means的效果并不好,比DBSCAN效果要差,但是DBSCAN的效果也不是很理想,我们的理想状态是可以分成三部分(内环、外环、右上角),接下来,我们可以通过调整一下参数来实现,我们在DBSCAN算法里,加上一些参数经过不断的优化,最后实现的下面的效果。

y_pred = DBSCAN(eps=0.1,min_samples=10).fit_predict(X)
    plt.scatter(X[:, 0], X[:, 1], c=y_pred)
    plt.show()

6、总结

  • 优点
    • 不需要指定簇的个数
    • 可以发现任意形状的簇
    • 擅长找到离群点(检测任务)
    • 两个参数就够了
  • 缺点
    • 高维数据有些困难(可以做降维)
    • 参数难以选择(参数对结果的影响非常大)
    • Sklearn中效率很慢(数据削减策略)

K-means算法到这里就结束了,有不懂的同学可以在下方留言。
注:最后这个案例是引用的博客刘建平老师的博客。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335