一文直击Graph Embedding图表示学习的原理及应用
導(dǎo)讀:我們都知道在數(shù)據(jù)結(jié)構(gòu)中,圖是一種基礎(chǔ)且常用的結(jié)構(gòu)。現(xiàn)實(shí)世界中許多場景可以抽象為一種圖結(jié)構(gòu),如社交網(wǎng)絡(luò),交通網(wǎng)絡(luò),電商網(wǎng)站中用戶與物品的關(guān)系等。
目前提到圖算法一般指:
-
經(jīng)典數(shù)據(jù)結(jié)構(gòu)與算法層面的:最小生成樹 (Prim,Kruskal,...) ,最短路 (Dijkstra,Floyed,...) ,拓?fù)渑判?#xff0c;關(guān)鍵路徑等
-
概率圖模型,涉及圖的表示,推斷和學(xué)習(xí),詳細(xì)可以參考 Koller 的書或者公開課
-
圖神經(jīng)網(wǎng)絡(luò),主要包括 Graph Embedding (基于隨機(jī)游走)和 Graph CNN (基于鄰居匯聚)兩部分。
這里先看下 Graph Embedding 的相關(guān)內(nèi)容。
Graph Embedding 技術(shù)將圖中的節(jié)點(diǎn)以低維稠密向量的形式進(jìn)行表達(dá),要求在原始圖中相似 ( 不同的方法對相似的定義不同 ) 的節(jié)點(diǎn)其在低維表達(dá)空間也接近。得到的表達(dá)向量可以用來進(jìn)行下游任務(wù),如節(jié)點(diǎn)分類,鏈接預(yù)測,可視化或重構(gòu)原始圖等。
01
DeepWalk
雖然 DeepWalk 是 KDD 2014的工作,但卻是我們了解 Graph Embedding 無法繞過的一個方法。
我們都知道在 NLP 任務(wù)中,word2vec 是一種常用的 word embedding 方法, word2vec 通過語料庫中的句子序列來描述詞與詞的共現(xiàn)關(guān)系,進(jìn)而學(xué)習(xí)到詞語的向量表示。
DeepWalk 的思想類似 word2vec,使用圖中節(jié)點(diǎn)與節(jié)點(diǎn)的共現(xiàn)關(guān)系來學(xué)習(xí)節(jié)點(diǎn)的向量表示。那么關(guān)鍵的問題就是如何來描述節(jié)點(diǎn)與節(jié)點(diǎn)的共現(xiàn)關(guān)系,DeepWalk 給出的方法是使用隨機(jī)游走 (RandomWalk) 的方式在圖中進(jìn)行節(jié)點(diǎn)采樣。
RandomWalk 是一種可重復(fù)訪問已訪問節(jié)點(diǎn)的深度優(yōu)先遍歷算法。給定當(dāng)前訪問起始節(jié)點(diǎn),從其鄰居中隨機(jī)采樣節(jié)點(diǎn)作為下一個訪問節(jié)點(diǎn),重復(fù)此過程,直到訪問序列長度滿足預(yù)設(shè)條件。
獲取足夠數(shù)量的節(jié)點(diǎn)訪問序列后,使用 skip-gram model 進(jìn)行向量學(xué)習(xí)。
DeepWalk 核心代碼
DeepWalk 算法主要包括兩個步驟,第一步為隨機(jī)游走采樣節(jié)點(diǎn)序列,第二步為使用 skip-gram modelword2vec 學(xué)習(xí)表達(dá)向量。
-
構(gòu)建同構(gòu)網(wǎng)絡(luò),從網(wǎng)絡(luò)中的每個節(jié)點(diǎn)開始分別進(jìn)行 Random Walk 采樣,得到局部相關(guān)聯(lián)的訓(xùn)練數(shù)據(jù)
-
對采樣數(shù)據(jù)進(jìn)行 SkipGram 訓(xùn)練,將離散的網(wǎng)絡(luò)節(jié)點(diǎn)表示成向量化,最大化節(jié)點(diǎn)共現(xiàn),使用 Hierarchical Softmax 來做超大規(guī)模分類的分類器
Random Walk
我們可以通過并行的方式加速路徑采樣,在采用多進(jìn)程進(jìn)行加速時,相比于開一個進(jìn)程池讓每次外層循環(huán)啟動一個進(jìn)程,我們采用固定為每個進(jìn)程分配指定數(shù)量的num_walks的方式,這樣可以最大限度減少進(jìn)程頻繁創(chuàng)建與銷毀的時間開銷。
deepwalk_walk方法對應(yīng)上一節(jié)偽代碼中第6行,_simulate_walks對應(yīng)偽代碼中第3行開始的外層循環(huán)。最后的Parallel為多進(jìn)程并行時的任務(wù)分配操作。
def deepwalk_walk(self, walk_length, start_node):walk = [start_node]while len(walk) < walk_length:cur = walk[-1]cur_nbrs = list(self.G.neighbors(cur))if len(cur_nbrs) > 0:walk.append(random.choice(cur_nbrs))else:breakreturn walkdef _simulate_walks(self, nodes, num_walks, walk_length,):walks = []for _ in range(num_walks):random.shuffle(nodes)for v in nodes: walks.append(self.deepwalk_walk(alk_length=walk_length, start_node=v))return walksresults = Parallel(n_jobs=workers, verbose=verbose, )(delayed(self._simulate_walks)(nodes, num, walk_length) for num inpartition_num(num_walks, workers))walks = list(itertools.chain(*results))Word2vec
這里就偷個懶直接用gensim里的 Word2Vec 了。
from gensim.models import Word2Vec w2v_model = Word2Vec(walks,sg=1,hs=1)DeepWalk 應(yīng)用
這里簡單的用 DeepWalk 算法在 wiki 數(shù)據(jù)集上進(jìn)行節(jié)點(diǎn)分類任務(wù)和可視化任務(wù)。? wiki 數(shù)據(jù)集包含 2,405 個網(wǎng)頁和17,981條網(wǎng)頁之間的鏈接關(guān)系,以及每個網(wǎng)頁的所屬類別。
本例中的訓(xùn)練,評測和可視化的完整代碼在下面的 git 倉庫中:
https://github.com/shenweichen/GraphEmbedding
G = nx.read_edgelist('../data/wiki/Wiki_edgelist.txt',create_using=nx.DiGraph(),nodetype=None,data=[('weight',int)])model = DeepWalk(G,walk_length=10,num_walks=80,workers=1) model.train(window_size=5,iter=3) embeddings = model.get_embeddings()evaluate_embeddings(embeddings) plot_embeddings(embeddings)分類任務(wù)結(jié)果
micro-F1 : 0.6674
macro-F1 : 0.5768
可視化結(jié)果
02
LINE
之前介紹過DeepWalk,DeepWalk使用DFS隨機(jī)游走在圖中進(jìn)行節(jié)點(diǎn)采樣,使用word2vec在采樣的序列學(xué)習(xí)圖中節(jié)點(diǎn)的向量表示。
LINE也是一種基于鄰域相似假設(shè)的方法,只不過與DeepWalk使用DFS構(gòu)造鄰域不同的是,LINE可以看作是一種使用BFS構(gòu)造鄰域的算法。此外,LINE還可以應(yīng)用在帶權(quán)圖中(DeepWalk僅能用于無權(quán)圖)。
之前還提到不同的graph embedding方法的一個主要區(qū)別是對圖中頂點(diǎn)之間的相似度的定義不同,所以先看一下LINE對于相似度的定義。
LINE 算法原理
1. 一種新的相似度定義
first-order proximity
1階相似度用于描述圖中成對頂點(diǎn)之間的局部相似度,形式化描述為若之間存在直連邊,則邊權(quán)即為兩個頂點(diǎn)的相似度,若不存在直連邊,則1階相似度為0。如上圖,6和7之間存在直連邊,且邊權(quán)較大,則認(rèn)為兩者相似且1階相似度較高,而5和6之間不存在直連邊,則兩者間1階相似度為0。
second-order proximity
僅有1階相似度就夠了嗎?顯然不夠,如上圖,雖然5和6之間不存在直連邊,但是他們有很多相同的鄰居頂點(diǎn)(1,2,3,4),這其實(shí)也可以表明5和6是相似的,而2階相似度就是用來描述這種關(guān)系的。形式化定義為,令表示頂點(diǎn)與所有其他頂點(diǎn)間的1階相似度,則與的2階相似度可以通過和的相似度表示。若與之間不存在相同的鄰居頂點(diǎn),則2階相似度為0。
2. 優(yōu)化目標(biāo)
1st-order
對于每一條無向邊,定義頂點(diǎn)和之間的聯(lián)合概率為:
,為頂點(diǎn)的低維向量表示。(可以看作一個內(nèi)積模型,計(jì)算兩個item之間的匹配程度)
同時定義經(jīng)驗(yàn)分布:
,?
優(yōu)化目標(biāo)為最小化:
是兩個分布的距離,常用的衡量兩個概率分布差異的指標(biāo)為KL散度,使用KL散度并忽略常數(shù)項(xiàng)后有:
1st order 相似度只能用于無向圖當(dāng)中。
2nd-order
這里對于每個頂點(diǎn)維護(hù)兩個embedding向量,一個是該頂點(diǎn)本身的表示向量,一個是該點(diǎn)作為其他頂點(diǎn)的上下文頂點(diǎn)時的表示向量。
對于有向邊,定義給定頂點(diǎn)條件下,產(chǎn)生上下文(鄰居)頂點(diǎn)的概率為:
,其中為上下文頂點(diǎn)的個數(shù)。
優(yōu)化目標(biāo)為:
,其中為控制節(jié)點(diǎn)重要性的因子,可以通過頂點(diǎn)的度數(shù)或者PageRank等方法估計(jì)得到。
經(jīng)驗(yàn)分布定義為:
,是邊的邊權(quán),是頂點(diǎn)的出度,對于帶權(quán)圖,使用KL散度并設(shè),忽略常數(shù)項(xiàng),有
3. 優(yōu)化技巧
Negative sampling
由于計(jì)算2階相似度時,softmax函數(shù)的分母計(jì)算需要遍歷所有頂點(diǎn),這是非常低效的,論文采用了負(fù)采樣優(yōu)化的技巧,目標(biāo)函數(shù)變?yōu)?#xff1a;
,是負(fù)邊的個數(shù)。
論文使用,是頂點(diǎn)的出度。
Edge Sampling
注意到我們的目標(biāo)函數(shù)在log之前還有一個權(quán)重系數(shù),在使用梯度下降方法優(yōu)化參數(shù)時,會直接乘在梯度上。如果圖中的邊權(quán)方差很大,則很難選擇一個合適的學(xué)習(xí)率。若使用較大的學(xué)習(xí)率那么對于較大的邊權(quán)可能會引起梯度爆炸,較小的學(xué)習(xí)率對于較小的邊權(quán)則會導(dǎo)致梯度過小。
對于上述問題,如果所有邊權(quán)相同,那么選擇一個合適的學(xué)習(xí)率會變得容易。這里采用了將帶權(quán)邊拆分為等權(quán)邊的一種方法,假如一個權(quán)重為的邊,則拆分后為個權(quán)重為1的邊。這樣可以解決學(xué)習(xí)率選擇的問題,但是由于邊數(shù)的增長,存儲的需求也會增加。
另一種方法則是從原始的帶權(quán)邊中進(jìn)行采樣,每條邊被采樣的概率正比于原始圖中邊的權(quán)重,這樣既解決了學(xué)習(xí)率的問題,又沒有帶來過多的存儲開銷。
這里的采樣算法使用的是Alias算法,Alias是一種時間復(fù)雜度的離散事件抽樣算法。具體內(nèi)容可以參考
Alias Method:時間復(fù)雜度O(1)的離散采樣方法
https://zhuanlan.zhihu.com/p/54867139
4. 其他問題
低度數(shù)頂點(diǎn)
對于一些頂點(diǎn)由于其鄰接點(diǎn)非常少會導(dǎo)致embedding向量的學(xué)習(xí)不充分,論文提到可以利用鄰居的鄰居構(gòu)造樣本進(jìn)行學(xué)習(xí),這里也暴露出LINE方法僅考慮一階和二階相似性,對高階信息的利用不足。
新加入頂點(diǎn)
對于新加入圖的頂點(diǎn),若該頂點(diǎn)與圖中頂點(diǎn)存在邊相連,我們只需要固定模型的其他參數(shù),優(yōu)化如下兩個目標(biāo)之一即可:
若不存在邊相連,則需要利用一些side info,留到后續(xù)工作研究。
LINE核心代碼
1. 模型和損失函數(shù)定義
LINE使用梯度下降的方法進(jìn)行優(yōu)化,直接使用tensorflow進(jìn)行實(shí)現(xiàn),就可以不用人工寫參數(shù)更新的邏輯了~
這里的 實(shí)現(xiàn)中把1階和2階的方法融合到一起了,可以通過超參數(shù)order控制是分開優(yōu)化還是聯(lián)合優(yōu)化,論文推薦分開優(yōu)化。
首先輸入就是兩個頂點(diǎn)的編號,然后分別拿到各自對應(yīng)的embedding向量,最后輸出內(nèi)積的結(jié)果。真實(shí)label定義為1或者-1,通過模型輸出的內(nèi)積和line_loss就可以優(yōu)化使用了負(fù)采樣技巧的目標(biāo)函數(shù)了~
def line_loss(y_true, y_pred):return -K.mean(K.log(K.sigmoid(y_true*y_pred)))def create_model(numNodes, embedding_size, order='second'):v_i = Input(shape=(1,))v_j = Input(shape=(1,))first_emb = Embedding(numNodes, embedding_size, name='first_emb')second_emb = Embedding(numNodes, embedding_size, name='second_emb')context_emb = Embedding(numNodes, embedding_size, name='context_emb')v_i_emb = first_emb(v_i)v_j_emb = first_emb(v_j)v_i_emb_second = second_emb(v_i)v_j_context_emb = context_emb(v_j)first = Lambda(lambda x: tf.reduce_sum(x[0]*x[1], axis=-1, keep_dims=False), name='first_order')([v_i_emb, v_j_emb])second = Lambda(lambda x: tf.reduce_sum(x[0]*x[1], axis=-1, keep_dims=False), name='second_order')([v_i_emb_second, v_j_context_emb])if order == 'first':output_list = [first]elif order == 'second':output_list = [second]else:output_list = [first, second]model = Model(inputs=[v_i, v_j], outputs=output_list)2. 頂點(diǎn)負(fù)采樣和邊采樣
下面的函數(shù)功能是創(chuàng)建頂點(diǎn)負(fù)采樣和邊采樣需要的采樣表。中規(guī)中矩,主要就是做一些預(yù)處理,然后創(chuàng)建alias算法需要的兩個表。
def _gen_sampling_table(self):# create sampling table for vertexpower = 0.75numNodes = self.node_sizenode_degree = np.zeros(numNodes) ?# out degreenode2idx = self.node2idxfor edge in self.graph.edges():node_degree[node2idx[edge[0]]] += self.graph[edge[0]][edge[1]].get('weight', 1.0)total_sum = sum([math.pow(node_degree[i], power)for i in range(numNodes)])norm_prob = [float(math.pow(node_degree[j], power)) /total_sum for j in range(numNodes)]self.node_accept, self.node_alias = create_alias_table(norm_prob)# create sampling table for edgenumEdges = self.graph.number_of_edges()total_sum = sum([self.graph[edge[0]][edge[1]].get('weight', 1.0)for edge in self.graph.edges()])norm_prob = [self.graph[edge[0]][edge[1]].get('weight', 1.0) *numEdges / total_sum for edge in self.graph.edges()]self.edge_accept, self.edge_alias = create_alias_table(norm_prob)LINE 應(yīng)用
和之前一樣,還是用LINE在wiki數(shù)據(jù)集上進(jìn)行節(jié)點(diǎn)分類任務(wù)和可視化任務(wù)。wiki數(shù)據(jù)集包含 2,405 個網(wǎng)頁和17,981條網(wǎng)頁之間的鏈接關(guān)系,以及每個網(wǎng)頁的所屬類別。由于1階相似度僅能應(yīng)用于無向圖中,所以本例中僅使用2階相似度。
本例中的訓(xùn)練,評測和可視化的完整代碼在下面的git倉庫中,
https://github.com/shenweichen/GraphEmbedding
G = nx.read_edgelist('../data/wiki/Wiki_edgelist.txt',create_using=nx.DiGraph(),nodetype=None,data=[('weight',int)])model = LINE(G,embedding_size=128,order='second') model.train(batch_size=1024,epochs=50,verbose=2) embeddings = model.get_embeddings()evaluate_embeddings(embeddings) plot_embeddings(embeddings)分類任務(wù)結(jié)果
micro-F1: 0.6403
macro-F1:0.5286
結(jié)果有一定隨機(jī)性,可以多運(yùn)行幾次,或者稍微調(diào)整epoch個數(shù)。
可視化結(jié)果
03
nodo2vec
前面介紹過基于DFS鄰域的DeepWalk和基于BFS鄰域的LINE。
node2vec是一種綜合考慮DFS鄰域和BFS鄰域的graph embedding方法。簡單來說,可以看作是deepwalk的一種擴(kuò)展,是結(jié)合了DFS和BFS隨機(jī)游走的deepwalk。
nodo2vec 算法原理
1. 優(yōu)化目標(biāo)
設(shè)?是將頂點(diǎn)?映射為embedding向量的映射函數(shù),對于圖中每個頂點(diǎn),定義?為通過采樣策略采樣出的頂點(diǎn)?的近鄰頂點(diǎn)集合。
node2vec優(yōu)化的目標(biāo)是給定每個頂點(diǎn)條件下,令其近鄰頂點(diǎn)(如何定義近鄰頂點(diǎn)很重要)出現(xiàn)的概率最大。
為了將上述最優(yōu)化問題可解,文章提出兩個假設(shè):
-
條件獨(dú)立性假設(shè)
假設(shè)給定源頂點(diǎn)下,其近鄰頂點(diǎn)出現(xiàn)的概率與近鄰集合中其余頂點(diǎn)無關(guān)。
-
特征空間對稱性假設(shè)
這里是說一個頂點(diǎn)作為源頂點(diǎn)和作為近鄰頂點(diǎn)的時候共享同一套embedding向量。(對比LINE中的2階相似度,一個頂點(diǎn)作為源點(diǎn)和近鄰點(diǎn)的時候是擁有不同的embedding向量的) 在這個假設(shè)下,上述條件概率公式可表示為:
根據(jù)以上兩個假設(shè)條件,最終的目標(biāo)函數(shù)表示為:
由于歸一化因子:
的計(jì)算代價高,所以采用負(fù)采樣技術(shù)優(yōu)化。
2. 頂點(diǎn)序列采樣策略
node2vec依然采用隨機(jī)游走的方式獲取頂點(diǎn)的近鄰序列,不同的是node2vec采用的是一種有偏的隨機(jī)游走。
給定當(dāng)前頂點(diǎn)?,訪問下一個頂點(diǎn)的概率為:
是頂點(diǎn)?和頂點(diǎn)之間的未歸一化轉(zhuǎn)移概率,?是歸一化常數(shù)。
node2vec引入兩個超參數(shù)?和來控制隨機(jī)游走的策略,假設(shè)當(dāng)前隨機(jī)游走經(jīng)過邊?到達(dá)頂點(diǎn)?設(shè),是頂點(diǎn)?和之間的邊權(quán),
為頂點(diǎn)和頂點(diǎn)之間的最短路徑距離。
下面討論超參數(shù)p和 q對游走策略的影響
-
Return parameter,p
參數(shù)p控制重復(fù)訪問剛剛訪問過的頂點(diǎn)的概率。注意到p僅作用于的情況,而?表示頂點(diǎn)x就是訪問當(dāng)前頂點(diǎn)?之前剛剛訪問過的頂點(diǎn)。那么若 p較高,則訪問剛剛訪問過的頂點(diǎn)的概率會變低,反之變高。
-
In-out papameter,q
q控制著游走是向外還是向內(nèi),若,隨機(jī)游走傾向于訪問和t接近的頂點(diǎn)(偏向BFS)。若,傾向于訪問遠(yuǎn)離t的頂點(diǎn)(偏向DFS)。
下面的圖描述的是當(dāng)從t訪問到時,決定下一個訪問頂點(diǎn)時每個頂點(diǎn)對應(yīng)的。
3. 學(xué)習(xí)算法
采樣完頂點(diǎn)序列后,剩下的步驟就和deepwalk一樣了,用word2vec去學(xué)習(xí)頂點(diǎn)的embedding向量。值得注意的是node2vecWalk中不再是隨機(jī)抽取鄰接點(diǎn),而是按概率抽取,node2vec采用了Alias算法進(jìn)行頂點(diǎn)采樣。
Alias Method:時間復(fù)雜度O(1)的離散采樣方法
https://zhuanlan.zhihu.com/p/54867139
node2vec 核心代碼
1. node2vecWalk
通過上面的偽代碼可以看到,node2vec和deepwalk非常類似,主要區(qū)別在于頂點(diǎn)序列的采樣策略不同,所以這里我們主要關(guān)注node2vecWalk的實(shí)現(xiàn)。
由于采樣時需要考慮前面2步訪問過的頂點(diǎn),所以當(dāng)訪問序列中只有1個頂點(diǎn)時,直接使用當(dāng)前頂點(diǎn)和鄰居頂點(diǎn)之間的邊權(quán)作為采樣依據(jù)。當(dāng)序列多余2個頂點(diǎn)時,使用文章提到的有偏采樣。
def node2vec_walk(self, walk_length, start_node):G = self.G ? ?alias_nodes = self.alias_nodes ? ?alias_edges = self.alias_edgeswalk = [start_node]while len(walk) < walk_length: ? ? ? ?cur = walk[-1] ? ? ? ?cur_nbrs = list(G.neighbors(cur)) ? ? ? ?if len(cur_nbrs) > 0: ? ? ? ? ? ?if len(walk) == 1: ? ? ? ? ? ? ? ?walk.append(cur_nbrs[alias_sample(alias_nodes[cur][0], alias_nodes[cur][1])]) ? ? ? ? ? ?else: ? ? ? ? ? ? ? ?prev = walk[-2] ? ? ? ? ? ? ? ?edge = (prev, cur) ? ? ? ? ? ? ? ?next_node = cur_nbrs[alias_sample(alias_edges[edge][0],alias_edges[edge][1])] ? ? ? ? ? ? ? ?walk.append(next_node) ? ? ? ?else: ? ? ? ? ? ?breakreturn walk2. 構(gòu)造采樣表
preprocess_transition_probs分別生成alias_nodes和alias_edges,alias_nodes存儲著在每個頂點(diǎn)時決定下一次訪問其鄰接點(diǎn)時需要的alias表(不考慮當(dāng)前頂點(diǎn)之前訪問的頂點(diǎn))。alias_edges存儲著在前一個訪問頂點(diǎn)為t,當(dāng)前頂點(diǎn)為?時決定下一次訪問哪個鄰接點(diǎn)時需要的alias表。
get_alias_edge方法返回的是在上一次訪問頂點(diǎn) t,當(dāng)前訪問頂點(diǎn)為時到下一個頂點(diǎn)的未歸一化轉(zhuǎn)移概率:
def get_alias_edge(self, t, v):G = self.G ? ?p = self.p ? ?q = self.qunnormalized_probs = [] ? ?for x in G.neighbors(v): ? ? ? ?weight = G[v][x].get('weight', 1.0)# w_vx ? ? ? ?if x == t:# d_tx == 0 ? ? ? ? ? ?unnormalized_probs.append(weight/p) ? ? ? ?elif G.has_edge(x, t):# d_tx == 1 ? ? ? ? ? ?unnormalized_probs.append(weight) ? ? ? ?else:# d_tx == 2 ? ? ? ? ? ?unnormalized_probs.append(weight/q) ? ?norm_const = sum(unnormalized_probs) ? ?normalized_probs = [float(u_prob)/norm_const for u_prob in unnormalized_probs]return create_alias_table(normalized_probs)def preprocess_transition_probs(self):G = self.Galias_nodes = {} ? ?for node in G.nodes(): ? ? ? ?unnormalized_probs = [G[node][nbr].get('weight', 1.0) for nbr in G.neighbors(node)] ? ? ? ?norm_const = sum(unnormalized_probs) ? ? ? ?normalized_probs = [float(u_prob)/norm_const for u_prob in unnormalized_probs] ? ? ? ? ? ? ? ?alias_nodes[node] = create_alias_table(normalized_probs)alias_edges = {}for edge in G.edges(): ? ? ? ?alias_edges[edge] = self.get_alias_edge(edge[0], edge[1])self.alias_nodes = alias_nodes ? ?self.alias_edges = alias_edgesreturnnode2vec 應(yīng)用
使用node2vec在wiki數(shù)據(jù)集上進(jìn)行節(jié)點(diǎn)分類任務(wù)和可視化任務(wù)。wiki數(shù)據(jù)集包含 2,405 個網(wǎng)頁和17,981條網(wǎng)頁之間的鏈接關(guān)系,以及每個網(wǎng)頁的所屬類別。通過簡單的超參搜索,這里使用p=0.25,q=4的設(shè)置。
本例中的訓(xùn)練,評測和可視化的完整代碼在下面的git倉庫中:
https://github.com/shenweichen/GraphEmbedding
G = nx.read_edgelist('../data/wiki/Wiki_edgelist.txt',create_using=nx.DiGraph(),nodetype=None,data=[('weight',int)])model = Node2Vec(G,walk_length=10,num_walks=80,p=0.25,q=4,workers=1) model.train(window_size=5,iter=3) ? ? embeddings = model.get_embeddings()evaluate_embeddings(embeddings) plot_embeddings(embeddings)分類任務(wù)
micro-F1: 0.6757 macro-F1: 0.5917
這個結(jié)果相比于DeepWalk和LINE是有提升的。
可視化
這個結(jié)果相比于DeepWalk和LINE可以看到不同類別的分布更加分散了。
最后補(bǔ)充一個node2vec在業(yè)界的應(yīng)用介紹
當(dāng)機(jī)器學(xué)習(xí)遇上復(fù)雜網(wǎng)絡(luò):解析微信朋友圈 Lookalike 算法
04
SDNE
SDNE(Structural Deep Network Embedding )是和node2vec并列的工作,均發(fā)表在2016年的KDD會議中。可以看作是基于LINE的擴(kuò)展,同時也是第一個將深度學(xué)習(xí)應(yīng)用于網(wǎng)絡(luò)表示學(xué)習(xí)中的方法。
SDNE使用一個自動編碼器結(jié)構(gòu)來同時優(yōu)化1階和2階相似度(LINE是分別優(yōu)化的),學(xué)習(xí)得到的向量表示能夠保留局部和全局結(jié)構(gòu),并且對稀疏網(wǎng)絡(luò)具有魯棒性。
SDNE 算法原理
相似度定義
SDNE中的相似度定義和LINE是一樣的。簡單來說,1階相似度衡量的是相鄰的兩個頂點(diǎn)對之間相似性。2階相似度衡量的是,兩個頂點(diǎn)他們的鄰居集合的相似程度。
2階相似度優(yōu)化目標(biāo)
這里我們使用圖的鄰接矩陣進(jìn)行輸入,對于第個頂點(diǎn),有,每一個都包含了頂點(diǎn)i的鄰居結(jié)構(gòu)信息,所以這樣的重構(gòu)過程能夠使得結(jié)構(gòu)相似的頂點(diǎn)具有相似的embedding表示向量。
這里存在的一個問題是由于圖的稀疏性,鄰接矩陣S中的非零元素是遠(yuǎn)遠(yuǎn)少于零元素的,那么對于神經(jīng)網(wǎng)絡(luò)來說只要全部輸出0也能取得一個不錯的效果,這不是我們想要的。
文章給出的一個方法是使用帶權(quán)損失函數(shù),對于非零元素具有更高的懲罰系數(shù)。修正后的損失函數(shù)為
其中為逐元素積,,若,則,否則
1階相似度優(yōu)化目標(biāo)
對于1階相似度,損失函數(shù)定義如下
該損失函數(shù)可以讓圖中的相鄰的兩個頂點(diǎn)對應(yīng)的embedding vector在隱藏空間接近。
還可以表示為
其中L是圖對應(yīng)的拉普拉斯矩陣,,D是圖中頂點(diǎn)的度矩陣,S是鄰接矩陣,?。
整體優(yōu)化目標(biāo)
聯(lián)合優(yōu)化的損失函數(shù)為
是正則化項(xiàng),為控制1階損失的參數(shù),為控制正則化項(xiàng)的參數(shù)。
模型結(jié)構(gòu)
? ??
先看左邊,是一個自動編碼器的結(jié)構(gòu),輸入輸出分別是鄰接矩陣和重構(gòu)后的鄰接矩陣。通過優(yōu)化重構(gòu)損失可以保留頂點(diǎn)的全局結(jié)構(gòu)特性(論文的圖畫錯了,上面應(yīng)該是Global structure preserved cost)。
再看中間一排,就是我們需要的embedding向量,模型通過1階損失函數(shù)使得鄰接的頂點(diǎn)對應(yīng)的embedding向量接近,從而保留頂點(diǎn)的局部結(jié)構(gòu)特性(中間應(yīng)該是 Local structure preserved cost)
實(shí)現(xiàn)
文章提出使用深度信念網(wǎng)絡(luò)進(jìn)行預(yù)訓(xùn)練來獲得較好的參數(shù)初始化,這里我就偷個懶省略這個步驟了~
損失函數(shù)定義
l_2nd是2階相似度對應(yīng)的損失函數(shù),參數(shù)beta控制著非零元素的懲罰項(xiàng)系數(shù)。y_true和y_pred分別是輸入的鄰接矩陣和網(wǎng)絡(luò)重構(gòu)出的鄰接矩陣。
l_1st是1階相似度對應(yīng)的損失函數(shù),參數(shù)alpha控制著其在整體損失函數(shù)中的占比。
def l_2nd(beta):
def loss_2nd(y_true, y_pred):
b_ = np.ones_like(y_true)
b_[y_true != 0] = beta
x = K.square((y_true - y_pred) * b_)
t = K.sum(x, axis=-1, )
return K.mean(t)
return loss_2nd
def l_1st(alpha):
def loss_1st(y_true, y_pred):
L = y_true
Y = y_pred
batch_size = tf.to_float(K.shape(L)[0])
return alpha * 2 * tf.linalg.trace(tf.matmul(tf.matmul(Y, L, transpose_a=True), Y)) / batch_size
return loss_1st
模型定義
create_model函數(shù)創(chuàng)建SDNE模型,l1和l2分別為模型的正則化項(xiàng)系數(shù),模型的輸入A為鄰接矩陣,L為拉普拉斯矩陣。輸出A_為重構(gòu)后的鄰接矩陣,Y為頂點(diǎn)的embedding向量。
函數(shù)中兩個for循環(huán)分別對應(yīng)encoder和decoder結(jié)構(gòu)。
def create_model(node_size, hidden_size=[256, 128], l1=1e-5, l2=1e-4):
A = Input(shape=(node_size,))
L = Input(shape=(None,))
fc = A
for i in range(len(hidden_size)):
if i == len(hidden_size) - 1:
fc = Dense(hidden_size[i], activation='relu',kernel_regularizer=l1_l2(l1, l2),name='1st')(fc)
else:
fc = Dense(hidden_size[i], activation='relu',kernel_regularizer=l1_l2(l1, l2))(fc)
Y = fc
for i in reversed(range(len(hidden_size) - 1)):
fc = Dense(hidden_size[i], activation='relu',kernel_regularizer=l1_l2(l1, l2))(fc)
A_ = Dense(node_size, 'relu', name='2nd')(fc)
model = Model(inputs=[A, L], outputs=[A_, Y])
return model
應(yīng)用
使用SDNE在wiki數(shù)據(jù)集上進(jìn)行節(jié)點(diǎn)分類任務(wù)和可視化任務(wù)(感興趣的同學(xué)可以試試別的數(shù)據(jù)集,我比較懶就選了個很小的數(shù)據(jù)集)。wiki數(shù)據(jù)集包含 2,405 個網(wǎng)頁和17,981條網(wǎng)頁之間的鏈接關(guān)系,以及每個網(wǎng)頁的所屬類別。
本例中的訓(xùn)練,評測和可視化的完整代碼在下面的git倉庫中:
https://github.com/shenweichen/GraphEmbedding
分類任務(wù)
micro-F1: 0.6341 macro-F1: 0.4962
可視化
這個貌似某些類別的點(diǎn)的向量都聚集在一起了,可能和超參的設(shè)置還有網(wǎng)絡(luò)權(quán)重的初始化有關(guān),我懶得調(diào)了~
? ? ?
這里還有一個SDNE在業(yè)界的應(yīng)用的介紹:
阿里湊單算法首次公開!基于Graph Embedding的打包購商品挖掘系統(tǒng)解析
05
Struc2Vec
前面介紹過DeepWalk,LINE,Node2Vec,SDNE幾個graph embedding方法。這些方法都是基于近鄰相似的假設(shè)的。其中DeepWalk,Node2Vec通過隨機(jī)游走在圖中采樣頂點(diǎn)序列來構(gòu)造頂點(diǎn)的近鄰集合。LINE顯式的構(gòu)造鄰接點(diǎn)對和頂點(diǎn)的距離為1的近鄰集合。SDNE使用鄰接矩陣描述頂點(diǎn)的近鄰結(jié)構(gòu)。
事實(shí)上,在一些場景中,兩個不是近鄰的頂點(diǎn)也可能擁有很高的相似性,對于這類相似性,上述方法是無法捕捉到的。Struc2Vec就是針對這類場景提出的。Struc2Vec的論文發(fā)表在2017年的KDD會議中。
Struc2Vec算法原理
相似度定義
Struc2Vec是從空間結(jié)構(gòu)相似性的角度定義頂點(diǎn)相似度的。
用下面的圖簡單解釋下,如果在基于近鄰相似的模型中,頂點(diǎn)u和頂點(diǎn)v是不相似的,第一他們不直接相連,第二他們不共享任何鄰居頂點(diǎn)。
而在struc2vec的假設(shè)中,頂點(diǎn)u和頂點(diǎn)v是具有空間結(jié)構(gòu)相似的。他們的度數(shù)分別為5和4,分別連接3個和2個三角形結(jié)構(gòu),通過2個頂點(diǎn)(d,e;x,w)和網(wǎng)絡(luò)的其他部分相連。
直觀來看,具有相同度數(shù)的頂點(diǎn)是結(jié)構(gòu)相似的,若各自鄰接頂點(diǎn)仍然具有相同度數(shù),那么他們的相似度就更高。
頂點(diǎn)對距離定義
令?表示到頂點(diǎn)u距離為k的頂點(diǎn)集合,則?表示是u的直接相連近鄰集合。
令?表示頂點(diǎn)集合S的有序度序列。
通過比較兩個頂點(diǎn)之間距離為k的環(huán)路上的有序度序列可以推出一種層次化衡量結(jié)構(gòu)相似度的方法。
令表示頂點(diǎn)u和v之間距離為k(這里的距離k實(shí)際上是指距離小于等于k的節(jié)點(diǎn)集合)的環(huán)路上的結(jié)構(gòu)距離(注意是距離,不是相似度)。
其中是衡量有序度序列和的距離的函數(shù),并且.
下面就是如何定義有序度序列之間的比較函數(shù)了,由于和的長度不同,并且可能含有重復(fù)元素。所以文章采用了Dynamic Time Warping(DTW)來衡量兩個有序度序列。
一句話,DTW可以用來衡量兩個不同長度且含有重復(fù)元素的的序列的距離(距離的定義可以自己設(shè)置)。
基于DTW,定義元素之間的距離函數(shù)
至于為什么使用這樣的距離函數(shù),這個距離函數(shù)實(shí)際上懲罰了當(dāng)兩個頂點(diǎn)的度數(shù)都比較小的時候兩者的差異。舉例來說,情況下的距離為1,情況下的距離差異為0.0099。這個特性正是我們想要的。
構(gòu)建層次帶權(quán)圖
根據(jù)上一節(jié)的距離定義,對于每一個我們都可以計(jì)算出兩個頂點(diǎn)之間的一個距離,現(xiàn)在要做的是通過上一節(jié)得到的頂點(diǎn)之間的有序度序列距離來構(gòu)建一個層次化的帶權(quán)圖(用于后續(xù)的隨機(jī)游走)。
我們定義在某一層k中兩個頂點(diǎn)的邊權(quán)為
這樣定義的邊權(quán)都是小于1的,當(dāng)且僅當(dāng)距離為0的是時候,邊權(quán)為1。
通過有向邊將屬于不同層次的同一頂點(diǎn)連接起來,具體來說,對每個頂點(diǎn),都會和其對應(yīng)的上層頂點(diǎn)還有下層頂點(diǎn)相連。邊權(quán)定義為
其中是第k層與u相連的邊的邊權(quán)大于平均邊權(quán)的邊的數(shù)量。
,就是第k層所有邊權(quán)的平均值。
采樣獲取頂點(diǎn)序列
使用有偏隨機(jī)游走在構(gòu)造出的圖中進(jìn)行頂點(diǎn)序列采樣。每次采樣時,首先決定是在當(dāng)前層游走,還是切換到上下層的層游走。
若決定在當(dāng)前層游走,設(shè)當(dāng)前處于第k層,則從頂點(diǎn)u到頂點(diǎn)v的概率為:
其中是第k層中關(guān)于頂點(diǎn)u的歸一化因子。
通過在圖M中進(jìn)行隨機(jī)游走,每次采樣的頂點(diǎn)更傾向于選擇與當(dāng)前頂點(diǎn)結(jié)構(gòu)相似的頂點(diǎn)。因此,采樣生成的上下文頂點(diǎn)很可能是結(jié)構(gòu)相似的頂點(diǎn),這與頂點(diǎn)在圖中的位置無關(guān)。
若決定切換不同的層,則以如下的概率選擇?層或?qū)?#xff0c;
三個時空復(fù)雜度優(yōu)化技巧:
-
OPT1 有序度序列長度優(yōu)化
前面提到過對于每個頂點(diǎn)在每一層都有一個有序度序列,而每一個度序列的空間復(fù)雜度為O(n)。
文章提出一種壓縮表示方法,對于序列中出現(xiàn)的每一個度,計(jì)算該度在序列里出現(xiàn)的次數(shù)。壓縮后的有序度序列存儲的是(度數(shù),出現(xiàn)次數(shù))這樣的二元組。
同時修改距離函數(shù)為:
為度數(shù),?為度的出現(xiàn)次數(shù)。
-
OPT2 相似度計(jì)算優(yōu)化
在原始的方法中,我們需要計(jì)算每一層k中,任意兩個頂點(diǎn)之間的相似度。事實(shí)上,這是不必要的。因?yàn)閮蓚€度數(shù)相差很大的頂點(diǎn),即使在的時候他們的距離也已經(jīng)非常大了,那么在隨機(jī)游走時這樣的邊就幾乎不可能被采樣到,所以我們也沒必要計(jì)算這兩個頂點(diǎn)之間的距離。
文章給出的方法是在計(jì)算頂點(diǎn)u和其他頂點(diǎn)之間的距離時,只計(jì)算那些與頂點(diǎn)u的度數(shù)接近的頂點(diǎn)的距離。具體來說,在頂點(diǎn)u對應(yīng)的有序度序列中進(jìn)行二分查找,查找的過程就是不斷逼近頂點(diǎn)u的度數(shù)的過程,只計(jì)算查找路徑上的頂點(diǎn)與u的距離。這樣每一次需要計(jì)算的邊的數(shù)量從數(shù)量級縮減到。
-
OPT3 限制層次帶權(quán)圖層數(shù)
層次帶權(quán)圖M中的層數(shù)是由圖的直徑?jīng)Q定的。但是對很多圖來說,圖的直徑會遠(yuǎn)遠(yuǎn)大于頂點(diǎn)之間的平均距離。
當(dāng)k接近?時,環(huán)上的度序列長度也會變得很短,??和會變得接近。
因此將圖中的層數(shù)限制為,使用最重要的一些層來評估結(jié)構(gòu)相似度。
這樣的限制顯著降低構(gòu)造M時的計(jì)算和存儲開銷。
Struc2Vec 核心代碼
Struc2Vec的實(shí)現(xiàn)相比于前面的幾個算法稍微復(fù)雜一些,這里我主要說下大體思路,對一些細(xì)節(jié)有疑問的同學(xué)可以郵件或者私信我~
根據(jù)前面的算法原理介紹,首先確定一下我們要做哪些事情 1. 獲取每一層的頂點(diǎn)對距離 2. 根據(jù)頂點(diǎn)對距離構(gòu)建帶權(quán)層次圖 3. 在帶權(quán)層次圖中隨機(jī)游走采樣頂點(diǎn)序列
頂點(diǎn)對距離計(jì)算
每一層的頂點(diǎn)對距離計(jì)算涉及到4個函數(shù),我們一個一個看。。。
首先看第一個函數(shù)_get_order_degreelist_node,這個函數(shù)的作用是計(jì)算頂點(diǎn)root對應(yīng)的有序度序列,也就是前面提到的。
這里我們采用層序遍歷的方式從root開始進(jìn)行遍歷,遍歷的過程計(jì)算當(dāng)前訪問的層次level,就是對應(yīng)文章中的。每次進(jìn)行節(jié)點(diǎn)訪問時只做一件事情,就是記錄該頂點(diǎn)的度數(shù)。當(dāng)level增加時,將當(dāng)前l(fā)evel中的度序列(如果使用優(yōu)化技巧就是壓縮度序列)排序,得到有序度序列。函數(shù)的返回值是一個字典,該字典存儲著root在每一層對應(yīng)的有序度序列。
第2個函數(shù)_compute_ordered_degreelist函數(shù)就很簡單了,用一個循環(huán)去計(jì)算每個頂點(diǎn)對應(yīng)的有序度序列。
def _get_order_degreelist_node(self, root, max_num_layers=None):if max_num_layers is None:max_num_layers = float('inf')ordered_degree_sequence_dict = {}visited = [False] * len(self.graph.nodes())queue = deque()level = 0queue.append(root)visited[root] = Truewhile (len(queue) > 0 and level <= max_num_layers):count = len(queue)if self.opt1_reduce_len:degree_list = {}else:degree_list = []while (count > 0):top = queue.popleft()node = self.idx2node[top]degree = len(self.graph[node])if self.opt1_reduce_len:degree_list[degree] = degree_list.get(degree, 0) + 1else:degree_list.append(degree)for nei in self.graph[node]:nei_idx = self.node2idx[nei]if not visited[nei_idx]:visited[nei_idx] = Truequeue.append(nei_idx)count -= 1if self.opt1_reduce_len:orderd_degree_list = [(degree, freq)for degree, freq in degree_list.items()]orderd_degree_list.sort(key=lambda x: x[0])else:orderd_degree_list = sorted(degree_list)ordered_degree_sequence_dict[level] = orderd_degree_listlevel += 1return ordered_degree_sequence_dictdef _compute_ordered_degreelist(self, max_num_layers):degreeList = {}vertices = self.idx # self.g.nodes()for v in vertices:degreeList[v] = self._get_order_degreelist_node(v, max_num_layers)return degreeList有了所有頂點(diǎn)對應(yīng)的后,我們要做的就是計(jì)算頂點(diǎn)對之間的距離?, 然后再利用公式?
得到頂點(diǎn)對之間的結(jié)構(gòu)距離?
這里先看第3個函數(shù)compute_dtw_dist,該函數(shù)實(shí)現(xiàn)的功能是計(jì)算頂點(diǎn)對之間的距離?,參數(shù)degreeList就是前面一步我們得到的存儲每個頂點(diǎn)在每一層的有序度序列的字典。
第4個函數(shù)convert_dtw_struc_dist的功能是根據(jù)compute_dtw_dist得到的頂點(diǎn)對距離完成關(guān)于?的迭代計(jì)算。
最后說明一下根據(jù)我們是否使用優(yōu)化技巧self.opt2_reduce_sim_calc函數(shù)會選擇計(jì)算所有頂點(diǎn)對間的距離,還是只計(jì)算度數(shù)接近的頂點(diǎn)對之間的距離。
def compute_dtw_dist(part_list, degreeList, dist_func):dtw_dist = {}for v1, nbs in part_list:lists_v1 = degreeList[v1] # lists_v1 :orderd degree list of v1for v2 in nbs:lists_v2 = degreeList[v2] # lists_v1 :orderd degree list of v2max_layer = min(len(lists_v1), len(lists_v2)) # valid layerdtw_dist[v1, v2] = {}for layer in range(0, max_layer):dist, path = fastdtw(lists_v1[layer], lists_v2[layer], radius=1, dist=dist_func)dtw_dist[v1, v2][layer] = distreturn dtw_distdef _compute_structural_distance(self, max_num_layers, workers=1, verbose=0,):if os.path.exists(self.temp_path+'structural_dist.pkl'):structural_dist = pd.read_pickle(self.temp_path+'structural_dist.pkl')else:if self.opt1_reduce_len:dist_func = cost_maxelse:dist_func = costif os.path.exists(self.temp_path + 'degreelist.pkl'):degreeList = pd.read_pickle(self.temp_path + 'degreelist.pkl')else:degreeList = self._compute_ordered_degreelist(max_num_layers)pd.to_pickle(degreeList, self.temp_path + 'degreelist.pkl')if self.opt2_reduce_sim_calc:degrees = self._create_vectors()degreeListsSelected = {}vertices = {}n_nodes = len(self.idx)for v in self.idx: # c:list of vertexnbs = get_vertices(v, len(self.graph[self.idx2node[v]]), degrees, n_nodes)vertices[v] = nbs # store nbsdegreeListsSelected[v] = degreeList[v] # store distfor n in nbs:# store dist of nbsdegreeListsSelected[n] = degreeList[n]else:vertices = {}for v in degreeList:vertices[v] = [vd for vd in degreeList.keys() if vd > v]results = Parallel(n_jobs=workers, verbose=verbose,)(delayed(compute_dtw_dist)(part_list, degreeList, dist_func) for part_list in partition_dict(vertices, workers))dtw_dist = dict(ChainMap(*results))structural_dist = convert_dtw_struc_dist(dtw_dist)pd.to_pickle(structural_dist, self.temp_path +'structural_dist.pkl')return structural_dist構(gòu)建帶權(quán)層次圖
構(gòu)建帶權(quán)層次圖的一個主要操作就是根據(jù)前面計(jì)算得到的每一層中頂點(diǎn)之間的結(jié)構(gòu)化距離來計(jì)算同一層中頂點(diǎn)之間和同一頂點(diǎn)在不同層之間的轉(zhuǎn)移概率,通過函數(shù)_get_transition_probs實(shí)現(xiàn)。
layers_adj存儲著每一層中每個頂點(diǎn)的鄰接點(diǎn),layers_distances存儲著每一層每個頂點(diǎn)對的結(jié)構(gòu)化距離。_get_transition_probs只做了一件事情,就是逐層的計(jì)算頂點(diǎn)之間的邊權(quán),并生成后續(xù)采樣需要的alias表。
def _get_transition_probs(self, layers_adj, layers_distances):layers_alias = {}layers_accept = {}for layer in layers_adj:neighbors = layers_adj[layer]layer_distances = layers_distances[layer]node_alias_dict = {}node_accept_dict = {}norm_weights = {}for v, neighbors in neighbors.items():e_list = []sum_w = 0.0for n in neighbors:if (v, n) in layer_distances:wd = layer_distances[v, n]else:wd = layer_distances[n, v]w = np.exp(-float(wd))e_list.append(w)sum_w += we_list = [x / sum_w for x in e_list]norm_weights[v] = e_listaccept, alias = create_alias_table(e_list)node_alias_dict[v] = aliasnode_accept_dict[v] = acceptpd.to_pickle(norm_weights, self.temp_path + 'norm_weights_distance-layer-' + str(layer)+'.pkl')layers_alias[layer] = node_alias_dictlayers_accept[layer] = node_accept_dictreturn layers_accept, layers_alias前面的部分僅僅得到了在同一層內(nèi),頂點(diǎn)之間的轉(zhuǎn)移概率,那么同一個頂點(diǎn)在不同層之間的轉(zhuǎn)移概率如何得到呢?
下面的prepare_biased_walk就是計(jì)算當(dāng)隨機(jī)游走需要跨層時,決定向上還是向下所用到的。
def prepare_biased_walk(self,):sum_weights = {}sum_edges = {}average_weight = {}gamma = {}layer = 0while (os.path.exists(self.temp_path+'norm_weights_distance-layer-' + str(layer))):probs = pd.read_pickle(self.temp_path+'norm_weights_distance-layer-' + str(layer))for v, list_weights in probs.items():sum_weights.setdefault(layer, 0)sum_edges.setdefault(layer, 0)sum_weights[layer] += sum(list_weights)sum_edges[layer] += len(list_weights)average_weight[layer] = sum_weights[layer] / sum_edges[layer]gamma.setdefault(layer, {})for v, list_weights in probs.items():num_neighbours = 0for w in list_weights:if (w > average_weight[layer]):num_neighbours += 1gamma[layer][v] = num_neighbourslayer += 1pd.to_pickle(average_weight, self.temp_path + 'average_weight')pd.to_pickle(gamma, self.temp_path + 'gamma.pkl')隨機(jī)游走采樣
采樣的主體框架和前面的DeepWalk,Node2Vec差不多,這里就說下不同的地方。由于Struc2Vec是在一個多層圖中進(jìn)行采樣,游走可能發(fā)生在同一層中,也可能發(fā)生跨層,所以要添加一些跨層處理的邏輯。
def _exec_random_walk(self, graphs, layers_accept,layers_alias, v, walk_length, gamma, stay_prob=0.3):initialLayer = 0layer = initialLayerpath = []path.append(self.idx2node[v])while len(path) < walk_length:r = random.random()if(r < stay_prob): # same layerv = chooseNeighbor(v, graphs, layers_alias,layers_accept, layer)path.append(self.idx2node[v])else: # different layerr = random.random()try:x = math.log(gamma[layer][v] + math.e)p_moveup = (x / (x + 1))except:print(layer, v)raise ValueError()if(r > p_moveup):if(layer > initialLayer):layer = layer - 1else:if((layer + 1) in graphs and v in graphs[layer + 1]):layer = layer + 1return pathStruc2Vec 應(yīng)用
Struc2Vec應(yīng)用于無權(quán)無向圖(帶權(quán)圖的權(quán)重不會用到,有向圖會當(dāng)成無向圖處理),主要關(guān)注的是圖中頂點(diǎn)的空間結(jié)構(gòu)相似性,這里我們采用論文中使用的一個數(shù)據(jù)集。該數(shù)據(jù)集是一個機(jī)場流量的數(shù)據(jù)集,頂點(diǎn)表示機(jī)場,邊表示兩個機(jī)場之間存在航班。機(jī)場會被打上活躍等級的標(biāo)簽。
這里我們用基于空間結(jié)構(gòu)相似的Struc2Vec和基于近鄰相似的Node2Vec做一個對比實(shí)驗(yàn)。
本例中的訓(xùn)練,評測和可視化的完整代碼在下面的git倉庫中,
https://github.com/shenweichen/GraphEmbedding
分類
-
Struc2Vec結(jié)果 micro-F1: 0.7143, macro-F1: 0.7357
-
Node2Vec結(jié)果 micro-F1: 0.3571, macro-F1: 0.3445
差距還是蠻大的,說明Struc2Vec確實(shí)能夠更好的捕獲空間結(jié)構(gòu)性。
可視化
-
Struc2Vec結(jié)果
-
Node2Vec結(jié)果
參考資料:
1. Perozzi B, Al-Rfou R, Skiena S. Deepwalk: Online learning of social representations[C]//Proceedings of the 20th ACM SIGKDD international conference on Knowledge discovery and data mining. ACM, 2014: 701-710.
http://www.perozzi.net/publications/14_kdd_deepwalk.pdf
2. Graph Neural Network Review
https://zhuanlan.zhihu.com/p/43972372
3. Tang J, Qu M, Wang M, et al. Line: Large-scale information network embedding[C]//Proceedings of the 24th International Conference on World Wide Web. International World Wide Web Conferences Steering Committee, 2015: 1067-1077.
4. Grover A, Leskovec J. node2vec: Scalable Feature Learning for Networks[C]// Acm Sigkdd International Conference on Knowledge Discovery & Data Mining. 2016.
https://www.kdd.org/kdd2016/papers/files/rfp0218-groverA.pdf
5. Wang D, Cui P, Zhu W. Structural deep network embedding[C]//Proceedings of the 22nd ACM SIGKDD international conference on Knowledge discovery and data mining. ACM, 2016: 1225-1234.
https://www.kdd.org/kdd2016/papers/files/rfp0191-wangAemb.pdf
6. struc2vec: Learning Node Representations from Structural Identity
https://arxiv.org/pdf/1704.03165.pdf
總結(jié)
以上是生活随笔為你收集整理的一文直击Graph Embedding图表示学习的原理及应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu16.04 安装docker
- 下一篇: oCPC:计算广告出价策略