0

0

一文读懂图卷积神经网络(GCN)

P粉084495128

P粉084495128

发布时间:2025-07-22 17:32:54

|

1210人浏览过

|

来源于php中文网

原创

本文围绕经典论文介绍GCN,解释其定义,即处理图结构数据的网络,输入为带特征的图,输出为节点特征。解析核心公式,通过添加自环、对称归一化邻接矩阵解决信息丢失和尺度问题,还提及在Cora数据集上的应用及数学证明参考。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

一文读懂图卷积神经网络(gcn) - php中文网

GCN 学习

本文讲的GCN理论知识来源于论文:

SEMI-SUPERVISED CLASSIFICATION WITH GRAPH CONVOLUTIONAL NETWORKS

这是在GCN领域最经典的论文之一

1. 什么是GCN

GCN结构图
一文读懂图卷积神经网络(GCN) - php中文网        

我们可以根据这个GCN的图看到,一个拥有 CC 个input channel的graph作为输入,经过中间的hidden layers,得到 FF 个 output channel的输出。

图卷积网络主要可以由两个级别的作用变换组成:

注意本文讲的图都特指无向无权重的图。

  1. graph level:

例如说通过引入一些形式的pooling 操作. 然后改变图的结构。但是本次讲过GCN并没有进行这个级别的操作。所以看到上图我们的网络结构的输出和输出的graph的结构是一样的。

  1. node level:

通常说node level的作用是不改变graph的结构的,仅通过对graph的特征/信号(特征信号 XX 作为输入:一个 NDN∗D 矩阵( NN: 输入图的nodes的个数, DD 输入的特征维度) ,得到输出 ZZ:一个 NFN∗F 的矩阵( FF 输出的特征维度)。

a) 一个特征描述(feature description) xixi : 指的是每个节点 ii 的特征表示

b) 每一个graph 的结构都可以通过邻接矩阵 AA 表示(或者其他根据它推导的矩阵)

举例:

我们可以很容易的根据一个邻接矩阵重构出一个graph。 例如下图: G=(V,E)G=(V,E) 其中 VV 代表节点, EE 代表边

一文读懂图卷积神经网络(GCN) - php中文网        

根据图,我们就可以得出下面的维度图和邻接矩阵

一文读懂图卷积神经网络(GCN) - php中文网        

论文公式提出

因为 AA 可以确定唯一的一张图,这么重要的一个属性我们肯定是要把他放到神经网络传递函数里面去的,所以网络中间的每一个隐藏层可以写成以下的非线性函数:

一文读懂图卷积神经网络(GCN) - php中文网        

其中输入层 H(0)=XH(0)=X , 输出层 H(L)=ZH(L)=Z , LL 是层数。 不同的GCN模型,采用不同 f(,)f(⋅,⋅)函数。

上面是我们理想中的函数的形式,论文中最终推导出来的函数是这样的:

一文读懂图卷积神经网络(GCN) - php中文网        

一文读懂图卷积神经网络(GCN) - php中文网        

论文公式逐步解析

每一个节点下一层的信息是由前一层本身的信息以及相邻的节点的信息加权加和得到,然后再经过线性变换 WW 以及非线性变换 σ()σ() 。

我们一步一步分解,我们要定义一个简单的f(H(l),A)f(H(l),A) 函数,作为基础的网络层。

可以很容易的采用最简单的层级传导(layer-wise propagation)规则:

f(H(l),A)=σ(AH(l)W(l))f(H(l),A)=σ(AH(l)W(l))

我们直接将 AHAH 做矩阵相乘,然后再通过一个权重矩阵 W(l)W(l)做线性变换,之后再经过非线性激活函数 σ()σ(⋅) , 比如说 ReLUReLU,最后得到下一层的输入 Hl+1Hl+1 。

In [ ]
import paddle

A = paddle.to_tensor([
    [0,1,0,0,1,0],
    [1,0,1,0,1,0],
    [0,1,0,1,0,0],
    [0,0,1,0,1,1],
    [1,1,0,1,0,0],
    [0,0,0,1,0,0]],dtype='float32')print(A)

H_0 = paddle.to_tensor([[1],[2],[3],[4],[5],[6]],dtype='float32')print(H_0)

x = paddle.matmul(A,H_0)print(x)
       
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[0., 1., 0., 0., 1., 0.],
        [1., 0., 1., 0., 1., 0.],
        [0., 1., 0., 1., 0., 0.],
        [0., 0., 1., 0., 1., 1.],
        [1., 1., 0., 1., 0., 0.],
        [0., 0., 0., 1., 0., 0.]])
Tensor(shape=[6, 1], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[1.],
        [2.],
        [3.],
        [4.],
        [5.],
        [6.]])
