一些常见的聚类算法原理解析与实践
目录
- 一、K-means
- 二、学习向量量化(LVQ)
- 三、DBSCAN聚类
- 四、BERT-TOPIC
- 1、bertopic安装:
- 2、算法过程
- 3、python中的实践
- 五、基于共现网络的聚类算法
- 1、Louvain社区检测
- 2、谱聚类(Spectral Clustering)
- 六、高斯混合聚类(Mixtrue-of-Gaussian)
- 七、聚类效果度量指标
- 1、轮廓系数(Silhouette Coefficient)
- 2、模块度
sklearn. version = ‘1.7.0’
一、K-means
- 质心:每个簇的"中心点"(簇内所有点的均值位置,非实际点);
- 每个样本都由一个 n 维的向量表示;
- 聚类过程:
- 设置聚类簇数 k;
- 随机选择 k 个初始样本,作为 k 个簇的初始质心,对应 k 个均值向量;
- 计算所有训练样本与 k 个初始质心的距离,分配样本到最近的质心;
- 得到 k 个簇,根据每个簇中所有的样本计算各个簇新的均值向量(簇中所有样本对应维度的向量求均值),得到 k 个新质心,重复上一步,直至达到最大轮数或阈值。
- 对异常值非常敏感!异常值会显著拉偏质心位置,可以使用K-medoids算法(用实际点而非均值作为中心);
- 球形数据适合基于距离的聚类算法(如k-means),非球形数据则适合基于密度或连通性的算法,该类数据可以考虑DBSCAN或谱聚类;
- python中的实践:
from sklearn.cluster import KMeanskm = KMeans(n_clusters=num_clusters)
result_labels = km.fit_predict(embeds) # embeds:array-like of shape (n_samples, n_features)
print('k-means的聚类中心结果:',km.cluster_centers_)
二、学习向量量化(LVQ)
- LVQ 主要用于模式分类,但也可用于聚类分析;
- 是一种带监督的聚类;
- 跟k-meas相似,原型向量类似于质心,聚类前也需要指定聚类簇数;
- 算法过程:
针对数据集:D={(x1,y1),(x2,y2),…,(xn,yn)},其中xi是数据点(特征向量),yi是类别标签(监督信息)。
(1)初始化原型向量:
选择 K 个原型向量 {p1,p2,…,pK},每个原型向量pj 有一个类别标签cj:
初始化方法:随机选择 K 个训练样本作为初始原型,或使用 K-means 聚类中心作为初始原型。
(2)对于每个训练样本(xi,yi),计算 xi 与所有原型向量 pj 的距离,选择距离最近的原型 pj*;
(3)调整原型向量:
如果 pj* 的标签 cj* 与 yi 相同(分类正确),则:
pj∗ ← pj∗ + α(xi − pj∗)
如果 pj* 的标签 cj* 与 yi 不同(分类错误),则:
pj∗ ← pj∗ - α(xi − pj∗)
(4)更新学习率(通常采用逐渐衰减的学习率):
αt=α0 ⋅ (1−t/T)
T 是最大迭代次数,t 是当前迭代。
(5)循环以上(2)-(4)步骤,直到满足停止条件。
停止条件可以是:①最大迭代次数、②原型向量的变化小于阈值、③分类准确率不再显著提升。
三、DBSCAN聚类
【HDBSCAN 聚类……】
- 是一种密度聚类;
- 需提前指定的参数:
- eps(ε):邻域半径,用于定义“附近”的范围;
- min_samples(MinPts):核心点所需的最小邻域点数。
- 算法过程:
针对数据集:D = {x₁, x₂, …, xₙ};
(1)所有点标记为未访问(unvisited),初始化簇编号 C = 0;
(2)针对每个点p∈D:- 如果 p 已被访问,跳过;
- 否则,标记 p 为已访问。
- 计算 p 的 eps(ε) 邻域内的点数 N_eps(p) 。
- 如果 N_eps(p) ≥ min_samples,则 p 是核心点:
- 创建一个新簇 C = C + 1。
- 将 p 加入簇 C。
- 遍历 N_eps(p) 中的所有点 q :
- 如果 q 是未访问且也是核心点,则递归扩展其邻域(深度优先搜索);
- 如果 q 不属于任何簇,将其加入簇 C。
- 否则 p 是噪声或边界点:
暂时标记为噪声(后续可能被重新分类为边界点)。
- 适合非球形数据;
- 在python中的实践:
from sklearn.cluster import DBSCANdbscan = DBSCAN(eps=2, min_samples=3)
labels = dbscan.fit_predict(X)
四、BERT-TOPIC
1、bertopic安装:
- 建议安装时创建一个全新的conda环境,直接先安装bertopic:
pip install bertopic -i https://pypi.tuna.tsinghua.edu.c
- 安装时会安装大量的相关模块,比如hdbscan、scikit-learn、torch、transformers、sentence-transformers等(如果后续要将结果写入excel需要单独安装 openpyxl);
- 安装完成后,各个相关模块版本如下图:
2、算法过程
- 文档嵌入:使用embedding模型;
- 向量降维:使用UMAP算法;
- 聚类:使用HDBSCAN算法;
- 主题表示:使用c-TF-IDF。
3、python中的实践
1、整个模型构建的过程如下:
- 相同的输入和参数,每次聚类结果都不同。
model_dir= 'E:/CQF/work_yanxue/AI_yanxue/LLM/models/embedding_model/m3e-base'
embedding_model = SentenceTransformer(model_dir)umap_model = UMAP(n_neighbors=15, n_components=5,min_dist=0.0,metric='cosine')hdbscan_model = HDBSCAN(min_cluster_size=10, metric='euclidean', prediction_data=True)lis_stopwords = ['我','的','你好','啊','吗']
vectorizer_model = CountVectorizer(stop_words=lis_stopwords)ctfidf_model = ClassTfidfTransformer()topic_model = BERTopic(embedding_model=embedding_model, # Step 1 - Extract embeddingsumap_model=umap_model, # Step 2 - Reduce dimensionalityhdbscan_model=hdbscan_model, # Step 3 - Cluster reduced embeddingsvectorizer_model=vectorizer_model, # Step 4 - Tokenize topicsctfidf_model=ctfidf_model, # Step 5 - Extract topic wordsnr_topics='none',top_n_words = 10 # The number of words per topic to extract.比如1簇中可能有2k个词,只抽取10个进行显示,此时无法知道2k个词具体是什么
)
filtered_text = data_prod_kws('C:\\Users\\cqf\\Downloads\\data (1).txt')
print('词数量:', len(filtered_text))
topics, probabilities = topic_model.fit_transform(filtered_text)
topic_info = topic_model.get_topic_info()
topic_info.to_excel('data/聚类结果.xlsx',index=False)
print(topic_info)topics = topic_model.get_topics()
print(topics)topic_freqs = topic_model.get_topic_freq()
topic_freqs.to_excel('data/聚类主题频次.xlsx',index=False)
print('主题频次:', topic_freqs)
2、输入的数据为包含所有关键词的 list,比如 ['聚类', '相似性度量', '大数据聚类', '小数据聚类', '聚类评价', '区块链', '隐私保护', '对等网络', '聚类分析', '比特币']
:
- 数据处理代码:
def data_prod_kws(file):text = []with open(file, 'r', encoding='utf-8') as f:dics = json.load(f)for dic in dics:text.extend([w for w in dic['中文关键词'].split(',')])return list(set(text))
- 聚类结果:
- get_topic_info()的结果:
count:该主题下存在的所有词数,相加为参与聚类的所有词;
representation:top_n_words 参数指定的10个词;
representative docs:默认给3个词.
- get_topic_freq()的结果:
- get_topics()的结果:
每个聚类下面 top_n_words 个representation词以及他们对应的 c-TF-IDF score。
- get_topic_info()的结果:
{-1: [('mann', np.float64(0.01204125761183207)), ('citespace', np.float64(0.010686919578811734)), ('一带一路', np.float64(0.009196415268009182)), ('支持向量机', np.float64(0.005623271905033993)), ('偏最小二乘', np.float64(0.005623271905033993)),
('kendall突变检验', np.float64(0.005623271905033993)), ('kendall趋势检验', np.float64(0.005623271905033993)), ('气相色谱', np.float64(0.005623271905033993)), ('电动汽车', np.float64(0.005623271905033993)), ('dbscan', np.float64(0.005623271905033993))], 0: [('means', np.float64(0.008056103664799924)), ('means算法', np.float64(0.008056103664799924)), ('means聚类算法', np.float64(0.006719382727287874)), ('means聚类', np.float64(0.005963621503722352)), ('均值聚类', np.float64(0.0051318754150817825)), ('dea', np.float64(0.004202358326079973)), ('模糊c', np.float64(0.004202358326079973)), ('est', np.float64(0.0031379543007534444)), ('means聚类分析', np.float64(0.0031379543007534444)), ('gmm', np.float64(0.0031379543007534444))], 1: [('模糊聚类算法', np.float64(0.04361382912059102)), ('层次聚类分析', np.float64(0.04361382912059102)), ('dpca', np.float64(0.025855984653890975)), ('bagging集成聚类', np.float64(0.025855984653890975)), ('ap聚类', np.float64(0.025855984653890975)),
('ap聚类算法', np.float64(0.025855984653890975)), ('初始聚类中心', np.float64(0.025855984653890975)), ('hca', np.float64(0.025855984653890975)), ('index聚类有效性检验指标', np.float64(0.025855984653890975)), ('ipc聚类', np.float64(0.025855984653890975))],
3、输入的数据为 list,其中元素为每篇文献用空格分隔的关键词,比如 ["聚类 相似性度量 大数据聚类 小数据聚类 聚类评价", "区块链 隐私保护 对等网络 聚类分析 比特币"]
:
- 数据处理代码:
def data_prod_docs(file):text = []with open(file, 'r', encoding='utf-8') as f:dics = json.load(f)for dic in dics:text.append(dic['中文关键词'].replace(',', ' '))# text.append(dic['中文关键词'])return text
- 聚类结果:
- 跟2中的输入结果相比,效果不是很好,聚类数量过少,且有时才2个簇;(如果每个doc中的词用逗号分隔,效果更差……)
- get_topic_info()的结果:
count:该主题下所有 doc 数,每个 doc 是 list 中的一个字符串,相加为参与聚类的所有 doc 数;
representation:top_n_words 参数指定的10个词;
representative docs:默认给3个 doc(非3个词).
五、基于共现网络的聚类算法
1、Louvain社区检测
- 主要用于复杂网络分析领域;
- 一种基于模块度(Modularity)优化的社区发现(Community Detection)算法;
- 聚类依据是模块度增益(ΔQ),计算公式:
例如将节点 i 从社区 D 移动到社区 C 带来的模块度变化,其中:
Σin:社区 C 内部边的权重和
Σtot:与社区 C 相连的所有边的权重和
ki,in:节点 i 与社区 C 内节点的边权重和
ki:节点 i 的度
ΔQ = [(Σin + ki,in)/2m - (Σtot + ki)2/(2m)2] - [Σin/2m - (Σtot)2/(2m)2 - (ki)2/(2m)2]
- 算法过程:
例如有网络:[(‘A’,‘B’), (‘A’,‘C’), (‘B’,‘C’), (‘C’,‘D’), (‘D’,‘E’), (‘D’,‘F’), (‘E’,‘F’)]- 第一阶段:模块度优化
初始化:将每个节点分配到独立的社区;
得到社区:{‘A’:0, ‘B’:1, ‘C’:2, ‘D’:3, ‘E’:4, ‘F’:5}
遍历节点:
(1)对于每个节点 i,计算将其移动到邻居社区的ΔQ
(2)将节点 i 移动到使ΔQ最大的社区(若ΔQ>0)
(3)重复:直到没有节点移动能提高模块度
得到社区:{‘A’:1, ‘B’:1, ‘C’:1, ‘D’:4, ‘E’:5, ‘F’:4} - 第二阶段:社区聚合
(1)将第一阶段得到的社区视为新网络的"超级节点":
社区1:{A,B,C} → 新节点X
社区2:{D,E,F} → 新节点Y
(2)超级节点之间的边权重为原社区间所有边权重和
X-Y权重:原C-D边权重=1
(3)社区内部的边形成新节点的自环,可以得到每个社区的权重
X自环:社区1内部边AB,AC,BC → 权重3
Y自环:社区4内部边DE,DF,EF → 权重3
- 第一阶段:模块度优化
- 适合有社区关系(比如关键词的共现关系)的数据;
- python中有专业的第三方模块进行louvain聚类计算:
import community as community_louvain
import networkx as nxG_datas = [('基金', 1, {'weight': 1}),('人工智能', 'AI研学', {'weight': 1}),(2, 0, {'weight': 1}),(3, 4, {'weight': 1}),(4, 5, {'weight': 1}),(5, 3, {'weight': 1}),(0, 3, {'weight': 0.1}) # 弱连接
]
G = nx.Graph()
G.add_edges_from(G_datas)# Louvain社区检测
partition = community_louvain.best_partition(G, weight='weight') # 返回结果是dict类型,如{'基金': 社区编号0, '人工智能': 社区编号2, ……}
2、谱聚类(Spectral Clustering)
- 适合处理非凸分布(如环形、流形结构)的数据,是K-means等传统聚类方法的有力补充;
- 计算复杂度很高;本质是构建相似性矩阵W、度矩阵D后,计算拉普拉斯矩阵L,然后分解L得到最终的数据集特征向量U,使用k-means等其他聚类算法对特征向量聚类;
- 需提前指定聚类簇数;
- 算法过程:
针对数据集X={x1,x2,…,xn}:- 构建相似性矩阵 W(Affinity Matrix):
计算所有数据点两两之间的相似度(算法如高斯核函数RBF Kernel),生成矩阵 W; - 计算度矩阵 D(Degree Matrix):
度矩阵 D 是一个对角矩阵,对角线元素为每个节点的度(即相似性矩阵的行和):Dii=Σnj=1Wij; - 计算拉普拉斯矩阵 L(Laplacian Matrix):
非归一化拉普拉斯矩阵:L=D−W
对称归一化拉普拉斯矩阵:L= I−D−1/2WD−1/2
游走归一化拉普拉斯矩阵:L= I−D−1 W - 计算拉普拉斯矩阵的特征向量 U:
对 L 进行特征分解,取前 k 个最小的非零特征值对应的特征向量,组成矩阵 U∈Rn×k。 - 使用k-means等算法对特征向量进行聚类:
将 U 的行向量作为数据点在低维空间的表示,用K-means等算法聚类。
- 构建相似性矩阵 W(Affinity Matrix):
- 在python中的实践:
from sklearn.cluster import SpectralClusteringmodel = SpectralClustering(n_clusters=2,affinity='nearest_neighbors', # 默认值 'rbf'assign_labels='kmeans' # 对特征向量聚类的方法,默认值kmeans
)
labels = model.fit_predict(X)
还有一种是层次聚类(Hierarchical Clustering),详细的后续有时间补充。
六、高斯混合聚类(Mixtrue-of-Gaussian)
-
是一种软聚类,算法仅提供概率归属,最终样本归属于概率最大的簇;
-
需要提前定义k(高斯分布个数,也是最终的簇数);
-
需要初始化高斯分布的各个参数:Πk(混合系数),uk(均值),Σk(协方差矩阵);
-
算法过程:
假设数据由 K 个高斯分布混合而成,其概率密度函数为:
p(x) = ΣKk=1Πk * N(x|uk, Σk)
其中,
Πk:第 k 个高斯分布的混合系数(权重),满足ΣKk=1Πk = 1;
N(x|uk, Σk):第 k 个高斯分布的概率密度函数,均值为 uk,协方差矩阵是 Σk。- (1)随机或通过k-means初始化高斯分布的参数: uk, Σk,Πk(一般为 1/K) ;
- (2)E步(Exceptation):
计算每个样本xi属于第 k 个高斯分布的后验概率 γik(责任值,表示 xi 属于簇 k 的概率):
- (3)M步(Maximization):
更新均值uk:
更新协方差Σk:
更新混合系数Πk:其中,N为样本总数
- (4)收敛判断
计算对数似然函数,判断是否收敛,若似然值拜年话小于阈值或达到最大迭代次数,则停止,否则返回E步。
-
python中的实践:
from sklearn.mixture import GaussianMixture
gmm = GaussianMixture(n_components = k, covariance_type = 'full', # 协方差矩阵的定义方式,full表示每个高斯分布都有自己的协方差矩阵init_params = 'kmeans' # 默认使用k-means方式初始化权重、均值、精度)
除上述之外,还有:
- AGNES聚类:每个样本初始为一个簇,迭代合并距离最近的簇,直到所有样本聚为一类。
- Meanshift聚类:是一种密度聚类,是指是沿着密度上升的方向寻找同属一个簇的数据点。
- 在python中调用:
from sklearn.cluster import AgglomerativeClustering, MeanShift
七、聚类效果度量指标
1、轮廓系数(Silhouette Coefficient)
- 用于衡量每个样本与其所在簇的紧密度(Cohesion)和与最近簇的分离度(Separation)。它综合了簇内紧密度和簇间分离度,结果范围在[−1,1] 之间:
- 值接近 1:表示样本很好地聚集在簇内,且与其他簇分离得很好,聚类效果好。
- 值接近 0:表示样本位于两个簇的边界,聚类效果一般。
- 值接近 -1:表示样本可能被错误地分配到簇中,聚类效果差。
2、模块度
- 计算公式,其中:
- Aij:节点i和节点j之间的边权重
- ki :节点i的度(所有边的权重和)
- m:网络中所有边的权重和
- ci:节点i所属的社区
- δ:克罗内克函数(同一社区为1,否则为0)
Q = (1/2m) * Σij [Aij - (ki * kj)/2m] * δ(ci, cj)
基于Kleinberg算法的关键词突现实现:
Detecting ‘bursts’ in time series data with Kleinberg’s burst detection algorithm
GitHub:BURST DETECTION