exp12 残差

This commit is contained in:
czzhangheng 2025-04-18 10:20:55 +08:00
parent 86fabd4ca7
commit bd94d3fdd3
17 changed files with 3465 additions and 5629 deletions

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ model:
input_dim: 1 input_dim: 1
output_dim: 1 output_dim: 1
embed_dim: 10 embed_dim: 10
in_len: 12
rnn_units: 64 rnn_units: 64
num_layers: 1 num_layers: 1
cheb_order: 2 cheb_order: 2

View File

@ -18,20 +18,7 @@ model:
input_dim: 1 input_dim: 1
output_dim: 1 output_dim: 1
in_len: 12 in_len: 12
dropout: 0.3
supports: null
gcn_bool: true
addaptadj: true
aptinit: null
in_dim: 2
out_dim: 12
residual_channels: 32
dilation_channels: 32
skip_channels: 256
end_channels: 512
kernel_size: 2
blocks: 4
layers: 2
train: train:
loss_func: mae loss_func: mae

45
config/EXP/PEMSD7.yaml Normal file
View File

@ -0,0 +1,45 @@
data:
num_nodes: 883
lag: 12
horizon: 12
val_ratio: 0.2
test_ratio: 0.2
tod: False
normalizer: std
column_wise: False
default_graph: True
add_time_in_day: True
add_day_in_week: True
steps_per_day: 288
days_per_week: 7
model:
batch_size: 64
input_dim: 1
output_dim: 1
in_len: 12
train:
loss_func: mae
seed: 10
batch_size: 64
epochs: 300
lr_init: 0.003
weight_decay: 0
lr_decay: False
lr_decay_rate: 0.3
lr_decay_step: "5,20,40,70"
early_stop: True
early_stop_patience: 15
grad_norm: False
max_grad_norm: 5
real_value: True
test:
mae_thresh: null
mape_thresh: 0.0
log:
log_step: 200
plot: False

View File

@ -14,18 +14,11 @@ data:
days_per_week: 7 days_per_week: 7
model: model:
batch_size: 64
input_dim: 1 input_dim: 1
output_dim: 1 output_dim: 1
embed_dim: 12 in_len: 12
rnn_units: 64
num_layers: 1
cheb_order: 2
use_day: True
use_week: True
graph_size: 30
expert_nums: 8
top_k: 2
hidden_dim: 64
train: train:
loss_func: mae loss_func: mae

156
model/EXP/EXP10.py Normal file
View File

