Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

一些问题和优化建议 #139

Open
TensorPulse opened this issue Aug 22, 2024 · 54 comments
Open

一些问题和优化建议 #139

TensorPulse opened this issue Aug 22, 2024 · 54 comments

Comments

@TensorPulse
Copy link

您好,作者,感谢提供如此完整的学习框架!本人在使用和移植基线的过程中遇到一些问题和不便的地方,在此提出来以便您参考优化。
声明:以下问题和建议仅代表个人看法,仅供参考
问题:利用pycham直接运行data_preparation显示找不到数据集文件,运行train时也一样,做如下修改就可以运行:
OUTPUT_DIR = "../../../experiments/datasets/" + DATASET_NAME
DATA_FILE_PATH = "../../../datasets/raw_data/{0}/{0}.npz".format(DATASET_NAME)
GRAPH_FILE_PATH = "../../../datasets/raw_data/{0}/adj_{0}".format(DATASET_NAME)
DISTANCE_FILE_PATH = "../../../datasets/raw_data/{0}/distance_{0}".format(DATASET_NAME)
优化建议:
1.数据集的归一化和反归一化:CFG.RESCALE:如果为True,表示既反归一化数据又将整个数据的标准化,如果为False,表示既不反归一化数据又将数据的每个通道标准化。可以拆解为两个变量,一个变量控制数据的标准化,一个变量控制数据和归一化和反归一化。
2. 模型训练结果表示不清:用模型名+epochs的方式所表达的直接信息不全,可做如下修改:
CFG.TRAIN.CKPT_SAVE_DIR = os.path.join(
"checkpoints",
CFG.MODEL.NAME,
"_".join([CFG.DATASET_NAME, str(CFG.TRAIN.NUM_EPOCHS)])
)
3.项目的可视化接口不足:可在tensorboard中增加一些指标或增加预测数据保存的接口
4.项目cfg文件中有许多隐藏接口,可以添加一个Simple_CFG将所有接口表达出来,例如:
CFG.MODEL.SETUP_GRAPH = False
CFG.TRAIN.FINETUNE_FROM
CFG.RESCALE = True
5.在基线STGODE中,需要引入A_sp_hat, A_se_hat两个张量,发现即使将整个模型放入gpu中,这两个张量仍然存在于cpu中,直到后续.to(x.device)。这在模型的移植中不太便利,需要找到张量最终使用的地方。建议使用
from easytorch.device import get_device_type
if get_device_type() == 'gpu':
device = 'cuda'
else:
device = 'cpu'
self.device = device
或者from easytorch.device import to_device
6.在test过程中没有进度条显示,可修改:

tqdm process bar

data_iter = tqdm(self.test_data_loader)

test loop

for iter_index, data in enumerate(data_iter):

@zezhishao
Copy link
Collaborator

非常感谢您的建议,这对我们帮助很大!
我们一直计划整体升级一版代码,但一直苦于没有时间。再次感谢您的建议,我们后续会一一修改。若您有一些已经完成的修改,可以通过PR的方式合并到主目录,成为BasicTS的开发者~

@zezhishao
Copy link
Collaborator

"tensorboard中增加一些指标",您指的是什么呢,可否给出一些具体的需求?

@TensorPulse
Copy link
Author

比如模型的计算图,框架图,权重,偏差随时间变化的直方图。
预测值,真实值,历史值的可视化,将嵌入投射到低维空间的可视化等等

@TensorPulse
Copy link
Author

个人觉得basicts论文中对ETT系列数据集的标准化处理有一点问题,因为ETT数据集的各列数据不像PEMS数据集是同一单位,导致各列的值差异有时候差异较大,采用各个通道标准化可能比整体标准化更合适,若需要在计算指标时反归一化,可参考优化建议第一条

@zezhishao
Copy link
Collaborator

zezhishao commented Aug 27, 2024

好的,感谢您的建议。我正在开发新版的BasicTS,基本已经完成,会在未来几天内发布。
后续用户将可以在训练的时候即时指定归一化方式和是否反归一化,而无需提前预处理数据。

@TensorPulse
Copy link
Author

作者您好,BasicTS能支持自动调参不?
参考链接:https://zhuanlan.zhihu.com/p/401190615?utm_id=0
https://github.com/LibCity/Bigscity-LibCity

@zezhishao
Copy link
Collaborator

您好,下一版本没有囊括自动调参功能,我对自动调参这块不是很熟悉,从我自己的经验来看这几个数据集似乎对超参数没那么敏感?

@huiguhean
Copy link

