初識圖學習
- 一、簡單的圖基礎
- 什么是圖?
- 生活中的圖
- 圖的分類
- 同構圖、異構圖舉例
- 圖的度和鄰居
- 圖的表示
- 鄰接矩陣
- 鄰接表
- 邊集
- 圖的特征
- 二、圖學習初印象
- 圖學習的應用
- 節點級別任務
- 金融詐騙檢測
- 自動駕駛
- 邊級別任務
- 推薦系統
- 圖級別任務
- 氣味識別
- 圖學習演算法分類
- 圖游走類演算法
- 圖神經網路演算法
- 三、PGL 圖學習庫初體驗
- 1. 環境安裝
- 2. 使用 PGL 來創建一張圖
- 3. 定義圖模型
- 4. 模型定義
- 5. 訓練前準備
- 6. 開始訓練
- 7. 模型測驗
一、簡單的圖基礎
在上一篇文章中,我整理了資料結構與演算法中的圖,講解了與圖有關的基本概念,
這里再做一個小小的回顧,
什么是圖?

七橋問題的定義是:一個步行者怎樣才能不重復、不遺漏地一次走完七座橋,最后回到出發點,
當年,大數學家歐拉在解答七橋問題的同時,也開創了數學的一個新分支——圖論,
可以毫不夸張地說,七橋圖是我們真正意義上的第一張圖,對七橋圖上的各個地點和橋做一個抽象,我們可以將七橋圖抽象為一個由點和邊構成的圖,
生活中的圖
事實上,圖是一種統一描述復雜事物的語言,在我們的實際生活中,存在著許許多多的復雜事物,而這些事物都可以抽象成圖,

我們生活在一個巨大的社交網路里面,這個社交網路里有著許許多多的人,從而抽象為了圖中的點;而人與人之間的各種聯系,包括父母關系、朋友關系、以及其他各種復雜的聯系,則構成了圖中的邊,
又比如,我們每天都在網上沖浪,而網頁與網頁之間存在著超鏈接關系,這也構成了一張圖,

我們經常會在淘寶等購物APP上買東西,在界面上經常會有各種好物推薦,而其實,這個推薦系統本身也是一張圖,在這張圖里面,用戶和商品都是圖中的節點,而用戶與商品的點擊、瀏覽、購買等行為則構成了圖中的邊,
在化學這門學科上,絕大多數化學分子就是由單個或多個原子組成的,原子就是節點,而原子之間的相互作用力,也就是化學鍵,它構成了圖中的邊,
圖的分類

- 根據圖的節點間是否有方向,可將圖分為無向圖與有向圖
- 根據圖的邊是否有權重,可以將圖分為無權圖和有權圖
- 根據圖的邊和點是否具有多種型別,可以將圖分為同構圖和異構圖
同構圖、異構圖舉例

圖的度和鄰居

- 度是圖上一節點,其邊的條數
- 鄰居指的是圖上一節點的相鄰節點
對于上面這兩張圖來說:
- 這張無向圖有4個節點,每條邊都是雙向的,所以有8條邊;
- 拿節點4舉例,其有3條邊,所以度為3;
- 3個節點與節點4相連,因此它有3個鄰居節點,
- 這張有向圖有4個節點,4條邊
- 拿節點4舉例,它有3條邊,所以度為3,根據箭頭的指向,可以分為出度和入度,其中入度為1,出度為2
- 有3個節點與節點4相連,其中,指向節點4的節點稱為前繼節點,反之則稱為后繼節點
圖的表示
鄰接矩陣
鄰接矩陣是用0和1表示節點間關系的矩陣

可以看出,無向圖的臨界矩陣就是對稱矩陣
鄰接表
鄰接表其實就是直接記錄著每個節點的鄰居資訊

邊集

圖的特征
對于一張圖來說,每個節點、每條邊可能都有各自的特征

二、圖學習初印象
圖學習(Graph Learning)是深度學習中的一個子領域,強調處理的資料物件為圖,
與一般深度學習的區別是圖學習能夠方便地處理不規則資料(樹、圖),同時也可以處理規則資料(如影像),

