简介基于GNN图神经网络预测的Python完整源码数据包面向图神经网络学习者和研究人员覆盖从数据预处理、模型构建到预测结果复现的完整流程。资源共32个文件包含19个Python脚本、4个Jupyter Notebook、4个npz数据文件及配置说明、模型示意图、许可文件等压缩包仅8.34MB结构清晰。Python脚本主要实现数据加载、图模型定义与训练逻辑Notebook则提供可直接运行的演示与复现入口兼顾学习与二次开发npz文件为实验用图数据开箱即用配以说明文档和目录组织易于嵌入现有项目。包内集成PPNP等经典GNN模型同时提供PyTorch与TensorFlow两个版本的实现适合不同框架偏好的用户并附有模型示意图辅助理解网络结构。目前已有2252人学习下载对想快速上手图神经网络预测的初学者和需要参考完整源码的研究者都很有价值。1. 图神经网络预测为什么“关系”比“特征”更值钱做表格数据预测时大多数人默认每行样本是独立的。但实际业务里用户和用户之间有社交关系设备和设备之间有调用链分子和分子之间有共价键——把这些关系丢掉等于把最值钱的信息扔在门口。GNN图神经网络干的事情就是把这些“关系”直接编码进预测过程节点自己长什么样、它周围邻居是谁、邻居之间怎么连接三件事一起决定最终预测结果。这个方向不是赶时髦而是当数据本身自带连接结构时传统机器学习模型基本都会输给GNN。本文从消息传递原理讲起落到环境搭建、数据准备、训练调参和避坑记录带你把这个Python完整源码数据包真正跑通并用在自己业务上。适合刚接触图神经网络、想用Python快速落地节点分类或边预测的工程师。2. 消息传递机制GNN到底在算什么最小模型怎么搭2.1 消息传递GNN唯一的核心运算GNN的理论基础可以浓缩成一句话每一个节点通过不断聚合邻居的信息来更新自己的表示。这个操作叫消息传递Message Passing所有GNN变体不管是GCN、GAT还是GraphSAGE本质都在做同一件事邻居节点把自己算好的向量发给中心节点中心节点拿这些向量做加权求和再经过一次非线性变换得到自己的新向量。用一段不依赖任何深度学习框架的代码来看最直观import numpy as np def gcn_layer(A_hat, X, W): 一层GCN的前向传播 A_hat: 加了自环并对称归一化后的邻接矩阵形状 (N, N) X: 节点特征矩阵形状 (N, D) W: 本层可训练权重形状 (D, F) 返回: 更新后的节点表示形状 (N, F) return A_hat X W这段代码里A_hat X就是在做消息聚合A_hat的每一行记录了当前节点应该以多大权重去拿邻居的特征乘完之后每个节点都拿到了“自己和邻居的加权特征和”。接着再乘W做一次线性变换这就是一层GCN的全部内容。逻辑上你可以把它理解成“把邻居信息汇总到一起再统一过一次线性层”。这里有个细节值得注意A_hat不是原始邻接矩阵必须做归一化。常见做法是对称归一化公式是D^(-1/2) A D^(-1/2)代码实现如下def normalize_adjacency(A): 对邻接矩阵做对称归一化防止节点度过高导致数值不稳定 A A np.eye(A.shape[0]) # 加自环让节点保留自身信息 degree np.sum(A, axis1) # 节点的度数 degree_inv_sqrt np.power(degree, -0.5) degree_inv_sqrt[np.isinf(degree_inv_sqrt)] 0.0 D_inv_sqrt np.diag(degree_inv_sqrt) return D_inv_sqrt A D_inv_sqrt为什么必须加自环因为如果不加中心节点在聚合时只能拿到邻居的信息自己的原始特征反而丢了这会导致预测效果明显变差。为什么必须归一化因为度数高的节点比如微博大V聚合了大量邻居信息度数低的节点只有零星几个邻居两者聚合后的向量尺度完全不同送入后续网络时会造成训练不稳定。2.2 主流GNN变体怎么选GCN、GAT、GraphSAGE理解消息传递之后选型就有了依据。三种最常用的GNN变体差别只在聚合邻居时的“加权方式”上。GCN图卷积网络的权重是固定的完全由图的拓扑结构决定度数高的节点邻居贡献被稀释度数低的节点邻居贡献被放大。优点是计算效率高、参数少适合图结构相对规整、没有复杂权重的场景。缺点也很明显——所有邻居一视同仁不管它和你是不是同类。GAT图注意力网络把固定权重换成了注意力机制模型自己学习每个邻居的重要性权重。我在做社交网络用户分类时GAT通常比GCN高3到5个点因为现实中确实是“某些邻居比另一些邻居更重要”。代价是训练更慢、显存消耗更大。GraphSAGE则走了另一条路不聚合全部邻居而是每层随机采样固定数量比如25个的邻居再做聚合。好处是天然支持大规模图和 inductive 场景——新节点来了不用重新训练整个图可以只算它周围邻居的表示。如果你的图有上千万节点GraphSAGE是稳妥起点。一个实际的选型建议是第一版用GCN把流程跑通建立基线如果效果不够再换GAT和GraphSAGE对比。不要一上来就在三四个模型之间反复横跳先跑通再优化。3. 环境搭建与源码数据包落地从Python装到跑通第一个脚本3.1 环境准备Python版本、PyTorch和DGL的兼容组合拿到这个Python完整源码数据包第一步永远是配环境。GNN的生态里主流的图深度学习库是DGL和PyTorch Geometric配合PyTorch使用。其中被最多项目采用、踩坑成本最低的组合是Python 3.9 PyTorch 1.13/2.0 DGL 1.x。我一般不建议一上来就装Python 3.12因为部分依赖库的预编译轮子还没跟上容易在编译环节浪费时间。推荐在虚拟环境里安装避免污染系统Pythonconda create -n gnn python3.9 -y conda activate gnn pip install torch --index-url https://download.pytorch.org/whl/cu118 pip install dgl这里conda create指定Python版本为3.9是为了让后续依赖库有稳定的二进制包支持。安装PyTorch时--index-url指定CUDA 11.8版本的安装源如果你的机器只有CPU把这个地址去掉安装CPU版即可。DGL的CPU版和GPU版在代码层面没有区别安装GPU版只是为了训练加速。版本兼容是这边最常见的翻车点。PyTorch 2.0发布后旧版本的DGL0.9.x直接无法导入最新PyTorch报错信息是undefined symbol或者OSError: libtorch_python.so。踩过这个坑之后我的习惯是先确定PyTorch版本再安装与之匹配的DGL。在bash里执行下面两行花一分钟确认版本python -c import torch; print(torch.__version__, torch.version.cuda) python -c import dgl; print(dgl.__version__)3.2 数据包目录结构典型的GNN项目长什么样解压数据包之后先别急着跑train.py。把目录结构看清楚比什么都重要。一个标准、可以直接上手的GNN项目目录一般长这样project_root/ ├── README.md # 项目说明和环境安装步骤 ├── requirements.txt # 依赖清单用 pip install -r 一键安装 ├── data/ # 原始数据和预处理脚本 │ ├── raw/ # 原始数据集比如CSV或JSON │ └── preprocess.py # 数据清洗和图构建脚本 ├── models/ # 模型定义 │ ├── gcn.py # GCN模型类 │ ├── gat.py # 注意力网络模型类 │ └── sage.py # GraphSAGE模型类 ├── training/ │ ├── train.py # 训练主脚本 │ └── evaluate.py # 评估脚本 └── utils/ ├── graph_builder.py # 从表格数据构建DGL图 └── metrics.py # 评估指标比如准确率、F1拿到数据包后先看README.md确认它要求哪一版Python和DGL再pip install -r requirements.txt。只看models/目录下的文件你就能判断这个项目的模型实现用的是DGL还是PyTorch Geometric以及支持哪几种图神经网络变体。3.3 跑通第一个训练脚本最小命令和参数说明数据包到手后最快的验证方式是用内置的小规模数据集跑一个最小的训练命令。以节点分类任务为例典型命令如下python training/train.py \ --dataset cora \ --model gcn \ --hidden-dim 16 \ --num-layers 2 \ --epochs 200--dataset cora指定Cora数据集GNN界最经典的论文引用数据集2708篇论文7个类别--model gcn选择GCN模型--hidden-dim表示隐藏层维度--num-layers是图神经网络层数--epochs是训练轮数。跑通之后你会在终端里看到每一轮的loss和验证集准确率。Cora这个规模下Epoch 50左右验证准确率应该稳定在76%到82%之间。如果你看到准确率一直在个位数打转或者loss不下降先别怀疑模型——大概率是环境或数据处理出了问题后面第5章会列出最常见的几个坑。4. 从原始数据到图结构邻接矩阵、特征矩阵与标签划分的实操步骤4.1 把表格数据变成图三种常见数据形态和对应构建方式现实项目里很少有现成的图数据给你用。多数情况是手里有几张数据库导出的表需要自己把它们组织成图。下面三种形态最典型覆盖了绝大多数业务场景。第一种形态是“节点表 边表”分离。节点表记录每个实体的ID和特征边表记录实体间的关系。这种结构是GNN最友好的输入直接可以喂给DGL或PyGimport pandas as pd import dgl import torch # ids.csv: 用户ID列和对应的特征列 # edges.csv: 两列表示用户A关注了用户B nodes pd.read_csv(data/raw/nodes.csv) edges pd.read_csv(data/raw/edges.csv) # 构建无向图节点ID需要做连续整数映射 node_ids nodes[id].unique() id_map {old: new for new, old in enumerate(node_ids)} src edges[src].map(id_map).values dst edges[dst].map(id_map).values g dgl.graph((torch.tensor(src), torch.tensor(dst))) g.ndata[feat] torch.tensor(nodes.drop(columns[id]).values, dtypetorch.float32)这里dgl.graph接收一个元组分别是源节点ID数组和目标节点ID数组。因为DGL要求节点ID必须是从0开始的连续整数所以必须先用id_map把原始的业务ID映射成整数索引。特征矩阵通过g.ndata[feat]挂到图上后续模型直接用g.ndata[feat]取特征。这是一个高效且不容易出错的图构建流程。第二种形态是“从高维稀疏关系矩阵重建图”。比如电商平台的共现数据或者文本数据的词共现矩阵本身是一个N乘N的稀疏矩阵。直接用scipy.sparse读取然后用dgl.from_scipy转为DGL图from scipy import sparse import dgl adj sparse.load_npz(data/raw/co_occurrence.npz) g dgl.from_scipy(adj)第三种形态是“从连续特征计算KNN图”。数据里没有显式的边关系只有每个样本的连续特征。常见做法是计算样本间的余弦相似度或欧氏距离取每个样本最相似的K个样本建边。我处理用户行为画像数据时经常走这条路用sklearn找K近邻from sklearn.neighbors import kneighbors_graph feat nodes.drop(columns[id]).values adj_matrix kneighbors_graph(feat, n_neighbors10, modeconnectivity) g dgl.from_scipy(adj_matrix)kneighbors_graph里n_neighbors10是邻居数量modeconnectivity表示只保留连接关系不计算权重。K的选择直接影响图的质量——K太小图会分裂成多个不连通小团K太大图会变成一锅粥所有节点都连在一起消息传递退化成了全局池化。一般可以先在15到30之间试探结合下游任务的验证集效果来定。4.2 邻接矩阵的归一化GCN能不能收敛的胜负手原始邻接矩阵直接作为输入GCN大概率会在前几个epoch就产生梯度爆炸或梯度消失。原因是节点的特征经过连乘之后数值尺度会迅速变大。必须对邻接矩阵做归一化。前面第2章里已经给出了对称归一化的NumPy实现工程上DGL有内置API可以直接调用不用自己造轮子import dgl.function as fn g dgl.add_self_loop(g) # 加自环保留节点自身信息 g dgl.add_self_loop(g).local_var() g.update_all(fn.copy_u(h, m), fn.sum(m, h))实际操作中用dgl.nn.GraphConv会帮你自动处理归一化逻辑不需要手动加自环或归一化。但如果你的模型是自定义消息传递层这一步千万不能漏。判断独立实现的对错有一个简单方法对比手动实现和dgl.nn.GraphConv在相同数据上的前向输出数值应该基本一致浮点误差范围内。4.3 标签划分训练/验证/测试的切分方式决定了模型是否过拟合GNN的标签划分和普通监督学习最大的区别是不能简单随机打乱。如果训练集和验证集共享了大量边比如同一社区的节点一部分在训练集、一部分在验证集验证指标会虚高因为你已经在训练时“见过”验证集节点的所有邻居信息。这在工程上叫信息泄露会让你的模型上线后表现远不如实验时那么好看。三种主流划分策略供参考划分策略做法适用场景随机划分对节点ID随机打乱按比例切分图结构相对稀疏边对预测影响弱按社区/连通分量划分先跑社区发现算法再把整个社区分到同一集合社交网络、推荐场景按时间划分用前70%时间的边训练后30%预测动态图、时序预测场景具体实现上随机划分用torch.randperm或sklearn.train_test_split就够了。而按社区划分可以用dgl.metis_partition把图切成多个子图再把切出来的子图分给训练和验证num_parts 10 parts dgl.metis_partition(g, num_parts) part_ids torch.arange(num_parts) train_part part_ids[:7] # 7个社区用于训练 val_part part_ids[7:] # 3个社区用于验证 train_nodes torch.cat([parts[i].ndata[dgl.NID] for i in train_part]) val_nodes torch.cat([parts[i].ndata[dgl.NID] for i in val_part])dgl.metis_partition返回一个字典键是分区的编号值是对应的子图。每个子图的ndata[dgl.NID]保存了原始节点的全局ID把它们收集起来就是该分区包含的所有节点。用这种方式切分训练集和验证集之间的跨集边会大幅减少指标更接近真实上线水平。4.4 特征工程的三个方向原始特征不够时还能从哪里找特征图神经网络的输入特征是节点级的向量。如果你的业务数据只有ID和类别没有数值特征直接输入模型的效果会很差。三个行之有效的方向第一入度、出度、PageRank等图结构特征。这些特征描述了节点在图中的结构性位置实现起来成本极低用NetworkX或DGL几行就能算出来但往往对预测效果提升明显。第二节点embedding——比如用Node2Vec、DeepWalk等图嵌入方法预先跑一遍把每个节点映射成低维稠密向量作为GNN输入特征的一部分。这个方案特别好用先用无监督方法把图的结构信息压缩成向量再交给GNN去做有监督预测两种方法的优势互补。第三如果每个节点本身有一段文本比如论文摘要或商品描述可以做一次文本向量化用预训练模型或TF-IDF转成固定维度向量作为附加特征。文本特征和图结构特征一起入模通常比单独使用其中一种效果更好。5. 避坑记录GNN预测项目里最容易翻车的五个细节与排查方式5.1 边方向没对齐构建无向图时漏了反向边现象模型训练时loss下降极快但验证集准确率非常低或者模型完全没有任何预测能力。原因关系数据通常只记了一对节点的一次方向。比如“用户A关注了用户B”原始数据里只会有一行记录。如果你直接用这行记录建图DGL建出来的是有向图消息只能从A传到B不能回来。很多GNN模型比如GCN默认在无向图上做对称聚合方向不一致就导致信息流不通畅。解决在构建图时显式加入反向边同时去掉重复边再做一次规范化import dgl import torch src torch.tensor(src) dst torch.tensor(dst) g dgl.graph((torch.cat([src, dst]), torch.cat([dst, src]))) g dgl.to_simple(g) # 去掉重复边 g dgl.add_self_loop(g)用torch.cat把src和dst对调后拼接一次得到的就是无向图的双向边集合。dgl.to_simple会自动合并重复边并把多重的边的权重统计好。处理完可以打印g.number_of_edges()验证边数大约是原始记录数的两倍左右。5.2 节点度数分布极不均衡导致预测结果偏向大v节点现象模型在验证集上的Accuracy看起来不错但按节点度数分组去看度数低的节点预测准确率惨不忍睹。原因图数据普遍存在幂律分布——只有少数节点拥有海量邻居大量节点只有三五个邻居。高集聚类了海量信息模型可以轻松从“邻居”里获得预测信号低度节点的邻居信息太少模型最终只能靠自身特征硬猜。解决第一个手段是按度分桶统计指标不要只看整体Accuracyimport torch pred torch.argmax(logits, dim1) labels g.ndata[label] degree g.in_degrees() for low, high in [(0, 10), (10, 50), (50, float(inf))]: mask (degree low) (degree high) acc (pred[mask] labels[mask]).float().mean() print(fdegree [{low:4}, {high:5}): accuracy {acc:.4f})代码里g.in_degrees()得到每个节点的入度按区间分桶计算各自准确率一眼看出模型对低度节点的表现。如果确实差太多改用GraphSAGE——它通过采样限制高Degree节点的信息无限制扩增对低度节点更友好。5.3 随机划分数据导致标签信息泄漏现象训练集准确率不错验证集准确率也很高线上真实场景效果远不如实验——这是典型的信息泄露特征。但这里的泄漏发生在数据划分阶段不是特征工程。原因训练和验证节点之间仍然存在大量边GNN的消息传递会通过边把验证节点的标签信息经过训练集节点的中介带进训练过程。这在半监督场景下尤其明显——GNN天然会用到图中所有节点的特征信息。解决使用按社区划分或按时间划分。按照第4.3节的做法用dgl.metis_partition把图切成训练、验证、测试三个子图让三个集合之间的边尽可能少。调整后验证集指标可能会掉3到5个点——不要慌这是去掉信息泄漏之后的真实水平线上效果反而会更可信。5.4 层数超过3层后性能不升反降过平滑问题的典型特征现象把模型层数从2层加到4层、6层训练loss降了但验证集准确率越来越差。直观上看是“过拟合”但即便加了Dropout和Weight Decay也没有改善。原因GNN的理论困境是“过平滑”。深层消息传递会让所有节点的表示趋于一致——每个节点反复聚合自己邻居和邻居的邻居最终和整个图上的均值向量趋同。浅层GCN1到2层能最好地捕捉局部局部结构层数越深全局均质化越严重。解决不要盲目加深网络。如果确实需要更大感受野比如要用到2跳以外的邻居信息优先调整扩展邻居范围用更大的邻接矩阵幂次或者给每一层加残差连接。最简单可靠的工程做法是深度固定为2到3层把主要调参精力放在隐藏层宽度hidden_dim和Dropout比例上。经验数据是Cora数据集上2层GCN的上限在80%附近加层数只会更差。5.5 DGL和PyTorch版本不匹配引发的运行时崩溃现象import dgl正常但训练过程中一执行dgl.graph就报错错误堆栈指向torch.ops相关的内容提示OSError或undefined symbol。原因DGL 0.9.x和PyTorch 2.0之间的二进制接口发生了变动旧版DGL编译时链接的是旧版PyTorch符号新版PyTorch里这些符号被移除运行到图构建操作时直接崩溃。解决这是环境问题不是代码问题解决方式很直接——检查当前环境中两者的版本把DGL升级到1.1以上或者把PyTorch降到与已有DGL兼容的版本pip install dgl -U python -c import dgl; print(dgl.__version__)如果项目代码还依赖老版本的DGL API比如dgl.udf或某些旧版函数升级后会出现接口弃用警告。处理原则是优先用新版DGL改掉弃用调用而不是回退到旧版PyTorch——后续无论装新依赖还是做GPU迁移新版都省心很多。6. 一个可靠的调参顺序先固定Seed再验证超参敏感性GNN项目真正花时间的不是写模型而是让超参数在一个可复现的基准上调起来。我习惯先规定一个“黄金Seed”——通常是42。在训练脚本开头固定随机种子让同一套参数每次跑出来的结果基本一致才有资格谈调参import random import numpy as np import torch seed 42 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)固定完随机种子后再逐一考察对最终结果影响最显著的四组超参数。按经验排序学习率 隐藏层维度 Dropout比例 权重衰减。下面的表格是从实际调参中得到的一组典型敏感性参考超参数低值表现适中值推荐高值风险学习率0.0005时收敛慢150轮后才进入平台期0.005到0.010.05以上loss震荡剧烈甚至发散隐藏层维度8维时表征能力不足准确率低约8个点16至32维64维以上提升有限显存和训练时间翻倍Dropout0时训练集准确率极高、验证集低过拟合0.5至0.60.8以上模型欠拟合验证集也掉点权重衰减0时训练后期验证集准确率波动5e-4至1e-31e-2以上模型过于平滑预训练风格退化验证方法上有明确的区分超参数选择看验证集最终汇报看测试集。训练过程中每个Epoch打印验证集指标选验证集最好的模型保存权重最后再用测试集做一次汇报。不要拿测试集反复调参——调上三次测试集的指标就没参考价值了。GNN的调参和普通深度模型不一样的地方在于它极度依赖“图”本身的构建质量。一个好的邻接矩阵顶得上几十次超参数搜索。我自己的习惯是调参前先花时间做特征工程和图的规范化研究邻接矩阵的度分布确认训练/验证节点的划分没有泄漏再去动模型和学习率——顺序反了用再精细的调参也是白费力气。这条路我踩了不少坑希望帮到你。本文还有配套的精品资源点击获取