聚类算法的代码解析与实现

谱聚类算法的代码解析与实现

本文将对一个基于未归一化拉普拉斯矩阵的谱聚类算法进行详细解析。该算法的实现源自 SpeechBrain 项目,适用于语音片段的聚类,例如语音分离、说话人识别等任务。我们将逐步分析代码的每个部分,并解释其背后的数学原理。

代码简介

以下是 SpectralCluster 类的完整代码:

class SpectralCluster:"""A spectral clustering method using unnormalized Laplacian of affinity matrix."""def __init__(self, min_num_spks=1, max_num_spks=15, pval=0.022):self.min_num_spks = min_num_spksself.max_num_spks = max_num_spksself.pval = pvaldef __call__(self, X, oracle_num=None):# 计算相似度矩阵sim_mat = self.get_sim_mat(X)# p-剪枝pruned_sim_mat = self.p_pruning(sim_mat)# 矩阵对称化sym_pruned_sim_mat = 0.5 * (pruned_sim_mat + pruned_sim_mat.T)# 计算拉普拉斯矩阵laplacian = self.get_laplacian(sym_pruned_sim_mat)# 获取谱嵌入emb, num_of_spk = self.get_spec_embs(laplacian, oracle_num)# 聚类labels = self.cluster_embs(emb, num_of_spk)return labelsdef get_sim_mat(self, X):# 计算余弦相似度M = sklearn.metrics.pairwise.cosine_similarity(X, X)return Mdef p_pruning(self, A):if A.shape[0] * self.pval < 6:pval = 6.0 / A.shape[0]else:pval = self.pvaln_elems = int((1 - pval) * A.shape[0])# 对每一行进行处理for i in range(A.shape[0]):low_indexes = np.argsort(A[i, :])low_indexes = low_indexes[0:n_elems]# 将较小的相似度值设为0A[i, low_indexes] = 0return Adef get_laplacian(self, M):M[np.diag_indices(M.shape[0])] = 0D = np.sum(np.abs(M), axis=1)D = np.diag(D)L = D - Mreturn Ldef get_spec_embs(self, L, k_oracle=None):lambdas, eig_vecs = scipy.linalg.eigh(L)if k_oracle is not None:num_of_spk = k_oracleelse:lambda_gap_list = self.getEigenGaps(lambdas[self.min_num_spks - 1 : self.max_num_spks + 1])num_of_spk = np.argmax(lambda_gap_list) + self.min_num_spksemb = eig_vecs[:, :num_of_spk]return emb, num_of_spkdef cluster_embs(self, emb, k):_, labels, _ = k_means(emb, k)return labelsdef getEigenGaps(self, eig_vals):eig_vals_gap_list = []for i in range(len(eig_vals) - 1):gap = float(eig_vals[i + 1]) - float(eig_vals[i])eig_vals_gap_list.append(gap)return eig_vals_gap_list

1. 类的定义

class SpectralCluster:"""A spectral clustering method using unnormalized Laplacian of affinity matrix."""def __init__(self, min_num_spks=1, max_num_spks=15, pval=0.022):self.min_num_spks = min_num_spks  # 最小聚类数self.max_num_spks = max_num_spks  # 最大聚类数self.pval = pval  # p 值,用于 p-剪枝
  • 作用:定义了一个 SpectralCluster 类,用于执行谱聚类算法。
  • 参数
    • min_num_spks:最小聚类数量,默认为 1。
    • max_num_spks:最大聚类数量,默认为 15。
    • pval:p-剪枝中的阈值参数,默认为 0.022。

2. 调用方法 __call__

