图卷积网络GCN交通流量预测实战:PyTorch实现与调参指南

发布时间:2026/10/10 5:07:35
图卷积网络GCN交通流量预测实战:PyTorch实现与调参指南
简介面向智能交通、深度学习与图神经网络方向的研究者和开发者这份 PDF 系统阐述基于图卷积神经网络GCN的交通流量预测方法。内容涵盖 GCN 基本原理、谱图方法建模城市道路网络、提取拓扑结构与时空特征并介绍车流量与车速预测的对比实验。实验表明该方法优于传统统计模型及部分深度学习方法适合作为课程设计、毕业设计或论文研读的参考材料。资源为单个 PDF 文件大小仅 1.18MB携带方便。文件包含论文原文、公式推导、实验图表和参考文献可完整看到从图结构建模、邻居信息聚合到结果验证的技术路线对于希望快速了解 GCN 在非欧几里得路网数据上如何应用的读者是一份高密度的入门和复现参考资料。目前已有 319 人学习下载。1. 图卷积凭什么比普通卷积更适合交通流量预测一张路网图就是最好的答案城市路网本质上是图结构——交叉路口是节点道路是连边几百个传感器散落在路网上记录着每个位置的流量、速度和密度。图卷积神经网络GCN把卷积运算从规则的二维网格搬到不规则的图上让模型真正理解“邻近路口的拥堵会沿着道路传播”这个基本事实。比起传统的 CNN、LSTM 一类方法GCN 天然支持任意拓扑结构对新增路口和缺失传感器也更宽容正是交通流量预测这类时空任务的主流解法。这篇文章把整个落地路径拆开讲从路网拓扑怎么变成图数据开始到 GCN 模型在 PyTorch 里的最少实现再到训练参数怎么调、数据坑在哪最后是验证模型效果的正确姿势。无论你是刚接触图神经网络的研究生还是想把它跑在真实路网数据上的工程师照着这套思路都能在本地把基线模型跑通再逐步加自己的改进。2. 路网数据怎么变成图邻接矩阵、特征矩阵和滑动窗口2.1 邻接矩阵用 0 和 1 表达“哪条路和哪条路连着”图神经网络和普通神经网络最本质的区别就在于输入里多了一张“连接关系表”——邻接矩阵 A。A 的大小是 N×NN 是传感器节点的数量。A 的第 i 行第 j 列表示节点 i 和节点 j 是否直接相连1 代表相连0 代表不相连。交通流量预测里最常用的建图方式是基于路网距离两个传感器之间的距离小于阈值常见做法是取所有距离的均值或 80% 分位数就认为它们相连。import numpy as np from scipy.spatial.distance import cdist # 假设 sensors 是一个 N×2 的数组每行是传感器的经纬度坐标 # 步骤1计算所有传感器两两之间的距离矩阵 dist_matrix cdist(sensors, sensors, metriceuclidean) # 步骤2按距离阈值建邻接矩阵阈值取距离的均值 threshold np.mean(dist_matrix) adj_mx (dist_matrix threshold).astype(int) # 步骤3自己到自己的连接不算对角线置0 np.fill_diagonal(adj_mx, 0) # 步骤4删除没有任何邻居的孤立节点否则 GCN 的消息传递会失效 valid_idx np.where(adj_mx.sum(axis1) 0)[0] adj_mx adj_mx[np.ix_(valid_idx, valid_idx)]这段代码的关键在步骤 2 的阈值。阈值太小图会碎成很多互不相连的小块信息传不出去阈值太大图变成全连接GCN 退化成全局平均池化空间局部性就没了。我一般先把距离矩阵的直方图画出来看分布集中在哪再取 75% 到 85% 分位数而不是无脑用均值。步骤 4 是血泪经验——有些传感器设备装了但数据一直为空或位置记录有问题导致距离异常大不删掉它们模型在训练时会对这些孤立节点产生 NaN 梯度。2.2 特征矩阵把流量、速度和占有率装进一个三维张量建好图结构之后要准备每个节点在每个时刻的特征。交通流量预测常见的特征组合是流量veh/h、平均速度km/h和占有率%。这三个量一起给比单用流量效果好得多因为流量稀疏时段波动很大速度反而稳定模型能从多模态信号里学到互补的信息。特征矩阵的常见组织方式是 shape (样本数, 节点数, 特征维度, 时间步长)。PyTorch 里更习惯用 (B, N, F, T) 或 (B, T, N, F) 两种排法我推荐后者因为和 LSTM、GRU 这类时序模型的输入格式更贴近后续接时序模块时不需要频繁做 permute。# 假设 raw_data 的 shape 是 (N, T_total, F)N 是节点数T_total 是总时间步F 是特征数这里为3 # 先做归一化再切滑窗 mean raw_data.mean(axis(0, 1), keepdimsTrue) std raw_data.std(axis(0, 1), keepdimsTrue) 1e-6 data_norm (raw_data - mean) / std def create_samples(data, input_steps12, pred_steps3): N, T_total, F data.shape X, Y [], [] for t in range(T_total - input_steps - pred_steps): x data[:, t:t input_steps, :] # 过去12个时间步 y data[:, t input_steps:t input_steps pred_steps, 0] # 预测未来3步的流量 X.append(x.transpose(1, 0, 2)) # 转成 (input_steps, N, F) Y.append(y.transpose(1, 0)) # 转成 (pred_steps, N) return np.array(X), np.array(Y)这里的 X 转成了 (T, N, F) 而不是 (N, T, F)原因是后面要接 GRU——PyTorch 的 GRU 要求输入是 (seq_len, batch, input_size)把时间步长放在第一维可以直接喂给 GRU 处理时间依赖GCN 再在节点维度上做空间聚合。pred_steps 取 3 对应预测未来 15 分钟假设时间间隔为 5 分钟这是交通流量预测最常见的多步预测配置。如果要预测 30 分钟或 1 小时直接把 pred_steps 调大但误差会明显上涨这是交通流本身的不可预测性决定的。2.3 归一化的两个层级全局归一化还是按节点归一化很多人在归一化这里翻车。交通流量数据有很强的周期性同一路口的工作日早高峰车流可能是凌晨的 20 倍不同路口之间的绝对数值差异也很大——主干道流量普遍在 5000支路可能就 500。如果用全局均值做归一化主干路和支路会被压缩到完全不同的数值区间GCN 做消息传递时支路的特征会被主干路的信息淹没。我一般按节点做归一化也就是对每个传感器单独计算均值和标准差而不是所有节点共用一个。这样每个节点的流量都落在差不多的数值范围内模型才能平等对待所有路口的信息。# 按节点归一化 (N, T, F) node_mean raw_data.mean(axis1, keepdimsTrue) # shape (N, 1, F) node_std raw_data.std(axis1, keepdimsTrue) 1e-6 data_norm (raw_data - node_mean) / node_std按节点归一化的代价是预测出来的值要乘回节点自己的标准差再加均值才能得到实际流量——反归一化时千万别用错节点否则预测曲线和真实曲线对不上画出来怎么看怎么别扭。这是个只在验证阶段才暴露出来的坑后面避坑那一章专门写。3. 用 PyTorch 实现一个 GCNGRU 的交通流量预测模型3.1 GCN 层消息传递就是邻居特征的加权求和GCN 层的核心思想非常简单把每个节点自己的特征和邻居节点的特征加权求和再经过一次线性变换得到新的节点表示。公式可以简化为 H σ(D̂⁻¹ᐟ² Â D̂⁻¹ᐟ² H W)其中 Â A I 是加了自环的邻接矩阵让节点聚合时包含自己D̂ 是 Â 的度矩阵每个节点的邻居数量左右乘 D̂⁻¹ᐟ² 相当于对邻居数量多的节点做归一化防止图里节点的度分布不均衡导致数值不稳定。这段公式看起来抽象落实到代码里其实很直接。关键是提前算好归一化后的邻接矩阵不要每次训练都重新计算。import torch import torch.nn as nn class GCNLayer(nn.Module): def __init__(self, in_features, out_features): super().__init__() self.linear nn.Linear(in_features, out_features) def forward(self, x, adj_norm): # x: (B, N, F_in) # adj_norm: (N, N)对称归一化后的邻接矩阵 support self.linear(x) # (B, N, F_out) out torch.einsum(bnf,nm-bmf, support, adj_norm) return outtorch.einsum(bnf,nm-bmf, support, adj_norm)的意思就是把 support 里每个节点的特征按 adj_norm 的权重加权到它的邻居节点上。这一步是 GCN 的全部核心。很多实现会把这一层包得特别花哨搞一堆 Dropout、残差连接、BN但模型的泛化能力其实主要来自数据质量、邻接矩阵的正确性和合理的训练方式层结构堆花哨帮助有限。3.2 对称归一化必须提前计算好且只有一种正确写法GCN 的邻接矩阵归一化有两种常见写法随机游走归一化和对称归一化。交通流量预测里我推荐对称归一化原因是它能保持邻接矩阵的对称性——流量传播在路网里应该是双向对称的除非是单行道对称归一化可以让模型不对节点顺序产生偏差。def symmetric_normalize(adj): 对称归一化 D^-0.5 * A * D^-0.5 adj adj np.eye(adj.shape[0]) # 加自环 d np.sum(adj, axis1) # 度向量 d_inv_sqrt np.power(d, -0.5) d_inv_sqrt[np.isinf(d_inv_sqrt)] 0.0 # 保护孤立节点 d_mat np.diag(d_inv_sqrt) adj_norm d_mat adj d_mat return torch.FloatTensor(adj_norm)一个极其隐蔽的坑是度矩阵 D 必须在加自环之后计算而不是先算原图的度再加 1。数学上讲如果在加自环之前算度那么自环贡献的权重会被错误归一化导致模型对自己特征的重视程度偏大对邻居特征的传播权重偏低。线上跑出来的效果就是模型总是倾向于保持上一时刻的趋势对突变反应迟缓看着像“惯性太大”。这个问题不对比实验基本发现不了很少有人会怀疑是归一化写错了。3.3 完整模型结构两层 GCN 捕捉空间依赖GRU 处理时间序列空间和时间是交织在一起的。GCN 负责捕捉同一时间步上节点之间的空间相关性——比如东四环堵车西四环 20 分钟后也会受影响。GRU 负责捕捉单节点的时间演化——比如一个路口早高峰的流量曲线是平滑变化的。把两者串起来是 STGCN、ASTGCN 这一类时空同步网络的通用做法。import torch.nn.functional as F class GCN_GRU(nn.Module): def __init__(self, n_nodes, in_features, hidden_dim, out_steps): super().__init__() self.gcn1 GCNLayer(in_features, hidden_dim) self.gcn2 GCNLayer(hidden_dim, hidden_dim) self.gru nn.GRU(hidden_dim, hidden_dim, batch_firstTrue) self.regressor nn.Linear(hidden_dim, 1) def forward(self, x, adj_norm): # x: (B, T_in, N, F_in) B, T, N, F x.shape x x.reshape(B * T, N, F) h F.relu(self.gcn1(x, adj_norm)) h F.relu(self.gcn2(h, adj_norm)) h h.reshape(B, T, N, -1) # 把节点特征展平成时间序列特征喂给GRU h h.permute(0, 2, 1, 3).reshape(B * N, T, -1) out, _ self.gru(h) # (B*N, T, hidden) # 取最后一个时间步做多步回归 out self.regressor(out[:, -1, :]) # (B*N, 1) out out.reshape(B, N, 1) out out.repeat(1, 1, 1) # single-step output return out这个模型的 forward 流程可以拆成四段第一段把 (B, T, N, F) 压成 (B*T, N, F)这样 GCN 可以一次性处理所有时间步的图卷积效率更高第二段经过两层 GCN 实现空间传播第三段把节点维度放到序列维度上让 GRU 对每个节点的特征沿时间轴建模第四段取 GRU 最后一个时间步的输出接一层线性层回归流量值。注意这里为了演示清晰模型只输出未来一个时间步实际做多步预测时常见的做法是加一个 seq2seq 架构或者直接让输出维度等于 pred_steps。3.4 训练一个最小可用模型损失函数、优化器和完整的训练循环主流的交通流量预测基线一般用 MAE 或 MSE 作为损失函数。MSE 收敛快但对峰值流量惩罚过重导致模型学得保守MAE 对异常值更鲁棒但梯度方向恒定收敛慢。我习惯用 MAE 一个非常小的 MSE 权重做混合损失既让模型关注绝对误差又保留一点梯度变化效果比单用任何一个稳定。model GCN_GRU(n_nodesN, in_features3, hidden_dim32, out_steps3) optimizer torch.optim.Adam(model.parameters(), lr0.001, weight_decay1e-5) loss_fn lambda pred, y: torch.abs(pred - y).mean() 0.1 * ((pred - y) ** 2).mean() for epoch in range(50): model.train() total_loss 0 for x_batch, y_batch in train_loader: # x_batch: (B, T_in, N, F), y_batch: (B, N) optimizer.zero_grad() pred model(x_batch, adj_norm) loss loss_fn(pred.squeeze(-1), y_batch) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step() total_loss loss.item() if (epoch 1) % 10 0: print(fEpoch {epoch1}/{50}, Loss: {total_loss / len(train_loader):.4f})这里面最容易被忽略的是clip_grad_norm_。GCN 的消息传递会让梯度在邻居节点之间反复传播深层模型或多层 GCN 堆叠时梯度很容易在节点间累积而爆炸。clip 的半径设 5.0 是我常用的值数据尺度不同可以调但每次都记得加。训练轮次设为 50 并不够模型完全收敛交通数据往往要 150-300 轮才稳定我先跑 50 轮看 loss 是否快速下降快速下降说明代码没问题再放开到全量训练。4. 训练与调参数据划分、学习率和训练轮次怎么定4.1 时间上的数据划分不能随机打乱只能按时间切交通流量数据是典型的时间序列划分训练集和测试集时最忌讳随机 shuffle。随机打乱会引入未来信息泄漏——训练集里的某一天凌晨数据可能紧挨着测试集的某天早高峰数据模型其实已经“见过”了测试集的趋势这在真实部署时完全不可能发生。正确做法是按时间顺序切分常见的比例是 7:1.5:1.5——前 70% 做训练接下来 15% 做验证最后 15% 做测试。验证集的作用是选模型、调超参测试集只在全部定稿后跑一次否则你就是在拿测试集调参得到的指标虚高。train_ratio, val_ratio 0.7, 0.15 train_end int(len(samples) * train_ratio) val_end int(len(samples) * (train_ratio val_ratio)) train_X, train_Y samples[:train_end] val_X, val_Y samples[train_end:val_end] test_X, test_Y samples[val_end:]一个更严谨的做法是每次预测不能跨越“天”的边界。比如用过去 60 分钟预测未来 15 分钟样本的切分如果允许前一天 23:50 的数据预测后一天 00:05 的流量模型会学到“每天最后一刻的流量可以预测第二天开头流量”的伪规律——这在实际系统里是边界点数据量极少但影响不小。我在做数据预处理时直接按天为单位切分对每天内部生成滑动窗口样本然后按天划分 train/val/test彻底杜绝跨天样本。4.2 学习率0.001 起步观察 loss 停滞再下降图卷积网络的训练动态和普通 CNN 不太一样。CNN 的学习率可以从 0.01 起步GCN 因为消息传递本身就有平滑效应梯度更新幅度容易过大一上来就用大学习率经常直接跑到 NaN。我所有 GCN 相关的任务都从 0.001 起步。怎么判断学习率合不合适跑 20 轮看 loss 的下降曲线。如果 loss 在头 5 轮骤降然后开始震荡说明学习率偏大降到 0.0005如果 loss 前 20 轮几乎不降说明学习率太小提到 0.003。这里没有标准答案数据量、节点数、特征维度都会影响最优学习率。最靠谱的还是跑一个对数网格找最优值0.0003、0.001、0.003 各跑 30 轮选验证集 loss 最低的那个。学习率衰减策略我用 CosineAnnealingLR比 StepLR 省心不需要指定衰减步长。总轮次设 200最小学习率设 1e-5让模型在后期能慢慢收敛到更平缓的局部最优解。4.3 Batch Size图结构决定了 batch 不能太小交通流量预测的样本格式是 (B, T, N, F)其中 N 是节点数一个 batch 里所有样本共享同一张邻接矩阵和图结构。Batch Size 的影响比普通 CV 任务更大——因为每个 batch 都要做一次全图的邻接矩阵乘法batch 太小则 GPU 利用率低batch 太大则显存占用高且梯度更新频率太低。我实测下来 16 到 64 之间是比较合理的区间。城市级数据集 500 个节点左右T12F3batch 32显存占用大约 2-4 GB一般训练卡都能跑。如果节点数到几千batch 降到 8 甚至 4配合梯度累积来模拟更大的 batch。# 梯度累积模拟大 batch accumulation_steps 4 for i, (x_batch, y_batch) in enumerate(train_loader): pred model(x_batch, adj_norm) loss loss_fn(pred, y_batch) / accumulation_steps loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()注意梯度累积时要先把 loss 除以累积步数否则梯度会被放大 accumulation_steps 倍效果相当于把学习率放大了一个系数模型会不小心训飞。这个细节我在早期踩过坑损失函数曲线前 100 轮看着正常之后突然暴涨排除了很久才发现是梯度累积写错了。4.4 早停用验证集 loss 决定何时停止别拍脑袋定轮次固定训练 200 轮在数据量大的时候不会有问题但小数据集上GCN 后期很容易在训练集上过拟合验证集 loss 反而开始上涨。早停是最便宜、最有效的正则化手段。best_val_loss float(inf) patience 15 wait 0 for epoch in range(200): train_loss train_one_epoch() val_loss evaluate(val_loader) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model.pth) wait 0 else: wait 1 if wait patience: print(fEarly stop at epoch {epoch}) breakpatience 设 15 是比较稳的值。交通数据一天之内有早晚高峰验证集上的 loss 天然有日内波动patience 太小容易在早上刚过高峰、loss 暂时偏高时误判为过拟合而提前停止。记得每次验证集 loss 降低都要保存模型权重因为最后拿去测试的应该是历史最优的权重而不是最后一个 epoch 的权重——最后一个 epoch 的模型往往已经过拟合不少了。5. 交通流量预测里最容易翻车的 5 个坑5.1 数据形状不统一模型在训练时静默出错推理时彻底崩现象训练时 loss 偶尔出现 NaN或者训练能跑但预测结果全部是同一个常数画出来是一条横线。原因数据形状 (B, T, N, F) 和 (B, N, T, F) 混淆permute 维度写错后流量特征张量可能把时间维和节点维交错在一起。GCN 的 einsum 对维度不匹配会直接报错但有些代码因为用了 reshape 而不是 permute张量在内存里被重新解释形状合法但含义全错模型把不同节点的数据混着训练学不到空间结构的任何信息。解决在数据进入模型前加一条形状断言每个 batch 都校验。assert x.shape (B, T, N, F)不满足就立刻报错。另外永远用permute而不是reshape来交换维度——reshape 是按内存顺序重新解释数据的维度交换必须用 permute。5.2 归一化泄漏验证集、测试集的数据不能参与均值方差计算现象训练时 loss 降到 0.01验证集 loss 也非常低但换到另一批新数据上预测误差突然翻了 3 倍。原因数据归一化时把整个数据集的均值、方差一并算出来再切分验证集和测试集的分布信息已经“泄漏”进训练过程。测试集被压到了模型熟悉的区间看起来效果很好换个真实场景就露馅。解决必须先划分 train/val/test再单独在训练集上计算均值和方差用训练集的均值和方差去转换所有数据。# 正确做法在训练集上计算统计量 train_mean train_data.mean(axis(0, 1), keepdimsTrue) train_std train_data.std(axis(0, 1), keepdimsTrue) 1e-6 train_norm (train_data - train_mean) / train_std val_norm (val_data - train_mean) / train_std test_norm (test_data - train_mean) / train_std反归一化时同样用训练集的统计量不要用验证集计算出来的 mean/std。5.3 邻接矩阵的节点索引和数据特征的行号对不上现象模型 loss 一直下降但预测值和真实值有恒定的偏差或者说模型做空间聚合后某些节点完全不学。原因原本数据集里的传感器 ID 是有业务意义的编号比如检测站的编号 3051你把它转成 0 到 N-1 的整数索引时邻接矩阵的行号和数据特征矩阵的行号分别用了两套排序逻辑。比如特征矩阵按传感器 ID 升序排列而距离矩阵计算时按读取顺序排列一旦不是同一个顺序整个图的边就连错了模型在解释一个“错位”的拓扑关系。解决建立一一映射表数据读进来后用同一个排序规则统一所有维度。我把传感器 ID 排过序后直接作为特征矩阵的行索引邻接矩阵也按这个顺序构建然后印输出前 10 行的对应关系做人工抽查确保 ID 一一对应。5.4 预测多个时间步时把真实值当成了输入现象多步预测的前 1-2 步很准第 3 步开始偏差迅速放大几个时间步后预测值变成一条平线。原因做 3 步预测时如果代码在第二步把上一步的预测结果和真实值拼接后传给模型这就是标签泄漏。在训练时可以拿到真实值看起来 loss 很低部署时根本没有未来数据第 2 步开始只能喂模型自己的预测结果误差逐级累计整个推理链路就崩溃了。解决训练时所有时间步的输入都来自同一条时间窗口内的观测数据预测目标完全独立于输入。如果要处理长期预测考虑用 seq2seq 结构在推理阶段把前一步的模型输出作为下一步的输入但训练阶段要加 schedule sampling按概率把真实输出替换成模型输出避免训练和推理不一致。5.5 图里存在孤立节点或断开的子图信息根本传不过去现象大部分节点预测效果不错但总有几个节点模型完全学不到任何空间信息要么一直是平均值要么直接输出常数。原因这些节点在邻接矩阵里的度为 0——它们没有邻居或者邻居太少GCN 的消息传递对它们没有输入节点无法聚合任何邻居信息只剩自己的历史信号甚至在前向传播中直接产生 NaN。解决建图之后先检查邻接矩阵的连通分量。把规模过小的连通分量删掉或者人工补充就近连接把距离最近的节点设为邻居来保证图连通。这一步是数据工程的范畴不是模型的错但这里不做后面浪费的都是训练时间。6. 验证模型没白做早晚高峰、单点曲线和误差指标的核对方法预测模型的验证不能只看一个宏观平均误差。交通流量预测一天里有几个明显不同的模式凌晨低流量平稳、早高峰陡增、午间波动、晚高峰缓降。一个模型完全可能白天预测得很好、早高峰全面拉胯平均误差看起来还不算离谱。# 按小时统计误差找出模型最弱的时段 import pandas as pd results pd.DataFrame({pred: preds.flatten(), true: y_test.flatten(), hour: hours.flatten()}) hourly_mae results.groupby(hour).apply( lambda df: abs(df[pred] - df[true]).mean(), include_groupsFalse ) print(hourly_mae.sort_values(ascendingFalse).head(5))正常情况早高峰7:00-9:00的 MAE 会是凌晨的 3-5 倍因为流量本身大且变化剧烈。如果早高峰的误差异乎寻常地低反而要怀疑数据泄漏或归一化写错了。如果固定某个小时的误差在结果里异常突刺——比如 8:00 远高于 7:30 和 8:30——大概率那个时段有数据缺失插补逻辑没处理干净。画单点曲线是每个做时序预测的人都绕不过去的步骤。随机抽 5 个节点把测试集的某个连续 3 天画出来预测曲线和真实曲线叠在一起。最典型的失败模式是预测曲线比真实曲线“延后”一个周期——峰值对不上、拐点明显滞后。这种现象叫惯性预测模型学到的规律是“现在什么值下一时刻大概率还是什么值”因为在 MAE 损失下这是风险最低的选择。解决不了的时候可以对损失函数加一阶差分项让模型对变化方向更敏感# 在损失函数里加入预测差分的惩罚抑制惰性预测 pred_trend pred[:, 1:] - pred[:, :-1] true_trend y[:, 1:] - y[:, :-1] trend_loss torch.abs(pred_trend - true_trend).mean() total_loss loss_fn(pred, y) 0.3 * trend_loss模型层面的确认做完后再看误差指标。MAE 的数值要结合流量量纲理解——如果路网峰值流量 6000 veh/hMAE 300 就是 5% 的误差相当不错如果平均流量本身就低MAE 300 就是不可用。我习惯同时报 MAE、RMSE 和 MAPE 三个指标RMSE 放大峰值误差如果 RMSE 远大于 MAE说明模型在高峰期或者异常事故事件上错得很离谱要继续查。MAPE 在流量接近 0 的时段会爆炸计算时可以把流量低于阈值的样本剔除否则数值难看还不能说明真实问题。最后提醒一句别忘了做 basline 对比。拿历史均值预测、上一时刻值平移persistence model和线性回归各跑一遍如果 GCN 连 persistence 模型都赢不了问题大概率出在数据本身而不是 GCN 的实现。我见过不止一个团队在复杂的图模型上调了一个月参最后发现数据缺失率超过 40%填充出来的数据本身就没有预测价值。我现在的习惯是换模型之前先换数据、换特征、换图结构这三样改动带来的收益通常远大于模型架构本身的改动。希望帮到你。本文还有配套的精品资源点击获取