请问一下,运行train找不到FileNotFoundError: [Errno 2] No such file or directory: 'datasets/ETTh1/scaler_in_96_out_336_rescale_True.pkl',修改哪个文件夹

@huiguhean
Copy link

修改了baselines里面对应的模型数据集好了

@zezhishao
Copy link
Collaborator

您好,在目前的版本下,您还需要手动生成不同输入输出长度的数据集。您可以通过下面的指令生成:

python scripts/data_preparation/${DATASET_NAME}/generate_training_data.py --history_seq_len ${INPUT_LEN} --future_seq_len ${OUTPUT_LEN}

例如:

python scripts/data_preparation/ETTh1/generate_training_data.py  --history_seq_len 96 --future_seq_len 336

马上会更新一个版本,可以在训练的时候即时指定,敬请期待~

@huiguhean
Copy link

伟大,无需多言!

@TensorPulse
Copy link
Author

作者您好,下个版本是否有支持单变量预测的接口?

@zezhishao
Copy link
Collaborator

您所说的单变量指的是什么?是指只有一条时间序列的数据集吗?

@TensorPulse
Copy link
Author

不好意思,好像只需要重新定义runner即可。单变量预测指的是带OT变量的数据集,例如ETT

@TensorPulse
Copy link
Author

作者您好,我想请教一下BasicTS论文中长时序预测结果的历史长度和预测长度,我看代码里给的不同模型的历史长度似乎存在不一样,预测长度应该都是336,所以我想确定一下

@zezhishao
Copy link
Collaborator

是的,事实上不同的论文对于历史长度的规定是不一样的,而不同方法的最优历史长度也不一样。
我们的做法是在几个常用的历史长度中采用效果最好的那一个。

@morestart
Copy link

I noticed that easytorch is no longer maintained. Are you considering switching the backend in the new version?

@zezhishao
Copy link
Collaborator

Currently, EasyTorch is still able to meet the needs of BasicTS, so there won't be any changes in the short term. However, in the longer term, I hope that the backend of BasicTS will no longer need to rely on other packages, although this will be time-consuming.

Do you have any other needs that the current EasyTorch backend cannot satisfy?"

@zezhishao
Copy link
Collaborator

大家好,BasicTS代码已更新,欢迎大家查看并使用!

Hello, everyone! The BasicTS code has been updated. Feel free to check it out and use it!

@TensorPulse
Copy link
Author

作者您好,关于PEMS序列数据集的图结构是否有方向性?我看代码里面默认是无向图,是否可以提供一个有向图,无向图的可选项?还是说数据集本身就是无向图?
参考代码:
i, j, distance = int(row[0]), int(row[1]), float(row[2])
adjacency_matrix_connectivity[id_dict[i], id_dict[j]] = 1
adjacency_matrix_distance[id_dict[i],
id_dict[j]] = distance
if not directed:
adjacency_matrix_connectivity[id_dict[j], id_dict[i]] = 1
adjacency_matrix_distance[id_dict[j],
id_dict[i]] = distance

@zezhishao
Copy link
Collaborator

您好,PEMS0X的数据集处理脚本是数据集自带的。你可以通过在github上搜索:A[id_dict[i], id_dict[j]] = 1,你可以找到很多仓库的实现代码,比如STGCN、ASTGCN、STSGCN、STFGCN等。

@TensorPulse
Copy link
Author

作者您好,数据可视化的代码是否可以更新一下?

@zezhishao
Copy link
Collaborator

好的,忘记更新了,明天更新啊

@TensorPulse
Copy link
Author

complete_config文件中CFG.DATASET.PARAM中的overlap参数未给出。
关于重叠数据集的划分是否合理?以pems08数据集为例,在验证集与测试集比例相同的情况下,验证集比测试集的多了11条数据

@zezhishao
Copy link
Collaborator

您好,感谢您的建议,overlap参数目前已经设置默认为False并自动调整,且给出警告。
默认情况下,overlap设置为False。
当Train/Valid/Test数据对应的原始数据长度过短,无法形成足够样本的时候(例如Illeness数据集),会自动启用overlap,并给出提示。例如STID运行Illeness数据集时会产生如下log:

2024-09-13 10:27:07,383 - easytorch-launcher - INFO - Launching EasyTorch training.
DESCRIPTION: An Example Config
GPU_NUM: 1
RUNNER: <class 'basicts.runners.runner_zoo.simple_tsf_runner.SimpleTimeSeriesForecastingRunner'>
DATASET:
  NAME: Illness
  TYPE: <class 'basicts.data.simple_tsf_dataset.TimeSeriesForecastingDataset'>
  PARAM:
    dataset_name: Illness
    train_val_test_ratio: [0.7, 0.1, 0.2]
    input_len: 96
    output_len: 48
