机器学习:10. 聚类算法KMeans

1 概述

无监督的算法在训练的时候只需要特征矩阵X,不需要标签。
有监督学习是在模型在训练的时候,即需要特征矩阵X,也需要真实标签y。
聚类算法又叫做“无监督分类”,其目的是将数据划分成有意义或有用的组(或簇)。

聚类和分类的区别:分类的标签已知,聚类的标签未知

2 KMeans是如何工作的

簇和质心
KMeans算法是将一组N个样本的特征矩阵X划分为K个无交集的簇,簇是聚类结果的表现。簇中所有数据的均值通常被称为这个簇的“质心”(centroids)。在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。

KMeans的核心任务:根据我们设定好的K,找出K个最优的质心,并将离这些质心最近的数据分别分配到这些质心代表的簇中去。

那什么情况下,质心的位置会不再变化呢?
当我们找到一个质心,在每次迭代中被分配到这个质心上的样本都是一致时,质心的位置就不会再改变。

聚类算法聚出的类有什么样的性质?
被分在同一个簇中的数据是有相似性的,而不同簇中的数据是不同的,即求“组内差异小,组间差异大”。而这个“差异“,由样本点到其所在簇的质心的距离来衡量。对于一个簇来说,所有样本点到质心的距离之和越小,我们就认为这个簇中的样本越相似,簇内差异就越小。

image.png

其中,m为一个簇中样本的个数,j是每个样本的编号。这个公式被称为簇内平方和(cluster Sum of Square),又叫做Inertia。而将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和(Total Cluster Sum of Square),又叫做total inertia。Total Inertia越小,代表着每个簇内样本越相似,聚类的效果就越好。因此KMeans追求的是,求解能够让Inertia最小化的质心。

3 sklearn.cluster.KMeans

class sklearn.cluster.KMeans (n_clusters=8, init=’k-means++’, n_init=10, max_iter=300, tol=0.0001,
precompute_distances=’auto’, verbose=0, random_state=None, copy_x=True, n_jobs=None, algorithm=’auto’)

3.1 重要参数n_clusters

n_clusters是KMeans中的k,表示着我们告诉模型我们要分几类。默认为8类,但通常我们的聚类结果会是一个小于8的结果,因此我们要对它进行探索。

3.1.1 先进行一次聚类看看

当我们拿到一个数据集,如果可能的话,我们希望能够通过绘图先观察一下这个数据集的数据分布,以此来为我们聚类时输入的n_clusters做一个参考。
创建一个数据集探索

from sklearn.datasets import make_blobs #make_blobs是帮我做几个簇的意思
import matplotlib.pyplot as plt
#自己创建数据集
X, y = make_blobs(n_samples=500,n_features=2,centers=4,random_state=1) #500个数据 2个特征,4个簇,随机性规定,让数据稳定
X.shape
#(500, 2)
y.shape
#(500,)

画图探索

fig, ax1 = plt.subplots(1) #生成子图1个:子图由两部分组成,画布fig 对象ax1
ax1.scatter(X[:, 0], X[:, 1] #将二维数据两列全部画进去
                ,marker='o' #点的形状
                ,s=8 #点的大小
                )
plt.show()
image.png

显示点的分布
这是我们在创建数据的时候,已经规定了分为4簇,所以这里直接range(4)。

#如果我们想要看见这个点的分布,怎么办?
color = ["red","pink","orange","gray"]
fig, ax1 = plt.subplots(1)
for i in range(4):
    ax1.scatter(X[y==i, 0], X[y==i, 1] #就是取出y_pred是0,1,2,3的那一簇的X
                ,marker='o' #点的形状
                ,s=8 #点的大小
                ,c=color[i]
                )
plt.show()
image.png

如果不知道真实数据分为几簇,我们来使用Kmeans进行聚类

#基于这个分布,我们来使用Kmeans进行聚类
from sklearn.cluster import KMeans
n_clusters = 3 #假设分为了3簇
#KMeans目的就是求出质心,已经把簇分好了,不需要接口
cluster = KMeans(n_clusters=n_clusters, random_state=0).fit(X) #实例化,训练,random_state也是为了模型稳定


# 重要属性labels_,查看聚好的类别,每个样本所对应的类,即标签
y_pred = cluster.labels_
y_pred


