在《聚类的几个重要问题(理论篇)》中,我们主要讲解了聚类的现实意义、无序属性的距离度量方法、以及几种聚类算法的优劣程度,但我没有对聚类算法做出详细讲解,一方面是因为聚类算法一般都比较简单,另一方面则是聚类算法数量和种类都很多,我不得不以更为宏观讲解其中的重要问题。在本文中,我们会利用几种分布不同的数据来对不同的聚类算法做对比,以及同一聚类算法中,不同参数对聚类算法的影响。

首先,因为聚类的性能标准并不统一,通过数值比较也并不直观,所以我们可以自然的想到,如果我们处理一个分类数据,如果此类数据利用聚类的结果与类别一致,那么就可以说聚类达到了好的效果,通过特征空间的直观对比,我们就可以大致比较不同聚类算法的优劣。在这里,我们的假设是,相似的样本具有相同的类别。

我们构建样例数据:

from sklearn import cluster,datasets

import matplotlib.pyplot as plt

import seaborn as sns

n_samples=1000

X,y=datasets.make_blobs(n_samples=n_samples, random_state=42,centers=5,cluster_std=3)

sns.set(style='darkgrid')

for i,v,l in [[0,'r','class_0'],[1,'b','class_1'],[2,'g','class_2']]:

plt.scatter(X[y==i][:,0],X[y==i][:,1],c=v,label=l,s=15,edgecolor='k')

plt.legend()

plt.show()

如图,样本分布为三个明显的类,但某些蓝色点掺杂到了红色点里面,我们需要特别注意这些点。

我们利用K均值算法需要设置初始的均值向量,也要预先指定好类别数,然后迭代更新这些均值向量,我们指定类别数为3,随机选择均值向量:

from sklearn import datasets

import matplotlib.pyplot as plt

import seaborn as sns

from sklearn.cluster import KMeans

n_samples=1000

X,y=datasets.make_blobs(n_samples=n_samples,centers=3,random_state=42,cluster_std=3)

kmean=KMeans(n_clusters=3,init='random')

y_pre=kmean.fit_predict(X)

sns.set(style='darkgrid')

for i,v,l in [[0,'r','cluster_0'],[1,'b','cluster_1'],[2,'g','cluster_2']]:

plt.scatter(X[y_pre==i][:,0],X[y_pre==i][:,1],c=v,label=l,s=15,edgecolor='k')

plt.legend()

plt.title('Kmeans for Bolbs')

plt.show()

如图,聚类的结果大致上只是对于这三个类重新标记了颜色,但在特征空间的同样位置,我们发现原本发生‘’掺杂‘’的点,在利用Kmeans算法之后变得严格分离,这是非常简单就可以预料到的。

如果我们增加指定的类别数会发生什么呢?

.......

kmean=KMeans(n_clusters=4,init='random')

.......

如图,指定类别数4的Kmeans又强行把某一类拆分出两个类,从kmeans的角度来看,初始的均值向量个数有几个,我们就会分成几类,这也是kmeans算法的缺点之一。

那么初始均值向量该如何选择,因为初始化会对我们最后的结果造成一定的影响,因为初始的均值向量完全随机可能会将两个靠得很近的样本当作均值向量,那么这两个均值向量为中心的簇就可能会把本来属于同一类的样本强行划分为两个类。在实际应用中,我们会kmeans中插入一种叫做k-means++的技术,它改变了选择初始均值向量的方法,距离越大的点会有更大的概率被选择为进入最终kmeans的算法。

但我们目前使用的数据是凸形结构的,如果我们选用不是凸形结构的数据会如何呢?我们使用在《核技巧》中提到过的一种环形数据:

.......

X,y=datasets.make_circles(n_samples=n_samples, factor=.5,noise=.05)

......

我们利用kmeans算法适应这样的数据会产生什么效果呢?

......

kmean=KMeans(n_clusters=2,init='k-means++')

y_pre=kmean.fit_predict(X)

sns.set(style='darkgrid')

for i,v,l in [[0,'r','cluster_0'],[1,'b','cluster_1']]:

plt.scatter(X[y_pre==i][:,0],X[y_pre==i][:,1],c=v,label=l,s=15,edgecolor='k')

plt.legend()

plt.title('Kmeans for Circles')

plt.show()

如图,我们可以看到原本特征空间的环形并没有得到保持,而是分裂为上下两个类别,因为kmeans算法会把所有的数据都当作凸形结构来处理。

我们转用密度聚类来处理这个问题,以DBSCAN为例,我们不需要设置类别数,在这方面比kmeans更加灵活,但我们主要需要另外两个参数,一个叫maximum distance,它衡量的是样本要离的多近才能算作邻域,另一个叫做minimum samples,它衡量的是要包含多少个core point才能算作一个簇。此外,传统的kmeans还有一个缺点,只要我们制定了类别数,样本一定会被划分到一个类,如果我们的数据包含噪声的话,kmeans的效果就会受到很大影响。

