CNN 中的卷积核(e.g., 3 * 3)算某一个像素点的 feature 的时候,可以看成把这个像素点周围的像素点的特征按照一定的权重加权求和。
卷积操作类似于内积,即把卷积核里的每个权重和对应像素点值相乘,最后相加,得到的结果就是这个点的新的特征。
Spatial-GNN 想要把这种卷积操作直接推广到 Graph 上。
将某个节点周围的领域节点特征收集起来,进行某一种操作,然后更新这个节点的特征。
图片来源:李宏毅老师 2020 课程:
上面我觉得直接把 i 看成 0 来理解会好一些,假设第 0 层的图和特征如上面图左边所示。
为了更新 h 3 0 h^{0}_{3} h30 (也就是第 0 层的 3 号节点)的节点特征,那么类似的我们需要将它周围的节点的特征聚合(aggregate)起来,然后进行某种操作,来更新这个节点的特征。类似于卷积中除了对周围像素操作职位,也对自己本身做了操作,因此 Spatial-GNN 通常也会结合自己本身的特征来更新下一次的特征。
这里在我读过的论文里面总结得最好的论文是 GIN《HOW POWERFUL ARE GRAPH NEURAL NETWORKS?》
a v ( k ) = a_{v}^{(k)}= av(k)= AGGREGATE ( k ) ( { h u ( k − 1 ) : u ∈ N ( v ) } ) , h v ( k ) = C O M B I N E ( k ) ( h v ( k − 1 ) , a v ( k ) ) ^{(k)}\left(\left\{h_{u}^{(k-1)}: u \in \mathcal{N}(v)\right\}\right), \quad h_{v}^{(k)}=\mathrm{COMBINE}^{(k)}\left(h_{v}^{(k-1)}, a_{v}^{(k)}\right) (k)({hu(k−1):u∈N(v)}),hv(k)=COMBINE(k)(hv(k−1),av(k))
k k k 代表第 k 层网络,经过 k k k 层网络的某一节点会收集到他 k k k 跳邻居的节点信息。
AGGREGATE 操作就是讲收集到的节点的特征进行聚合,聚合方案的不同是导致这一类方法不同模型的主要原因。同样的 COMBINE 操作就是讲聚合的特征和自己节点这一层的特征结合起来,形成新的特征。这样就可以经过 k k k 层网络之后,每个节点就会根据自己周围的拓扑结构和特征,得到新的特征,可以更好的用于下游任务。
当然对于图分类的任务,需要整个图的特征:
h G = READOUT ( { h v ( K ) ∣ v ∈ G } ) h_{G}=\operatorname{READOUT}\left(\left\{h_{v}^{(K)} | v \in G\right\}\right) hG=READOUT({hv(K)∣v∈G})
其中 K K K 是最后一层的网络,这里表示将最后一层所有节点的特征进行某种 READOUT 操作,就得到整个图的特征。当然也可以把前面所有层的特征一起进行操作,甚至加权都是可以的。比如 GIN 中就用到了所有层的特征。
接下来就是这一类方法的经典例子:这里面有大名鼎鼎的 GAT 和 GraphSage。
论文《Neural Network for Graphs: A Contextual Constructive Approach》
NN4G 的想法很简单:就是把一跳邻居的节点特征相加,然后通过一个权重矩阵做变换,加上自身的值做一个权重矩阵相乘的变换。这里的 x 3 x_{3} x3 是这个节点本来值(比如化学分子中的原子符号),然后经过一种 embedding(例如 word2vec 等),得到第一次的特征 h 3 0 h^{0}_{3} h30。通常现有的数据集预处理原始数据的过程是已经实现了的,因此,我们可以认为他 COMBINE 的就是上次层自己的特征。
对于获取整个图的特征:
将每一层的所有节点特征求平均得到这一层的图表示,然后将所有层的图片是加权求和得到最终的图表示。
论文:《Diffusion-Convolutional Neural Networks》
MEAN ( d ( 3 , ⋅ ) = 1 ) \operatorname{MEAN}(d(3, \cdot)=1) MEAN(d(3,⋅)=1) 指的是在 Graph 中距离某个节点距离为 1 的节点。
MEAN ( d ( 3 , ⋅ ) = 2 ) \operatorname{MEAN}(d(3, \cdot)=2) MEAN(d(3,⋅)=2) 指的是在 Graph 中距离某个节点距离为 2 的节点。
这样就很好理解 diffusion 扩散操作的含义了。
这样第一层就收集 1 跳邻居的信息,第二层就收集 2 跳邻居的信息。
这里收集多跳邻居的行为和其他大部分方法有点差异:大部分方法并不是显式的收集多跳邻居,而是按照下面方法:第一层网络每个节点收集自己 1 跳邻居的信息更新节点特征。第二层也是收集自己 1 跳邻居的信息更新节点特征。注意,这种行为是可以收集到 2 跳邻居的信息的,因为第二层自己 1 跳邻居已经在第一层收集了他们的 1 跳邻居,而 1 跳邻居的 1 跳邻居中包含了自己的 2 跳邻居。
当然这种显式的方法本质上是一样的,只是最终学到的网络参数而已
进一步,将每一层网络中所有节点的向量组成一个矩阵:
H K \mathbf{H}^{\mathbf{K}} HK 代表第 K K K 层的矩阵,假设有 n n n 个节点,特征维度为 d d d,那么这个矩阵就是一个 n × d n \times d n×d 的矩阵,然后 K K K 层,就有一个 K × n × d K \times n \times d K×n×d 的一个张量。
然后最后的特征不是简单的最后一层网络输出的节点特征,他显式的考虑之前的节点。
图中所示的意思是,将 n × d n \times d n×d 矩阵中的某一行拿出来,也就是某一节点的特征,然后 K × n × d K \times n \times d K×n×d 张量就成了一个 K × d K \times d K×d 的矩阵,然后做一个 Transform (乘以一个矩阵),最后得到这个节点的特征向量。
其实我们可以这样想节点最后特征的形成:
之所以连接成矩阵形式,是为了充分利用 GPU 并行计算的能力。
因此这里的 AGGREGATE 方案和 COMBINE 方案有一点差异:
论文《Geometric deep learning on graphs and manifolds using mixture model CNNs》
MoNet 也提出了一个统一的框架,并且表示 GCN 等都是这个框架的特例。其实 GCN 也可以看成一种 Spatial-GNN,即使它是从频谱 GNN 推导出来的。
MoNet 的基本公式如下:
其中 N ( x ) \mathcal{N}(x) N(x) 代表 x x x 的邻居节点, u ( x , y ) \mathbf{u}(x, y) u(x,y) 是一种定义好的节点之间的度量(我们可以理解为距离、相似度等),注意这个度量是一个向量,然后我们通过一个网络/加权函数(也可以看作核),这个网络可以理解为一种模式(然后检查这个节点和邻居节点组合一起有没有构成某种模式,也就是模式匹配),然后可以有多个这样的网络(核或者模式),最后所有邻居节点和所有模式加权求和,更新这个节点的特征。
不同的度量选择和不同的加权函数选择对应不同的方法:
MoNet 使用的核是多个高斯核,度量是基于节点度的度量,因此也可以认为 MoNet 是一种高斯混合模型(GMM):
我们可以简单的理解, u ( x , y ) \mathbf{u}(x, y) u(x,y) 和 w j ( u ) w_{j}(\mathbf{u}) wj(u) (多个 w j ( u ) w_{j}(\mathbf{u}) wj(u)) 一起计算这个邻居节点对于这个本身节点的重要性,然后所有邻居节点加权求和来更新这个节点的特征。
这个时候一个自然地问题,我们上面表中的方法和 Monet 中的 u ( x , y ) \mathbf{u}(x, y) u(x,y) 和 w j ( u ) w_{j}(\mathbf{u}) wj(u) 都是手动定义的, 因此一个自然地想法,这个重要性(权重)我们自己去学,这也就是后面 GAT 的想法。
论文《Inductive Representation Learning on Large Graphs》
半监督学习可以分为:Transductive 和 Inductive
大多数现有方法 Graph Embedding 的方法都是 Transductive 的,训练的时候需要所有的节点,无法很好地泛化到之前没有见过的节点。
GraphSAGE:学习一个 function 来通过采样和聚合一个节点领域的特征来生成 Embedding。实际上 GraphSAGE 学习的是一组聚合函数(Aggregate Function),然后在测试的时候使用学习到的这一组聚合函数对节点的领域进行采样和聚合最后得到节点的 embedding。
这是前向算法,也就是假设我们训练好了聚合函数如何在测试的时候使用:这里有 K K K 个聚合函数,没个函数有自己的权重矩阵 W k \mathbf{W}^{k} Wk。我们可以看成 K K K 层网络。
我们每次采样都是采样自己的 1 跳邻居,那么是怎么可以收集到多跳邻居的信息的呢?注意,每一次外层循环,我们要对所有节点的特征进行更新,这样我们第 2 层的网络即时采样自己的 1 跳邻居也可以收集到 2 跳邻居的信息,因为在第 1 层网络的时候,1 跳邻居根据自己的 1 跳邻居更新自己的特征(其中包括了 2 跳邻居)。因此 K K K 个聚合函数,相当于 K K K 层网络,相当于最终节点会收集到 K K K 跳邻居的信息。
然后每一次外层循环,都是聚合自己领域节点的信息,然后和本身链接起来,最后更新自身的节点信息。最后一层,也就是第 K K K 层的输出就是最终的 Embedding。
根据算法,可以发现 GraphSAGE 就是我们 Spatial-GNN 的标准范式
接下来就是具体的聚合方案:
Mean aggregator:很好理解,就是对所有领域节点的特征向量求平均,然后和自身向量一起做一个线性变化加一个非线性激活。这种方式做一点细微的修改就和 GCN 很类似了:
这个公式和标准的 GCN 只差一个归一化常数。GCN 可以看成就是把邻居向量和自身向量一起求平均,通过一个线性转换加一个非线性激活,得到新的特征表示,注意这一公式包括了算法中的第 4 行和第 5 行。虽然过程有点差异,但是本质上是类似的,GraphSAGE-Mean 中把自身一起求平均修改为领域节点求平均然后和自身连接在一起。
Pooling aggregator:将领域节点向量送到一个完全连接层,然后对输出之后的向量赤化。例如 max-pooling 就是逐元素求最大值。
LSTM aggregator:我们上面所有的聚合方案都有两个很好地特性:(1)对于不同数量的领域节点,都可以直接使用,避免了 Graph 数据不同节点的领域数不同的问题。(2)与领域节点的输入顺序无关。 但是 LTSM 不一样,LSTM 对输入顺序是很敏感的,但是 LTSM 模型的表现力更强,为了借助这个表现了,GraphSAGE-LSTM 把采样到的节点顺序打乱之后送入 LSTM,得到聚合的向量,然后和自身连接起来,送到一个完全连接层。
模型的训练分为有监督的和无监督的:有监督的很简单,就是根据监督任务定义损失函数,学习每个聚合函数也就是每层网络的参数(聚合方案一旦确定就是不变的)。如果是无监督的,损失函数基于 graph 结构:相邻节点具有相似的特征向量,相互远离的特征向量不同:
z u ⊤ z v \mathbf{z}_{u}^{\top} \mathbf{z}_{v} zu⊤zv 代表两个节点向量内积,向量相似,内积大,然后取相反数,就是损失函数越小越好。( z v \mathbf{z}_{v} zv 是在一定长度的随机游走上出现了点,即相近的点,我们期望他们向量相似)。后面的是负采样, Q ⋅ E v n ∼ P n ( v ) log ( σ ( − z u ⊤ z v n ) ) Q \cdot \mathbb{E}_{v_{n} \sim P_{n}(v)} \log \left(\sigma\left(-\mathbf{z}_{u}^{\top} \mathbf{z}_{v_{n}}\right)\right) Q⋅Evn∼Pn(v)log(σ(−zu⊤zvn)) , Q Q Q 是负采样节点数,负采样中向量内积多了一个相反数,也就是希望负采样向量和这个节点向量差异越大越好。
实验实施:
只需要两个聚合函数,也就是两层网络,第一层采样 25 个邻居节点,第二层采样 10。之所以设置固定的领域节点数,而不是采样所有节点是为了使每个节点的时间空间消耗一直,直接原因就是为了方便简单。至于有的节点少于需要的节点数,那么就是又放回的采样,如果充足的领域节点,那么可以无放回的采样。
优点:(1)可以使用在不断扩张演进的图上;(2)可以泛化到新的图上;
论文:《Graph Attention Networks》
GAT 的核心思想就是隐式地给不同领域节点不同的权重,不用像 MoNet 一样手动的定义节点之间的度量,而是通过学习得到的。 GAT 也可用于 Inductive 的半监督学习。
知道想法就是学习加权和的权重,那么就很好理解了:
论文《How Powerful are Graph Neural Network?》
文章主要贡献:
文中提出的框架首先将给定节点的邻居的特征向量集表示为一个多集,即,一个可能有重复元素的集合。然后,可以将 GNN 中的邻居聚合看作是多集上的聚合函数。因此,为了拥有强大的表示能力,GNN 必须能够将不同的多集聚合到不同的表示中。单射函数指的是:对每一值域内的 y,存在至多一个定义域内的 x 使得 f(x) = y。也就是当且仅当 a = b, f(a) = f(b),在 GNN 中 也就是具有完全相同的领域的一个节点才会被映射为同一个 embedding。
WL 测试和 Spatial-GNN 的联系:Weisfeler-Lehman 迭代进行以下操作得到节点新标签以判断同构性:
文中给出了具体证明(详细证明参考论文):WL 测试是 GNN 性能的上限。
那么问题来了,什么样的 GNN 和 WL 测试一样强大呢?
如果邻居Aggregate函数和Readout函数是单射的,那么得到的GNN与WL测试一样强大。
h v ( k ) = MLP ( k ) ( ( 1 + ϵ ( k ) ) ⋅ h v ( k − 1 ) + ∑ u ∈ N ( v ) h u ( k − 1 ) ) h_{v}^{(k)}=\operatorname{MLP}^{(k)}\left(\left(1+\epsilon^{(k)}\right) \cdot h_{v}^{(k-1)}+\sum_{u \in \mathcal{N}(v)} h_{u}^{(k-1)}\right) hv(k)=MLP(k)((1+ϵ(k))⋅hv(k−1)+∑u∈N(v)hu(k−1))
h G = h_{G}= hG= CONCAT ( READOUT ( { h v ( k ) ∣ v ∈ G } ) ∣ k = 0 , 1 , … , K ) \left(\text { READOUT }\left(\left\{h_{v}^{(k)} | v \in G\right\}\right) | k=0,1, \ldots, K\right) ( READOUT ({hv(k)∣v∈G})∣k=0,1,…,K)
实际上 READOUT 是用的求和,即
h G = C O N C A T ( sum ( { h v ( k ) ∣ v ∈ G } ) ∣ k = 0 , 1 , … , K ) h_{G}=\mathrm{CONCAT}\left(\operatorname{sum}\left(\left\{h_{v}^{(k)} | v \in G\right\}\right) | k=0,1, \ldots, K\right) hG=CONCAT(sum({hv(k)∣v∈G})∣k=0,1,…,K)
最后获取图表示,是将每一层的节点表示 READOUT 然后连接起来。和之前一些方法只操作最后一层不同。
文章实例说明为什么 mean 和 max 的不是最强大的 GNN:
由于 mean 和 max-pooling 函数不满足单射性,无法区分某些结构的图,因此不是最强力的 GNN。但是实际中那么多网络使用这两种方法,为什么有效呢?