#但其实KMeans也有接口predict和fit_predict,表示学习数据X并对X的类进行预测
#但所得的结果和直接fit之后调用属性lables_一模一样
pre = cluster.fit_predict(X)
pre == y_pred


#但是数据量大的时候,可以先使用部分数据来确定质心
#在用法剩下的数据聚类结果,使用predict来调用,这样计算量会少很多
cluster_smallsub = KMeans(n_clusters=n_clusters, random_state=0).fit(X[:200]) #X[:200]切片取200行进行质心计算
y_pred_ = cluster_smallsub.predict(X) #再用整体数据做predict
y_pred == y_pred_ #这说明整体数据不完全相似,但是在数据量很大的时候,效果还是比较好的
image.png

重要属性cluster_centers_, inertia_

#重要属性cluster_centers_,查看质心
centroid = cluster.cluster_centers_
print(centroid)
centroid.shape

#重要属性inertia_,查看总距离平方和,越小,模型效果越好。
inertia = cluster.inertia_
inertia
image.png

画出3簇的散点图

color = ["red","pink","orange","gray"]
fig, ax1 = plt.subplots(1)
for i in range(n_clusters):
    ax1.scatter(X[y_pred==i, 0], X[y_pred==i, 1] #就是取出y_pred是0,1,2的那一类的X
                ,marker='o' #点的形状
                ,s=8 #点的大小
                ,c=color[i]
                )
#对不同颜色的簇找出质心
ax1.scatter(centroid[:,0],centroid[:,1]
            ,marker="x"
            ,s=20
            ,c="black")
plt.show()
分成了3簇

inertia不是一个有效的聚类评估指标
当n_cluster = 500时,inertia将等于0。

#如果聚类效果更好的话,inertia应该更低
#随着n_cluster越来越大,inertia可以等于0;所以模型的效果调整只有在K不变的时候,调整才对。
#可见,inertia不是一个有效的评估指标
n_clusters = 4
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_

输出结果

908.3855684760603

3.1.2聚类算法的有效模型评估指标

当真实标签未知的时候:轮廓系数
未知标签聚类是完全依赖于评价簇内的稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果。其中轮廓系数是最常用的聚类算法的评价指标。
1)样本与其自身所在的簇中的其他样本的相似度a,等于样本与同一簇中所有其他点之间的平均距离
2)样本与其他簇中的样本的相似度b,等于样本与下一个最近的簇中的所有点之间的平均距离
根据聚类的要求”簇内差异小,簇外差异大“,我们希望b永远大于a,并且大得越多越好。

轮廓系数计算公式

轮廓系数范围是(-1,1),其中值越接近1表示样本与自己所在的簇中的样本很相似,并且与其他簇中的样本不相似。轮廓系数越接近于1越好,负数则表示聚类效果非常差。如果一个簇中的大多数样本具有比较高的轮廓系数,则簇会有较高的总轮廓系数,则整个数据集的平均轮廓系数越高,则聚类是合适的。

我们使用模块metrics中的类silhouette_score来计算轮廓系数,它返回的是一个数据集中,所有样本的轮廓系数的均值。
metrics模块中的silhouette_sample,它的参数与轮廓系数一致,但返回的是数据集中每个样本自己的轮廓系数。

轮廓系数在自建模型上的表现

#轮廓系数
from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples

#分3簇
n_clusters = 3
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
silhouette_score(X,cluster_.labels_) #输入的是X和预测的标签(分好的簇),silhouette_score返回的是所有样本的轮廓系数均值

#分4簇
n_clusters = 4
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
silhouette_score(X,cluster_.labels_)

silhouette_samples(X,y_pred) #返回每一个样本的轮廓系数

输出结果

0.5882004012129721   3簇
0.6505186632729437   4簇

轮廓系数优点:它在有限空间中取值,使得我们对模型的聚类效果有一个“参考”。并且,轮廓系数对数据的分布没有假设,因此在很多数据集上都表现良好。它在每个簇的分割比较清洗时表现最好。

3.1.3 案例:基于轮廓系数来选择n_clusters

我们通常会绘制轮廓系数分布图和聚类后的数据分布图来选择我们的最佳n_clusters。
先解释代码,用n_cluster=4来画两图

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
import pandas as pd