@ -0,0 +1,156 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
KAN网络
"""
class KANLinear(nn.Module):
"""
A simple KolmogorovArnold Network linear layer.
y_k = sum_{q=1}^Q alpha_{kq} * phi_q( sum_{i=1}^I beta_{qi} * x_i )
"""
def __init__(self, in_features, out_features, hidden_funcs=10):
super().__init__()
self.in_features = in_features
self.out_features = out_features
self.num_hidden = hidden_funcs
# mixing weights from input to Q hidden functions
self.beta = nn.Parameter(torch.randn(hidden_funcs, in_features))
# one univariate phi function per hidden channel
self.phi = nn.ModuleList([
nn.Sequential(nn.Linear(1, 1), nn.ReLU())
for _ in range(hidden_funcs)
])
# mixing weights from hidden functions to outputs
self.alpha = nn.Parameter(torch.randn(out_features, hidden_funcs))
def forward(self, x):
# x: (..., in_features)
# compute univariate projections for each hidden func: u_q = sum_i beta_{qi} * x_i
u = torch.einsum('...i,qi->...q', x, self.beta) # (..., Q)
# apply phi elementwise
u_phi = torch.stack([
self.phi[q](u[..., q].unsqueeze(-1)).squeeze(-1)
for q in range(self.num_hidden)
], dim=-1) # (..., Q)
# mix to out_features
y = torch.einsum('...q,kq->...k', u_phi, self.alpha) # (..., out_features)
return y
class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim):
super().__init__()
self.nodevec1 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
self.nodevec2 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
def forward(self):
# (N, D) @ (D, N) -> (N, N)
adj = torch.matmul(self.nodevec1, self.nodevec2.T)
adj = F.relu(adj)
adj = F.softmax(adj, dim=-1)
return adj
class GraphConvBlock(nn.Module):
def __init__(self, input_dim, output_dim, kan_hidden=8):
super().__init__()
self.theta = KANLinear(input_dim, output_dim, hidden_funcs=kan_hidden)
self.residual = input_dim == output_dim
if not self.residual:
self.res_proj = KANLinear(input_dim, output_dim, hidden_funcs=kan_hidden)
def forward(self, x, adj):
# x: (B, N, C) / adj: (N, N)
res = x
x = torch.matmul(adj, x)
# apply KAN-based linear mapping
B, N, C = x.shape
x = x.view(B * N, C)
x = self.theta(x)
x = x.view(B, N, -1)
if self.residual:
x = x + res
else:
x = x + self.res_proj(res.view(B*N, C)).view(B, N, -1)
return F.relu(x)
class MANBA_Block(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
self.attn = nn.MultiheadAttention(embed_dim=input_dim, num_heads=4, batch_first=True)
self.ffn = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim)
)
self.norm1 = nn.LayerNorm(input_dim)
self.norm2 = nn.LayerNorm(input_dim)
def forward(self, x):
# x: (B, N, C) -> treat N as temporal for attention
res = x
# swap dims to (B, T, C) for attn if needed
x_attn, _ = self.attn(x, x, x)
x = self.norm1(res + x_attn)
res2 = x
x_ffn = self.ffn(x)
x = self.norm2(res2 + x_ffn)
return x
class EXP(nn.Module):
def __init__(self, args):
super().__init__()
self.horizon = args['horizon']
self.output_dim = args['output_dim']
self.seq_len = args.get('in_len', 12)
self.hidden_dim = args.get('hidden_dim', 64)
self.num_nodes = args['num_nodes']
kan_hidden = args.get('kan_hidden', 8)
# 动态图构建
self.graph = DynamicGraphConstructor(self.num_nodes, embed_dim=16)
# 输入映射KAN替代线性层
self.input_proj = KANLinear(self.seq_len, self.hidden_dim, hidden_funcs=kan_hidden)
# 图卷积
self.gc = GraphConvBlock(self.hidden_dim, self.hidden_dim, kan_hidden=kan_hidden)
# 时间建模保持MANBA
self.manba = MANBA_Block(self.hidden_dim, self.hidden_dim * 2)
# 输出映射KAN替代线性层
out_size = self.horizon * self.output_dim
self.out_proj = KANLinear(self.hidden_dim, out_size, hidden_funcs=kan_hidden)
def forward(self, x):
# x: (B, T, N, D_total)
x = x[..., 0]
B, T, N = x.shape
assert T == self.seq_len
# 输入投影 (B, T, N) -> (B, N, T) -> (B*N, T)
x = x.permute(0, 2, 1).reshape(B * N, T)
h = self.input_proj(x) # (B*N, hidden_dim)
h = h.view(B, N, self.hidden_dim)
# 动态图
adj = self.graph()
# 空间:图卷积
h = self.gc(h, adj)
# 时间MANBA
h = self.manba(h)
# 输出
h_flat = h.view(B * N, self.hidden_dim)
out = self.out_proj(h_flat)
out = out.view(B, N, self.horizon, self.output_dim).permute(0, 2, 1, 3)
return out

123
model/EXP/EXP11.py Normal file
View File

@ -0,0 +1,123 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
含残差版本 + 时间-空间-时间三明治结构
"""
class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim):
super().__init__()
self.nodevec1 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
self.nodevec2 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
def forward(self):
# (N, D) @ (D, N) -> (N, N)
adj = torch.matmul(self.nodevec1, self.nodevec2.T)
adj = F.relu(adj)
adj = F.softmax(adj, dim=-1)
return adj
class GraphConvBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.theta = nn.Linear(input_dim, output_dim)
self.residual = input_dim == output_dim
if not self.residual:
self.res_proj = nn.Linear(input_dim, output_dim)
def forward(self, x, adj):
# x: (B, N, C) / adj: (N, N)
res = x
x = torch.matmul(adj, x) # (B, N, C)
x = self.theta(x)
# 残差连接
if self.residual:
x = x + res
else:
x = x + self.res_proj(res)
return F.relu(x)
class MANBA_Block(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
self.attn = nn.MultiheadAttention(embed_dim=input_dim, num_heads=4, batch_first=True)
self.ffn = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim)
)
self.norm1 = nn.LayerNorm(input_dim)
self.norm2 = nn.LayerNorm(input_dim)
def forward(self, x):
# x: (B, T, C) 或 (B, N, C) 当 N 视为 时间维度
res = x
x_attn, _ = self.attn(x, x, x)
x = self.norm1(res + x_attn)
res2 = x
x_ffn = self.ffn(x)
x = self.norm2(res2 + x_ffn)
return x
class EXP(nn.Module):
def __init__(self, args):
super().__init__()
self.horizon = args['horizon']
self.output_dim = args['output_dim']
self.seq_len = args.get('in_len', 12)
self.hidden_dim = args.get('hidden_dim', 64)
self.num_nodes = args['num_nodes']
# 动态图构建
self.graph = DynamicGraphConstructor(self.num_nodes, embed_dim=16)
# 输入映射层
self.input_proj = nn.Linear(self.seq_len, self.hidden_dim)
# 图卷积
self.gc = GraphConvBlock(self.hidden_dim, self.hidden_dim)
# MANBA block时间建模
self.manba = MANBA_Block(self.hidden_dim, self.hidden_dim * 2)
# 输出映射
self.out_proj = nn.Linear(self.hidden_dim, self.horizon * self.output_dim)
def forward(self, x):
# x: (B, T, N, D_total)
x = x[..., 0] # 只用主通道 (B, T, N)
B, T, N = x.shape
assert T == self.seq_len
# 输入投影 (B, T, N) -> (B, N, T) -> (B*N, T) -> (B*N, hidden_dim)
x_flat = x.permute(0, 2, 1).reshape(B * N, T)
h = self.input_proj(x_flat) # (B*N, hidden_dim)
h = h.view(B, N, self.hidden_dim) # (B, N, hidden_dim)
# === 时间建模(首次) ===
# 将 N 视作 时间维度进行注意力
h_time1 = self.manba(h) # (B, N, hidden_dim)
# 动态图构建
adj = self.graph() # (N, N)
# === 空间建模 ===
h_space = self.gc(h_time1, adj) # (B, N, hidden_dim)
# === 时间建模(再一次) ===
h_time2 = self.manba(h_space) # (B, N, hidden_dim)
# 输出映射
out = self.out_proj(h_time2) # (B, N, horizon * output_dim)
out = out.view(B, N, self.horizon, self.output_dim).permute(0, 2, 1, 3)
return out # (B, horizon, N, output_dim)

