圖學習【參考資料2】-知識補充與node2vec代碼注解( 二 )

# 可增加輪次提高精度--epoch# 當前參數精度大概在95%左右!python my_deepwalk.py --use_my_random_walk --epoch 35 #35 用自己實現的random walk訓練DeepWalk模型,可在 ./tmp/deepwalk/walks/ 中查看構造的節點路徑[INFO] 2022-11-11 14:28:28,099 [my_deepwalk.py:250]: Step 1200 DeepWalk Loss: 0.1981060.242671 s/step.[INFO] 2022-11-11 14:28:30,539 [my_deepwalk.py:250]: Step 1210 DeepWalk Loss: 0.1871830.309996 s/step.[INFO] 2022-11-11 14:28:33,171 [my_deepwalk.py:250]: Step 1220 DeepWalk Loss: 0.1895330.244672 s/step.[INFO] 2022-11-11 14:28:35,537 [my_deepwalk.py:250]: Step 1230 DeepWalk Loss: 0.2022930.232859 s/step.[INFO] 2022-11-11 14:28:37,920 [my_deepwalk.py:250]: Step 1240 DeepWalk Loss: 0.1893660.244727 s/step.[INFO] 2022-11-11 14:28:40,450 [my_deepwalk.py:250]: Step 1250 DeepWalk Loss: 0.1886010.254400 s/step.[INFO] 2022-11-11 14:28:42,875 [my_deepwalk.py:250]: Step 1260 DeepWalk Loss: 0.1913430.247985 s/step.[INFO] 2022-11-11 14:28:45,286 [my_deepwalk.py:250]: Step 1270 DeepWalk Loss: 0.1865490.255688 s/step.[INFO] 2022-11-11 14:28:47,653 [my_deepwalk.py:250]: Step 1280 DeepWalk Loss: 0.1886380.240493 s/step.[INFO] 2022-11-11 14:29:45,898 [link_predict.py:199]: Step 180 Train Loss: 0.398023 Train AUC: 0.960870[INFO] 2022-11-11 14:29:46,023 [link_predict.py:223]:Step 180 Test Loss: 0.399052 Test AUC: 0.960234[INFO] 2022-11-11 14:29:48,816 [link_predict.py:199]: Step 190 Train Loss: 0.396805 Train AUC: 0.960916[INFO] 2022-11-11 14:29:48,951 [link_predict.py:223]:Step 190 Test Loss: 0.397910 Test AUC: 0.960275[INFO] 2022-11-11 14:29:51,783 [link_predict.py:199]: Step 200 Train Loss: 0.396290 Train AUC: 0.960936[INFO] 2022-11-11 14:29:51,913 [link_predict.py:223]:Step 200 Test Loss: 0.397469 Test AUC: 0.960292 2.0 SkipGram模型訓練NOTE:在得到節點路徑后,node2vec會使用SkipGram模型學習節點表示,給定中心節點,預測局部路徑中還有哪些節點 。模型中用了negative sampling來降低計算量 。

圖學習【參考資料2】-知識補充與node2vec代碼注解

文章插圖
參考 PGL/examples/node2vec/node2vec.py 中的 node2vec_model 函數
2.1 SkipGram模型實現代碼參考--理解
  1. 這部分的話,官方代碼已經給的很清晰了,這里主要是做一些解釋補充--大都可以跟上邊算法公式對應著看
  2. 這里采用組合損失--組合損失計算時,要注意在不必要的參數創建后,記得關閉梯度記錄--否則會對他求梯度,這樣不太好:如:ones_label,他只是一個中間量,用于存放結果的 , 不需要對他求梯度,因為不需要優化它
  3. 還有一點,靜態圖下 , 盡量使用layers下的運算方法,避免出現超出計算圖的一些邏輯循環操作這一部分沒什么好說的,大家理解就好--多看看源碼哦!
import paddle.fluid.layers as ldef userdef_loss(embed_src, weight_pos, weight_negs):"""輸入:embed_src- 中心節點向量 list (batch_size, 1, embed_size)weight_pos- 標簽節點向量 list (batch_size, 1, embed_size)weight_negs - 負樣本節點向量 list (batch_size, neg_num, embed_size)輸出:loss - 正負樣本的交叉熵 float"""################################### 請在這里實現SkipGram的loss計算過程### 負采樣計算部分——Multi Sigmoids# 分別計算正樣本和負樣本的 logits(概率)pos_logits = l.matmul(embed_src, weight_pos, transpose_y=True)# [batch_size, 1, 1] -- matmul:矩陣相乘neg_logits = l.matmul(embed_src, weight_negs, transpose_y=True)# [batch_size, 1, neg_num]# 設置正樣本標簽,并計算正樣本lossones_label = pos_logits * 0. + 1.ones_label.stop_gradient = True# 關閉梯度記錄pos_loss = l.sigmoid_cross_entropy_with_logits(pos_logits, ones_label)# 交叉熵計算==對應公式2# 設置負樣本標簽,并計算負樣本losszeros_label = neg_logits * 0.zeros_label.stop_gradient = Trueneg_loss = l.sigmoid_cross_entropy_with_logits(neg_logits, zeros_label)# 交叉熵計算==對應公式3# 總的Loss計算為正樣本與負樣本loss之和loss = (l.reduce_mean(pos_loss) + l.reduce_mean(neg_loss)) / 2# 得到總的loss##################################return lossNOTE:Node2Vec會根據與上個節點的距離按不同概率采樣得到當前節點的下一個節點 。<img src="http://img.zhejianglong.com/231019/2254304628-4.jpg" width="85%" height="85%" /><br>參考; PGL/pgl/graph_kernel.pyx 中用Cython語言實現了節點采樣函數node2vec_sample3.1 Node2Vec采樣算法轉換代碼注解
  1. 這部分代碼,用于隨機游走后得到的路徑,然后對這些路徑進行吸收學習,訓練圖結構
