马尔可夫聚类算法(Markov Clustering Algorithm,MCL)是一种用于图聚类的算法,广泛应用于生物信息学、社交网络分析、推荐系统等领域。
其核心思想是模拟随机游走过程,通过迭代地扩散和收缩图上的概率分布来识别图中的自然聚类或社区结构。
马尔可夫聚类算法的核心步骤
-
构建转移矩阵:
-
对于给定的图,生成转移矩阵(Markov Matrix),其中每个元素表示从一个节点转移到另一个节点的概率。
-
设图的邻接矩阵为 A,转移矩阵 M 的元素定义为
,即从节点 i 转移到节点 j 的概率是节点 i 的所有边的权重的归一化值。
-
-
扩散(扩展)操作:
-
对转移矩阵进行幂运算 Mr,通常 r=2。这相当于模拟多步随机游走,使得概率分布扩散开来。
-
扩展步骤是为了使节点间的相似性得以传播,能捕捉到较长路径上的信息。
-
-
收缩(膨胀)操作:
-
对转移矩阵的每个元素进行逐项幂操作
,然后重新归一化。通常,膨胀因子 α 取值大于1。
-
膨胀步骤是为了增强高概率路径,同时抑制低概率路径,这样能把概率集中在更紧密相关的节点上,从而分离出聚类。
-
-
归一化:
-
收缩操作后需要重新归一化矩阵,使得每行的概率和为1,以确保结果仍然是一个有效的转移矩阵。
-
-
迭代:
-
不断重复扩散和收缩操作,直到矩阵收敛,得到稳定的概率分布。收敛时,转移矩阵的列通常会收敛到一些特定的块结构,这些块对应图中的不同聚类。
-
-
解码聚类:
-
通过观察收敛后的矩阵,确定聚类。一般来说,转移矩阵的每一列的高值(接近1)集中在特定的行上,这些行就代表了同一个聚类。
-
算法优点
-
自适应性:不需要预先指定聚类的数量。
-
适应性强:可以很好地处理带有噪声的网络。
-
捕捉复杂结构:能有效捕捉到网络中的复杂拓扑结构。
算法缺点
-
参数选择:膨胀因子和扩展次数的选择对结果有较大影响。
-
计算复杂度:对于大规模图,计算量较大,需要有效的矩阵运算方法。
算法实现
import numpy as np
def add_self_loops(matrix):
"""在对角线上添加自环(self-loops)"""
np.fill_diagonal(matrix, 1)
return matrix
def normalize(matrix):
"""归一化矩阵使其行和为1"""
return matrix / np.sum(matrix, axis=1, keepdims=True)
def expand(matrix, power):
"""对矩阵进行幂次乘法"""
return np.linalg.matrix_power(matrix, power)
def inflate(matrix, power):
"""对矩阵元素逐元素幂乘"""
return normalize(np.power(matrix, power))
def converged(matrix1, matrix2, tol=1e-5):
"""检查矩阵是否收敛"""
return np.all(np.abs(matrix1 - matrix2) < tol)
def mcl(graph, expand_factor=2, inflate_factor=2, max_iterations=100, tol=1e-5):
"""执行马尔可夫聚类算法"""
# 创建初始转移矩阵
matrix = add_self_loops(graph)
matrix = normalize(matrix)
for i in range(max_iterations):
print(f"Iteration {i+1}")
previous_matrix = matrix.copy()
# 扩展
matrix = expand(matrix, expand_factor)
# 收缩(膨胀)
matrix = inflate(matrix, inflate_factor)
# 检查收敛
if converged(previous_matrix, matrix, tol):
break
return matrix
def extract_clusters(result_matrix):
"""
从 result_matrix 中提取聚类。
:param result_matrix: 马尔可夫聚类算法得到的稳定转移矩阵
:return: 每个节点的聚类标签
"""
# 获取每一行的最大值索引,这些索引代表聚类标签
cluster_assignments = np.argmax(result_matrix, axis=1)
return cluster_assignments
def group_nodes_by_cluster(cluster_assignments):
"""
将节点按聚类分组。
:param cluster_assignments: 每个节点的聚类标签
:return: 每个聚类包含的节点列表
"""
clusters = defaultdict(list)
for node, cluster in enumerate(cluster_assignments):
clusters[cluster].append(node)
return clusters
# 示例图(邻接矩阵)
graph = np.array([
[0, 1, 1, 0, 0, 0],
[1, 0, 1, 1, 0, 0],
[1, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 1, 1],
[0, 0, 0, 1, 0, 1],
[0, 0, 0, 1, 1, 0]
])
# 执行MCL
result_matrix = mcl(graph)
# 提取聚类信息
cluster_assignments = extract_clusters(result_matrix)
clusters_grouped = group_nodes_by_cluster(cluster_assignments)
print("Cluster Assignments:", cluster_assignments)
print("Grouped Clusters:", clusters_grouped)