社区发现算法的个人解析

以下直接上代码:

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.

相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页