圖神經網絡(GCN) 詳解(一)

圖神經網絡(GCN) 原理實現解讀

本文轉載機器之心的的博文,如有冒犯,還望指正。

原文鏈接地址如下:https://towardsdatascience.com/how-to-do-deep-learning-on-graphs-with-graph-convolutional-networks-7d2250723780

何爲圖卷積網絡?

 

GCN 是一類非常強大的用於圖數據的神經網絡架構。事實上,它非常強大,即使是隨機初始化的兩層 GCN 也可以生成圖網絡中節點的有用特徵表徵。下圖展示了這種兩層 GCN 生成的每個節點的二維表徵。請注意,即使沒有經過任何訓練,這些二維表徵也能夠保存圖中節點的相對鄰近性。

 

 

更形式化地說,圖卷積網絡(GCN)是一個對圖數據進行操作的神經網絡。給定圖 G = (V, E),GCN 的輸入爲:

 

  • 一個輸入維度爲 N × F⁰ 的特徵矩陣 X,其中 N 是圖網絡中的節點數而 F⁰ 是每個節點的輸入特徵數。

  • 一個圖結構的維度爲 N × N 的矩陣表徵,例如圖 G 的鄰接矩陣 A。[1]

 

因此,GCN 中的隱藏層可以寫作 Hⁱ = f(Hⁱ⁻¹, A))。其中,H⁰ = X,f 是一種傳播規則 [1]。每一個隱藏層 Hⁱ 都對應一個維度爲 N × Fⁱ 的特徵矩陣,該矩陣中的每一行都是某個節點的特徵表徵。在每一層中,GCN 會使用傳播規則 f 將這些信息聚合起來,從而形成下一層的特徵。這樣一來,在每個連續的層中特徵就會變得越來越抽象。在該框架下,GCN 的各種變體只不過是在傳播規則 f 的選擇上有所不同 [1]。

 

傳播規則的簡單示例

 

下面,本文將給出一個最簡單的傳播規則示例 [1]:

 

f(Hⁱ, A) = σ(AHⁱWⁱ)

 

其中,Wⁱ 是第 i 層的權重矩陣,σ 是非線性激活函數(如 ReLU 函數)。權重矩陣的維度爲 Fⁱ × Fⁱ⁺¹,即權重矩陣第二個維度的大小決定了下一層的特徵數。如果你對卷積神經網絡很熟悉,那麼你會發現由於這些權重在圖中的節點間共享,該操作與卷積核濾波操作類似。

 

簡化

 

接下來我們在最簡單的層次上研究傳播規則。令:

 

  • i = 1,(約束條件 f 是作用於輸入特徵矩陣的函數)

  • σ 爲恆等函數

  • 選擇權重(約束條件: AH⁰W⁰ =AXW⁰ = AX)

 

換言之,f(X, A) = AX。該傳播規則可能過於簡單,本文後面會補充缺失的部分。此外,AX 等價於多層感知機的輸入層。

 

簡單的圖示例

 

我們將使用下面的圖作爲簡單的示例:

 

一個簡單的有向圖。

 

使用 numpy 編寫的上述有向圖的鄰接矩陣表徵如下:

 

A = np.matrix([
    [0, 1, 0, 0],
    [0, 0, 1, 1], 
    [0, 1, 0, 0],
    [1, 0, 1, 0]],
    dtype=float
)

 

接下來,我們需要抽取出特徵!我們基於每個節點的索引爲其生成兩個整數特徵,這簡化了本文後面手動驗證矩陣運算的過程。

 

In [3]: X = np.matrix([
            [i, -i]
            for i in range(A.shape[0])
        ], dtype=float)
        X
Out[3]: matrix([
           [ 0.,  0.],
           [ 1., -1.],
           [ 2., -2.],
           [ 3., -3.]
        ])

 

應用傳播規則

 

我們現在已經建立了一個圖,其鄰接矩陣爲 A,輸入特徵的集合爲 X。下面讓我們來看看,當我們對其應用傳播規則後會發生什麼:

 

