基于GN算法(Girvan-Newman)实现社交网络中社区划分

本文介绍GN算法,一种经典的社区划分算法,通过计算边介数和模块度Q来识别网络中的社区结构。GN算法能有效揭示社交网络中功能相似或关系紧密的节点群,如空手道俱乐部成员间的社交圈。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 背景

网络结构的研究是了解整个网络结构和功能的重要途经。对于网络中社区结构的研究是了解整个网络结构和功能的重要途经。一般来说,社区结构是大规模网络中普遍存在的基本结构。网络中的顶点可以进行分组,组内顶点间的连接比较“稠密”,组间顶点的连接比较“稀疏”,比如下面这个网络结构模型。

在信息网络中,社区经常对应着功能相同,性质相近或者关系比较密切的结点集合,比如,社会关系网络中的朋友圈。GN算法是Girvan和Newman 提出的一个比较经典的社区划分算法。

2 算法思想

社区与社区之间的连接比较少,从一个社区到另一个社区至少要通过这些连接中的一条。如果找到这些重要通道并将他们移除则自然分出了社区。为了定量的描述边的“重要”程度,提出了边介数的概念。

边介数:网络中通过这条边的最短路径的数目。

一个网络的社区的划分可以有很多划分方法,哪种划分方法比较好呢?为了定量描述社区划分的好坏,Girvan和Newman 提出了模块度Q的概念,对社区进行模块化描述。对于一个可以表示为n×n矩阵的无向网络,模块化Q函数如下:

    Q= 

其中,i代表的是第i个社区,eii表示社区i的边占原始网络所有边的比例,ai表示所有连接了社区i中的顶点的边占总边数的比例。

模块度的含义其实就是一个网络在某种社区划分下与随机网络的差异,因为随机网络不具备社区结构,对应的差距越大,说明社区的划分结果越好。

查阅资料得知,Q值取值范围:-0.5到1 。

3 算法步骤

1)算每条边的边介数;

2)找出边介数最大的边,并移除;

3)计算所得社区结构Q值,寻找Q值最大的情况;

4)重新网络剩余边的边介23两步,直至网络中所有边都被移

GN的过程对应着一颗自顶向下构建的层次树,在层次树中选择一个合适的层次分割即可。例如,下图这个层次树,移除掉边介数最大的一条边,效果就像从红线那一层分割,其他同理。

4 实验复现

选取比较经典的美国一所大学空手道俱乐部成员间的人际关系的数据,使用python语言进行复现。

Python实现该社交网络的社区划分代码(完整代码以及实验数据)如下:

#util.py文件
#coding=utf-8
import networkx as nx

# 加载网络
def load_graph(path):
	G = nx.Graph()
	with open(path) as text:
		for line in text:
			vertices = line.strip().split(" ")
			source = int(vertices[0])
			target = int(vertices[1])
			G.add_edge(source, target)
	return G

# 克隆
def clone_graph(G):
	cloned_graph = nx.Graph()
	for edge in G.edges():
		cloned_graph.add_edge(edge[0], edge[1])
	return cloned_graph

# 计算Q值
def cal_Q(partition, G):
	m = len(list(G.edges()))  #边的个数
	a = []
	e = []

	# 计算每个社区的a值
	for community in partition:
		t = 0
		for node in community:
			t += len(list(G.neighbors(node)))
		a.append(t / float(2 * m))

	# 计算每个社区的e值
	for community in partition:
		t = 0
		for i in range(len(community)):
			for j in range(len(community)):
				if i != j:
					if G.has_edge(community[i], community[j]):
						t += 1
		e.append(t / float(2 * m))

	# 计算Q
	q = 0
	for ei, ai in zip(e, a):
		q += (ei - ai ** 2)

	return q



#GN.py文件
# coding=utf-8
# 首先导入包
import networkx as nx
import matplotlib.pyplot as plt
import util


