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)重新计算网络中剩余边的边介数,重复2、3两步,直至网络中所有边都被移除 。
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
复现结果如下:
该空手道俱乐部初始社交网络结构:
进行社区划分之后的效果:
划分成了五个子社区,这与论文里面的实验结果也是相一致的。