128
model/EXP/EXP12.py Normal file
View File

@ -0,0 +1,128 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
含残差的双层三明治结构模型
第一层时间 -> 空间 -> 时间
残差连接层输出 + 层输入
第二层同样三明治结构 -> 最终输出
有效
"""
class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim):
super().__init__()
self.nodevec1 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
self.nodevec2 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
def forward(self):
# (N, D) @ (D, N) -> (N, N)
adj = torch.matmul(self.nodevec1, self.nodevec2.T)
adj = F.relu(adj)
adj = F.softmax(adj, dim=-1)
return adj
class GraphConvBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.theta = nn.Linear(input_dim, output_dim)
self.residual = (input_dim == output_dim)
if not self.residual:
self.res_proj = nn.Linear(input_dim, output_dim)
def forward(self, x, adj):
# x: (B, N, C); adj: (N, N)
res = x
x = torch.matmul(adj, x) # 空间卷积
x = self.theta(x)
# 残差
x = x + (res if self.residual else self.res_proj(res))
return F.relu(x)
class MANBA_Block(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
self.attn = nn.MultiheadAttention(embed_dim=input_dim, num_heads=4, batch_first=True)
self.ffn = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim)
)
self.norm1 = nn.LayerNorm(input_dim)
self.norm2 = nn.LayerNorm(input_dim)
def forward(self, x):
# x: (B, N, C) 视 N 维为时间序列长度
res = x
x_attn, _ = self.attn(x, x, x)
x = self.norm1(res + x_attn)
res2 = x
x_ffn = self.ffn(x)
x = self.norm2(res2 + x_ffn)
return x
class SandwichBlock(nn.Module):
"""
时间-空间-时间 三明治结构
输入/输出 (B, N, hidden_dim)
"""
def __init__(self, num_nodes, embed_dim, hidden_dim):
super().__init__()
self.manba1 = MANBA_Block(hidden_dim, hidden_dim * 2)
self.graph_constructor = DynamicGraphConstructor(num_nodes, embed_dim)
self.gc = GraphConvBlock(hidden_dim, hidden_dim)
self.manba2 = MANBA_Block(hidden_dim, hidden_dim * 2)
def forward(self, h):
# h: (B, N, hidden_dim)
h1 = self.manba1(h)
adj = self.graph_constructor() # (N, N)
h2 = self.gc(h1, adj)
h3 = self.manba2(h2)
return h3
class EXP(nn.Module):
def __init__(self, args):
super().__init__()
self.horizon = args['horizon']
self.output_dim = args['output_dim']
self.seq_len = args.get('in_len', 12)
self.hidden_dim = args.get('hidden_dim', 64)
self.num_nodes = args['num_nodes']
self.embed_dim = args.get('embed_dim', 16)
# 输入映射
self.input_proj = nn.Linear(self.seq_len, self.hidden_dim)
# 两层三明治块
self.sandwich1 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
self.sandwich2 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
# 输出映射
self.out_proj = nn.Linear(self.hidden_dim, self.horizon * self.output_dim)
def forward(self, x):
# x: (B, T, N, D_total)
x_main = x[..., 0] # (B, T, N)
B, T, N = x_main.shape
assert T == self.seq_len
# 输入投影 (B, T, N) -> (B*N, T) -> (B, N, hidden_dim)
x_flat = x_main.permute(0, 2, 1).reshape(B * N, T)
h0 = self.input_proj(x_flat).view(B, N, self.hidden_dim)
# 第一层三明治 + 残差
h1 = self.sandwich1(h0)
h1 = h1 + h0
# 第二层三明治
h2 = self.sandwich2(h1)
# 输出映射
out = self.out_proj(h2) # (B, N, H*D_out)
out = out.view(B, N, self.horizon, self.output_dim)
out = out.permute(0, 2, 1, 3) # (B, horizon, N, output_dim)
return out

125
model/EXP/EXP13.py Normal file
View File

@ -0,0 +1,125 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
含残差的双层 空间->时间->空间 结构模型 无效
"""
class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim):
super().__init__()
self.nodevec1 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
self.nodevec2 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
def forward(self):
# 构造动态邻接矩阵 (N, N)
adj = torch.matmul(self.nodevec1, self.nodevec2.T)
adj = F.relu(adj)
adj = F.softmax(adj, dim=-1)
return adj
class GraphConvBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.theta = nn.Linear(input_dim, output_dim)
self.residual = (input_dim == output_dim)
if not self.residual:
self.res_proj = nn.Linear(input_dim, output_dim)
def forward(self, x, adj):
# x: (B, N, C)
res = x
x = torch.matmul(adj, x)
x = self.theta(x)
x = x + (res if self.residual else self.res_proj(res))
return F.relu(x)
class MANBA_Block(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
self.attn = nn.MultiheadAttention(embed_dim=input_dim, num_heads=4, batch_first=True)
self.ffn = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim)
)
self.norm1 = nn.LayerNorm(input_dim)
self.norm2 = nn.LayerNorm(input_dim)
def forward(self, x):
# x: (B, N, C) 当 N 视为时间序列长度
res = x
x_attn, _ = self.attn(x, x, x)
x = self.norm1(res + x_attn)
res2 = x
x_ffn = self.ffn(x)
x = self.norm2(res2 + x_ffn)
return x
class SandwichBlock(nn.Module):
"""
空间 -> 时间 -> 空间 三明治结构
输入/输出 (B, N, hidden_dim)
"""
def __init__(self, num_nodes, embed_dim, hidden_dim):
super().__init__()
self.graph_constructor = DynamicGraphConstructor(num_nodes, embed_dim)
self.gc1 = GraphConvBlock(hidden_dim, hidden_dim)
self.manba = MANBA_Block(hidden_dim, hidden_dim * 2)
self.gc2 = GraphConvBlock(hidden_dim, hidden_dim)
def forward(self, h):
# 第一步:空间卷积
adj = self.graph_constructor()
h1 = self.gc1(h, adj)
# 第二步:时间注意力
h2 = self.manba(h1)
# 第三步:空间卷积
h3 = self.gc2(h2, adj)
return h3
class EXP(nn.Module):
def __init__(self, args):
super().__init__()
self.horizon = args['horizon']
self.output_dim = args['output_dim']
self.seq_len = args.get('in_len', 12)
self.hidden_dim = args.get('hidden_dim', 64)
self.num_nodes = args['num_nodes']
self.embed_dim = args.get('embed_dim', 16)
# 输入映射
self.input_proj = nn.Linear(self.seq_len, self.hidden_dim)
# 两层 空间-时间-空间 三明治块
self.sandwich1 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
self.sandwich2 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
# 输出映射
self.out_proj = nn.Linear(self.hidden_dim, self.horizon * self.output_dim)
def forward(self, x):
# x: (B, T, N, D)
x_main = x[..., 0] # (B, T, N)
B, T, N = x_main.shape
assert T == self.seq_len
# 投影到隐藏维 (B,N,hidden)
x_flat = x_main.permute(0, 2, 1).reshape(B * N, T)
h0 = self.input_proj(x_flat).view(B, N, self.hidden_dim)
# 第一层三明治 + 残差
h1 = self.sandwich1(h0)
h1 = h1 + h0
# 第二层三明治
h2 = self.sandwich2(h1)
# 输出
out = self.out_proj(h2) # (B, N, H*D_out)
out = out.view(B, N, self.horizon, self.output_dim)
out = out.permute(0, 2, 1, 3) # (B, horizon, N, output_dim)
return out