#需要画两个图
#知道每个聚类出来类的轮廓系数是多少(图1),还想要各个类之间的轮廓系数的对比(图2)
#知道聚类后的图像的分布是什么模样
#先画下一个n_clusters下两个图是怎么画出来的
#先设定分成的簇数
n_clusters = 4

#创建一个画布,画布1行两列。
fig, (ax1, ax2) = plt.subplots(1, 2)
#画布尺寸
fig.set_size_inches(18, 7) #给两个子图设置

#第一个图是轮廓系数图像,是各个簇的轮廓系数组成的横向条形图
#横坐标是轮廓系数取值,纵坐标是每个样本
#横坐标的取值是[-1.1],但是我们至少希望轮廓系数>0,所以只设定X轴的取值在[-0.1,1]之间
ax1.set_xlim([-0.1, 1])

#纵坐标的取值从0开始,最大值是 X.shape[0],但是我们希望每个簇的在一起,不同簇的分开
#因此,在 X.shape[0]上加上一个距离(n_clusters + 1) * 10,留作间隔用
ax1.set_ylim([0, X.shape[0] + (n_clusters + 1) * 10])

#开始建模,调用聚类好的标签
clusterer = KMeans(n_clusters=n_clusters, random_state=10).fit(X)
cluster_labels = clusterer.labels_

#调用轮廓系数,silhouette_score是生成所有样本点的轮廓系数均值
#两个需要的参数:特征矩阵X和聚类完毕后的标签
silhouette_avg = silhouette_score(X, cluster_labels)
#打印K和现在的轮廓系数
print("For n_clusters =", n_clusters,
        "The average silhouette_score is :", silhouette_avg)


#调用silhouette_samples生成每个样本点的轮廓系数,就是横坐标
sample_silhouette_values = silhouette_samples(X, cluster_labels)


#设定y的初始取值
y_lower = 10

#接下来对每一个簇进行循环
for i in range(n_clusters):
    #从每个样本的轮廓系数结果中抽取出第i个簇的轮廓系数,sample_silhouette_values是每个样本的轮廓系数取值
    ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
    #然后对他进行排序
    ith_cluster_silhouette_values.sort()
    #查看这一个簇中有多少个样本
    size_cluster_i = ith_cluster_silhouette_values.shape[0]
    #这个簇在y上的取值就是y_lower=10开始,到初始值加上这个簇中样本数量结束(y_upper)
    y_upper = y_lower + size_cluster_i
    
    
    #在coclormap库中,使用小数来调用颜色的函数
    #在nipy_spectral()输入任意小数来代表颜色
    #所以我们用i的浮点数除以n_clusters,在不同的i下,自然生成不同的小数
    color = cm.nipy_spectral(float(i)/n_clusters)
    
    #开始填充图1的内容
    #fill_between是让一个范围中的柱状图都统一颜色的函数
    #fill_betweenx的范围是在纵坐标上
    #fill_betweeny的范围是在横坐标上
    #fill_betweenx的参数输入(纵坐标的下限,上限,x轴上的取值,柱状图的颜色)
    ax1.fill_betweenx(np.arange(y_lower, y_upper)
                                ,ith_cluster_silhouette_values #横坐标的取值是每一簇样本对应的轮廓系数
                                ,facecolor=color
                                ,alpha=0.7
                                )
    
    #为每个簇的轮廓系数添加编号,且簇的编号显示在每个条形图的中间位置
    #text(要显示编号的横坐标,纵坐标,编号内容)
    ax1.text(-0.05 #在左边
                , y_lower + 0.5 * size_cluster_i #放在中间
                , str(i))
    
    #每一次迭代后,y轴的上限+10,就是下一簇计算新的y轴的初始值
    y_lower = y_upper + 10


#给图1加标签和横纵坐标标签
ax1.set_title("The silhouette plot for the various clusters.")
ax1.set_xlabel("The silhouette coefficient values")
ax1.set_ylabel("Cluster label")

#将轮廓系数均值划线,可以看到哪个簇分的更好
ax1.axvline(x=silhouette_avg, color="red", linestyle="--")

#y轴不显示刻度,因为我不想知道每个簇有多少个样本
ax1.set_yticks([])
ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])

#这样子图1就处理完毕了



