RuntimeError: Expected 2D (unbatched) or 3D (batched) input to conv1d, but got input of size: [64, 64, 358, 12]

I’m trying to run a Graph Neural Network model called “STFGNN” (available on GitHub at https://github.com/lwm412/STFGNN-Pytorch/tree/main?tab=readme-ov-file) on Kaggle. However, I’m encountering several issues:

1: RuntimeWarning: invalid value encountered in divide return (a-mu)/std0

2:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Traceback (most recent call last):
File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/main.py", line 33, in <module>
executor.train(train_data, valid_data)
File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/executor/multi_step_executor.py", line 82, in train
output = self.model(trainx)
File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl
return forward_call(*args, **kwargs)
File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/model/STFGNN.py", line 420, in forward
x = model(x, self.mask)
File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl
return forward_call(*args, **kwargs)
File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/model/STFGNN.py", line 207, in forward
data_left = torch.sigmoid(self.conv1(data_temp))
File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl
return forward_call(*args, **kwargs)
File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/conv.py", line 310, in forward
return self._conv_forward(input, self.weight, self.bias)
File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/conv.py", line 306, in _conv_forward
return F.conv1d(input, weight, bias, self.stride,
RuntimeError: Expected 2D (unbatched) or 3D (batched) input to conv1d, but got input of size: [64, 64, 358, 12]
</code>
<code>Traceback (most recent call last): File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/main.py", line 33, in <module> executor.train(train_data, valid_data) File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/executor/multi_step_executor.py", line 82, in train output = self.model(trainx) File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl return self._call_impl(*args, **kwargs) File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl return forward_call(*args, **kwargs) File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/model/STFGNN.py", line 420, in forward x = model(x, self.mask) File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl return self._call_impl(*args, **kwargs) File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl return forward_call(*args, **kwargs) File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/model/STFGNN.py", line 207, in forward data_left = torch.sigmoid(self.conv1(data_temp)) File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl return self._call_impl(*args, **kwargs) File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl return forward_call(*args, **kwargs) File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/conv.py", line 310, in forward return self._conv_forward(input, self.weight, self.bias) File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/conv.py", line 306, in _conv_forward return F.conv1d(input, weight, bias, self.stride, RuntimeError: Expected 2D (unbatched) or 3D (batched) input to conv1d, but got input of size: [64, 64, 358, 12] </code>
Traceback (most recent call last):
  File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/main.py", line 33, in <module>
    executor.train(train_data, valid_data)
  File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/executor/multi_step_executor.py", line 82, in train
    output = self.model(trainx)
  File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl
    return forward_call(*args, **kwargs)
  File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/model/STFGNN.py", line 420, in forward
    x = model(x, self.mask)
  File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl
    return forward_call(*args, **kwargs)
  File "/kaggle/working/STFGNN/STFGNN-Pytorch-main/model/STFGNN.py", line 207, in forward
    data_left = torch.sigmoid(self.conv1(data_temp))
  File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1518, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl
    return forward_call(*args, **kwargs)
  File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/conv.py", line 310, in forward
    return self._conv_forward(input, self.weight, self.bias)
  File "/opt/conda/lib/python3.10/site-packages/torch/nn/modules/conv.py", line 306, in _conv_forward
    return F.conv1d(input, weight, bias, self.stride,

RuntimeError: Expected 2D (unbatched) or 3D (batched) input to conv1d, but got input of size: [64, 64, 358, 12]

I’ve tried using the following normalization function:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>def normalize(a):
mu=np.mean(a,axis=1,keepdims=True)
std=np.std(a,axis=1,keepdims=True)
return (a-mu)/std
</code>
<code>def normalize(a): mu=np.mean(a,axis=1,keepdims=True) std=np.std(a,axis=1,keepdims=True) return (a-mu)/std </code>
def normalize(a):
    mu=np.mean(a,axis=1,keepdims=True)
    std=np.std(a,axis=1,keepdims=True)
    return (a-mu)/std

However, I continue to experience these issues. Below is a snippet of the main code and the model (STFGNN) I’m working with. Could you provide some guidance on what might be going wrong?

Main:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>
config = {}
for filename in ["config/PEMS03.json", "config/STFGNN.json"]:
with open(filename, "r") as f:
_config = json.load(f)
for key in _config:
if key not in config:
config[key] = _config[key]
dataset = STFGNNDataset(config)
train_data, valid_data, test_data = dataset.get_data()
data_feature = dataset.get_data_feature()
model_cache_file = 'cache/model_cache/PEMS03_STFGNN.m'
model = STFGNN(config, data_feature)
executor = STFGNNExecutor(config, model)
train = True #标识是否需要重新训练
if train or not os.path.exists(model_cache_file):
executor.train(train_data, valid_data)
executor.save_model(model_cache_file)
else:
executor.load_model(model_cache_file)
# 评估,评估结果将会放在 cache/evaluate_cache 下
executor.evaluate(test_data)
</code>
<code> config = {} for filename in ["config/PEMS03.json", "config/STFGNN.json"]: with open(filename, "r") as f: _config = json.load(f) for key in _config: if key not in config: config[key] = _config[key] dataset = STFGNNDataset(config) train_data, valid_data, test_data = dataset.get_data() data_feature = dataset.get_data_feature() model_cache_file = 'cache/model_cache/PEMS03_STFGNN.m' model = STFGNN(config, data_feature) executor = STFGNNExecutor(config, model) train = True #标识是否需要重新训练 if train or not os.path.exists(model_cache_file): executor.train(train_data, valid_data) executor.save_model(model_cache_file) else: executor.load_model(model_cache_file) # 评估,评估结果将会放在 cache/evaluate_cache 下 executor.evaluate(test_data) </code>

config = {}
for filename in ["config/PEMS03.json", "config/STFGNN.json"]:
    with open(filename, "r") as f:
        _config = json.load(f)
        for key in _config:
            if key not in config:
                config[key] = _config[key]

dataset = STFGNNDataset(config)

train_data, valid_data, test_data = dataset.get_data()
data_feature = dataset.get_data_feature()

model_cache_file = 'cache/model_cache/PEMS03_STFGNN.m'

model = STFGNN(config, data_feature)

executor = STFGNNExecutor(config, model)


train = True #标识是否需要重新训练

if train or not os.path.exists(model_cache_file):
    executor.train(train_data, valid_data)
    executor.save_model(model_cache_file)
else:
    executor.load_model(model_cache_file)
# 评估,评估结果将会放在 cache/evaluate_cache 下
executor.evaluate(test_data)

STFGNN:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>
class gcn_operation(nn.Module):
def __init__(self, adj, in_dim, out_dim, num_vertices, activation='GLU'):
"""
图卷积模块
:param adj: 邻接图
:param in_dim: 输入维度
:param out_dim: 输出维度
:param num_vertices: 节点数量
:param activation: 激活方式 {'relu', 'GLU'}
"""
super(gcn_operation, self).__init__()
self.adj = adj
self.in_dim = in_dim
self.out_dim = out_dim
self.num_vertices = num_vertices
self.activation = activation
assert self.activation in {'GLU', 'relu'}
if self.activation == 'GLU':
self.FC = nn.Linear(self.in_dim, 2 * self.out_dim, bias=True)
else:
self.FC = nn.Linear(self.in_dim, self.out_dim, bias=True)
def forward(self, x, mask=None):
"""
:param x: (3*N, B, Cin)
:param mask:(3*N, 3*N)
:return: (3*N, B, Cout)
"""
adj = self.adj
if mask is not None:
adj = adj.to(mask.device) * mask
x = torch.einsum('nm, mbc->nbc', adj.to(x.device), x) # 4*N, B, Cin
if self.activation == 'GLU':
lhs_rhs = self.FC(x) # 4*N, B, 2*Cout
lhs, rhs = torch.split(lhs_rhs, self.out_dim, dim=-1) # 4*N, B, Cout
out = lhs * torch.sigmoid(rhs)
del lhs, rhs, lhs_rhs
return out
elif self.activation == 'relu':
return torch.relu(self.FC(x)) # 3*N, B, Cout
class STSGCM(nn.Module):
def __init__(self, adj, in_dim, out_dims, num_of_vertices, activation='GLU'):
"""
:param adj: 邻接矩阵
:param in_dim: 输入维度
:param out_dims: list 各个图卷积的输出维度
:param num_of_vertices: 节点数量
:param activation: 激活方式 {'relu', 'GLU'}
"""
super(STSGCM, self).__init__()
self.adj = adj
self.in_dim = in_dim
self.out_dims = out_dims
self.num_of_vertices = num_of_vertices
self.activation = activation
self.gcn_operations = nn.ModuleList()
self.gcn_operations.append(
gcn_operation(
adj=self.adj,
in_dim=self.in_dim,
out_dim=self.out_dims[0],
num_vertices=self.num_of_vertices,
activation=self.activation
)
)
for i in range(1, len(self.out_dims)):
self.gcn_operations.append(
gcn_operation(
adj=self.adj,
in_dim=self.out_dims[i-1],
out_dim=self.out_dims[i],
num_vertices=self.num_of_vertices,
activation=self.activation
)
)
def forward(self, x, mask=None):
"""
:param x: (3N, B, Cin)
:param mask: (3N, 3N)
:return: (N, B, Cout)
"""
need_concat = []
for i in range(len(self.out_dims)):
x = self.gcn_operations[i](x, mask)
need_concat.append(x)
# shape of each element is (1, N, B, Cout)
need_concat = [
torch.unsqueeze(
h[self.num_of_vertices: 2 * self.num_of_vertices], dim=0
) for h in need_concat
]
out = torch.max(torch.cat(need_concat, dim=0), dim=0).values # (N, B, Cout)
del need_concat
return out
class STSGCL(nn.Module):
def __init__(self,
adj,
history,
num_of_vertices,
in_dim,
out_dims,
strides=4,
activation='GLU',
temporal_emb=True,
spatial_emb=True):
"""
:param adj: 邻接矩阵
:param history: 输入时间步长
:param in_dim: 输入维度
:param out_dims: list 各个图卷积的输出维度
:param strides: 滑动窗口步长,local时空图使用几个时间步构建的,默认为3
:param num_of_vertices: 节点数量
:param activation: 激活方式 {'relu', 'GLU'}
:param temporal_emb: 加入时间位置嵌入向量
:param spatial_emb: 加入空间位置嵌入向量
"""
super(STSGCL, self).__init__()
self.adj = adj
self.strides = strides
self.history = history
self.in_dim = in_dim
self.out_dims = out_dims
self.num_of_vertices = num_of_vertices
self.activation = activation
self.temporal_emb = temporal_emb
self.spatial_emb = spatial_emb
self.conv1 = nn.Conv1d(self.in_dim, self.out_dims[-1], kernel_size=(1, 2), stride=(1, 1), dilation=(1, 3))
self.conv2 = nn.Conv1d(self.in_dim, self.out_dims[-1], kernel_size=(1, 2), stride=(1, 1), dilation=(1, 3))
self.STSGCMS = nn.ModuleList()
for i in range(self.history - self.strides + 1):
self.STSGCMS.append(
STSGCM(
adj=self.adj,
in_dim=self.in_dim,
out_dims=self.out_dims,
num_of_vertices=self.num_of_vertices,
activation=self.activation
)
)
if self.temporal_emb:
self.temporal_embedding = nn.Parameter(torch.FloatTensor(1, self.history, 1, self.in_dim))
# 1, T, 1, Cin
if self.spatial_emb:
self.spatial_embedding = nn.Parameter(torch.FloatTensor(1, 1, self.num_of_vertices, self.in_dim))
# 1, 1, N, Cin
self.reset()
def reset(self):
if self.temporal_emb:
nn.init.xavier_normal_(self.temporal_embedding, gain=0.0003)
if self.spatial_emb:
nn.init.xavier_normal_(self.spatial_embedding, gain=0.0003)
def forward(self, x, mask=None):
"""
:param x: B, T, N, Cin
:param mask: (N, N)
:return: B, T-3, N, Cout
"""
if self.temporal_emb:
x = x + self.temporal_embedding
if self.spatial_emb:
x = x + self.spatial_embedding
#############################################
# shape is (B, C, N, T)
data_temp = x.permute(0, 3, 2, 1)
data_left = torch.sigmoid(self.conv1(data_temp))
data_right = torch.tanh(self.conv2(data_temp))
data_time_axis = data_left * data_right
data_res = data_time_axis.permute(0, 3, 2, 1)
# shape is (B, T-3, N, C)
#############################################
need_concat = []
batch_size = x.shape[0]
for i in range(self.history - self.strides + 1):
t = x[:, i: i+self.strides, :, :] # (B, 4, N, Cin)
t = torch.reshape(t, shape=[batch_size, self.strides * self.num_of_vertices, self.in_dim])
# (B, 4*N, Cin)
t = self.STSGCMS[i](t.permute(1, 0, 2), mask) # (4*N, B, Cin) -> (N, B, Cout)
t = torch.unsqueeze(t.permute(1, 0, 2), dim=1) # (N, B, Cout) -> (B, N, Cout) ->(B, 1, N, Cout)
need_concat.append(t)
mid_out = torch.cat(need_concat, dim=1) # (B, T-3, N, Cout)
out = mid_out + data_res
del need_concat, batch_size
return out
class output_layer(nn.Module):
def __init__(self, num_of_vertices, history, in_dim, out_dim,
hidden_dim=128, horizon=12):
"""
预测层,注意在作者的实验中是对每一个预测时间step做处理的,也即他会令horizon=1
:param num_of_vertices:节点数
:param history:输入时间步长
:param in_dim: 输入维度
:param hidden_dim:中间层维度
:param horizon:预测时间步长
"""
super(output_layer, self).__init__()
self.num_of_vertices = num_of_vertices
self.history = history
self.in_dim = in_dim
self.out_dim = out_dim
self.hidden_dim = hidden_dim
self.horizon = horizon
#print("#####################")
#print(self.in_dim)
#print(self.history)
#print(self.hidden_dim)
self.FC1 = nn.Linear(self.in_dim * self.history, self.hidden_dim, bias=True)
#self.FC2 = nn.Linear(self.hidden_dim, self.horizon , bias=True)
self.FC2 = nn.Linear(self.hidden_dim, self.horizon * self.out_dim, bias=True)
def forward(self, x):
"""
:param x: (B, Tin, N, Cin)
:return: (B, Tout, N)
"""
batch_size = x.shape[0]
x = x.permute(0, 2, 1, 3) # B, N, Tin, Cin
out1 = torch.relu(self.FC1(x.reshape(batch_size, self.num_of_vertices, -1)))
# (B, N, Tin, Cin) -> (B, N, Tin * Cin) -> (B, N, hidden)
out2 = self.FC2(out1) # (B, N, hidden) -> (B, N, horizon * 2)
out2 = out2.reshape(batch_size, self.num_of_vertices, self.horizon, self.out_dim)
del out1, batch_size
return out2.permute(0, 2, 1, 3) # B, horizon, N
# return out2.permute(0, 2, 1) # B, horizon, N
class STFGNN(nn.Module):
def __init__(self, config, data_feature):
"""
:param adj: local时空间矩阵
:param history:输入时间步长
:param num_of_vertices:节点数量
:param in_dim:输入维度
:param hidden_dims: lists, 中间各STSGCL层的卷积操作维度
:param first_layer_embedding_size: 第一层输入层的维度
:param out_layer_dim: 输出模块中间层维度
:param activation: 激活函数 {relu, GlU}
:param use_mask: 是否使用mask矩阵对adj进行优化
:param temporal_emb:是否使用时间嵌入向量
:param spatial_emb:是否使用空间嵌入向量
:param horizon:预测时间步长
:param strides:滑动窗口步长,local时空图使用几个时间步构建的,默认为4
"""
super(STFGNN, self).__init__()
self.config = config
self.data_feature = data_feature
self.scaler = data_feature["scaler"]
self.num_batches = data_feature["num_batches"]
adj = self.data_feature["adj_mx"]
history = self.config.get("window", 12)
num_of_vertices = self.config.get("num_nodes", None)
in_dim = self.config.get("input_dim", 1)
out_dim = self.config.get("output_dim", 1)
hidden_dims = self.config.get("hidden_dims", None)
first_layer_embedding_size = self.config.get("first_layer_embedding_size", None)
out_layer_dim = self.config.get("out_layer_dim", None)
activation = self.config.get("activation", "GLU")
use_mask = self.config.get("mask")
temporal_emb = self.config.get("temporal_emb", True)
spatial_emb = self.config.get("spatial_emb", True)
horizon = self.config.get("horizon", 12)
strides = self.config.get("strides", 4)
self.adj = adj
self.num_of_vertices = num_of_vertices
self.hidden_dims = hidden_dims
self.out_layer_dim = out_layer_dim
self.activation = activation
self.use_mask = use_mask
self.temporal_emb = temporal_emb
self.spatial_emb = spatial_emb
self.horizon = horizon
self.strides = 4
self.First_FC = nn.Linear(in_dim, first_layer_embedding_size, bias=True)
self.STSGCLS = nn.ModuleList()
#print("____________________")
#print(history)
self.STSGCLS.append(
STSGCL(
adj=self.adj,
history=history,
num_of_vertices=self.num_of_vertices,
in_dim=first_layer_embedding_size,
out_dims=self.hidden_dims[0],
strides=self.strides,
activation=self.activation,
temporal_emb=self.temporal_emb,
spatial_emb=self.spatial_emb
)
)
in_dim = self.hidden_dims[0][-1]
history -= (self.strides - 1)
#print("!!!!!!!!!!!!!!!!!!!")
#print(history)
for idx, hidden_list in enumerate(self.hidden_dims):
#print("?????? ", idx)
if idx == 0:
continue
#print("---------", idx)
self.STSGCLS.append(
STSGCL(
adj=self.adj,
history=history,
num_of_vertices=self.num_of_vertices,
in_dim=in_dim,
out_dims=hidden_list,
strides=self.strides,
activation=self.activation,
temporal_emb=self.temporal_emb,
spatial_emb=self.spatial_emb
)
)
history -= (self.strides - 1)
in_dim = hidden_list[-1]
self.predictLayer = nn.ModuleList()
#print("***********************")
#print(history)
for t in range(self.horizon):
self.predictLayer.append(
output_layer(
num_of_vertices=self.num_of_vertices,
history=history,
in_dim=in_dim,
out_dim = out_dim,
hidden_dim=out_layer_dim,
horizon=1
)
)
if self.use_mask:
mask = torch.zeros_like(self.adj)
mask[self.adj != 0] = self.adj[self.adj != 0]
self.mask = nn.Parameter(mask)
else:
self.mask = None
def forward(self, x):
"""
:param x: B, Tin, N, Cin)
:return: B, Tout, N
"""
x = torch.relu(self.First_FC(x)) # B, Tin, N, Cin
#print(1)
for model in self.STSGCLS:
x = model(x, self.mask)
# (B, T - 8, N, Cout)
#print(2)
need_concat = []
for i in range(self.horizon):
out_step = self.predictLayer[i](x) # (B, 1, N, 2)
need_concat.append(out_step)
#print(3)
out = torch.cat(need_concat, dim=1) # B, Tout, N, 2
del need_concat
return out
</code>
<code> class gcn_operation(nn.Module): def __init__(self, adj, in_dim, out_dim, num_vertices, activation='GLU'): """ 图卷积模块 :param adj: 邻接图 :param in_dim: 输入维度 :param out_dim: 输出维度 :param num_vertices: 节点数量 :param activation: 激活方式 {'relu', 'GLU'} """ super(gcn_operation, self).__init__() self.adj = adj self.in_dim = in_dim self.out_dim = out_dim self.num_vertices = num_vertices self.activation = activation assert self.activation in {'GLU', 'relu'} if self.activation == 'GLU': self.FC = nn.Linear(self.in_dim, 2 * self.out_dim, bias=True) else: self.FC = nn.Linear(self.in_dim, self.out_dim, bias=True) def forward(self, x, mask=None): """ :param x: (3*N, B, Cin) :param mask:(3*N, 3*N) :return: (3*N, B, Cout) """ adj = self.adj if mask is not None: adj = adj.to(mask.device) * mask x = torch.einsum('nm, mbc->nbc', adj.to(x.device), x) # 4*N, B, Cin if self.activation == 'GLU': lhs_rhs = self.FC(x) # 4*N, B, 2*Cout lhs, rhs = torch.split(lhs_rhs, self.out_dim, dim=-1) # 4*N, B, Cout out = lhs * torch.sigmoid(rhs) del lhs, rhs, lhs_rhs return out elif self.activation == 'relu': return torch.relu(self.FC(x)) # 3*N, B, Cout class STSGCM(nn.Module): def __init__(self, adj, in_dim, out_dims, num_of_vertices, activation='GLU'): """ :param adj: 邻接矩阵 :param in_dim: 输入维度 :param out_dims: list 各个图卷积的输出维度 :param num_of_vertices: 节点数量 :param activation: 激活方式 {'relu', 'GLU'} """ super(STSGCM, self).__init__() self.adj = adj self.in_dim = in_dim self.out_dims = out_dims self.num_of_vertices = num_of_vertices self.activation = activation self.gcn_operations = nn.ModuleList() self.gcn_operations.append( gcn_operation( adj=self.adj, in_dim=self.in_dim, out_dim=self.out_dims[0], num_vertices=self.num_of_vertices, activation=self.activation ) ) for i in range(1, len(self.out_dims)): self.gcn_operations.append( gcn_operation( adj=self.adj, in_dim=self.out_dims[i-1], out_dim=self.out_dims[i], num_vertices=self.num_of_vertices, activation=self.activation ) ) def forward(self, x, mask=None): """ :param x: (3N, B, Cin) :param mask: (3N, 3N) :return: (N, B, Cout) """ need_concat = [] for i in range(len(self.out_dims)): x = self.gcn_operations[i](x, mask) need_concat.append(x) # shape of each element is (1, N, B, Cout) need_concat = [ torch.unsqueeze( h[self.num_of_vertices: 2 * self.num_of_vertices], dim=0 ) for h in need_concat ] out = torch.max(torch.cat(need_concat, dim=0), dim=0).values # (N, B, Cout) del need_concat return out class STSGCL(nn.Module): def __init__(self, adj, history, num_of_vertices, in_dim, out_dims, strides=4, activation='GLU', temporal_emb=True, spatial_emb=True): """ :param adj: 邻接矩阵 :param history: 输入时间步长 :param in_dim: 输入维度 :param out_dims: list 各个图卷积的输出维度 :param strides: 滑动窗口步长,local时空图使用几个时间步构建的,默认为3 :param num_of_vertices: 节点数量 :param activation: 激活方式 {'relu', 'GLU'} :param temporal_emb: 加入时间位置嵌入向量 :param spatial_emb: 加入空间位置嵌入向量 """ super(STSGCL, self).__init__() self.adj = adj self.strides = strides self.history = history self.in_dim = in_dim self.out_dims = out_dims self.num_of_vertices = num_of_vertices self.activation = activation self.temporal_emb = temporal_emb self.spatial_emb = spatial_emb self.conv1 = nn.Conv1d(self.in_dim, self.out_dims[-1], kernel_size=(1, 2), stride=(1, 1), dilation=(1, 3)) self.conv2 = nn.Conv1d(self.in_dim, self.out_dims[-1], kernel_size=(1, 2), stride=(1, 1), dilation=(1, 3)) self.STSGCMS = nn.ModuleList() for i in range(self.history - self.strides + 1): self.STSGCMS.append( STSGCM( adj=self.adj, in_dim=self.in_dim, out_dims=self.out_dims, num_of_vertices=self.num_of_vertices, activation=self.activation ) ) if self.temporal_emb: self.temporal_embedding = nn.Parameter(torch.FloatTensor(1, self.history, 1, self.in_dim)) # 1, T, 1, Cin if self.spatial_emb: self.spatial_embedding = nn.Parameter(torch.FloatTensor(1, 1, self.num_of_vertices, self.in_dim)) # 1, 1, N, Cin self.reset() def reset(self): if self.temporal_emb: nn.init.xavier_normal_(self.temporal_embedding, gain=0.0003) if self.spatial_emb: nn.init.xavier_normal_(self.spatial_embedding, gain=0.0003) def forward(self, x, mask=None): """ :param x: B, T, N, Cin :param mask: (N, N) :return: B, T-3, N, Cout """ if self.temporal_emb: x = x + self.temporal_embedding if self.spatial_emb: x = x + self.spatial_embedding ############################################# # shape is (B, C, N, T) data_temp = x.permute(0, 3, 2, 1) data_left = torch.sigmoid(self.conv1(data_temp)) data_right = torch.tanh(self.conv2(data_temp)) data_time_axis = data_left * data_right data_res = data_time_axis.permute(0, 3, 2, 1) # shape is (B, T-3, N, C) ############################################# need_concat = [] batch_size = x.shape[0] for i in range(self.history - self.strides + 1): t = x[:, i: i+self.strides, :, :] # (B, 4, N, Cin) t = torch.reshape(t, shape=[batch_size, self.strides * self.num_of_vertices, self.in_dim]) # (B, 4*N, Cin) t = self.STSGCMS[i](t.permute(1, 0, 2), mask) # (4*N, B, Cin) -> (N, B, Cout) t = torch.unsqueeze(t.permute(1, 0, 2), dim=1) # (N, B, Cout) -> (B, N, Cout) ->(B, 1, N, Cout) need_concat.append(t) mid_out = torch.cat(need_concat, dim=1) # (B, T-3, N, Cout) out = mid_out + data_res del need_concat, batch_size return out class output_layer(nn.Module): def __init__(self, num_of_vertices, history, in_dim, out_dim, hidden_dim=128, horizon=12): """ 预测层,注意在作者的实验中是对每一个预测时间step做处理的,也即他会令horizon=1 :param num_of_vertices:节点数 :param history:输入时间步长 :param in_dim: 输入维度 :param hidden_dim:中间层维度 :param horizon:预测时间步长 """ super(output_layer, self).__init__() self.num_of_vertices = num_of_vertices self.history = history self.in_dim = in_dim self.out_dim = out_dim self.hidden_dim = hidden_dim self.horizon = horizon #print("#####################") #print(self.in_dim) #print(self.history) #print(self.hidden_dim) self.FC1 = nn.Linear(self.in_dim * self.history, self.hidden_dim, bias=True) #self.FC2 = nn.Linear(self.hidden_dim, self.horizon , bias=True) self.FC2 = nn.Linear(self.hidden_dim, self.horizon * self.out_dim, bias=True) def forward(self, x): """ :param x: (B, Tin, N, Cin) :return: (B, Tout, N) """ batch_size = x.shape[0] x = x.permute(0, 2, 1, 3) # B, N, Tin, Cin out1 = torch.relu(self.FC1(x.reshape(batch_size, self.num_of_vertices, -1))) # (B, N, Tin, Cin) -> (B, N, Tin * Cin) -> (B, N, hidden) out2 = self.FC2(out1) # (B, N, hidden) -> (B, N, horizon * 2) out2 = out2.reshape(batch_size, self.num_of_vertices, self.horizon, self.out_dim) del out1, batch_size return out2.permute(0, 2, 1, 3) # B, horizon, N # return out2.permute(0, 2, 1) # B, horizon, N class STFGNN(nn.Module): def __init__(self, config, data_feature): """ :param adj: local时空间矩阵 :param history:输入时间步长 :param num_of_vertices:节点数量 :param in_dim:输入维度 :param hidden_dims: lists, 中间各STSGCL层的卷积操作维度 :param first_layer_embedding_size: 第一层输入层的维度 :param out_layer_dim: 输出模块中间层维度 :param activation: 激活函数 {relu, GlU} :param use_mask: 是否使用mask矩阵对adj进行优化 :param temporal_emb:是否使用时间嵌入向量 :param spatial_emb:是否使用空间嵌入向量 :param horizon:预测时间步长 :param strides:滑动窗口步长,local时空图使用几个时间步构建的,默认为4 """ super(STFGNN, self).__init__() self.config = config self.data_feature = data_feature self.scaler = data_feature["scaler"] self.num_batches = data_feature["num_batches"] adj = self.data_feature["adj_mx"] history = self.config.get("window", 12) num_of_vertices = self.config.get("num_nodes", None) in_dim = self.config.get("input_dim", 1) out_dim = self.config.get("output_dim", 1) hidden_dims = self.config.get("hidden_dims", None) first_layer_embedding_size = self.config.get("first_layer_embedding_size", None) out_layer_dim = self.config.get("out_layer_dim", None) activation = self.config.get("activation", "GLU") use_mask = self.config.get("mask") temporal_emb = self.config.get("temporal_emb", True) spatial_emb = self.config.get("spatial_emb", True) horizon = self.config.get("horizon", 12) strides = self.config.get("strides", 4) self.adj = adj self.num_of_vertices = num_of_vertices self.hidden_dims = hidden_dims self.out_layer_dim = out_layer_dim self.activation = activation self.use_mask = use_mask self.temporal_emb = temporal_emb self.spatial_emb = spatial_emb self.horizon = horizon self.strides = 4 self.First_FC = nn.Linear(in_dim, first_layer_embedding_size, bias=True) self.STSGCLS = nn.ModuleList() #print("____________________") #print(history) self.STSGCLS.append( STSGCL( adj=self.adj, history=history, num_of_vertices=self.num_of_vertices, in_dim=first_layer_embedding_size, out_dims=self.hidden_dims[0], strides=self.strides, activation=self.activation, temporal_emb=self.temporal_emb, spatial_emb=self.spatial_emb ) ) in_dim = self.hidden_dims[0][-1] history -= (self.strides - 1) #print("!!!!!!!!!!!!!!!!!!!") #print(history) for idx, hidden_list in enumerate(self.hidden_dims): #print("?????? ", idx) if idx == 0: continue #print("---------", idx) self.STSGCLS.append( STSGCL( adj=self.adj, history=history, num_of_vertices=self.num_of_vertices, in_dim=in_dim, out_dims=hidden_list, strides=self.strides, activation=self.activation, temporal_emb=self.temporal_emb, spatial_emb=self.spatial_emb ) ) history -= (self.strides - 1) in_dim = hidden_list[-1] self.predictLayer = nn.ModuleList() #print("***********************") #print(history) for t in range(self.horizon): self.predictLayer.append( output_layer( num_of_vertices=self.num_of_vertices, history=history, in_dim=in_dim, out_dim = out_dim, hidden_dim=out_layer_dim, horizon=1 ) ) if self.use_mask: mask = torch.zeros_like(self.adj) mask[self.adj != 0] = self.adj[self.adj != 0] self.mask = nn.Parameter(mask) else: self.mask = None def forward(self, x): """ :param x: B, Tin, N, Cin) :return: B, Tout, N """ x = torch.relu(self.First_FC(x)) # B, Tin, N, Cin #print(1) for model in self.STSGCLS: x = model(x, self.mask) # (B, T - 8, N, Cout) #print(2) need_concat = [] for i in range(self.horizon): out_step = self.predictLayer[i](x) # (B, 1, N, 2) need_concat.append(out_step) #print(3) out = torch.cat(need_concat, dim=1) # B, Tout, N, 2 del need_concat return out </code>

class gcn_operation(nn.Module):
    def __init__(self, adj, in_dim, out_dim, num_vertices, activation='GLU'):
        """
        图卷积模块
        :param adj: 邻接图
        :param in_dim: 输入维度
        :param out_dim: 输出维度
        :param num_vertices: 节点数量
        :param activation: 激活方式 {'relu', 'GLU'}
        """
        super(gcn_operation, self).__init__()
        self.adj = adj
        self.in_dim = in_dim
        self.out_dim = out_dim
        self.num_vertices = num_vertices
        self.activation = activation

        assert self.activation in {'GLU', 'relu'}

        if self.activation == 'GLU':
            self.FC = nn.Linear(self.in_dim, 2 * self.out_dim, bias=True)
        else:
            self.FC = nn.Linear(self.in_dim, self.out_dim, bias=True)

    def forward(self, x, mask=None):
        """
        :param x: (3*N, B, Cin)
        :param mask:(3*N, 3*N)
        :return: (3*N, B, Cout)
        """
        adj = self.adj
        if mask is not None:
            adj = adj.to(mask.device) * mask

        x = torch.einsum('nm, mbc->nbc', adj.to(x.device), x)  # 4*N, B, Cin

        if self.activation == 'GLU':
            lhs_rhs = self.FC(x)  # 4*N, B, 2*Cout
            lhs, rhs = torch.split(lhs_rhs, self.out_dim, dim=-1)  # 4*N, B, Cout

            out = lhs * torch.sigmoid(rhs)
            del lhs, rhs, lhs_rhs

            return out

        elif self.activation == 'relu':
            return torch.relu(self.FC(x))  # 3*N, B, Cout


class STSGCM(nn.Module):
    def __init__(self, adj, in_dim, out_dims, num_of_vertices, activation='GLU'):
        """
        :param adj: 邻接矩阵
        :param in_dim: 输入维度
        :param out_dims: list 各个图卷积的输出维度
        :param num_of_vertices: 节点数量
        :param activation: 激活方式 {'relu', 'GLU'}
        """
        super(STSGCM, self).__init__()
        self.adj = adj
        self.in_dim = in_dim
        self.out_dims = out_dims
        self.num_of_vertices = num_of_vertices
        self.activation = activation

        self.gcn_operations = nn.ModuleList()

        self.gcn_operations.append(
            gcn_operation(
                adj=self.adj,
                in_dim=self.in_dim,
                out_dim=self.out_dims[0],
                num_vertices=self.num_of_vertices,
                activation=self.activation
            )
        )

        for i in range(1, len(self.out_dims)):
            self.gcn_operations.append(
                gcn_operation(
                    adj=self.adj,
                    in_dim=self.out_dims[i-1],
                    out_dim=self.out_dims[i],
                    num_vertices=self.num_of_vertices,
                    activation=self.activation
                )
            )

    def forward(self, x, mask=None):
        """
        :param x: (3N, B, Cin)
        :param mask: (3N, 3N)
        :return: (N, B, Cout)
        """
        need_concat = []

        for i in range(len(self.out_dims)):
            x = self.gcn_operations[i](x, mask)
            need_concat.append(x)

        # shape of each element is (1, N, B, Cout)
        need_concat = [
            torch.unsqueeze(
                h[self.num_of_vertices: 2 * self.num_of_vertices], dim=0
            ) for h in need_concat
        ]

        out = torch.max(torch.cat(need_concat, dim=0), dim=0).values  # (N, B, Cout)

        del need_concat

        return out


class STSGCL(nn.Module):
    def __init__(self,
                 adj,
                 history,
                 num_of_vertices,
                 in_dim,
                 out_dims,
                 strides=4,
                 activation='GLU',
                 temporal_emb=True,
                 spatial_emb=True):
        """
        :param adj: 邻接矩阵
        :param history: 输入时间步长
        :param in_dim: 输入维度
        :param out_dims: list 各个图卷积的输出维度
        :param strides: 滑动窗口步长,local时空图使用几个时间步构建的,默认为3
        :param num_of_vertices: 节点数量
        :param activation: 激活方式 {'relu', 'GLU'}
        :param temporal_emb: 加入时间位置嵌入向量
        :param spatial_emb: 加入空间位置嵌入向量
        """
        super(STSGCL, self).__init__()
        self.adj = adj
        self.strides = strides
        self.history = history
        self.in_dim = in_dim
        self.out_dims = out_dims
        self.num_of_vertices = num_of_vertices

        self.activation = activation
        self.temporal_emb = temporal_emb
        self.spatial_emb = spatial_emb


        self.conv1 = nn.Conv1d(self.in_dim, self.out_dims[-1], kernel_size=(1, 2), stride=(1, 1), dilation=(1, 3))
        self.conv2 = nn.Conv1d(self.in_dim, self.out_dims[-1], kernel_size=(1, 2), stride=(1, 1), dilation=(1, 3))


        self.STSGCMS = nn.ModuleList()
        for i in range(self.history - self.strides + 1):
            self.STSGCMS.append(
                STSGCM(
                    adj=self.adj,
                    in_dim=self.in_dim,
                    out_dims=self.out_dims,
                    num_of_vertices=self.num_of_vertices,
                    activation=self.activation
                )
            )

        if self.temporal_emb:
            self.temporal_embedding = nn.Parameter(torch.FloatTensor(1, self.history, 1, self.in_dim))
            # 1, T, 1, Cin

        if self.spatial_emb:
            self.spatial_embedding = nn.Parameter(torch.FloatTensor(1, 1, self.num_of_vertices, self.in_dim))
            # 1, 1, N, Cin

        self.reset()

    def reset(self):
        if self.temporal_emb:
            nn.init.xavier_normal_(self.temporal_embedding, gain=0.0003)

        if self.spatial_emb:
            nn.init.xavier_normal_(self.spatial_embedding, gain=0.0003)

    def forward(self, x, mask=None):
        """
        :param x: B, T, N, Cin
        :param mask: (N, N)
        :return: B, T-3, N, Cout
        """
        if self.temporal_emb:
            x = x + self.temporal_embedding

        if self.spatial_emb:
            x = x + self.spatial_embedding

        #############################################
        # shape is (B, C, N, T)
        data_temp = x.permute(0, 3, 2, 1)
        data_left = torch.sigmoid(self.conv1(data_temp))
        data_right = torch.tanh(self.conv2(data_temp))
        data_time_axis = data_left * data_right
        data_res = data_time_axis.permute(0, 3, 2, 1)
        # shape is (B, T-3, N, C)
        #############################################

        need_concat = []
        batch_size = x.shape[0]

        for i in range(self.history - self.strides + 1):
            t = x[:, i: i+self.strides, :, :]  # (B, 4, N, Cin)

            t = torch.reshape(t, shape=[batch_size, self.strides * self.num_of_vertices, self.in_dim])
            # (B, 4*N, Cin)

            t = self.STSGCMS[i](t.permute(1, 0, 2), mask)  # (4*N, B, Cin) -> (N, B, Cout)

            t = torch.unsqueeze(t.permute(1, 0, 2), dim=1)  # (N, B, Cout) -> (B, N, Cout) ->(B, 1, N, Cout)

            need_concat.append(t)

        mid_out = torch.cat(need_concat, dim=1)  # (B, T-3, N, Cout)
        out = mid_out + data_res

        del need_concat, batch_size

        return out


class output_layer(nn.Module):
    def __init__(self, num_of_vertices, history, in_dim, out_dim, 
                 hidden_dim=128, horizon=12):
        """
        预测层,注意在作者的实验中是对每一个预测时间step做处理的,也即他会令horizon=1
        :param num_of_vertices:节点数
        :param history:输入时间步长
        :param in_dim: 输入维度
        :param hidden_dim:中间层维度
        :param horizon:预测时间步长
        """
        super(output_layer, self).__init__()
        self.num_of_vertices = num_of_vertices
        self.history = history
        self.in_dim = in_dim
        self.out_dim = out_dim
        self.hidden_dim = hidden_dim
        self.horizon = horizon

        #print("#####################")
        #print(self.in_dim)
        #print(self.history)
        #print(self.hidden_dim)

        self.FC1 = nn.Linear(self.in_dim * self.history, self.hidden_dim, bias=True)

        #self.FC2 = nn.Linear(self.hidden_dim, self.horizon , bias=True)

        self.FC2 = nn.Linear(self.hidden_dim, self.horizon * self.out_dim, bias=True)

    def forward(self, x):
        """
        :param x: (B, Tin, N, Cin)
        :return: (B, Tout, N)
        """
        batch_size = x.shape[0]

        x = x.permute(0, 2, 1, 3)  # B, N, Tin, Cin

        out1 = torch.relu(self.FC1(x.reshape(batch_size, self.num_of_vertices, -1)))
        # (B, N, Tin, Cin) -> (B, N, Tin * Cin) -> (B, N, hidden)

        out2 = self.FC2(out1)  # (B, N, hidden) -> (B, N, horizon * 2)

        out2 = out2.reshape(batch_size, self.num_of_vertices, self.horizon, self.out_dim)

        del out1, batch_size

        return out2.permute(0, 2, 1, 3)  # B, horizon, N
        # return out2.permute(0, 2, 1)  # B, horizon, N


class STFGNN(nn.Module):
    def __init__(self, config, data_feature):
        """

        :param adj: local时空间矩阵
        :param history:输入时间步长
        :param num_of_vertices:节点数量
        :param in_dim:输入维度
        :param hidden_dims: lists, 中间各STSGCL层的卷积操作维度
        :param first_layer_embedding_size: 第一层输入层的维度
        :param out_layer_dim: 输出模块中间层维度
        :param activation: 激活函数 {relu, GlU}
        :param use_mask: 是否使用mask矩阵对adj进行优化
        :param temporal_emb:是否使用时间嵌入向量
        :param spatial_emb:是否使用空间嵌入向量
        :param horizon:预测时间步长
        :param strides:滑动窗口步长,local时空图使用几个时间步构建的,默认为4
        """
        super(STFGNN, self).__init__()

        self.config = config
        self.data_feature = data_feature
        self.scaler = data_feature["scaler"]
        self.num_batches = data_feature["num_batches"]

        adj = self.data_feature["adj_mx"]
        history = self.config.get("window", 12)
        num_of_vertices = self.config.get("num_nodes", None)
        in_dim = self.config.get("input_dim", 1)
        out_dim = self.config.get("output_dim", 1)
        hidden_dims = self.config.get("hidden_dims", None)
        first_layer_embedding_size = self.config.get("first_layer_embedding_size", None)
        out_layer_dim = self.config.get("out_layer_dim", None)
        activation = self.config.get("activation", "GLU")
        use_mask = self.config.get("mask")
        temporal_emb = self.config.get("temporal_emb", True)
        spatial_emb = self.config.get("spatial_emb", True)
        horizon = self.config.get("horizon", 12)
        strides = self.config.get("strides", 4)

        self.adj = adj
        self.num_of_vertices = num_of_vertices
        self.hidden_dims = hidden_dims
        self.out_layer_dim = out_layer_dim
        self.activation = activation
        self.use_mask = use_mask

        self.temporal_emb = temporal_emb
        self.spatial_emb = spatial_emb
        self.horizon = horizon
        self.strides = 4

        self.First_FC = nn.Linear(in_dim, first_layer_embedding_size, bias=True)
        self.STSGCLS = nn.ModuleList()
        #print("____________________")
        #print(history)

        self.STSGCLS.append(
            STSGCL(
                adj=self.adj,
                history=history,
                num_of_vertices=self.num_of_vertices,
                in_dim=first_layer_embedding_size,
                out_dims=self.hidden_dims[0],
                strides=self.strides,
                activation=self.activation,
                temporal_emb=self.temporal_emb,
                spatial_emb=self.spatial_emb
            )
        )

        in_dim = self.hidden_dims[0][-1]
        history -= (self.strides - 1)

        #print("!!!!!!!!!!!!!!!!!!!")
        #print(history)

        for idx, hidden_list in enumerate(self.hidden_dims):
            #print("?????? ", idx)
            if idx == 0:
                continue
            #print("---------", idx)
            self.STSGCLS.append(
                STSGCL(
                    adj=self.adj,
                    history=history,
                    num_of_vertices=self.num_of_vertices,
                    in_dim=in_dim,
                    out_dims=hidden_list,
                    strides=self.strides,
                    activation=self.activation,
                    temporal_emb=self.temporal_emb,
                    spatial_emb=self.spatial_emb
                )
            )
            history -= (self.strides - 1)
            in_dim = hidden_list[-1]

        self.predictLayer = nn.ModuleList()
        #print("***********************")
        #print(history)
        for t in range(self.horizon):
            self.predictLayer.append(
                output_layer(
                    num_of_vertices=self.num_of_vertices,
                    history=history,
                    in_dim=in_dim,
                    out_dim = out_dim,
                    hidden_dim=out_layer_dim,
                    horizon=1
                )
            )

        if self.use_mask:
            mask = torch.zeros_like(self.adj)
            mask[self.adj != 0] = self.adj[self.adj != 0]
            self.mask = nn.Parameter(mask)
        else:
            self.mask = None

    def forward(self, x):
        """
        :param x: B, Tin, N, Cin)
        :return: B, Tout, N
        """

        x = torch.relu(self.First_FC(x))  # B, Tin, N, Cin
        #print(1)

        for model in self.STSGCLS:
            x = model(x, self.mask)
        # (B, T - 8, N, Cout)
        #print(2)
        need_concat = []
        for i in range(self.horizon):
            out_step = self.predictLayer[i](x)  # (B, 1, N, 2)
            need_concat.append(out_step)
        #print(3)
        out = torch.cat(need_concat, dim=1)  # B, Tout, N, 2

        del need_concat

        return out

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật