聚類 | Map-Equation多級網絡聚類模型——InfoMap

受蘇神的《最小熵原理(五):“層層遞進”之社區發現與聚類》啓發,拿來做詞聚類,看蘇神的貼出來的效果蠻好,就上手試了試,感覺確實不錯。

最新的v1.0版本還有專門網站:https://mapequation.github.io/infomap/

在這裏插入圖片描述



1 簡單的理論

Infomap 的雙層編碼方式把羣組識別(社區發現)同信息編碼聯繫到了一起。一個好的羣組劃分,可以帶來更短的編碼。所以,如果能量化編碼長度,找到使得長度最短的羣組劃分,那就找到了一個好的羣組劃分。

Infomap 在具體做法上,爲了區分隨機遊走從一個羣組進入到了另一個羣組,除了羣組的名字之外,對於每個羣組的跳出動作也給予了一個編碼。比如,下圖(c)中紅色節點部分是一個羣組,羣組名的編碼是 111,跳出編碼是 0001。這樣在描述某個羣組內部的一段隨機遊走路徑的時候,總是以羣組名的編碼開頭,以跳出編碼結束。

在這裏插入圖片描述

總結一下,Infomap 算法的大體步驟如下(看起來跟 Louvain 有些許類似):

(1)初始化,對每個節點都視作獨立的羣組;
(2)對圖裏的節點隨機採樣出一個序列,按順序依次嘗試將每個節點賦給鄰居節點所在的社區,取平均比特
下降最大時的社區賦給該節點,如果沒有下降,該節點的社區不變;
(3)重複直到步驟 2 直到 L(M)不再能被優化。


2 Benchmark

參考:Source code for multilevel community detection with Infomap

該聚類方法剛好可以順着詞向量做一些詞間發現,相比Kmeans之類的效果確實好不少。相比其他network 方法(Louvain)實驗結果也要好一些,來看一下對比:

  • 速度:運行時長
    在這裏插入圖片描述

  • 精確度:精度以輸出羣集和參考羣集之間的標準化互信息(NMI)進行衡量。基準網絡由5000個節點組成,社區規模在20到200之間。
    在這裏插入圖片描述

  • 分層精度:該圖顯示了該算法很好地揭示了不同級別的三角網絡中節點的層次結構(請參見下圖)。
    在這裏插入圖片描述


3 安裝

蘇神v0.x的結果和v1.0的結果有一些差異的。v0.x要比v1.0多出很多算法,而v1.0只有最簡單的一種。
而且,github上掛的example都是v0.x的版本,所以如果照着example好像還得切換回去。

3.1 v0.x版本

蘇神博客中所述:

wget -c https://github.com/mapequation/infomap/archive/6ab17f8b18a6fdf34b2a53454f79a3b976a49201.zip
unzip 6ab17f8b18a6fdf34b2a53454f79a3b976a49201.zip
cd infomap-6ab17f8b18a6fdf34b2a53454f79a3b976a49201
cd examples/python
make

# 編譯完之後,當前目錄下就會有一個infomap文件夾,就是編譯好的模塊;
# 爲了方便調用,可以複製到python的模塊文件夾(每臺電腦的路徑可能不一樣)中
python example-simple.py
cp infomap /home/you/you/python/lib/python2.7/site-packages -rf

筆者電腦安裝的時候,還要安裝一個apt-get install swig

3.2 v1.0版本

pip install infomap

4 基於infomap的詞聚類

兩個版本中,

  • from infomap import infomap是v0.x版本,
  • import infomap是v1.0版本