Tensor(shape=[6, 1], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[7. ],
        [9. ],
        [6. ],
        [14.],
        [7. ],
        [4. ]])
       

从上面输出可以看的出来:每个值保留了相邻节点的值

造好物
造好物

一站式AI造物设计平台

下载

输入层的 x1=[1]x1=[1] , 根据矩阵的运算公式我们可以很容易地得到下一层的该节点的表示 X1=[7]X1′=[7], 也很容易发现 X1(1)=x2+x5X1′(1)=x2+x5,而 x2,x5x2,x5就是节点1的相邻节点。

就是可以看图:

[1] = 2 + 5
[2] = 1+ 3 + 5
······
[6] = 4

所以我们 AHAH 就是快速将相邻的节点的信息相加得到自己下一层的输入。

但是这样就出现了新的问题:

问题一
我们虽然获得了周围节点的信息了,但是自己本身的信息却没了

解决方案:
对每个节点手动增加一条self-loop 到每一个节点,即 A^=A+IA=A+I
其中 II是单位矩阵identity matrix。

问题二
从上面的结果也可以看出,在经过一次的AHAH 矩阵变换后,得到的输出会变大,即特征向量 XX 的scale会改变,在经过多层的变化之后,将和输入的scale差距越来越大。

解决方案:
可以将邻接矩阵 AA 做归一化使得最后的每一行的加和为1,使得 AHAH 获得的是weighted sum。

我们可以将 AA 的每一行除以行的和,这就可以得到normalized的 AA 。而其中每一行的和,就是每个节点的度degree。

用矩阵表示则为: A=D1AA=D−1A ,对于Aij=AijdiAij=diAij

代码展示:

In [8]
import paddleimport numpy as np

A = paddle.to_tensor([
    [0,1,0,0,1,0],
    [1,0,1,0,1,0],
    [0,1,0,1,0,0],
    [0,0,1,0,1,1],
    [1,1,0,1,0,0],
    [0,0,0,1,0,0]],dtype='float32')print(A)

D = paddle.to_tensor([
    [2,0,0,0,0,0],
    [0,3,0,0,0,0],
    [0,0,2,0,0,0],
    [0,0,0,3,0,0],
    [0,0,0,0,3,0],
    [0,0,0,0,0,1]], dtype='float32')print(D)# 防止为0时取倒数变成无穷大D[D==0] = D.max() + 1e100DD = paddle.reciprocal(D)print(DD)

hat_A = paddle.matmul(DD,A)print(hat_A)
       
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[0., 1., 0., 0., 1., 0.],
        [1., 0., 1., 0., 1., 0.],
        [0., 1., 0., 1., 0., 0.],
        [0., 0., 1., 0., 1., 1.],
        [1., 1., 0., 1., 0., 0.],
        [0., 0., 0., 1., 0., 0.]])
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[2., 0., 0., 0., 0., 0.],
        [0., 3., 0., 0., 0., 0.],
        [0., 0., 2., 0., 0., 0.],
        [0., 0., 0., 3., 0., 0.],
        [0., 0., 0., 0., 3., 0.],
        [0., 0., 0., 0., 0., 1.]])
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[0.50000000, 0.        , 0.        , 0.        , 0.        , 0.        ],
        [0.        , 0.33333334, 0.        , 0.        , 0.        , 0.        ],
        [0.        , 0.        , 0.50000000, 0.        , 0.        , 0.        ],
        [0.        , 0.        , 0.        , 0.33333334, 0.        , 0.        ],
        [0.        , 0.        , 0.        , 0.        , 0.33333334, 0.        ],
        [0.        , 0.        , 0.        , 0.        , 0.        , 1.        ]])
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[0.        , 0.50000000, 0.        , 0.        , 0.50000000, 0.        ],
        [0.33333334, 0.        , 0.33333334, 0.        , 0.33333334, 0.        ],
        [0.        , 0.50000000, 0.        , 0.50000000, 0.        , 0.        ],
        [0.        , 0.        , 0.33333334, 0.        , 0.33333334, 0.33333334],
        [0.33333334, 0.33333334, 0.        , 0.33333334, 0.        , 0.        ],
        [0.        , 0.        , 0.        , 1.        , 0.        , 0.        ]])
       

但是在实际运用中采用的是对称的normalization:

用矩阵表示则为:

A=D12AD12A=D−21AD−21

对于Aij=AijdidjAij=didjAij

这其实是跟Laplacian Matrix 有关,拉普拉斯算子是这样的:

L=IND12AD12L=IN−D−21AD−21

In [9]
import paddleimport numpy as np

A = paddle.to_tensor([
    [0,1,0,0,1,0],
    [1,0,1,0,1,0],
    [0,1,0,1,0,0],
    [0,0,1,0,1,1],
    [1,1,0,1,0,0],
    [0,0,0,1,0,0]],dtype='float32')print(A)

D = paddle.to_tensor([
    [2,0,0,0,0,0],
    [0,3,0,0,0,0],
    [0,0,2,0,0,0],
    [0,0,0,3,0,0],
    [0,0,0,0,3,0],
    [0,0,0,0,0,1]], dtype='float32')print(D)# 取逆D[D==0] = D.max() + 1e100DD = paddle.reciprocal(D)print(DD)# 开方_DD = paddle.sqrt(DD)# 求积hat_A = paddle.matmul(paddle.matmul(_DD,A),_DD)print(hat_A)
       
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[0., 1., 0., 0., 1., 0.],
        [1., 0., 1., 0., 1., 0.],
        [0., 1., 0., 1., 0., 0.],
        [0., 0., 1., 0., 1., 1.],
        [1., 1., 0., 1., 0., 0.],
        [0., 0., 0., 1., 0., 0.]])
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[2., 0., 0., 0., 0., 0.],
        [0., 3., 0., 0., 0., 0.],
        [0., 0., 2., 0., 0., 0.],
        [0., 0., 0., 3., 0., 0.],
        [0., 0., 0., 0., 3., 0.],
        [0., 0., 0., 0., 0., 1.]])
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[0.50000000, 0.        , 0.        , 0.        , 0.        , 0.        ],
        [0.        , 0.33333334, 0.        , 0.        , 0.        , 0.        ],
        [0.        , 0.        , 0.50000000, 0.        , 0.        , 0.        ],
        [0.        , 0.        , 0.        , 0.33333334, 0.        , 0.        ],
        [0.        , 0.        , 0.        , 0.        , 0.33333334, 0.        ],
        [0.        , 0.        , 0.        , 0.        , 0.        , 1.        ]])
Tensor(shape=[6, 6], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[0.        , 0.40824828, 0.        , 0.        , 0.40824828, 0.        ],
        [0.40824828, 0.        , 0.40824828, 0.        , 0.33333331, 0.        ],
        [0.        , 0.40824828, 0.        , 0.40824828, 0.        , 0.        ],
        [0.        , 0.        , 0.40824828, 0.        , 0.33333331, 0.57735026],
        [0.40824828, 0.33333331, 0.        , 0.33333331, 0.        , 0.        ],
        [0.        , 0.        , 0.        , 0.57735026, 0.        , 0.        ]])
       

A0,1=A0,1d0d1=123=0.4082A0,1=d0d1A0,1=231=0.4082

把这两个tricks结合起来,我们就可以原文的公式:

一文读懂图卷积神经网络(GCN) - php中文网        

其中 A^=A+IA=A+I , D^D 是 A^A 的degree matrix。 而 D^12A^D^12D−21AD−21 是对 AA 做了一个对称的归一化。

在Cora数据集上训练的两层GCN的隐藏层激活的可视化效果如下:

一文读懂图卷积神经网络(GCN) - php中文网        

2. 数学证明:

附上参考的视频:

https://www.bilibili.com/video/BV1Vw411R7Fj

一文读懂图卷积神经网络(GCN) - php中文网        

一文读懂图卷积神经网络(GCN) - php中文网        

一文读懂图卷积神经网络(GCN) - php中文网        

一文读懂图卷积神经网络(GCN) - php中文网        

相关专题

更多
Golang channel原理
Golang channel原理

本专题整合了Golang channel通信相关介绍,阅读专题下面的文章了解更多详细内容。

244

2025.11.14

golang channel相关教程
golang channel相关教程

本专题整合了golang处理channel相关教程,阅读专题下面的文章了解更多详细内容。

342

2025.11.17

点击input框没有光标怎么办
点击input框没有光标怎么办

点击input框没有光标的解决办法:1、确认输入框焦点;2、清除浏览器缓存;3、更新浏览器;4、使用JavaScript;5、检查硬件设备;6、检查输入框属性;7、调试JavaScript代码;8、检查页面其他元素;9、考虑浏览器兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

180

2023.11.24

http与https有哪些区别
http与https有哪些区别

http与https的区别:1、协议安全性;2、连接方式;3、证书管理;4、连接状态;5、端口号;6、资源消耗;7、兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1969

2024.08.16

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

34

2026.01.14

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

14

2026.01.13

PHP 高性能
PHP 高性能

本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

33

2026.01.13

MySQL数据库报错常见问题及解决方法大全
MySQL数据库报错常见问题及解决方法大全

本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

18

2026.01.13

PHP 文件上传
PHP 文件上传

本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

12

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号