书接上一回,对于线性不可分的数据,怎么用KMeans来做聚类。

数据

make_moons来生成一个比较复杂的流形数据。

from sklearn.datasets import make_moons
 
X, y = make_moons(200, noise=0.05, random_state=0)

画图看一下,数据是长这样子的:

import matplotlib.pyplot as plt
 
plt.scatter(X[:, 0], X[:, 1], s=50, c=y, cmap='viridis')

简单的KMeans是分不好的

from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2)
labels = kmeans.fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=labels, s=50, cmap='viridis')

我们用KMeans来聚类,然后用聚类结果进行上色:

效果只能说不行。

t-SNE + KMeans

做过单细胞分析的,都知道t-SNE,当然你也可以试试UMAP,效果应该差不多。t-SNE擅长捕捉局部结构,就特别适合这种流形数据。它在高维空间用高斯分布计算样本间的相似度,然后用t分布匹配高维概率分布,通过梯度下降优化低维嵌入,优化目标是最小化高维和低维概率分布的KL散度。

我们对数据先进行标准化:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

再进行t-SNE的变换:

from sklearn.manifold import TSNE
 
 
X_tsne = TSNE(n_components=2, perplexity=30, random_state=42).fit_transform(X_scaled)
y_tsne_kmeans = KMeans(n_clusters=2).fit_predict(X_tsne)

这时候,我们可以对变换后的数据进行可视化:

plt.scatter(X_tsne[:,0], X_tsne[:,1], c=y, cmap='viridis', s=50)

非常好地保留了原来的分类信息,然后这个数据就成了线性可分了。

那么我们可以可视化原始的数据,并用KMeans聚类结果进行上色,看看聚类效果如何:

plt.scatter(X[:, 0], X[:, 1], c=y_tsne_kmeans, s=50, cmap='viridis')

谱聚类

谱聚类本质上就是核KMeans的优化版本,就是上次说的应用核函数再加KMeans,当然如果你自己这么简单做了,效果可能不太好,因为谱聚类做得更多,所以更加鲁棒。它先计算一个相似度矩阵(可以通过高斯核函数+欧式距离,也可以通过KNN连接矩阵),再构建拉普拉斯矩阵对相似度矩阵进行归一化,再计算特征向量(本质就是低维嵌入),然后对特征向量运行KMeans

from sklearn.cluster import KMeans, SpectralClustering
spectral = SpectralClustering(n_clusters=2, 
                             affinity='rbf',
                             gamma=15,
                             random_state=42)
y_spectral = spectral.fit_predict(X_scaled)
plt.scatter(X[:, 0], X[:, 1], c=y_spectral, s=50, cmap='viridis')

这里我就用了affinity='rbf'也就是高斯核函数,gamma这个参数控制高斯核函数的宽度,数据复杂的情况下,gamma就可以调大一点试试。最后的聚类结果效果就非常好。