SCALER:
  TYPE: <class 'basicts.scaler.z_score_scaler.ZScoreScaler'>
  PARAM:
    dataset_name: Illness
    train_ratio: 0.7
    norm_each_channel: True
    rescale: False
MODEL:
  NAME: STID
  ARCH: <class 'baselines.STID.arch.stid_arch.STID'>
  PARAM:
    num_nodes: 7
    input_len: 96
    input_dim: 1
    embed_dim: 2048
    output_len: 48
    num_layer: 1
    if_node: True
    node_dim: 32
    if_T_i_D: True
    if_D_i_W: True
    temp_dim_tid: 8
    temp_dim_diw: 8
    time_of_day_size: 1
    day_of_week_size: 7
  FORWARD_FEATURES: [0, 1, 2]
  TARGET_FEATURES: [0]
METRICS:
  FUNCS:
    MAE: masked_mae
    MSE: masked_mse
  TARGET: MAE
  NULL_VAL: nan
TRAIN:
  NUM_EPOCHS: 100
  CKPT_SAVE_DIR: checkpoints/STID/Illness_100_96_48
  LOSS: masked_mae
  OPTIM:
    TYPE: Adam
    PARAM:
      lr: 0.0005
      weight_decay: 0.0005
  LR_SCHEDULER:
    TYPE: MultiStepLR
    PARAM:
      milestones: [1, 3, 5]
      gamma: 0.1
  CLIP_GRAD_PARAM:
    max_norm: 5.0
  DATA:
    BATCH_SIZE: 64
    SHUFFLE: True
VAL:
  INTERVAL: 1
  DATA:
    BATCH_SIZE: 64
TEST:
  INTERVAL: 1
  DATA:
    BATCH_SIZE: 64
EVAL:
  USE_GPU: True

2024-09-13 10:27:07,451 - easytorch-env - INFO - Use devices 0.
2024-09-13 10:27:07,506 - easytorch-launcher - INFO - Initializing runner "<class 'basicts.runners.runner_zoo.simple_tsf_runner.SimpleTimeSeriesForecastingRunner'>"
2024-09-13 10:27:07,506 - easytorch-env - INFO - Disable TF32 mode
2024-09-13 10:27:07,506 - easytorch - INFO - Set ckpt save dir: 'checkpoints/STID/Illness_100_96_48/9cd15181d2d202a278536bfd1f1031a0'
2024-09-13 10:27:07,506 - easytorch - INFO - Building model.
2024-09-13 10:27:07,747 - easytorch-training - INFO - Initializing training.
2024-09-13 10:27:07,747 - easytorch-training - INFO - Set clip grad, param: {'max_norm': 5.0}
2024-09-13 10:27:07,748 - easytorch-training - INFO - Building training data loader.
2024-09-13 10:27:07,748 - easytorch-training - INFO - Train dataset length: 534
2024-09-13 10:27:08,271 - easytorch-training - INFO - Set optim: Adam (
Parameter Group 0
    amsgrad: False
    betas: (0.9, 0.999)
    capturable: False
    differentiable: False
    eps: 1e-08
    foreach: None
    fused: None
    lr: 0.0005
    maximize: False
    weight_decay: 0.0005
)
2024-09-13 10:27:08,271 - easytorch-training - INFO - Set lr_scheduler: <torch.optim.lr_scheduler.MultiStepLR object at 0x7faaa1792550>
2024-09-13 10:27:08,271 - easytorch-training - INFO - Loading Checkpoint from 'checkpoints/STID/Illness_100_96_48/9cd15181d2d202a278536bfd1f1031a0/STID_100.pt'
2024-09-13 10:27:08,343 - easytorch-training - INFO - Resume training
2024-09-13 10:27:08,344 - easytorch-training - INFO - Initializing validation.
2024-09-13 10:27:08,345 - easytorch-training - INFO - Building val data loader.
2024-09-13 10:27:08,345 - easytorch-training - INFO - Validation dataset is too short, enabling overlap. See details in /home/S22/workspace/BasicTS/basicts/data/simple_tsf_dataset.py at line 96.
2024-09-13 10:27:08,345 - easytorch-training - INFO - Validation dataset length: 96
2024-09-13 10:27:08,383 - easytorch-training - INFO - Test dataset length: 50
2024-09-13 10:27:08,384 - easytorch-training - INFO - Number of parameters: 9090224
2024-09-13 10:27:08,384 - easytorch-training - INFO - The training finished at 2024-09-13 10:27:08
2024-09-13 10:27:08,384 - easytorch-training - INFO - Evaluating the best model on the test set.
2024-09-13 10:27:08,384 - easytorch-training - INFO - Loading Checkpoint from 'checkpoints/STID/Illness_100_96_48/9cd15181d2d202a278536bfd1f1031a0/STID_best_val_MAE.pt'
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  7.40it/s]
2024-09-13 10:27:08,657 - easytorch-training - INFO - Result <test>: [test_time: 0.21 (s), test_MAE: 1.3040, test_MSE: 3.2762]
2024-09-13 10:27:08,658 - easytorch-training - INFO - Test results saved to checkpoints/STID/Illness_100_96_48/9cd15181d2d202a278536bfd1f1031a0/test_results.npz.
2024-09-13 10:27:08,659 - easytorch-training - INFO - Test metrics saved to checkpoints/STID/Illness_100_96_48/9cd15181d2d202a278536bfd1f1031a0/test_metrics.json.