class GN(object):
    """docstring for GN"""

    def __init__(self, G):
        self._G_cloned = util.clone_graph(G)
        self._G = G
        self._partition = [[n for n in G.nodes()]]
        self._max_Q = 0.0

    # GN算法
    def execute(self):
        while len(self._G.edges()) > 0:
            # 1.计算所有边的edge betweenness
            edge = max(nx.edge_betweenness(self._G).items(),
                       key=lambda item: item[1])[0]
            # 2.移去edge betweenness最大的边
            self._G.remove_edge(edge[0], edge[1])
            # 获得移去边后的子连通图
            components = [list(c) for c in list(nx.connected_components(self._G))]
            if len(components) != len(self._partition):
                # 3.计算Q值
                cur_Q = util.cal_Q(components, self._G_cloned)
                # print(cur_Q)
                if cur_Q > self._max_Q:
                    self._max_Q = cur_Q
                    self._partition = components
        return self._partition


# 可视化划分结果
def showCommunity(G, partition, pos):
    # 划分在同一个社区的用一个符号表示,不同社区之间的边用黑色粗体
    cluster = {}
    labels = {}
    for index, item in enumerate(partition):
        for nodeID in item:
            labels[nodeID] = r'$' + str(nodeID) + '$'  # 设置可视化label
            cluster[nodeID] = index  # 节点分区号

    # 可视化节点
    colors = ['r', 'g', 'b', 'y', 'm']
    shapes = ['v', 'D', 'o', '^', '<']
    for index, item in enumerate(partition):
        nx.draw_networkx_nodes(G, pos, nodelist=item,
                               node_color=colors[index],
                               node_shape=shapes[index],
                               node_size=350,
                               alpha=1)

    # 可视化边
    edges = {len(partition): []}
    for link in G.edges():
        # cluster间的link
        if cluster[link[0]] != cluster[link[1]]:
            edges[len(partition)].append(link)
        else:
            # cluster内的link
            if cluster[link[0]] not in edges:
                edges[cluster[link[0]]] = [link]
            else:
                edges[cluster[link[0]]].append(link)

    for index, edgelist in enumerate(edges.values()):
        # cluster内
        if index < len(partition):
            nx.draw_networkx_edges(G, pos,
                                   edgelist=edgelist,
                                   width=1, alpha=0.8, edge_color=colors[index])
        else:
            # cluster间
            nx.draw_networkx_edges(G, pos,
                                   edgelist=edgelist,
                                   width=3, alpha=0.8, edge_color=colors[index])

    # 可视化label
    nx.draw_networkx_labels(G, pos, labels, font_size=12)

    plt.axis('off')
    plt.show()


if __name__ == '__main__':
    # 加载网络数据并可视化
    G = util.load_graph("network/data.txt")
    pos = nx.spring_layout(G)
    nx.draw(G, pos, with_labels=True, font_weight='bold')
    plt.show()

    # GN算法
    algo = GN(G)
    partition = algo.execute()
    print(partition)

    # 可视化结果
    showCommunity(algo._G_cloned, partition, pos)


##数据文件data.txt如下
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
1 11
1 12
1 13
1 14
1 18
1 20
1 22
1 32
2 3
2 4
2 8
2 14
2 18
2 20
2 22
2 31
3 4
3 8
3 9
3 10
3 14
3 28
3 29
3 33
4 8
4 13
4 14
5 7
5 11
6 7
6 11
6 17
7 17
9 31
9 33
9 34
10 34
14 34
15 33
15 34
16 33
16 34
19 33
19 34
20 34
21 33
21 34
23 33
23 34
24 26
24 28
24 30
24 33
24 34
25 26
25 28
25 32
26 32
27 30
27 34
28 34
29 32
29 34
30 33
30 34
31 33
31 34
32 33
32 34
33 34

复现结果如下:

该空手道俱乐部初始社交网络结构:

进行社区划分之后的效果:

划分成了五个子社区,这与论文里面的实验结果也是相一致的。

5 算法优缺点

优点:计算简洁、易于程序实现;
缺点:复杂度高(由于需要不断地通过求解最短路径来求解边介数,复杂度比较高 );
 
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值