日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

复杂网络作业六:Louvain社区发现算法原理,细节以及实现

發布時間:2023/12/10 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 复杂网络作业六:Louvain社区发现算法原理,细节以及实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
  • 一、Louvain是什么?
  • 二、算法思路
    • 1.社區劃分的合理性
    • 2.算法流程
    • 3.ΔQ\Delta{Q}ΔQ的計算方式
  • 三、代碼實現
  • 總結


前言

這個第五題本身并不難,只是我個人對這個Louvain的算法比較感興趣。所以,就花的一周時間。可能是因為這是一篇算法型的論文吧。所以,復現難度不算太大。但是,如果不參考網上的一些已經寫完的代碼其實也會漏掉很多細節。包括現在也是并不確定我寫的是否是一定正確。每次更新之后也會在博客里同步更新。如果是你的目標是想要實現那就從一開始慢慢看,在這里有很多實現細節的說明和我個人的理解。如果要代碼就直接跳到最后 ,其實我也不能保證一定正確。畢竟這不是ACM的算法題能有一個準確的對錯。


一、Louvain是什么?

Louvain是一個用于社區發現的傳統算法。這個算法出現于2008年,那么什么是社區發現呢?舉個例子:假設我們有一個圖,在這個圖中的節點是某個鎮的所有的人,圖中的每一條邊代表的是兩個人之間的說話的數量。(現實生活中這個可能難以統計)而社區發現就是在這個圖中我們需要確定哪些人之間是一個團體,這個所謂的團體可能是同一個小學,也可能是同一個家庭或者是同一個小區等等。論文的原文全稱是:Fast unfolding of communities in large networks 作者:Vincent D. Blondel, Jean-Loup Guillaume, Renaud Lambiotte and Etienne Lefebvre

二、算法思路

1.社區劃分的合理性

作為一個認真的人,在放出公式之前先說明。論文中給出的用于衡量社區劃分合理性的公式并非是最好的。畢竟,十年都過去了,應該有其他的一些公式吧(我猜的)。但是,我能確定的是最好的一種劃分方式一定是真實結果(這句話看起來像句費話)。公式如下(其中的幾個乘號是我自己加上去的,我想應該不會錯。):
Q=12?m∑i,j[Aij?ki?kj2?m]δ(ci,cj)Q=\frac{1}{2*m}\sum_{i,j} [A_{ij} - \frac{k_i*k_j}{2*m}]\delta(c_i,c_j)Q=2?m1?i,j?[Aij??2?mki??kj??]δ(ci?,cj?)
mmm:如果是無向圖,圖中所有邊的權值和。如果是有向圖就是圖中所有邊的權值和的一半。論文中原本的寫法是m=12∑ijAijm = \frac{1}{2}\sum_{ij}A_{ij}m=21?ij?Aij?表面上看是圖中所有邊的一半。但是對于無向圖而言需要把每一條邊當作兩條有向邊來看。
AijA_{ij}Aij?:代表的是節點i和節點j之間的邊的權值。
kik_iki?:是所有指向節點i的邊的權值和。注意:就是這一個定義當我們在處理自環的時候就需要注意一個細節。假設有一個節點的圖案如下圖所示:

其中權值為4的邊是一個自環需要當作兩條邊來看,就上圖的i點而言其中的kik_iki?的值是28。
δ(ci,cj)\delta(c_i,c_j)δ(ci?,cj?):就是判斷i節點和j節點是否是同一個社區。如果是那么值就是1否則就是0
我這么寫可能是有此啰嗦。但是如果你能有耐心看完那么在實現的時候就能避開很多的坑。因為我這個可不是對論文的簡單翻譯。

2.算法流程

