社区发现算法 - Fast Unfolding(Louvian)算法初探

1. 社团划分

0x1:社区是什么

在社交网络中,用户至关于每个点,用户之间经过互相的关注关系构成了整个网络的结构。html

在这样的网络中,有的用户之间的链接较为紧密,有的用户之间的链接关系较为稀疏。其中链接较为紧密的部分能够被当作一个社区,其内部的节点之间有较为紧密的链接,而在两个社区间则相对链接较为稀疏。node

整个总体的结构被称为社团结构。以下图,红色的黑色的点集呈现出社区的结构,python

用红色的点和黑色的点对其进行标注,整个网络被划分红了两个部分,其中,这两个部分的内部链接较为紧密,而这两个社区之间的链接则较为稀疏。git

如何去划分上述的社区便称为社区划分的问题。github

0x2:社区划分的出发点和意图

直观地说,community detection的通常目标是要探测网络中的“块”cluster或是“社团”community。算法

这么作的目的和效果有许多,好比说机房里机器的链接方式,这里造成了网络结构,那么,哪些机器能够视做一个“块”?进一步地,什么样的链接方式才有比较高的稳定性呢?若是咱们想要让这组服务瘫痪,选择什么样的目标呢?

咱们再看一个例子,word association network。即词的联想/搭配构成的网络: 网络

咱们用不一样的颜色对community进行标记,能够看到这种detection获得的结果颇有意思。app

这个网络从词bright开始进行演化,到后面分别造成了4个组:ColorsLightAstronomy & Intelligenceide

能够说以上这4个词能够较好地归纳其所在community的特色(有点聚类的感受);另外,community中心的词,好比color, Sun, Smart也有很好的表明性(自动提取摘要)。函数

同时咱们注意到,那些处在交叠位置的词呢,好比Bright、light等词,他们是同义项比较多的词。这个图也揭示出了这一层含义。

0x3:节点间存在链接的抽象本质 - 逻辑拓朴结构

社区的节点间是网络拓朴结构,即节点间是存在拓朴链接结构的,咱们不能将其和欧式空间或者P空间中的点向量集合空间混为一谈

以欧式空间为例,不一样的节点向量存在于不一样的空间位置中,向量夹角近的点向量彼此距离近,而向量夹角远的向量彼此距离远。可是即便是欧式距离很近的向量点,也不必定就表明这它们之间存在拓朴链接关系,只能说在必定的度量下(例如欧式距离度量),这两个节点很相近。

可是在社区结构中,节点之间没有什么空间位置的概念。相对的,节点间存在的是一种逻辑拓朴结构,即存在一种共有关系

存在共有关系的节点在逻辑上会汇集为一个社区,而社区以前不存在或者存在很弱的共有关系,则呈现分离的逻辑拓朴结构。

读者朋友必定要注意不要用空间结构的概念来试图理解社区结构,否则会陷入理解的困境,社区中的节点只是由于逻辑上的共有关系而汇集在一块儿而已,彼此之间的位置也没有实际意义,而社区族群之间的分离也是表达一种逻辑上的弱共有关系。

举一些实际的例子:

1. 节点表明消费者:节点间的链接表明了它们共同购买了一批书籍,weight表明共同购买的书籍数;
2. 节点表明DNS域名:节点间的链接表明了它们拥有一批共同的src client ip(客户端),weight表明了共同的src_ip数量;

0x4:何时可使用社区发现算法

下面这句话很明确地说明了在什么业务场景下可使用社区发现算法:

(Newman and Gievan 2004) A community is a subgraph containing nodes which are more densely linked to each other than to the rest of the graph or equivalently, a graph has a community structure if the number of links into any subgraph is higher than the number of links between those subgraphs.

即咱们须要先肯定要解决的业务场景中,存在明显的汇集规律,节点(能够是抽象的)之间造成必定的族群结构,而不是呈现无规律的随机分散。同时另外一方面,这种汇集的结构是“有意义的”,这里所谓的有意义是指这种汇集自己能够翻译为必定的上层业务场景的表现。

可是不少时候,咱们业务场景中的数据集之间的共有关系并非表现的很明显,即节点之间互相都或多或少存在一些共有关系,这样直接进行社区发现效果确定是很差的。

因此一个很重要点是,咱们在进行社区发现以前,必定要进行数据降噪