def __call__(self, X, oracle_num=None):# 计算相似度矩阵sim_mat = self.get_sim_mat(X)# p-剪枝pruned_sim_mat = self.p_pruning(sim_mat)# 矩阵对称化sym_pruned_sim_mat = 0.5 * (pruned_sim_mat + pruned_sim_mat.T)# 计算拉普拉斯矩阵laplacian = self.get_laplacian(sym_pruned_sim_mat)# 获取谱嵌入emb, num_of_spk = self.get_spec_embs(laplacian, oracle_num)# 聚类labels = self.cluster_embs(emb, num_of_spk)return labels
  • 流程概述
    1. 计算相似度矩阵:调用 get_sim_mat 方法,计算数据点之间的相似度。
    2. p-剪枝:调用 p_pruning 方法,对相似度矩阵进行稀疏化,保留较大的相似度值,去除较小的相似度值。
    3. 矩阵对称化:将剪枝后的相似度矩阵对称化,得到对称的相似度矩阵。
    4. 计算拉普拉斯矩阵:调用 get_laplacian 方法,计算未归一化的拉普拉斯矩阵。
    5. 获取谱嵌入:调用 get_spec_embs 方法,计算拉普拉斯矩阵的特征值和特征向量,得到谱嵌入。
    6. 聚类:调用 cluster_embs 方法,对谱嵌入进行聚类(使用 k-means 算法)。
    7. 返回结果:输出聚类标签。

3. 详细步骤分析

3.1 计算相似度矩阵

def get_sim_mat(self, X):# 计算余弦相似度M = sklearn.metrics.pairwise.cosine_similarity(X, X)return M
  • 作用:计算数据点之间的余弦相似度,得到相似度矩阵 ( M )。
  • 数学公式

M i j = cos ⁡ ( θ i j ) = X i ⋅ X j ∥ X i ∥ ∥ X j ∥ M_{ij} = \cos(\theta_{ij}) = \frac{X_i \cdot X_j}{\|X_i\| \|X_j\|} Mij=cos(θij)=Xi∥∥XjXiXj

  • 说明
    • 输入:特征矩阵 ( X ),形状为 ( (n_{\text{samples}}, n_{\text{features}}) )。
    • 输出:相似度矩阵 ( M ),形状为 ( (n_{\text{samples}}, n_{\text{samples}}) ),其中 ( M_{ij} ) 表示样本 ( X_i ) 和 ( X_j ) 的余弦相似度。

3.2 p-剪枝(p-pruning)

def p_pruning(self, A):if A.shape[0] * self.pval < 6:pval = 6.0 / A.shape[0]else:pval = self.pvaln_elems = int((1 - pval) * A.shape[0])# 对每一行进行处理for i in range(A.shape[0]):low_indexes = np.argsort(A[i, :])low_indexes = low_indexes[0:n_elems]# 将较小的相似度值设为0A[i, low_indexes] = 0return A
  • 作用:对相似度矩阵 ( A ) 进行稀疏化,保留每个数据点与其他数据点中最大的 ( p ) 百分比的相似度值,其余的相似度值设为零。
  • 说明
    • 调整 ( p ) 值:如果 ( A.shape[0] \times pval < 6 ),则调整 ( pval = \dfrac{6.0}{A.shape[0]} ),确保每行至少保留 6 个非零元素。

    • 计算要保留的元素数量

      n elems = int ( ( 1 − p v a l ) × A . s h a p e [ 0 ] ) n_{\text{elems}} = \text{int}\left((1 - pval) \times A.shape[0]\right) nelems=int((1pval)×A.shape[0])

    • 对每一行

      • 排序索引:对第 ( i ) 行的相似度值进行升序排序,得到索引 ( \text{low_indexes} )。
      • 保留较大的相似度值:将较小的 ( n_{\text{elems}} ) 个相似度值设为零,保留较大的相似度值。
  • 目的:减少计算复杂度,构建稀疏的相似度矩阵,同时保留最相关的相似性信息。

3.3 矩阵对称化

sym_pruned_sim_mat = 0.5 * (pruned_sim_mat + pruned_sim_mat.T)
  • 作用:将剪枝后的相似度矩阵对称化,得到对称的相似度矩阵 ( W )。
  • 说明
    • 对于非对称矩阵 ( A ),对称化操作为:

      W = 1 2 ( A + A ⊤ ) W = \dfrac{1}{2}(A + A^\top) W=21(A+A)

    • 对称化后,( W_{ij} = W_{ji} ),满足相似度矩阵的对称性。