表面上来看噪声项是离群点,但在DBSCAN中,可以被定义为无法由core point通过密度可达关系包含的点,这些点不会属于任何一个簇,我们构建DBSCAN,来观察其在circles数据上的表现:

from sklearn import datasets

import matplotlib.pyplot as plt

import seaborn as sns

from sklearn.cluster import KMeans

from sklearn.cluster import DBSCAN

n_samples=1000

X,y=datasets.make_circles(n_samples=n_samples, factor=.5,noise=.05)

dbscan=DBSCAN(eps=0.08)

y_pre=dbscan.fit_predict(X)

sns.set(style='darkgrid')

for i,v,l in [[0,'r','cluster_0'],[1,'b','cluster_1'],[-1,'y','Noisy Samples']]:

plt.scatter(X[y_pre==i][:,0],X[y_pre==i][:,1],c=v,label=l,s=15,edgecolor='k')

plt.legend()

plt.title('DBSCAN for Circles')

plt.show()

如图,DBSCAN可以良好的适应circles数据,并且将一些可能的噪声项(黄色点)做了标记。

但是,DBSCAN对参数非常敏感,我们在这里将邻域参数设置为了0.08,是希望足够小的邻域可以对类别有很好区分,但是太小的话,可能会将同一类的样本划到不同类,太大的话,可能会将不同的类当作一个类。如果对他进行微小调节,我们将会看到聚类结果发生很大的变化:

......

gammas=[0.01,0.08,0.2,5]

sns.set(style='darkgrid')

for k,j in enumerate(gammas):

dbscan=DBSCAN(eps=j)

y_pre=dbscan.fit_predict(X)

plt.subplot(len(gammas)/2,len(gammas)/2,k+1)

for i,v,l in [[0,'r','cluster_0'],[1,'b','cluster_1'],[-1,'y','Noisy Samples']]:

plt.scatter(X[y_pre==i][:,0],X[y_pre==i][:,1],c=v,label=l,s=15,edgecolor='k')

plt.title('$\gamma=$%s'%j)

plt.show()

如图,我们可以看到,当邻域太小的时候,以至于每个类都没有足够的密度可达对象,造成全部的样本都被认为是噪声;当邻域为合适的大小时,大体形状相似,但对噪声的识别会不太一样;而当邻域太大时,所有的点都被当作core points,所以所有的样本都会被当作一个类。

此外我们还可以利用层次聚类、网格聚类和概率模型聚类,但为了更好的理解降维与聚类的关系,我们主要尝试谱聚类的,谱聚类会对数据做降维,然后再利用kmeans的基本框架完成聚类,那么降维之后真的可以处理非凸结构类型的数据吗?

......

spectral=SpectralClustering(n_clusters=2,affinity='nearest_neighbors',eigen_solver='arpack')

y_pre=spectral.fit_predict(X)

sns.set(style='darkgrid')

for i,v,l in [[0,'r','cluster_0'],[1,'b','cluster_1']]:

plt.scatter(X[y_pre==i][:,0],X[y_pre==i][:,1],c=v,label=l,s=15,edgecolor='k')

plt.legend()

plt.title('Spectral Clusteringfor Circles')

plt.show()

如图,spectral clustering 也可以处理非凸结构的数据,我们得到了与DBSCAN类似的结果。

之所以我特意展示spectral clustering的威力,并不是它是最好的,因为根本不存在“最好”的算法,而是这里面反映出我们在前面的文章《核技巧(kernel trick)》中提到的将一个线性不可分(非凸)变为线性可分(凸),继续再做降维,spectral clustering正是利用了这一思想,我们将会在后面的文章中反复的利用前面的知识。

读芯君开扒

课堂TIPS

• 谱聚类利用的是一种叫做拉普拉斯特征映射的降维方法,本质上也是非线性的,所以才会有处理非凸结构的能力。

• 高斯混合模型这里并没有提到,但进行极大似然估计时,会有隐变量需要估计,我们会使用经典的EM算法处理。

• 很多聚类算法都有不同的变种,以k-means为例,为了解决其需要指定类别数的问题,我们可以把类别数目K当作超参数来处理,然后根据不同K下的silhouette Coefficient来确定K;为了解决初始化聚类中心的问题,除了k-means++,我们还会使用intelligent k-means;为了解决噪声点的影响,我们还有k-medians。

作者:唐僧不用海飞丝

如需转载,请后台留言,遵守转载规范

查看原文 >>
相关文章