在这篇文章中我将介绍无监督算法中“聚类”部分的知识,其中关于K均值聚类、层次聚类、密度聚类部分我将各附上一份实际运用的代码,而其余的像学习向量量化、高斯混合聚类部分则只是简单介绍下概念。
一、 关于聚类
首先我先简单介绍下聚类算法有关的东西。
1.1 聚类任务
我们知道聚类就是试图把数据集中的样本划分为若干个不相交的自己,而这样形成的每一个自己都被称为一个“簇”。不过算法只能自动地形成簇结构,至于形成的这个簇的概念还需要使用者自己去命名定义。(所以我在思考,这里如果在形成了簇的基础上添加一个新的算法,像图像识别、自然语言处理等方法,应该就可以做到形成簇与自动命名簇的一体化了,如流水线那样。在之后我将对之用代码尝试实现下,然后会一并些出对应的文章来)
1.2 性能度量
聚类的性能度量亦称为聚类“有效性指标”。在聚类中,我们希望它能物以类聚,要让同一簇中的样本间尽可能相似,不同簇的尽可能不同。因此可换种说法为:簇内相似度高,簇间相似度低。
所以关于聚类的性能度量有两类:一类是“外部指标”,即将聚类结果与某参考模型去比较,而另一类则是“内部指标”,即直接去考察聚类结果而不必依靠任何的参考模型。
关于外部指标有:Jaccard系数、FM指数、Rand指数。
关于内部指标有:DB指数、Dunn指数。
1.3 距离计算
距离算法同KNN算法有些地方有些相似,比如在距离计算上,这二者都需要进行距离的计算。
在聚类算法中,距离计算是一个关键的步骤,这是因为聚类本质上是将数据点分组成具有相似特征的群集。为了实现这一点,算法需要一种度量方法来判断数据点之间的相似程度。距离计算就是这样一个度量方法,它用于量化数据点之间的相似性或差异性。
我们定义函数为一个度量距离的函数,那么对于样本:
就可以用如下几种方法来度量计算:
闵可夫斯基距离
我们最常用的距离就是闵可夫斯基距离,其公式可以表示为:
K均值算法可以使用不同形式的闵可夫斯基距离来计算数据点与质心之间的距离,从而将数据点分配到最近的簇。而在需要调整距离度量的情况下,可以使用闵可夫斯基距离,并通过调整 pp 参数来适应不同的数据特征。
欧式距离
当p=2时,就可以得到欧式距离,公式如下:
K均值算法通常使用欧几里得距离来计算数据点与质心之间的距离,从而将数据点分配到最近的簇。在凝聚层次聚类中,可以使用欧几里得距离来计算簇与簇之间的距离。
曼哈顿距离
当p=1时,则可以得到曼哈顿距离,公式如下:
虽然K均值通常使用欧几里得距离,但在某些情况下,也可以使用曼哈顿距离来计算数据点与质心之间的距离。在某些特定的应用场景中,曼哈顿距离可能更适合,例如在城市道路网络中计算两点之间的距离。
切比雪夫距离
当p趋近于无穷大时就可以得到切比雪夫距离。
如果数据点的移动受到限制,只能沿着垂直或水平方向移动,那么切比雪夫距离可能更适合。对于某些特定的数据集,如果只需要考虑最大差异而不是综合差异,那么切比雪夫距离可能更有意义。
二、 K均值(K-means)聚类
2.1 概念
K均值聚类(K-Means Clustering)是一种常用的无监督学习算法,用于将数据集中的对象分成K个簇,使得簇内的数据点彼此相似,而簇间的数据点则尽可能不同。K均值算法的核心思想是采用了贪心策略,通过迭代的方式找到数据的最佳划分。
2.2 相关基本概念
簇
簇是指一组数据点,这些数据点在某些特征上彼此相似。K均值算法的目标是将数据集划分为K个簇。
质心
每个簇都有一个质心(中心点),它是簇内所有数据点的平均值。质心通常表示为一个向量,其维度与数据点相同。
K值
K是簇的数量,需要在算法开始之前确定。K值的选择通常基于领域知识或者通过交叉验证等方法确定。
2.3 基本步骤
在这个算法中,其步骤可以说是有四步,分别为:初始化质心、分配数据点、更新质心、迭代。
其中在初始化质心中,是要先随机选择K个点作为初始质心,这些点可以是原本就有的,也可以是随机生成的;然后在分配过程中,计算每个数据点到各个质心的距离(通常使用欧几里得距离),并将每个数据点分配给最近的质心所代表的簇;接下来在更新的过程中,对于每个簇,重新计算其质心。新的质心是簇内所有数据点的平均值。
在这里我可以给出一个伪代码来:
function k_means(X, K, max_iterations):
Initialize centroids randomly or using some heuristic
for iteration in range(max_iterations):
# 分配数据点
clusters = assign_to_nearest_centroid(X, centroids)
# 更新质心
new_centroids = update_centroids(X, clusters)
# 如果质心没有显著变化,则退出循环
if centroids_converged(centroids, new_centroids):
break
centroids = new_centroids
return clusters, centroids
2.4 python实例
然后这是实际运用的一份代码,先通过sklearn中的函数make_blobs来随机生成些点,然后再用聚类算法,代码如下:
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# 生成数据
X, y = make_blobs(random_state=42, centers=3)
# 构建数据集
kmeans = KMeans(n_clusters=3, n_init=10, random_state=42)
kmeans.fit(X)
assignments = kmeans.labels_
print(assignments)
# 结果分数
score = silhouette_score(X, assignments)
print("The score is: {}".format(score))
# 可视化
plt.figure()
plt.scatter(X[:,0], X[:,1], c=kmeans.labels_, cmap='viridis', marker='o',
label="Training Data")
plt.scatter(kmeans.cluster_centers_[:,0], kmeans.cluster_centers_[:,1], s=300, c='red',
label="Centroids")
plt.title("KMeans Clustering")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.legend()
plt.show()
输出为:
[1 2 0 2 1 2 0 2 2 0 0 1 1 0 0 1 1 0 1 1 0 1 1 0 0 0 2 1 1 1 1 2 2 1 0 0 0
0 2 2 1 0 2 0 0 2 1 1 1 2 2 2 0 1 1 1 0 0 2 0 1 2 1 2 1 1 2 1 2 2 2 1 1 0
2 1 2 1 2 2 0 2 0 1 0 0 0 2 0 2 2 2 0 2 0 0 0 2 1 0]
The score is: 0.8469881221532085
其画出的图像为:
三、 层次聚类
3.1 概念
层次聚类(Hierarchical Clustering)是试图在不同层次上对于数据集进行划分,从而形成一种树形的聚类结构。其中关于数据集的划分可以采用“自底向上”的聚合策略,也可以采用“自顶向下”的分拆策略。
3.2 个人想法
对于参差聚类这种思想与方法,我感觉它和递归与动态规划有几分相似处。因为:
与递归比较:
层次聚类算法生成的层次树本身就是一个递归结构。每一层的簇可以进一步细分为更小的簇,直到到达原始数据点为止。这种结构类似于递归算法中的递归调用,每一层的簇都可以看作是更高层次簇的一部分。
然后在过程上,在凝聚层次聚类(Agglomerative Hierarchical Clustering)中,算法从每个数据点作为一个簇开始,然后逐步合并最相似的簇,直到达到所需的簇数或形成一个单一的大簇。这个过程可以看作是一个递归过程,每次合并实际上是在构建层次树的一层。
其次可以说层次聚类算法遵循分治策略,将大问题分解为小问题(即较小的簇),然后逐步合并这些小簇。而这与递归算法的分治策略也极为相似,都是大化小,再合成。
与动态规划比较:
我们知道动态规划有一个特点,就是将子问题进行记忆然后在需要时重用,以此来避免重复的计算。那么我们看回参差聚类中去,在层次聚类中,每次合并簇时,都会计算新的簇之间的距离。这些计算结果可以存储起来,以便在后续的合并步骤中重用,从而减少重复计算。
动态规划通常用于求解全局最优解问题,通过自底向上或自顶向下的方式逐步构建解决方案。层次聚类算法通过逐步合并簇来构建层次树,每次合并都选择最优的簇对进行合并,从而逐步构建出全局最优的层次结构。
3.3 凝聚聚类
凝聚聚类是一种自下而上的层次聚类方法,它从每个数据点作为一个独立的簇开始,然后逐步合并最相似的簇,直到满足某个终止条件(如达到预定的簇数或形成一个单一的大簇)。如下的AGNES算法就是对于凝聚聚类这个框架的具体实现。
3.4 AGNES
ABNES是一种采用自底向上聚合策略的层次聚类算法。它先将数据集中的每一个样本都看作是一个初始聚类簇,然后再一步步地找出距离最近的两个聚类簇去合并,并不断重复直到到底某个预设的簇的个数。
由此显而易见,在这个算法里关于距离的计算是十分重要的,而关于它,我们也有多种方法来计算。比如单连接:
全连接:
与平均连接:
3.5 树状图
有一个工具可以将层次聚类可视化,不过在sklearn中目前还做不到,而需要利用SciPy来实现。SciPy提供了一个函数,能接收数组X并计算出一个链接数组,它对层次聚类的相似度进行编码,然后我们可以将这个链接数组提供给SciPy中的dendrogram函数来绘制树状图。
如下就是一个绘制树状图的代码:
from scipy.cluster.hierarchy import dendrogram, ward
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
# 生成数据
X, y = make_blobs(random_state=42, n_samples=12)
# 进行层次聚类并绘制树状图
linkage_array = ward(X)
dendrogram(linkage_array)
ax = plt.gca()
bounds = ax.get_xbound()
# 绘制分割线
ax.plot(bounds, [7.25, 7.25], '--', c='k')
ax.plot(bounds, [4,4], '--', c='k')
# 添加注释
ax.text(bounds[1], 7.25, ' two clusters', va='center', fontdict={'size':15})
ax.text(bounds[1], 4, ' three clusters', va='center', fontdict={'size':15})
plt.xlabel("Sample index")
plt.ylabel("Cluster distance")
plt.show()
(注意:代码中ward函数可以直接进行层次聚类而不再需要调用sklearn中的函数了)
而它绘制出来的树状图则是这样:
3.6 python实例
代码如下:
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
import mglearn
X, y = make_blobs(random_state=42)
# 凝聚聚类实例化
agg = AgglomerativeClustering(n_clusters=3)
assigment = agg.fit_predict(X)
print(assigment)
score = silhouette_score(X, assigment)
print("The score is:{}".format(score))
# 绘图
mglearn.discrete_scatter(X[:,0], X[:,1], assigment)
plt.xlabel("feature 0")
plt.ylabel("Feature 1")
plt.show()
其输出如下:
[1 2 0 2 1 2 0 2 2 0 0 1 1 0 0 1 1 0 1 1 0 1 1 0 0 0 2 1 1 1 1 2 2 1 0 0 0
0 2 2 1 0 2 0 0 2 1 1 1 2 2 2 0 1 1 1 0 0 2 0 1 2 1 2 1 1 2 1 2 2 2 1 1 0
2 1 2 1 2 2 0 2 0 1 0 0 0 2 0 2 2 2 0 2 0 0 0 2 1 0]
The score is:0.8469881221532085
其画出的图如下:
观察刚才的代码,我们可以发现,在绘图的函数上我调用的是《Introduction to ML with Python》中提供的mglearn函数库中的函数而非matplotlib中的函数,因为mglearn中的函数可以自动分配颜色、自动标记等,较为方便。
四、 密度聚类
1.1 概念
密度聚类也称为“基于密度的聚类”,此类算法假设聚类结构能通过样本分布的紧密度确定。在通常情况下,密度聚类算法从样本密度的角度来考察样本间的可连续性,并基于可连续性样本不断扩展聚类簇以获得最终的聚类结果。
1.2 DBSCAN
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的空间聚类算法,它可以发现任意形状的数据簇,并且能够识别并排除噪声点。与其他一些需要预先指定簇的数量的聚类算法不同,DBSCAN能够根据数据集的局部密度分布来确定簇的数量。
1.3 DBSCAN工作原理
DBSCAN算法有两个重要的概念:领域和核心点。
领域:
给定一个点 p,以及一个距离阈值 ϵ,则 p 的邻域是指所有距离 p 不超过 ϵ 的点集合。即:
在sklearn中这个邻域的半径大小用eps代表。
核心点:
如果一个点的邻域内至少包含 min_samples 个点,则该点为核心点。min_samples 是用户定义的一个参数,表示成为一个核心点所需的最小邻居数量。
因为DBSCAN算法又被称为“具有噪声的基于密度的空间聚类应用”,所以它存在噪声点。如果距起始点的距离在eps之内的数据点个数小于min_samples,那么这个点就被标记为“噪声点”,也就是说它不属于任何一个簇。
所以,我们知道在这个算法中,一共会有三类点:核心点、边界点(与核心点的距离在eps内的点)以及噪声点。
1.4 python实例
在代码中采用了一个不太完整的网格搜索来寻找eps和min_simples参数的最佳组合,不过最后出来的效果还是不佳,代码如下:
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
import mglearn
X, y = make_blobs(random_state=42, n_samples=300, centers=3)
# 尝试不同的 eps 和 min_samples 参数
eps_values = [0.1, 0.2, 0.3, 0.4, 0.5]
min_samples_values = [3, 5, 10, 15, 20]
best_score = -1
best_eps = None
best_min_samples = None
for eps in eps_values:
for min_samples in min_samples_values:
# DBSCAN实例化
dbscan = DBSCAN(eps=eps, min_samples=min_samples)
assigment = dbscan.fit_predict(X)
# 计算轮廓系数
if len(np.unique(assigment)) > 1: # 至少有两个簇才能计算轮廓系数
score = silhouette_score(X, assigment)
if score > best_score:
best_score = score
best_eps = eps
best_min_samples = min_samples
# 使用最佳参数进行聚类
dbscan = DBSCAN(eps=best_eps, min_samples=best_min_samples)
assigment = dbscan.fit_predict(X)
score = silhouette_score(X, assigment)
print("Best eps:", best_eps)
print("Best min_samples:", best_min_samples)
print("The score is: {}".format(score))
# 绘图
mglearn.discrete_scatter(X[:, 0], X[:, 1], assigment)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.title(f"DBSCAN Clustering (eps={best_eps}, min_samples={best_min_samples})")
plt.show()
它的输出为:
Best eps: 0.5
Best min_samples: 3
The score is: 0.3421678003253009
绘制的图像为:
我们无论从得出的score还是绘制的图像都可以看出这个预测并不理想,因为在score中,silhouette_score的值越接近于1,则说明聚类的效果越好,在前面的算法里,其结果都是0.8多,而在此则只有0.3多,而且观察图像就可以发现图像很乱。
那么我们输出它的预测后的标签为:
[ 0 0 1 2 -1 2 1 2 1 1 -1 2 1 1 0 1 0 -1 1 -1 1 1 2 0
1 3 0 -1 2 1 1 1 0 1 4 1 0 2 0 2 2 1 0 2 1 1 0 2
0 5 2 3 0 1 0 -1 0 1 2 1 0 -1 2 -1 0 2 2 0 0 1 2 -1
0 1 1 0 0 2 1 2 1 1 0 1 2 0 0 1 6 1 0 1 0 1 1 0
0 1 0 3 2 1 2 1 1 1 1 1 -1 0 -1 1 1 1 1 2 0 -1 0 -1
2 2 -1 0 0 0 0 1 0 0 1 1 1 1 1 2 2 0 1 0 1 1 -1 1
2 2 2 1 2 1 1 0 2 4 1 2 2 0 -1 1 -1 0 0 0 1 0 2 -1
1 1 1 -1 2 1 2 2 2 -1 6 2 0 1 0 5 2 -1 2 1 -1 2 0 -1
2 0 2 -1 2 6 1 0 1 1 2 2 1 2 0 0 2 1 1 0 2 -1 0 0
3 0 1 -1 -1 -1 0 0 1 2 0 0 2 1 -1 3 1 0 2 2 0 2 0 0
3 2 2 1 0 2 2 2 0 2 0 2 0 -1 -1 0 2 1 4 1 1 1 0 1
2 2 0 2 2 1 1 2 2 2 0 0 0 1 1 1 2 2 5 -1 0 2 3 2
2 0 1 2 2 1 0 1 2 1 3 3]
其中凡是为-1的都是被标记为被算法识别为噪声点的,那么我们可以来判断下是否是噪声点过多导致的,如下就是关于判断这个标签集中是否噪声过多的方法:
我们先统计噪声点的数量:
num_noise_points = np.sum(assigment == -1)
print(f"Number of noise points: {num_noise_points}")
得出结果为32个,然后看它在整体的占比如何,通过统计得知总共有300个点,则噪声点占比为10.67%,这个数据一般认为是合适的,只有当它大于20%时才会被认为是有问题需要处理的。
那么我们可以考虑是否eps和min_samples这个两个参数需要在细化,以及数据集本身有一定问题需要一定的预处理来解决。在此不多言了。
五、 其他聚类算法
5.1 学习向量量化
学习向量量化(Learning Vector Quantization, LVQ)是一种与k均值算法一样通过寻找一组原型向量来刻画聚类结构的算法,但它与其他聚类算法不同的是它假设数据样本带有类别标签,所以它也是一种监督学习,而非无监督学习。
关于它的后半段VQ,我们知道向量量化(VQ)是一种信号处理技术,用于将连续的输入数据映射到离散的符号或代码书中。在 VQ 中,输入空间被一组称为“码本向量”(codebook vectors)的点所覆盖。码本向量通常通过训练数据来学习得到。
而LVQ也有许多的变体,像LVQ1、LVQ2、LVQ3等等。
5.2 高斯混合聚类
高斯混合模型(Gaussian Mixture Model, GMM)是一种常用的概率模型,用于描述多模态数据分布,并且可以用于聚类任务。GMM 是一种软聚类方法,它可以估计每个数据点属于不同簇的概率,而不仅仅是硬性的分配。
我们首先知道高斯分布,也就是正态分布,它是一种常见的概率函数,其概率密度函数由均值(mean)和方差(variance)决定。而所谓的混合模型则是将一些高斯分布按一定的权重给混合起来,形成更复杂的概率分布函数。
一个GMM可以用多个高斯分布的加权和来表示,即如下:
而关于GMM的参数则可以用EM算法得知,这种算法是一种迭代算法。它包含两个步骤:E 步(Expectation Step)和 M 步(Maximization Step)。
六、 补充
最后我再补充下关于聚类的一些东西。
在密度聚类算法中,除开刚才提及的DBSCAN算法外常见的算法还有:OPTICS、DENCLUE等。
而层次聚类上,AGNES采用了自底向上的聚合策略来产生层次聚类结构,而与之相反有DIANA算法则是采用了自顶向下的分拆策略。但它们都不能对已合并或已分拆的聚类簇进行回溯调整,而常用的层次聚类算法BIRCH等则对此进行了改进。
我们发现在聚类算法中,其家族成员众多,而聚类算法正是机器学习中各种其范畴内新算法出现最多、最快的领域,一个重要原因就是聚类不存在客观标准,当给定数据集后,总可以从某个以往算法尚未覆盖的标准去设计出新的算法来。
在之前的文章里我有介绍集成,像随机森林、梯度提升回归树等都是采用了集成学习。同样的,我们也可以考虑在聚类算法中引入集成的想法,如此则可以有效降低聚类假设与真实聚类不符合的现象,就像我在密度算法中的python例子就可以这样解决,此外还可以在聚类过程中降低随即性等因素带来的不利影响。
最后,关于异常检测,其常借助于聚类和距离计算进行,如将远离所有簇中心的样本作为异常点,或将密度极低出的样本作为异常点,就像可以将一个数据集先用DBSCAN来聚类,然后将所有标签为-1的数据点剔除,然后再开始真正的预测。
此上