COPRA算法
LPA 算法简单直观 ,易于理解。而且求解准确性很高(指的是运气好的时候,毕竟随机性太大了hh)无需指定社区个数等其他任何参数.最主要是算法时间复杂度很低.接近线性但是 LPA 算法存在两个问题 :
- 第一,其稳定性较差,原因是社区间标签易传播 ,当一个节点存在多个可选标签时.随机地选择其中一个 . 对于不同的随机选择会产生不同的社区发现结果
- 第二 ,在现实生活中,很多节点可能同时属于多个标签 .而 LPA 算法是无法挖掘出重叠社区结构的.
对此,基于LPA算法,引入了新的标签结构(c,b),其中,c表示社区标识符(它可能属于哪个社区),b表示节点x在社区c中的从属系数(它属于该社区的可能性),且0≤b≤1。顶点x的所有从属系数之和等于1,通过 b t b_t bt(c,b)表示迭代次数t时的顶点x对于社区c的从属系数,N(x)表示顶点x的邻接顶点集。具体计算公式为:
b t ( c , x ) = ∑ y ∈ N ( x ) b t − 1 ( c , y ) ∣ N ( x ) ∣ b_t(c,x) = {{\displaystyle \sum_{y\in N(x)}b_{t-1}(c,y)}\over{|N(x)|}} bt(c,x)=∣N(x)∣y∈N(x)∑bt−1(c,y)
每次传播过程都是将邻居节点的归属系数取并集进行归一化。
其中,N(x)是节点x的所有邻居节点。t是迭代次数。该公式的意义是计算x节点所有邻居节点属于c社区的隶属度之和与邻居节点数的比值。
迭代t中一个顶点的标签总是基于迭代t−1中它相邻的标签。
因为我们需要一个节点保存多个社区标识符信息,而不是所有的社区标识符信息。所以我们会删除那些低于阈值1/v的社区信息,v是超参数,表示一个节点最多可以属于几个社区。
从标签向量中删除社区对之后,将剩余的隶属度进行归一化(分别乘(除)以一个常数,使其之和为1)。
假设v为2,故阈值为1/v=0.5,低于此阈值的标签信息都会被删除,如果一个节点中所有的标签都低于此阈值就随机选择一个。删除标签后的网络信息为:
比如上图节点b三个从属系数都为1/3,都小于阈值1/2,所以都删除,并且随机选择一个,这里假如选择了c,然后归一化,从属系数为1,b的标签变为(c,1)。
对于节点c,从属系数都为1/2大于阈值,都合格,按公式计算{(c,1)+(c,1)}/2 所以结果为{(c,1}.
其它节点也类似,然后网络信息就变成了下图。
对于v<2时,COPRA算法就变成了LPA算法。
与LPA算法一样,COPRA可能具有高度的不确定性,一个顶点的社区通常是由随机选择决定的。然而,在真实的网络中,COPRA通常比其他测试算法产生更好的结果(就模块化而言)。
算法流程如下:
评价指标:扩展Q函数
Dolphin 数据集是 D.Lusseau 等人使用长达 7 年的时间观察新西兰 Doubtful Sound海峡 62 只海豚群体的交流情况而得到的海豚社会关系网络。这个网络具有 62 个节点,159 条边。节点表示海豚,而边表示海豚间的频繁接触
代码与数据下载github
Python代码如下:
class COPRA:
def __init__(self, G, T, v):
"""
:param G:图本身
:param T: 迭代次数T
:param r:满足社区次数要求的阈值r
"""
self._G = G
self._n = len(G.nodes(False)) # 节点数目
self._T = T
self._v = v
def execute(self):
# 建立成员标签记录
# 节点将被分配隶属度大于阈值的社区标签
lablelist = {i: {i: 1} for i in self._G.nodes()}
for t in range(self._T):
visitlist = list(self._G.nodes())
# 随机排列遍历顺序
np.random.shuffle(visitlist)
# 开始遍历节点
for visit in visitlist:
temp_count = 0
temp_label = {}
total = len(self._G[visit])
# 根据邻居利用公式计算标签
for i in self._G.neighbors(visit):
res = {key: value / total for key, value in lablelist[i].items()}
temp_label = dict(Counter(res) + Counter(temp_label))
temp_count = len(temp_label)
temp_label2 = temp_label.copy()
for key, value in list(temp_label.items()):
if value < 1 / self._v:
del temp_label[key]
temp_count -= 1
# 如果一个节点中所有的标签都低于阈值就随机选择一个
if temp_count == 0:
b = random.sample(temp_label2.keys(), 1)
temp_label = {b[0]: 1}
# 否则标签个数一定小于等于v个 进行归一化即可
else:
tsum = sum(temp_label.values())
temp_label = {key: value / tsum for key, value in temp_label.items()}
lablelist[visit] = temp_label
communities = collections.defaultdict(lambda: list())
# 扫描lablelist中的记录标签,相同标签的节点加入同一个社区中
for primary, change in lablelist.items():
for label in change.keys():
communities[label].append(primary)
# 返回值是个数据字典,value以集合的形式存在
return communities.values()```
参考结果:
>D:\anaconda3\python.exe D:/APTX-4869/CommunityDetection/CORPA.py
0 [0, 40, 10, 14, 47, 15, 42, 36, 28, 2, 44, 61, 3, 8, 59, 4, 51, 30, 37, 45, 20, 29, 11, 12, 33, 34, 38, 43, 16, 50, 52, 24, 18, 55, 21, 23, 35, 49, 39, 58, 46, 53]
1 [1, 41, 17, 19, 54, 26, 27, 5, 56, 9, 13, 57, 6, 7, 30, 32, 22, 25, 31, 60, 39, 48]
2 [4, 51, 11, 24, 55, 21, 23, 35]
0.3655
算法执行时间0.028006553649902344