1 圖學習任務
我們簡單回顧下,上一節我們介紹了,圖的機器學習任務主要是以下三種:
- Node Level:節點級別
- Link Level:邊級別
-
Graph Level:圖級別å
並且三部分難度依次是由淺入深的
2 傳統ML流程
- 定義和設計節點/邊/圖的特徵
-
對所有訓練數據構造特徵
- 訓練ML模型
(1)隨機森林
(2)支持向量機
(3)神經網絡等 - 應用模型
給定一個新的節點、邊、圖,然後獲取特徵進行預測
我們總結下 基於Graph的機器學習相關概念和流程,首先明確下目標
目標:對一些對象集合進行預測,比如是分類或者回歸任務
特徵設計:
- 特徵:
d-dimensional
向量 - 對象:Nodes,edges,或者是graps
-
目標函數:結合具體任務設計目標函數,如下圖所示給定一個圖G(V,E),其中V代表節點集合,E代表邊集合,然後學習節點到向量空間R的映射函數,也就是我們要學習權重參數W
爲了方便,我們下面的例子是基於無向圖(undirected grpah)進行解釋的。
3 節點級別的相關任務
基於圖中帶有標籤的節點訓練模型,然後預測未標註節點的標籤,
在這裏我們主要闡述下Node的四種特徵:
- Node degree:節點的度
- Node centrality:節點的中度
- Clustering coefficient:相似性
-
Graphlets:圖元
節點的度
- kv代表是節點v與鄰居節點相連邊的個數
- 所有鄰居節點都是相等的
如下圖所示,A的度爲1,B的度爲2,C的度爲3,D的度爲4
節點的中心度 Node Centrality
- 節點的度只計算了相連節點的個數,但是沒有評估節點的重要性
- 節點的中心度考慮了節點在圖中的重要程度
- 節點的中心度有很多種計算方式:
(1) Engienvector centrality:特徵向量中心性
(2) Betweenness centrality:中介中心性
(3) Closeness centrality:緊密中心性
(4) 其他方式
Eigenvector centrality:特徵向量中心性
- 如果一個節點的鄰居節點們越重要,那麼該節點就越重要
- 我們將節點𝑣的中心性建模爲相鄰節點的中心性之和:
其中爲一個正常數。 - 我們注意到上面的等式是通過遞歸的方式來計算度度中心性的,所有我們應該怎麼求解
其中爲正常數,爲鄰接矩陣,如果 ,那麼
- 我們可以看到度中心性是一個特徵向量(eigenvector),根據非負矩陣定理(Perron-Frobenius Theorem)我們可以知道是一個正值並且是唯一的,特徵向量當做度中心性。
通常來說,有許多不同的特徵值 能使得一個特徵方程有非零解存在。然而,考慮到特徵向量中的所有項均爲非負值,根據佩倫-弗羅貝尼烏斯定理,只有特徵值最大時才能測量出想要的中心性。然後通過計算網絡中的節點其特徵向量的相關分量便能得出其對應的中心性的分數。
特徵向量的定義只有一個公因子,因此各節點中心性的比例可以很好確定。爲了確定一個絕對分數,必須將其中一個特徵值標準化,例如所有節點評分之和爲1或者節點數 n。冪次迭代是許多特徵值算法中的一種,該算法可以用來尋找這種主導特徵向量。此外,以上方法可以推廣,使得矩陣A中每個元素可以是表示連接強度的實數,例如隨機矩陣。---特徵向量中心性wiki
這裏其實涉及到比較多的線性代數的理論以及矩陣分析的算法,我特意查了一些文章幫大家去回顧和理解下這裏涉及到的知識:
(1) 線性代數之——特徵值和特徵向量
(2)非負矩陣之Perron-Frobenius定理
(3)非負矩陣
(4)乾貨 | 萬字長文帶你複習線性代數!
我們這裏再通過文章誰是社會網絡中最重要的人?解釋特徵向量中心性:
特徵向量中心性的基本思想是,一個節點的中心性是相鄰節點中心性的函數。也就是說,與你連接的人越重要,你也就越重要。
特徵向量中心性和點度中心性不同,一個點度中心性高即擁有很多連接的節點特徵向量中心性不一定高,因爲所有的連接者有可能特徵向量中心性很低。同理,特徵向量中心性高並不意味着它的點度中心性高,它擁有很少但很重要的連接者也可以擁有高特徵向量中心性。
考慮下面的圖,以及相應的5x5的鄰接矩陣(Adjacency Matrix),A。
鄰接矩陣的含義是,如果兩個節點沒有直接連接,記爲0,否則記爲1。
現在考慮x,一個5x1的向量,向量的值對應圖中的每個點。在這種情況下,我們計算的是每個點的點度中心性(degree centrality),即以點的連接數來衡量中心性的高低。
矩陣A乘以這個向量的結果是一個5x1的向量:
結果向量的第一個元素是用矩陣A的第一行去“獲取”每一個與第一個點有連接的點的值(連接數,點度中心性),也就是第2個、第3個和第4個點的值,然後將它們加起來。
換句話說,鄰接矩陣做的事情是將相鄰節點的求和值重新分配給每個點。這樣做的結果就是“擴散了”點度中心性。你的朋友的朋友越多,你的特徵向量中心性就越高。
我們繼續用矩陣A乘以結果向量。如何理解呢?實際上,我們允許這一中心性數值再次沿着圖的邊界“擴散”。我們會觀察到兩個方向上的擴散(點既給予也收穫相鄰節點)。我們猜測,這一過程最後會達到一個平衡,特定點收穫的數量會和它給予相鄰節點的數量取得平衡。既然我們僅僅是累加,數值會越來越大,但我們最終會到達一個點,各個節點在整體中的比例會保持穩定。
現在把所有點的數值構成的向量用更一般的形式表示:
我們認爲,圖中的點存在一個數值集合,對於它,用矩陣A去乘不會改變向量各個數值的相對大小。也就是說,它的數值會變大,但乘以的是同一個因子。用數學符號表示就是:
滿足這一屬性的向量就是矩陣M的特徵向量。特徵向量的元素就是圖中每個點的特徵向量中心性。
特徵向量中心性的計算需要讀者具備矩陣乘法和特徵向量的知識,但不影響這裏讀者對特徵向量中心性思想的理解,不再贅述。
Betweenness centrality:中介中心性
如果一個節點位於很多條其他節點的最短路徑上,那麼改節點比較重要,定義如下:
我們可以看一個下面的例子:
假設我們要計算D的中介中心性:
- 首先,我們計算節點D之外,所有節點對之間的最短路徑有多少條,這裏是1條(在5個節點中選擇兩個節點即節點對的個數)。
- 然後,我們再看所有這些最短路徑中有多少條經過節點D,例如節點A要想找到節點E,必須經過節點D。經過節點D的最短路徑有3條(A-C-D-E,B-D-E,C-D-E)。
- 最後,我們用經過節點D的最短路徑除以所有節點對的最短路徑總數,這個比率就是節點D的中介中心性。節點D的中介中心性是3/1=3。
4 PyTorch Geometric教程
官方文檔:https://pytorch-geometric.readthedocs.io/en/latest/index.html
PyTorch Geometric(PyG)是PyTorch的擴展庫。 它提供了有用的框架來開發圖深度學習模型,包括各種圖神經網絡層和大量基準數據集。
如果我們暫時不瞭解某些概念,例如“ GCNConv”,請不要擔心,之後我們將在以後的文章中介紹這些概念。
這個教程來自: https://colab.research.google.com/drive/1h3-vJGRVloF5zStxL5I0rSy4ZUPNsjy8?usp=sharing#scrollTo=ci-LpZWhRJoI
by Matthias Fey
大家可以參照colab教程直接運行
4.1 PyG安裝
- 查看當前torch和cuda版本
!python -c "import torch; print(torch.__version__)"
輸出:1.6.0
!python -c "import torch; print(torch.version.cuda)"
輸出:10.1
- 安裝gpu版本
# 安裝GPU版本
!pip install --no-index torch-scatter -f https://pytorch-geometric.com/whl/torch-1.7.0+cu101.html
!pip install --no-index torch-sparse -f https://pytorch-geometric.com/whl/torch-1.7.0+cu101.html
!pip install --no-index torch-cluster -f https://pytorch-geometric.com/whl/torch-1.7.0+cu101.html
!pip install --no-index torch-spline-conv -f https://pytorch-geometric.com/whl/torch-1.7.0+cu101.html
!pip install torch-geometric
可能安裝gpu版本比較麻煩,大家可以多參照下官網進行安裝:
https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html
- 安裝cuda版本
!pip install torch-scatter==latest+cpu -f https://pytorch-geometric.com/whl/torch-1.6.0.html
!pip install torch-sparse==latest+cpu -f https://pytorch-geometric.com/whl/torch-1.6.0.html
!pip install torch-cluster==latest+cpu -f https://pytorch-geometric.com/whl/torch-1.6.0.html
!pip install torch-spline-conv==latest+cpu -f https://pytorch-geometric.com/whl/torch-1.6.0.html
!pip install torch-geometric
4.2 PyG數據集
PyTorch Geometric 可以通過 torch_geometric.datasets
快速的獲取默認的數據集:
https://pytorch-geometric.readthedocs.io/en/latest/modules/datasets.html
一共有20+種數據,比較豐富~
# 導入空手道俱樂部數據集
from torch_geometric.datasets import KarateClub
dataset = KarateClub()
print(f'Dataset: {dataset}:')
print('======================')
print(f'Number of graphs: {len(dataset)}')
print(f'Number of features: {dataset.num_features}')
print(f'Number of classes: {dataset.num_classes}')
輸出:
Dataset: KarateClub():
======================
Number of graphs: 1
Number of features: 34
Number of classes: 4
初始化[KarateClub
](https://pytorch-geometric.readthedocs.io/en/latest/modules/datasets.html#torch_geometric.datasets.KarateClub) 數據集之後,我們首先可以檢查其某些屬性。
例如,我們可以看到該數據集恰好有一個Graph,並且該數據集中的每個節點都被分配了** 34維特徵向量(唯一地描述了空手道俱樂部的成員)。
此外,該圖正好包含 4個類別**,代表每個節點所屬的社區。
現在讓我們更詳細地看一下這個Graph:
data = dataset[0] # 獲取graph對象.
print(data)
print('==============================================================')
# 獲取一些graph的統計信息.
print(f'Number of nodes: {data.num_nodes}') # 節點的個數
print(f'Number of edges: {data.num_edges}') # 邊的個屬於
print(f'Average node degree: {data.num_edges / data.num_nodes:.2f}') # 節點的度平均數
print(f'Number of training nodes: {data.train_mask.sum()}')
print(f'Training node label rate: {int(data.train_mask.sum()) / data.num_nodes:.2f}')
print(f'Contains isolated nodes: {data.contains_isolated_nodes()}')
print(f'Contains self-loops: {data.contains_self_loops()}')
print(f'Is undirected: {data.is_undirected()}')
輸出如下:
Data(edge_index=[2, 156], train_mask=[34], x=[34, 34], y=[34])
==============================================================
Number of nodes: 34
Number of edges: 156
Average node degree: 4.59
Number of training nodes: 4
Training node label rate: 0.12
Contains isolated nodes: False
Contains self-loops: False
Is undirected: True
通過上面的信息我們可以發現,這個KarateClub網絡圖一共有34個節點,邊的個數爲1,每個節點度的平均數有4.59,是一個無向圖等等。
4.3 Data對象
PyTorch Geometric中的每個圖形都由單個[Data
](https://pytorch-geometric.readthedocs.io/en/latest/modules/data.html#torch_geometric.data.Data)對象表示,該對象包含所有 描述其圖形表示的信息。
我們可以隨時通過print(data)打印數據對象,以接收有關其屬性及其形狀的簡短介紹:
Data(edge_index=[2, 156], x=[34, 34], y=[34], train_mask=[34])
我們可以看到這個data
對象擁有4個屬性:
(1)“ edge_index”屬性保存有關“圖形連接性”(即)的信息,即每個邊緣的源節點和目標節點索引的元組。
PyG進一步將
(2)節點特徵稱爲x(向34個節點中的每個節點分配了34維度的特徵向量),將
(3)節點標記*稱爲y(每個節點僅分配給一個類別)。
(4)還有一個名爲“ train_mask”的附加屬性,它描述了我們已經知道其社區歸屬的節點。
總體而言,我們只知道4個節點的基本標籤(每個社區一個),任務是推斷其餘節點的社區分配。
數據對象還提供了一些“實用功能”來推斷基礎圖的一些基本屬性。
例如,我們可以輕鬆推斷圖中是否存在孤立的節點(* ie 任何節點都沒有邊),圖是否包含自環(i.e., ),或者圖形是否是無向的( (i.e.*, for each edge there also exists the edge ).
from IPython.display import Javascript # Restrict height of output cell.
display(Javascript('''google.colab.output.setIframeHeight(0, true, {maxHeight: 300})'''))
edge_index = data.edge_index
# 打印節點
print(edge_index.t())
輸出:
tensor([[ 0, 1],
[ 0, 2],
[ 0, 3],
[ 0, 4],
[ 0, 5],
[ 0, 6],
[ 0, 7],
[ 0, 8],
[ 0, 10],
....
[33, 31],
[33, 32]])
通過打印“ edge_index”,我們可以進一步瞭解PyG如何在內部表示圖形連接。
我們可以看到,對於每個邊緣,“ edge_index”都包含兩個節點索引的元組,其中第一個值描述源節點的節點索引,第二個值描述邊緣目標節點的節點索引。
這種表示稱爲** COO格式(座標格式)**,通常用於表示稀疏矩陣。
代替以密集表示形式保存鄰接信息 ,,PyG稀疏地表示圖形,這是指僅保存 中的條目爲非零的座標/值。
最後,我們可以通過將圖形轉換爲networkx
庫格式來進一步可視化圖形,該格式除了圖形操作功能之外,還實現了強大的可視化工具。我們創建一個專門用於展示Graph的函數
%matplotlib inline
import torch
import networkx as nx
import matplotlib.pyplot as plt
# Visualization function for NX graph or PyTorch tensor
def visualize(h, color, epoch=None, loss=None):
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])
if torch.is_tensor(h):
h = h.detach().cpu().numpy()
plt.scatter(h[:, 0], h[:, 1], s=140, c=color, cmap="Set2")
if epoch is not None and loss is not None:
plt.xlabel(f'Epoch: {epoch}, Loss: {loss.item():.4f}', fontsize=16)
else:
nx.draw_networkx(G, pos=nx.spring_layout(G, seed=42), with_labels=False,
node_color=color, cmap="Set2")
plt.show()
from torch_geometric.utils import to_networkx
G = to_networkx(data, to_undirected=True)
visualize(G, color=data.y)
4.4 實戰:基於GCN的Graph節點分類
接下來將通過Pytorch實現一個最基本的GCN網絡用語節點分類,基於帶有標籤的節點數據進行訓練模型,然後預測未帶有標籤的數據
在瞭解了PyG的數據處理之後,是時候實現我們的第一個Graph神經網絡了!我們在這裏將使用最簡單的GNN網絡之一,即GCN層**([Kipf等人(2017)](https://arxiv.org/abs/1609.02907))用於Graph節點分類。
PyG通過[GCNConv
](https://pytorch-geometric.readthedocs.io/en/latest/modules/nn.html#torch_geometric.nn.conv.GCNConv)來實現此層,可以通過傳入 節點要素表示形式“ x”和COO圖形連接性表示形式“ edge_index”。
這樣,我們就可以通過在torch.nn.Module類中定義我們的網絡架構來創建我們的第一個圖形神經網絡:
import torch
from torch.nn import Linear
from torch_geometric.nn import GCNConv
class GCN(torch.nn.Module):
def __init__(self):
super(GCN, self).__init__()
torch.manual_seed(12345)
self.conv1 = GCNConv(dataset.num_features, 4)
self.conv2 = GCNConv(4, 4)
self.conv3 = GCNConv(4, 2)
self.classifier = Linear(2, dataset.num_classes)
def forward(self, x, edge_index):
h = self.conv1(x, edge_index)
h = h.tanh()
h = self.conv2(h, edge_index)
h = h.tanh()
h = self.conv3(h, edge_index)
h = h.tanh() # Final GNN embedding space.
# Apply a final (linear) classifier.
out = self.classifier(h)
return out, h
model = GCN()
print(model)
網絡結構輸出如下:
GCN(
(conv1): GCNConv(34, 4)
(conv2): GCNConv(4, 4)
(conv3): GCNConv(4, 2)
(classifier): Linear(in_features=2, out_features=4, bias=True)
)
在這裏,我們首先在__init__
中初始化所有構建塊,並在“轉發”中定義網絡的計算流程。
我們首先定義並堆疊三層圖卷積層,這對應於在每個節點周圍(距離3個“跳”爲止的所有節點)彙總3跳鄰域信息。
另外,GCNConv
層將節點特徵維數減小爲, i.e., .。每個[GCNConv]層都通過[tanh](https://pytorch.org/docs/stable/generation/torch.nn.Tanh.html?highlight=tanh#torch.nn.Tanh)非線性進行了增強。
之後,我們應用單個線性變換([torch.nn.Linear
](https://pytorch.org/docs/stable/generated/torch.nn.Linear.html?highlight=linear#torch.nn。線性)),用作將我們的節點映射到4個類/社區中的1個的分類器。
我們返回最終分類器的輸出以及GNN生成的最終節點嵌入。
我們繼續通過GCN()
初始化最終模型,並打印我們的模型以產生所有使用過的子模塊的概括。
獲取隱藏層的表示:
model = GCN()
_, h = model(data.x, data.edge_index)
print(f'Embedding shape: {list(h.shape)}')
visualize(h, color=data.y)
輸出:
Embedding shape: [34, 2]
在這裏值得注意的是,即使在訓練我們的模型權重之前,這個初始化的GCN網絡也會產生節點的嵌入,並且這些嵌入與圖的社區結構非常相似。儘管我們的模型的權重是“完全隨機地初始化”的,並且到目前爲止,我們還沒有進行任何訓練,但是相同顏色(社區)的節點已經在嵌入空間中緊密地聚集在一起。得出這樣的結論,即GNN引入了強烈的節點偏差,從而導致在輸入圖中彼此靠近的節點具有相似的嵌入
。
但是,節點表示並不是很完美,我們可以看出來四種類別的節點還是混在一塊了(對應途中是四種顏色),我們可以做得更好嗎?
讓我們看一個示例,該示例如何基於對圖中4四種節點的社區分配(每個社區一個)的知識來訓練我們的網絡參數:
由於模型中的所有內容都是可區分的和參數化的,因此我們可以添加一些標籤,訓練模型並觀察嵌入如何反應。
在這裏,我們使用一種半監督學習程序:我們僅針對每個類訓練一個節點,但是允許使用完整的輸入圖數據。
這個怎麼理解呢,我們其實可以輸出data.y
和data.train_mask
就明白了:
data.y
tensor([0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 1, 0, 0, 0, 2, 2, 1, 0, 2, 0, 2, 0, 2, 2,
3, 3, 2, 2, 3, 2, 2, 3, 2, 2])
data.train_mask
tensor([ True, False, False, False, True, False, False, False, True, False,
False, False, False, False, False, False, False, False, False, False,
False, False, False, False, True, False, False, False, False, False,
False, False, False, False])
大家可以看到相當於我們訓練網絡的時候只針對每一個類別下的token做優化,這樣可以加速網絡的訓練和收斂。
訓練我們的模型與任何其他PyTorch模型非常相似。
除了定義我們的網絡架構之外,我們還定義了損失函數在這裏[[CrossEntropyLoss]](https://pytorch.org/docs/stable/generate/torch.nn.CrossEntropyLoss.html)) 並初始化隨機梯度優化器(此處爲[Adam
](https://pytorch.org/docs/stable/optim.html?highlight=adam#torch.optim.Adam))。
之後,我們執行多輪優化,其中每一輪都包含一個正向和反向傳遞,以計算模型參數w.r.t的梯度。從前向通行證產生的損失。
如果您對PyTorch並不陌生,則該方案應該對您來說很熟悉。
否則,PyTorch文檔會提供[有關如何在PyTorch中訓練神經網絡的很好的介紹](https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#define-a-loss-function-and-optimizer )。
請注意,我們的半監督學習場景是通過以下行實現的:
loss = criterion(out[data.train_mask], data.y[data.train_mask])
在計算所有節點的節點嵌入時,我們“僅利用訓練節點來計算loss” **。
在這裏,這是通過過濾分類器“ out”和真實性標籤“ data.y”的輸出以僅包含“ train_mask”中的節點來實現的。
現在讓我們開始訓練,看看我們的節點嵌入隨時間如何演變(最好是顯式地運行代碼,打印出模型的訓練效果):
import time
from IPython.display import Javascript #畫圖.
display(Javascript('''google.colab.output.setIframeHeight(0, true, {maxHeight: 430})'''))
model = GCN()
criterion = torch.nn.CrossEntropyLoss() # 定義loss
optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # 優化器
def train(data):
optimizer.zero_grad() # 梯度清零.
out, h = model(data.x, data.edge_index) # GCN模型.
loss = criterion(out[data.train_mask], data.y[data.train_mask]) # 計算真實loss.
loss.backward() # 反向求導.
optimizer.step() # 參數更新.
return loss, h
for epoch in range(401):
loss, h = train(data)
# 每10個epochs打印Graph
if epoch % 10 == 0:
visualize(h, color=data.y, epoch=epoch, loss=loss)
time.sleep(0.3)
過了一段時間之後,我們可以看到GCN強大的效果:
可以看到,我們的3層GCN模型可以有效地社區發現並正確地對大多數節點進行分類。