PyG中的图神经网络中,scatter和gather运算(离散和聚集)都是非常常见的操作。
我们以GAT为例,说明scatter在GAT中的应用:一是相似度系数的归一,二是 \(h_i^{’}\) 的加权邻域聚合。
相似度系数归一
$$ \alpha_{i,j} = \frac{\exp(LeakyReLU(e_{i,j}))}{\sum \limits_{k \in N(i)} \exp(LeakyReLU(e_{i,k})) } $$
其中,分母部分聚合节点 \(i\) 所有邻边相似度系数的运算可以用 scatter_add
简洁优雅的实现。
\(h_i^{’}\) 的加权邻域聚合
$$ h_{i}^{’} = \sigma( \sum \limits_{k \in N(i)} \alpha_{i,k} h_{k} ) $$
这里的邻域聚合的操作逻辑基本同上,同样可以使用 scatter_add
。
Q:为什么要用scatter?同样一个邻域聚合的操作,我们可以使用for循环,预处理出neighbor照样可以聚合,那么我们为什么需要使用scatter呢? A:答案就是scatter支持pytorch的并行化,同时更加便于pytorch的梯度传播。(for循环本身的效率其实不低,因为一个node只有有限个neighbor)
scatter #
self.scatter(dim, index, src)
self: tensor张量
Args:
dim: 沿dim轴索引
index(LongTensor): 索引
src: 写入self中的值张量,类型需要和self相同
作用:将src中的每个值,按index的指定索引,scatter至self中。
以3维张量为例,self中的值由如下公式决定:
同时self,index以及src需要满足如下条件:
- self、index、src的维度相同(即dim相同:self.dim()=index.dim()=src.dim(),非维度大小)
具体来说就是
index.shape (3,4,5), dim()=3
,同理src.shape=(4,5,6), dim()=3
。因为需要执行如上公式的赋值,所以self、index、src的维度需要相同。 - index每一个维度的大小 <= src每一个维度的大小, 即 \(index.shape(i) \leq src.shape(i),\ i \in [0,src.dim())\)
- \(index.shape(i) \leq self.shape(i),\ i \in [0,src.dim()) \ 且 i \neq dim_{arg}\), 即除开操作维度dim之外(指的是scatter的参数dim之外),其他维度的大小都必须小于self的对应维度大小。
理解起来同样很简单,因为其他维度需要索引self,而
index[i][j][k]
在操作维度之上无所谓,他只需要满足index[i][j][k]<=self.dim(arg_dim)
即可。
张量index的数值大小约束:
- index中的任意值,其大小需要在
[0, self.dim(arg_dim)-1]
的范围内。 - index沿dim维度的一行,值必须唯一(弱约束,违反不报错,但是会产生无意义的重复scatter)
例1.index沿dim的一行数组,值不唯一,产生scatter的重复赋值
例2. index沿dim的一行数组,值唯一,scatter离散到不同的位置
scatter_add #
scatter_add的基本原理同scatter,但是self的同一位置可以多次add(即不需要满足上面数学约束的第五条,允许index沿dim轴有重复值),以GAT的邻域聚合为例:
单头GAT #

邻域相加,得到最后的结果,即 neighbor_sum[i]
为node_i的邻域聚合值。
多头GAT #
基本原理仍然和单头相似,具体如下图所示:
