当前位置: 首页 > news >正文

GCN: 图卷积网络,概念以及代码实现

GCN: 图卷积网络

1. GCN介绍

好的,这是一份关于 GCN 简单明了的介绍。

1.1 GCN (图卷积网络) 是什么?

它是一种专门处理图结构数据(如社交网络、分子结构)的神经网络。

它的核心思想可以用一句话概括:“近朱者赤,近墨者黑”


1.2 它是如何工作的?

GCN 的每一层都在做一件非常简单的事:

对于图中的每一个节点,把它自己和它所有邻居的信息“混合”一下,来更新自己。

举个例子:
想象一个社交网络,GCN 想更新你的个人资料(特征向量):

  1. 收集信息:GCN 找到你和你所有朋友的个人资料。
  2. 混合信息:它把这些资料(包括你自己的)做个“加权平均”,形成一份对你更全面的“新资料”。

就这样,经过一层 GCN,每个节点的新特征都融合了其邻居的特征。

在这里插入图片描述


1.3 为什么要堆叠多层?

  • 一层 GCN:节点只能看到直接相连的邻居。
  • 两层 GCN:信息可以传播到**“邻居的邻居”**。因为你的邻居在第一层已经融合了它邻居的信息。
  • 多层 GCN:节点能感知到网络中越来越远的信息,获得更全局的视野

1.4 总结要点:

  • 目标:为图中的每个节点学习一个包含结构信息的强大特征向量。
  • 核心操作聚合邻居信息来更新节点自身。
  • 能力:通过堆叠层数,可以捕捉从局部到全局的图结构特征。
  • 用途:常用于节点分类(预测用户兴趣)、链接预测(推荐好友)等任务。