Validation dataset is too short, enabling overlap. See details in /home/S22/workspace/BasicTS/basicts/data/simple_tsf_dataset.py at line 96. 代表此时测试集不够长,因此将Validation数据集的overlap设置为True。

@zezhishao
Copy link
Collaborator

作者您好,数据可视化的代码是否可以更新一下?

已更新

@TensorPulse
Copy link
Author

作者您好,您刚更新的子集预测是否有一个问题?
如果选择数据反归一化,inverse_transform无法索引选择的self.select_target_time_series

@zezhishao
Copy link
Collaborator

感谢您的报告!现在应该已经可以了,您可以试一下

@zezhishao
Copy link
Collaborator

@all-contributors please add @TensorPulse for bug

Copy link
Contributor

@zezhishao

I've put up a pull request to add @TensorPulse! 🎉

@TensorPulse
Copy link
Author

作者您好,关于子集预测问题,假设模型输入为BTN1C,输出为BTN2C,BasicTS好像并不支持

@zezhishao
Copy link
Collaborator

作者您好,关于子集预测问题,假设模型输入为B_T_N1_C,输出为B_T_N2_C,BasicTS好像并不支持

麻烦您提供一下更详细的描述,比如您想要实现的具体功能和现有版本之间的差异。

@TensorPulse
Copy link
Author

作者您好,关于子集预测问题,假设模型输入为B_T_N1_C,输出为B_T_N2_C,BasicTS好像并不支持

麻烦您提供一下更详细的描述,比如您想要实现的具体功能和现有版本之间的差异。

以PEMS08为例,假设我的模型输入为[64, 12, 170, 1],输出为[64, 12, 1, 1],现有版本首先simple_tsf_runner这里会报错
assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes],
"The shape of the output is incorrect. Ensure it matches [B, L, N, C]."
其次,在数据反归一化inverse_transform无法索引选择的self.select_target_time_series

@zezhishao
Copy link
Collaborator

zezhishao commented Oct 4, 2024

您好,目前实现子集预测主要是通过SimpleTimeSeriesForecastingRunner.select_target_time_series实现的,它要求模型的输入输出的N都是相同的,否则通过CFG.MODEL.TARGET_TIME_SERIES实现。这个功能的加入主要是为了快速实现multivariate预测Univariate。

@zezhishao
Copy link
Collaborator

如果您希望模型的输出数据的N和输入数据不同,那么可以通过定制一个Datasets类实现(并取消CFG.MODEL.TARGET_TIME_SERIES的设置)。

@TensorPulse
Copy link
Author

如果您希望模型的输出数据的N和输入数据不同,那么可以通过定制一个Datasets类实现(并取消CFG.MODEL.TARGET_TIME_SERIES的设置)。

