pytorchgeometric 教程⼆GCN 源码详解+实战
pytorch geometric 教程⼆ GCN 源码详解+实战
pytorch geometric 教程⼆ GCN 源码详解&实战这⼀篇是建⽴在你已经对pytorch geometric 消息传递&跟新的原理有⼀定了解的基础上。如果没有的话,推荐先看这篇关于pytorch geometric 消息传递&更新的博⽂()。
原理回顾
矩阵形式
先回顾⼀下GCN的原理,矩阵形式如下:
其中 是增加了⾃环的邻接矩阵,是度对⾓矩阵。是特征矩阵,是参数矩阵。在pytorch geometric 中,邻接矩阵可以通过可选择参数edge_weight 来赋予边权重。
点维度这是pytorch geometric 代码注释中的数学公式。我们可以看到⾮线性变化不是在卷积层中实现的,需要我们后期⾃⼰加上。其中定义了从source 点j 到target 点i 的边权重。点维度理解GCN 就是,将邻居(包括⾃⼰)对应的特征进⾏⼀个权重叠加,并进⾏⼀个维度变换。GCN 代码(GCNConv )
init
X =′XΘD
^−1/2A ^D ^−1/2=A
^A +I =D ^ii ∑j =0A ^ij X Θx =i ′Θx ⊤j ∈N (v )∪{i }∑d ^j d ^i e j ,i
j
e j ,i
import torch
小学6年级数学辅导
from torch import Tensor
from torch .nn import Parameter
from torch_geometric .nn .den .linear import Linear
from torch_geometric .nn .conv import MessagePassing
class GCNConv (MessagePassing ):
def __init__(lf , in_channels : int , out_channels : int ,
improved : bool = Fal , cached : bool = Fal ,
add_lf_loops : bool = True , normalize : bool = True ,
bias : bool = True , **kwargs ):
kwargs .tdefault ('aggr', 'add') # 'add' can be replaced with 'mean', 'max'rt是什么意思
super (GCNConv , lf ).__init__(**kwargs )
dateofissue
lf .in_channels = in_channels
lf .out_channels = out_channels
lf .improved = improved
lf .cached = cached
lf .add_lf_loops = add_lf_loops
lf .normalize = normalize
lf ._cached_edge_index = None
lf ._cached_adj_t = None
lf .lin = Linear (in_channels , out_channels , bias =Fal ,
weight_initializer ='glorot')
if bias :
lf .bias = Parameter (torch .Tensor (out_channels ))
el :
lf .register_parameter ('bias', None )
lf .ret_parameters ()
def ret_parameters (lf ):
lf .lin .ret_parameters ()
zeros (lf .bias )
lf ._cached_edge_index = None
lf ._cached_adj_t = None
邻域聚合⽅式kwargs.tdefault('aggr', 'add')检查关键字参数中是否定义了邻域聚合⽅式,也就是是否包含名为aggr 的key 。如果没有的话,采⽤默认的add 聚合⽅式,也就是邻居特征求和(因为GCN 对邻接矩阵进⾏了归⼀化,所以这⾥虽然是add ,但实现的效果等同于于带权平均)。在我们定义model 的时候,我们可以通过参数aggr = add, mean, max 来选择邻域聚合⽅式。参数含义下⾯具体解释各个参数的含义:
in_channels :输⼊原始特征或者隐含层embedding 的维度
out_channels :输出embedding 的维度
improved : 默认是Fal , 如果是True 的话,则,增强了⾃⾝的权重。
cached : 默认是Fal ,如果是True 的话,第⼀次执⾏就会缓存的计算结果,且在后期调⽤它。这个参数只应该在transductive ,邻接矩阵不变的情况下才可设置为True .add_lf_loops : 默认是True ,如果是Fal 的话,则邻接矩阵不会加上⾃环。normalize : 默认是True ,给邻接矩阵加上⾃环并且对称归⼀化邻接矩阵。
bias :默认是True ,如果是Fal 的话,layer 中没有bias 项。
=A ^A +2I D
^−1/2A
^D ^−1/2
这⾥定义特征的线性变换lf.lin时,使⽤的是 den.linear.Linear,它类似于Linear,不过额外加上
了weight和bias的初始化⽅式。 den.linear.Linear中weight的默认初始化⽅式是glorot,bias的默认初始化⽅式是zeros。这⾥使⽤Linear的时候,将Linear⾃⾝的bias设为Fal,但是额外给GCNConv layer设置了⼀个bias。所以
在ret_parameters的时候,不但需要ret lf.lin的参数,还需要ret GCNConv layer的bias。
forward
下⾯我列出了forward函数的代码
def forward(lf, x: Tensor, edge_index: Adj,
edge_weight: OptTensor =None)-> Tensor:
""""""
alize:
if isinstance(edge_index, Tensor):
cache = lf._cached_edge_index
if cache is None:
edge_index, edge_weight = gcn_norm(# yapf: disable
edge_index, edge_weight, x.de_dim),
lf.improved, lf.add_lf_loops)
if lf.cached:
lf._cached_edge_index =(edge_index, edge_weight)
el:
edge_index, edge_weight = cache[0], cache[1]
elif isinstance(edge_index, SparTensor):
cache = lf._cached_adj_t
if cache is None:
edge_index = gcn_norm(# yapf: disable
edge_index, edge_weight, x.de_dim),
lf.improved, lf.add_lf_loops)
if lf.cached:
lf._cached_adj_t = edge_index
el:
世界上最远的距离泰戈尔edge_index = cache
小区英文x = lf.lin(x)
# propagate_type: (x: Tensor, edge_weight: OptTensor)
out = lf.propagate(edge_index, x=x, edge_weight=edge_weight,
size=None)
foxholeif lf.bias is not None:
out += lf.bias
return out
参数
x: 所有节点的特征或者隐含层的embedding
edge_index:边信息,这⾥可以是(2, N_edges)的Tensor,也可以是(N_nodes, N_nodes)的SparTensor
edge_weight: 可选参数,如果不是空的话,邻接矩阵是带权重的。
forward主体吹风造型
我们看到forward函数做了以下⼏件事情:
1. normalize邻接矩阵(如果normalize为True的话)。
这其中对edge_index为Tensor和SparTensor两种情况分别处理。另外如果cache为True,则获取之前缓存
的normalized的edge_index或adj_t (SparTensor的edge_index会写作adj_t)。如果cache为Fal,则重新调⽤gcn_norm函数。
XΘ
2. lf.lin实现特征线性变换,也就是公式中的。
3. 对第2步中得到的结果调⽤propagate 函数。
propagate我们前⽂提过,edge_index为Tensor的时候,会调⽤message和aggregate实现消息传递和更
新。edge_index为SparTensor的时候,则会在message_and_aggregate被定义的情况下优先调⽤message_and_aggregate。
4. 跟新后的结果上加上bias。
gcn_norm函数这⾥就不细写了。
消息传递
这⾥详解⼀下GCN中的message函数。
def message(lf, x_j: Tensor, edge_weight: OptTensor)-> Tensor:
return x_j if edge_weight is None el edge_weight.view(-1,1)* x_j
def message_and_aggregate(lf, adj_t: SparTensor, x: Tensor)-> Tensor:
return matmul(adj_t, x,reduce=lf.aggr)
⼀,edge_index为Tensor
这⾥不明⽩的⼩伙伴可以先看这篇博⽂()
edge_index为Tensor的时候,propagate调⽤message和aggregate实现消息传递和更新。
我们搞懂以下⼏个维度:
edge_index的shape是(2, N_edges)
邻居特征x_j的shape是(N_edges, N_features)
x_j是将x scatter到edge_index的第⼀个元素上,所以shape变为(N_edges, N_features)。
edge_weight.view(-1, 1)的shape是(N_edges, 1)
涉水而过
所以可以进⾏edge_weight.view(-1, 1) * x_j,等同于根据每条边的权重对每个邻居加上了相应的权重。
message得到的结果维度是(N_edges, N_features),会在aggregate函数中⽤pytorch的scatter,将message聚合到对应边中的target点
propagate输出的结果维度为(N_nodes,N_features)
⼆,edge_index为SparTensor
edge_index为SparTensor的时候,直接调⽤类似矩阵计算matmul(adj_t, x, reduce=lf.aggr)。这⾥的matmul来⾃于torch_spar,除了类似常规的矩阵相乘外,还给出了可选的reduce,所以除了add,mean和max也是可以在这⾥实现的。
实战
定义模型
pytorch geometric的卷积层调⽤还是挺简单的,下⾯是⼀个两层的GCN。
import torch
functional as Fbend是什么意思
from v import GCNConv
class Module):
def__init__(lf, in_channels, hidden_channels, out_channels, dropout=0.): super(GCN, lf).__init__()
长沙化妆培训学校
lf.dropout = dropout
def ret_parameters():
for conv vs:
<_parameters()
def forward(lf, x, edge_index):
x = lf.convs[0](x, edge_index)
x = F.relu(x)
x = F.dropout(x, p=lf.dropout, aining)
x = lf.convs[1](x, edge_index)
return x.log_softmax(dim=-1)
模型调⽤
接下来,我们⽤Cora数据集尝试⼀下。