3.4 计算拉普拉斯矩阵

def get_laplacian(self, M):M[np.diag_indices(M.shape[0])] = 0D = np.sum(np.abs(M), axis=1)D = np.diag(D)L = D - Mreturn L
  • 作用:计算未归一化的拉普拉斯矩阵 ( L )。

  • 步骤

    1. 将对角元素设为零:( M_{ii} = 0 ),避免自环(节点自身的连接)。

    2. 计算度矩阵 ( D )

      D i i = ∑ j ∣ M i j ∣ D_{ii} = \sum_{j} |M_{ij}| Dii=jMij

    3. 计算拉普拉斯矩阵 ( L )

      L = D − M L = D - M L=DM

  • 说明

    • 度矩阵 ( D ):是一个对角矩阵,元素 ( D_{ii} ) 表示节点 ( i ) 的度数(与其他节点的连接强度之和)。
    • 拉普拉斯矩阵 ( L ):反映了图的结构性质,包含了节点之间的连接信息。

3.5 获取谱嵌入

def get_spec_embs(self, L, k_oracle=None):lambdas, eig_vecs = scipy.linalg.eigh(L)if k_oracle is not None:num_of_spk = k_oracleelse:lambda_gap_list = self.getEigenGaps(lambdas[self.min_num_spks - 1 : self.max_num_spks + 1])num_of_spk = np.argmax(lambda_gap_list) + self.min_num_spksemb = eig_vecs[:, :num_of_spk]return emb, num_of_spk
  • 作用:计算拉普拉斯矩阵的特征值和特征向量,得到谱嵌入,并确定聚类的数量。
  • 步骤
    1. 特征值分解:使用 scipy.linalg.eigh 计算拉普拉斯矩阵 ( L ) 的特征值 lambdas 和特征向量 eig_vecs
    2. 确定聚类数量 num_of_spk
      • 如果提供了真实的类别数 k_oracle,则直接使用。

      • 否则,计算特征值的间隙(差值),选择间隙最大的索引作为聚类数量。

        Gap i = λ i + 1 − λ i \text{Gap}_i = \lambda_{i+1} - \lambda_i Gapi=λi+1λi

      • self.min_num_spksself.max_num_spks 范围内,找到最大的特征值间隙,推断出聚类数量。

    3. 获取谱嵌入 emb
      • 选择对应于最小特征值的前 num_of_spk 个特征向量。
  • 说明
    • 特征值和特征向量:拉普拉斯矩阵的特征值和特征向量包含了图的结构信息。
    • 谱嵌入:使用特征向量作为新的表示,将数据点从高维空间映射到低维空间。

3.6 聚类

def cluster_embs(self, emb, k):_, labels, _ = k_means(emb, k)return labels
  • 作用:对谱嵌入 emb 进行聚类,得到聚类标签 labels
  • 说明
    • 使用 k-means 算法:在谱嵌入的低维空间中,应用 k-means 聚类算法。
    • 输入
      • emb:谱嵌入,形状为 ( (n_{\text{samples}}, \text{num_of_spk}) )。
      • k:聚类数量,即 num_of_spk
    • 输出
      • labels:聚类标签,表示每个样本所属的簇。

4. 辅助方法

4.1 计算特征值间隙

def getEigenGaps(self, eig_vals):eig_vals_gap_list = []for i in range(len(eig_vals) - 1):gap = float(eig_vals[i + 1]) - float(eig_vals[i])eig_vals_gap_list.append(gap)return eig_vals_gap_list
  • 作用:计算特征值之间的差值,用于确定聚类的数量。
  • 说明
    • 输入:特征值列表 eig_vals,取自特征值序列的一部分(从最小的特征值开始)。
    • 输出:特征值差值列表 eig_vals_gap_list,用于寻找最大的间隙。

5. 总结

