文章目錄
圖的概念
學習新特徵
圖卷積
GCN的PyTorch實現
半監督分類實例
結語
參考
我們面對的很多數據其實是圖(graph),圖在生活中無處不在,如社交網絡,知識圖譜,蛋白質結構等。在2020年這個寒冬,窩在家裏的小編終於打算入門GNN(Graph Neural Networks)中的分支:GCN(Graph Convolutional Networks)。
圖的概念
對於圖,我們習慣上用G=(V,E)
G=(V,E)表示。這裏VV是圖中節點的集合,而EE爲邊的集合,這裏記圖的節點數爲NN。一個G
G中有3個比較重要的矩陣:
鄰接矩陣A
A:adjacency matrix,用來表示節點間的連接關係,這裏我們假定是0-1矩陣;
度矩陣D
D:degree matrix,每個節點的度指的是其連接的節點數,這是一個對角矩陣,其中對角線元素Dii=∑jAij
Dii=∑jAij;
特徵矩陣X
X:用於表示節點的特徵,X∈RN×F
X∈RN×F,這裏F是特徵的維度;
數學表示是比較抽象的,下面是一個實例:
圖1 圖以及鄰接矩陣(來源:stanford cs224w)
注意左圖是無向圖,而右圖是有向圖,前者的鄰接矩陣是對稱的,而後者是不對稱的。
相比圖像和文本,圖這種拓撲結構是較複雜的:任意的節點數以及節點間的複雜關係:
圖2 圖與圖像和文本的結構對比(來源:stanford cs224w)
這種複雜性給神經網絡在圖上的應用帶來了一定困難,但是我們依然有解決辦法。
學習新特徵
深度學習中最重要的是學習特徵:隨着網絡層數的增加,特徵越來越抽象,然後用於最終的任務。對於圖任務來說,這點同樣適用,我們希望深度模型從圖的最初始特徵X
X出發學習到更抽象的特徵,比如學習到了某個節點的高級特徵,這個特徵根據圖結構融合了圖中其他節點的特徵,我們就可以用這個特徵用於節點分類或者屬性預測。那麼圖網絡就是要學習新特徵,用公式表達就是:
H(k+1)=f(H(k),A)
H(k+1)=f(H(k),A)
這裏k指的是網絡層數,H(k)
H(k)就是網絡第k層的特徵,其中H(0)=X
H(0)=X。那麼現在的問題是如何學習,我們可以從CNN中得到啓發:
圖3 CNN與圖學習類比(來源:stanford cs224w)
這是一個簡單的3x3卷積層,每個新特徵的學習是這樣的:對其領域(3x3局部空間)的特徵進行變換(wixi
wixi),然後求和(∑iwixi∑iwixi)。類比到圖學習上,每個節點的新特徵可以類似得到:對該節點的鄰域節點特徵進行變換,然後求和。用公式表達就是:
H(k+1)=f(H(k),A)=σ(AH(k)W(k))H(k+1)=f(H(k),A)=σ(AH(k)W(k))
這裏的WkWk是學習權重,維度爲Fk−1×FkFk−1×Fk,而σ(⋅)
σ(⋅)是激活函數,比如是ReLU,這是神經網路的基本單元。上述公式其實就是對領域內節點特徵求和,這裏:
其中鄰接矩陣A
A是0-1矩陣,當節點j與節點i連接時,Aij=1
Aij=1,所以節點i的新特徵就是其鄰接節點的特徵和。
其實我們可以將上述學習分成三個部分:
變換(transform):對當前的節點特徵進行變換學習,這裏就是乘法規則(Wx);
聚合(aggregate):聚合領域節點的特徵,得到該節點的新特徵,這裏是簡單的加法規則;
激活(activate):採用激活函數,增加非線性。
其實這就算是圖卷積(graph convolution)了,首先這裏的權重是所有節點共享的,類比於CNN中的參數共享;另外可以將節點的鄰居節點看成感受野,隨着網絡層數的增加,感受野越來越大,即節點的特徵融合了更多節點的信息。直觀的圖卷積示意圖如下:
圖4 圖卷積的示意圖 (來源:https://www.jianshu.com/p/2fd5a2454781)
圖卷積
上述的加法規則只是一個簡單實現,其存在兩個問題:首先在計算新特徵時沒有考慮自己的特徵,這肯定是個重大缺陷;另外採用加法規則時,對於度大的節點特徵越來越大,而對於度小的節點卻相反,這可能導致網絡訓練過程中梯度爆炸或者消失的問題。
針對第一個問題,我們可以給圖中每個節點增加自連接,實現上可以直接改變鄰接矩陣:
A˜=A+IN
A~=A+IN
針對第二個問題,我們可以對鄰接矩陣進行歸一化,使得A
A的每行和值爲1,在實現上我們可以乘以度矩陣的逆矩陣:D˜−1A˜D~−1A~,這裏的度矩陣是更新AA後重新計算的。這樣我們就得到:
H(k+1)=f(H(k),A)=σ(D˜−1A˜H(k)W(k))
H(k+1)=f(H(k),A)=σ(D~−1A~H(k)W(k))
相比加法規則,這種聚合方式其實是對領域節點特徵求平均,這裏:
由於D˜=∑jA˜ij
D~=∑jA~ij,所以這種聚合方式其實就是求平均,對領域節點的特徵是求平均值,這樣就進行了歸一化,避免求和方式所造成的問題。
更進一步地,我們可以採用對稱歸一化來進行聚合操作,這就是論文1中所提出的圖卷積方法:
H(k+1)=f(H(k),A)=σ(D˜−0.5A˜D˜−0.5H(k)W(k))
H(k+1)=f(H(k),A)=σ(D~−0.5A~D~−0.5H(k)W(k))
這種新的聚合方法不再是單單地對鄰域節點特徵進行平均,這裏:
可以看到這種聚合方式不僅考慮了節點i的度,而且也考慮了鄰居節點j的度,當鄰居節點j的度較大時,而特徵反而會受到抑制。
這種圖卷積方法其實譜圖卷積的一階近似(first-order approximation of spectral graph convolutions),關於更多的數學證明比較難理解,這裏不做展開,詳情可見論文。
定義了圖卷積,我們只需要將圖卷積層堆積起來就構成了圖卷積網絡GCN:
圖5 GCN示意圖
其實圖神經網路(GNN,Graph Neural Network)是一個龐大的家族,如果按照f
f分類,其可以分成以下類型:
圖6 GNN分類
可以看到GCN只是其中的一個很小的分支,我們上面所述的GCN其實是屬於譜圖卷積。更多關於GNN的學習,可以閱讀這三篇綜述文章:
Graph Neural Networks: A Review of Methods and Application
A Comprehensive Survey on Graph Neural Networks
Deep Learning on Graphs: A Survey
GCN的PyTorch實現
雖然GCN從數學上較難理解,但是它的實現是非常簡單的,值得注意的一點是一般情況下鄰接矩陣A
A是稀疏矩陣,所以我們在實現矩陣乘法時,採用稀疏運算會更高效。這裏我們參考論文作者的官方實現。首先是圖卷積層的實現:
import torch
import torch.nn as nn
class GraphConvolution(nn.Module):
"""GCN layer"""
def __init__(self, in_features, out_features, bias=True):
super(GraphConvolution, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.weight = nn.Parameter(torch.Tensor(in_features, out_features))
if bias:
self.bias = nn.Parameter(torch.Tensor(out_features))
else:
self.register_parameter('bias', None)
self.reset_parameters()
def reset_parameters(self):
nn.init.kaiming_uniform_(self.weight)
if self.bias is not None:
nn.init.zeros_(self.bias)
def forward(self, input, adj):
support = torch.mm(input, self.weight)
output = torch.spmm(adj, support)
if self.bias is not None:
return output + self.bias
else:
return output
def extra_repr(self):
return 'in_features={}, out_features={}, bias={}'.format(
self.in_features, self.out_features, self.bias is not None
)
對於GCN,只需要將圖卷積層堆積起來就可以,這裏我們實現一個兩層的GCN:
class GCN(nn.Module):
"""a simple two layer GCN"""
def __init__(self, nfeat, nhid, nclass):
super(GCN, self).__init__()
self.gc1 = GraphConvolution(nfeat, nhid)
self.gc2 = GraphConvolution(nhid, nclass)
def forward(self, input, adj):
h1 = F.relu(self.gc1(input, adj))
logits = self.gc2(h1, adj)
return logits
這裏的激活函數採用ReLU,後面我們將用這個網絡實現一個圖中節點的半監督分類任務。
半監督分類實例
這裏給出的是GCN論文中的一個半監督分類任務,官方代碼也給出這個任務。我們要處理的數據集是cora數據集,該數據集是一個論文圖,共2708個節點,每個節點都是一篇論文,所有樣本點被分爲7類別:
Case_Based, Genetic_Algorithms, Neural_Networks,
Probabilistic_Methods, Reinforcement_Learning, Rule_Learning, Theory
每篇論文都由一個1433維的詞向量表示,即節點特徵維度爲1433。詞向量的每個特徵都對應一個詞,取0表示該特徵對應的詞不在論文中,取1則表示在論文中。每篇論文都至少引用了一篇其他論文,或者被其他論文引用,這是一個連通圖,不存在孤立點。
這裏的任務是給定圖中某些節點的類別,然後訓練一個網絡能夠預測其它節點標籤,所以這裏一個半監督學習任務。我們建立一個兩層GCN來解決這個問題:
Z=f(X;A)=softmax(Aˆ(ReLU(AˆXW(0))W(1)),Aˆ=D˜−0.5A˜D˜−0.5
Z=f(X;A)=softmax(A^(ReLU(A^XW(0))W(1)),A^=D~−0.5A~D~−0.5
從結構上看,中間層用於提出特徵,而最後一層的節點特徵用於分類任務(送入softmax,計算交叉熵):
在這裏插入圖片描述
圖7 兩層GCN用於分類任務
數據的提取,論文官方實現已經給出,我們只需要load就可以:
# https://github.com/tkipf/pygcn/blob/master/pygcn/utils.py
adj, features, labels, idx_train, idx_val, idx_test = load_data(path="./data/cora/")
值得注意的有兩點,一是論文引用應該是單向圖,但是在網絡時我們要先將其轉成無向圖,或者說建立雙向引用,我發現這個對模型訓練結果影響較大:
# build symmetric adjacency matrix
adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)
另外官方實現中對鄰接矩陣採用的是普通均值歸一化,當然我們也可以採用對稱歸一化方式:
def normalize_adj(adj):
"""compute L=D^-0.5 * (A+I) * D^-0.5"""
adj += sp.eye(adj.shape[0])
degree = np.array(adj.sum(1))
d_hat = sp.diags(np.power(degree, -0.5).flatten())
norm_adj = d_hat.dot(adj).dot(d_hat)
return norm_adj
這裏我們只採用圖中140個有標籤樣本對GCN進行訓練,每個epoch計算出這些節點特徵,然後計算loss:
loss_history = []
val_acc_history = []
for epoch in range(epochs):
model.train()
logits = model(features, adj)
loss = criterion(logits[idx_train], labels[idx_train])
train_acc = accuracy(logits[idx_train], labels[idx_train])
optimizer.zero_grad()
loss.backward()
optimizer.step()
val_acc = test(idx_val)
loss_history.append(loss.item())
val_acc_history.append(val_acc.item())
print("Epoch {:03d}: Loss {:.4f}, TrainAcc {:.4}, ValAcc {:.4f}".format(
epoch, loss.item(), train_acc.item(), val_acc.item()))
只需要訓練200個epoch,我們就可以在測試集上達到80%左右的分類準確,GCN的強大可想而知:
在這裏插入圖片描述
圖8 訓練收斂曲線
結語
GCN只是GNN中的冰山一角,這可能連入門都不算,但是千里之行始於足下。
參考
Semi-Supervised Classification with Graph Convolutional Networks
How to do Deep Learning on Graphs with Graph Convolutional Networks
Graph Convolutional Networks
Graph Convolutional Networks in PyTorch
回顧頻譜圖卷積的經典工作:從ChebNet到GCN
圖數據集之cora數據集介紹- 用pyton處理 - 可用於GCN任務