这个功能可以通过self.if_out_target_nodes参数,它默认为False, 在SimpleTimeSeriesForecastingRunner.forward下通过
if self.target_time_series is not None:
if list(model_return['prediction'].shape)[2] == len(self.target_time_series):
self.if_out_target_nodes = True
assert list(model_return['prediction'].shape)[:3] == [batch_size, length, len(self.target_time_series)],
"The shape of the output is incorrect. Ensure it matches [B, L, N, C]."
else:
assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes],
"The shape of the output is incorrect. Ensure it matches [B, L, N, C]."
else:
assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes],
"The shape of the output is incorrect. Ensure it matches [B, L, N, C]."
自动判断模型输入与输出的N,如果N不同,则self.if_out_target_nodes为True
然后在BaseTimeSeriesForecastingRunner.postprocessing中通过
# rescale data
if self.scaler is not None and self.scaler.rescale:
input_data['target'] = self.scaler.inverse_transform(input_data['target'])
input_data['inputs'] = self.scaler.inverse_transform(input_data['inputs'])
if self.if_out_target_nodes:
input_data['prediction'] = self.scaler.inverse_transform(input_data['prediction'], target_time_series=self.target_time_series)
else:
input_data['prediction'] = self.scaler.inverse_transform(input_data['prediction'])

    # subset forecasting
    if self.target_time_series is not None:
        input_data['target'] = input_data['target'][:, :, self.target_time_series, :]
        if not self.if_out_target_nodes:
            input_data['prediction'] = input_data['prediction'][:, :, self.target_time_series, :]

其中,self.scaler.inverse_transform增加一个索引参数target_time_series
这样BasicTS即可自动识别模型在子集预测中输出数据的N和输入数据的N是否相同的问题

@TensorPulse
Copy link
Author

如果您希望模型的输出数据的N和输入数据不同,那么可以通过定制一个Datasets类实现(并取消CFG.MODEL.TARGET_TIME_SERIES的设置)。

这个功能可以通过self.if_out_target_nodes参数,它默认为False, 在SimpleTimeSeriesForecastingRunner.forward下通过 if self.target_time_series is not None: if list(model_return['prediction'].shape)[2] == len(self.target_time_series): self.if_out_target_nodes = True assert list(model_return['prediction'].shape)[:3] == [batch_size, length, len(self.target_time_series)], "The shape of the output is incorrect. Ensure it matches [B, L, N, C]." else: assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes], "The shape of the output is incorrect. Ensure it matches [B, L, N, C]." else: assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes], "The shape of the output is incorrect. Ensure it matches [B, L, N, C]." 自动判断模型输入与输出的N,如果N不同,则self.if_out_target_nodes为True 然后在BaseTimeSeriesForecastingRunner.postprocessing中通过 # rescale data if self.scaler is not None and self.scaler.rescale: input_data['target'] = self.scaler.inverse_transform(input_data['target']) input_data['inputs'] = self.scaler.inverse_transform(input_data['inputs']) if self.if_out_target_nodes: input_data['prediction'] = self.scaler.inverse_transform(input_data['prediction'], target_time_series=self.target_time_series) else: input_data['prediction'] = self.scaler.inverse_transform(input_data['prediction'])

    # subset forecasting
    if self.target_time_series is not None:
        input_data['target'] = input_data['target'][:, :, self.target_time_series, :]
        if not self.if_out_target_nodes:
            input_data['prediction'] = input_data['prediction'][:, :, self.target_time_series, :]

其中,self.scaler.inverse_transform增加一个索引参数target_time_series 这样BasicTS即可自动识别模型在子集预测中输出数据的N和输入数据的N是否相同的问题

self.scaler.inverse_transform的具体实现方式如下:
def inverse_transform(self, input_data: torch.Tensor, target_time_series: List = None) -> torch.Tensor:
mean = self.mean.to(input_data.device)
std = self.std.to(input_data.device)
if target_time_series is not None:
mean = mean[:, target_time_series]
std = std[:, target_time_series]
# Clone the input data to prevent in-place modification (which is not allowed in PyTorch)
input_data = input_data.clone()
input_data[..., self.target_channel] = input_data[..., self.target_channel] * std + mean
return input_data

@zezhishao
Copy link
Collaborator

感谢您的提议,但我还是没搞明白这么做的必要性是什么。麻烦您用markdown语法给出完整的代码,目前这个格式完全乱了。

@TensorPulse
Copy link
Author

TensorPulse commented Oct 6, 2024

感谢您的提议,但我还是没搞明白这么做的必要性是什么。麻烦您用markdown语法给出完整的代码,目前这个格式完全乱了。

这样做的目的是为了适应单变量预测模型,以ETTh1数据集为例,当预测变量为OT,其他为辅助变量时,有些单变量的模型输出为[B,T,1,1],这样做可以适应这种模型的移植。
#SimpleTimeSeriesForecastingRunner.forward

# Ensure the output shape is correct
if self.target_time_series is not None:
    if list(model_return['prediction'].shape)[2] == len(self.target_time_series):
        self.if_out_target_nodes = True
        assert list(model_return['prediction'].shape)[:3] == [batch_size, length, len(self.target_time_series)], \
            "The shape of the output is incorrect. Ensure it matches [B, L, N, C]."
    else:
        assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes], \
            "The shape of the output is incorrect. Ensure it matches [B, L, N, C]."