谱聚类流程如下

  1. 计算相似度矩阵(Similarity Matrix)

    • 使用余弦相似度计算数据点之间的相似性,得到相似度矩阵 ( M )。
  2. p-剪枝(p-Pruning)

    • 对相似度矩阵进行稀疏化,保留每个数据点与其他数据点中最大的 ( p ) 百分比的相似度值,去除较小的相似度值,构建稀疏图。
  3. 矩阵对称化(Symmetrization)

    • 将剪枝后的相似度矩阵对称化,得到对称的相似度矩阵 ( W )。
  4. 计算拉普拉斯矩阵(Laplacian Matrix)

    • 计算度矩阵 ( D ),然后计算未归一化的拉普拉斯矩阵 ( L = D - W )。
  5. 计算特征值和特征向量(Spectral Embedding)

    • 对拉普拉斯矩阵 ( L ) 进行特征值分解,得到特征值和特征向量。
    • 通过寻找特征值的最大间隙,确定聚类数量 ( k )。
    • 选择对应于最小特征值的前 ( k ) 个特征向量,构成谱嵌入。
  6. 聚类(Clustering)

    • 在谱嵌入的低维空间中,使用 k-means 算法对数据进行聚类,得到聚类标签。

6. 数学原理

6.1 谱聚类的核心思想

  • 数据表示为图结构:将数据点视为图的节点,节点之间的边权重由数据点之间的相似度决定。
  • 拉普拉斯矩阵的作用:利用图的拉普拉斯矩阵的特征值和特征向量,获取数据的聚类信息。
  • 低维嵌入与聚类:通过拉普拉斯矩阵的特征向量,将数据从高维空间映射到低维空间,然后在该空间中进行聚类。

6.2 未归一化拉普拉斯矩阵

  • 度矩阵 ( D )

    D i i = ∑ j W i j D_{ii} = \sum_{j} W_{ij} Dii=jWij

  • 拉普拉斯矩阵 ( L )

    L = D − W L = D - W L=DW

  • 特征值与特征向量:拉普拉斯矩阵的特征值和特征向量反映了图的结构性质。

6.3 特征值的意义

  • 特征值为零的数量:与图的连通分量数量有关。
  • 特征值间隙:特征值之间的差值可以用于估计最优的聚类数量。

7. 代码中的特殊处理

  • p-剪枝的调整

    • 为了确保在小规模数据集上仍能保留足够的相似度信息,如果 ( n \times pval < 6 ),则调整 ( pval ),使得每行至少保留 6 个非零元素:

      如果  A . s h a p e [ 0 ] × p v a l < 6 , 则  p v a l = 6.0 A . s h a p e [ 0 ] \text{如果 } A.shape[0] \times pval < 6, \text{ 则 } pval = \dfrac{6.0}{A.shape[0]} 如果 A.shape[0]×pval<6,  pval=A.shape[0]6.0

  • 确定聚类数量

    • 如果未提供真实的聚类数量(oracle_num),则通过特征值间隙自动推断。
    • 这种方法利用了谱聚类中“特征值间隙最大处对应最优聚类数”的理论。

8. 实际应用中的注意事项

  • 参数选择

    • pval:影响相似度矩阵的稀疏程度,需要根据数据规模和特性进行调整。
    • min_num_spksmax_num_spks:设置聚类数量的搜索范围,需要根据先验知识设定。
  • 计算复杂度

    • 特征值分解的计算复杂度较高,对于大型数据集,可能需要优化或使用近似算法。
  • 数据预处理

    • 余弦相似度适用于已归一化的数据,如果输入数据未归一化,可能需要进行预处理。

9. 结论

该代码实现了谱聚类的标准流程,通过计算相似度矩阵、构建拉普拉斯矩阵、特征值分解和聚类,完成对数据的聚类任务。特殊之处在于使用 p-剪枝来稀疏化相似度矩阵,提高计算效率,并通过特征值间隙自动确定聚类数量。

该实现适用于语音说话者聚类等任务,但在其他应用中,需要根据具体情况调整参数和方法,以获得最佳效果。


参考文献

  • Spectral Clustering 原理与实现
  • Understanding Spectral Clustering

