社区发现算法的个人解析
以下直接上代码:
def greedy_modularity_communities(G, weight=None):
N = len(G.nodes()) # 节点数
m = len(G.edges()) # 边数
q0 = 1.0 / (2.0*m)
label_for_node = dict((i, v) for i, v in enumerate(G.nodes())) # 将每一个节点都赋予其位置,例如第一个点标号为0, 第二个点标号为1,以此类推
# label_for_node = { 0:node1, 1:node2, 2:node3, ...}
node_for_label = dict((label_for_node[i], i) for i in range(N)) # 与上述定义的位置正相反
# node_for_label = { node1:0, node2:1, node3:2, ...}
k_for_label = G.degree(G.nodes(), weight=weight) # 每个节点的度
k = [k_for_label[label_for_node[i]] for i in range(N)] # 对于N个节点从0到N-1标号位置,各个位置上节点的度数
communities = dict((i, frozenset([i])) for i in range(N)) # 初始化社区划分
merges = [] # 初始化合并结果
partition = [[label_for_node[x] for x in c] for c in communities.values()] # 初始化分区方式
partition_id = [[x for x in c] for c in communities.values()] # 这一行个人认为无用
q_cnm = modularity(G, partition) # 初始化模块度
# 接下来初始化各个数据结构
# CNM Eq 8-9 (Eq 8 was missing a factor of 2 (from A_ij + A_ji)
# a[i]: i社区i中的边数对总边数的占比
# dq_dict[i][j]: 合并社区i, j所产生的dq增益度变化值字典,初始值将每一个点视作一个社区
# dq_heap[i][n] : dq变化值字典的堆表示,最大的排在前面,小的排在后面,便于取出最大值
# H[n]: (-dq, i, j) dq_ij中最大的n个值组成的向量,n的个数初始时为有边的节点的个数
a = [k[i]*q0 for i in range(N)] # a[i]组成的向量
dq_dict = dict(
(i, dict(
(j, 2*q0 - 2*k[i]*k[j]*q0*q0)
for j in [
node_for_label[u]
for u in G.neighbors(label_for_node[i])]
if j != i))
for i in range(N)) # 增益度矩阵
dq_heap = [
MappedQueue([
(-dq, i, j)
for j, dq in dq_dict[i].items()])
for i in range(N)] # 增益度最大堆
H = MappedQueue([
dq_heap[i].h[0]
for i in range(N)
if len(dq_heap[i]) > 0]) # 最大堆中前n个最大值
# 开始合并社区,直到不可以提升模块度
while len(H) > 1:
# 寻找最优的合并
# 移除堆中每一行的最大值
# Ties will be broken by choosing the pair with lowest min community id
try:
dq, i, j = H.pop() # 将堆向量中的最大值弹出
except IndexError:# 这里做了异常捕获
break
dq = -dq # 因为MappedQueue数据结构中是最小堆,且前面形成堆时均采用的-dq,故在这里需要转换为正值才能正确表示dq
dq_heap[i].pop()# 在堆中移除关于i最好的合并结果
# Push new row max onto H
if len(dq_heap[i]) > 0: # 此时若i还有邻居可以合并,则需要在H向量中添加一个新的增益度最大值元组,在这里也就是其h[0]号元素
H.push(dq_heap[i].h[0])
# If this element was also at the root of row j, we need to remove the
# duplicate entry from H
if dq_heap[j].h[0] == (-dq, j, i): # 考虑到若j的最佳合并也为i时,需要重复操作,就移除此值,并且在H中用j的次最大值替换
H.remove((-dq, j, i))
# Remove best merge from row j heap
dq_heap[j].remove((-dq, j, i))
# Push new row max onto H
if len(dq_heap[j]) > 0: # j也一样,移除之后若还可以和邻居合并,则在H中新增可合并的增益度的最大值元组
H.push(dq_heap[j].h[0])
else:
# Duplicate wasn't in H, just remove from row j heap
dq_heap[j].remove((-dq, j, i)) # 若为单向边,则直接移除,因为其本身不在H中也不会在H中了
# Stop when change is non-positive
if dq <= 0: # 假如dq小于0则可以退出循环,(我也不太理解)
break
communities[j] = frozenset(communities[i] | communities[j])# 合并
del communities[i] # 删除原社区i
merges.append((i, j, dq)) # 添加合并结果
q_cnm += dq # 更新模块度
# Get list of communities connected to merged communities
i_set = set(dq_dict[i].keys()) # i节点的邻居集合
j_set = set(dq_dict[j].keys()) # j节点的邻居集合
all_set = (i_set | j_set) - set([i, j]) # i和j的邻居总集合,注意这里i的邻居可能有j,j的邻居可能有i,故需要减去
both_set = i_set & j_set # 同时为i和j邻居集合
# 合并i社区和j社区,并且更新增益度
for k in all_set:# 这里以j社区为更新后的结果接受器,i社区已经在上面删除了,用k来遍历其余邻居来做出修改
# Calculate new dq value
if k in both_set: # 参照论文Eq 10
dq_jk = dq_dict[j][k] + dq_dict[i][k]
elif k in j_set:
dq_jk = dq_dict[j][k] - 2.0*a[i]*a[k]
else:
# k在i的邻居集合中的情况
dq_jk = dq_dict[i][k] - 2.0*a[j]*a[k]
# 更新第j行和第k行
for row, col in [(j, k), (k, j)]:
# 保存旧的jk增益度元组,便于后续判断及更新
if k in j_set:
d_old = (-dq_dict[row][col], row, col)
else: # 若没有则置空
d_old = None
# 为[j][k]位置更新增益度矩阵,dq_dict[i]在下面将被删除
dq_dict[row][col] = dq_jk
# 保存旧的j,k行的增益度最大值,前提为j行或k行还有邻居可以进行合并,为了后续的判断和更新
if len(dq_heap[row]) > 0:
d_oldmax = dq_heap[row].h[0]
else:
d_oldmax = None
# 更新增益度堆中的值
d = (-dq_jk, row, col)
if d_old is None:
# 假如k(j)以前不是j(k)的邻居,则需要新增该值,因为该元组之前不存在
dq_heap[row].push(d)
else:
# 假如jk以前时邻居,则更新该值就好了
dq_heap[row].update(d_old, d)
# 必要时更新行向量最大堆
if d_oldmax is None:
# No entries previously in this row, push new max
# 同理,若旧的该行(该行已被更新)没有邻居可以合并则添加合并后的增益度最大值
H.push(d)
else:
# 此时为了保证H向量中的为最大值,这里做了判断更新前后是否最大值发生了改变,最终更新的仍为最大值
if dq_heap[row].h[0] != d_oldmax:
H.update(d_oldmax, dq_heap[row].h[0])
# 上述都是对更新后接受结果的j行以及i和j的邻居节点做出的改变
# 以下是对更新后弃用的i行做出的改变
# Remove row/col i from matrix
i_neighbors = dq_dict[i].keys()
for k in i_neighbors:
dq_old = dq_dict[k][i]# 先保留 k和i的旧增益度用于后续操作
del dq_dict[k][i]# 同时删除该位置
if k != j:
# 行和列均移除与i相邻接的节点, 除了j
for row, col in [(k, i), (i, k)]:
# 这里移除时还要检查是否为dq中的最大值,若为其最大值则需要弃用,因为本次计算结束了
d_old = (-dq_old, row, col)
if dq_heap[row].h[0] == d_old:
dq_heap[row].remove(d_old)
H.remove(d_old)
# Update row max
if len(dq_heap[row]) > 0:# 若第row行还可以有邻居合并,也要更新其增益度向量
H.push(dq_heap[row].h[0])
else:
# 如果不是则直接移除,因为其根本没有出现在H中
dq_heap[row].remove(d_old)
del dq_dict[i] # 最后删除增益度矩阵第i行
# Mark row i as deleted, but keep placeholder
dq_heap[i] = MappedQueue() # 在堆中也做出相似的操作,只不过需要保留其位置,以免后续遍历的时候报错IndexError
# Merge i into j and update a
a[j] += a[i] # 记得改变j社区的边数之比
a[i] = 0 # 对弃用的i社区比值置0
communities = [
[label_for_node[i] for i in c]
for c in communities.values()] # 筛选出最终的社区分类结果
return sorted(communities, key=len, reverse=True) # 最后返回值
References:
[1] M. E. J Newman 'Networks: An Introduction', page 224 Oxford University Press 2011.
[2] Clauset, A., Newman, M. E., & Moore, C. "Finding community structure in very large networks." Physical Review E 70(6), 2004.

浙公网安备 33010602011771号