理想状况下,降噪后获得的数据集已是社区彻底内聚,社区间彻底零链接,这样pylouvain只要一轮运行就直接获得结果。固然实际场景中不可能有这么好的状况,数据源质量,专家经验的丰富程度等等都会影响降噪的效果,通常状况下,降噪只要能cutoff 90%以上的噪音,pylouvain就基本能经过几轮的迭代完成总体的社区发现过程。

0x5:社区划分的思路概要

什么样的结构能成为团?一种很直观的想法是,同一团内的节点链接更紧密,即具备更大的density。

接下来的问题是,什么样的metrics能够用来描述这种density?Louvian 定义了一个数值上的概念(本质上就是一个目标函数),有了这个目标函数,就能够引出接下来要讨论的 method based on modularity optimization

要注意的,社区划分有不少不一样的算法,本文讨论的 Fast Unfolding(Louvian)只是其中一种,而这种所谓的density密度评估方法也其实其中一种思想,不要固话地认为社区划分就只有这一种方法。

Relevant Link:

https://stackoverflow.com/questions/21814235/how-can-modularity-help-in-network-analysis 
http://iopscience.iop.org/article/10.1088/1742-5468/2008/10/P10008/fulltext/
https://www.researchgate.net/publication/1913681_Fast_Unfolding_of_Communities_in_Large_Networks?enrichId=rgreq-d403e26a5cb211b7053c36946c71acb3-XXX&enrichSource=Y292ZXJQYWdlOzE5MTM2ODE7QVM6MTAxOTUyNjc5NTc5NjY3QDE0MDEzMTg4MjE3ODA%3D&el=1_x_3&_esc=publicationCoverPdf
https://www.jianshu.com/p/4ebe42dfa8ec
https://blog.csdn.net/u011089523/article/details/79090453
https://blog.csdn.net/google19890102/article/details/48660239
《Fast Unfolding of Communities in Large Networks》

 

2. LOUVAIN算法模型

Louvain算法是一种基于多层次(逐轮启发式迭代)优化Modularity的算法。Modularity函数最初被用于衡量社区发现算法结果的质量,它可以刻画发现的社区的紧密程度。
同时,Modularity函数既然能刻画社区的紧密程度,也就可以被用来看成一个优化函数(目标函数),即将结点加入它的某个邻居所在的社区中,若是可以提高当前社区结构的modularity。则说明此次迭代优化是可接受的。
下面咱们来讨论Louvain算法模型的核心组件。

0x1:Modularity的定义 - 描述社区内紧密程度的值Q

模块度是评估一个社区网络划分好坏的度量方法,它的物理含义是社区内节点的连边数与随机状况下的边数只差,它的取值范围是 [−1/2,1),其定义以下:

A为邻接矩阵,Aij表明了节点 i 和节点 j 之间 边的权重,网络不是带权图时,全部边的权重能够看作是 1;

是全部与节点 i 相连的 边的权重之和(度数),kj也是一样;

表示全部边的权重之和(边的数目),充当归一化的做用;

是节点 i 的社区, 函数表示若节点 i 和节点 j 在同一个社区内,则返回 1,不然返回 0;

模块度的公式定义能够做以下简化:

其中 Σin 表示社区 C 内的边的权重之和;Σtot 表示与社区 C 内的节点相连的全部边的权重之和。

上面的公式还能够进一步简化成:

这样模块度也能够理解是:

首先modularity是针对一个社区的全部节点进行了累加计算。

modularity Q的计算公式背后体现了这种思想:社区内部边的权重减去全部与社区节点相连的边的权重和,对无向图更好理解,即社区内部边的度数减去社区内节点的总度数。

能够直观去想象一下,若是一个社区节点彻底是“封闭的(即全部节点都互相内部链接,可是不和社区外部其余节点有链接,则modularity公式的计算结果为1)”

基于模块度的社区发现算法,都是以最大化模块度Q为目标。能够看到,这种模型能够支持咱们经过策略优化,去不断地构造出一个内部汇集,外部稀疏链接的社区结构

在一轮迭代后,若整个 Q 没有变化,则中止迭代,不然继续迭代,直至收敛。

0x2:模块度增量 delta Q

模块增益度是评价本次迭代效果好坏的数值化指标,这是一种启发式的优化过程。相似决策树中的熵增益启发式评价

表明由节点 i 入射集群 C 的权重之和;表明入射集群 C 的总权重;ki 表明入射节点 i 的总权重;