希望本文能帮助您深入理解谱聚类算法的实现与原理!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57177.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DS快速排序和归并排序的非递归实现(16)

文章目录 前言一、快排的非递归实现二、归排的非递归实现总结 前言 打破递归桎梏&#xff0c;迎接迭代解放&#xff01; 一、快排的非递归实现 我们要替代递归&#xff0c;就要用到迭代或者循环&#xff0c;也就是说&#xff0c;其核心思想是不变的&#xff0c;只是换一种方式来…

使用 CDN 后 Apache 的日志记录客户真实 IP

经常搭建网站服务器的都知道&#xff0c;在给站点使用了 CDN 后 Web 应用的日志记录里就会只记录 CDN 节点 IP 了&#xff0c;这就没法看到真实客户请求 IP&#xff0c;对于日志分析、运维日常维护来说就有点儿麻烦了&#xff0c;今天明月结合在五洛云服务器上搭建的Apache环境…

探索C嘎嘎:模版初阶

前言&#xff1a; 小编在前文讲述了C的内存管理&#xff0c;下面我们来开始继续探索C&#xff0c;开启C又一个重要的内容&#xff0c;模版初阶的详解&#xff0c;代码时间到&#xff01; 目录 1.泛型编程 1.1.引子 1.2.泛型编程 2.函数模版 2.1.函数模版的概念 2.2.函数模版的格…

基于知识图谱的电子元器件问答系统

你还在为寻找电子元器件的相关信息头疼吗&#xff1f;作为一名程序员或电子工程师&#xff0c;在项目中经常需要快速查询电子元件的属性或关联关系。今天给大家介绍一个可以大大提升工作效率的神器——基于知识图谱的电子元器件问答系统。这不仅是你学习和工作的好帮手&#xf…

解读华为云Kuasar多沙箱容器技术,带来更强隔离性和安全性

摘要&#xff1a;沙箱技术的引入&#xff0c;为容器提供了更强的隔离性和安全性&#xff0c;成为云原生技术的重要组成部分。 本文来源 《华为云DTSE》第五期开源专刊&#xff0c;作者&#xff1a;华为云云原生开源团队研发工程师。 近年来&#xff0c;云原生容器技术飞速发展&…

详解Java之异常

目录 防御式编程 捕获异常 基础语法 示例1 【不处理异常】 示例2 【使用try catch捕获异常】 示例3 【catch只能处理对应的异常】 示例4 【catch可以有多个】 示例5 【可以用一个catch捕获所有异常】 示例6 【finally一定会执行】 示例7 【使用try回收资源】 示例8 …

鸿蒙网络编程系列24-Web组件与应用互操作示例

1. APP内嵌网页与应用互操作概述 在通常的APP开发中&#xff0c;经常会采用内嵌网页的形式&#xff0c;通过网页来展现丰富的动态内容&#xff0c;虽少了很多原生开发的功能&#xff0c;但是这么做无可厚非&#xff0c;毕竟APP需要适配的系统平台很多&#xff0c;比如安卓、苹…

【HarmonyOS NEXT】权限申请及应用设置页跳转

关键词&#xff1a;鸿蒙、程序访问控制、定位、应用详情页、startability、want 在app开发过程中&#xff0c;常进行系统权限的申请以提供设备访问或个性化功能&#xff08;如扫一扫、城市定位、剪贴板等&#xff09;&#xff0c;从而保障应用功能的完整性&#xff0c;那么本期…

Jupyter notebook和Conda使用

Jupyter notebook和Conda使用 文章目录 Jupyter notebook和Conda使用AnacondaJupyter notebook简介页面使用技巧编写格式自动补全查看函数文档魔术命令远程访问交互式 Anaconda Anaconda是一个开源的Python发行版本&#xff0c;其包含了conda、Python等180多个科学包及其依赖项…

stm32实现esp8266连接到TCP服务器(二)未完