else:
    assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes], \
        "The shape of the output is incorrect. Ensure it matches [B, L, N, C]."

BaseTimeSeriesForecastingRunner.postprocessing,在初始化中增加self.if_out_target_nodes = False

      # rescale data
      if self.scaler is not None and self.scaler.rescale:
          input_data['target'] = self.scaler.inverse_transform(input_data['target'])
          input_data['inputs'] = self.scaler.inverse_transform(input_data['inputs'])
          if self.if_out_target_nodes:
              input_data['prediction'] = self.scaler.inverse_transform(input_data['prediction'], target_time_series=self.target_time_series)
          else:
              input_data['prediction'] = self.scaler.inverse_transform(input_data['prediction'])

      # subset forecasting
      if self.target_time_series is not None:
          input_data['target'] = input_data['target'][:, :, self.target_time_series, :]
          if not self.if_out_target_nodes:
              input_data['prediction'] = input_data['prediction'][:, :, self.target_time_series, :]
  #self.scaler.inverse_transform
     def inverse_transform(self, input_data: torch.Tensor, target_time_series: List = None) -> torch.Tensor:
             mean = self.mean.to(input_data.device)
             std = self.std.to(input_data.device)
             if target_time_series is not None:
                 mean = mean[:, target_time_series]
                 std = std[:, target_time_series]
             input_data = input_data.clone()
             input_data[..., self.target_channel] = input_data[..., self.target_channel] * std + mean
             return input_data

@zezhishao
Copy link
Collaborator

现在已经支持了,只需要设置CFG.MODEL.TARGET_TIME_SERIES=[6] (以ETT为例)应该就能实现。目前的实现方式是哪里不满足您的使用场景吗?

@TensorPulse
Copy link
Author

现在已经支持了,只需要设置CFG.MODEL.TARGET_TIME_SERIES=[6] (以ETT为例)应该就能实现。目前的实现方式是哪里不满足您的使用场景吗?

是的,使用场景要求模型的输出特征为1

@zezhishao
Copy link
Collaborator

您好,我仔细思考了一下,非常感谢您的提议,但您的需求不太适合通过直接修改现有架构实现(同一功能的两种实现会引起误会)。

但您可以通过非侵入式的方式快速实现您的需求:

  1. 定义新的Runner。继承SimpleTimeSeriesForecastingRunner并重载postprocessing函数。
  2. 定义新的Scaler。可以继承现有的Scaler并重载inverse_transform函数。
  3. 定义新的Datasets(如果您的待预测样本和历史样本的N大小不同、含义不同的话)

@TensorPulse
Copy link
Author

您好,我仔细思考了一下,非常感谢您的提议,但您的需求不太适合通过直接修改现有架构实现(同一功能的两种实现会引起误会)。

但您可以通过非侵入式的方式快速实现您的需求:

  1. 定义新的Runner。继承SimpleTimeSeriesForecastingRunner并重载postprocessing函数。
  2. 定义新的Scaler。可以继承现有的Scaler并重载inverse_transform函数。
  3. 定义新的Datasets(如果您的待预测样本和历史样本的N大小不同、含义不同的话)

确实,这种特殊的适应场景会引起误解。感谢作者您提供完整的非侵入式的思路

@zezhishao
Copy link
Collaborator

您好,我仔细思考了一下,非常感谢您的提议,但您的需求不太适合通过直接修改现有架构实现(同一功能的两种实现会引起误会)。
但您可以通过非侵入式的方式快速实现您的需求:

  1. 定义新的Runner。继承SimpleTimeSeriesForecastingRunner并重载postprocessing函数。
  2. 定义新的Scaler。可以继承现有的Scaler并重载inverse_transform函数。
  3. 定义新的Datasets(如果您的待预测样本和历史样本的N大小不同、含义不同的话)

确实,这种特殊的适应场景会引起误解。感谢作者您提供完整的非侵入式的思路

感觉您应该也可以通过在模型输出位置,添加一个复制操作来兼容现有的框架。

比如,假设您的模型输出prediction的维度是B, L,可以通过 prediction = prediction.unsqueeze(-1).unsqueeze(-1).repeat(1, 1, self.num_nodes, 1)来得到符合当前架构的输出。
此时在config里面设置CFG.MODEL.TARGET_TIME_SERIES为正确的索引,应该就可以了。

虽然反归一化的时候依旧会在非TARGET_TIME_SERIES上进行计算,但在计算指标(以及loss)的时候只取出了目标时间序列,对最终结果不会造成影响,只是做了一些多余的计算。