其中,還有一些差異:
v0.x版本還有:

  • node.physIndex - v0.x版本的詞編號
  • node.moduleIndex - v0.x版本的聚類編號
  • infomapWrapper = infomap.MemInfomap("--two-level") 這個好像是v0.x中特有的算法(Memory networks
  • tree.leafIter()- 樹狀結構
  • infomapWrapper.addTrigram(3, 2, 3),v1.0沒有這種形態,Trigrams represents a path from node A through B to C.

v1.0版本還有:

  • node.physicalId - v1.0版本的詞編號
  • node.moduleIndex() - v1.0版本的聚類編號
  • myInfomap.iterTree() - 樹狀結構
  • network = myInfomap.network() 好像是v1.0獨有的算法模塊
  • 1.0不能夠使用 --overlapping這樣的命令,一用就卡掉。。。

兩者類似的是:

- tree.numTopModules() - 聚類之後的總數,2365個聚類
- tree.codelength() - 每個聚類中平均有多少個詞
- addLink(self, n1, n2, weight=1.0) - _infomap.Infomap_addLink(self, n1, n2, weight),可以[點1,點2,權重]

4.1 v0.x版本

直接看蘇神的代碼即可,跟Word2Vec配合,跑一個詞聚類的例子,代碼位於:
https://github.com/bojone/infomap/blob/master/word_cluster.py

其中相關的代碼爲:

from infomap import infomap

infomapWrapper = infomap.Infomap("--two-level --directed")
# 如果重疊社區發現,則只需要:
# infomapWrapper = infomap.Infomap("--two-level --directed --overlapping")


for (i, j), sim in tqdm(links.items()):
    _ = infomapWrapper.addLink(i, j, sim)

infomapWrapper.run()
tree = infomapWrapper.tree


word2class = {}
class2word = {}
for node in tree.leafIter():
    if id2word[node.physIndex] not in word2class:
        word2class[id2word[node.physIndex]] = []
    word2class[id2word[node.physIndex]].append(node.moduleIndex())
    if node.moduleIndex() not in class2word:
        class2word[node.moduleIndex()] = []
    class2word[node.moduleIndex()].append(id2word[node.physIndex])

infomap.Infomap初始化,關於這些指令,可以在Options中找到:
– two-level:兩階段網絡,Optimize a two-level partition of the network.
– 對應的
– directed :有向
–overlapping:Let nodes be part of different and overlapping modules. Applies to ordinary networks by first representing the memoryless dynamics with memory nodes.
–undirected:無向
–expanded:打印記憶網絡的節點,Print the expanded network of memory nodes if possible.
–silent:No output on the console,命令中不顯示結果

輸出結果爲:

在這裏插入圖片描述

4.2 v1.0版本

官方的小案例(參考:https://mapequation.github.io/infomap/):

import infomap

# Command line flags can be added as a string to Infomap
infomapSimple = infomap.Infomap("--two-level --directed")

# Access the default network to add links programmatically
network = myInfomap.network()

# Add weight as optional third argument
network.addLink(0, 1)
network.addLink(0, 2)
network.addLink(0, 3)
network.addLink(1, 0)
network.addLink(1, 2)
network.addLink(2, 1)
network.addLink(2, 0)
network.addLink(3, 0)
network.addLink(3, 4)
network.addLink(3, 5)
network.addLink(4, 3)
network.addLink(4, 5)
network.addLink(5, 4)
network.addLink(5, 3)

# Run the Infomap search algorithm to find optimal modules
myInfomap.run()

print("Found {} modules with codelength: {}".format(myInfomap.numTopModules(), myInfomap.codelength()))

print("Result")
print("\n#node module")
for node in myInfomap.iterTree():
  if node.isLeaf():
    print("{} {}".format(node.physicalId, node.moduleIndex()))

來看一下v1.0版本,跟v0.x版本還不太一樣呢。

#import uniout
import numpy as np
from gensim.models import Word2Vec
from tqdm import tqdm
#from infomap import infomap # v0.x
import infomap # v1.0

num_words = 10000 # 只保留前10000個詞
min_sim = 0.6


word2vec = Word2Vec.load('baike_word2vec/word2vec_baike')

word_vecs = word2vec.wv.syn0[:num_words]
word_vecs /= (word_vecs**2).sum(axis=1, keepdims=True)**0.5
id2word = word2vec.wv.index2word[:num_words]
word2id = {j: i for i, j in enumerate(id2word)}

# 構造[wordA,wordB,相似性]
links = {}

# 每個詞找與它相似度不小於0.6的詞(不超過50個),來作爲圖上的邊
for i in tqdm(range(num_words)):
    sims = np.dot(word_vecs, word_vecs[i])
    idxs = sims.argsort()[::-1][1:]
    for j in idxs[:50]:
        if sims[j] >= min_sim:
            links[(i, j)] = float(sims[j])
        else:
            break


# 方式一(infomap模型初始化):Infomap直接addLink

infomapWrapper = infomap.Infomap("--two-level --directed")
#infomapWrapper = infomap.Infomap("--two-level")
# 如果重疊社區發現,則只需要:
# infomapWrapper = infomap.Infomap("--two-level --directed --overlapping")

for (i, j), sim in tqdm(links.items()):
    #print(i, j,sim)
    _ = infomapWrapper.addLink(int(i), int(j),sim)

# 方式二(infomap模型初始化):network 添加addLink
infomapWrapper = infomap.Infomap("--two-level --directed")
network = infomapWrapper.network() 
for (i, j), sim in tqdm(links.items()):
    network.addLink(int(i), int(j),sim)

# 聚類運算
infomapWrapper.run()

# 有多少聚類數
print("Found {} modules with codelength: {}".format(infomapWrapper.numTopModules(), infomapWrapper.codelength()))

# 聚類結果顯示
word2class = {}
class2word = {}
# for node in tree.leafIter():
#     if id2word[node.physIndex] not in word2class:
#         word2class[id2word[node.physIndex]] = []
#     word2class[id2word[node.physIndex]].append(node.moduleIndex())

#     if node.moduleIndex() not in class2word:
#         class2word[node.moduleIndex()] = []
#     class2word[node.moduleIndex()].append(id2word[node.physIndex])
for node in tree.iterTree():
    if id2word[node.physicalId] not in word2class:
        word2class[id2word[node.physicalId]] = []  # node.physicalId 詞的編號
    word2class[id2word[node.physicalId]].append(node.moduleIndex())  # node.moduleIndex() 聚類的編號

    if node.moduleIndex() not in class2word:
        class2word[node.moduleIndex()] = []
    class2word[node.moduleIndex()].append(id2word[node.physicalId])


for i in range(100):
    print('---------------')
    print (class2word[i][1:])

在infomap設置的時候,v1.0還有一種network的方式。

最後輸出的結果,如果是Infomap直接addLink:
在這裏插入圖片描述
如果是network 添加addLink(感覺上,使用network要好一些):

在這裏插入圖片描述


5 v1.0版本其他的一些嘗試

因爲v1.0版本安裝非常簡單,如果作者會持續優化的情況下,雖然還不如v0.x算法全,但這個版本應該比較更好(PS:v1.0的教程太少了。。)

5.1 Infomap + NetworkX 畫圖

這個改編自官方example一個案例,不過不知道筆者有沒有寫對。。。
最終效果,不如之前的 版本。

import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.colors as colors
%matplotlib inline

def findCommunities(G):
    """
    Partition network with the Infomap algorithm.
    Annotates nodes with 'community' id and return number of communities found.
    """
    infomapWrapper = infomap.Infomap("--two-level --silent")

    print("Building Infomap network from a NetworkX graph...")
    for e in G.edges():
        infomapWrapper.addLink(*e)

    print("Find communities with Infomap...")
    infomapWrapper.run();

    tree = infomapWrapper

    print("Found %d modules with codelength: %f" % (tree.numTopModules(), tree.codelength()))

    communities = {}
    #for node in tree.leafIter():
    for node in tree.iterTree():
        #communities[node.originalLeafIndex] = node.moduleIndex()
        communities[node.physicalId] = node.moduleIndex()
    nx.set_node_attributes(G, name='community', values=communities)
    return tree.numTopModules()

def drawNetwork(G):
    # position map
    pos = nx.spring_layout(G)
    # community ids
    communities = [v for k,v in nx.get_node_attributes(G, 'community').items()]
    numCommunities = max(communities) + 1
    # color map from http://colorbrewer2.org/
    cmapLight = colors.ListedColormap(['#a6cee3', '#b2df8a', '#fb9a99', '#fdbf6f', '#cab2d6'], 'indexed', numCommunities)
    cmapDark = colors.ListedColormap(['#1f78b4', '#33a02c', '#e31a1c', '#ff7f00', '#6a3d9a'], 'indexed', numCommunities)

    # Draw edges
    nx.draw_networkx_edges(G, pos)

    # Draw nodes
    nodeCollection = nx.draw_networkx_nodes(G,
        pos = pos,
        node_color = communities,
        cmap = cmapLight
    )
    # Set node border color to the darker shade
    darkColors = [cmapDark(v) for v in communities]
    nodeCollection.set_edgecolor(darkColors)

    # Draw node labels
    for n in G.nodes():
        plt.annotate(n,
            xy = pos[n],
            textcoords = 'offset points',
            horizontalalignment = 'center',
            verticalalignment = 'center',
            xytext = [0, 0],
            color = cmapDark(communities[n])
        )

    plt.axis('off')
    # plt.savefig("karate.png")
    plt.show()



G=nx.karate_club_graph()

findCommunities(G)

drawNetwork(G)

最終輸出:

Building Infomap network from a NetworkX graph...
Find communities with Infomap...
Found 3 modules with codelength: 4.311793

在這裏插入圖片描述

其中編號爲0的點有錯誤,筆者也沒深究。。


5.2 v1.0版本分層infoMap——Multilayer

分層指的是節點本身是有層次關係的,現在很多知識圖譜本來就有非常多的等級。

從實驗來看,初始化狀態infomap和network,應該是沒差別的。

5.2.1 infomap直接初始化

import infomap

infomapWrapper = infomap.Infomap("--two-level --directed")

# from (layer, node) to (layer, node) weight
# infomapWrapper.addMultiplexLink(2, 1, 1, 2, 1.0)
# infomapWrapper.addMultiplexLink(1, 2, 2, 1, 1.0)
# infomapWrapper.addMultiplexLink(3, 2, 2, 3, 1.0)

# from (layer, node) to (layer, node) weight
infomapWrapper.addMultilayerLink(2, 1, 1, 2, 1.0)
infomapWrapper.addMultilayerLink(1, 2, 2, 1, 1.0)
infomapWrapper.addMultilayerLink(3, 2, 2, 3, 1.0)


infomapWrapper.run()

tree = infomapWrapper

print("Found %d modules with codelength: %f" % (tree.numTopModules(), tree.codelength()))


for node in tree.iterTree():
    print(node.stateId,node.physicalId,node.moduleIndex(),node.path(),node.data.flow,node.data.enterFlow,node.data.exitFlow)

輸出:

Found 2 modules with codelength: 0.930233
0 0 0 () 0.9999999999999998 0.0 0.0
0 0 0 (0,) 0.9302325581395346 0.0 0.0
0 1 0 (0, 0) 0.4651162790697673 0.4651162790697673 0.4651162790697673
1 2 0 (0, 1) 0.4651162790697673 0.4651162790697673 0.4651162790697673
0 0 1 (1,) 0.06976744186046516 0.0 0.0
2 2 1 (1, 0) 0.0 0.0 0.06976744186046516
3 3 1 (1, 1) 0.06976744186046516 0.06976744186046516 0.0

5.2.2 network初始化

import infomap

infomapWrapper = infomap.Infomap("--two-level --directed")

# from (layer, node) to (layer, node) weight
# infomapWrapper.addMultiplexLink(2, 1, 1, 2, 1.0)
# infomapWrapper.addMultiplexLink(1, 2, 2, 1, 1.0)
# infomapWrapper.addMultiplexLink(3, 2, 2, 3, 1.0)

network = infomapWrapper.network() 

# from (layer, node) to (layer, node) weight
network.addMultilayerLink(2, 1, 1, 2, 1.0)
network.addMultilayerLink(1, 2, 2, 1, 1.0)
network.addMultilayerLink(3, 2, 2, 3, 1.0)

infomapWrapper.run()

tree = infomapWrapper

print("Found %d modules with codelength: %f" % (tree.numTopModules(), tree.codelength()))


for node in tree.iterTree():
    print(node.stateId,node.physicalId,node.moduleIndex(),node.path(),node.data.flow,node.data.enterFlow,node.data.exitFlow)


輸出:

Found 2 modules with codelength: 0.930233
0 0 0 () 0.9999999999999998 0.0 0.0
0 0 0 (0,) 0.9302325581395346 0.0 0.0
0 1 0 (0, 0) 0.4651162790697673 0.4651162790697673 0.4651162790697673
1 2 0 (0, 1) 0.4651162790697673 0.4651162790697673 0.4651162790697673
0 0 1 (1,) 0.06976744186046516 0.0 0.0
2 2 1 (1, 0) 0.0 0.0 0.06976744186046516
3 3 1 (1, 1) 0.06976744186046516 0.06976744186046516 0.0

其中,node.stateId 在一般的網絡之中就等於node.physicalId,在分層網絡addMultilayerLink中兩者 纔有差異。

stateId 

The state node id, equals physicalId for ordinary networks

Returns
-------
unsigned int
    The state node id

其中,node.depth()是節點當前的深度。

The current depth from the start node in the iterator

Returns
-------
unsigned int
    The current depth

node.depth()

其中,addMultilayerLink 包括:network.addMultilayerLink(layer1, n1, layer2, n2, weight)
其中,node.data,其中的這個flow與編碼相關

"""
The flow data of the node that defines:
node.data.flow
node.data.enterFlow
node.data.exitFlow

Returns
-------
FlowData
    The flow data

"""
node.data


參考:

1 機器學習-社區發現算法介紹(一):Infomap
Source code for multilevel community detection with Infomap
3 Multi-level network clustering based on the Map Equation
4 最小熵原理(五):“層層遞進”之社區發現與聚類
mapequation/infomap

發佈了262 篇原創文章 · 獲贊 1515 · 訪問量 454萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章