圖學習的應用


可以將基于圖能做的任務進行一個分類,對于一張圖:
- 我們希望預測這個點的類別或者其他的特性,那么這就是一個節點級別的任務;
- 又比如我們希望預測這條邊的權值,或者預測這條邊是否存在,等等,那么這就是一個邊級別的任務;
- 再比如,我們想要預測整張圖的一個類別,或者想比較兩張圖之間的相似性等等,這就是一個圖級別的任務了,
節點級別任務
金融詐騙檢測

在建圖的時候,它的節點是用戶和商家,同時還包含了各自共有的資訊作為節點,
其中,每個用戶或者商家都有著各自的特征,也具備著某些相同的特征,同時也有著與他人的互動,傳統方法通常是直接利用用戶和商家的特征來訓練一個分類網路,而沒有利用節點與節點之間的互動,因此使用圖學習,我們可以同時學習圖結構以及節點特征,更好的進行分類,從而更好地找到金融詐騙分子,
自動駕駛


點云是通過激光掃描等來獲得的點資料,而3D點云這個結構可以建模為圖結構,
在點云中構建好圖之后,將圖結構和圖特征經過這個叫 Point-GNN 的模型,從而預測出點云中每個點所對應的 object,也就是目標物件,同時要預測出對應目標的所在三維邊界,也就是 bounding box,
由于預測物件是每個點,因此這是一個節點級別的任務,
邊級別任務
推薦系統

推薦系統可以表示成圖
比如,我們想要向用戶推薦新聞,以左邊這個圖為例,我們已經知道了用戶 ABC的歷史點擊行為,那么接下來,想要預測用戶B會不會點擊某條廣告,其實就相當于預測這條邊是否存在,因此這就是一個邊預測的任務,
具體實作的時候,會把用戶行為圖關系通過圖表示學習后,得到用戶、商品或內容的向量表示;得到對應這些節點的 Embeddings 之后, 就可以利用這些 embeddings 來做各種的推薦任務,
圖級別任務
氣味識別

氣味識別其實是一個非常典型的圖識別任務了,而且對于實際生活也很有幫助,
假設這樣的一個場景,我們有兩種花,蒙住眼睛,只能用鼻子來分辨花,如果我們光靠鼻子搞不定,那么這時候就可以派圖學習上場了,

圖學習演算法分類

這里分為了三大類:游走類演算法、圖神經網路演算法、以及知識圖譜嵌入演算法,
因為知識圖譜也是一種典型的圖,因此把它也加入到了這個分類里面,
其中,圖神經網路演算法還可以進行更加具體的劃分,比如分為卷積網路和遞回網路,等等,
圖游走類演算法

圖游走類演算法就有點像我們去旅游一樣,任意選擇一個出發點,然后隨機地選擇下一個目的地,不斷地走,直到我們累了,
通過不斷地旅游,我們得到了多個序列,而游走類演算法就是在得到這些序列之后,對它們應用圖表示學習,再進行接下來的其他操作,
圖神經網路演算法

圖神經網路演算法相對來說則復雜一點,它的一種實作方式是訊息傳遞,
訊息傳遞,其實質就是把當前節點的鄰居發送到自身,將這些資訊聚合后,再利用這些資訊更新自身的表示,
三、PGL 圖學習庫初體驗
-
Github 鏈接:https://github.com/PaddlePaddle/PGL
-
API檔案: https://pgl.readthedocs.io/en/latest/
1. 環境安裝
# 安裝 PaddlePaddle 框架
!pip install paddlepaddle==1.8.5
# 安裝 PGL 學習庫
!pip install pgl
2. 使用 PGL 來創建一張圖
假設我們有下面的這一張圖,其中包含了10個節點以及14條邊,

