聚类
在实际的生产生活中,当我们处理的数据集为 D = ( x i , y i ) D=(x_{i},y_{i}) D=(xi,yi)时,其中 y i y_{i} yi为 x i x_{i} xi对应的类标签,我们可以使用有监督的学习算法训练一个模型,使其不仅在现有的训练集上表现良好,而且在未知的数据上也可以达到接近正确分类的效果,相关的算法如SVM、LR、CNN……但是在更多的情况下,我们得到的数据集为 D = ( x i ) D=(x_{i}) D=(xi) ,它并没有标签,当然如果可行的话,我们也可以人工的进行的标注,然后在使用现有的有监督的分类算法进行后续的处理。但是这样做不仅会耗费大量的时间、人力和金钱,当数据集很大时显得不太可行。那么有没有算法可以在无标注的情况下,同样的可以按照一定的标准,将数据分成多个独立的部分呢?YEP~~聚类!
聚类算法按照“物以类聚,人以群分”的思想,将数据集中的样本划分为若干个不相交的子集,称为簇(cluster),使得簇内的样本相似性很高,而不同簇的样本之间相差很大。聚类算法的意义在于从大量的结构化数据和非结构化数据中提取价值。它允许你根据数据的属性或特性划分数据,并根据它们的相似性将它们分组到不同的簇中。
在正式介绍具体的聚类算法之前,首先来看两个相关的基本问题:性能度量和距离计算。
性能度量
当我们使用某一个聚类算法得到结果后,为了知道结果究竟是好还是坏,这就需要一定的标准来比较,这就是性能度量。它一般分为两类:
- 外部指标:即将聚类结果和某个外部的参考模型进行比较
- 内部指标:即直接考察聚类的结果而不使用任何的参考模型
对于外部指标而言,假设我们处理的数据集为 D = { x 1 , x 2 , … , x m } D=\{x_{1},x_{2}, …,x_{m}\} D={x1,x2,…,xm} ,通过某个聚类算法得到的簇划分为 C = { C 1 , C 2 , . . . , C m } C=\{C_{1},C_{2},...,C_{m}\} C={C1,C2,...,Cm} ,而所选的参考模型的划分标准结果为 C ∗ = { C 1 ∗ , C 2 ∗ , . . . , C m ∗ } C^* =\{C_{1}^*,C_{2}^*,...,C_{m}^*\} C∗={C1∗,C2∗,...,Cm∗},用 λ \lambda λ和 λ ∗ \lambda^* λ∗分别表示 C C C和 C ∗ C^* C∗的标记,则令
a = ∣ S S ∣ , S S = { λ i = λ j , λ i ∗ = λ j ∗ , i < j } b = ∣ S D ∣ , S D = { λ i = λ j , λ i ∗ ≠ λ j ∗ , i < j } c = ∣ D S ∣ , D S = { λ i ≠ λ j , λ i ∗ = λ j ∗ , i < j } d = ∣ D D ∣ , D D = { λ i ≠ λ j , λ i ∗ ≠ λ j ∗ , i < j } \begin{aligned} a &=|S S|, S S=\left\{\lambda_{i}=\lambda_{j}, \lambda_{i}^{*}=\lambda_{j}^{*}, i<j\right\} \\ b &=|S D|, S D=\left\{\lambda_{i}=\lambda_{j}, \lambda_{i}^{*} \neq \lambda_{j}^{*}, i<j\right\} \\ c &=|D S|, D S=\left\{\lambda_{i} \neq \lambda_{j}, \lambda_{i}^{*}=\lambda_{j}^{*}, i<j\right\} \\ d &=|D D|, D D=\left\{\lambda_{i} \neq \lambda_{j}, \lambda_{i}^{*} \neq \lambda_{j}^{*}, i<j\right\} \end{aligned} abcd=∣SS∣,SS={λi=λj,λi∗=λj∗,i<j}=∣SD∣,SD={λi=λj,λi∗=λj∗,i<j}=∣DS∣,DS={λi=λj,λi∗=λj∗,i<j}=∣DD∣,DD={λi=λj,λi∗=λj∗,i<j}
其中有 a + b + c + d = m ( m + 1 ) 2 a+b+c+d=\frac{m(m+1)}{2} a+b+c+d=2m(m+1)成立。那么就可以导出几个常用的外部指标:
- Jaccard Coefficient:
J C = a a + b + c J C=\frac{a}{a+b+c} JC=a+b+ca - Fowlkes and Mallows Index:
F M I = a a + b a a + c F M I=\sqrt{\frac{a}{a+b} \frac{a}{a+c}} FMI=a+baa+ca - Rand Index:
R I = 2 ( a + d ) m ( m − 1 ) R I=\frac{2(a+d)}{m(m-1)} RI=m(m−1)2(a+d)
其中取值均在 ( 0 , 1 ) (0,1) (0,1),显然值越大越好。
对于内部指标而言,定义如下的几个量:
- 簇内样本间的平均距离: avg ( C ) = 2 ∣ C ∣ ( ∣ C ∣ − 1 ) ∑ 1 ≤ i < j ≤ ∣ C ∣ d i s t ( x i , x j ) \operatorname{avg}(C)=\frac{2}{|C|(|C|-1)} \sum_{1 \leq i<j \leq|C|} d i s t\left(x_{i}, x_{j}\right) avg(C)=∣C∣(∣C∣−1)2∑1≤i<j≤∣C∣dist(xi,xj)
- 簇内样本间的最远距离: diam ( C ) = max 1 ≤ i < j ≤ ∣ C ∣ dist ( x i , x j ) \operatorname{diam}(C)=\max _{1 \leq i<j \leq|C|} \operatorname{dist}\left(x_{i}, x_{j}\right) diam(C)=max1≤i<j≤∣C∣dist(xi,xj)
- 簇内样本间的最短距离: d min ( C i , C j ) = min x i ∈ C i , x j ∈ C j d i s t ( x i , x j ) d_{\min }\left(C_{i}, C_{j}\right)=\min _{x_{i} \in C_{i}, x_{j} \in C_{j}} d i s t\left(x_{i}, x_{j}\right) dmin(Ci,Cj)=minxi∈Ci,xj∈Cjdist(xi,xj)
- 簇间样本间的中心点距离: d c e n ( C i , C j ) = dist ( μ i , μ j ) d_{c e n}\left(C_{i}, C_{j}\right)=\operatorname{dist}\left(\mu_{i}, \mu_{j}\right) dcen(Ci,Cj)=dist(μi,μj),其中 μ = 1 ∣ C ∣ ∑ 1 ≤ i ≤ ∣ C ∣ x i \mu=\frac{1}{|C|} \sum_{1 \leq i \leq|C|} x_{i} μ=∣C∣1∑1≤i≤∣C∣xi为具体某个簇 C C C的样本中心
使用上述定义的量,就可以导出几个常用的内部指标
- Davies-Bouldin Index:
D B I = 1 k ∑ 1 k max j ≠ i ( avg ( C i ) + avg ( C j ) d cen ( μ i , μ j ) ) D B I=\frac{1}{k} \sum_{1}^{k} \max _{j \neq i}\left(\frac{\operatorname{avg}\left(C_{i}\right)+\operatorname{avg}\left(C_{j}\right)}{d_{\operatorname{cen}}\left(\mu_{i}, \mu_{j}\right)}\right) DBI=k11∑kj=imax(dcen(μi,μj)avg(Ci)+avg(Cj)) - Dunn Index:
D B = min 1 ≤ i ≤ k { min j ≤ i { d min ( C i , C j ) max 1 < l < k diam ( C l ) ) } D B=\min _{1 \leq i \leq k}\left\{\min _{j \leq i}\left\{\frac{d_{\min }\left(C_{i}, C_{j}\right)}{\max _{1<l<k} \operatorname{diam}\left(C_{l}\right)}\right)\right\} DB=1≤i≤kmin{j≤imin{max1<l<kdiam(Cl)dmin(Ci,Cj))}
其中DBI值越小越好,而DB越大越好。
距离计算
距离计算用于在聚类的过程中衡量样本之间的相似性,常用的有如下几个:
- Minkowski distance:也就是最经典的马氏距离
dist m k ( x i , x j ) = ( ∑ u = 1 n ∣ x i u − x j u ∣ p ) 1 p \operatorname{dist}_{\mathrm{mk}}\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{j}\right)=\left(\sum_{u=1}^{n}\left|x_{i u}-x_{j u}\right|^{p}\right)^{\frac{1}{p}} distmk(xi,xj)=(u=1∑n∣xiu−xju∣p)p1 - Euclidean distance:当马氏距离中 p = 2 p=2 p=2的情况
( x i , x j ) = ∥ x i − x j ∥ 2 = ∑ u = 1 n ∣ x i u − x j u ∣ 2 \left(\boldsymbol{x}_{i}, \boldsymbol{x}_{j}\right)=\left\|\boldsymbol{x}_{i}-\boldsymbol{x}_{j}\right\|_{2}=\sqrt{\sum_{u=1}^{n}\left|x_{i u}-x_{j u}\right|^{2}} (xi,xj)=∥xi−xj∥2=u=1∑n∣xiu−xju∣2 - Manhattan distance:当马氏距离中 p = 1 p=1 p=1的情况
dist man ( x i , x j ) = ∥ x i − x j ∥ 1 = ∑ u = 1 n ∣ x i u − x j u ∣ \operatorname{dist}_{\operatorname{man}}\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{j}\right)=\left\|\boldsymbol{x}_{i}-\boldsymbol{x}_{j}\right\|_{1}=\sum_{u=1}^{n}\left|x_{i u}-x_{j u}\right| distman(xi,xj)=∥xi−xj∥1=u=1∑n∣xiu−xju∣
聚类算法
下面我们就来看几个基本的聚类算法:
- K-means clustering
- Mean-Shift Clustering
- Learning Vector Quantization
- Density-based Clustering
- Hierarchical Clustering
K-Means
K-means是最常用的聚类算法,它易于理解和实现,而且在大多数的情况下可以达到不错的效果。它的基本思想是:对于具体要处理的数据集,根据样本之间距离的大小,将样本划分为 K K K个簇,使得簇内的点尽可能紧密的连在一起,而不同簇之间的距离尽可能的大。因此,K-means的目标是最小化残差平方和(residual sum of squares) E E E:
E = ∑ i = 1 k ∑ x ∈ C i ∣ ∣ x − μ i ∣ ∣ 2 2 E = \sum\limits_{i=1}^k\sum\limits_{x \in C_i} ||x-\mu_i||_2^2 E=i=1∑kx∈Ci∑∣∣x−μi∣∣22
算法描述:
随机选取k个初始均值中心;
repeat:
对每个样本点,计算得到距其最近的均值中心,将其类别标为该均值中心所对应的簇;
重新计算k个簇对应的均值中心;
until 均值中心不再发生变化:
下面是一个 K = 3 K=3 K=3时的K-means的聚类过程,从动态图中可以看出,算法在不断优化残差平方和值的同时不断的修改簇中心的位置,经过十几步就已经可以取得不错的结果。
K-means算法的特点:
- 优点:简单,解释性好,可以自动将数据按照算法的距离度量方式将其划分到相应的簇中
- 缺点:聚类的结果依赖于初始 K K K的取值;最终可能受困于局部的最小值处;它是一种“硬划分”,即每一个数据只能属于某一个簇,而且只能属于某一个簇;对于离群点很敏感
而且K-means的均值中心大多数情况下并不是数据集中存在的数据点,因此如果我们使用中心点,即簇中样本最中心处的样本点,而不是使用簇中样本的均值作为中心点,就可到了K-Medoids算法,基本原理和K-means类似,所以不再介绍。
除了之前的各种分类和回归算法外,sklearn在Clustering部分对于各种常用的聚类算法也提供了很好的支持。
其中主要涉及以下的八大类聚类算法:最常用的K均值聚类、基于图的聚类(AP聚类和谱聚类)、基于滑动窗口的均值移动聚类、层次聚类(Ward hierarchical clustering、Agglomerative clustering)、密度聚类(DBSCAN、OPTICS,Birch)、高斯混合聚类。针对于具体所处理的数据类型和应用场景,用户可以选择最为合适的聚类算法。
K-means
k-means是一种最为简单且较为有效的聚类算法,关于算法的原理可见聚类或是查看sklearn中的说明。除了对于算法原理的介绍,文档中还具体指出了k-means算法的两大缺点:
- k-means可行建立在一个基本的假设之上,即算法要求簇是凸的且各向同性,当实际处理数据的流形是不规则形状时,k-means是无法正确进行处理的,此时就需要考虑使用其他对于数据的形状无严格要求的算法,如DBSCAN等
- 通常k-means在簇中心更新过程中所涉及的距离度量采用欧式距离,但当数据特征的维度很高时,距离的计算将难以处理。因此,当遇到这种情况时,通常需要使用PCA等降维算法对数据进行出来来缓解维度灾问题并加快计算
k-means算法效果的优劣收到簇数目 K K K和初始聚类中心选择的影响,为了缓解它们对于结果的影响便出现了K-means++ 。它改进了原始k-means对于聚类中心初始点的选择,从而使得初始的聚类中心之间的相互距离尽可能的远。
算法流程:
- 从输入数据中随机选择一个点作为第一个聚类中心 μ 1 \mu_{1} μ1
- 对于数据集中的其他每一个数据 x i x_{i} xi计算它与已选择的聚类中心中最近的聚类中心的距离 D ( x i ) = a r g m a x ∣ ∣ x i − μ r ∣ ∣ 2 2 , r = 1 , 2 , . . . , k s e l e c t e d D(x_{i}) = argmax||x_{i}-\mu_{r}||_{2}^2,r=1,2,...,k_{selected} D(xi)=argmax∣∣xi−μr∣∣22,r=1,2,...,kselected接着计算每个样本被选为下一个聚类中心的概率 p = D ( x ) 2 ∑ x ∈ X D ( x ) 2 p=\frac{D(x)^2}{\sum_{x \in X}D(x)^2} p=∑x∈XD(x)2D(x)2,最后按照轮盘法选择出下一个聚类中心
- 重复上述步骤知道选择出 K K K个聚类中心
另外为了解决k-means在大规模数据集上的使用集成了Mini-Batch K-Means。 它可以在能尽量保持聚类准确性下又大幅度降低计算时间。所谓的Mini Batch是指每次训练算法时随机抽取的数据子集,采用这些随机选取的数据进行训练,大大的减少了计算的时间,从而加快KMeans的收敛。相比较于传统的k-means,它的结果要略差一点。
k-means的实现类为sklearn.cluster.KMeans,它主要的参数如下:
- n_clusters:即簇的数目 K K K,默认为8
- init:即如何选择聚类中的初始点,常用的有random和k-means++,默认为后者
- n_init:采用不同的随机初始点执行算法的次数,它是为了缓解初始点的选择对于算法结果的影响,默认为10
- algorithm:有auto、full和elkan三种,默认为auto,即根据数据是否稀疏来决定如何选择full和elkan
- …
Mini-Batch K-Means的实现类为sklearn.cluster.MiniBatchKMeans,它的参数大部分了上面的sklearn.cluster.KMeans相同,另外值得注意的有如下几个参数:
- batch_size:即算法运行时所采样子集的大小,默认为100
- max_no_improvement:即连续运行多少了mini-batch后没有改善最后的聚类结果就停止运行,默认为10
- reassignment_ratio:即某个类别被重新复制的最大次数比例,主要是为了控制算法的运行时间,默认为0.01
- …
sklearn中提供了手写数字聚类的例子:
from time import time
import numpy as np
import matplotlib.pyplot as plt
from sklearn import metrics
from sklearn.cluster import KMeans
from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
from sklearn.preprocessing import scale
np.random.seed(42)
# 加载数据集
X_digits, y_digits = load_digits(return_X_y=True)
# 数据预处理
data = scale(X_digits)
n_samples, n_features = data.shape
n_digits = len(np.unique(y_digits))
labels = y_digits
sample_size = 300
print("n_digits: %d, \t n_samples %d, \t n_features %d"
% (n_digits, n_samples, n_features))
print(82 * '_')
print('init\t\ttime\tinertia\thomo\tcompl\tv-meas\tARI\tAMI\tsilhouette')
def bench_k_means(estimator, name, data):
t0 = time()
estimator.fit(data)
print('%-9s\t%.2fs\t%i\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f'
% (name, (time() - t0), estimator.inertia_,
metrics.homogeneity_score(labels, estimator.labels_),
metrics.completeness_score(labels, estimator.labels_),
metrics.v_measure_score(labels, estimator.labels_),
metrics.adjusted_rand_score(labels, estimator.labels_),
metrics.adjusted_mutual_info_score(labels, estimator.labels_),
metrics.silhouette_score(data, estimator.labels_,
metric='euclidean',
sample_size=sample_size)))
# 通过设置不同的参数来比较效果
bench_k_means(KMeans(init='k-means++', n_clusters=n_digits, n_init=10),
name="k-means++", data=data)
bench_k_means(KMeans(init='random', n_clusters=n_digits, n_init=10),
name="random", data=data)
# in this case the seeding of the centers is deterministic, hence we run the
# kmeans algorithm only once with n_init=1
pca = PCA(n_components=n_digits).fit(data)
bench_k_means(KMeans(init=pca.components_, n_clusters=n_digits, n_init=1),
name="PCA-based",
data=data)
print(82 * '_')
# #############################################################################
# Visualize the results on PCA-reduced data
reduced_data = PCA(n_components=2).fit_transform(data)
kmeans = KMeans(init='k-means++', n_clusters=n_digits, n_init=10)
kmeans.fit(reduced_data)
# Step size of the mesh. Decrease to increase the quality of the VQ.
h = .02 # point in the mesh [x_min, x_max]x[y_min, y_max].
# Plot the decision boundary. For that, we will assign a color to each
x_min, x_max = reduced_data[:, 0].min() - 1, reduced_data[:, 0].max() + 1
y_min, y_max = reduced_data[:, 1].min() - 1, reduced_data[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# Obtain labels for each point in mesh. Use last trained model.
Z = kmeans.predict(np.c_[xx.ravel(), yy.ravel()])
# Put the result into a color plot
Z = Z.reshape(xx.shape)
plt.figure(1)
plt.clf()
plt.imshow(Z, interpolation='nearest',
extent=(xx.min(), xx.max(), yy.min(), yy.max()),
cmap=plt.cm.Paired,
aspect='auto', origin='lower')
plt.plot(reduced_data[:, 0], reduced_data[:, 1], 'k.', markersize=2)
# Plot the centroids as a white X
centroids = kmeans.cluster_centers_
plt.scatter(centroids[:, 0], centroids[:, 1],
marker='x', s=169, linewidths=3,
color='w', zorder=10)
plt.title('K-means clustering on the digits dataset (PCA-reduced data)\n'
'Centroids are marked with white cross')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.xticks(())
plt.yticks(())
plt.show()
实验结果: