基于标签传递的重叠社区发现算法(COPRA算法)

前言

COPRA算法[1]是Gregory在2010年提出的一种基于标签传递的社区发现算法,该算法可以看作是RAK算法[2]的改进算法。COPRA算法对RAK算法的最大改进在于其可以进行重叠社区的发现,而RAK算法只能用于非重叠社区的发现。

COPRA算法

COPRA算法在执行之初会为网络中每一个节点设置一个唯一的社区编号,一般这个社区编号就是节点的自身的ID;之后,节点会根据自己的邻居节点的社区分布决定自己的社区,简单的来说就是自己的邻居节点倾向于选择哪个社区,自己就选择哪个社区。算法在执行时会使用隶属度(Belonging Coefficient)来帮助节点决定选择哪一个社区。如果节点对于邻居节点所在社区的隶属度都低于阈值,那么节点就随机选择一个社区;最后,算法会根据一些条件来决定是否停止算法。停止条件一般分为两种:第一种是连续两次迭代社区标签数量相同;第二种是连续两次迭代社区内节点数目不变。伪代码如下:

输入:图graph(V,E),K
输出:节点的社区信息partition
1: 为每一个节点设置唯一的社区标签
2: 在没有达到终止条件前,对每一个节点重复执行:
3:     更新节点对其邻居节点所在社区的隶属度bc
4:     如果 bc < 1/K5:         排除社区标签
6:     如果所有社区标签 bc < 1/K :
7:         随机选取一个社区标签

Java代码

建立图

图的数据结构选取了邻接表

package util;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;

public class Graph {
    /**
     * 图数据结构:邻接表
     * */
    private Map<String, ArrayList<String>> adjT;
    /**
     * 节点属性列表,维护节点的id和社区信息
     * */
    private Map<String, HashSet<String>> nodeCommunityInfoPast = new HashMap<>();
    private Map<String, HashSet<String>> nodeCommunityInfoNew = new HashMap<>();

    public Graph(){
        this.adjT = new HashMap<>();
    }

    public Graph(String edgePath){
        this.adjT = new HashMap<String, ArrayList<String>>();
        try{
            BufferedReader reader = new BufferedReader(new FileReader(edgePath));
            String line = null;
            while((line=reader.readLine())!=null){
                String item[] = line.split(",");//CSV格式文件为逗号分隔符文件,这里根据逗号切分
//                System.out.println(item[1]);

                if (! this.adjT.containsKey(item[0])){
                    this.adjT.put(item[0], new ArrayList<>());
                    this.nodeCommunityInfoNew.put(item[0], new HashSet<>());
                }
                if (! this.adjT.containsKey(item[1])){
                    this.adjT.put(item[1], new ArrayList<>());
                    this.nodeCommunityInfoNew.put(item[1], new HashSet<>());
                }

                if (! this.adjT.get(item[0]).contains(item[1])){
                    this.adjT.get(item[0]).add(item[1]);
                }
                if (! this.adjT.get(item[1]).contains(item[0])){
                    this.adjT.get(item[1]).add(item[0]);
                }

            }
        }catch(Exception e){
            e.printStackTrace();
        }

    }

    /**
     * 判断节点之间是否有边
     * */
    public boolean hasEdge(String v, String w){
        return this.adjT.get(v).contains(w);
    }

    /**
     * 获取节点的邻居节点
     * */
    public ArrayList<String> neighbors(String node){
        return this.adjT.get(node);
    }

    /**
     * 获取网络中的所有节点
     * */
    public Iterable<String> nodes(){
        return this.adjT.keySet();
    }

    /**
     * 获取所有节点的社区信息
     * */
    public Map<String, HashSet<String>> getNodeCommunityInfo() {
        return this.nodeCommunityInfoPast;
    }

    /**
     * 获取节点的社区信息
     * */
    public HashSet<String> getCommnityLabel(String node){
        return this.nodeCommunityInfoPast.get(node);
    }

    /**
     * 更新节点的社区信息
     * */
    public void updateNodeCommunityLabel(String node, String cLabel){
        this.nodeCommunityInfoNew.get(node).add(cLabel);
    }

    /**
     * 在社区信息一轮更新完成后,将原始的社区信息进行覆盖
     * */
    public void coverCommunityInfo(){
        this.nodeCommunityInfoPast.clear();
        for (Map.Entry<String, HashSet<String>> entry : this.nodeCommunityInfoNew.entrySet()){
            nodeCommunityInfoPast.put(entry.getKey(), new HashSet<>(entry.getValue()));
        }
        for (Map.Entry<String, HashSet<String>> entry : this.nodeCommunityInfoNew.entrySet()){
            entry.getValue().clear();
        }
    }

}
使用COPRA算法进行社区发现

由于在项目中要处理的网络规模非常的巨大,所以算法的终止条件设置为“当连续两次迭代的社区数量不发生变化时,停止算法”。当然这样的设置并不能保证算法真正的收敛。如果网络规模较小,可以将算法的终止条件设置为“在连续两次迭代中,各个社区规模不发生变化时,停止算法”,但是这样做迭代过程会非常的长。

package community;

import util.Graph;
import java.util.*;

public class COPRA {

    public static List<String> getRandomList(List<String> paramList,int count){
        /**
         * @function: 从list中随机抽取若干不重复元素
         * @param paramList:被抽取list
         * @param count:抽取元素的个数
         * @return: 由抽取元素组成的新list
         * */
        if(paramList.size()<count){
            return paramList;
        }
        Random random=new Random();
        List<Integer> tempList=new ArrayList<Integer>();
        List<String> newList=new ArrayList<>();
        int temp=0;
        for(int i=0;i<count;i++){
            temp=random.nextInt(paramList.size());//将产生的随机数作为被抽list的索引
            if(!tempList.contains(temp)){
                tempList.add(temp);
                newList.add(paramList.get(temp));
            }
            else{
                i--;
            }
        }
        return newList;
    }

    public Map<String, HashSet<String>> divide_community(Graph graph, int v, int maxIterations){
        /**
         * @function: 使用COPRA算法划分社区
         * @graph :图
         * @v :一个节点可以属于的最大社区数
         * @maxIteration :最大迭代次数
         * */

        /**
         * 初始,为每一个节点附上唯一的社区编号
         * */
        Iterable nodes = graph.nodes();
        for (Object id : nodes){
            graph.updateNodeCommunityLabel((String)id, (String)id);
        }
        graph.coverCommunityInfo();

        Random random = new Random();
        /**
         * 更新节点社区信息
         * */
        int interations = 0;
        Map<String, Integer> communitySizePast = new HashMap<>();
        Map<String, Integer> communitySizeNow = new HashMap<>();
        Integer flag = 0;
        while (interations < maxIterations){
            for (Object id : nodes){
                /**
                 * 统计节点的邻居节点的社区分布
                 * */
                Map<String, Integer> labels_freq = new HashMap<>();
                ArrayList<String> neighbors = graph.neighbors((String)id);
                for (String n : neighbors){
                    HashSet<String> n_labels = graph.getCommnityLabel(n);
                    for (String label : n_labels){
                        if (labels_freq.keySet().contains(label)){
                            labels_freq.put(label, labels_freq.get(label) + 1);
                        }else{
                            labels_freq.put(label, 1);
                        }
                    }
                }

                int temp_count = 0;
                List<String> label_list = new ArrayList<>();
                List<String> label_list_add = new ArrayList<>();
                /**
                 * 计算节点与社区的隶属度
                 * 节点将被分配隶属度大于阈值的社区标签
                 * */
                for (Map.Entry<String, Integer> entry : labels_freq.entrySet()){

                    if (entry.getValue() / (float)neighbors.size() >= 1 / (float)v) { //
                        temp_count += 1;
                        label_list.add(entry.getKey());
//                        graph.updateNodeCommunityLabel((String)id, entry.getKey());
                    }
                }
                // 隶属度大于阈值的社区数量超过v,则随机选取v个隶属度大于阈值的社区
                if (temp_count >= v){
                    label_list_add = getRandomList(label_list, v);
                    for (String l : label_list_add){
                        graph.updateNodeCommunityLabel((String)id, l);
                    }
                //隶属度大于阈值的社区数量不超过v,则选取所有的隶属度大于阈值的社区
                }else if (temp_count > 0){
                    for (String l : label_list){
                        graph.updateNodeCommunityLabel((String)id, l);
                    }
                }
                //节点对于每一个社区的隶属度都低于阈值,随机选择一个社区
                if (temp_count == 0){
                    int maxNum = labels_freq.keySet().size();
                    int index = random.nextInt(maxNum)%(maxNum+1);
                    List<String> labels_list = new ArrayList<>(labels_freq.keySet());
                    graph.updateNodeCommunityLabel((String)id, labels_list.get(index));
                }
            }
            graph.coverCommunityInfo();
            interations += 1;

            /**
             * 前后两次社区的数量不变,则停止算法
             * */
            Map<String, HashSet<String>> partitions = graph.getNodeCommunityInfo();
            for (Map.Entry<String, HashSet<String>> entry : partitions.entrySet()){
                for (String label : entry.getValue()){
                    if (communitySizeNow.containsKey(label)){
                        communitySizeNow.put(label, communitySizeNow.get(label) + 1);
                    }else{
                        communitySizeNow.put(label, 1);
                    }
                }
            }

            Integer community_num_now = communitySizeNow.keySet().size();
            Integer community_num_past = communitySizePast.keySet().size();
            if (community_num_now.equals(community_num_past)){
                flag = 1;
            }

            // 社区数量不变,停止迭代
            if (flag.equals(1)){
                interations = maxIterations;
            }
            // 更新过去的社区信息,清空当前的社区信息
            communitySizePast.clear();
            for (Map.Entry<String, Integer> entry : communitySizeNow.entrySet()){
                communitySizePast.put(entry.getKey(), new Integer(entry.getValue()));
            }
            communitySizeNow.clear();
        }

        return graph.getNodeCommunityInfo();
    }
}
执行社区发现

程序的输入格式为csv格式,数据以边的形式组织,比如:

a,b
a,d
a,e
a,g
b,d
b,c
c,d
e,g
e,f
f,g

test.csv中的图就是文献[1]中的例子。在运行程序前,设置K为2,即节点最多可以属于两个社区;最大迭代次数为10000。
这里写图片描述

import community.COPRA;
import util.Graph;
import java.io.IOException;
import java.util.*;


public class Main {
    public static void main(String[] args) throws IOException {
        Graph graph = new Graph("./data/test.csv");
        COPRA copra = new COPRA();
        Map<String, HashSet<String>> partitions = copra.divide_community(graph, 2, 10000);
        System.out.println(partitions);

        /**
         * 统计社区数量
         * */
        Set<String> community = new HashSet<>();
        for (Map.Entry<String, HashSet<String>> entry : partitions.entrySet()){
            community.addAll(entry.getValue());
        }
        System.out.println("社区数量: " + community.size());
    }
}

程序执行后,得到的结果为:

{a=[b, g], b=[b], c=[b], d=[b], e=[g], f=[g], g=[g]}
社区数量: 2

但是该算法也具有明显的缺陷:随机性太强。几乎每一次的社区发现结果都不相同,以上的结果是正确的输出,但是这个输出是执行多次才得到的,中间很多次的社区发现结果都是错误的。在这个过程中,我也使用了第二种算法终止条件,使用该终止条件后,算法输出的结果明显可靠多了,但是因为要划分的网络过于庞大,只能选择第一种终止条件。当然,出现这种状况的原因也可能是我的程序中存在Bug,欢迎大家指正!

引用

