https://www.bilibili.com/medialist/play/watchlater/BV18b4y1k7no
这个视频是讲怎么用代码把GCN的公式实现的

DGL

消息传递范式

image.png
如何更新(t+1)时刻结点v的特征? 假设v有邻居有u等结点,以下步骤以邻居u举例

  1. (t+1)时刻,收集 t时刻 结点u,v和边e(由uv构成)的特征
  2. 聚合v的所有邻居发送过来的消息,再加上v自身的特征。更新v

image.png
image.png

GraphConv

参数说明

image.png
入度为0的情况下:
image.png只有bias,没有意义。
但是参数构造里可以选择是否允许。
(可以在构造图的时候加一个自环loof,避免入度为0的情况。仅限同构图。)
image.png

forward()函数

image.png
image.png
把源节点的特征h 作为消息m存储在边上

image.png
h 乘以 edge_weight 存储在m

根据输入和输出特征的大小来选择计算量比较小的方式
image.png
例子

  1. import dgl
  2. import numpy as np
  3. import torch as th
  4. from dgl.nn import GraphConv
  1. # Case 1: 同构图
  2. g = dgl.graph(([0,1,2,3,2,5], [1,2,3,4,0,3]))
  3. g = dgl.add_self_loop(g) #这里加了环
  4. feat = th.ones(6, 10)
  5. conv = GraphConv(10, 2, norm='both', weight=True, bias=True)
  6. res = conv(g, feat)
  7. print(res)
  8. #
  9. tensor([[ 1.3326, -0.2797],
  10. [ 1.4673, -0.3080],
  11. [ 1.3326, -0.2797],
  12. [ 1.6871, -0.3541],
  13. [ 1.7711, -0.3717],
  14. [ 1.0375, -0.2178]], grad_fn=<AddBackward0>)
  15. # allow_zero_in_degree example 不加环的情况
  16. g = dgl.graph(([0,1,2,3,2,5], [1,2,3,4,0,3]))
  17. conv = GraphConv(10, 2, norm='both', weight=True, bias=True, allow_zero_in_degree=True)
  18. res = conv(g, feat)
  19. print(res)
  20. #
  21. tensor([[-0.2473, -0.4631],
  22. [-0.3497, -0.6549],
  23. [-0.3497, -0.6549],
  24. [-0.4221, -0.7905],
  25. [-0.3497, -0.6549],
  26. [ 0.0000, 0.0000]], grad_fn=<AddBackward0>)

代码

删去注释后的实现代码,官方文档:https://docs.dgl.ai/_modules/dgl/nn/pytorch/conv/graphconv.html#GraphConv

  1. class GraphConv(nn.Module):
  2. def __init__(self,
  3. in_feats,
  4. out_feats,
  5. norm='both',
  6. weight=True,
  7. bias=True,
  8. activation=None,
  9. allow_zero_in_degree=False):
  10. super(GraphConv, self).__init__()
  11. if norm not in ('none', 'both', 'right', 'left'):
  12. raise DGLError('Invalid norm value. Must be either "none", "both", "right" or "left".'
  13. ' But got "{}".'.format(norm))
  14. self._in_feats = in_feats
  15. self._out_feats = out_feats
  16. self._norm = norm
  17. self._allow_zero_in_degree = allow_zero_in_degree
  18. if weight:
  19. self.weight = nn.Parameter(th.Tensor(in_feats, out_feats))
  20. else:
  21. self.register_parameter('weight', None)
  22. if bias:
  23. self.bias = nn.Parameter(th.Tensor(out_feats))
  24. else:
  25. self.register_parameter('bias', None)
  26. self.reset_parameters()
  27. self._activation = activation
  28. def reset_parameters(self):
  29. if self.weight is not None:
  30. init.xavier_uniform_(self.weight)
  31. if self.bias is not None:
  32. init.zeros_(self.bias)
  33. def set_allow_zero_in_degree(self, set_value):
  34. self._allow_zero_in_degree = set_value
  35. def forward(self, graph, feat, weight=None, edge_weight=None):
  36. with graph.local_scope():
  37. if not self._allow_zero_in_degree:
  38. if (graph.in_degrees() == 0).any():
  39. raise DGLError('There are 0-in-degree nodes in the graph, '
  40. 'output for those nodes will be invalid. '
  41. 'This is harmful for some applications, '
  42. 'causing silent performance regression. '
  43. 'Adding self-loop on the input graph by '
  44. 'calling `g = dgl.add_self_loop(g)` will resolve '
  45. 'the issue. Setting ``allow_zero_in_degree`` '
  46. 'to be `True` when constructing this module will '
  47. 'suppress the check and let the code run.')
  48. aggregate_fn = fn.copy_src('h', 'm')
  49. if edge_weight is not None:
  50. assert edge_weight.shape[0] == graph.number_of_edges()
  51. graph.edata['_edge_weight'] = edge_weight
  52. aggregate_fn = fn.u_mul_e('h', '_edge_weight', 'm')
  53. # (BarclayII) For RGCN on heterogeneous graphs we need to support GCN on bipartite.
  54. feat_src, feat_dst = expand_as_pair(feat, graph)
  55. if self._norm in ['left', 'both']:
  56. degs = graph.out_degrees().float().clamp(min=1)
  57. if self._norm == 'both':
  58. norm = th.pow(degs, -0.5)
  59. else:
  60. norm = 1.0 / degs
  61. shp = norm.shape + (1,) * (feat_src.dim() - 1)
  62. norm = th.reshape(norm, shp)
  63. feat_src = feat_src * norm
  64. if weight is not None:
  65. if self.weight is not None:
  66. raise DGLError('External weight is provided while at the same time the'
  67. ' module has defined its own weight parameter. Please'
  68. ' create the module with flag weight=False.')
  69. else:
  70. weight = self.weight
  71. if self._in_feats > self._out_feats:
  72. # mult W first to reduce the feature size for aggregation.
  73. if weight is not None:
  74. feat_src = th.matmul(feat_src, weight)
  75. graph.srcdata['h'] = feat_src
  76. graph.update_all(aggregate_fn, fn.sum(msg='m', out='h'))
  77. rst = graph.dstdata['h']
  78. else:
  79. # aggregate first then mult W
  80. graph.srcdata['h'] = feat_src
  81. graph.update_all(aggregate_fn, fn.sum(msg='m', out='h'))
  82. rst = graph.dstdata['h']
  83. if weight is not None:
  84. rst = th.matmul(rst, weight)
  85. if self._norm in ['right', 'both']:
  86. degs = graph.in_degrees().float().clamp(min=1)
  87. if self._norm == 'both':
  88. norm = th.pow(degs, -0.5)
  89. else:
  90. norm = 1.0 / degs
  91. shp = norm.shape + (1,) * (feat_dst.dim() - 1)
  92. norm = th.reshape(norm, shp)
  93. rst = rst * norm
  94. if self.bias is not None:
  95. rst = rst + self.bias
  96. if self._activation is not None:
  97. rst = self._activation(rst)
  98. return rst
  99. def extra_repr(self):
  100. summary = 'in={_in_feats}, out={_out_feats}'
  101. summary += ', normalization={_norm}'
  102. if '_activation' in self.__dict__:
  103. summary += ', activation={_activation}'
  104. return summary.format(**self.__dict__)