在算法的first phase,判断一个节点加入到哪一个社区,须要找到一个delta Q最大的节点 i,具体的算法咱们后面会详细讨论,这里只须要记住 delta Q的做用相似决策树中的信息增益评估的做用,它帮助整个模型向着Modularity不断增大的方向去靠拢。

 

3. LOUVAIN算法策略

Louvain算法是基于模块度的社区发现算法,该算法在效率和效果上都表现较好,而且可以发现层次性的社区结构,其优化目标是:最大化整个社区网络的模块度。

即让整个社区网络呈现出一种模块汇集的结构。

0x1:算法思想的联想

Louvain算法包括两个阶段,在步骤一它不断地遍历网络中的结点,尝试将单个结点加入可以使modularity提高最大的社区中,直到全部结点都再也不变化。在步骤二,它处理第一阶段的结果,将一个个小的社区归并为一个超结点来从新构造网络,这时边的权重为两个结点内全部原始结点的边权重之和。迭代这两个步骤直至算法稳定。
从核心思想上来看,Louvain算法的分步迭代优化过程,和EM优化算法有殊途同归之妙。
同时有一个题外话值得注意,Louvain算法是一个迭代算法,每一轮迭代都会产出一个当前局部最优的社区结构,因此理论上,假如算法迭代了5次,咱们能够获得5个不一样粒度层次的社区结构,从业务场景上,这为咱们发现不一样的社区汇集提供了一个更灵活的视角。
 
关于社区发现算法能够应用在哪些领域,我也还在思考中,从这个算法的思想上来看,我倾向于认为社区发现算法比较适合发现一种"抽象泛共现模式",这种共现是一种泛化的共现,它能够是任何形式的共现,例如
1. 两台主机拥有相似的网络对外发包模式 2. 两台主机间拥有累计的event log序列 3. 两个攻击payload拥有相似的词频特征,能够认为是同一组漏洞利用方式 4. 在netword gateway上发现了相似的网络raw流量,也能够反过来用一直的label流量特征进行有监督的聚类 ..

社区发现可能能够提供一种更高层的视角来看待总体的大盘状况,具体的应用场景还须要不断的摸索。

0x2:关于启发式/贪婪思想的社区发现的进一步思考

社区发现算法,或者说在社区发现的项目中,很容易遇到的一个问题就是:“社区过大,将过多的outerlier包括到了社区中”,换句话说,社区聚类的过程当中没有能及时收敛。

咱们来看下面这张图:

若是按照启发式/贪婪思想进行”one-step one node“的社区聚类,O九、O十、O11会被先加入到社区D中,由于在每次这样的迭代中,D社区内部的紧密度(无论基于node密度仍是edge得modularity评估)都是不断提升,符合算法的check条件,所以,O九、O十、O11会被加入到社区D中。

随后,O1 ~ O8也会被逐个被加入到社区D中,加入的缘由和O九、O十、O11被加入是同样的。

从局部上来看,这些步骤是合理的,可是若是从上帝视角的全局来看,这种作法致使outerlier被错误的聚类到的社区中,致使precision降低。

解决这种问题的一个办法我以为能够从CNN/DNN的作法中获得灵感,即设置一个Delta增益的阈值,即在每轮的迭代中(社区扩增后紧密度提高的度量)若是不能超过这个阈值,则断定为收敛成功,当即中止算法迭代。

0x3:社区发现(聚类)的效果很是依赖于weight权重计算的策略和方法

louvain社区发现是将一个无向权重图,转化为多个节点集合,每一个节点集合表明了一个社区。

社区发现的思想是很是直接简单的,由于聚类的效果很是依赖于weight权重计算的方法,咱们选择的权重计算方法必需要可以“很好地”在值域空间中分离开来,不然会致使overcluster。

举个例子,假设咱们有一组节点间权重,咱们按列的形式写出来:

A - B:2
A - C:2
B - C:2
D - E:12(明显和2不同)
D - F:13
E - F:14
F - G:11
A - D:1(社区间存在弱关系)
B - F:1

上述社区中,咱们很容易理解,应该分为两个社区:

A/B/C、D/E/F

由于这2个社区知足社区内内聚,社区间相异的社区拓朴结构特性。

反过来能够想象,若是weight权重的计算公式不够合理,社区间的关系(A-D、B-F)和各自的社区weight很接近,则pylouvain就没法很好的将两个社区区分开来了。由于在pylouvain看来,它们也属于社区的一部分。

 

4. LOUVAIN算法流程

0x1:算法形式化描述

1)初始化:

将图中的每一个节点当作一个独立的社区,社区的数目与节点个数相同;

2)开始first phase迭代 - 社区间节点转移:

对每一个节点i,依次尝试把节点 i 分配到其每一个邻居节点所在的社区,计算分配前与分配后的模块度变化ΔQ,并记录ΔQ最大的那个邻居节点,若是maxΔQ>0,则把节点 i 分配ΔQ最大的那个邻居节点所在的社区,不然保持不变; 

3)重复2)- 继续进行社区间节点转移评估:

直到全部节点的所属社区再也不变化,即社区间的节点转移结束,能够理解为本轮迭代的 Local Maximization 已达到;

4)second phase - Rebuilding Graph:

由于在这轮的first phase中,社区 C 中新增了一个新的节点 i,而 i 所在的旧的社区少了一个节点,所以须要对整个图进行一个rebuild。

对图进行重构,将全部在同一个社区的节点重构成一个新社区,社区内节点之间的边的权重更新为新节点的环的权重,社区间的边权重更新为新节点间的边权重;

5)重复2)- 继续开始下一轮的first/second phase:

直到整个图的模块度再也不发生变化。

0x2:算法时间复杂度

从流程来看,该算法可以产生层次性的社区结构,其中计算耗时较多的是最底一层的社区划分,节点按社区压缩后,将大大缩小边和节点数目,
而且计算节点 i 分配到其邻居 j 时,模块度的变化只与节点 i、j 的社区有关,与其余社区无关,所以计算很快。
在论文中,把节点 i 分配到邻居节点 j 所在的社区 c 时模块度变化为:

DeltaQ 分了两部分,前面部分表示把节点i加入到社区c后的模块度,后一部分是节点i做为一个独立社区和社区c的模块度

Relevant Link:
https://blog.csdn.net/xuanyuansen/article/details/68941507
https://www.cnblogs.com/fengfenggirl/p/louvain.html 
http://www.cnblogs.com/allanspark/p/4197980.html 
https://github.com/gephi/gephi/wiki
https://blog.csdn.net/qq547276542/article/details/70175157

 

5. A Python implementation of the Louvain method to find communities in large networks

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
    Implements the Louvain method.
    Input: a weighted undirected graph
    Ouput: a (partition, modularity) pair where modularity is maximum