In [6]: A * X
Out[6]: matrix([
            [ 1., -1.],
            [ 5., -5.],
            [ 1., -1.],
            [ 2., -2.]]

 

每個節點的表徵(每一行)現在是其相鄰節點特徵的和!換句話說,圖卷積層將每個節點表示爲其相鄰節點的聚合。大家可以自己動手驗證這個計算過程。請注意,在這種情況下,如果存在從 v 到 n 的邊,則節點 n 是節點 v 的鄰居。

 

問題

 

你可能已經發現了其中的問題:

 

  • 節點的聚合表徵不包含它自己的特徵!該表徵是相鄰節點的特徵聚合,因此只有具有自環(self-loop)的節點纔會在該聚合中包含自己的特徵 [1]。

  • 度大的節點在其特徵表徵中將具有較大的值,度小的節點將具有較小的值。這可能會導致梯度消失或梯度爆炸 [1, 2],也會影響隨機梯度下降算法(隨機梯度下降算法通常被用於訓練這類網絡,且對每個輸入特徵的規模(或值的範圍)都很敏感)。

 

接下來,本文將分別對這些問題展開討論。

 

增加自環

 

爲了解決第一個問題,我們可以直接爲每個節點添加一個自環 [1, 2]。具體而言,這可以通過在應用傳播規則之前將鄰接矩陣 A 與單位矩陣 I 相加來實現。

 

In [4]: I = np.matrix(np.eye(A.shape[0]))
        I
Out[4]: matrix([
            [1., 0., 0., 0.],
            [0., 1., 0., 0.],
            [0., 0., 1., 0.],
            [0., 0., 0., 1.]
        ])
In [8]: A_hat = A + I
        A_hat * X
Out[8]: matrix([
            [ 1., -1.],
            [ 6., -6.],
            [ 3., -3.],
            [ 5., -5.]])

 

現在,由於每個節點都是自己的鄰居,每個節點在對相鄰節點的特徵求和過程中也會囊括自己的特徵!

 

對特徵表徵進行歸一化處理

 

通過將鄰接矩陣 A 與度矩陣 D 的逆相乘,對其進行變換,從而通過節點的度對特徵表徵進行歸一化。因此,我們簡化後的傳播規則如下:

 

f(X, A) = D⁻¹AX

 

讓我們看看發生了什麼。我們首先計算出節點的度矩陣。

 

In [9]: D = np.array(np.sum(A, axis=0))[0]
        D = np.matrix(np.diag(D))
        D
Out[9]: matrix([
            [1., 0., 0., 0.],
            [0., 2., 0., 0.],
            [0., 0., 2., 0.],
            [0., 0., 0., 1.]
        ])

 

在應用傳播規則之前,不妨看看我們對鄰接矩陣進行變換後發生了什麼。

 

變換之前

 

A = np.matrix([
    [0, 1, 0, 0],
    [0, 0, 1, 1], 
    [0, 1, 0, 0],
    [1, 0, 1, 0]],
    dtype=float
)

 

變換之後

 

In [10]: D**-1 * A
Out[10]: matrix([
             [0. , 1. , 0. , 0. ],
             [0. , 0. , 0.5, 0.5],
             [0. , 0.5, 0. , 0. ],
             [0.5, 0. , 0.5, 0. ]
])

 

可以觀察到,鄰接矩陣中每一行的權重(值)都除以該行對應節點的度。我們接下來對變換後的鄰接矩陣應用傳播規則:

 

In [11]: D**-1 * A * X
Out[11]: matrix([
             [ 1. , -1. ],
             [ 2.5, -2.5],
             [ 0.5, -0.5],
             [ 2. , -2. ]
         ])

 

得到與相鄰節點的特徵均值對應的節點表徵。這是因爲(變換後)鄰接矩陣的權重對應於相鄰節點特徵加權和的權重。大家可以自己動手驗證這個結果。

 

整合

 

現在,我們將把自環和歸一化技巧結合起來。此外,我們還將重新介紹之前爲了簡化討論而省略的有關權重和激活函數的操作。

 

添加權重

 

首先要做的是應用權重。請注意,這裏的 D_hat 是 A_hat = A + I 對應的度矩陣,即具有強制自環的矩陣 A 的度矩陣。

 

In [45]: W = np.matrix([
             [1, -1],
             [-1, 1]
         ])
         D_hat**-1 * A_hat * X * W
Out[45]: matrix([
            [ 1., -1.],
            [ 4., -4.],
            [ 2., -2.],
            [ 5., -5.]
        ])

 

如果我們想要減小輸出特徵表徵的維度,我們可以減小權重矩陣 W 的規模:

 

In [46]: W = np.matrix([
             [1],
             [-1]
         ])
         D_hat**-1 * A_hat * X * W
Out[46]: matrix([[1.],
        [4.],
        [2.],
        [5.]]
)

 

添加激活函數

 

本文選擇保持特徵表徵的維度,並應用 ReLU 激活函數。

 

In [51]: W = np.matrix([
             [1, -1],
             [-1, 1]
         ])
         relu(D_hat**-1 * A_hat * X * W)
Out[51]: matrix([[1., 0.],
        [4., 0.],
        [2., 0.],
        [5., 0.]])

 

這就是一個帶有鄰接矩陣、輸入特徵、權重和激活函數的完整隱藏層!

 

在真實場景下的應用

 

最後,我們將圖卷積網絡應用到一個真實的圖上。本文將向讀者展示如何生成上文提到的特徵表徵。

 

Zachary 空手道俱樂部

 

Zachary 空手道俱樂部是一個被廣泛使用的社交網絡,其中的節點代表空手道俱樂部的成員,邊代表成員之間的相互關係。當年,Zachary 在研究空手道俱樂部的時候,管理員和教員發生了衝突,導致俱樂部一分爲二。下圖顯示了該網絡的圖表徵,其中的節點標註是根據節點屬於俱樂部的哪個部分而得到的,「A」和「I」分別表示屬於管理員和教員陣營的節點。

 

Zachary 空手道俱樂部圖網絡

 

構建 GCN

 

接下來,我們將構建一個圖卷積網絡。我們並不會真正訓練該網絡,但是會對其進行簡單的隨機初始化,從而生成我們在本文開頭看到的特徵表徵。我們將使用 networkx,它有一個可以很容易實現的 Zachary 空手道俱樂部的圖表徵。然後,我們將計算 A_hat 和 D_hat 矩陣。

 

from networkx import to_numpy_matrix
zkc = karate_club_graph()
order = sorted(list(zkc.nodes()))
A = to_numpy_matrix(zkc, nodelist=order)
I = np.eye(zkc.number_of_nodes())
A_hat = A + I
D_hat = np.array(np.sum(A_hat, axis=0))[0]
D_hat = np.matrix(np.diag(D_hat))

 

接下來,我們將隨機初始化權重。

 

W_1 = np.random.normal(
    loc=0, scale=1, size=(zkc.number_of_nodes(), 4))
W_2 = np.random.normal(
    loc=0, size=(W_1.shape[1], 2))

 

接着,我們會堆疊 GCN 層。這裏,我們只使用單位矩陣作爲特徵表徵,即每個節點被表示爲一個 one-hot 編碼的類別變量。

 

def gcn_layer(A_hat, D_hat, X, W):
    return relu(D_hat**-1 * A_hat * X * W)
H_1 = gcn_layer(A_hat, D_hat, I, W_1)
H_2 = gcn_layer(A_hat, D_hat, H_1, W_2)
output = H_2

 

我們進一步抽取出特徵表徵。

 

feature_representations = {
    node: np.array(output)[node] 
    for node in zkc.nodes()}

 

你看,這樣的特徵表徵可以很好地將 Zachary 空手道俱樂部的兩個社區劃分開來。至此,我們甚至都沒有開始訓練模型!

 

Zachary 空手道俱樂部圖網絡中節點的特徵表徵。

 

我們應該注意到,在該示例中由於 ReLU 函數的作用,在 x 軸或 y 軸上隨機初始化的權重很可能爲 0,因此需要反覆進行幾次隨機初始化才能生成上面的圖。

 

結語

 

本文中對圖卷積網絡進行了高屋建瓴的介紹,並說明了 GCN 中每一層節點的特徵表徵是如何基於其相鄰節點的聚合構建的。讀者可以從中瞭解到如何使用 numpy 構建這些網絡,以及它們的強大:即使是隨機初始化的 GCN 也可以將 Zachary 空手道俱樂部網絡中的社區分離開來。 

 

參考文獻

[1] Blog post on graph convolutional networks by Thomas Kipf.

[2] Paper called Semi-Supervised Classification with Graph Convolutional Networks by Thomas Kipf and Max Welling.

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