其算法流程一共有兩個大步驟,不斷的迭代往復。
1.我們先要把每一個單一的節點都看作是一個單獨的社區。對于每一個單一的節點,先要把它從它所在的社區中取出來。然后,再嘗試依次加入到它相鄰的社區中計算其收益ΔQ\Delta{Q}ΔQ。(ΔQ\Delta{Q}ΔQ的計算方法之后在詳細說,本文給出了三種不同的計算方法。)如果,最大的ΔQ\Delta{Q}ΔQ小于0則將它放回到原先的社區中,否則就加入到收益最大的那一個社區中。對所有的節點都不斷的循環反復的進行這一操作。直到所有節點的所處社區都不再發生變化。(不知道有沒有人和我一樣看原文的時候這一句沒看到。)
從這一步的做法中我主觀的感覺這個算法的目的是想要最大化Q的。但是,在實際情況中卻又不是Q越大就越接近真實劃分。詳情就要參考論文中的第12篇參考文獻了。(我沒有看過,詳細了解這個公式Q的同學可以說一下)
2. 通過第一步我們已經對圖有了一個新的社區劃分。于是在這一步中我們會對每一個社區都要把其中的對應的那些節點都縮成一個超節點,而這個超節點就代表了這一個社區中的若干個結點。而邊的處理方式與咱們的邏輯十分相符。如果一條邊連接的是同一個社區就把它變成對應超節點的一條自環邊,如果一條邊連接的是兩個不同的社區就讓這條邊連接兩個對應的超節點。然后計算一個新的全局Q值。如果這個Q值與之前的Q值相同那么就結束整個算法。(如果是第一次迭代那就不用考慮結束的事了)
為了能夠更加形象的理解,我試試看畫一個圖。
(假裝有圖,好吧我畫不出來。還是會的工具太少。)

3.ΔQ\Delta{Q}ΔQ的計算方式

在這一部分中我會一共會放三種不同的計算方式這三種方式都是基于一個大前提——我們需要嘗試將一個節點加入到社區C中。其中第一種是論文中給出的,第二種是其它的一些代碼中使用的版,(據說論文的初稿用的也是這個版本)第三種是我根據自己的理解推導得到的。(其正確性有待商榷,推薦使用第二種。)
1.ΔQ=[∑in+ki,in2?m?(∑tot+ki2?m)2]?[∑in2?m?(∑tot2?m)2?(ki2?m)2]\Delta{Q} = [\frac{\sum_{in}+k_{i,in}}{2*m} - (\frac{\sum_{tot} + k_i}{2*m})^2] - [\frac{\sum_{in}}{2*m}-(\frac{\sum_{tot}}{2*m})^2 - (\frac{k_i}{2*m})^2]ΔQ=[2?min?+ki,in???(2?mtot?+ki??)2]?[2?min???(2?mtot??)2?(2?mki??)2]
由于我個人對這個公式的理解并不完全,因此不對這個公式作非常詳細的解讀。僅僅只對其中的變量做一些簡單的解釋。
∑in\sum_{in}in?:社區C的內部的所有邊的權值和,注意:如果是無向圖則需要每一條邊看作是兩條有向邊,所以,是所有邊的權值和的兩倍。
ki,ink_{i,in}ki,in?:所有從節點i指向區域C的邊的權值和。
mmm:如果是無向圖,圖中所有邊的權值和。如果是有向圖就是圖中所有邊的權值和的一半。
∑tot\sum_{tot}tot?:所有指向區域C中的節點的邊的權值和。剛開始的時候,我把這個理解成了從區域C指向外面的所有邊的權值和。因此,踩了一個大坑。(這機種理解的區別就在于對自環的處理不同。)
kik_iki?:指向節點i的所有邊的權值和。(注意對自環的處理。)
在接下了我們就在這個公式的基礎上進行劃簡:
ΔQ=[∑in2?m+ki,in2?m?(∑tot2?m)2?(2?∑tot?ki4?m2)?(ki2?m)2]?[∑in2?m?(∑tot2?m)2?(ki2?m)2]=ki,in2?m?2?∑tot?ki4?m2=12?m?(ki,in?∑tot?kim)\Delta{Q} = [\frac{\sum_{in}} {2*m} + \frac{k_{i,in}}{2*m} - (\frac{\sum_{tot}}{2*m})^2 - (\frac{2*\sum_{tot}*k_i}{4*m^2}) - (\frac{k_i}{2*m})^2] - [\frac{\sum_{in}}{2*m}-(\frac{\sum_{tot}}{2*m})^2 - (\frac{k_i}{2*m})^2] \\ = \frac{k_{i,in}}{2*m} - \frac{2*\sum_{tot}*k_i}{4*m^2} \\ =\frac{1}{2*m}*(k_{i,in} - \frac{\sum_{tot}*k_i}{m})ΔQ=[2?min??+2?mki,in???(2?mtot??)2?(4?m22?tot??ki??)?(2?mki??)2]?[2?min???(2?mtot??)2?(2?mki??)2]=2?mki,in???4?m22?tot??ki??=2?m1??(ki,in??mtot??ki??)
在上述公式中12m\frac{1}{2m}2m1?在算法進行的過程中全程是一個常數,不會影響其大小的比較。因此在比較大小的過程中只需要比較(ki,in?∑tot?kim)(k_{i,in} - \frac{\sum_{tot}*k_i}{m})(ki,in??mtot??ki??)即可。
2.ΔQ=2?ki,in?∑tot?kim\Delta{Q}=2*k_{i,in} - \frac{\sum_{tot}*k_i}{m}ΔQ=2?ki,in??mtot??ki??
這個公式對應的上一個公式就是在ki,ink_{i,in}ki,in?前多了一個系數2,據說,作者剛發布的第一個版本的論文中用的就是這個后來修改了系數。同時用用使用這個公式作為ΔQ\Delta{Q}ΔQ的計算分割效果要更好一點。同時在karate的數據集中使用這個方法的效果確實比上一個公式的效果要更好一點。
3.這個是根據我自己的理解推出來的,但是它的正確性我無法驗證。我目前只用它在karate的數據集中運行過,不管是最終的Q值比前兩個方法的計算得到的Q值要更大一點。而目測效果與第二個相比相差不大。公式如下:
ΔQ=ki,in2?m?∑iki?f(i,j)?kj4?m2\Delta_{Q} = \frac{k_{i,in}}{2*m} - \frac{\sum_{i}{k_{i}*f(i,j)*k_{j}}}{4*m^2}ΔQ?=2?mki,in???4?m2i?ki??f(i,j)?kj??
其中f(i,j) 表示的是i,j之間是否有直接連邊。如果有則為1否則為0。