@TensorPulse
Copy link
Author

    # Ensure the output shape is correct
    assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes], \
        "The shape of the output is incorrect. Ensure it matches [B, L, N, C]."

作者您好,请问这里为什么没有判断通道数C的大小

@zezhishao
Copy link
Collaborator

因为输入的C和输出的C一般是不一样的,输出的C一般是1,输入的C除了1之外还有一些temporal feature。

@TensorPulse
Copy link
Author

TensorPulse commented Nov 24, 2024

作者您好,我在现有的basicts框架下利用nni自动调参工具对STID做了一个简易的实现,希望能对您有所帮助。
下载nni:pip install nni
1:需在experiments新建一个.yml和一个.json文件,其中.yml用于配置nni,.json定义搜索空间。
#这里我命名.yml文件为train_nni.yml

experimentName: exp-Linear
trialConcurrency: 2
maxExperimentDuration: 100h
maxTrialNumber: 120
searchSpaceFile: search_space.json
useAnnotation: false
trialCommand: python -u train.py
trialCodeDirectory: .
trialGpuNumber: 1
tuner:
  name: GridSearch
  classArgs:
    optimize_mode: minimize
assessor:
  name: Curvefitting
trainingService:
  platform: local
  maxTrialNumberPerGpu: 2
  useActiveGpu: true

#这里我命名.json文件为search_space.json

{
   "num_layer":{"_type":"choice","_value":[2,3,4]} #这里假设对STID中的num_layer参数调参
}

2:在base_epoch_runner.py中的self.to_running_device = to_device和self.model_name = cfg['MODEL.NAME']中间加入如下代码

import nni
from easytorch.config import init_cfg
def replace_nested_values(nested_dict, flat_dict):
    """
    用单层字典中的值替换多层嵌套字典中的相同键的值。

    :param nested_dict: 多层嵌套字典
    :param flat_dict: 单层字典
    :return: 替换后的嵌套字典
    """
    for key, value in nested_dict.items():
        if isinstance(value, dict):  # 如果值是字典,递归处理
            nested_dict[key] = replace_nested_values(value, flat_dict)
        elif key in flat_dict:  # 如果键在单层字典中,用单层字典的值替换
            nested_dict[key] = flat_dict[key]
    return nested_dict

        self.if_nni = cfg.get('NNI', False) #self.if_nni为cfg文件中新增的一个参数,用于控制是否启用nni,我们将其打开CFG.NNI = True
        if self.if_nni:
            params = nni.get_next_parameter()
            cfg = replace_nested_values(cfg, params)
            self.logger.info('Last md5: \'{}\''.format(cfg['MD5']))
            cfg['MD5'] = None
            cfg = init_cfg(cfg, get_local_rank() == 0)
            self.logger.info('Set md5: \'{}\''.format(cfg['MD5']))

在base_tsf_runner.py中的test函数中的metrics_results = self.compute_evaluation_metrics(returns_all)和if save_results:中间加入如下代码

import nni

        if self.if_nni:
            nni.report_intermediate_result(float(metrics_results['overall'][self.target_metrics]))

还是test函数的最后加入如下代码

        if save_metrics:
            # save metrics_results to self.ckpt_save_dir/test_metrics.json
            with open(os.path.join(self.ckpt_save_dir, 'test_metrics.json'), 'w') as f:
                json.dump(metrics_results, f, indent=4)
            # LLZ
            if self.if_nni:
                nni.report_final_result(float(metrics_results['overall'][self.target_metrics]))

3在终端中输入nnictl create --config .....\train_nni.yml,其中--config后面是train_nni.yml文件所在的路径即可运行,打开localhost:8080网站即可看到可视化的结果界面
现在nni好像不支持多卡?

@1229308805
Copy link

现在已经支持了,只需要设置CFG.MODEL.TARGET_TIME_SERIES=[6] (以ETT为例)应该就能实现。目前的实现方式是哪里不满足您的使用场景吗?

您好,我仔细思考了一下,非常感谢您的提议,但您的需求不太适合通过直接修改现有架构实现(同一功能的两种实现会引起误会)。

但您可以通过非侵入式的方式快速实现您的需求:

  1. 定义新的Runner。继承SimpleTimeSeriesForecastingRunner并重载postprocessing函数。
  2. 定义新的Scaler。可以继承现有的Scaler并重载inverse_transform函数。
  3. 定义新的Datasets(如果您的待预测样本和历史样本的N大小不同、含义不同的话)