147
model/EXP/EXP14.py Normal file
View File

@ -0,0 +1,147 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
含时间/空间额外特征的双层 时间->空间->时间 三明治结构模型
使用 x[...,0] 主通道x[...,1] time_in_dayx[...,2] day_in_week
通过独立投影融合三路信息
无改进
"""
class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim):
super().__init__()
self.nodevec1 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
self.nodevec2 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
def forward(self):
# 构造动态邻接矩阵 (N, N)
adj = torch.matmul(self.nodevec1, self.nodevec2.T)
adj = F.relu(adj)
adj = F.softmax(adj, dim=-1)
return adj
class GraphConvBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.theta = nn.Linear(input_dim, output_dim)
self.residual = (input_dim == output_dim)
if not self.residual:
self.res_proj = nn.Linear(input_dim, output_dim)
def forward(self, x, adj):
# x: (B, N, C)
res = x
x = torch.matmul(adj, x)
x = self.theta(x)
x = x + (res if self.residual else self.res_proj(res))
return F.relu(x)
class MANBA_Block(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
self.attn = nn.MultiheadAttention(embed_dim=input_dim, num_heads=4, batch_first=True)
self.ffn = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim)
)
self.norm1 = nn.LayerNorm(input_dim)
self.norm2 = nn.LayerNorm(input_dim)
def forward(self, x):
# x: (B, N, C) 视 N 维为时间序列长度
res = x
x_attn, _ = self.attn(x, x, x)
x = self.norm1(res + x_attn)
res2 = x
x_ffn = self.ffn(x)
x = self.norm2(res2 + x_ffn)
return x
class SandwichBlock(nn.Module):
"""
时间 -> 空间 -> 时间 三明治结构
输入/输出 (B, N, hidden_dim)
"""
def __init__(self, num_nodes, embed_dim, hidden_dim):
super().__init__()
self.manba1 = MANBA_Block(hidden_dim, hidden_dim * 2)
self.graph_constructor = DynamicGraphConstructor(num_nodes, embed_dim)
self.gc = GraphConvBlock(hidden_dim, hidden_dim)
self.manba2 = MANBA_Block(hidden_dim, hidden_dim * 2)
def forward(self, h):
# h: (B, N, hidden_dim)
# 第一步:时间注意力
h1 = self.manba1(h)
# 第二步:空间卷积
adj = self.graph_constructor()
h2 = self.gc(h1, adj)
# 第三步:时间注意力
h3 = self.manba2(h2)
return h3
class EXP(nn.Module):
def __init__(self, args):
super().__init__()
self.horizon = args['horizon']
self.output_dim = args['output_dim']
self.seq_len = args.get('in_len', 12)
self.hidden_dim = args.get('hidden_dim', 64)
self.num_nodes = args['num_nodes']
self.embed_dim = args.get('embed_dim', 16)
# 对三路输入分别投影到隐藏维度
self.main_proj = nn.Linear(self.seq_len, self.hidden_dim)
self.time_proj = nn.Linear(self.seq_len, self.hidden_dim)
self.week_proj = nn.Linear(self.seq_len, self.hidden_dim)
# 双层 时间->空间->时间 三明治块
self.sandwich1 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
self.sandwich2 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
# 输出映射
self.out_proj = nn.Linear(self.hidden_dim, self.horizon * self.output_dim)
def forward(self, x):
# x: (B, T, N, D_total)
x_main = x[..., 0] # (B, T, N)
x_time = x[..., 1] # (B, T, N)
x_week = x[..., 2] # (B, T, N)
B, T, N = x_main.shape
assert T == self.seq_len
# 将三路特征分别映射后叠加
x_main_flat = x_main.permute(0, 2, 1).reshape(B * N, T)
h_main = self.main_proj(x_main_flat).view(B, N, self.hidden_dim)
x_time_flat = x_time.permute(0, 2, 1).reshape(B * N, T)
h_time = self.time_proj(x_time_flat).view(B, N, self.hidden_dim)
x_week_flat = x_week.permute(0, 2, 1).reshape(B * N, T)
h_week = self.week_proj(x_week_flat).view(B, N, self.hidden_dim)
# 初始隐藏表示,融合三路信息
h0 = h_main + h_time + h_week
# 第一层三明治 + 残差
h1 = self.sandwich1(h0)
h1 = h1 + h0
# 第二层三明治
h2 = self.sandwich2(h1)
# 输出
out = self.out_proj(h2)
out = out.view(B, N, self.horizon, self.output_dim)
out = out.permute(0, 2, 1, 3) # (B, horizon, N, D_out)
return out
# 示例测试
# args = {'horizon':12,'output_dim':1,'in_len':12,'hidden_dim':64,'num_nodes':307,'embed_dim':16}
# model = EXP(args)
# x = torch.randn(16, 12, 307, 3)
# print(model(x).shape) # (16,12,307,1)

139
model/EXP/EXP15.py Normal file
View File

@ -0,0 +1,139 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
含残差的双层三明治结构模型
第一层时间 -> 空间 -> 时间 -> Conv -> Residual差分 -> 输入第二层
第二层时间 -> 空间 -> 时间 -> Conv -> 最终输出
无效但接近
"""
class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim):
super().__init__()
self.nodevec1 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
self.nodevec2 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
def forward(self):
# (N, D) @ (D, N) -> (N, N)
adj = torch.matmul(self.nodevec1, self.nodevec2.T)
adj = F.relu(adj)
adj = F.softmax(adj, dim=-1)
return adj
class GraphConvBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.theta = nn.Linear(input_dim, output_dim)
self.residual = (input_dim == output_dim)
if not self.residual:
self.res_proj = nn.Linear(input_dim, output_dim)
def forward(self, x, adj):
# x: (B, N, C); adj: (N, N)
res = x
x = torch.matmul(adj, x) # 空间卷积
x = self.theta(x)
# 残差
x = x + (res if self.residual else self.res_proj(res))
return F.relu(x)
class MANBA_Block(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
self.attn = nn.MultiheadAttention(embed_dim=input_dim, num_heads=4, batch_first=True)
self.ffn = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim)
)
self.norm1 = nn.LayerNorm(input_dim)
self.norm2 = nn.LayerNorm(input_dim)
def forward(self, x):
# x: (B, N, C) 视 N 维为时间序列长度
res = x
x_attn, _ = self.attn(x, x, x)
x = self.norm1(res + x_attn)
res2 = x
x_ffn = self.ffn(x)
x = self.norm2(res2 + x_ffn)
return x
class SandwichBlock(nn.Module):
"""
时间-空间-时间 三明治结构
输入/输出 (B, N, hidden_dim)
"""
def __init__(self, num_nodes, embed_dim, hidden_dim):
super().__init__()
self.manba1 = MANBA_Block(hidden_dim, hidden_dim * 2)
self.graph_constructor = DynamicGraphConstructor(num_nodes, embed_dim)
self.gc = GraphConvBlock(hidden_dim, hidden_dim)
self.manba2 = MANBA_Block(hidden_dim, hidden_dim * 2)
def forward(self, h):
# h: (B, N, hidden_dim)
h1 = self.manba1(h)
adj = self.graph_constructor() # (N, N)
h2 = self.gc(h1, adj)
h3 = self.manba2(h2)
return h3
class EXP(nn.Module):
def __init__(self, args):
super().__init__()
self.horizon = args['horizon']
self.output_dim = args['output_dim']
self.seq_len = args.get('in_len', 12)
self.hidden_dim = args.get('hidden_dim', 64)
self.num_nodes = args['num_nodes']
self.embed_dim = args.get('embed_dim', 16)
# 输入映射: (batch*N, seq_len) -> hidden_dim
self.input_proj = nn.Linear(self.seq_len, self.hidden_dim)
# 两层三明治块
self.sandwich1 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
self.sandwich2 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
# 卷积层用于残差处理
self.res_conv1 = nn.Conv1d(in_channels=self.hidden_dim, out_channels=self.hidden_dim, kernel_size=1)
self.res_conv2 = nn.Conv1d(in_channels=self.hidden_dim, out_channels=self.hidden_dim, kernel_size=1)
# 输出映射
self.out_proj = nn.Linear(self.hidden_dim, self.horizon * self.output_dim)
def forward(self, x):
# x: (B, T, N, D_total)
x_main = x[..., 0] # (B, T, N)
B, T, N = x_main.shape
assert T == self.seq_len
# 输入投影 (B, T, N) -> (B*N, T) -> (B, N, hidden_dim)
x_flat = x_main.permute(0, 2, 1).reshape(B * N, T)
h0 = self.input_proj(x_flat).view(B, N, self.hidden_dim)
# 第一层三明治
h1_sand = self.sandwich1(h0) # (B, N, hidden_dim)
# 卷积残差 (节点维度视为长度)
h1_perm = h1_sand.permute(0, 2, 1) # (B, C, N)
h1_conv = self.res_conv1(h1_perm)
h1 = h1_conv.permute(0, 2, 1) # (B, N, hidden_dim)
# 计算差分残差作为第二层输入
h2_input = h1 - h0
# 第二层三明治
h2_sand = self.sandwich2(h2_input)
# 再次卷积处理
h2_perm = h2_sand.permute(0, 2, 1)
h2 = self.res_conv2(h2_perm).permute(0, 2, 1)
# 输出映射
out = self.out_proj(h2) # (B, N, H*D_out)
out = out.view(B, N, self.horizon, self.output_dim)
out = out.permute(0, 2, 1, 3) # (B, horizon, N, output_dim)
return out