'''
class PyLouvain:

    '''
        Builds a graph from _path.
        _path: a path to a file containing "node_from node_to" edges (one per line)
    '''
    @classmethod
    def from_file(cls, path):
        f = open(path, 'r')
        lines = f.readlines()
        f.close()
        nodes = {}
        edges = []
        for line in lines:
            n = line.split()
            if not n:
                break
            nodes[n[0]] = 1
            nodes[n[1]] = 1
            w = 1
            if len(n) == 3:
                w = int(n[2])
            edges.append(((n[0], n[1]), w))
        # rebuild graph with successive identifiers
        nodes_, edges_ = in_order(nodes, edges)
        print("%d nodes, %d edges" % (len(nodes_), len(edges_)))
        return cls(nodes_, edges_)

    '''
        Builds a graph from _path.
        _path: a path to a file following the Graph Modeling Language specification
    '''
    @classmethod
    def from_gml_file(cls, path):
        f = open(path, 'r')
        lines = f.readlines()
        f.close()
        nodes = {}
        edges = []
        current_edge = (-1, -1, 1)
        in_edge = 0
        for line in lines:
            words = line.split()
            if not words:
                break
            if words[0] == 'id':
                nodes[int(words[1])] = 1
            elif words[0] == 'source':
                in_edge = 1
                current_edge = (int(words[1]), current_edge[1], current_edge[2])
            elif words[0] == 'target' and in_edge:
                current_edge = (current_edge[0], int(words[1]), current_edge[2])
            elif words[0] == 'value' and in_edge:
                current_edge = (current_edge[0], current_edge[1], int(words[1]))
            elif words[0] == ']' and in_edge:
                edges.append(((current_edge[0], current_edge[1]), 1))
                current_edge = (-1, -1, 1)
                in_edge = 0
        nodes, edges = in_order(nodes, edges)
        print("%d nodes, %d edges" % (len(nodes), len(edges)))
        return cls(nodes, edges)

    '''
        Initializes the method.
        _nodes: a list of ints
        _edges: a list of ((int, int), weight) pairs
    '''
    def __init__(self, nodes, edges):
        self.nodes = nodes
        self.edges = edges
        # precompute m (sum of the weights of all links in network)
        #            k_i (sum of the weights of the links incident to node i)
        self.m = 0
        self.k_i = [0 for n in nodes]
        self.edges_of_node = {}
        self.w = [0 for n in nodes]

        for e in edges:
            self.m += e[1]
            self.k_i[e[0][0]] += e[1]
            self.k_i[e[0][1]] += e[1] # there's no self-loop initially
            # save edges by node
            if e[0][0] not in self.edges_of_node:
                self.edges_of_node[e[0][0]] = [e]
            else:
                self.edges_of_node[e[0][0]].append(e)
            if e[0][1] not in self.edges_of_node:
                self.edges_of_node[e[0][1]] = [e]
            elif e[0][0] != e[0][1]:
                self.edges_of_node[e[0][1]].append(e)
        # access community of a node in O(1) time
        self.communities = [n for n in nodes]
        self.actual_partition = []


    '''
        Applies the Louvain method.
    '''
    def apply_method(self):
        network = (self.nodes, self.edges)
        best_partition = [[node] for node in network[0]]
        best_q = -1
        i = 1
        while 1:
            i += 1
            partition = self.first_phase(network)
            q = self.compute_modularity(partition)
            partition = [c for c in partition if c]
            # clustering initial nodes with partition
            if self.actual_partition:
                actual = []
                for p in partition:
                    part = []
                    for n in p:
                        part.extend(self.actual_partition[n])
                    actual.append(part)
                self.actual_partition = actual
            else:
                self.actual_partition = partition
            if q == best_q: # 若是本轮迭代modularity没有改变,则认为收敛,中止
                break
            network = self.second_phase(network, partition)
            best_partition = partition
            best_q = q
        return (self.actual_partition, best_q)

    '''
        Computes the modularity of the current network.
        _partition: a list of lists of nodes
    '''
    def compute_modularity(self, partition):
        q = 0
        m2 = self.m * 2
        for i in range(len(partition)):
            q += self.s_in[i] / m2 - (self.s_tot[i] / m2) ** 2
        return q

    '''
        Computes the modularity gain of having node in community _c.
        _node: an int
        _c: an int
        _k_i_in: the sum of the weights of the links from _node to nodes in _c
    '''
    def compute_modularity_gain(self, node, c, k_i_in):
        return 2 * k_i_in - self.s_tot[c] * self.k_i[node] / self.m

    '''
        Performs the first phase of the method.
        _network: a (nodes, edges) pair
    '''
    def first_phase(self, network):
        # make initial partition
        best_partition = self.make_initial_partition(network)
        while 1:
            improvement = 0
            for node in network[0]:
                node_community = self.communities[node]
                # default best community is its own
                best_community = node_community
                best_gain = 0
                # remove _node from its community
                best_partition[node_community].remove(node)
                best_shared_links = 0
                for e in self.edges_of_node[node]:
                    if e[0][0] == e[0][1]:
                        continue
                    if e[0][0] == node and self.communities[e[0][1]] == node_community or e[0][1] == node and self.communities[e[0][0]] == node_community:
                        best_shared_links += e[1]
                self.s_in[node_community] -= 2 * (best_shared_links + self.w[node])
                self.s_tot[node_community] -= self.k_i[node]
                self.communities[node] = -1
                communities = {} # only consider neighbors of different communities
                for neighbor in self.get_neighbors(node):
                    community = self.communities[neighbor]
                    if community in communities:
                        continue
                    communities[community] = 1
                    shared_links = 0
                    for e in self.edges_of_node[node]:
                        if e[0][0] == e[0][1]:
                            continue
                        if e[0][0] == node and self.communities[e[0][1]] == community or e[0][1] == node and self.communities[e[0][0]] == community:
                            shared_links += e[1]
                    # compute modularity gain obtained by moving _node to the community of _neighbor
                    gain = self.compute_modularity_gain(node, community, shared_links)
                    if gain > best_gain:
                        best_community = community
                        best_gain = gain
                        best_shared_links = shared_links
                # insert _node into the community maximizing the modularity gain
                best_partition[best_community].append(node)
                self.communities[node] = best_community
                self.s_in[best_community] += 2 * (best_shared_links + self.w[node])
                self.s_tot[best_community] += self.k_i[node]
                if node_community != best_community:
                    improvement = 1
            if not improvement:
                break
        return best_partition

    '''
        Yields the nodes adjacent to _node.
        _node: an int
    '''
    def get_neighbors(self, node):
        for e in self.edges_of_node[node]:
            if e[0][0] == e[0][1]: # a node is not neighbor with itself
                continue
            if e[0][0] == node:
                yield e[0][1]
            if e[0][1] == node:
                yield e[0][0]

    '''
        Builds the initial partition from _network.
        _network: a (nodes, edges) pair
    '''
    def make_initial_partition(self, network):
        partition = [[node] for node in network[0]]
        self.s_in = [0 for node in network[0]]
        self.s_tot = [self.k_i[node] for node in network[0]]
        for e in network[1]:
            if e[0][0] == e[0][1]: # only self-loops
                self.s_in[e[0][0]] += e[1]
                self.s_in[e[0][1]] += e[1]
        return partition

    '''
        Performs the second phase of the method.
        _network: a (nodes, edges) pair
        _partition: a list of lists of nodes
    '''
    def second_phase(self, network, partition):
        nodes_ = [i for i in range(len(partition))]
        # relabelling communities
        communities_ = []
        d = {}
        i = 0
        for community in self.communities:
            if community in d:
                communities_.append(d[community])
            else:
                d[community] = i
                communities_.append(i)
                i += 1
        self.communities = communities_
        # building relabelled edges
        edges_ = {}
        for e in network[1]:
            ci = self.communities[e[0][0]]
            cj = self.communities[e[0][1]]
            try:
                edges_[(ci, cj)] += e[1]
            except KeyError:
                edges_[(ci, cj)] = e[1]
        edges_ = [(k, v) for k, v in edges_.items()]
        # recomputing k_i vector and storing edges by node
        self.k_i = [0 for n in nodes_]
        self.edges_of_node = {}
        self.w = [0 for n in nodes_]
        for e in edges_:
            self.k_i[e[0][0]] += e[1]
            self.k_i[e[0][1]] += e[1]
            if e[0][0] == e[0][1]:
                self.w[e[0][0]] += e[1]
            if e[0][0] not in self.edges_of_node:
                self.edges_of_node[e[0][0]] = [e]
            else:
                self.edges_of_node[e[0][0]].append(e)
            if e[0][1] not in self.edges_of_node:
                self.edges_of_node[e[0][1]] = [e]
            elif e[0][0] != e[0][1]:
                self.edges_of_node[e[0][1]].append(e)
        # resetting communities
        self.communities = [n for n in nodes_]
        return (nodes_, edges_)

'''
    Rebuilds a graph with successive nodes' ids.
    _nodes: a dict of int
    _edges: a list of ((int, int), weight) pairs