您好,我也有类似的问题,我有9条时间序列,其中3个是待预测的时间序列,6个是辅助序列。我的理解是设置CFG.MODEL.TARGET_TIME_SERIES=[0,1,2]后,模型从输入到输出就只用这3个待预测的时间序列了,不知道我理解的正确不,如果我希望完成9个时间序列输入,3个目标序列输出,最好的办法是这样吗。

@1229308805
Copy link

现在已经支持了,只需要设置CFG.MODEL.TARGET_TIME_SERIES=[6] (以ETT为例)应该就能实现。目前的实现方式是哪里不满足您的使用场景吗?

您好,我仔细思考了一下,非常感谢您的提议,但您的需求不太适合通过直接修改现有架构实现(同一功能的两种实现会引起误会)。
但您可以通过非侵入式的方式快速实现您的需求:

  1. 定义新的Runner。继承SimpleTimeSeriesForecastingRunner并重载postprocessing函数。
  2. 定义新的Scaler。可以继承现有的Scaler并重载inverse_transform函数。
  3. 定义新的Datasets(如果您的待预测样本和历史样本的N大小不同、含义不同的话)

您好,我也有类似的问题,我有9条时间序列,其中3个是待预测的时间序列,6个是辅助序列。我的理解是设置CFG.MODEL.TARGET_TIME_SERIES=[0,1,2]后,模型从输入到输出就只用这3个待预测的时间序列了,不知道我理解的正确不,如果我希望完成9个时间序列输入,3个目标序列输出,最好的办法是这样吗。

我阅读了 complete_config_cn.py这个文件,看到了关于这个参数的解释。 能否这么理解,如此设置之后,模型还会输出全部9个变量的预测结果,但是后处理 计算loss时只取三个目标变量。 这么做 会不会使模型额外做了其他6个变量的预测,存在冗余计算?

@zezhishao
Copy link
Collaborator

您的理解是正确的,至于是否存在冗余取决于模型的实现方式。这种做法主要是为了在现有的数据集上快速的实现M-S的预测设置,并兼容常见的长序列预测模型。

如果您使用的是自己的数据和模型的话,建议实现一个自己的dataset函数。该datasets可以预先产生好输入序列(包含目标序列和辅助序列)inputs、输出序列(目标序列)target。此时模型只需要产生对输出序列的预测即可,无需设置TARGET_TIME_SERIES参数。

@1229308805
Copy link

您的理解是正确的,至于是否存在冗余取决于模型的实现方式。这种做法主要是为了在现有的数据集上快速的实现M-S的预测设置,并兼容常见的长序列预测模型。

如果您使用的是自己的数据和模型的话,建议实现一个自己的dataset函数。该datasets可以预先产生好输入序列(包含目标序列和辅助序列)inputs、输出序列(目标序列)target。此时模型只需要产生对输出序列的预测即可,无需设置TARGET_TIME_SERIES参数。

好的,我明白了,看来我需要自己实现一个dataset函数。 感谢您的解答,感谢这个开源库!

@JFz0419
Copy link

JFz0419 commented Dec 4, 2024

请问后续会加入一些基于Diffusion的模型吗,因为感觉diffusion的训练推理方式和现在这些模型略有不同

@zezhishao
Copy link
Collaborator

我们不太熟悉基于Diffusion时序模型,它相比于普通模型的优势是什么呢,有什么经典之作推荐吗?

请问后续会加入一些基于Diffusion的模型吗,因为感觉diffusion的训练推理方式和现在这些模型略有不同

@JFz0419
Copy link

JFz0419 commented Dec 4, 2024

我们不太熟悉基于Diffusion时序模型,它相比于普通模型的优势是什么呢,有什么经典之作推荐吗?

请问后续会加入一些基于Diffusion的模型吗,因为感觉diffusion的训练推理方式和现在这些模型略有不同

优势的话我还真不知道,因为最近注意到不少工作是基于diffusion做的所以就想了解了解来着
Rasul K, Seward C, Schuster I, et al. Autoregressive denoising diffusion models for multivariate probabilistic time series forecasting[C]//International Conference on Machine Learning. PMLR, 2021: 8857-8868.
Tashiro Y, Song J, Song Y, et al. Csdi: Conditional score-based diffusion models for probabilistic time series imputation[J]. Advances in Neural Information Processing Systems, 2021, 34: 24804-24816.
Shen L, Chen W, Kwok J. Multi-Resolution Diffusion Models for Time Series Forecasting[C]//The Twelfth International Conference on Learning Representations. 2024.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants