来源:gcn官方代码实现:gcn_mp.py
以下主要是记录个人对官方代码的理解。
由多层GCNLayer组成
class GCN(nn.Module):
def __init__(self,
g,
in_feats, # 输入特征维度
n_hidden, # 隐藏层特征维度
n_classes, # 输出维度(分类个数)
n_layers, # 层数,每一层是最基本的卷积操作
activation, # 激活函数
dropout):
super(GCN, self).__init__()
self.layers = nn.ModuleList()
# input layer
self.layers.append(GCNLayer(g, in_feats, n_hidden, activation, dropout))
# hidden layers
for i in range(n_layers - 1):
self.layers.append(GCNLayer(g, n_hidden, n_hidden, activation, dropout))
# output layer
self.layers.append(GCNLayer(g, n_hidden, n_classes, None, dropout))
def forward(self, features):
h = features
for layer in self.layers:
h = layer(h)
return h
其实就是执行这样一次操作:
D ~ = D + I N \tilde{D}=D+I_N D~=D+IN, A ~ = A + I N \tilde{A}=A+I_N A~=A+IN, D D D为度矩阵(对角阵), A A A为邻接矩阵, I N I_N IN为单位矩阵。
H ( l ) H^{(l)} H(l)为第 l l l层的特征矩阵,维度 N × F l N\times F_l N×Fl
H ( l + 1 ) H^{(l+1)} H(l+1)为第 l + 1 l+1 l+1层的特征矩阵,维度 N × F l + 1 N\times F_{l+1} N×Fl+1
W ( l ) W^{(l)} W(l)第 l l l层的维度变换矩阵,维度 F l × F l + 1 F_l\times F_{l+1} Fl×Fl+1,矩阵乘法
class GCNLayer(nn.Module):
def __init__(self,
g,
in_feats,
out_feats,
activation,
dropout,
bias=True):
super(GCNLayer, self).__init__()
self.g = g
# 公式里的 W
self.weight = nn.Parameter(torch.Tensor(in_feats, out_feats))
if dropout:
self.dropout = nn.Dropout(p=dropout)
else:
self.dropout = 0.
self.node_update = NodeApplyModule(out_feats, activation, bias)
self.reset_parameters()
def reset_parameters(self):
stdv = 1. / math.sqrt(self.weight.size(1))
self.weight.data.uniform_(-stdv, stdv)
def forward(self, h):
if self.dropout:
h = self.dropout(h)
# 公式里的 H^{l} * W^{l},对第l层特征进行维度变换后命名为'h'
self.g.ndata['h'] = torch.mm(h, self.weight)
# !重要:消息函数gcn_msg,聚合函数gcn_reduce,更新函数self.node_update
self.g.update_all(gcn_msg, gcn_reduce, self.node_update)
h = self.g.ndata.pop('h')
return h
dgl库的消息传递过程如下图所示:
消息函数
def gcn_msg(edge):
msg = edge.src['h'] * edge.src['norm']
return {'m': msg}
聚合函数
def gcn_reduce(node):
accum = torch.sum(node.mailbox['m'], 1) * node.data['norm']
return {'h': accum}
更新函数
这里是对节点的特征做一些变换
class NodeApplyModule(nn.Module):
def __init__(self, out_feats, activation=None, bias=True):
super(NodeApplyModule, self).__init__()
if bias:
self.bias = nn.Parameter(torch.Tensor(out_feats))
else:
self.bias = None
self.activation = activation
self.reset_parameters()
def reset_parameters(self):
if self.bias is not None:
stdv = 1. / math.sqrt(self.bias.size(0))
self.bias.data.uniform_(-stdv, stdv)
def forward(self, nodes):
h = nodes.data['h']
if self.bias is not None:
h = h + self.bias
if self.activation:
h = self.activation(h)
return {'h': h}
'norm’特征哪里来的,就是在主函数里计算的
# normalization
degs = g.in_degrees().float() # D
norm = torch.pow(degs, -0.5) # D^{-1/2}
norm[torch.isinf(norm)] = 0
g.ndata['norm'] = norm.unsqueeze(1)
# 所有节点增加特征'norm',为自己的度的-1/2次方
https://blog.csdn.net/Wolf_AgOH/article/details/124482946