文章介绍
本文讲的是社区发现算法中的标签传播算法,是标签传播算法经典的论文。
内容介绍
社区的定义:一群节点,其内部节点之间比较相似,与外部节点之间不相似,我们认为是社区。通常社区被认为是内部节点间连接紧密,与外部节点间外部连接稀疏。
值得一提的是,社区的定义并没有一个广为人知的定义,很多的学者尝试着去定义和识别社区。
社区发现的应用有很多,这里不一一列举。
社区发现算法也有很多,此处挖个坑,有机会做个总结。
文中提到的社区发现算法:
- 删边法
根据边的中心性(最短路径经过的次数)对边进行删除,进而划分出社区。用树图来表示。
通过删除虚线经过的边,将节点划分为三个社区。
进而有人提出优化选边的标准,将边的中心性改为边的聚类系数(边属于三角形的个数),将低聚类系数的边删除,划分出社区。
2.合并法
核心思想:给定一些不同社区,然后将相似的社区进行合并。
基于模块度优化的合并算法,每次合并之前计算两个社区合并的模块度的变化。合并最大化模块度的社区。
3.标签传播算法
在这篇论文之前有人提出使用标签传播,不过之前的方法通过构建矩阵来进行更新。导致算法的时间复杂度达到了三次方。
将两个节点x和y设为1和0。其他节点均设为0.每个节点的值定义为节点邻居值的和除以邻居的个数,迭代之后每个节点得到一个介于0-1之间的值,进行排序得出社区的划分。但是这个方法只能识别两个社区。
光谱二分法,没太看明白,以后看懂了在聊。
总结:之前的算法,要么就是需要一些先验知识,比如社区的数量,节点的标签等。要么就是需要不断优化指标函数进行划分(目前来说,指标函数也不一定可以正确的代表社区结构,毕竟社区的定义都没有很官方的定义)。要么就是时间复杂度太高了,不能用在大规模的数据集。
作者天降猛男,提出不需要先验知识,也不需要优化指标函数,时间复杂度还是线性的社区发现算法–标签传播算法。
算法核心
算法基于一个假设:节点属于哪个社区由它的邻居节点定义。邻居节点属于哪个社区的数目最多,它就属于哪个社区。
更新方法:
1.同步更新,就是说本次迭代更新中,只能用上一次迭代的结果,而不能使用本次迭代更新的结果。
2.异步更新,与同步更新相反,每次迭代,都可以使用本次迭代所生成的结果。避免了一个节点有相同数目的邻居节点属于不同的社区,造成节点的震荡。该算法使用异步更新。
迭代停止条件:
理想状态是没有节点改变其标签,也就是所属社区。但是因为最大邻居数量可能一样,这时就得随机选择一个。所以如果按照理想状态可能就很难收敛了,我们只能将收敛条件改为只要我所属的社区是邻居节点最大数目之一就行。换句话说,假如节点x的邻居节点分为两个社区,且属于两个社区的邻居节点数目一致,那节点x只要属于两者之一,我们就认为稳定,即可完成迭代。
算法步骤:
1)初始化所有节点标签为他们的ID。
2)设置迭代次数t=1
3)对节点随机排序进行更新
4)对于每个节点x选择其邻居节点数目最多的社区。
5)每个节点都是选择了其邻居节点数目最多的社区,我们就认为社区划分达到稳定,就退出迭代。否则就跳到第三步 进行迭代。
算法缺点:
随机性的问题是这个算法一大弊端。因为更新顺序是随机的(中心节点不好找),其次呢,选择节点所属社区也是可能是随机的。这就造成了很大的随机性。对于同一个数据集可能每次跑的解决都不太一样。作者在文章中也做了大量实验来说明随机性的问题。后来提出将多次结果进行融合的办法,改善了划分效果。
下面是标签传播算法的一个简单实现,网上也有关于该论文的源码。
def loadData(filepath):
f = open(filepath)
vector_dict = {} # 节点标签
edge_dict = {} # 边之间的权重
for line in f.readlines():
# 读入每行数据,并进行划分,前两个数字为节点,最后一个为节点间的权重
lines = line.strip().split(' ')
for i in range(2): # 循环遍历前两个点
if lines[i] not in vector_dict: # 判断当前节点是否存在字典中
vector_dict[lines[i]] = int(lines[i]) # 初始化标签为ID
edge_list = [] # 创建边的列表,将一个节点所有的邻居节点边的信息存进去。
if len(lines) == 3: # 判断边的信息是否缺失
edge_list.append(lines[1-i] + ':' + lines[2]) # lines[1-i]是与lines[i]相连的节点,lines[2]为两节点间的权重
else: # 如果边的信息缺失,就设为1
edge_list.append(lines[1-i] + ':' + "1")
edge_dict[lines[i]] = edge_list
else: # 如果节点信息存在
edge_list = edge_dict[lines[i]] # 从节点中提取出来之前的信息。
if len(lines) == 3:
edge_list.append(lines[1 - i] + ':' + lines[2])
else:
edge_list.append(lines[1-i] + ':' + '1')
edge_dict[lines[i]] = edge_list
return vector_dict, edge_dict
# if __name__ == '__main__':
# file_path = 'E:\HM\code\LPA\label_data.txt'
# vector_dict, edge_dict = loadData(file_path)
# print(vector_dict)
# print(edge_dict)
# 检查标签是否稳定
def check(vector_dict, edge_dict):
for node in vector_dict.keys():
adjacency_node_list = edge_dict[node] # 邻居节点列表
node_label = vector_dict[node] # 当前节点标签
label = get_max_community_label(vector_dict, adjacency_node_list) # 返回最大值标签
if node_label >= label: # 标签稳定
continue # 退出循环,返回1
else: # 标签不稳定,返回0
return 0
return 1
def get_max_community_label(vector_list, adjacency_node_list):
"""
找出最大值的标签,并返回
:param vector_list: 节点标签列表
:param adjacency_node_list: 节点对于邻居节点信息的列表
:return: 返回最大值的标签
"""
label_dict = {} # 标签字典,存储了节点ID(标签)对应的权重
for node in adjacency_node_list: # 遍历邻居节点
node_id_weight = node.strip().split(':')
node_id = node_id_weight[0]
node_weight = int(node_id_weight[1])
if vector_list[node_id] not in label_dict: # 节点不在标签字典中
label_dict[vector_list[node_id]] = node_weight
else: # 节点存在标签字典中
label_dict[vector_list[node_id]] += node_weight
sort_list = sorted(label_dict.items(), key=lambda d: d[1], reverse=True) # 降序排列标签列表
return sort_list[0][0] # 返回最大权重的标签
def label_propagation(vector_dict, edge_dict):
"""
标签传播函数
:param vector_dict: 节点:标签字典
:param edge_dict: 存储节点与另邻居节点边的信息
:return: 更新后的vector_dict
"""
t = 0
print('First label:')
while True:
if check(vector_dict, edge_dict) == 0: # 标签不稳定时进行标签传播
t = t + 1
print('iteration: ', t)
for node in vector_dict.keys(): # 更新所有节点的标签
adjecancy_node_list = edge_dict[node]
vector_dict[node] = get_max_community_label(vector_dict, adjecancy_node_list)
else: # 标签稳定退出循环
break
return vector_dict # 返回节点标签列表
if __name__ == '__main__':
file_path = 'E:\HM\code\LPA\label_data.txt'
vector, edge = loadData(file_path)
print("load and initial the community....")
print(vector)
print(edge)
print("start LPA clustering....")
vector_dict = label_propagation(vector, edge)
print("ending LPA clustering....")
print(vector_dict)
cluster_group = dict() # 聚类结果字典,保存了每个聚类结果对应的节点
for node in vector_dict.keys():
cluster_id = vector_dict[node] # 聚类结果
print('node: {}, cluster_id: {}'.format(node, cluster_id))
if cluster_id not in cluster_group.keys():
cluster_group[cluster_id] = [node] # 将同一聚类结果的节点存在一个列表中
else:
cluster_group[cluster_id].append(node)
print(cluster_group)