'''
def in_order(nodes, edges):
        # rebuild graph with successive identifiers
        nodes = list(nodes.keys())
        nodes.sort()
        i = 0
        nodes_ = []
        d = {}
        for n in nodes:
            nodes_.append(i)
            d[n] = i
            i += 1
        edges_ = []
        for e in edges:
            edges_.append(((d[e[0][0]], d[e[0][1]]), e[1]))
        return (nodes_, edges_)

社区发现的最后一轮结果为:

以polbooks.gml为例,1六、1七、1八、19被归类为同一个社区:

node
  [
    id 16
    label "Betrayal"
    value "c"
  ]
  node
  [
    id 17
    label "Shut Up and Sing"
    value "c"
  ]
  node
  [
    id 18
    label "Meant To Be"
    value "n"
  ]
  node
  [
    id 19
    label "The Right Man"
    value "c"
  ]

从读者的共同购买状况做为权重评估,社区发现的结果暗示了这几本书可能属于同一类的书籍

利用louvain进行社区发现的核心在于,咱们须要对咱们的业务场景进行抽象,提取出node1_src/node_dst的节点概念,同时要经过专家领域经验,进行节点间weight的计算,获得了weight后,就能够经过louvain这种迭代算法进行社区拓朴的发现了。

Relevant Link:

http://www.cnblogs.com/allanspark/p/4197980.html
https://arxiv.org/pdf/0803.0476.pdf
https://github.com/LittleHann/pylouvain
http://www.cnblogs.com/allanspark/p/4197980.html
https://www.jianshu.com/p/e543dc63454f

 

6. 其余社区发现算法

Relevant Link:

http://blog.sina.com.cn/s/blog_63891e610101722t.html
https://www.zhihu.com/question/29042018 
https://wenku.baidu.com/view/36fa145a3169a4517623a313.html
相关文章
相关标签/搜索