三、代碼實現

在給出我自己的實現代碼之前,先給大家另一個網站的實現方法。同時這個實現方法也是我在調試的過程中主要參考的一個代碼。放在前面是因為推薦大家使用這個,我想這個代碼的應該會比我自己實現的更準確吧。
1.code1
code轉載自:https://blog.csdn.net/weixin_40308540/article/details/101269508
(使用方法在最后面)

'''Implements the Louvain method.Input: a weighted undirected graphOuput: 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)'''@classmethoddef from_file(cls, path): #從txt中讀取一個netf = open(path, 'r')lines = f.readlines()f.close()nodes = {}edges = []for line in lines:n = line.split()if not n:breaknodes[n[0]] = 1nodes[n[1]] = 1w = 1if len(n) == 3:w = int(n[2])edges.append(((n[0], n[1]), w))# rebuild graph with successive identifiersnodes_, edges_ = in_order(nodes, edges)print("%d nodes, %d edges" % (len(nodes_), len(edges_)))return nodes_, edges_ #此處作了一點修改'''Builds a graph from _path._path: a path to a file following the Graph Modeling Language specification'''@classmethoddef from_gml_file(cls, path): #從gml中讀取一個netf = open(path, 'r')lines = f.readlines()f.close()nodes = {}edges = []current_edge = (-1, -1, 1)in_edge = 0for line in lines:words = line.split()if not words:breakif words[0] == 'id':nodes[int(words[1])] = 1elif words[0] == 'source':in_edge = 1current_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 = 0nodes, edges = in_order(nodes, edges)print("%d nodes, %d edges" % (len(nodes), len(edges)))return 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 = nodesself.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 = 0self.k_i = [0 for n in nodes]self.edges_of_node = {}self.w = [0 for n in nodes]self.orginer_network = []self.orginer_k_i = []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 nodeif 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) timeself.communities = [n for n in nodes]self.actual_partition = []self.orginer_k_i = []for k in self.k_i:self.orginer_k_i.append(k)'''Applies the Louvain method.'''def findRoot(self,node):for i,community in enumerate(self.actual_partition):if(node in community):return idef my_compute_modularity(self):sum_Q = 0for edge in self.orginer_network[1]:u,v = edge[0]w = edge[1]if(self.findRoot(u) == self.findRoot(v)):sum_Q = (w - (self.orginer_k_i[u]*self.orginer_k_i[v])/(2*self.m))/(2*self.m)return sum_Qdef apply_method(self):network = (self.nodes, self.edges)self.orginer_network = networkbest_partition = [[node] for node in network[0]]best_q = -1i = 1while 1:# print("pass #%d" % i)i += 1partition = self.first_phase(network)#q = self.compute_modularity(partition)q = self.my_compute_modularity()partition = [c for c in partition if c]# print("%s (%.8f)" % (partition, q))# clustering initial nodes with partitionif 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 = actualelse:self.actual_partition = partitionprint(q)if q == best_q:breaknetwork = self.second_phase(network, partition)best_partition = partitionbest_q = qreturn (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 = 0m2 = self.m * 2for i in range(len(partition)):q += self.s_in[i] / m2 - (self.s_tot[i] / m2) ** 2return 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 partitionbest_partition = self.make_initial_partition(network)while 1:improvement = 0for node in network[0]:node_community = self.communities[node]# default best community is its ownbest_community = node_communitybest_gain = 0# remove _node from its communitybest_partition[node_community].remove(node)best_shared_links = 0for e in self.edges_of_node[node]:if e[0][0] == e[0][1]:continueif 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.s_tot[node_community] -= (self.k_i[node] - 2*best_shared_links)self.communities[node] = -1communities = {} # only consider neighbors of different communitiesfor neighbor in self.get_neighbors(node):community = self.communities[neighbor]if community in communities:continuecommunities[community] = 1shared_links = 0for e in self.edges_of_node[node]:if e[0][0] == e[0][1]:continueif 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 _neighborgain = self.compute_modularity_gain(node, community, shared_links)if gain > best_gain:best_community = communitybest_gain = gainbest_shared_links = shared_links# insert _node into the community maximizing the modularity gainbest_partition[best_community].append(node)self.communities[node] = best_communityself.s_in[best_community] += 2*( best_shared_links + self.w[node])#self.s_tot[best_community] += (self.k_i[node])self.s_tot[best_community] += (self.k_i[node] - 2*best_shared_links)if node_community != best_community:improvement = 1if not improvement:breakreturn 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 itselfcontinueif 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-loopsself.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 communitiescommunities_ = []d = {}i = 0for community in self.communities:if community in d:communities_.append(d[community])else:d[community] = icommunities_.append(i)i += 1self.communities = communities_# building relabelled edgesedges_ = {}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 nodeself.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 communitiesself.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 identifiersnodes = list(nodes.keys())nodes.sort()i = 0nodes_ = []d = {}for n in nodes:nodes_.append(i)d[n] = ii += 1edges_ = []for e in edges:edges_.append(((d[e[0][0]], d[e[0][1]]), e[1]))return (nodes_, edges_) ###################******************---下面是使用方法的一個例子---******************################### #下面是Louvain的使用方式 import argparse import networkx as nx import random import matplotlib.pyplot as plt def getRandomColor():'''這是隨機獲取一種顏色。但是,不能保證獲取的顏色差距一定很大。所以,如果想要直觀的看到結果。有時候需要多運行幾次。'''return random.randint(0,255) def drawNet_Louvain(G,part_list):'''將社區劃分完成的圖進行直觀的展示。'''for i in range(len(part_list)):for j in range(len(part_list[i])):part_list[i][j] += 1print(part_list)color_list = []for part in part_list:color = getRandomColor()for node in part:color_list.append((node,color))color_list.sort(key = lambda x: x[0])print(color_list)color_list = [x[1] for x in color_list]plt.figure(figsize=(5, 5))print("finish")print(len(G.nodes()))print(len(color_list))pos = nx.spring_layout(G)nx.draw(G, with_labels=True, node_color=color_list, pos=pos)plt.show()plt.savefig(r"filename.png")def main(option):data_path = option.data_path#此處需要修改對應的數據文件的路徑if (data_path[-3:] == "txt"): #如果文件是在txt中的讀取方式net_G_nodes,net_G_edges = PyLouvain.from_file(data_path) #使他的方法進行數據讀取,返回的是點集和邊集net_G = nx.read_edgelist(data_path) #因為他使用的是完全自己實現的代碼,無法進行畫圖展示。所以,需要自己在讀入一個networkx的elif (data_path[-3:] == "gml"): #如果文件是在gml中的讀取方式net_G_nodes,net_G_edges = PyLouvain.from_gml_file(data_path)#使他的方法進行數據讀取,返回的是點集和邊集net_G = nx.read_gml(data_path,label="id") #因為他使用的是完全自己實現的代碼,無法進行畫圖展示。所以,需要自己在讀入一個networkx的new_G = PyLouvain(net_G_nodes,net_G_edges) #使用它的方法構造成一個類,傳入的參數依次是點集和邊集t,d = new_G.apply_method() #應用其中的方法,對社區進行分割。返回值分別是最佳的社區劃分,以及對應的Q值。drawNet_Louvain(net_G, t) #對分割完成的圖進行展示。if __name__ == "__main__":parser = argparse.ArgumentParser()parser.add_argument("--data_path", type=str, default="karate.gml", help="data_path is the path of the net")opt = parser.parse_args()main(opt)