119
model/EXP/EXP16.py Normal file
View File

@ -0,0 +1,119 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
含残差的双层三明治结构模型
第一层时间 -> 空间 -> 时间
残差连接层输出 + 层输入
第二层同样三明治结构 -> 最终输出
无小残差
"""
class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim):
super().__init__()
self.nodevec1 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
self.nodevec2 = nn.Parameter(torch.randn(node_num, embed_dim), requires_grad=True)
def forward(self):
# (N, D) @ (D, N) -> (N, N)
adj = torch.matmul(self.nodevec1, self.nodevec2.T)
adj = F.relu(adj)
adj = F.softmax(adj, dim=-1)
return adj
class GraphConvBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.theta = nn.Linear(input_dim, output_dim)
def forward(self, x, adj):
# x: (B, N, C) / adj: (N, N)
x = torch.matmul(adj, x) # (B, N, C)
x = self.theta(x)
return F.relu(x)
class MANBA_Block(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
self.attn = nn.MultiheadAttention(embed_dim=input_dim, num_heads=4, batch_first=True)
self.ffn = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim)
)
self.norm1 = nn.LayerNorm(input_dim)
self.norm2 = nn.LayerNorm(input_dim)
def forward(self, x):
# x: (B, T, C)
x_attn, _ = self.attn(x, x, x)
x = self.norm1(x + x_attn)
x_ffn = self.ffn(x)
return self.norm2(x + x_ffn)
class SandwichBlock(nn.Module):
"""
时间-空间-时间 三明治结构
输入/输出 (B, N, hidden_dim)
"""
def __init__(self, num_nodes, embed_dim, hidden_dim):
super().__init__()
self.manba1 = MANBA_Block(hidden_dim, hidden_dim * 2)
self.graph_constructor = DynamicGraphConstructor(num_nodes, embed_dim)
self.gc = GraphConvBlock(hidden_dim, hidden_dim)
self.manba2 = MANBA_Block(hidden_dim, hidden_dim * 2)
def forward(self, h):
# h: (B, N, hidden_dim)
h1 = self.manba1(h)
adj = self.graph_constructor() # (N, N)
h2 = self.gc(h1, adj)
h3 = self.manba2(h2)
return h3
class EXP(nn.Module):
def __init__(self, args):
super().__init__()
self.horizon = args['horizon']
self.output_dim = args['output_dim']
self.seq_len = args.get('in_len', 12)
self.hidden_dim = args.get('hidden_dim', 64)
self.num_nodes = args['num_nodes']
self.embed_dim = args.get('embed_dim', 16)
# 输入映射
self.input_proj = nn.Linear(self.seq_len, self.hidden_dim)
# 两层三明治块
self.sandwich1 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
self.sandwich2 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim)
# 输出映射
self.out_proj = nn.Linear(self.hidden_dim, self.horizon * self.output_dim)
def forward(self, x):
# x: (B, T, N, D_total)
x_main = x[..., 0] # (B, T, N)
B, T, N = x_main.shape
assert T == self.seq_len
# 输入投影 (B, T, N) -> (B*N, T) -> (B, N, hidden_dim)
x_flat = x_main.permute(0, 2, 1).reshape(B * N, T)
h0 = self.input_proj(x_flat).view(B, N, self.hidden_dim)
# 第一层三明治 + 残差
h1 = self.sandwich1(h0)
h1 = h1 + h0
# 第二层三明治
h2 = self.sandwich2(h1)
# 输出映射
out = self.out_proj(h2) # (B, N, H*D_out)
out = out.view(B, N, self.horizon, self.output_dim)
out = out.permute(0, 2, 1, 3) # (B, horizon, N, output_dim)
return out

View File

@ -2,6 +2,10 @@ import torch
import torch.nn as nn import torch.nn as nn
import torch.nn.functional as F import torch.nn.functional as F
"""
不含残差版本
"""
class DynamicGraphConstructor(nn.Module): class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim): def __init__(self, node_num, embed_dim):
@ -75,7 +79,7 @@ class EXP(nn.Module):
def forward(self, x): def forward(self, x):
# x: (B, T, N, D_total) # x: (B, T, N, D_total)
x = x.sum(dim=-1) # (B, T, N) x = x[..., 0] # 只用主通道 (B, T, N)
B, T, N = x.shape B, T, N = x.shape
assert T == self.seq_len assert T == self.seq_len

View File

@ -2,6 +2,9 @@ import torch
import torch.nn as nn import torch.nn as nn
import torch.nn.functional as F import torch.nn.functional as F
"""
含残差版本
"""
class DynamicGraphConstructor(nn.Module): class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim): def __init__(self, node_num, embed_dim):

View File

@ -2,6 +2,9 @@ import torch
import torch.nn as nn import torch.nn as nn
import torch.nn.functional as F import torch.nn.functional as F
"""
加入混合专家
"""
class DynamicGraphConstructor(nn.Module): class DynamicGraphConstructor(nn.Module):
def __init__(self, node_num, embed_dim): def __init__(self, node_num, embed_dim):

View File

@ -13,7 +13,7 @@ from model.STFGNN.STFGNN import STFGNN
from model.STSGCN.STSGCN import STSGCN from model.STSGCN.STSGCN import STSGCN
from model.STGODE.STGODE import ODEGCN from model.STGODE.STGODE import ODEGCN
from model.PDG2SEQ.PDG2Seq import PDG2Seq from model.PDG2SEQ.PDG2Seq import PDG2Seq
from model.EXP.EXP9 import EXP as EXP from model.EXP.EXP16 import EXP as EXP
def model_selector(model): def model_selector(model):
match model['type']: match model['type']:

4
run.py
View File

@ -30,10 +30,12 @@ def main():
else: else:
args['device'] = 'cpu' args['device'] = 'cpu'
args['model']['device'] = args['device'] args['model']['device'] = args['device']
init_seed(args['train']['seed'])
# Initialize model # Initialize model
model = init_model(args['model'], device=args['device']) model = init_model(args['model'], device=args['device'])
if args['mode'] == "benchmark": if args['mode'] == "benchmark":
# 支持计算消耗分析,设置 mode为 benchmark # 支持计算消耗分析,设置 mode为 benchmark
import torch.profiler as profiler import torch.profiler as profiler