KMeans
KMeans聚类算法也称k均值聚类算法,是集简单和经典于一身的基于距离的聚类算法。它采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。该算法认为类簇是由距离靠近的对象组成的,因此把得到紧凑且独立的簇作为最终目标。
KMeans聚类算法是一种迭代求解的聚类分析算法,其步骤是随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。
聚类算法在sklearn模块中有两种表现形式,一种是类,需要实例化,训练并使用接口和属性来调用结果。另一种是函数(function),只需要输入特征矩阵和超参数,即可返回聚类的结果和各种指标。
簇与质心
KMeans算法将一组N个样本的特征矩阵x划分为K个无交集的簇,直观上来看是簇是一组一组聚集在一起的数据,在一个簇中的数据就认为是同一类。簇就是聚类的结果表现。
簇中所有数据的均值 μ i {\mu_{i}} μi通常被称为这个簇的"质心”(centroids)。在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。
在KMeans算法中,簇的个数K是一个超参数,需要我们人为输入来确定。KMeans的核心任务就是根据我们设定好的K,找出K个最优的质心,并将离这些质心最近的数据分别分配到这些质心代表的簇中去。具体过程如下表所示:
那什么情况下,质心的位置会不再变化呢?当我们找到一个质心,在每次迭代中被分配到这个质心上的样本都是一致的,即每次新生成的筷都是一致的,所有的样本点都不会再从一个簇转移到另一个簇,质心就不会变化了。
簇内误差平方和
对于一个簇来说,所有样本点到质心的距离之和越小,我们就认为这个簇中的样本越相似,簇内差异就越小。而距离的衡量方法有多种,令
x
x
x表示簇中的一个样本点,
μ
{\mu}
μ表示该簇中的质心,
n
n
n表示每个样本点中的特征数目,
i
i
i表示组成点
x
x
x的每个特征,则该样本点到质心的距离可以由以下距离来度量:
欧
几
里
得
距
离
:
d
(
x
,
μ
)
=
∑
i
=
1
n
(
x
i
−
μ
i
)
2
曼
哈
顿
距
离
:
d
(
x
,
μ
)
=
∑
i
=
1
n
(
∣
x
i
−
μ
i
∣
)
余
弦
距
离
:
cos
θ
=
∑
i
=
1
n
(
x
i
∗
μ
i
)
∑
i
=
1
n
(
x
i
)
2
⋅
∑
i
=
1
n
(
μ
i
)
2
欧几里得距离:d(x,\mu) = \sqrt{\sum^{n}_{i=1}(x_{i}-\mu_{i})^{2}} \\曼哈顿距离:d(x,\mu) = \sum^{n}_{i=1}(\vert{x_{i}-\mu_{i}}\vert) \\余弦距离:\cos\theta = \frac{\sum^{n}_{i=1}(x_{i}*\mu_{i})}{\sqrt{\sum^{n}_{i=1}(x_{i})^{2}\cdot}\sqrt{\sum^{n}_{i=1}(\mu_{i})^{2}}}
欧几里得距离:d(x,μ)=i=1∑n(xi−μi)2曼哈顿距离:d(x,μ)=i=1∑n(∣xi−μi∣)余弦距离:cosθ=∑i=1n(xi)2⋅∑i=1n(μi)2∑i=1n(xi∗μi)
如采用欧几里得距离,则一个簇中所有样本点到质心的距离的平方和为:
C
l
u
s
t
e
r
S
u
m
o
f
S
q
u
a
r
e
(
C
S
S
)
=
∑
j
=
0
m
∑
i
=
1
n
(
x
i
−
μ
i
)
2
T
o
t
a
l
C
l
u
s
t
e
r
S
u
m
o
f
S
q
u
a
r
e
=
∑
l
=
1
k
C
S
S
l
Cluster\space Sum \space of \space Square (CSS)=\sum^{m}_{j=0}\sum^{n}_{i=1}(x_{i}-\mu_{i})^{2} \\ Total\space Cluster\space Sum \space of \space Square=\sum^{k}_{l=1}CSS_{l}
Cluster Sum of Square(CSS)=j=0∑mi=1∑n(xi−μi)2Total Cluster Sum of Square=l=1∑kCSSl
其中,
m
m
m为一个簇中样本的个数,
j
j
j是每个样本的编号。这个公式被称为簇内平方和(cluster Sum of Square),又叫做Inertia。而将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和(Total Cluster Sum of Square),又叫做total inertia。Total lnertia越小,代表着每个簇内样本越相似,聚类的效果就越好。因此KMeans追求的是,求解能够让Inertia最小化的质心。实际上,在质心不断变化不断迭代的过程中,总体平方和是越来越小的。我们可以使用数学来证明,当整体平方和最小的时候,质心就不再发生变化了。如此,KMeans的求解过程,就变成了一个最优化问题。
Inertia是基于欧几里得距离的计算公式得来的。实际上,我们也可以使用其他距离,每个距离都有自己对应的Inertia。在过去的经验中,我们总结出不同距离所对应的质心选择方法和Inertia,在KMeans中,只要使用了正确的质心和距离组合.无论使用什么样的距离.都可以达到不错的聚类效果:
核心class
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=1, algorithm='auto')
其中,n_clusters是KMeans中的k,表示模型需要分几个类别。
简单代码实现
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
# 创建数据集
X, y = make_blobs(n_samples=500, n_features=2, centers=4,
random_state=1
)
# 数据可视化
colors = ['red', 'pink', 'orange', 'gray']
fig, ax1 = plt.subplots(1)
for i in range(4):
ax1.scatter(X[y == i, 0], X[y == i, 1]
, marker='o' # 点的形状
, s=8 # 点的大小
, c=colors[i]
)
plt.show()
上图是数据的真实分布情况,接下来使用KMeans对数据进行聚类!
# 将数据聚类成三类
from sklearn.cluster import KMeans
n_clusters = 3
cluster = KMeans(n_clusters=n_clusters, random_state=1).fit(X)
# labels_属性是用来查看聚类完成后的类别,每个样本对应一个类
y_pred = cluster.labels_
print(y_pred)
# KMeans因为并不需要建立模型或者预测结果,因此只需要fit就能得到聚类结果
# 然而KMeans也有predict和fit_predict接口,表示学习数据X并对X的类进行预测,
# 结果与label_一致
pre = cluster.fit_predict(X)
# cluster_centers_属性用来查看质心
centroid = cluster.cluster_centers_
print(centroid)
# inertia_属性是用来查看总距离平方和的
inertia = cluster.inertia_
print(inertia)
# 聚类数据可视化
colors = ['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]
, marker='o' # 点的形状
, s=8 # 点的大小
, c=colors[i]
)
ax1.scatter(centroid[:, 0], centroid[:, 1]
, marker="x"
, s=15
, c="black"
)
plt.show()
然而在实际中,我们也许并不清楚将数据分成多少个类才合适,就如上述代码所示(选择将真实的4类数据分成3类),当我们不确定将数据分成多少类时,就只能尝试多种情况。为此,为找到合适的分类参数值,我们需要对模型建立评估指标。
模型评估指标
KMeans的目标是确保"簇内差异小,簇外差异大",因此,我们可以通过衡量簇内差异来衡量聚类的效果。而Inertia虽然是用距离来衡量簇内差异的指标,但是这个指标的缺点和极限太大,所以并不适合,其具体缺点在于:
- 它不是有界的,Inertia是越小越好,是0最好,但我们不知道,一个较小的Inertia究竟有没有达到模型的极限,能否继续提高。
- 它的计算太容易受到特征数目的影响,数据维度很大的时候,Inertia的计算量会陷入维度诅咒之中,计算量会爆炸,不适合用来一次次评估模型。
- Inertia对数据的分布有假设,它假设数据满足凸分布(即数据在二维平面图像上看起来是一个凸函数的样子),并且它假设数据是各向同性的 (isotropic),即是说数据的属性在不同方向上代表着相同的含义。但是现实中的数据往往不是这样。所以使用Inertia作为评估指标,会让聚类算法在一些细长簇,环形簇,或者不规则形状的流形时表现不佳。
轮廓系数
在样本的真实标签未知的情况下,聚类是完全依赖于评价簇内的稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果,其中轮廓系数是最常用的聚类算法的评价指标。它是对每个样本来定义的,它能够同时衡量:
- 样本与其自身所在的簇中的其他样本的相似度 a a a,等于样本与同一簇中所有其他点之间的平均距离
- 样本与其他簇中的样本的相似度 b b b,等于样本与下一个最近的簇中得所有点之间的平均距离
根据聚类的要求簇内差异小,簇外差异大",希望
b
b
b永远大于
a
a
a,并且大得越多越好。于是单个样本的轮廓系数计算为:
s
=
b
−
a
m
a
x
(
a
,
b
)
s = \frac{b-a}{max(a,b)}
s=max(a,b)b−a
很容易理解轮廓系数范围是(-1,1),其中值越接近1表示样本与自己所在的簇中的样本很相似,并且与其他簇中的样本不相似,当样本点与簇外的样本更相似的时候,轮廓系数就为负。当轮廓系数为0时,则代表两个簇中的样本相似度一致,两个簇本应该是一个簇。
如果一个簇中的大多数样本具有比较高的轮廓系数,则簇会有较高的总轮廓系数,整个数据集的平均轮廓系数也越高,则聚类是合适的。如果许多样本点具有低轮廓系数甚至负值,则聚类是不合适的,聚类的超参数K可能设定得太大或者太小。
在sklearn中,我们使用模块metrics中的类silhouette_score来计算轮廓系数,它返回的是一个数据集中,所有样本的轮廓系数的均值。但我们还有同在metrics模块中的silhouette_sample_,它的参数与轮廓系数一致,但返回的是数据集中每个样本自己的轮廓系数。
from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples
print(silhouette_score(X, y_pred))
print(silhouette_samples(X, y_pred))
基于轮廓系数选择n_clusters
import numpy as np
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples
# 创建数据集
X, y = make_blobs(n_samples=500, n_features=2, centers=4, random_state=1)
for n_clusters in [2, 3, 4, 5, 6]:
n_clusters = n_clusters
# 创建一个画布,画布上共有一行两列两个图
fig, (ax1, ax2) = plt.subplots(1, 2)
# 画布尺寸
fig.set_size_inches(18, 7)
# 第一个图是轮廓系数图像,是由各个簇的轮廓系数组成的横向条形图
# 轮廓系数的取值是在[-1, 1]之间,但是横坐标太长不利于可视化
ax1.set_xlim([-0.1, 1])
ax1.set_ylim([0, X.shape[0] + (n_clusters + 1) * 10])
# 模型建立
cluster = KMeans(n_clusters=n_clusters, random_state=1).fit(X)
cluster_labels = cluster.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轴上的初始取值
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.set_title("The silhouette plot for the various cluster")
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])
y_lower = y_upper
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(X[:, 0], X[:, 1]
, marker='o'
, s=8
, c=colors
)
center = cluster.cluster_centers_
ax2.scatter(center[:, 0], center[:, 1]
, marker='x'
, c='red'
, alpha=1
, s=200
)
ax2.set_title("The visualization of the clustered data")
ax2.set_xlabel("Feature space of the 1st feature")
ax2.set_ylabel("Feature space of the 2nd feature")
plt.suptitle(("Silhouette analysis for KMeans clustering on sample data"),
fontsize=14,
fontweight='bold'
)
plt.show()
从图中分析可知,当分类为4时效果最佳!