然后是我根據我自己的理解所實現的算法
2.code

import networkx as nx import argparsedef CalDeltaQ1(G,node_i,k_i_in,k_i,community_list,community_value,community_tot,community_id,neg_community_id,node_edge,edge_list,m,opt):'''function:用于計算deltaQparameter:G:當前的圖node_i:正在操作的節點的編號k_i_in:從i指向區域C的所有邊的權值和k_i:指向節點i的所有邊的權值和(包括自環的邊)community_list:每一個社區所包含的結點的編號community_value:社區內的所有這的權值和community_tot:社區指向外部節點的所有邊的權值和community_id :node_i 所處社區的編號neg_community_id:相鄰社區的社區編號node_edge:node_i這一節點的鄰邊edge_list:圖的所有邊的列表m:全圖的所有邊的和opt:其中有選擇deltaQ的計算方式的選項return:返回對應的deltaQ'''sumC = community_value[neg_community_id]sum_tot = community_tot[neg_community_id] + community_value[neg_community_id]if(opt.calDeltaQfunction == "fun_copy"): #使用的是參考代碼中的公式將其中的2改成1就是優化后的論文中的公式return 2*k_i_in - sum_tot*k_i/melif(opt.calDeltaQfunction == "my_fun"): #使用我自己推導的公式,因為沒有驗證過它的正確性。所以不推薦使用sum_k_i = 0delta_Q = (k_i_in)/(2*m)k_j = 0for edge in G[node_i]:if(edge in community_list[neg_community_id]):k_j += G.nodes[edge]["node_out"] + G.nodes[edge]["value"]delta_Q -= (k_j*k_i)/(4*(m**2))return delta_Qelse: #原模原樣的使用論文中的公式m2 = 2*mreturn ((sumC + k_i_in)/m2 - ((sum_tot + k_i)/m2)**2) - (sumC/m2- (sum_tot/m2)**2 - (k_i/m2)**2)def PutNode2Community(node,community_list,community_value,community_tot,k_i_c,k_i,root_id,m,level):'''使用并查集的方法進行編號合并parameter:node:結點 包涵信息root_id: 需要把結點加入的社區的idm:全圖的權值總合level:經過的第幾次合并(不知道怎么用)return:無,在過程中會更新node_list中的各個結點的root_list屬性值'''node["root_list"][0] = root_idcommunity_list[root_id].append(node["node_id"])community_value[root_id] += (node["value"] + 2*k_i_c)community_tot[root_id] += (k_i - 2*k_i_c)def getInitCommunity(G,node_list): #社區中所包涵的節點,社區value(sumC_in),社區的出度(sumC_tot)node2community = []node2community_value = []node2community_tot = []for node in node_list:node2community.append([node])node2community_value.append(G.nodes[node]["value"])node2community_tot.append(G.nodes[node]["node_out"])return node2community,node2community_value,node2community_tot def flag2Merge(G,node_list,m,level,opt):'''function :對接收的圖G進行社區劃分,返回劃分結果node_list:結點列表m :全圖的權值和level :迭代的次數opt :里面包含了deltaQ的計算方式return:社區劃分的表示形式。"set":每一個社區中所包含的節點的編號。"value":社區內部的權值。(社區內的邊的兩倍)"tot":社區內的節點指向,社區外的邊的和'''#對社區的信息進行初始化community_list,community_value,community_tot = getInitCommunity(G,node_list)while(True):promote = False #是否對于社區劃分是否有更新for node in node_list:#先將node結點移出node所對應的社區 可優化community_id = G.nodes[node]["root_list"][0] #該節點所屬的社區的編號community_list[community_id].remove(node) #將節點移出其所屬的社區k_i_c = 0 #從節點指向社區的所有邊的權值和for edge in G[node]:if(edge in community_list[community_id]):k_i_c += G[node][edge]["weight"]community_value[community_id] -= (2*k_i_c + G.nodes[node]["value"]) #由于已經將節點移出社區,所以需要更新其區社的value值community_tot[community_id] -= (G.nodes[node]["node_out"] - 2*k_i_c) #由于已經將節點移出社區,所以需要更新其區社的tot值#在相鄰的社區中找最優max_delta_Q = 0max_k_i_c = k_i_cbest_community_id = community_id#k_i = sum([edge["weight"] for edge in G[node].values()]) #原寫法k_i = G.nodes[node]["node_out"] #此處先把k_i 表示為該節點指向其它節點的邊的權值和#嘗試將節點加入到相鄰的社區之中neg_community_use = set()for neg_node in G[node]:if(isinstance(neg_node,int)):neg_community = G.nodes[neg_node]["root_list"][0] #相鄰節點所屬的社區if(neg_community in neg_community_use): #如果已經嘗試過則不再嘗試continueelse:neg_community_use.add(neg_community)k_i_c = 0 #節點相相鄰節點之間的權值和for edge in G[node]:if(edge in community_list[neg_community]):k_i_c += G[node][edge]["weight"]#計算deltaQdelta_Q = CalDeltaQ1(G,node,k_i_c,k_i + G.nodes[node]["value"],community_list,community_value,community_tot,community_id,neg_community,G[node],G.edges(),m,opt)#更新最優劃分,以及相關信息if(delta_Q > max_delta_Q):max_delta_Q = delta_Qmax_k_i_c = k_i_cbest_community_id = G.nodes[neg_node]["root_list"][0]#將節點加入到最優劃分的區間中if(max_delta_Q > 0 and best_community_id != community_id):promote = True#將節點加入到最優的社區中,并且更新其comuniyu的相關屬性PutNode2Community(G.nodes[node], community_list,community_value,community_tot,max_k_i_c,k_i,best_community_id, m, level)if(not promote):breakreturn {"set":community_list,"value":community_value,"tot":community_tot} def MergeG2getNewG(G,community):'''function:根據其社區劃分的結果重新生成一個新的圖G:原先的圖結構community:社區劃分的結果,community["set"]——社區中所包含的節點的編號community["value"]——社區內部的所有邊的權值和的兩倍community["tot"]——所有社區內部節點指向社區外部節點的邊的權值和return:返回一個縮點后的圖,以及其中對應的邊'''new_G = nx.Graph() #一個新的圖cnt_community = 0#對社區進行遍歷for i,community_set in enumerate(community["set"]): #將節點進行合并產生一批新圖中的總節點#node_id node_set root_list value outif(community_set): #如果在社區中有元素則構造新的節點,加入到圖中#編號i作為節點編號,community["set"]中的元素就是該社區所包含的對應的元素,將cnt_community作為臨時根,community["value"]作為該節點的編號,community["tot"]作為該節點的出度#community["value"] + community["tot"]就是指向該社區節點的所有邊的權值和new_G.add_node(i,node_id = i,node_set = community_set,root_list = [cnt_community],value = community["value"][i],node_out = community["tot"][i])cnt_community += 1for edge in G.edges(): #將邊進行處理加入到適當的新圖中#weightnode1,node2 = edgeroot1 = G.nodes[node1]["root_list"][0]root2 = G.nodes[node2]["root_list"][0]if(root1 == root2): #這條邊的一個社區的內部#在community["value"]中已經加上,所以不用在這里再加。在PutNode2Community函數中#new_G.nodes[root1]["value"] += 2*G[node1][node2]["weight"]continueelse:#這條邊的兩個社區之間,則與其它的邊一同進行合并if((root1,root2) not in new_G.edges()):new_G.add_edge(root1,root2,weight = G[node1][node2]["weight"])else:new_G[root1][root2]["weight"] += G[node1][node2]["weight"]return new_Gdef dealSelfLoop(G):'''對圖中的所有的邊進行遍歷,如果存在自環就將其刪除,同時一個自環就對那個對應的節點上的值+2問題:對于networkx 而言它會自動的刪除重邊。所以在這一點上對于使用networkx對最終的結果會有影響。'''for node in G:cnt_self_node = 0flag = Falsefor neg_node in G[node]:if(node == neg_node): #如果有重邊就刪除同時更新節點上的值flag = Truecnt_self_node += 2if(flag):G.remove_edge(node, node)G.nodes[node]["value"] = cnt_self_nodereturn Gdef stateINIT(G):for node in G:for edge in G[node]:G[node][edge]["weight"] = 1 #每一條邊都有一個權值,由于是無權圖因此權值都需要變為1for i,node in enumerate(G.nodes):G.nodes[node]["node_id"] = node #節點的IDG.nodes[node]["node_set"] = {node}#社區中所包含的節點(對于第一個圖用不上)G.nodes[node]["root_list"] = [i] #每一個節點從屬的社區的編號G.nodes[node]["node_out"] = sum([G[node][edge]["weight"] for edge in G[node]]) #節點相鄰邊的權值和def graphArrange(levelG_list):'''圖的整理,也就是根節點的向下傳遞更新每一層的圖的根節點'''l = -1*len(levelG_list) - 1for level in range(-2,l,-1):G1 = levelG_list[level + 1]G2 = levelG_list[level]for node in G1.nodes():#獲取節點的根,然后將該社區內的所有節點的根也進行更新root_id = G1.nodes[node]["root_list"][-1]# print(root_id,":")for member_id in G1.nodes[node]["node_set"]:# print(member_id,end=" ")G2.nodes[member_id]["root_list"].append(root_id)# print("")# print(G1.nodes())# print(G2.nodes())def calGraphQ(G,new_G,m,level,opt):'''function:計算圖在當前的社區劃分下的Q值G:原圖new_G:最近的經過縮點后的圖m:全圖的權值和level:遞歸的次數opt:其中指定了是使用哪一種Q的計算方法return:圖的Q值'''if(opt.calQFunction == "fun_copy"): #這是參考代碼中使用的Q的計算方式sum_Q = 0for new_node in new_G:sum_in = new_G.nodes[new_node]["value"]sum_tot = new_G.nodes[new_node]["node_out"]sum_Q += (sum_in/(2*m) - (sum_tot/(2*m))**2)return sum_Qelse: #這是在論文中使用的Q的計算方式sum_Q = 1for edge in G.edges:u,v = edgeroot_u = G.nodes[u]["root_list"][-1]root_v = G.nodes[v]["root_list"][-1]if(root_u == root_v): #如果u和v同屬于一個社區#G.nodes[u]["value"]節點內部的邊(自環)#G.nodes[v]["node_out"] 節點的鄰邊sum_u = G.nodes[u]["value"] + G.nodes[v]["node_out"]sum_v = G.nodes[v]["value"] + G.nodes[v]["node_out"]sum_Q -= sum_u*sum_v/(4*(m**2))*2 #乘二是因為(u,v)(v,u)要當作兩條邊來計算#sum_Q += (G[u][v]["weight"] - (sum_u)*(sum_v )/(2*m))/(2*m)return sum_Qdef Louvain(G,m,opt):dealSelfLoop(G) #對自環進行處理stateINIT(G) #對G添加屬性,同時完成初始化level = 0 #記錄迭代次數merageG_list = [] #在這個列表中用于記錄在迭代過程中的所有形成的子圖best_q = -2 #不斷更新,獲取最終的Q值while(True):level += 1#返回的是對圖的一個社區劃分,以及每一個社區中的信息#其中# community["set"]中存儲的是每一個社區中所包涵的結點的編號# community["value"] 中表示的是每一個社區的權值,其權值主要是自環的2倍# sommunity["tot"]中表示的是每一個社區內的結點指向外面的所有邊的權值和。(注意:此外不是指向所有社區內的所有邊的權值和)community = flag2Merge(G,list(G.nodes),m,level,opt)#將圖的社區劃分進行縮點重新組成一個新的圖,同時對于新的圖添加必要的信息new_G = MergeG2getNewG(G,community)#將新的圖添加到時圖列表中merageG_list.append(G)#將圖列表進行整理,只整理根節點的信息graphArrange(merageG_list)#重新更新使用的圖G = new_G#計算圖的Q值new_q = calGraphQ(merageG_list[0],G,m,level,opt)print(new_q)#如果Q值沒有變化則,線束算法if(new_q == best_q):break#更新最優的Q值(其實就是最后一種劃分的Q值)best_q = new_qreturn merageG_listdef CalGraphM1(net_G): #把單個邊當作無向圖的一條邊來處理#因為是無權所以邊的權值看作是1m = len(net_G.edges())return m import random import matplotlib.pyplot as plt def getRandomColor():return random.randint(0,255) def drawNet_Louvain(G_Louvain_list,show_level):"""function:將每一次迭代產生的社區劃分結果進行展示"""show_G = G_Louvain_list[0]#print(len(G_Louvain_list[show_level].nodes))color_list = []color_dict = {}for node in show_G.nodes():node_color = show_G.nodes[node]["root_list"][show_level]if(node_color in color_dict):color_list.append(color_dict[node_color])else:color = getRandomColor()color_dict[node_color] = colorcolor_list.append(color_dict[node_color])plt.figure(figsize=(5, 5))#print("finish")#print(len(show_G.nodes()))#print(len(color_list))pos = nx.spring_layout(show_G)nx.draw(show_G,with_labels=True,node_color=color_list,pos=pos)#plt.show()plt.savefig("filename" + str(show_level) + ".png")def main(option):data_path = option.data_pathif(data_path[-3:] == "txt"):net_G = nx.read_edgelist(data_path,comments='#',delimiter=None,create_using=None,nodetype=int,data=True,edgetype=int,encoding='utf-8',)elif(data_path[-3:] == "gml"):net_G = nx.read_gml(data_path,label="id")m = CalGraphM1(net_G) #計算全圖換邊權和G_merage_list = Louvain(net_G,m,option) #進行Louvain算法for i in range(len(G_merage_list)): #畫圖drawNet_Louvain(G_merage_list,i) if __name__ == "__main__":parser = argparse.ArgumentParser()parser.add_argument("--data_path",type = str,default="karate.gml", #數據的路徑help="data_path is the path of the net")parser.add_argument("--calQFunction",type = str,default="22", #計算Q的方式#Q的選擇只能是使用"fun_copy"或者其它,如果是"fun_copy"則使用的是code1中的Q的計算方法。如果是其它則使用的是原論文中的Q的計算方法help="calQFunction is the mode to calQ,should choose from ['fun_copy',other]") parser.add_argument("--calDeltaQfunction",type=str ,default="my_fun", #計算deltaQ的方式#deltaQ的選擇只能是使用"fun_copy"或者其它,如果是"fun_copy"則使用的是code1中的deltaQ的計算方法。如果是"my_fun"則使用的是我自己的理解推的一個公式。如果是其它則使用的是原論文中的deltaQ的計算方法help="calDeltaQfunction the mode to calDeltaQ,should choose from ['fun_copy','my_fun',other]")opt = parser.parse_args()#如果使用的是jupyter需要把上面一行注釋掉修改成下面這一行#opt = args = parser.parse_args(args=[])main(opt)

總結

我已經把我的代碼進行過更新了,暫時先這樣吧!效果也還算可觀。如果,之后有變化那就在更新。另外,好像第五題的第一題就是代碼(略)。第二題就是之前的東西(略)。第三題就是跑代碼(略)。第四題看情況吧,如果我做了我就一定會發博客。


因為一些原因在此添加數據的下載鏈接。(上面代碼使用的數據) 下載鏈接:http://www-personal.umich.edu/~mejn/netdata/ 數據名稱:Zachary's karate club 數據中的結點較少可以直接看到。

最后,如果有問題可以直接留言咨詢。如果我看到了,我一定會回復的

總結

以上是生活随笔為你收集整理的复杂网络作业六:Louvain社区发现算法原理,细节以及实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。