[1]Gregory S. Finding overlapping communities in networks by label propagation[J]. New Journal of Physics, 2009, 12(10):2011-2024.
[2]Raghavan U N, Albert R, Kumara S. Near linear time algorithm to detect community structures in large-scale networks[J]. Physical Review E Statistical Nonlinear & Soft Matter Physics, 2007, 76(2):036106.

  • 11
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 重叠社区发现算法是一种用于社交网络分析的算法,可以揭示社交网络中存在的不同重叠社区结构。在Python中,可以使用NetworkX库来实现重叠社区发现算法。 首先,需要导入NetworkX库,并创建一个有向图或无向图来表示社交网络。然后,可以使用NetworkX中的相关函数来执行重叠社区发现算法。 一种常见的重叠社区发现算法是基于节点的重叠社区结构的Louvain算法。以下是一个示例代码: ``` import networkx as nx import community # 创建一个无向图表示社交网络 G = nx.Graph() # 添加节点和边 G.add_nodes_from([1, 2, 3, 4, 5]) G.add_edges_from([(1, 2), (2, 3), (3, 4), (4, 5), (1, 5)]) # 使用Louvain算法进行重叠社区发现 communities = community.greedy_modularity_communities(G) # 打印每个节点所属的重叠社区 for i, com in enumerate(communities): print("Community", i+1, ":", com) ``` 上述代码首先导入了NetworkX库,并创建了一个无向图G。然后使用Louvain算法中的greedy_modularity_communities函数找到每个节点所属的重叠社区,最后打印出每个节点所属的重叠社区。 此外,还有其他重叠社区发现算法可供选择,例如在论文《Overlapping Community Detection in Social Networks: The State-of-the-Art and Comparative Study》中介绍的一种方法称为COPRA算法。 希望以上内容对于重叠社区发现算法在Python中的实现有所帮助。 ### 回答2: 重叠社区发现算法是一种用于识别社交网络中重叠社区的方法。它能够发现网络中存在的多个社区,并且允许一个节点同时属于不同的社区。 在Python中,我们可以使用NetworkX库来实现重叠社区发现算法。NetworkX是一个用于创建、操作和研究复杂网络的Python库,具有强大的功能和易用的接口。 首先,我们需要引入NetworkX库并创建一个图对象。可以使用NetworkX提供的各种方法来加载网络数据,例如从文件中读取或手动添加节点和边。 接下来,我们可以使用现有的重叠社区发现算法来识别图中的重叠社区。NetworkX库提供了一些常见的重叠社区发现算法实现,例如Louvain算法、BigClam算法等。我们可以根据具体需求选择适合的算法。 调用重叠社区发现算法函数后,我们将获得一个包含重叠社区信息的结果对象。这个结果对象可以让我们查看每个节点属于哪些社区,并且可以对结果进行进一步的分析和可视化。 最后,我们可以根据实际需求决定如何使用重叠社区发现算法的结果。例如,我们可以根据节点在不同社区重叠程度进行节点影响力分析,或者通过比较不同社区的结构特征来进行社区比较和聚类分析。 在这个简要的解答中,我向您介绍了使用Python中NetworkX库来实现重叠社区发现算法的基本步骤。具体的实施细节和算法选择取决于实际应用的需求和网络数据的特点。 ### 回答3: 重叠社区发现算法是一种用于识别复杂网络中存在的社区结构的方法。该算法可以帮助我们理解网络的内部连接方式和节点之间的关系,从而更好地研究网络的演化规律和功能。 在Python中,可以使用第三方库NetworkX来实现重叠社区发现算法。首先,我们需要导入NetworkX库,并创建一个有向或无向的图对象: ```python import networkx as nx # 创建一个无向图对象 G = nx.Graph() ``` 然后,我们可以通过添加节点和边来构建网络结构: ```python # 添加节点 G.add_node(1) G.add_node(2) # 添加边 G.add_edge(1, 2) ``` 接下来,我们可以使用第三方库`community`的`louvain`函数来进行重叠社区发现: ```python import community # 使用Louvain算法进行重叠社区发现 partition = community.best_partition(G) # 输出每个节点所属的社区编号 for node, comm_id in partition.items(): print(f"Node {node}: Community {comm_id}") ``` 最后,我们可以根据节点的社区归属,将节点分组为不同的社区: ```python # 创建一个空的字典,用于存储每个社区的节点 communities = {} # 将节点按照所属社区归类 for node, comm_id in partition.items(): if comm_id not in communities: communities[comm_id] = [node] else: communities[comm_id].append(node) # 输出每个社区的节点 for comm_id, nodes in communities.items(): print(f"Community {comm_id}: {nodes}") ``` 通过以上代码,我们就可以实现重叠社区发现算法的功能,并将节点归类到不同的社区中。 这是一个简单的用Python实现重叠社区发现算法的示例,实际使用时,还可以根据不同的问题和需求选择其他适合的算法和库来实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值