2. 代码实现

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid# 这段代码的核心任务是节点分类 (Node Classification)。
# 其中的nn.ModuleList: 一个特殊的列表,你放进去的层能被 PyTorch 自动识别,它们的参数(权重)才能被训练。
class GCN(nn.Module):def __init__(self,# 不再需要传入图对象 gin_channels,  # PyG 惯例: in_channelshidden_channels,  # PyG 惯例: hidden_channelsout_channels,  # PyG 惯例: out_channelsnum_layers,  # DGL 中叫 n_layersactivation,dropout):super().__init__()# 1. 创建一个列表来存放我们的网络层self.layers = nn.ModuleList()# 2. 定义输入层# GCNConv(输入维度, 输出维度)# 它会把每个节点最初的特征向量,转换成一个更短、更精华的“隐藏”向量self.layers.append(GCNConv(in_channels, hidden_channels))# 3. 定义中间的隐藏层 (如果 num_layers > 1)for _ in range(num_layers - 1):self.layers.append(GCNConv(hidden_channels, hidden_channels))# 4. 定义输出层# 它会把最终的隐藏向量,转换成对应每个类别的“得分” (logits)self.layers.append(GCNConv(hidden_channels, out_channels))# 5. 保存激活函数和 dropout 层self.activation = activationself.dropout = nn.Dropout(p=dropout)def forward(self, x, edge_index):h = x # h 代表每个节点的特征向量,初始就是原始特征 xfor i, layer in enumerate(self.layers):# 在进入非第一层之前,先做 dropoutif i != 0:h = self.dropout(h)# 核心步骤:调用 GCNConv 层# 把当前的特征 h 和图的结构 edge_index 传进去# GCNConv 在内部完成了“邻居信息汇总与自身更新”h = layer(h, edge_index)# 在非最后一层之后,使用激活函数# 作用是增加非线性,让模型能学习更复杂的关系if i < len(self.layers) - 1:h = self.activation(h)return h# 2. 评估函数 (evaluate) 的修改
def evaluate(model, data, mask):model.eval()with torch.no_grad():logits = model(data.x, data.edge_index) # 得到所有节点的得分logits = logits[mask] # 只选择我们关心的节点 (比如测试集节点)labels = data.y[mask] # 获取这些节点的真实标签,mask: 一个布尔类型的张量([True, False, True, ...]),用来精确地挑选出训练集、验证集或测试集的节点。_, indices = torch.max(logits, dim=1) # 找到每个节点得分最高的类别correct = torch.sum(indices == labels) # 计算预测正确的数量return correct.item() * 1.0 / len(labels) # 返回准确率# 3. 训练函数 (train) 的修改
def train(n_epochs=100, lr=1e-2, weight_decay=5e-4, n_hidden=16, n_layers=1, activation=F.relu, dropout=0.5):# 1. 准备工作device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')dataset = Planetoid(root='data/Cora', name='Cora')data = dataset[0].to(device)  # 获取图数据对象并送到设备# 2. 获取图的基本信息in_channels = dataset.num_node_featuresout_channels = dataset.num_classes# 3. 实例化模型、损失函数和优化器model = GCN(in_channels=in_channels,hidden_channels=n_hidden,out_channels=out_channels,num_layers=n_layers,activation=activation,dropout=dropout).to(device)loss_fcn = torch.nn.CrossEntropyLoss()optimizer = torch.optim.Adam(model.parameters(),lr=lr,weight_decay=weight_decay)# 4. 开始训练循环for epoch in range(n_epochs):model.train()# 前向传播:计算模型对所有节点的预测得分logits = model(data.x, data.edge_index)# 计算损失:只用训练集节点来计算!# 比较模型在训练节点上的预测得分,和它们的真实标签有多大差距loss = loss_fcn(logits[data.train_mask], data.y[data.train_mask])# 反向传播和优化optimizer.zero_grad()loss.backward()optimizer.step()# 打印当前周期的损失和在验证集上的准确率val_acc = evaluate(model, data, data.val_mask)print(f"Epoch {epoch:03d} | Loss: {loss.item():.4f} | Val Accuracy: {val_acc:.4f}")print()test_acc = evaluate(model, data, data.test_mask)print(f"Test Accuracy: {test_acc:.2%}")if __name__ == '__main__':train()

3.对于代码的解释

3.1 最终目标:它在做什么?

这段代码的核心任务是节点分类 (Node Classification)

想象一下你有一个社交网络,网络里有些人你已经知道他们的兴趣爱好(比如“体育迷”、“电影迷”),但大部分人的兴趣是未知的。你想通过他们之间的好友关系,来预测那些未知兴趣的人最可能属于哪个兴趣小组。

这个代码做的就是类似的事情,只不过用的是一个叫 Cora 的学术论文引用网络:

  • 节点 (Node):一篇学术论文。
  • 边 (Edge):论文A引用了论文B,那么A和B之间就有一条边。
  • 特征 (Feature):每篇论文都有一串数字(一个向量)来表示其内容,比如论文中某些关键词是否出现。
  • 标签 (Label):每篇论文被分到了一个类别(比如“神经网络”、“强化学习”等)。

代码的目标就是: 训练一个 GCN 模型,只看一小部分论文的标签(训练集),去预测网络里其他所有论文的类别(验证集和测试集)。


3.2 GCN 的核心思想:邻居决定你(“近朱者赤,近墨者黑”)

GCN 的工作原理非常直观,就像一句老话:“物以类聚,人以群分”。一个节点的特性,很大程度上受其邻居的影响。

GCN 的每一层都在做一件事:

对于每个节点,去看一眼它所有直接相连的邻居,把邻居们的信息(特征)“汇总”起来,再结合自己原有的信息,形成一个“新”的信息表示。

  • 一层 GCN:一个节点能了解到它1跳邻居的信息。
  • 两层 GCN:第一层结束后,每个节点已经融合了1跳邻居的信息。在第二层,它再去汇总“新”邻居的信息,这相当于间接获取了2跳邻居(邻居的邻居)的信息。

通过堆叠多层,一个节点就能感知到网络中越来越远的节点,从而获得更全局的视野。


3.3 代码逐段详解

现在,我们带着“信息汇总”这个思想来看代码。

3.3.1. 模型定义 (class GCN)

这是 GCN 模型的“设计蓝图”。

class GCN(nn.Module):def __init__(self, in_channels, hidden_channels, out_channels, num_layers, activation, dropout):super().__init__()# 1. 创建一个列表来存放我们的网络层self.layers = nn.ModuleList()# 2. 定义输入层# GCNConv(输入维度, 输出维度)# 它会把每个节点最初的特征向量,转换成一个更短、更精华的“隐藏”向量self.layers.append(GCNConv(in_channels, hidden_channels))# 3. 定义中间的隐藏层 (如果 num_layers > 1)for _ in range(num_layers - 1):self.layers.append(GCNConv(hidden_channels, hidden_channels))# 4. 定义输出层# 它会把最终的隐藏向量,转换成对应每个类别的“得分” (logits)self.layers.append(GCNConv(hidden_channels, out_channels))# 5. 保存激活函数和 dropout 层self.activation = activationself.dropout = nn.Dropout(p=dropout)
  • __init__: 构造函数,负责“搭建”模型。
  • nn.ModuleList: 一个特殊的列表,你放进去的层能被 PyTorch 自动识别,它们的参数(权重)才能被训练。
  • GCNConv: 这是 PyG 库提供的核心“积木”。GCNConv(A, B) 的意思是,它能接收一个维度为 A 的特征向量,通过邻居信息汇总和线性变换,输出一个维度为 B 的新向量。
  • 流程:
    1. 输入层: (in_channels, hidden_channels) 接收原始特征,压缩成隐藏特征。Cora 数据集里,in_channels 是 1433。
    2. 隐藏层: (hidden_channels, hidden_channels) 在隐藏维度上继续“提炼”信息。
    3. 输出层: (hidden_channels, out_channels) 将最终提炼的特征,映射到每个类别的得分上。Cora 有 7 个类别,所以 out_channels 是 7。
  • dropout: 一种防止模型“死记硬背”(过拟合)的技术。在训练时,它会随机地“丢弃”一些神经元的输出,强迫模型学习到更鲁棒的特征。
    def forward(self, x, edge_index):h = x # h 代表每个节点的特征向量,初始就是原始特征 xfor i, layer in enumerate(self.layers):# 在进入非第一层之前,先做 dropoutif i != 0:h = self.dropout(h)# 核心步骤:调用 GCNConv 层# 把当前的特征 h 和图的结构 edge_index 传进去# GCNConv 在内部完成了“邻居信息汇总与自身更新”h = layer(h, edge_index)# 在非最后一层之后,使用激活函数# 作用是增加非线性,让模型能学习更复杂的关系if i < len(self.layers) - 1:h = self.activation(h)return h
  • forward: 定义了数据在模型中是如何“流动”的。
  • 输入:
    • x: 节点特征矩阵,形状是 [节点数, 特征维度]
    • edge_index: 边的列表,形状是 [2, 边的数量],记录了哪些节点之间有连接。
  • 流动过程:
    1. h = x: 一开始,h 就是原始特征。
    2. for 循环:数据依次流过我们定义的每一层 GCNConv
    3. h = layer(h, edge_index): 这就是神奇发生的地方! layer 会利用 edge_index 找到每个节点的邻居,然后执行 GCN 的数学运算,更新 h
    4. h = self.activation(h): F.relu 等激活函数会让模型不只是做简单的线性叠加,能学到更复杂模式。最后一层通常不加,因为我们需要原始的“得分”来计算损失。
3.3.2. 评估函数 (evaluate)

这个函数用来在验证集或测试集上,检查模型学得怎么样,但它不会更新模型参数

def evaluate(model, data, mask):model.eval() # 切换到评估模式 (这会关闭 dropout)with torch.no_grad(): # 不计算梯度,节省计算资源logits = model(data.x, data.edge_index) # 得到所有节点的得分logits = logits[mask] # 只选择我们关心的节点 (比如测试集节点)labels = data.y[mask] # 获取这些节点的真实标签_, indices = torch.max(logits, dim=1) # 找到每个节点得分最高的类别correct = torch.sum(indices == labels) # 计算预测正确的数量return correct.item() * 1.0 / len(labels) # 返回准确率
  • mask: 一个布尔类型的张量([True, False, True, ...]),用来精确地挑选出训练集、验证集或测试集的节点。
3.3.3. 训练函数 (train)

这是整个流程的“总指挥”。

def train(...):# 1. 准备工作device = ... # 判断用 CPU 还是 GPUdataset = Planetoid(root='data/Cora', name='Cora') # 加载数据集data = dataset[0].to(device) # 获取图数据对象并送到设备# 2. 获取图的基本信息in_channels = dataset.num_node_featuresout_channels = dataset.num_classes# 3. 实例化模型、损失函数和优化器model = GCN(...).to(device)loss_fcn = torch.nn.CrossEntropyLoss()optimizer = torch.optim.Adam(...)# 4. 开始训练循环for epoch in range(n_epochs):model.train() # 切换到训练模式 (开启 dropout)# 前向传播:计算模型对所有节点的预测得分logits = model(data.x, data.edge_index)# 计算损失:只用训练集节点来计算!# 比较模型在训练节点上的预测得分,和它们的真实标签有多大差距loss = loss_fcn(logits[data.train_mask], data.y[data.train_mask])# 反向传播和优化optimizer.zero_grad() # 清空上一轮的梯度loss.backward()       # 根据损失计算梯度optimizer.step()      # 根据梯度更新模型参数(权重)# 打印当前周期的损失和在验证集上的准确率val_acc = evaluate(model, data, data.val_mask)print(...)# 5. 训练结束后,在测试集上报告最终性能test_acc = evaluate(model, data, data.test_mask)print(...)
  • Epoch: 指的是把整个训练数据集过一遍。这里我们循环 n_epochs 次。
  • 损失函数 (CrossEntropyLoss): 它衡量的是“预测得分”和“真实标签”之间的差距。差距越大,损失值越高。
  • 优化器 (Adam): 它的工作就是根据损失值,微调模型中所有 GCNConv 层的参数(权重),目标是让下一轮的损失变得更小。
  • 训练的核心三步曲:
    1. loss.backward(): 计算出每个参数应该朝哪个方向调整才能让损失变小。
    2. optimizer.step(): 执行这个调整。
    3. optimizer.zero_grad(): 清理现场,为下一次计算做准备。

3.4 总结:整个故事线

  1. 加载数据:拿到 Cora 这个论文引用网络,包含节点(论文)、边(引用关系)、特征(论文内容向量)和一小部分已知标签。
  2. 建立模型:设计一个多层的 GCN 模型。每一层都是一个 GCNConv,负责执行“邻居信息汇总”。
  3. 开始训练循环
    • 预测:模型对所有节点进行一次预测,得到每个节点属于各个类别的分数(logits)。
    • 算账:只看训练集的节点,用 CrossEntropyLoss 计算预测分数和真实标签的差距(loss)。
    • 调优Adam 优化器根据这个差距,微调模型里所有层的参数,争取下次预测得更准。
    • 验证:在验证集上跑一下,看看模型在新数据上的表现如何,防止过拟合。
  4. 循环往复:重复第3步很多次(比如100个 epoch)。
  5. 最终测试:训练结束后,把模型在从未见过的测试集上跑一次,得到最终的准确率,作为模型的最终成绩。

最终结果运行:

在这里插入图片描述

http://www.dtcms.com/a/326632.html

相关文章:

  • 【LeetCode刷题集】--排序(三)
  • Protocol Buffers (protobuf) API 接口完全指南
  • maven项目打包成sdk后在别的项目使用
  • 从0开始的中后台管理系统-5(部门管理以及菜单管理页面功能实现)
  • 【科研绘图系列】R语言绘制散点图折线图误差棒组合图
  • 指派问题-匈牙利算法
  • 2025牛客多校第八场 根号-2进制 个人题解
  • HTTPS应用层协议-CA签名与证书
  • Vue 3 快速入门 第六章
  • MaixPy简介
  • Projects
  • 进程管理是什么
  • DeepSeek生成的高精度大数计算器
  • 自制网页并爬取,处理异常值(第十九节课内容总结)
  • .NET/C# webapi框架下给swagger的api文档中显示注释(可下载源码)
  • MP3Tag 软件功能简介
  • (二)vscode搭建espidf环境,配置wsl2
  • 第16届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2025年4月真题
  • 进阶版|企业级 AI Agent 的构建实践
  • 【03】厦门立林科技——立林科技 嵌入式 校招笔试,题目记录及解析
  • 从零开始的ReAct Agent尝试
  • 应用监控工具Skywalking
  • bitbake –s:列出所有可编译的模块
  • 【STL】queue队列容器
  • priority_queue(优先级队列)和仿函数
  • ArkUI中的自定义组件(一)
  • 用于计算的程序是部署在哪里,为什么可以这样?
  • 从 WebView2 迁移至 DotNetBrowser:第一部分
  • android 换肤框架详解2-LayoutInflater源码解析
  • 《零基础入门AI:深度学习基础核心概念解析(从激活函数到反向传播)》