1.2 连接到TCP Server 1.2.1 使用网络助手&#xff0c;设立TCP服务器 ​ 编辑 1.2.2 连接服务器 ATCIPSTART"TCP","192.168.1.18",8080 //指令&#xff0c;注意双引号逗号都要半角(英文)输入 CONNECT //结果&#xff1a;成功 OK //结果&#xff1a;成功 …

jmeter中用csv data set config做参数化2

在jmeter中&#xff0c;使用csv data set config进行参数化是很重要的一个功能&#xff0c;但是这个功能的使用需要十分仔细和小心&#xff0c;因为细节之处往往决定着结果的正确与否。 举例&#xff1a; 一个登录接口用加密密码登录&#xff0c;一个登录接口用原始密码登录。…

STM32G4系列MCU的低功耗模式介绍

目录 概述 1 认识低功耗模式 1.1 低功耗模式的应用 1.2 低功耗模式介绍 2 低功耗模式的状态关系 2.1 低功耗模式可能的转换状态图 2.2 低功耗模式总结 3 运行模式 3.1 减慢系统时钟 3.2 外围时钟门控 3.3 低功耗运行模式&#xff08;LP运行&#xff09; 概述 本文主…

JavaFx学习--chapter02(网络对话)

chapter02(网络对话) 简单网络对话程序 设计任务&#xff1a;客户端向服务器发送字符串&#xff0c;并能读取服务器返回的字符串。 知识点&#xff1a;TCP套接字技术&#xff0c;C/S软件架构程序设计 重点理解&#xff1a;Java客户套接字类Socket和服务器套接字类ServerSoc…

蜜罐技术的出现究竟影响了什么

自网络诞生以来&#xff0c;攻击威胁事件层出不穷&#xff0c;网络攻防对抗已成为信息时代背景下的无硝烟战争。然而&#xff0c;传统的网络防御技术如防火墙、入侵检测技术等都是一种敌暗我明的被动防御&#xff0c;难以有效应对攻击者随时随地发起的无处不在的攻击和威胁。蜜…

linux线程 | 同步与互斥 | 互斥(下)

前言&#xff1a;本篇文章主要讲述linux线程的互斥的知识。 讲解流程为先讲解锁的工作原理&#xff0c; 再自己封装一下锁并且使用一下。 做完这些就要输出一堆理论性的东西&#xff0c; 但博主会总结两条结论&#xff01;&#xff01;最后就是讲一下死锁。 那么&#xff0c; 废…

什么是 Idempotence 以及它在哪里使用?

大家好&#xff0c;我是锋哥。今天分享关于【什么是 Idempotence 以及它在哪里使用&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; 什么是 Idempotence 以及它在哪里使用&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Idempotence&am…

【C++STL】list的基本介绍与使用方式

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f525; 所属专栏&#xff1a;C深入学习笔记 &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 一、list的介绍 文档内容以及大致翻…

ESP32-IDF 非易失存储 NVS

目录 零、前言一、基本介绍1、配置结构体1.1 nvs_entry_info_t 2、常用 API2.1 nvs_flash_init2.2 nvs_flash_init_partition2.3 nvs_flash_init_partition_ptr2.4 nvs_flash_erase2.5 nvs_flash_erase_partition2.6 nvs_flash_erase_partition_ptr2.7 nvs_flash_generate_keys…

element plus中menu菜单技巧

我在使用element plus的menu&#xff08;侧边栏&#xff09;组件的过程中遇到了一些问题&#xff0c;就是menu编写样式和路由跳转&#xff0c;下面给大家分享以下&#xff0c;我是怎么解决的。 1.页面效果 我要实现的网站布局是这样的&#xff1a; 侧边栏折叠以后的效果&#…

python爬虫快速入门之---Scrapy 从入门到包吃包住

python爬虫快速入门之—Scrapy 从入门到包吃包住 文章目录 python爬虫快速入门之---Scrapy 从入门到包吃包住一、scrapy简介1.1、scrapy是什么?1.2、Scrapy 的特点1.3、Scrapy 的主要组件1.4、Scrapy 工作流程1.5、scrapy的安装 二、scrapy项目快速入门2.1、scrapy项目快速创建…