####好好好#####GCN圖文解讀

文章目錄

 

            圖的概念

            學習新特徵

            圖卷積

            GCN的PyTorch實現

            半監督分類實例

            結語

            參考

 

我們面對的很多數據其實是圖(graph),圖在生活中無處不在,如社交網絡,知識圖譜,蛋白質結構等。在2020年這個寒冬,窩在家裏的小編終於打算入門GNN(Graph Neural Networks)中的分支:GCN(Graph Convolutional Networks)。

uploading.4e448015.gif正在上傳…重新上傳取消

圖的概念

 

對於圖,我們習慣上用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​=∑j​Aij​;

特徵矩陣X

X:用於表示節點的特徵,X∈RN×F

 

    X∈RN×F,這裏F是特徵的維度;

 

數學表示是比較抽象的,下面是一個實例:

 

uploading.4e448015.gif正在上傳…重新上傳取消

圖1 圖以及鄰接矩陣(來源:stanford cs224w)

 

注意左圖是無向圖,而右圖是有向圖,前者的鄰接矩陣是對稱的,而後者是不對稱的。

 

相比圖像和文本,圖這種拓撲結構是較複雜的:任意的節點數以及節點間的複雜關係:

 

uploading.4e448015.gif正在上傳…重新上傳取消

圖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中得到啓發:

 

uploading.4e448015.gif正在上傳…重新上傳取消

圖3 CNN與圖學習類比(來源:stanford cs224w)

 

這是一個簡單的3x3卷積層,每個新特徵的學習是這樣的:對其領域(3x3局部空間)的特徵進行變換(wixi

wi​xi​),然後求和(∑iwixi∑i​wi​xi​)。類比到圖學習上,每個節點的新特徵可以類似得到:對該節點的鄰域節點特徵進行變換,然後求和。用公式表達就是:

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,這是神經網路的基本單元。上述公式其實就是對領域內節點特徵求和,這裏:

uploading.4e448015.gif正在上傳…重新上傳取消

其中鄰接矩陣A

A是0-1矩陣,當節點j與節點i連接時,Aij=1

 

Aij​=1,所以節點i的新特徵就是其鄰接節點的特徵和。

 

其實我們可以將上述學習分成三個部分:

 

    變換(transform):對當前的節點特徵進行變換學習,這裏就是乘法規則(Wx);

    聚合(aggregate):聚合領域節點的特徵,得到該節點的新特徵,這裏是簡單的加法規則;

    激活(activate):採用激活函數,增加非線性。

 

其實這就算是圖卷積(graph convolution)了,首先這裏的權重是所有節點共享的,類比於CNN中的參數共享;另外可以將節點的鄰居節點看成感受野,隨着網絡層數的增加,感受野越來越大,即節點的特徵融合了更多節點的信息。直觀的圖卷積示意圖如下:

uploading.4e448015.gif正在上傳…重新上傳取消

 

圖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))

相比加法規則,這種聚合方式其實是對領域節點特徵求平均,這裏:

uploading.4e448015.gif正在上傳…重新上傳取消

 

由於D˜=∑jA˜ij

 

D~=∑j​A~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))

 

這種新的聚合方法不再是單單地對鄰域節點特徵進行平均,這裏:

uploading.4e448015.gif正在上傳…重新上傳取消

 

可以看到這種聚合方式不僅考慮了節點i的度,而且也考慮了鄰居節點j的度,當鄰居節點j的度較大時,而特徵反而會受到抑制。

 

這種圖卷積方法其實譜圖卷積的一階近似(first-order approximation of spectral graph convolutions),關於更多的數學證明比較難理解,這裏不做展開,詳情可見論文。

 

定義了圖卷積,我們只需要將圖卷積層堆積起來就構成了圖卷積網絡GCN:

uploading.4e448015.gif正在上傳…重新上傳取消

 

圖5 GCN示意圖

 

其實圖神經網路(GNN,Graph Neural Network)是一個龐大的家族,如果按照f

 

f分類,其可以分成以下類型:

uploading.4e448015.gif正在上傳…重新上傳取消

 

圖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任務

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章