我們的目的是,訓練一個圖模型,使得該圖模型可以區分圖上的黃色節點和綠色節點,我們可以使用以下代碼來構圖,
import pgl
from pgl import graph # 匯入 PGL 中的圖模塊
import paddle.fluid as fluid # 匯入飛槳框架
import numpy as np
def build_graph():
# 定義圖中的節點數目,我們使用數字來表示圖中的每個節點
num_nodes = 10
# 定義圖中的邊集
edge_list = [(2, 0), (2, 1), (3, 1),(4, 0), (5, 0),
(6, 0), (6, 4), (6, 5), (7, 0), (7, 1),
(7, 2), (7, 3), (8, 0), (9, 7)]
# 隨機初始化節點特征,特征維度為 d
d = 16
feature = np.random.randn(num_nodes, d).astype("float32")
# 隨機地為每條邊賦值一個權重
edge_feature = np.random.randn(len(edge_list), 1).astype("float32")
# 創建圖物件,最多四個輸入
g = graph.Graph(num_nodes = num_nodes,
edges = edge_list,
node_feat = {'feature':feature},
edge_feat ={'edge_feature': edge_feature})
return g
g = build_graph()
圖創建完畢后,我們可以列印出圖中的一些資訊,
print('圖中共計 %d 個節點' % g.num_nodes)
print('圖中共計 %d 條邊' % g.num_edges)
圖中共計 10 個節點
圖中共計 14 條邊
3. 定義圖模型
我們可以定義下面的一個簡單圖模型層,這里的結構是添加了邊權重資訊的類 GCN 層,
# 定義一個同時傳遞節點特征和邊權重的簡單模型層,
def model_layer(gw, nfeat, efeat, hidden_size, name, activation):
'''
gw: GraphWrapper 圖資料容器,用于在定義模型的時候使用,后續訓練時再feed入真實資料
nfeat: 節點特征
efeat: 邊權重
hidden_size: 模型隱藏層維度
activation: 使用的激活函式
'''
# 定義 send 函式
def send_func(src_feat, dst_feat, edge_feat):
# 將源節點的節點特征和邊權重共同作為訊息發送
return src_feat['h'] * edge_feat['e']
# 定義 recv 函式
def recv_func(feat):
# 目標節點接收源節點訊息,采用 sum 的聚合方式
return fluid.layers.sequence_pool(feat, pool_type='sum')
# 觸發訊息傳遞機制
msg = gw.send(send_func, nfeat_list=[('h', nfeat)], efeat_list=[('e', efeat)])
output = gw.recv(msg, recv_func)
output = fluid.layers.fc(output,
size=hidden_size,
bias_attr=False,
act=activation,
name=name)
return output
4. 模型定義
這里我們簡單的把上述定義好的模型層堆疊兩層,作為我們的最終模型,
class Model(object):
def __init__(self, graph):
"""
graph: 我們前面創建好的圖
"""
# 創建 GraphWrapper 圖資料容器,用于在定義模型的時候使用,后續訓練時再feed入真實資料
self.gw = pgl.graph_wrapper.GraphWrapper(name='graph',
node_feat=graph.node_feat_info(),
edge_feat=graph.edge_feat_info())
# 作用同 GraphWrapper,此處用作節點標簽的容器
self.node_label = fluid.layers.data("node_label", shape=[None, 1],
dtype="float32", append_batch_size=False)
def build_model(self):
# 定義兩層model_layer
output = model_layer(self.gw,
self.gw.node_feat['feature'],
self.gw.edge_feat['edge_feature'],
hidden_size=8,
name='layer_1',
activation='relu')
output = model_layer(self.gw,
output,
self.gw.edge_feat['edge_feature'],
hidden_size=1,
name='layer_2',
activation=None)
# 對于二分類任務,可以使用以下 API 計算損失
loss = fluid.layers.sigmoid_cross_entropy_with_logits(x=output,
label=self.node_label)
# 計算平均損失
loss = fluid.layers.mean(loss)
# 計算準確率
prob = fluid.layers.sigmoid(output)
pred = prob > 0.5
pred = fluid.layers.cast(prob > 0.5, dtype="float32")
correct = fluid.layers.equal(pred, self.node_label)
correct = fluid.layers.cast(correct, dtype="float32")
acc = fluid.layers.reduce_mean(correct)
return loss, acc
5. 訓練前準備
# 是否在 GPU 或 CPU 環境運行
use_cuda = True
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 定義程式,也就是我們的 Program
startup_program = fluid.Program() # 用于初始化模型引數
train_program = fluid.Program() # 訓練時使用的主程式,包含前向計算和反向梯度計算
test_program = fluid.Program() # 測驗時使用的程式,只包含前向計算
with fluid.program_guard(train_program, startup_program):
model = Model(g)
# 創建模型和計算 Loss
loss, acc = model.build_model()
# 選擇Adam優化器,學習率設定為0.01
adam = fluid.optimizer.Adam(learning_rate=0.01)
adam.minimize(loss) # 計算梯度和執行梯度反向傳播程序
# 復制構造 test_program,與 train_program的區別在于不需要梯度計算和反向程序,
test_program = train_program.clone(for_test=True)
# 定義一個在 place(CPU)上的Executor來執行program
exe = fluid.Executor(place)
# 引數初始化
exe.run(startup_program)
# 獲取真實圖資料
feed_dict = model.gw.to_feed(g)
# 獲取真實標簽資料
# 由于我們是位元組點分類任務,因此可以簡單的用0、1表示節點類別,其中,黃色點標簽為0,綠色點標簽為1,
y = [0,1,1,1,0,0,0,1,0,1]
label = np.array(y, dtype="float32")
label = np.expand_dims(label, -1)
feed_dict['node_label'] = label
列印輸入的資料:
print(feed_dict)
{'graph/num_edges': array([14]), 'graph/edges_src': array([2, 4, 5, 6, 7, 8, 2, 3, 7, 7, 7, 6, 6, 9]), 'graph/edges_dst': array([0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 7]), 'graph/num_nodes': array([10]), 'graph/uniq_dst': array([0, 1, 2, 3, 4, 5, 7]), 'graph/uniq_dst_count': array([ 0, 6, 9, 10, 11, 12, 13, 14], dtype=int32), 'graph/indegree': array([6, 3, 1, 1, 1, 1, 0, 1, 0, 0]), 'graph/graph_lod': array([ 0, 10], dtype=int32), 'graph/num_graph': array([1]), 'graph/node_feat/feature': array([[ 0.58795387, 1.2630547 , 0.15662637, 1.3688753 , 1.1675597 ,
0.04462763, 0.18476363, -0.25253114, -0.21759783, 0.8734884 ,
0.13879131, 1.0323961 , 0.03691616, 0.10940287, -1.792123 ,
0.04989915],
[-0.1444743 , -1.2530277 , 0.15299311, -1.6170429 , 0.64711034,
-2.7449965 , -1.3882436 , 0.51445365, -1.2793802 , -0.25707176,
-1.0026166 , 0.5339329 , -0.2335032 , -1.8645428 , 1.8710616 ,
1.0047083 ],
[-2.0020528 , 0.21761087, 1.333866 , -1.306326 , -0.01795861,
1.0073777 , -0.253056 , -0.2271674 , 2.1695845 , -0.96707493,
-0.76226276, 0.16730066, 0.54326695, 1.3340389 , 0.28255144,
0.10114926],
[-1.9666518 , -1.9607522 , -0.43653187, 1.4989506 , -0.3806762 ,
0.9109142 , 0.6621056 , 0.27713525, 0.3436323 , 0.6689543 ,
-0.4054542 , 0.7418638 , 0.09361912, -0.51458985, 0.56043166,
0.6162147 ],
[-1.1269506 , -1.1637336 , -1.3745695 , -0.17090996, -0.52521807,
1.1339206 , 0.36089063, 1.1876916 , -0.4320988 , 0.18111283,
-0.9603039 , -1.542511 , -0.5449157 , 0.0152637 , 1.2007928 ,
-0.74825037],
[-1.3099471 , 1.2409917 , 0.51250875, -1.0624666 , -0.02053989,
2.0178044 , -0.59536004, 0.3934452 , 0.98477745, -0.1810182 ,
0.65636057, 2.3335536 , -0.42765683, 0.09425509, -0.4363421 ,
-1.1370741 ],
[-0.48036537, -0.44820678, -1.1542974 , -0.6099145 , -1.2221897 ,
1.9440795 , -1.2649409 , -0.301374 , -0.42991576, -0.6353119 ,
-0.32859164, 1.0071183 , 0.5237574 , -1.0096937 , -0.544537 ,
-0.99841434],
[-1.8510556 , 0.8686978 , 1.4590447 , -1.9817588 , -1.2099521 ,
0.40852544, 0.52567303, 0.89494723, 0.03127011, -0.4033486 ,
0.60143316, 0.19032563, 0.6081489 , -0.297795 , 0.9398178 ,
-0.32976308],
[-0.7306164 , -0.1550996 , 1.2815462 , -0.20480168, 0.21323843,
-0.80160755, -1.155071 , 1.8388264 , 0.5788635 , 0.01409068,
0.57363164, -1.3481847 , 0.33940238, 0.9301134 , 0.9973273 ,
0.3144489 ],
[ 0.03115364, 0.4957952 , -1.2972994 , -0.42183682, 1.939209 ,
-0.6846291 , 0.4093842 , 0.7665659 , 1.5198345 , -0.94440025,
0.57780015, 1.2090318 , 0.33736354, 0.34547713, -1.1328425 ,
-0.96179086]], dtype=float32), 'graph/edge_feat/edge_feature': array([[ 0.49790657],
[-0.44967642],
[ 0.92381525],
[-0.0052013 ],
[-0.22613016],
[-0.927129 ],
[ 0.8273991 ],
[-0.5968252 ],
[-0.37763336],
[-0.39886096],
[ 0.55989164],
[ 0.12900583],
[-1.9236063 ],
[ 0.15244085]], dtype=float32), 'node_label': array([[0.],
[1.],
[1.],
[1.],
[0.],
[0.],
[0.],
[1.],
[0.],
[1.]], dtype=float32)}
6. 開始訓練
for epoch in range(30):
train_loss = exe.run(train_program,
feed=feed_dict, # feed入真實訓練資料
fetch_list=[loss], # fetch出需要的計算結果
return_numpy=True)[0]
print('Epoch %d | Loss: %f' % (epoch, train_loss))
Epoch 0 | Loss: 0.706203
Epoch 1 | Loss: 0.689478
Epoch 2 | Loss: 0.677695
Epoch 3 | Loss: 0.667933
Epoch 4 | Loss: 0.659103
Epoch 5 | Loss: 0.650838
Epoch 6 | Loss: 0.642839
Epoch 7 | Loss: 0.635119
Epoch 8 | Loss: 0.627715
Epoch 9 | Loss: 0.620718
Epoch 10 | Loss: 0.614087
Epoch 11 | Loss: 0.607863
Epoch 12 | Loss: 0.602069
Epoch 13 | Loss: 0.596725
Epoch 14 | Loss: 0.591840
Epoch 15 | Loss: 0.587411
Epoch 16 | Loss: 0.583429
Epoch 17 | Loss: 0.579876
Epoch 18 | Loss: 0.576725
Epoch 19 | Loss: 0.573948
Epoch 20 | Loss: 0.571514
Epoch 21 | Loss: 0.569391
Epoch 22 | Loss: 0.567547
Epoch 23 | Loss: 0.565950
Epoch 24 | Loss: 0.564570
Epoch 25 | Loss: 0.563379
Epoch 26 | Loss: 0.562358
Epoch 27 | Loss: 0.561476
Epoch 28 | Loss: 0.560711
Epoch 29 | Loss: 0.560047
7. 模型測驗
test_acc = exe.run(test_program, feed=feed_dict, fetch_list=[acc], return_numpy=True)[0]
print("Test Acc: %f" % test_acc)
Test Acc: 0.700000
result = exe.run(test_program, feed=feed_dict, fetch_list=[acc], return_numpy=True)
print(result)
[array([0.70000005], dtype=float32)]
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/228007.html
標籤:AI