import numpy as np# 隨機節點的獲取def node2vec_sample(succ, prev_succ, prev_node, p, q):"""輸入:succ - 當前節點的下一個相鄰節點id列表 list (num_neighbors,)prev_succ - 前一個節點的下一個相鄰節點id列表 list (num_neighbors,)prev_node - 前一個節點id intp - 控制回到上一節點的概率 floatq - 控制偏向DFS還是BFS float輸出:下一個節點id int"""################################### 請在此實現node2vec的節點采樣函數# 節點參數信息succ_len = len(succ)# 獲取相鄰節點id列表節點長度(相對當前)prev_succ_len = len(prev_succ)# 獲取相鄰節點id列表節點長度(相對前一個節點)prev_succ_set = np.asarray([])# 前一節點的相鄰節點id列表for i in range(prev_succ_len):# 遍歷得到前一節點的相鄰節點id列表的新list——prev_succ_set,用于后邊概率的討論# 將前一節點list,依次押入新的list中prev_succ_set = np.append(prev_succ_set,prev_succ[i])# ? prev_succ_set.insert(prev_succ[i])# 概率參數信息probs = []# 保存每一個待前往的概率prob = 0# 記錄當前討論的節點概率prob_sum = 0.# 所有待前往的節點的概率之和# 遍歷當前節點的相鄰節點for i in range(succ_len):# 遍歷每一個當前節點前往的概率if succ[i] == prev_node:# case 1 : 采樣節點與前一節點一致,那么概率為--1/q(原地)prob = 1. / p# case 2 完整的應該是: np.where(prev_succ_set==succ[i]) and np.where(succ==succ[i])# 但是因為succ本身就是采樣集,所以np.where(succ==succ[i])總成立,故而忽略,不考慮elif np.where(prev_succ_set==succ[i]):# case 2: 采樣節點在前一節點list內,那么概率為--1?cpython中的代碼: prev_succ_set.find(succ[i]) != prev_succ_set.end()prob = 1.elif np.where(prev_succ_set!=succ[i]):# case 3: 采樣節點不在前一節點list內 , 那么概率為--1/qprob = 1. / qelse:prob = 0.# case 4 : otherprobs.append(prob)# 將待前往的每一個節點的概率押入保存prob_sum += prob# 計算所有節點的概率之和RAND_MAX = 65535# 這是一個隨機數的最值,用于計算隨機值的--根據C/C++標準,最小在30000+,這里取2^16次方rand_num = float(np.random.randint(0, RAND_MAX+1)) / RAND_MAX * prob_sum# 計算一個隨機概率:0~prob_sum. ?cpython中的代碼: float(rand())/RAND_MAX * prob_sumsampled_succ = 0.# 當前節點的相鄰節點中確定的采樣點# rand_num => 是0~prob_num的一個值,表示我們的截取概率閾值--即當遍歷了n個節點時 , 若已遍歷的節點的概率之和已經超過了rand_num# 我們取剛好滿足已遍歷的節點的概率之和已經超過了rand_num的最近一個節點作為我們的采樣節點# 比如: 遍歷到第5個節點時,權重概率和大于等于rand_num,此時第5個節點就是對應的采樣的節點了# 為了方便實現:這里利用循環遞減--判斷條件就變成了————當rand_num減到<=0時 , 開始采樣節點for i in range(succ_len):# 遍歷當前節點的所有相鄰節點rand_num -= probs[i]# 利用rand_num這個隨機獲得的概率值作為依據,進行一個循環概率檢驗if rand_num <= 0:# 當遇到第一次使得rand_num減到<=0后,說明到這個節點為止, 遍歷應該終止了,此時的節點即未所求的節點,【停止檢驗條件】sampled_succ = succ[i]# 并把當前節點作為確定的節點return sampled_succ# 返回待采樣的節點--節點一定在succ中

推薦閱讀