![268bbfcb2e4a3a193771d2d4cc880d31.png](https://i-blog.csdnimg.cn/blog_migrate/18e71836c178b21c4303a76df906a812.png)
作者:汤进
本文详细阐述了聚类中的一种算法--谱聚类,并通过代码实现展示了谱聚类的所有细节,将实践和理论紧密的结合起来,值得细细把玩。
在聚类与K-Means一文中,ARGO就非监督学习的聚类算法做了比较系统的介绍,在其中提到了用图聚类的算法-谱聚类,这次ARGO将详细聊聊这个算法。本文将分以下几个部分进行介绍:
- 基础概念
- 怎么度量样本间的相似程度
- 谱聚类的优化目标
- 算法步骤总结
- 实例
一 基础概念
聚类的概念是指,将多维空间中的样本分为多个组,使得组内的样本尽可能相似,组间样本尽可能不同。有K-means,谱聚类,DBSCAN等多种聚类算法。
谱聚类是图论演变而来的聚类算法。将所有样本点
对于n维空间内,一个有m个顶点的图,它的顶点组成矩阵:
![b7107fe09e4a126b70902c8faa36ac95.png](https://i-blog.csdnimg.cn/blog_migrate/ca8000ae8c9064026fa1e5d2f9cf32a9.png)
这样的图,它的邻接矩阵是一个m×m矩阵:
![912e6d2ef0cccdbb1a6bc71c8272f939.png](https://i-blog.csdnimg.cn/blog_migrate/405030e7fb8c15f7c44b28a0a9acd961.png)
之后,谱聚类算法将这样的图切分成k个子图,使得子图间的权重尽可能小,子图内的权重尽可能大,而这k个子图就是聚类生成的k个类。可是如何才能建立上述的邻接矩阵图呢?这得从描述样本间的相似程度说起。
二 如何度量样本间的相似程度
K-means算法使用欧式距离度量样本间的相似度,距离越小相似程度越大。与此类似, 谱聚类使用边的权重度量样本的相似程度,权重越大相似度越大。所以谱聚类的关键是计算出样本间的边的权重所使用的方法。谱聚类的相似度量方式有很多,常见的方法有两种,一是高斯核函数,一是K近邻方式。
高斯核函数
通常使用高斯核函数RBF将欧式距离转换为权重,以度量相似程度。 举一个简单的例子,假设空间中有10个顶点如下图所示:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0, 10, 1)
y = np.zeros(x.shape)
fig, ax = plt.subplots()
fig.set_size_inches(5, 5)
ax.scatter(x, y)
for i in range(10):
ax.annotate('$x_{%d}$' % (i+1), xy=(x[i], y[i]), xytext=(x[i]+0.05, y[i]+0.001))
# Hide the right and top spines
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.show()
![6bae286bc07bf3081ff29cb3214f735e.png](https://i-blog.csdnimg.cn/blog_migrate/411f7e9b5ee1e0981e08a70cdf14cc3a.png)
对于
结果如下图所示:
fig, rbf = plt.subplots()
fig.set_size_inches(6, 6)
x1 = [np.exp(-np.square(x[i]-x[5])/2) for i in range(10)]
xi = np.linspace(x.min(),x.max(),300)
from scipy.interpolate import spline
yi = spline(x,x1,xi)
rbf.plot( xi, yi )
for i in range(10):
rbf.annotate('$w_{6%d}$' % (i+1), xy=(x[i], x1[i]), xytext=(x[i]+0.05, x1[i]+0.001))
rbf.spines['right'].set_visible(False)
rbf.spines['top'].set_visible(False)
rbf.set_xticks([i for i in range(10)])
rbf.set_xticklabels(['$x_{%d}$' % (i+1) for i in range(10)])
rbf.set_ylabel('$w_6$')
plt.show()
![bec9bd7db28ef27395a28b45f3a851d9.png](https://i-blog.csdnimg.cn/blog_migrate/42912d2728ac2b1b7adb32c4d4876afe.png)
可以看出,与
K近邻法
除此之外,还可以使用K近邻法度量相似程度,对于某个顶点,找出距离它最近的k个顶点,这k个顶点的权重设为1,其它的权重设为0。
高斯核函数和K近邻法在scikit-learn的谱聚类算法都已经被支持,在本文最终的例子中,使用K近邻法作用相似程度度量方法计算邻接矩阵。
三 谱聚类的优化目标
谱聚类的目标是聚类之后,子图间权重尽可能小,子图内权重尽可能大。可以用
![63a4e039102287b8757a3c3ed90197e0.png](https://i-blog.csdnimg.cn/blog_migrate/f824e366893391238e4f749a754b461d.png)
表示聚类之后得到k个组,即k个子图。对于某个子图
对于两个子图A和B,定义A与B间的权重:
![9a4efeb4eef8d8454c48ac71e216ff97.png](https://i-blog.csdnimg.cn/blog_migrate/986ef15cf84d660fcbcaf88d666ffa9e.png)
则
![d23871cd78bdd47aecced9d193ce5ce4.png](https://i-blog.csdnimg.cn/blog_migrate/7abad3045d89ff57cab190974a108945.png)
进而得到样本集合的度矩阵
![7365448a13f3bdaccb4768fb3bd24114.png](https://i-blog.csdnimg.cn/blog_migrate/c669a318688e0fa67d0efcbd9d6e24d8.png)
对于子图
![5fcb0092d09acf8e909e5cd07f8ee479.png](https://i-blog.csdnimg.cn/blog_migrate/ef7237a8dce3ca17905ac19dbcb4d8e1.png)
对于子图
![dd3dcbb81290a47b620968ccdb38cd5c.png](https://i-blog.csdnimg.cn/blog_migrate/12ef0f5b0c73afc19bcaafc9b27e02ed.png)
以子图
![2db1e4ae26ead84477c22a7a5a3b7e63.png](https://i-blog.csdnimg.cn/blog_migrate/1cbcd6c8fe7f036cb4972d511708688a.png)
将
![6caa2a93e57cc1d88d26a8f0420c2a40.png](https://i-blog.csdnimg.cn/blog_migrate/6e0f4689878e7f0ab0738bdf2550e974.png)
代入可得:
![4ed9af46d5db258d4c32afb15309b9f8.png](https://i-blog.csdnimg.cn/blog_migrate/26aefef6ab74db1e9502efd4df258e71.jpeg)
令
![6d78b534767ce475b686a5a21f25aa99.png](https://i-blog.csdnimg.cn/blog_migrate/dcf328a45a2d2ffdab52e25f9bdc3504.png)
对k个子图的优化目标进行求和,那么对于整个图,优化目标可以表示为
![00e60504d5b8d0888053b7c9da14b773.png](https://i-blog.csdnimg.cn/blog_migrate/6c3487f418086a89b6e9d032931b3b11.png)
于是我们对于整张图,我们的优化目标变成了:
![bb8cf96aef28b11de68a07c353fbe420.png](https://i-blog.csdnimg.cn/blog_migrate/68ffcc01e6ebee5ceee353396730eb20.png)
考虑到指示向量h与度d的定义,可以得到:
![52d7ca3e2e8175e0efcbba2b78801dd5.png](https://i-blog.csdnimg.cn/blog_migrate/11873fafd1165660393fa03681ccb511.png)
而这个式子很眼熟,与主成分分析PCA的优化目标仅仅的差异在于约束条件多了一个D,接下来把差异的部分去掉:
![0a30fea465912a66f66232d62937fe6a.png](https://i-blog.csdnimg.cn/blog_migrate/efa1584d05c9e73b48c8ad7c739e9bb4.png)
优化条件可转化为
![815b556c2c1e9fdf2260121d63936098.png](https://i-blog.csdnimg.cn/blog_migrate/6b31bfc08302417208a637691224995c.png)
将
四 算法步骤总结
根据前面的分析,可以总结出,算法的步骤如下:
- 计算出邻接矩阵W
- 根据W,计算出度矩阵D
- 根据D与W,计算出拉普拉斯矩阵L
- 根据L与D,计算出
- 求出B最小的k个特征值对应的k个特征向量组成的矩阵F
- 对F进行聚类(如k-means),得到最终的结果
五 实例
以scikit-learn众多聚类实例中的圆圈分类为例。有1500个样本,样本分布如图所示:
import time
import warnings
import numpy as np
import matplotlib.pyplot as plt
from sklearn import cluster, datasets, mixture
from sklearn.preprocessing import StandardScaler
from itertools import cycle, islice
np.random.seed(0)
n_samples = 1500
noisy_circles = datasets.make_circles(n_samples=n_samples, factor=.5,
noise=.05)[0]
x1 = noisy_circles[:,0]
x2 = noisy_circles[:,1]
originX = np.array([x1, x2]).T
fig, ax = plt.subplots()
fig.set_size_inches(4, 4)
ax.scatter(x1, x2)
![b6de5f282306e7e9df468331e8f11a24.png](https://i-blog.csdnimg.cn/blog_migrate/74f102374f3fc7ade53757541ff72bc0.png)
现在需要对数据集进行聚类。可以从分布图看出,样本聚集在2个同心圆上。所以,理想的分类结果应该是聚成2类,每个圆上的样本聚成一类。
按照谱聚类的步骤,先算出邻接矩阵W:
from sklearn.neighbors import kneighbors_graph
connectivity = kneighbors_graph(originX, n_neighbors=10, include_self=False, mode = 'distance')
W = 0.5 * (connectivity + connectivity.T)
W = W.toarray()
利用邻接矩阵W,计算出度矩阵D:
D = np.zeros(W.shape)
for i in range(W.shape[0]):
for j in range(W.shape[0]):
D[i, i] = D[i, i]+W[i,j]
使用W与D,计算出拉普拉斯矩阵L:
L = D-W
计算
tmp = np.zeros(D.shape)
for i in range(tmp.shape[0]):
tmp[i,i] = 1.0/np.sqrt(D[i,i])
B = tmp@L@tmp
for i in range(B.shape[0]):
for j in range(B.shape[1]):
B[i,j] = '%.9f'%(B[i,j])
求出B的2(k=2)个最小的特征值,即这2个特征对应的特征向量组成的矩阵F:
from numpy.linalg import eig
eigenvalue,eigenvector=eig(B)
first = np.where(eigenvalue == np.min(eigenvalue))[0][0]
second = np.where(eigenvalue == np.max(eigenvalue))[0][0]
for i in range(eigenvalue.shape[0]):
if i == first:
continue;
if eigenvalue[i]<eigenvalue[second]:
second=i;
F = np.array([eigenvector[:,first], eigenvector[:,second]], np.float64).T
最后,用K-Means对F中1500个样本进行聚类,结果如图所示:
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2, random_state=0).fit(F)
colors = []
for i in range(kmeans.labels_.shape[0]):
if kmeans.labels_[i] == 1:
colors.append('red')
else:
colors.append('blue')
fig, ax = plt.subplots()
fig.set_size_inches(4, 4)
ax.scatter(x1, x2, c=colors, marker='o')
![5c4c611dc4484323c09411df52cca720.png](https://i-blog.csdnimg.cn/blog_migrate/607137db9ce58bdbf742fc88887515c6.png)
从图中可以看出,算法成功地将样本聚成2类,每一类分别聚集在2个圆上,与我们期待的结果一致。 实际上,scikit-learn已经封装好了算法的所有步骤,在实际使用的时候,只需要调用scikit-learn的SpectralClustering类就行了,以下是scikit-learn的计算方法:
from sklearn.cluster import SpectralClustering
originX = StandardScaler().fit_transform(originX)
clustering = SpectralClustering(n_clusters=2,
eigen_solver='arpack',
affinity="nearest_neighbors").fit(originX)
colors = []
for i in range(clustering.labels_.shape[0]):
if clustering.labels_[i] == 1:
colors.append('red')
else:
colors.append('blue')
fig, ax = plt.subplots()
fig.set_size_inches(4, 4)
ax.scatter(x1, x2, c=colors, marker='o')
最终的聚类结果如下:
![00f9e8d03226bd93cc01549c7ec1f618.png](https://i-blog.csdnimg.cn/blog_migrate/6fbc281a057c42f0552b441270215a2a.png)
今天我们的谱聚类就介绍完了。 ARGO,专注AI与金融,用Fintech改变世界。欢迎大家关注我们,与ARGO一同进步。
![57522fce775c351fc66bad18a07f7694.png](https://i-blog.csdnimg.cn/blog_migrate/c624cfd787bafedbdd95710f383f86ff.png)