• MPNN 模型:GNN 传递规则的实现


    首先,假如我们定义一个极简的传递规则

    f(X,A) = AX

    A是邻接矩阵,X是特征矩阵, 其物理意义就是 通过矩阵乘法操作,批量把图中的相邻节点汇聚到当前节点。

    但是由于A的对角线都是 0.因此自身的节点特征会被过滤掉。

    图神经网络的核心是 吸周围之精华,再叠加自身,因而需要改进来保留自身特征。如何做?

    方法是给每个节点添加一个自环,即将邻接矩阵对角线值各加1,此时用\widetilde{A}表示,\widetilde{A}X做到了聚合邻居节点并保留自身信息。

    但是当图过于复杂时,聚合邻居信息会不断执行矩阵乘法或加法,可能导致特征值太大而溢出。如何做?

    方法是邻接矩阵归一化。那么如何归一化呢?我们由A可以得到图的度D,由于A变成了\widetilde{A},我们认为\widetilde{A}的度为\widetilde{D}。常用的归一化方式就是用度数矩阵的倒数\widetilde{D}^{-1}

    f(X,A) = \widetilde{D}^{-1}\widetilde{A}X

    但是\widetilde{D}^{-1}\widetilde{A}仅仅对矩阵A进行了列上的缩放,操作后的元素值是不对称的,某种程度破坏了图结构的对称性。(这是为什么?)那么如何修复这种对称性呢?

    方法是在行的方向上也进行对等缩放,具体 做法是,让邻接矩阵\widetilde{A}右乘一个缩放因子\widetilde{D}^{-1},这样就使得缩放版本的邻接矩阵重新恢复对称性。于是信息聚合的方式为

    f(X,A) = \widetilde{D}^{-1}\widetilde{A}\widetilde{D}^{-1}X

    \widetilde{D}^{-1}\widetilde{A}\widetilde{D}^{-1}能够很好地缩放邻接矩阵,既然-1次幂可以完成,为什么不尝试一下(-1/2)次幂呢?

    事实上,对每个矩阵元素都实施\widetilde{D}^{-\frac{1}{2}}\widetilde{D}^{-\frac{1}{2}}=\frac{1}{\sqrt{deg(v_i)\sqrt{deg(v_j)}}}

    这种操作可以对邻接矩阵地每一行每一列”无偏差“地进行一次归一化,以防相邻节点间度数不匹配对归一化地影响。(why)?

    于是就出现了被众多学术论文广泛采纳地邻接矩阵地缩放形式

    f(X,A) = \widetilde{D}^{-\frac{1}{2}}\widetilde{A}\widetilde{D}^{-\frac{1}{2}}X

    考虑权值影响的信息聚合

    上述仅仅考虑到邻接矩阵对获取邻居节点信息的影响,即只考虑拓扑结构施加的影响。事实上,对于特定节点而言,不同维度的特征值对给定任务的影响程度是不同的,如果第对各个特征值进行时 打分就,就要涉及到权值矩阵W了,也就是要构造更为完整的图神经网络模型 AWX。权值矩阵W通常是通过学习得到的。

    f(X,A) = \widetilde{D}^{-\frac{1}{2}}\widetilde{A}\widetilde{D}^{-\frac{1}{2}}XW

    如果我们想压缩节点输出的维度,也可以缩减权值矩阵的输出维度。

    在以上的分析中,没有考虑激活函数的影响,无法给予神经网络的非线性变换能力,因此通常我们需要使用sigmoid、tanh、Relu等作为激活函数,最后再用argmax函数模拟一个分类的输出。

    reference:

    《从深度学习到图神经网络:模型与实践》  张玉宏 等

    code:

    1. import networkx as nx
    2. import matplotlib.pyplot as plt
    3. import numpy as np
    4. #定义节点
    5. N = [(f"v{i}", 0) for i in range (1,3)] + [(f"v{i}",1) for i in range (3,5)] + [(f"v{i}",2) for i in range (5,6)] #定义节点
    6. #定义边
    7. E = [("v1","v2"),("v1","v3"),
    8. ("v2","v1"),("v2","v3"),("v2","v4"),
    9. ("v3","v1"),("v3","v2"),("v3","v4"),
    10. ("v4","v2"),("v4","v3"),("v4","v5"),
    11. ("v5","v4")
    12. ] #定义边
    13. G = nx.Graph() #构造图
    14. G.add_nodes_from(list(map(lambda x: x[0],N))) #给图添加节点
    15. G.add_edges_from(E) #给图添加边
    16. ncolor =['r']*2 + ['b']*2 +['g']*1 #设置节点颜色
    17. nsize = [700]*2 + [700]*2 + [700]*1 #设置节点的大小
    18. #显示图
    19. nx.draw(G, with_labels= True, font_weight ='bold', font_color = 'w', node_color =ncolor, node_size =nsize)
    20. plt.show()
    21. #借用nx构造邻接矩阵
    22. A = np.array(nx.adjacency_matrix(G).todense())
    23. print(A)
    24. #构造特征矩阵X
    25. X = np.array([[i,-i, i+2] for i in range (A.shape[0])])
    26. print(X)
    27. #为了不丢失自己的属性,需要修改本身的邻接矩阵,因为最初邻接矩阵的斜对角线为0
    28. I = np.eye(A.shape[0])
    29. A_hat = A + I
    30. print('A_hat')
    31. print(A_hat)
    32. #计算自环邻接矩阵的度
    33. D_hat = np.diag(np.sum(A_hat,axis= 0 ))
    34. print(D_hat)
    35. #获取D——hat的逆矩阵,即一个缩放因子
    36. D_1 = np.diag(D_hat) ** (-1) *np.eye(A_hat.shape[0])
    37. print('D_1')
    38. print(D_1)
    39. #缩放版的邻接矩阵
    40. A_scale = D_1 @ A_hat #对矩阵A仅仅进行了列方向上的缩放
    41. print('A_scale')
    42. print(A_scale)
    43. #用A_scale来聚合邻居节点的信息
    44. X_new = A_scale @ X
    45. print('X_new')
    46. print(X_new)
    47. #修复原本的缩放的不对称性
    48. scale_factor = D_1 @ A_hat @ D_1 #scale_factor 是对称的,而 A_scale是不对称 的
    49. print('scale_factor')
    50. print(scale_factor)
    51. #用scale_factor来聚合邻居节点的信息
    52. X_new1 = scale_factor @ X
    53. print('X_new1')
    54. print(X_new1)
    55. D_sq_half = np.diag(D_hat) ** (-0.5) *np.eye(A_hat.shape[0])
    56. print('D_sq_half')
    57. print(D_sq_half)
    58. #修复原本的缩放的不对称性
    59. scale_factor2 = D_sq_half @ A_hat @ D_sq_half #scale_factor 是对称的,而 A_scale是不对称 的
    60. print('scale_factor2')
    61. print(scale_factor2)
    62. #用scale_factor2来聚合邻居节点的信息
    63. X_new2 = scale_factor2 @ X
    64. print('X_new2')
    65. print(X_new2)
    66. #给出的权值矩阵
    67. W = np.array([[0.13,0.24],
    68. [0.37,-0.32],
    69. [0.14,-0.15]])
    70. X_new3 = X_new2 @ W
    71. print(X_new3)
    72. #也可以缩减W的尺寸压缩节点的输出维度
    73. W1 = np.array([[0.13],
    74. [0.37],
    75. [0.14]])
    76. #计算logits
    77. logits = X_new2 @ W1
    78. print(logits)
    79. #以上都没有考虑到激活函数,无法模拟神经网络的非线性变换能力,可以使用激活函数
    80. y = logits * (logits >0) #使用Relu函数
    81. print(y)
    82. #为了实现分类等功能,还需要添加一层Softmax
    83. def softmax(x):
    84. return np.exp(x) /np.sum(np.exp(x), axis = 0)
    85. prob = softmax(y)
    86. print('y')
    87. print(y)
    88. #模拟一个分类输出
    89. pred = np.argmax(prob)
    90. print(pred)

  • 相关阅读:
    JSP out对象:向客户端输出数据
    vue修改 el-input 输入框默认背景色
    Altair:Python数据可视化库的魅力之旅
    实现部署在WM虚拟机中的Linux系统上的地图服务的远程访问
    基于Boostrap+Html的响应式Web电影网站设计
    70-Java的日期时间:Date、SimpleDateFormat、Calendar、JDK8后新增日期API
    中望软件笔试
    java+ssh+mysql毕业生实习招聘管理系统
    【C++】命名空间和using namespace std的注意事项
    Minecraft我的世界部署教程
  • 原文地址:https://blog.csdn.net/qq_38480311/article/details/133829799