#开始画第二个图:各个类之间的轮廓系数的对比
#获取新颜色。cluster_labels.astype(float)取出500个颜色取出,但其实只有4类
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)

ax2.scatter(X[:, 0], X[:, 1]
            ,marker='o'
            ,s=8
            ,c=colors
            )

#把质心放到图像中
centers = clusterer.cluster_centers_
# Draw white circles at cluster centers
ax2.scatter(centers[:, 0], centers[:, 1], marker='x',
            c="red", alpha=1, s=200)

#设置标题
ax2.set_title("The visualization of the clustered data.")
ax2.set_xlabel("Feature space for the 1st feature")
ax2.set_ylabel("Feature space for the 2nd feature")

#为整个图设置标题
plt.suptitle(("Silhouette analysis for KMeans clustering on sample data "
"with n_clusters = %d" % n_clusters),
fontsize=14, fontweight='bold')
plt.show()
轮廓系数分布图和聚类后的数据分布图

将上面代码包装成循环

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np

for n_clusters in [2,3,4,5,6,7]:
    n_clusters = n_clusters
    fig, (ax1, ax2) = plt.subplots(1, 2)
    fig.set_size_inches(18, 7)
    ax1.set_xlim([-0.1, 1])
    ax1.set_ylim([0, X.shape[0] + (n_clusters + 1) * 10])
    clusterer = KMeans(n_clusters=n_clusters, random_state=10).fit(X)
    cluster_labels = clusterer.labels_
    silhouette_avg = silhouette_score(X, cluster_labels)
    print("For n_clusters =", n_clusters,
    "The average silhouette_score is :", silhouette_avg)
    sample_silhouette_values = silhouette_samples(X, cluster_labels)
    y_lower = 10
    for i in range(n_clusters):
        ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
        ith_cluster_silhouette_values.sort()
        size_cluster_i = ith_cluster_silhouette_values.shape[0]
        y_upper = y_lower + size_cluster_i
        color = cm.nipy_spectral(float(i)/n_clusters)
        ax1.fill_betweenx(np.arange(y_lower, y_upper)
                                    ,ith_cluster_silhouette_values
                                    ,facecolor=color
                                    ,alpha=0.7
                                    )
        ax1.text(-0.05
                    , y_lower + 0.5 * size_cluster_i
                    , str(i))

        y_lower = y_upper + 10

    ax1.set_title("The silhouette plot for the various clusters.")
    ax1.set_xlabel("The silhouette coefficient values")
    ax1.set_ylabel("Cluster label")
    ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
    ax1.set_yticks([])
    ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
    colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
    ax2.scatter(X[:, 0], X[:, 1]
                ,marker='o'
                ,s=8
                ,c=colors
                )
    centers = clusterer.cluster_centers_
    # Draw white circles at cluster centers
    ax2.scatter(centers[:, 0], centers[:, 1], marker='x',
    c="red", alpha=1, s=200)
    ax2.set_title("The visualization of the clustered data.")
    ax2.set_xlabel("Feature space for the 1st feature")
    ax2.set_ylabel("Feature space for the 2nd feature")
    plt.suptitle(("Silhouette analysis for KMeans clustering on sample data "
                  "with n_clusters = %d" % n_clusters),
    fontsize=14, fontweight='bold')
    plt.show()
可以看到分为2或者4簇的时候,轮廓系数都可以,且分出来的簇都大于了平均聚类系数。但是从图可以看到2的时候,黑色的聚类不好。这里主要根据不同的需求来选择。

3.2 重要参数init & random_state & n_init?

init:是用来帮助我们决定初始化方式的参数。在sklearn中,默认使用"k-means++"的方法,不需要修改。 ”k-means ++“初始化方案,使得初始质心(通常)彼此远离,以此来引导出比随机初始化更可靠的结果。

n_init:是每个随机数种子下运行的次数。这个参数不常用到,默认10次。

random_state:控制每次质心随机初始化的随机数种子。

3.3 重要参数max_iter & tol:在数据量很大的时候,需要用到

max_iter:整数,默认300,单次运行的k-means算法的最大迭代次数
tol:浮点数,默认1e-4,两次迭代间Inertia下降的量,如果两次迭代之间Inertia下降的值小于tol所设定的值,迭代就会停下。

3.4 重要属性和接口

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

推荐阅读更多精彩内容