From eb8684bf91076e81b865876fa61ffb37324faff8 Mon Sep 17 00:00:00 2001 From: czzhangheng Date: Tue, 22 Apr 2025 14:54:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=88=B0=E5=9E=83=E5=9C=BE=E5=A0=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 0 LICENSE | 0 README.md | 0 Result.xlsx | Bin baseline.ipynb | 0 baseline1.ipynb | 0 config/AGCRN/PEMSD3.yaml | 0 config/AGCRN/PEMSD4.yaml | 0 config/AGCRN/PEMSD7.yaml | 0 config/AGCRN/PEMSD8.yaml | 0 config/ARIMA/Hainan.yaml | 0 config/ARIMA/PEMSD3.yaml | 0 config/ARIMA/PEMSD4.yaml | 0 config/ARIMA/PEMSD7(L).yaml | 0 config/ARIMA/PEMSD7(M).yaml | 0 config/ARIMA/PEMSD7.yaml | 0 config/ARIMA/PEMSD8.yaml | 0 config/DCRNN/PEMSD3.yaml | 0 config/DCRNN/PEMSD4.yaml | 0 config/DCRNN/PEMSD7.yaml | 0 config/DCRNN/PEMSD8.yaml | 0 config/DDGCRN/Hainan.yaml | 0 config/DDGCRN/PEMSD3.yaml | 0 config/DDGCRN/PEMSD4.yaml | 0 config/DDGCRN/PEMSD7(L).yaml | 0 config/DDGCRN/PEMSD7(M).yaml | 0 config/DDGCRN/PEMSD7.yaml | 0 config/DDGCRN/PEMSD8.yaml | 0 config/DSANET/PEMSD3.yaml | 0 config/DSANET/PEMSD4.yaml | 0 config/DSANET/PEMSD7.yaml | 0 config/DSANET/PEMSD8.yaml | 0 config/EXP/PEMSD3.yaml | 0 config/EXP/PEMSD4.yaml | 0 config/EXP/PEMSD7.yaml | 0 config/EXP/PEMSD8.yaml | 0 config/EXP/SD.yaml | 0 config/EXPB/PEMSD4.yaml | 0 config/GWN/PEMSD3.yaml | 0 config/GWN/PEMSD4.yaml | 0 config/GWN/PEMSD7.yaml | 0 config/GWN/PEMSD8.yaml | 0 config/NLT/PEMSD3.yaml | 0 config/NLT/PEMSD4.yaml | 0 config/NLT/PEMSD7.yaml | 0 config/NLT/PEMSD8.yaml | 0 config/PDG2SEQ/PEMSD3.yaml | 0 config/PDG2SEQ/PEMSD4.yaml | 0 config/PDG2SEQ/PEMSD7.yaml | 0 config/PDG2SEQ/PEMSD8.yaml | 0 config/STAEFormer/PEMSD3.yaml | 56 +++ config/STAEFormer/PEMSD4.yaml | 55 +++ config/STAEFormer/PEMSD7.yaml | 56 +++ config/STAEFormer/PEMSD8.yaml | 56 +++ config/STFGNN/PEMSD3.yaml | 0 config/STFGNN/PEMSD4.yaml | 0 config/STFGNN/PEMSD7.yaml | 0 config/STFGNN/PEMSD8.yaml | 0 config/STGCN/PEMSD3.yaml | 0 config/STGCN/PEMSD4.yaml | 0 config/STGCN/PEMSD7.yaml | 0 config/STGCN/PEMSD8.yaml | 0 config/STGNCDE/PEMSD3.yaml | 0 config/STGNCDE/PEMSD4.yaml | 0 config/STGNCDE/PEMSD7.yaml | 0 config/STGNCDE/PEMSD8.yaml | 0 config/STGODE/PEMSD3.yaml | 0 config/STGODE/PEMSD4.yaml | 0 config/STGODE/PEMSD7.yaml | 0 config/STGODE/PEMSD8.yaml | 0 config/STID/PEMSD4.yaml | 0 config/STSGCN/PEMSD3.yaml | 0 config/STSGCN/PEMSD4.yaml | 0 config/STSGCN/PEMSD7.yaml | 0 config/STSGCN/PEMSD8.yaml | 0 config/TCN/PEMSD3.yaml | 0 config/TCN/PEMSD4.yaml | 0 config/TCN/PEMSD7.yaml | 0 config/TCN/PEMSD8.yaml | 0 config/TWDGCN/Hainan.yaml | 0 config/TWDGCN/PEMSD3.yaml | 0 config/TWDGCN/PEMSD4.yaml | 0 config/TWDGCN/PEMSD7(L).yaml | 0 config/TWDGCN/PEMSD7(M).yaml | 0 config/TWDGCN/PEMSD7.yaml | 0 config/TWDGCN/PEMSD8.yaml | 0 config/args_parser.py | 0 dataloader/DCRNNdataloader.py | 0 dataloader/PeMSDdataloader.py | 0 dataloader/PeMSDdataloader_old.py | 0 dataloader/cde_loader/__init__.py | 0 dataloader/cde_loader/add_window.py | 0 dataloader/cde_loader/cdeDataloader.py | 0 dataloader/cde_loader/load_dataset.py | 0 dataloader/loader_selector.py | 0 lib/Download_data.py | 0 lib/LargeST.py | 0 lib/Trainer_old.py | 0 lib/initializer.py | 0 lib/logger.py | 0 lib/loss_function.py | 2 + lib/normalization.py | 0 model/AGCRN/AGCN.py | 0 model/AGCRN/AGCRN.py | 0 model/AGCRN/AGCRNCell.py | 0 model/ARIMA/ARIMA.py | 0 model/ARIMA/Polynomial.py | 0 model/ARIMA/Transform.py | 0 model/ARIMA/torch_utils.py | 0 model/DCRNN/dcrnn_cell.py | 0 model/DCRNN/dcrnn_model.py | 0 model/DCRNN/utils.py | 0 model/DDGCRN/DDGCRN.py | 0 model/DDGCRN/DDGCRN_old.py | 0 model/DSANET/DSANET.py | 0 model/DSANET/Layers.py | 0 model/DSANET/Modules.py | 0 model/DSANET/SubLayers.py | 0 model/EXP/EXP19.py | 0 model/EXP/EXP21.py | 0 model/EXP/EXP31.py | 147 +++++++ model/EXP/EXP8.py | 0 model/EXP/{ => trash}/EXP0.py | 0 model/EXP/{ => trash}/EXP1.py | 0 model/EXP/{ => trash}/EXP10.py | 0 model/EXP/{ => trash}/EXP11.py | 0 model/EXP/{ => trash}/EXP12.py | 0 model/EXP/{ => trash}/EXP13.py | 0 model/EXP/{ => trash}/EXP14.py | 0 model/EXP/{ => trash}/EXP15.py | 0 model/EXP/{ => trash}/EXP16.py | 0 model/EXP/{ => trash}/EXP17.py | 0 model/EXP/{ => trash}/EXP18.py | 0 model/EXP/{ => trash}/EXP2.py | 0 model/EXP/{ => trash}/EXP20.py | 0 model/EXP/{ => trash}/EXP22.py | 0 model/EXP/{ => trash}/EXP23.py | 0 model/EXP/{ => trash}/EXP24.py | 0 model/EXP/{ => trash}/EXP25.py | 0 model/EXP/{ => trash}/EXP26.py | 0 model/EXP/{ => trash}/EXP27.py | 3 + model/EXP/trash/EXP28.py | 209 +++++++++ model/EXP/trash/EXP29.py | 195 +++++++++ model/EXP/{ => trash}/EXP3.py | 0 model/EXP/trash/EXP30.py | 217 ++++++++++ model/EXP/{ => trash}/EXP3_easy.py | 0 model/EXP/{ => trash}/EXP4.py | 0 model/EXP/{ => trash}/EXP5.py | 0 model/EXP/{ => trash}/EXP6.py | 0 model/EXP/{ => trash}/EXP7.py | 0 model/EXP/{ => trash}/EXP8b.py | 0 model/EXP/{ => trash}/EXP9.py | 0 model/EXPB/EXP_b.py | 0 model/GWN/GraphWaveNet.py | 0 model/GWN/GraphWaveNet_bk.py | 0 model/GWN/GraphWaveNet_exp.py | 0 model/NLT/HierAttnLstm.py | 0 model/PDG2SEQ/PDG2Seq.py | 0 model/PDG2SEQ/PDG2SeqCell.py | 0 model/PDG2SEQ/PDG2Seq_DGCN.py | 0 model/PDG2SEQ/PDG2Seqb.py | 243 +++++++++++ model/STAEFormer/STAEFormer.py | 237 ++++++++++ model/STFGNN/STFGNN.py | 0 model/STGCN/layers.py | 0 model/STGCN/models.py | 0 model/STGNCDE/BasicTrainer_cde.py | 0 model/STGNCDE/GCDE.py | 0 model/STGNCDE/Make_model.py | 0 model/STGNCDE/PEMSD4_GCDE.conf | 0 model/STGNCDE/Run_cde.py | 0 model/STGNCDE/controldiffeq/__init__.py | 0 model/STGNCDE/controldiffeq/cdeint_module.py | 0 model/STGNCDE/controldiffeq/interpolate.py | 0 model/STGNCDE/controldiffeq/misc.py | 0 model/STGNCDE/vector_fields.py | 0 model/STGODE/STGODE.py | 0 model/STGODE/adj.py | 0 model/STGODE/odegcn.py | 0 model/STID/MLP.py | 0 model/STID/STID.py | 0 model/STSGCN/STSGCN.py | 0 model/STSGCN/get_adj.py | 0 model/TCN/TCN.py | 0 model/TWDGCN/ConnectionMatrix.py | 0 model/TWDGCN/ConnectionMatrix2.py | 0 model/TWDGCN/ConnectionMatrix_old.py | 0 model/TWDGCN/DGCN.py | 0 model/TWDGCN/DGCRU.py | 0 model/TWDGCN/TWDGCN.py | 0 model/model_selector.py | 6 +- requirements.txt | 0 run.py | 0 trainer/DCRNN_Trainer.py | 0 trainer/EXP_trainer.py | 0 trainer/PDG2SEQ_Trainer.py | 0 trainer/Trainer.py | 0 trainer/cdeTrainer/cdetrainer.py | 0 trainer/trainer_selector.py | 0 transfer_guide.md | 0 utils/ADFtest.py | 74 ++++ utils/__init__.py | 0 utils/augmentation.py | 434 +++++++++++++++++++ utils/dtw.py | 223 ++++++++++ utils/dtw_metric.py | 156 +++++++ utils/losses.py | 89 ++++ utils/m4_summary.py | 140 ++++++ utils/masking.py | 26 ++ utils/metrics.py | 41 ++ utils/print_args.py | 58 +++ utils/timefeatures.py | 148 +++++++ utils/tools.py | 120 +++++ 211 files changed, 2989 insertions(+), 2 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 Result.xlsx mode change 100644 => 100755 baseline.ipynb mode change 100644 => 100755 baseline1.ipynb mode change 100644 => 100755 config/AGCRN/PEMSD3.yaml mode change 100644 => 100755 config/AGCRN/PEMSD4.yaml mode change 100644 => 100755 config/AGCRN/PEMSD7.yaml mode change 100644 => 100755 config/AGCRN/PEMSD8.yaml mode change 100644 => 100755 config/ARIMA/Hainan.yaml mode change 100644 => 100755 config/ARIMA/PEMSD3.yaml mode change 100644 => 100755 config/ARIMA/PEMSD4.yaml mode change 100644 => 100755 config/ARIMA/PEMSD7(L).yaml mode change 100644 => 100755 config/ARIMA/PEMSD7(M).yaml mode change 100644 => 100755 config/ARIMA/PEMSD7.yaml mode change 100644 => 100755 config/ARIMA/PEMSD8.yaml mode change 100644 => 100755 config/DCRNN/PEMSD3.yaml mode change 100644 => 100755 config/DCRNN/PEMSD4.yaml mode change 100644 => 100755 config/DCRNN/PEMSD7.yaml mode change 100644 => 100755 config/DCRNN/PEMSD8.yaml mode change 100644 => 100755 config/DDGCRN/Hainan.yaml mode change 100644 => 100755 config/DDGCRN/PEMSD3.yaml mode change 100644 => 100755 config/DDGCRN/PEMSD4.yaml mode change 100644 => 100755 config/DDGCRN/PEMSD7(L).yaml mode change 100644 => 100755 config/DDGCRN/PEMSD7(M).yaml mode change 100644 => 100755 config/DDGCRN/PEMSD7.yaml mode change 100644 => 100755 config/DDGCRN/PEMSD8.yaml mode change 100644 => 100755 config/DSANET/PEMSD3.yaml mode change 100644 => 100755 config/DSANET/PEMSD4.yaml mode change 100644 => 100755 config/DSANET/PEMSD7.yaml mode change 100644 => 100755 config/DSANET/PEMSD8.yaml mode change 100644 => 100755 config/EXP/PEMSD3.yaml mode change 100644 => 100755 config/EXP/PEMSD4.yaml mode change 100644 => 100755 config/EXP/PEMSD7.yaml mode change 100644 => 100755 config/EXP/PEMSD8.yaml mode change 100644 => 100755 config/EXP/SD.yaml mode change 100644 => 100755 config/EXPB/PEMSD4.yaml mode change 100644 => 100755 config/GWN/PEMSD3.yaml mode change 100644 => 100755 config/GWN/PEMSD4.yaml mode change 100644 => 100755 config/GWN/PEMSD7.yaml mode change 100644 => 100755 config/GWN/PEMSD8.yaml mode change 100644 => 100755 config/NLT/PEMSD3.yaml mode change 100644 => 100755 config/NLT/PEMSD4.yaml mode change 100644 => 100755 config/NLT/PEMSD7.yaml mode change 100644 => 100755 config/NLT/PEMSD8.yaml mode change 100644 => 100755 config/PDG2SEQ/PEMSD3.yaml mode change 100644 => 100755 config/PDG2SEQ/PEMSD4.yaml mode change 100644 => 100755 config/PDG2SEQ/PEMSD7.yaml mode change 100644 => 100755 config/PDG2SEQ/PEMSD8.yaml create mode 100755 config/STAEFormer/PEMSD3.yaml create mode 100755 config/STAEFormer/PEMSD4.yaml create mode 100755 config/STAEFormer/PEMSD7.yaml create mode 100755 config/STAEFormer/PEMSD8.yaml mode change 100644 => 100755 config/STFGNN/PEMSD3.yaml mode change 100644 => 100755 config/STFGNN/PEMSD4.yaml mode change 100644 => 100755 config/STFGNN/PEMSD7.yaml mode change 100644 => 100755 config/STFGNN/PEMSD8.yaml mode change 100644 => 100755 config/STGCN/PEMSD3.yaml mode change 100644 => 100755 config/STGCN/PEMSD4.yaml mode change 100644 => 100755 config/STGCN/PEMSD7.yaml mode change 100644 => 100755 config/STGCN/PEMSD8.yaml mode change 100644 => 100755 config/STGNCDE/PEMSD3.yaml mode change 100644 => 100755 config/STGNCDE/PEMSD4.yaml mode change 100644 => 100755 config/STGNCDE/PEMSD7.yaml mode change 100644 => 100755 config/STGNCDE/PEMSD8.yaml mode change 100644 => 100755 config/STGODE/PEMSD3.yaml mode change 100644 => 100755 config/STGODE/PEMSD4.yaml mode change 100644 => 100755 config/STGODE/PEMSD7.yaml mode change 100644 => 100755 config/STGODE/PEMSD8.yaml mode change 100644 => 100755 config/STID/PEMSD4.yaml mode change 100644 => 100755 config/STSGCN/PEMSD3.yaml mode change 100644 => 100755 config/STSGCN/PEMSD4.yaml mode change 100644 => 100755 config/STSGCN/PEMSD7.yaml mode change 100644 => 100755 config/STSGCN/PEMSD8.yaml mode change 100644 => 100755 config/TCN/PEMSD3.yaml mode change 100644 => 100755 config/TCN/PEMSD4.yaml mode change 100644 => 100755 config/TCN/PEMSD7.yaml mode change 100644 => 100755 config/TCN/PEMSD8.yaml mode change 100644 => 100755 config/TWDGCN/Hainan.yaml mode change 100644 => 100755 config/TWDGCN/PEMSD3.yaml mode change 100644 => 100755 config/TWDGCN/PEMSD4.yaml mode change 100644 => 100755 config/TWDGCN/PEMSD7(L).yaml mode change 100644 => 100755 config/TWDGCN/PEMSD7(M).yaml mode change 100644 => 100755 config/TWDGCN/PEMSD7.yaml mode change 100644 => 100755 config/TWDGCN/PEMSD8.yaml mode change 100644 => 100755 config/args_parser.py mode change 100644 => 100755 dataloader/DCRNNdataloader.py mode change 100644 => 100755 dataloader/PeMSDdataloader.py mode change 100644 => 100755 dataloader/PeMSDdataloader_old.py mode change 100644 => 100755 dataloader/cde_loader/__init__.py mode change 100644 => 100755 dataloader/cde_loader/add_window.py mode change 100644 => 100755 dataloader/cde_loader/cdeDataloader.py mode change 100644 => 100755 dataloader/cde_loader/load_dataset.py mode change 100644 => 100755 dataloader/loader_selector.py mode change 100644 => 100755 lib/Download_data.py mode change 100644 => 100755 lib/LargeST.py mode change 100644 => 100755 lib/Trainer_old.py mode change 100644 => 100755 lib/initializer.py mode change 100644 => 100755 lib/logger.py mode change 100644 => 100755 lib/loss_function.py mode change 100644 => 100755 lib/normalization.py mode change 100644 => 100755 model/AGCRN/AGCN.py mode change 100644 => 100755 model/AGCRN/AGCRN.py mode change 100644 => 100755 model/AGCRN/AGCRNCell.py mode change 100644 => 100755 model/ARIMA/ARIMA.py mode change 100644 => 100755 model/ARIMA/Polynomial.py mode change 100644 => 100755 model/ARIMA/Transform.py mode change 100644 => 100755 model/ARIMA/torch_utils.py mode change 100644 => 100755 model/DCRNN/dcrnn_cell.py mode change 100644 => 100755 model/DCRNN/dcrnn_model.py mode change 100644 => 100755 model/DCRNN/utils.py mode change 100644 => 100755 model/DDGCRN/DDGCRN.py mode change 100644 => 100755 model/DDGCRN/DDGCRN_old.py mode change 100644 => 100755 model/DSANET/DSANET.py mode change 100644 => 100755 model/DSANET/Layers.py mode change 100644 => 100755 model/DSANET/Modules.py mode change 100644 => 100755 model/DSANET/SubLayers.py mode change 100644 => 100755 model/EXP/EXP19.py mode change 100644 => 100755 model/EXP/EXP21.py create mode 100755 model/EXP/EXP31.py mode change 100644 => 100755 model/EXP/EXP8.py rename model/EXP/{ => trash}/EXP0.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP1.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP10.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP11.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP12.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP13.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP14.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP15.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP16.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP17.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP18.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP2.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP20.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP22.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP23.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP24.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP25.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP26.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP27.py (99%) mode change 100644 => 100755 create mode 100755 model/EXP/trash/EXP28.py create mode 100755 model/EXP/trash/EXP29.py rename model/EXP/{ => trash}/EXP3.py (100%) mode change 100644 => 100755 create mode 100755 model/EXP/trash/EXP30.py rename model/EXP/{ => trash}/EXP3_easy.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP4.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP5.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP6.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP7.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP8b.py (100%) mode change 100644 => 100755 rename model/EXP/{ => trash}/EXP9.py (100%) mode change 100644 => 100755 mode change 100644 => 100755 model/EXPB/EXP_b.py mode change 100644 => 100755 model/GWN/GraphWaveNet.py mode change 100644 => 100755 model/GWN/GraphWaveNet_bk.py mode change 100644 => 100755 model/GWN/GraphWaveNet_exp.py mode change 100644 => 100755 model/NLT/HierAttnLstm.py mode change 100644 => 100755 model/PDG2SEQ/PDG2Seq.py mode change 100644 => 100755 model/PDG2SEQ/PDG2SeqCell.py mode change 100644 => 100755 model/PDG2SEQ/PDG2Seq_DGCN.py create mode 100755 model/PDG2SEQ/PDG2Seqb.py create mode 100755 model/STAEFormer/STAEFormer.py mode change 100644 => 100755 model/STFGNN/STFGNN.py mode change 100644 => 100755 model/STGCN/layers.py mode change 100644 => 100755 model/STGCN/models.py mode change 100644 => 100755 model/STGNCDE/BasicTrainer_cde.py mode change 100644 => 100755 model/STGNCDE/GCDE.py mode change 100644 => 100755 model/STGNCDE/Make_model.py mode change 100644 => 100755 model/STGNCDE/PEMSD4_GCDE.conf mode change 100644 => 100755 model/STGNCDE/Run_cde.py mode change 100644 => 100755 model/STGNCDE/controldiffeq/__init__.py mode change 100644 => 100755 model/STGNCDE/controldiffeq/cdeint_module.py mode change 100644 => 100755 model/STGNCDE/controldiffeq/interpolate.py mode change 100644 => 100755 model/STGNCDE/controldiffeq/misc.py mode change 100644 => 100755 model/STGNCDE/vector_fields.py mode change 100644 => 100755 model/STGODE/STGODE.py mode change 100644 => 100755 model/STGODE/adj.py mode change 100644 => 100755 model/STGODE/odegcn.py mode change 100644 => 100755 model/STID/MLP.py mode change 100644 => 100755 model/STID/STID.py mode change 100644 => 100755 model/STSGCN/STSGCN.py mode change 100644 => 100755 model/STSGCN/get_adj.py mode change 100644 => 100755 model/TCN/TCN.py mode change 100644 => 100755 model/TWDGCN/ConnectionMatrix.py mode change 100644 => 100755 model/TWDGCN/ConnectionMatrix2.py mode change 100644 => 100755 model/TWDGCN/ConnectionMatrix_old.py mode change 100644 => 100755 model/TWDGCN/DGCN.py mode change 100644 => 100755 model/TWDGCN/DGCRU.py mode change 100644 => 100755 model/TWDGCN/TWDGCN.py mode change 100644 => 100755 model/model_selector.py mode change 100644 => 100755 requirements.txt mode change 100644 => 100755 run.py mode change 100644 => 100755 trainer/DCRNN_Trainer.py mode change 100644 => 100755 trainer/EXP_trainer.py mode change 100644 => 100755 trainer/PDG2SEQ_Trainer.py mode change 100644 => 100755 trainer/Trainer.py mode change 100644 => 100755 trainer/cdeTrainer/cdetrainer.py mode change 100644 => 100755 trainer/trainer_selector.py mode change 100644 => 100755 transfer_guide.md create mode 100755 utils/ADFtest.py create mode 100755 utils/__init__.py create mode 100755 utils/augmentation.py create mode 100755 utils/dtw.py create mode 100755 utils/dtw_metric.py create mode 100755 utils/losses.py create mode 100755 utils/m4_summary.py create mode 100755 utils/masking.py create mode 100755 utils/metrics.py create mode 100755 utils/print_args.py create mode 100755 utils/timefeatures.py create mode 100755 utils/tools.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/Result.xlsx b/Result.xlsx old mode 100644 new mode 100755 diff --git a/baseline.ipynb b/baseline.ipynb old mode 100644 new mode 100755 diff --git a/baseline1.ipynb b/baseline1.ipynb old mode 100644 new mode 100755 diff --git a/config/AGCRN/PEMSD3.yaml b/config/AGCRN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/AGCRN/PEMSD4.yaml b/config/AGCRN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/AGCRN/PEMSD7.yaml b/config/AGCRN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/AGCRN/PEMSD8.yaml b/config/AGCRN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/ARIMA/Hainan.yaml b/config/ARIMA/Hainan.yaml old mode 100644 new mode 100755 diff --git a/config/ARIMA/PEMSD3.yaml b/config/ARIMA/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/ARIMA/PEMSD4.yaml b/config/ARIMA/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/ARIMA/PEMSD7(L).yaml b/config/ARIMA/PEMSD7(L).yaml old mode 100644 new mode 100755 diff --git a/config/ARIMA/PEMSD7(M).yaml b/config/ARIMA/PEMSD7(M).yaml old mode 100644 new mode 100755 diff --git a/config/ARIMA/PEMSD7.yaml b/config/ARIMA/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/ARIMA/PEMSD8.yaml b/config/ARIMA/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/DCRNN/PEMSD3.yaml b/config/DCRNN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/DCRNN/PEMSD4.yaml b/config/DCRNN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/DCRNN/PEMSD7.yaml b/config/DCRNN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/DCRNN/PEMSD8.yaml b/config/DCRNN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/DDGCRN/Hainan.yaml b/config/DDGCRN/Hainan.yaml old mode 100644 new mode 100755 diff --git a/config/DDGCRN/PEMSD3.yaml b/config/DDGCRN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/DDGCRN/PEMSD4.yaml b/config/DDGCRN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/DDGCRN/PEMSD7(L).yaml b/config/DDGCRN/PEMSD7(L).yaml old mode 100644 new mode 100755 diff --git a/config/DDGCRN/PEMSD7(M).yaml b/config/DDGCRN/PEMSD7(M).yaml old mode 100644 new mode 100755 diff --git a/config/DDGCRN/PEMSD7.yaml b/config/DDGCRN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/DDGCRN/PEMSD8.yaml b/config/DDGCRN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/DSANET/PEMSD3.yaml b/config/DSANET/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/DSANET/PEMSD4.yaml b/config/DSANET/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/DSANET/PEMSD7.yaml b/config/DSANET/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/DSANET/PEMSD8.yaml b/config/DSANET/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/EXP/PEMSD3.yaml b/config/EXP/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/EXP/PEMSD4.yaml b/config/EXP/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/EXP/PEMSD7.yaml b/config/EXP/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/EXP/PEMSD8.yaml b/config/EXP/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/EXP/SD.yaml b/config/EXP/SD.yaml old mode 100644 new mode 100755 diff --git a/config/EXPB/PEMSD4.yaml b/config/EXPB/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/GWN/PEMSD3.yaml b/config/GWN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/GWN/PEMSD4.yaml b/config/GWN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/GWN/PEMSD7.yaml b/config/GWN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/GWN/PEMSD8.yaml b/config/GWN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/NLT/PEMSD3.yaml b/config/NLT/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/NLT/PEMSD4.yaml b/config/NLT/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/NLT/PEMSD7.yaml b/config/NLT/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/NLT/PEMSD8.yaml b/config/NLT/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/PDG2SEQ/PEMSD3.yaml b/config/PDG2SEQ/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/PDG2SEQ/PEMSD4.yaml b/config/PDG2SEQ/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/PDG2SEQ/PEMSD7.yaml b/config/PDG2SEQ/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/PDG2SEQ/PEMSD8.yaml b/config/PDG2SEQ/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/STAEFormer/PEMSD3.yaml b/config/STAEFormer/PEMSD3.yaml new file mode 100755 index 0000000..2014b44 --- /dev/null +++ b/config/STAEFormer/PEMSD3.yaml @@ -0,0 +1,56 @@ +data: + num_nodes: 358 + 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: + num_nodes: 358 + in_steps: 12 + out_steps: 12 + steps_per_day: 288 + input_dim: 1 + output_dim: 1 + input_embedding_dim: 24 + tod_embedding_dim: 24 + dow_embedding_dim: 24 + spatial_embedding_dim: 0 + adaptive_embedding_dim: 80 + feed_forward_dim: 256 + num_heads: 4 + num_layers: 3 + dropout: 0.1 + use_mixed_proj: true + +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 diff --git a/config/STAEFormer/PEMSD4.yaml b/config/STAEFormer/PEMSD4.yaml new file mode 100755 index 0000000..bb94654 --- /dev/null +++ b/config/STAEFormer/PEMSD4.yaml @@ -0,0 +1,55 @@ +data: + num_nodes: 307 + lag: 12 + horizon: 12 + val_ratio: 0.1 + 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: + num_nodes: 307 + in_steps: 12 + out_steps: 12 + steps_per_day: 288 + input_dim: 1 + output_dim: 1 + input_embedding_dim: 24 + tod_embedding_dim: 24 + dow_embedding_dim: 24 + spatial_embedding_dim: 0 + adaptive_embedding_dim: 80 + feed_forward_dim: 256 + num_heads: 4 + num_layers: 3 + dropout: 0.1 + use_mixed_proj: true + +train: + loss_func: Huber + seed: 10 + batch_size: 16 + epochs: 200 + lr_init: 0.001 + weight_decay: 0.0003 + lr_decay: True + lr_decay_rate: 0.1 + lr_decay_step: "5,20,40,70" + early_stop: True + early_stop_patience: 30 + grad_norm: False + real_value: True + +test: + mae_thresh: null + mape_thresh: 0.0 + +log: + log_step: 2000 + plot: False diff --git a/config/STAEFormer/PEMSD7.yaml b/config/STAEFormer/PEMSD7.yaml new file mode 100755 index 0000000..a593bd3 --- /dev/null +++ b/config/STAEFormer/PEMSD7.yaml @@ -0,0 +1,56 @@ +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: + num_nodes: 883 + in_steps: 12 + out_steps: 12 + steps_per_day: 288 + input_dim: 1 + output_dim: 1 + input_embedding_dim: 24 + tod_embedding_dim: 24 + dow_embedding_dim: 24 + spatial_embedding_dim: 0 + adaptive_embedding_dim: 80 + feed_forward_dim: 256 + num_heads: 4 + num_layers: 3 + dropout: 0.1 + use_mixed_proj: true + +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 diff --git a/config/STAEFormer/PEMSD8.yaml b/config/STAEFormer/PEMSD8.yaml new file mode 100755 index 0000000..f625d5d --- /dev/null +++ b/config/STAEFormer/PEMSD8.yaml @@ -0,0 +1,56 @@ +data: + num_nodes: 170 + 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: + num_nodes: 170 + in_steps: 12 + out_steps: 12 + steps_per_day: 288 + input_dim: 1 + output_dim: 1 + input_embedding_dim: 24 + tod_embedding_dim: 24 + dow_embedding_dim: 24 + spatial_embedding_dim: 0 + adaptive_embedding_dim: 80 + feed_forward_dim: 256 + num_heads: 4 + num_layers: 3 + dropout: 0.1 + use_mixed_proj: true + +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 diff --git a/config/STFGNN/PEMSD3.yaml b/config/STFGNN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/STFGNN/PEMSD4.yaml b/config/STFGNN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/STFGNN/PEMSD7.yaml b/config/STFGNN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/STFGNN/PEMSD8.yaml b/config/STFGNN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/STGCN/PEMSD3.yaml b/config/STGCN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/STGCN/PEMSD4.yaml b/config/STGCN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/STGCN/PEMSD7.yaml b/config/STGCN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/STGCN/PEMSD8.yaml b/config/STGCN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/STGNCDE/PEMSD3.yaml b/config/STGNCDE/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/STGNCDE/PEMSD4.yaml b/config/STGNCDE/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/STGNCDE/PEMSD7.yaml b/config/STGNCDE/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/STGNCDE/PEMSD8.yaml b/config/STGNCDE/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/STGODE/PEMSD3.yaml b/config/STGODE/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/STGODE/PEMSD4.yaml b/config/STGODE/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/STGODE/PEMSD7.yaml b/config/STGODE/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/STGODE/PEMSD8.yaml b/config/STGODE/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/STID/PEMSD4.yaml b/config/STID/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/STSGCN/PEMSD3.yaml b/config/STSGCN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/STSGCN/PEMSD4.yaml b/config/STSGCN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/STSGCN/PEMSD7.yaml b/config/STSGCN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/STSGCN/PEMSD8.yaml b/config/STSGCN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/TCN/PEMSD3.yaml b/config/TCN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/TCN/PEMSD4.yaml b/config/TCN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/TCN/PEMSD7.yaml b/config/TCN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/TCN/PEMSD8.yaml b/config/TCN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/TWDGCN/Hainan.yaml b/config/TWDGCN/Hainan.yaml old mode 100644 new mode 100755 diff --git a/config/TWDGCN/PEMSD3.yaml b/config/TWDGCN/PEMSD3.yaml old mode 100644 new mode 100755 diff --git a/config/TWDGCN/PEMSD4.yaml b/config/TWDGCN/PEMSD4.yaml old mode 100644 new mode 100755 diff --git a/config/TWDGCN/PEMSD7(L).yaml b/config/TWDGCN/PEMSD7(L).yaml old mode 100644 new mode 100755 diff --git a/config/TWDGCN/PEMSD7(M).yaml b/config/TWDGCN/PEMSD7(M).yaml old mode 100644 new mode 100755 diff --git a/config/TWDGCN/PEMSD7.yaml b/config/TWDGCN/PEMSD7.yaml old mode 100644 new mode 100755 diff --git a/config/TWDGCN/PEMSD8.yaml b/config/TWDGCN/PEMSD8.yaml old mode 100644 new mode 100755 diff --git a/config/args_parser.py b/config/args_parser.py old mode 100644 new mode 100755 diff --git a/dataloader/DCRNNdataloader.py b/dataloader/DCRNNdataloader.py old mode 100644 new mode 100755 diff --git a/dataloader/PeMSDdataloader.py b/dataloader/PeMSDdataloader.py old mode 100644 new mode 100755 diff --git a/dataloader/PeMSDdataloader_old.py b/dataloader/PeMSDdataloader_old.py old mode 100644 new mode 100755 diff --git a/dataloader/cde_loader/__init__.py b/dataloader/cde_loader/__init__.py old mode 100644 new mode 100755 diff --git a/dataloader/cde_loader/add_window.py b/dataloader/cde_loader/add_window.py old mode 100644 new mode 100755 diff --git a/dataloader/cde_loader/cdeDataloader.py b/dataloader/cde_loader/cdeDataloader.py old mode 100644 new mode 100755 diff --git a/dataloader/cde_loader/load_dataset.py b/dataloader/cde_loader/load_dataset.py old mode 100644 new mode 100755 diff --git a/dataloader/loader_selector.py b/dataloader/loader_selector.py old mode 100644 new mode 100755 diff --git a/lib/Download_data.py b/lib/Download_data.py old mode 100644 new mode 100755 diff --git a/lib/LargeST.py b/lib/LargeST.py old mode 100644 new mode 100755 diff --git a/lib/Trainer_old.py b/lib/Trainer_old.py old mode 100644 new mode 100755 diff --git a/lib/initializer.py b/lib/initializer.py old mode 100644 new mode 100755 diff --git a/lib/logger.py b/lib/logger.py old mode 100644 new mode 100755 diff --git a/lib/loss_function.py b/lib/loss_function.py old mode 100644 new mode 100755 index ee84270..6645e11 --- a/lib/loss_function.py +++ b/lib/loss_function.py @@ -16,6 +16,8 @@ def get_loss_function(args, scaler): return torch.nn.L1Loss().to(args['device']) elif args['loss_func'] == 'mse': return torch.nn.MSELoss().to(args['device']) + elif args['loss_func'] == 'Huber': + return torch.nn.HuberLoss().to(args['device']) else: raise ValueError('Unsupported loss function: {}'.format(args.loss_func)) diff --git a/lib/normalization.py b/lib/normalization.py old mode 100644 new mode 100755 diff --git a/model/AGCRN/AGCN.py b/model/AGCRN/AGCN.py old mode 100644 new mode 100755 diff --git a/model/AGCRN/AGCRN.py b/model/AGCRN/AGCRN.py old mode 100644 new mode 100755 diff --git a/model/AGCRN/AGCRNCell.py b/model/AGCRN/AGCRNCell.py old mode 100644 new mode 100755 diff --git a/model/ARIMA/ARIMA.py b/model/ARIMA/ARIMA.py old mode 100644 new mode 100755 diff --git a/model/ARIMA/Polynomial.py b/model/ARIMA/Polynomial.py old mode 100644 new mode 100755 diff --git a/model/ARIMA/Transform.py b/model/ARIMA/Transform.py old mode 100644 new mode 100755 diff --git a/model/ARIMA/torch_utils.py b/model/ARIMA/torch_utils.py old mode 100644 new mode 100755 diff --git a/model/DCRNN/dcrnn_cell.py b/model/DCRNN/dcrnn_cell.py old mode 100644 new mode 100755 diff --git a/model/DCRNN/dcrnn_model.py b/model/DCRNN/dcrnn_model.py old mode 100644 new mode 100755 diff --git a/model/DCRNN/utils.py b/model/DCRNN/utils.py old mode 100644 new mode 100755 diff --git a/model/DDGCRN/DDGCRN.py b/model/DDGCRN/DDGCRN.py old mode 100644 new mode 100755 diff --git a/model/DDGCRN/DDGCRN_old.py b/model/DDGCRN/DDGCRN_old.py old mode 100644 new mode 100755 diff --git a/model/DSANET/DSANET.py b/model/DSANET/DSANET.py old mode 100644 new mode 100755 diff --git a/model/DSANET/Layers.py b/model/DSANET/Layers.py old mode 100644 new mode 100755 diff --git a/model/DSANET/Modules.py b/model/DSANET/Modules.py old mode 100644 new mode 100755 diff --git a/model/DSANET/SubLayers.py b/model/DSANET/SubLayers.py old mode 100644 new mode 100755 diff --git a/model/EXP/EXP19.py b/model/EXP/EXP19.py old mode 100644 new mode 100755 diff --git a/model/EXP/EXP21.py b/model/EXP/EXP21.py old mode 100644 new mode 100755 diff --git a/model/EXP/EXP31.py b/model/EXP/EXP31.py new file mode 100755 index 0000000..0dc4e6f --- /dev/null +++ b/model/EXP/EXP31.py @@ -0,0 +1,147 @@ +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): + 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): + 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 TransformerBlock(nn.Module): + def __init__(self, embed_dim, num_heads=4, dim_feedforward=None): + super().__init__() + # feedforward dimension defaults to 2*embed_dim if not provided + ff_dim = dim_feedforward if dim_feedforward is not None else 2 * embed_dim + self.layer = nn.TransformerEncoderLayer( + d_model=embed_dim, + nhead=num_heads, + dim_feedforward=ff_dim, + batch_first=True + ) + + def forward(self, x): + # x: (batch, seq_len, embed_dim) + return self.layer(x) + + +class SandwichBlock(nn.Module): + def __init__(self, num_nodes, embed_dim, hidden_dim, num_heads=4): + super().__init__() + self.transformer1 = TransformerBlock(hidden_dim, num_heads=num_heads, dim_feedforward=hidden_dim * 2) + self.graph_constructor = DynamicGraphConstructor(num_nodes, embed_dim) + self.gc = GraphConvBlock(hidden_dim, hidden_dim) + self.transformer2 = TransformerBlock(hidden_dim, num_heads=num_heads, dim_feedforward=hidden_dim * 2) + + def forward(self, h): + # h: (batch, num_nodes, hidden_dim) + h1 = self.transformer1(h) + adj = self.graph_constructor() + h2 = self.gc(h1, adj) + h3 = self.transformer2(h2) + return h3 + + +class MLP(nn.Module): + def __init__(self, in_dim, hidden_dims, out_dim, activation=nn.ReLU): + super().__init__() + dims = [in_dim] + hidden_dims + [out_dim] + layers = [] + for i in range(len(dims) - 2): + layers += [nn.Linear(dims[i], dims[i + 1]), activation()] + layers += [nn.Linear(dims[-2], dims[-1])] + self.net = nn.Sequential(*layers) + + def forward(self, x): + return self.net(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.embed_dim = args.get('embed_dim', 16) + + # time embeddings + self.time_slots = args.get('time_slots', 24 * 60 // args.get('time_slot', 5)) + self.time_embedding = nn.Embedding(self.time_slots, self.hidden_dim) + self.day_embedding = nn.Embedding(7, self.hidden_dim) + + # input projection + self.input_proj = MLP( + in_dim=self.seq_len, + hidden_dims=[self.hidden_dim], + out_dim=self.hidden_dim + ) + + # two Sandwich blocks with transformer + self.sandwich1 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim) + self.sandwich2 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim) + + # output projection + self.out_proj = MLP( + in_dim=self.hidden_dim, + hidden_dims=[2 * self.hidden_dim], + out_dim=self.horizon * self.output_dim + ) + + def forward(self, x): + # x: (B, T, N, D_total) + x_flow = x[..., 0] + x_time = x[..., 1] + x_day = x[..., 2] + + B, T, N = x_flow.shape + assert T == self.seq_len + + # project flow history + x_flat = x_flow.permute(0, 2, 1).reshape(B * N, T) + h0 = self.input_proj(x_flat).view(B, N, self.hidden_dim) + + # time embeddings + t_idx = (x_time[:, -1, :] * (self.time_slots - 1)).long() + d_idx = x_day[:, -1, :].long() + time_emb = self.time_embedding(t_idx) + day_emb = self.day_embedding(d_idx) + + # inject embeddings + h0 = h0 + time_emb + day_emb + + # Sandwich blocks with residuals + h1 = self.sandwich1(h0) + h1 = h1 + h0 + h2 = self.sandwich2(h1) + + # output + out = self.out_proj(h2) + out = out.view(B, N, self.horizon, self.output_dim) + out = out.permute(0, 2, 1, 3) + return out diff --git a/model/EXP/EXP8.py b/model/EXP/EXP8.py old mode 100644 new mode 100755 diff --git a/model/EXP/EXP0.py b/model/EXP/trash/EXP0.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP0.py rename to model/EXP/trash/EXP0.py diff --git a/model/EXP/EXP1.py b/model/EXP/trash/EXP1.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP1.py rename to model/EXP/trash/EXP1.py diff --git a/model/EXP/EXP10.py b/model/EXP/trash/EXP10.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP10.py rename to model/EXP/trash/EXP10.py diff --git a/model/EXP/EXP11.py b/model/EXP/trash/EXP11.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP11.py rename to model/EXP/trash/EXP11.py diff --git a/model/EXP/EXP12.py b/model/EXP/trash/EXP12.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP12.py rename to model/EXP/trash/EXP12.py diff --git a/model/EXP/EXP13.py b/model/EXP/trash/EXP13.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP13.py rename to model/EXP/trash/EXP13.py diff --git a/model/EXP/EXP14.py b/model/EXP/trash/EXP14.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP14.py rename to model/EXP/trash/EXP14.py diff --git a/model/EXP/EXP15.py b/model/EXP/trash/EXP15.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP15.py rename to model/EXP/trash/EXP15.py diff --git a/model/EXP/EXP16.py b/model/EXP/trash/EXP16.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP16.py rename to model/EXP/trash/EXP16.py diff --git a/model/EXP/EXP17.py b/model/EXP/trash/EXP17.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP17.py rename to model/EXP/trash/EXP17.py diff --git a/model/EXP/EXP18.py b/model/EXP/trash/EXP18.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP18.py rename to model/EXP/trash/EXP18.py diff --git a/model/EXP/EXP2.py b/model/EXP/trash/EXP2.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP2.py rename to model/EXP/trash/EXP2.py diff --git a/model/EXP/EXP20.py b/model/EXP/trash/EXP20.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP20.py rename to model/EXP/trash/EXP20.py diff --git a/model/EXP/EXP22.py b/model/EXP/trash/EXP22.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP22.py rename to model/EXP/trash/EXP22.py diff --git a/model/EXP/EXP23.py b/model/EXP/trash/EXP23.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP23.py rename to model/EXP/trash/EXP23.py diff --git a/model/EXP/EXP24.py b/model/EXP/trash/EXP24.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP24.py rename to model/EXP/trash/EXP24.py diff --git a/model/EXP/EXP25.py b/model/EXP/trash/EXP25.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP25.py rename to model/EXP/trash/EXP25.py diff --git a/model/EXP/EXP26.py b/model/EXP/trash/EXP26.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP26.py rename to model/EXP/trash/EXP26.py diff --git a/model/EXP/EXP27.py b/model/EXP/trash/EXP27.py old mode 100644 new mode 100755 similarity index 99% rename from model/EXP/EXP27.py rename to model/EXP/trash/EXP27.py index 075a222..ef882d3 --- a/model/EXP/EXP27.py +++ b/model/EXP/trash/EXP27.py @@ -2,6 +2,9 @@ import torch import torch.nn as nn import torch.nn.functional as F +""" +(无效)节点混合专家 +""" class MANBA_Block(nn.Module): def __init__(self, input_dim, hidden_dim): diff --git a/model/EXP/trash/EXP28.py b/model/EXP/trash/EXP28.py new file mode 100755 index 0000000..c66135e --- /dev/null +++ b/model/EXP/trash/EXP28.py @@ -0,0 +1,209 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +""" +完整的 EXP 模型,基于 Greenshields 模型反推密度,并结合 LWR 守恒方程物理引导模块,支持仅流量数据输入。 +""" + +class FlowToDensity(nn.Module): + """ + 根据 Greenshields 基本图反解密度: + q = v_f * k * (1 - k / k_j) + 通过求解二次方程获得 k。 + """ + def __init__(self, v_f=15.0, k_j=1.0): + super().__init__() + self.v_f = nn.Parameter(torch.tensor(v_f), requires_grad=False) + self.k_j = nn.Parameter(torch.tensor(k_j), requires_grad=False) + + def forward(self, q): # q: (B, T, N) + a = -self.v_f / self.k_j + b = self.v_f + c = -q + delta = b**2 - 4 * a * c + delta = torch.clamp(delta, min=1e-6) + sqrt_delta = torch.sqrt(delta) + k1 = (-b + sqrt_delta) / (2 * a) + k2 = (-b - sqrt_delta) / (2 * a) + k = torch.where((k1 > 0) & (k1 < self.k_j), k1, k2) + return k + +class FundamentalDiagram(nn.Module): + """ + Greenshields 基本图:根据密度计算速度与流量。 + """ + def __init__(self, v_free=30.0, k_jam=200.0): + super().__init__() + self.v_free = nn.Parameter(torch.tensor(v_free), requires_grad=True) + self.k_jam = nn.Parameter(torch.tensor(k_jam), requires_grad=True) + + def forward(self, density): + speed = self.v_free * (1 - density / self.k_jam) + flux = density * speed + return speed, flux + +class ConservationLayer(nn.Module): + """ + 基于 LWR 方程离散化的守恒层。 + """ + def __init__(self, dt=1.0, dx=1.0): + super().__init__() + self.dt = dt + self.dx = dx + + def forward(self, density, flux, adj): + # density, flux: (B, N); adj: (N, N) + # outflow: 流量从节点流出到邻居 + outflow = flux @ adj + # inflow: 邻居流量流入该节点 + inflow = flux @ adj.T + # 更新密度 + delta = (inflow - outflow) * (self.dt / self.dx) + d_next = density + delta + return d_next.clamp(min=0.0) + +class DynamicGraphConstructor(nn.Module): + def __init__(self, node_num, embed_dim): + super().__init__() + self.nodevec1 = nn.Parameter(torch.randn(node_num, embed_dim)) + self.nodevec2 = nn.Parameter(torch.randn(node_num, embed_dim)) + + def forward(self): + 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): + 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): + 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): + 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) + self.fundamental = FundamentalDiagram() + self.conserve = ConservationLayer() + + def forward(self, h, density): + # h: (B, N, D),density: (B, N) + h1 = self.manba1(h) + adj = self.graph_constructor() + _, flux = self.fundamental(density) + density_next = self.conserve(density, flux, adj) + h1_updated = h1 + density_next.unsqueeze(-1) + h2 = self.gc(h1_updated, adj) + h3 = self.manba2(h2) + return h3, density_next + +class MLP(nn.Module): + def __init__(self, in_dim, hidden_dims, out_dim, activation=nn.ReLU): + super().__init__() + dims = [in_dim] + hidden_dims + [out_dim] + layers = [] + for i in range(len(dims) - 2): + layers += [nn.Linear(dims[i], dims[i+1]), activation()] + layers += [nn.Linear(dims[-2], dims[-1])] + self.net = nn.Sequential(*layers) + + def forward(self, x): + return self.net(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.embed_dim = args.get('embed_dim', 16) + self.time_slots = args.get('time_slots', 24 * 60 // args.get('time_slot', 5)) + + self.flow_to_density = FlowToDensity( + v_f=args.get('v_f', 15.0), + k_j=args.get('k_j', 1.0) + ) + self.time_embedding = nn.Embedding(self.time_slots, self.hidden_dim) + self.day_embedding = nn.Embedding(7, self.hidden_dim) + self.input_proj = MLP( + in_dim=self.seq_len, + hidden_dims=[self.hidden_dim], + out_dim=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 = MLP( + in_dim=self.hidden_dim, + hidden_dims=[2 * self.hidden_dim], + out_dim=self.horizon * self.output_dim + ) + + def forward(self, x): + """ + x: (B, T, N, 3) + x[...,0]=flow, x[...,1]=time_in_day, x[...,2]=day_in_week + """ + x_flow = x[..., 0] + x_time = x[..., 1] + x_day = x[..., 2] + + B, T, N = x_flow.shape + assert T == self.seq_len + + x_density = self.flow_to_density(x_flow) # (B, T, N) + dens0 = x_density[:, -1, :] # (B, N) + + x_flat = x_flow.permute(0, 2, 1).reshape(B * N, T) + h0 = self.input_proj(x_flat).view(B, N, self.hidden_dim) + + t_idx = (x_time[:, -1] * (self.time_slots - 1)).long() + d_idx = x_day[:, -1].long() + time_emb = self.time_embedding(t_idx) + day_emb = self.day_embedding(d_idx) + h0 = h0 + time_emb + day_emb + + h1, dens1 = self.sandwich1(h0, dens0) + h1 = h1 + h0 + h2, dens2 = self.sandwich2(h1, dens1) + + out = self.out_proj(h2) + out = out.view(B, N, self.horizon, self.output_dim) + out = out.permute(0, 2, 1, 3) + return out diff --git a/model/EXP/trash/EXP29.py b/model/EXP/trash/EXP29.py new file mode 100755 index 0000000..db30f22 --- /dev/null +++ b/model/EXP/trash/EXP29.py @@ -0,0 +1,195 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +""" +在原 EXP 模型基础上,添加 Haar 小波变换实现的一层小波去噪,增强时序特征。 +""" + +class WaveletDenoise(nn.Module): + """ + 单层 Haar 小波去噪: + - 使用低通滤波器提取近似系数 + - 通过转置卷积重构时序信号 + """ + def __init__(self): + super().__init__() + # Haar 低通滤波器 [1/√2, 1/√2] + lp = torch.tensor([1.0, 1.0]) / (2**0.5) + self.register_buffer('lp_filter', lp.view(1, 1, 2)) + # 转置卷积同滤波器 + self.register_buffer('lp_rec', lp.view(1, 1, 2)) + + def forward(self, x): + """ + x: (B, T, N) + 返回去噪后的 (B, T, N) + """ + B, T, N = x.shape + # reshape for conv1d: (B*N, 1, T) + x_flat = x.permute(0,2,1).contiguous().view(-1, 1, T) + # 分解 + cA = F.conv1d(x_flat, self.lp_filter, stride=2, padding=0) + # 重构 + # 反卷积: stride=2, output_padding=T%2 + out = F.conv_transpose1d(cA, self.lp_rec, stride=2, + output_padding=(T % 2)) + # 裁剪至原始长度 + out = out[:, :, :T] + # reshape back + x_dn = out.view(B, N, T).permute(0,2,1) + return x_dn + +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): + 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): + 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): + 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): + 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): + h1 = self.manba1(h) + adj = self.graph_constructor() + h2 = self.gc(h1, adj) + h3 = self.manba2(h2) + return h3 + +class MLP(nn.Module): + def __init__(self, in_dim, hidden_dims, out_dim, activation=nn.ReLU): + super().__init__() + dims = [in_dim] + hidden_dims + [out_dim] + layers = [] + for i in range(len(dims)-2): + layers += [nn.Linear(dims[i], dims[i+1]), activation()] + layers += [nn.Linear(dims[-2], dims[-1])] + self.net = nn.Sequential(*layers) + + def forward(self, x): + return self.net(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.embed_dim = args.get('embed_dim', 16) + + # ==== NEW: discrete time embeddings ==== + self.time_slots = args.get('time_slots', 24 * 60 // args.get('time_slot', 5)) + self.time_embedding = nn.Embedding(self.time_slots, self.hidden_dim) + self.day_embedding = nn.Embedding(7, self.hidden_dim) + + # ==== NEW: 小波去噪层 ==== + self.wavelet = WaveletDenoise() + + # input projection now only takes the denoised flow history + self.input_proj = MLP( + in_dim = self.seq_len, + hidden_dims = [self.hidden_dim], + out_dim = self.hidden_dim + ) + + # two Sandwich blocks remain unchanged + self.sandwich1 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim) + self.sandwich2 = SandwichBlock(self.num_nodes, self.embed_dim, self.hidden_dim) + + # output projection unchanged + self.out_proj = MLP( + in_dim = self.hidden_dim, + hidden_dims = [2 * self.hidden_dim], + out_dim = self.horizon * self.output_dim + ) + + def forward(self, x): + """ + x: (B, T, N, D_total) + D_total >= 3 where + x[...,0] = flow, + x[...,1] = time_in_day (0 … 1), + x[...,2] = day_in_week (0–6) + """ + x_flow = x[..., 0] # (B, T, N) + x_time = x[..., 1] # (B, T, N) + x_day = x[..., 2] # (B, T, N) + + B, T, N = x_flow.shape + assert T == self.seq_len + + # 1) 小波去噪 + x_dn = self.wavelet(x_flow) # (B, T, N) + + # 2) project the denoised flow history + x_flat = x_dn.permute(0, 2, 1).reshape(B * N, T) + h0 = self.input_proj(x_flat).view(B, N, self.hidden_dim) + + # 3) lookup discrete time indexes at the last time step + t_idx = (x_time[:, -1, :,] * (self.time_slots - 1)).long() + d_idx = x_day[:, -1, :,].long() + time_emb = self.time_embedding(t_idx) # (B, N, hidden_dim) + day_emb = self.day_embedding(d_idx) # (B, N, hidden_dim) + + # 4) inject them into the initial hidden state + h0 = h0 + time_emb + day_emb + + # 5) the usual Sandwich + residuals + h1 = self.sandwich1(h0) + h1 = h1 + h0 + h2 = self.sandwich2(h1) + + # 6) output projection + out = self.out_proj(h2) # (B, N, horizon*output_dim) + out = out.view(B, N, self.horizon, self.output_dim) + out = out.permute(0, 2, 1, 3) # (B, horizon, N, output_dim) + return out diff --git a/model/EXP/EXP3.py b/model/EXP/trash/EXP3.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP3.py rename to model/EXP/trash/EXP3.py diff --git a/model/EXP/trash/EXP30.py b/model/EXP/trash/EXP30.py new file mode 100755 index 0000000..a3e8280 --- /dev/null +++ b/model/EXP/trash/EXP30.py @@ -0,0 +1,217 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +""" +在 EXP 模型中添加趋势专家、周期专家和物理专家,并通过门控网络(Gating Network)动态融合专家输出 +""" + +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): + 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): + 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): + 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): + 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): + h1 = self.manba1(h) + adj = self.graph_constructor() + h2 = self.gc(h1, adj) + h3 = self.manba2(h2) + return h3 + + +# --------- 新增:专家网络定义 --------- +class TrendExpert(nn.Module): + """捕捉数据中的长期趋势""" + def __init__(self, hidden_dim): + super().__init__() + self.trend_mlp = nn.Sequential( + nn.Linear(hidden_dim, hidden_dim), + nn.ReLU(), + nn.Linear(hidden_dim, hidden_dim) + ) + + def forward(self, h): + return self.trend_mlp(h) + + +class PeriodicExpert(nn.Module): + """捕捉周期性模式""" + def __init__(self, hidden_dim): + super().__init__() + self.periodic_mlp = nn.Sequential( + nn.Linear(hidden_dim, hidden_dim), + nn.GELU(), + nn.Linear(hidden_dim, hidden_dim) + ) + + def forward(self, h): + # 占位:可扩展为傅里叶域处理 + return self.periodic_mlp(h) + + +class PhysicalExpert(nn.Module): + """基于物理规律的图卷积专家""" + def __init__(self, num_nodes, embed_dim, hidden_dim): + super().__init__() + self.graph_constructor = DynamicGraphConstructor(num_nodes, embed_dim) + self.graph_conv = GraphConvBlock(hidden_dim, hidden_dim) + + def forward(self, h): + adj = self.graph_constructor() + return self.graph_conv(h, adj) + + +class MLP(nn.Module): + def __init__(self, in_dim, hidden_dims, out_dim, activation=nn.ReLU): + super().__init__() + dims = [in_dim] + hidden_dims + [out_dim] + layers = [] + for i in range(len(dims)-2): + layers += [nn.Linear(dims[i], dims[i+1]), activation()] + layers += [nn.Linear(dims[-2], dims[-1])] + self.net = nn.Sequential(*layers) + + def forward(self, x): + return self.net(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.embed_dim = args.get('embed_dim', 16) + + # 时间嵌入 + self.time_slots = args.get('time_slots', 24*60//args.get('time_slot',5)) + self.time_embedding = nn.Embedding(self.time_slots, self.hidden_dim) + self.day_embedding = nn.Embedding(7, self.hidden_dim) + + # 输入流量投影 + self.input_proj = MLP( + in_dim=self.seq_len, + hidden_dims=[self.hidden_dim], + out_dim=self.hidden_dim + ) + + # --------- 新增:专家与门控网络 --------- + self.num_experts = 3 + self.trend_expert = TrendExpert(self.hidden_dim) + self.periodic_expert = PeriodicExpert(self.hidden_dim) + self.physical_expert = PhysicalExpert(self.num_nodes, self.embed_dim, self.hidden_dim) + # 门控网络,根据 h0 动态生成专家权重 + self.gating = nn.Sequential( + nn.Linear(self.hidden_dim, self.hidden_dim), + nn.ReLU(), + nn.Linear(self.hidden_dim, self.num_experts) + ) + + # 两个 Sandwich 模块 + 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 = MLP( + in_dim=self.hidden_dim, + hidden_dims=[2*self.hidden_dim], + out_dim=self.horizon * self.output_dim + ) + + def forward(self, x): + # x: (B, T, N, D_total) + x_flow = x[..., 0] # 流量 + x_time = x[..., 1] # 时间槽归一化 + x_day = x[..., 2] # 星期几 + B, T, N = x_flow.shape + assert T == self.seq_len + + # 1) 流量历史投影 + x_flat = x_flow.permute(0,2,1).reshape(B*N, T) + h0 = self.input_proj(x_flat).view(B, N, self.hidden_dim) + + # 2) 时间与星期嵌入 + t_idx = (x_time[:, -1, :,] * (self.time_slots - 1)).long() + d_idx = x_day[:, -1, :,].long() + time_emb = self.time_embedding(t_idx) + day_emb = self.day_embedding(d_idx) + # 注入 + h0 = h0 + time_emb + day_emb + + # 3) 门控融合专家输出 + g = self.gating(h0) # (B, N, 3) + g = F.softmax(g, dim=-1) + h_trend = self.trend_expert(h0) + h_periodic = self.periodic_expert(h0) + h_physical = self.physical_expert(h0) + # 加权相加 + h0 = g[..., 0:1] * h_trend + g[..., 1:2] * h_periodic + g[..., 2:3] * h_physical + + # 4) Sandwich + 残差 + h1 = self.sandwich1(h0) + h1 = h1 + h0 + h2 = self.sandwich2(h1) + + # 5) 输出 + out = self.out_proj(h2) + out = out.view(B, N, self.horizon, self.output_dim) + out = out.permute(0,2,1,3) + return out diff --git a/model/EXP/EXP3_easy.py b/model/EXP/trash/EXP3_easy.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP3_easy.py rename to model/EXP/trash/EXP3_easy.py diff --git a/model/EXP/EXP4.py b/model/EXP/trash/EXP4.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP4.py rename to model/EXP/trash/EXP4.py diff --git a/model/EXP/EXP5.py b/model/EXP/trash/EXP5.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP5.py rename to model/EXP/trash/EXP5.py diff --git a/model/EXP/EXP6.py b/model/EXP/trash/EXP6.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP6.py rename to model/EXP/trash/EXP6.py diff --git a/model/EXP/EXP7.py b/model/EXP/trash/EXP7.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP7.py rename to model/EXP/trash/EXP7.py diff --git a/model/EXP/EXP8b.py b/model/EXP/trash/EXP8b.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP8b.py rename to model/EXP/trash/EXP8b.py diff --git a/model/EXP/EXP9.py b/model/EXP/trash/EXP9.py old mode 100644 new mode 100755 similarity index 100% rename from model/EXP/EXP9.py rename to model/EXP/trash/EXP9.py diff --git a/model/EXPB/EXP_b.py b/model/EXPB/EXP_b.py old mode 100644 new mode 100755 diff --git a/model/GWN/GraphWaveNet.py b/model/GWN/GraphWaveNet.py old mode 100644 new mode 100755 diff --git a/model/GWN/GraphWaveNet_bk.py b/model/GWN/GraphWaveNet_bk.py old mode 100644 new mode 100755 diff --git a/model/GWN/GraphWaveNet_exp.py b/model/GWN/GraphWaveNet_exp.py old mode 100644 new mode 100755 diff --git a/model/NLT/HierAttnLstm.py b/model/NLT/HierAttnLstm.py old mode 100644 new mode 100755 diff --git a/model/PDG2SEQ/PDG2Seq.py b/model/PDG2SEQ/PDG2Seq.py old mode 100644 new mode 100755 diff --git a/model/PDG2SEQ/PDG2SeqCell.py b/model/PDG2SEQ/PDG2SeqCell.py old mode 100644 new mode 100755 diff --git a/model/PDG2SEQ/PDG2Seq_DGCN.py b/model/PDG2SEQ/PDG2Seq_DGCN.py old mode 100644 new mode 100755 diff --git a/model/PDG2SEQ/PDG2Seqb.py b/model/PDG2SEQ/PDG2Seqb.py new file mode 100755 index 0000000..c8a5d50 --- /dev/null +++ b/model/PDG2SEQ/PDG2Seqb.py @@ -0,0 +1,243 @@ +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +from typing import List, Tuple + + +class HyperMLP(nn.Module): + """General hypernetwork with configurable hidden dims""" + def __init__(self, in_dim: int, out_dim: int, hidden_dims: List[int], activation=nn.Sigmoid): + super().__init__() + layers = [] + dims = [in_dim] + hidden_dims + [out_dim] + for i in range(len(dims)-1): + layers.append(nn.Linear(dims[i], dims[i+1])) + if i < len(dims)-2: + layers.append(activation()) + self.net = nn.Sequential(*layers) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.net(x) + + +class ChebConv(nn.Module): + """Chebyshev graph convolution supporting learnable weights per node""" + def __init__( + self, + in_channels: int, + out_channels: int, + cheb_k: int, + embed_dim: int, + hidden_dims: List[int] # renamed to match instantiation + ): + super().__init__() + # weight pool: [embed_dim, cheb_k*2+1, in_channels, out_channels] + self.weight_pool = nn.Parameter( + torch.Tensor(embed_dim, cheb_k*2+1, in_channels, out_channels) + ) + self.bias_pool = nn.Parameter(torch.Tensor(embed_dim, out_channels)) + # hypernetwork for dynamic mix coefficients + # uses hidden_dims + self.hyper = HyperMLP(in_dim=in_channels + out_channels, out_dim=embed_dim, hidden_dims=hidden_dims) + self.k = cheb_k + self.reset_parameters() + + def reset_parameters(self): + nn.init.xavier_uniform_(self.weight_pool) + nn.init.zeros_(self.bias_pool) + + def forward( + self, + x: torch.Tensor, # [B, N, in_channels] + supports: List[torch.Tensor] # list of [N, N] adjacency matrices + ) -> torch.Tensor: + B, N, C_in = x.shape + # compute hyper coefficients per batch + agg = x.mean(dim=1) # [B, in_channels] + coeff = self.hyper(agg) # [B, embed_dim] + + # build dynamic weights and bias + # weights_pool: [E, K, in, out]; coeff: [B, E] -> [B, K, in, out] + W = torch.einsum('be,ekio->bkio', coeff, self.weight_pool) # [B, K, in, out] + b = torch.einsum('be,eo->bo', coeff, self.bias_pool) # [B, out] + + # gather K+1 supports: original and repeated diffusions + x0 = x # [B, N, in_channels] + x_list = [x0] + for support in supports: + x1 = torch.einsum('ij,bjk->bik', support, x0) + x_list.append(x1) + xk = x1 + for _ in range(2, self.k+1): + xk = torch.einsum('ij,bjk->bik', support, xk) + x_list.append(xk) + # stack to [B, N, K+1, in] + x_stack = torch.stack(x_list, dim=2) + # apply weights + out = torch.einsum('bnki,bkio->bno', x_stack, W) + b.unsqueeze(1) + return out + + +class PDG2SeqCell(nn.Module): + def __init__( + self, + node_num: int, + in_dim: int, + hidden_dim: int, + cheb_k: int, + embed_dim: int, + time_dim: int + ): + super().__init__() + self.hidden_dim = hidden_dim + merge_dim = in_dim + hidden_dim + # gates and candidate use ChebConv with hidden_dims=[time_dim] + self.conv_gate = ChebConv(merge_dim, 2*hidden_dim, cheb_k, embed_dim, hidden_dims=[time_dim]) + self.conv_cand = ChebConv(merge_dim, hidden_dim, cheb_k, embed_dim, hidden_dims=[time_dim]) + + def forward( + self, + x: torch.Tensor, # [B, N, in_dim] + h_prev: torch.Tensor, # [B, N, hidden_dim] + supports: List[torch.Tensor] # dynamic supports + ) -> torch.Tensor: + merged = torch.cat([x, h_prev], dim=-1) + gates = self.conv_gate(merged, supports) + z, r = gates.chunk(2, dim=-1) + z, r = torch.sigmoid(z), torch.sigmoid(r) + merged_cand = torch.cat([x, z * h_prev], dim=-1) + h_tilde = torch.tanh(self.conv_cand(merged_cand, supports)) + h = r * h_prev + (1 - r) * h_tilde + return h + + def init_hidden(self, batch_size: int) -> torch.Tensor: + return torch.zeros(batch_size, self.node_num, self.hidden_dim, device=next(self.parameters()).device) + + +class PDG2SeqEncoder(nn.Module): + def __init__( + self, + node_num: int, + input_dim: int, + hidden_dim: int, + cheb_k: int, + embed_dim: int, + time_dim: int, + num_layers: int = 1 + ): + super().__init__() + self.cells = nn.ModuleList([ + PDG2SeqCell(node_num, input_dim if i==0 else hidden_dim, + hidden_dim, cheb_k, embed_dim, time_dim) + for i in range(num_layers) + ]) + + def forward( + self, + x_seq: torch.Tensor, # [B, T, N, in_dim] + h0: torch.Tensor, # [num_layers, B, N, hidden] + supports_seq: List[List[torch.Tensor]] + ) -> Tuple[torch.Tensor, torch.Tensor]: + B, T, N, _ = x_seq.shape + states = [] + inputs = x_seq + for layer, cell in enumerate(self.cells): + h = h0[layer] + outs = [] + for t in range(T): + h = cell(inputs[:, t], h, supports_seq[t]) + outs.append(h) + inputs = torch.stack(outs, dim=1) + states.append(h) + return inputs, torch.stack(states, dim=0) + + +class PDG2SeqDecoder(nn.Module): + def __init__( + self, + node_num: int, + input_dim: int, + hidden_dim: int, + cheb_k: int, + embed_dim: int, + time_dim: int, + num_layers: int = 1 + ): + super().__init__() + self.cells = nn.ModuleList([ + PDG2SeqCell(node_num, input_dim if i==0 else hidden_dim, + hidden_dim, cheb_k, embed_dim, time_dim) + for i in range(num_layers) + ]) + + def forward( + self, + go: torch.Tensor, # [B, N, input_dim] + h_prev: torch.Tensor, # [num_layers, B, N, hidden] + supports: List[torch.Tensor] + ) -> Tuple[torch.Tensor, torch.Tensor]: + new_states = [] + inp = go + for layer, cell in enumerate(self.cells): + h = cell(inp, h_prev[layer], supports) + new_states.append(h) + inp = h + return inp, torch.stack(new_states, dim=0) + + +class PDG2Seq(nn.Module): + def __init__(self, args): + super().__init__() + self.num_nodes = args['num_nodes'] + self.horizon = args['horizon'] + self.rnn = args['rnn_units'] + self.encoder = PDG2SeqEncoder( + self.num_nodes, args['input_dim'], args['rnn_units'], + args['cheb_k'], args['embed_dim'], args['time_dim'], args['num_layers'] + ) + self.decoder = PDG2SeqDecoder( + self.num_nodes, args['output_dim'], args['rnn_units'], + args['cheb_k'], args['embed_dim'], args['time_dim'], args['num_layers'] + ) + self.proj = nn.Linear(args['rnn_units'], args['output_dim']) + self.time_emb = nn.Embedding(288, args['time_dim']) + self.week_emb = nn.Embedding(7, args['time_dim']) + self.cl_decay = args['lr_decay_step'] + + def forward( + self, + source: torch.Tensor, # [B, T1, N, D] + target: torch.Tensor = None, + batches_seen: int = 0, + supports_seq: List[List[torch.Tensor]] = None, + ) -> torch.Tensor: + # time embeddings + t_idx = (source[..., -2] * 287).long() + w_idx = source[..., -1].long() + # can extend embedding combination + supports_seq = supports_seq or [[] for _ in range(source.size(1))] + B = source.size(0) + # init hidden + h0 = torch.zeros( + len(self.encoder.cells), B, self.num_nodes, self.rnn, + device=source.device + ) + enc_out, enc_states = self.encoder(source[..., :1], h0, supports_seq) + last = enc_out[:, -1] + go = last + h_prev = enc_states + outputs = [] + for t in range(self.horizon): + step_supports = supports_seq[-1] if supports_seq else [] + go, h_prev = self.decoder(go, h_prev, step_supports) + pred = self.proj(go) + outputs.append(pred) + if self.training and target is not None: + thresh = self.cl_decay / (self.cl_decay + math.exp(batches_seen/self.cl_decay)) + if torch.rand(1).item() < thresh: + go = target[:, t, :, :1] + else: + go = pred + return torch.stack(outputs, dim=1) diff --git a/model/STAEFormer/STAEFormer.py b/model/STAEFormer/STAEFormer.py new file mode 100755 index 0000000..0f8b187 --- /dev/null +++ b/model/STAEFormer/STAEFormer.py @@ -0,0 +1,237 @@ +import torch.nn as nn +import torch + + +class AttentionLayer(nn.Module): + """Perform attention across the -2 dim (the -1 dim is `model_dim`). + + Make sure the tensor is permuted to correct shape before attention. + + E.g. + - Input shape (batch_size, in_steps, num_nodes, model_dim). + - Then the attention will be performed across the nodes. + + Also, it supports different src and tgt length. + + But must `src length == K length == V length`. + + """ + + def __init__(self, model_dim, num_heads=8, mask=False): + super().__init__() + + self.model_dim = model_dim + self.num_heads = num_heads + self.mask = mask + + self.head_dim = model_dim // num_heads + + self.FC_Q = nn.Linear(model_dim, model_dim) + self.FC_K = nn.Linear(model_dim, model_dim) + self.FC_V = nn.Linear(model_dim, model_dim) + + self.out_proj = nn.Linear(model_dim, model_dim) + + def forward(self, query, key, value): + # Q (batch_size, ..., tgt_length, model_dim) + # K, V (batch_size, ..., src_length, model_dim) + batch_size = query.shape[0] + tgt_length = query.shape[-2] + src_length = key.shape[-2] + + query = self.FC_Q(query) + key = self.FC_K(key) + value = self.FC_V(value) + + # Qhead, Khead, Vhead (num_heads * batch_size, ..., length, head_dim) + query = torch.cat(torch.split(query, self.head_dim, dim=-1), dim=0) + key = torch.cat(torch.split(key, self.head_dim, dim=-1), dim=0) + value = torch.cat(torch.split(value, self.head_dim, dim=-1), dim=0) + + key = key.transpose( + -1, -2 + ) # (num_heads * batch_size, ..., head_dim, src_length) + + attn_score = ( + query @ key + ) / self.head_dim**0.5 # (num_heads * batch_size, ..., tgt_length, src_length) + + if self.mask: + mask = torch.ones( + tgt_length, src_length, dtype=torch.bool, device=query.device + ).tril() # lower triangular part of the matrix + attn_score.masked_fill_(~mask, -torch.inf) # fill in-place + + attn_score = torch.softmax(attn_score, dim=-1) + out = attn_score @ value # (num_heads * batch_size, ..., tgt_length, head_dim) + out = torch.cat( + torch.split(out, batch_size, dim=0), dim=-1 + ) # (batch_size, ..., tgt_length, head_dim * num_heads = model_dim) + + out = self.out_proj(out) + + return out + + +class SelfAttentionLayer(nn.Module): + def __init__( + self, model_dim, feed_forward_dim=2048, num_heads=8, dropout=0, mask=False + ): + super().__init__() + + self.attn = AttentionLayer(model_dim, num_heads, mask) + self.feed_forward = nn.Sequential( + nn.Linear(model_dim, feed_forward_dim), + nn.ReLU(inplace=True), + nn.Linear(feed_forward_dim, model_dim), + ) + self.ln1 = nn.LayerNorm(model_dim) + self.ln2 = nn.LayerNorm(model_dim) + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + + def forward(self, x, dim=-2): + x = x.transpose(dim, -2) + # x: (batch_size, ..., length, model_dim) + residual = x + out = self.attn(x, x, x) # (batch_size, ..., length, model_dim) + out = self.dropout1(out) + out = self.ln1(residual + out) + + residual = out + out = self.feed_forward(out) # (batch_size, ..., length, model_dim) + out = self.dropout2(out) + out = self.ln2(residual + out) + + out = out.transpose(dim, -2) + return out + + +class STAEformer(nn.Module): + def __init__(self, args): + super().__init__() + self.num_nodes = args['num_nodes'] + self.in_steps = args.get('in_steps', 12) + self.out_steps = args.get('out_steps', 12) + self.steps_per_day = args.get('steps_per_day', 288) + self.input_dim = args.get('input_dim', 3) + self.output_dim = args.get('output_dim', 1) + self.input_embedding_dim = args.get('input_embedding_dim', 24) + self.tod_embedding_dim = args.get('tod_embedding_dim', 24) + self.dow_embedding_dim = args.get('dow_embedding_dim', 24) + self.spatial_embedding_dim = args.get('spatial_embedding_dim', 0) + self.adaptive_embedding_dim = args.get('adaptive_embedding_dim', 80) + self.feed_forward_dim = args.get('feed_forward_dim', 256) + self.num_heads = args.get('num_heads', 4) + self.num_layers = args.get('num_layers', 3) + self.dropout = args.get('dropout', 0.1) + self.use_mixed_proj = args.get('use_mixed_proj', True) + + self.model_dim = ( + self.input_embedding_dim + + self.tod_embedding_dim + + self.dow_embedding_dim + + self.spatial_embedding_dim + + self.adaptive_embedding_dim + ) + + self.input_proj = nn.Linear(self.input_dim, self.input_embedding_dim) + if self.tod_embedding_dim > 0: + self.tod_embedding = nn.Embedding(self.steps_per_day, self.tod_embedding_dim) + if self.dow_embedding_dim > 0: + self.dow_embedding = nn.Embedding(7, self.dow_embedding_dim) + if self.spatial_embedding_dim > 0: + self.node_emb = nn.Parameter( + torch.empty(self.num_nodes, self.spatial_embedding_dim) + ) + nn.init.xavier_uniform_(self.node_emb) + if self.adaptive_embedding_dim > 0: + self.adaptive_embedding = nn.init.xavier_uniform_( + nn.Parameter(torch.empty(self.in_steps, self.num_nodes, self.adaptive_embedding_dim)) + ) + + if self.use_mixed_proj: + self.output_proj = nn.Linear( + self.in_steps * self.model_dim, self.out_steps * self.output_dim + ) + else: + self.temporal_proj = nn.Linear(self.in_steps, self.out_steps) + self.output_proj = nn.Linear(self.model_dim, self.output_dim) + + self.attn_layers_t = nn.ModuleList( + [ + SelfAttentionLayer(self.model_dim, self.feed_forward_dim, self.num_heads, self.dropout) + for _ in range(self.num_layers) + ] + ) + + self.attn_layers_s = nn.ModuleList( + [ + SelfAttentionLayer(self.model_dim, self.feed_forward_dim, self.num_heads, self.dropout) + for _ in range(self.num_layers) + ] + ) + + def forward(self, x): + # x: (batch_size, in_steps, num_nodes, input_dim+tod+dow=3) + batch_size = x.shape[0] + + if self.tod_embedding_dim > 0: + tod = x[..., 1] + if self.dow_embedding_dim > 0: + dow = x[..., 2] + x = x[..., 0:1] + + x = self.input_proj(x) # (batch_size, in_steps, num_nodes, input_embedding_dim) + features = [x] + if self.tod_embedding_dim > 0: + tod_emb = self.tod_embedding( + (tod * self.steps_per_day).long() + ) # (batch_size, in_steps, num_nodes, tod_embedding_dim) + features.append(tod_emb) + if self.dow_embedding_dim > 0: + dow_emb = self.dow_embedding( + dow.long() + ) # (batch_size, in_steps, num_nodes, dow_embedding_dim) + features.append(dow_emb) + if self.spatial_embedding_dim > 0: + spatial_emb = self.node_emb.expand( + batch_size, self.in_steps, *self.node_emb.shape + ) + features.append(spatial_emb) + if self.adaptive_embedding_dim > 0: + adp_emb = self.adaptive_embedding.expand( + size=(batch_size, *self.adaptive_embedding.shape) + ) + features.append(adp_emb) + x = torch.cat(features, dim=-1) # (batch_size, in_steps, num_nodes, model_dim) + + for attn in self.attn_layers_t: + x = attn(x, dim=1) + for attn in self.attn_layers_s: + x = attn(x, dim=2) + # (batch_size, in_steps, num_nodes, model_dim) + + if self.use_mixed_proj: + out = x.transpose(1, 2) # (batch_size, num_nodes, in_steps, model_dim) + out = out.reshape( + batch_size, self.num_nodes, self.in_steps * self.model_dim + ) + out = self.output_proj(out).view( + batch_size, self.num_nodes, self.out_steps, self.output_dim + ) + out = out.transpose(1, 2) # (batch_size, out_steps, num_nodes, output_dim) + else: + out = x.transpose(1, 3) # (batch_size, model_dim, num_nodes, in_steps) + out = self.temporal_proj( + out + ) # (batch_size, model_dim, num_nodes, out_steps) + out = self.output_proj( + out.transpose(1, 3) + ) # (batch_size, out_steps, num_nodes, output_dim) + + return out + + +if __name__ == "__main__": + model = STAEformer(207, 12, 12) diff --git a/model/STFGNN/STFGNN.py b/model/STFGNN/STFGNN.py old mode 100644 new mode 100755 diff --git a/model/STGCN/layers.py b/model/STGCN/layers.py old mode 100644 new mode 100755 diff --git a/model/STGCN/models.py b/model/STGCN/models.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/BasicTrainer_cde.py b/model/STGNCDE/BasicTrainer_cde.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/GCDE.py b/model/STGNCDE/GCDE.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/Make_model.py b/model/STGNCDE/Make_model.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/PEMSD4_GCDE.conf b/model/STGNCDE/PEMSD4_GCDE.conf old mode 100644 new mode 100755 diff --git a/model/STGNCDE/Run_cde.py b/model/STGNCDE/Run_cde.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/controldiffeq/__init__.py b/model/STGNCDE/controldiffeq/__init__.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/controldiffeq/cdeint_module.py b/model/STGNCDE/controldiffeq/cdeint_module.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/controldiffeq/interpolate.py b/model/STGNCDE/controldiffeq/interpolate.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/controldiffeq/misc.py b/model/STGNCDE/controldiffeq/misc.py old mode 100644 new mode 100755 diff --git a/model/STGNCDE/vector_fields.py b/model/STGNCDE/vector_fields.py old mode 100644 new mode 100755 diff --git a/model/STGODE/STGODE.py b/model/STGODE/STGODE.py old mode 100644 new mode 100755 diff --git a/model/STGODE/adj.py b/model/STGODE/adj.py old mode 100644 new mode 100755 diff --git a/model/STGODE/odegcn.py b/model/STGODE/odegcn.py old mode 100644 new mode 100755 diff --git a/model/STID/MLP.py b/model/STID/MLP.py old mode 100644 new mode 100755 diff --git a/model/STID/STID.py b/model/STID/STID.py old mode 100644 new mode 100755 diff --git a/model/STSGCN/STSGCN.py b/model/STSGCN/STSGCN.py old mode 100644 new mode 100755 diff --git a/model/STSGCN/get_adj.py b/model/STSGCN/get_adj.py old mode 100644 new mode 100755 diff --git a/model/TCN/TCN.py b/model/TCN/TCN.py old mode 100644 new mode 100755 diff --git a/model/TWDGCN/ConnectionMatrix.py b/model/TWDGCN/ConnectionMatrix.py old mode 100644 new mode 100755 diff --git a/model/TWDGCN/ConnectionMatrix2.py b/model/TWDGCN/ConnectionMatrix2.py old mode 100644 new mode 100755 diff --git a/model/TWDGCN/ConnectionMatrix_old.py b/model/TWDGCN/ConnectionMatrix_old.py old mode 100644 new mode 100755 diff --git a/model/TWDGCN/DGCN.py b/model/TWDGCN/DGCN.py old mode 100644 new mode 100755 diff --git a/model/TWDGCN/DGCRU.py b/model/TWDGCN/DGCRU.py old mode 100644 new mode 100755 diff --git a/model/TWDGCN/TWDGCN.py b/model/TWDGCN/TWDGCN.py old mode 100644 new mode 100755 diff --git a/model/model_selector.py b/model/model_selector.py old mode 100644 new mode 100755 index 7df6e32..ffe79b3 --- a/model/model_selector.py +++ b/model/model_selector.py @@ -12,9 +12,10 @@ from model.GWN.GraphWaveNet import gwnet from model.STFGNN.STFGNN import STFGNN from model.STSGCN.STSGCN import STSGCN from model.STGODE.STGODE import ODEGCN -from model.PDG2SEQ.PDG2Seq import PDG2Seq +from model.PDG2SEQ.PDG2Seqb import PDG2Seq from model.STID.STID import STID -from model.EXP.EXP26 import EXP as EXP +from model.STAEFormer.STAEFormer import STAEformer +from model.EXP.EXP31 import EXP as EXP def model_selector(model): match model['type']: @@ -34,5 +35,6 @@ def model_selector(model): case 'STGODE': return ODEGCN(model) case 'PDG2SEQ': return PDG2Seq(model) case 'STID': return STID(model) + case 'STAEFormer': return STAEformer(model) case 'EXP': return EXP(model) diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/run.py b/run.py old mode 100644 new mode 100755 diff --git a/trainer/DCRNN_Trainer.py b/trainer/DCRNN_Trainer.py old mode 100644 new mode 100755 diff --git a/trainer/EXP_trainer.py b/trainer/EXP_trainer.py old mode 100644 new mode 100755 diff --git a/trainer/PDG2SEQ_Trainer.py b/trainer/PDG2SEQ_Trainer.py old mode 100644 new mode 100755 diff --git a/trainer/Trainer.py b/trainer/Trainer.py old mode 100644 new mode 100755 diff --git a/trainer/cdeTrainer/cdetrainer.py b/trainer/cdeTrainer/cdetrainer.py old mode 100644 new mode 100755 diff --git a/trainer/trainer_selector.py b/trainer/trainer_selector.py old mode 100644 new mode 100755 diff --git a/transfer_guide.md b/transfer_guide.md old mode 100644 new mode 100755 diff --git a/utils/ADFtest.py b/utils/ADFtest.py new file mode 100755 index 0000000..967f776 --- /dev/null +++ b/utils/ADFtest.py @@ -0,0 +1,74 @@ +import pandas as pd +import numpy as np +import os +from statsmodels.tsa.stattools import adfuller +from arch.unitroot import ADF + +def calculate_ADF(root_path,data_path): + df_raw = pd.read_csv(os.path.join(root_path,data_path)) + cols = list(df_raw.columns) + cols.remove('date') + df_raw = df_raw[cols] + adf_list = [] + for i in cols: + df_data = df_raw[i] + adf = adfuller(df_data, maxlag = 1) + print(adf) + adf_list.append(adf) + return np.array(adf_list) + +def calculate_target_ADF(root_path,data_path,target='OT'): + df_raw = pd.read_csv(os.path.join(root_path,data_path)) + target_cols = target.split(',') + # df_data = df_raw[target] + df_raw = df_raw[target_cols] + adf_list = [] + for i in target_cols: + df_data = df_raw[i] + adf = adfuller(df_data, maxlag = 1) + # print(adf) + adf_list.append(adf) + return np.array(adf_list) + +def archADF(root_path, data_path): + df = pd.read_csv(os.path.join(root_path,data_path)) + cols = df.columns[1:] + stats = 0 + for target_col in cols: + series = df[target_col].values + adf = ADF(series) + stat = adf.stat + stats += stat + return stats/len(cols) + +if __name__ == '__main__': + + # * Exchange - result: -1.902402344564288 | report: -1.889 + ADFmetric = archADF(root_path="./dataset/exchange_rate/",data_path="exchange_rate.csv") + print("Exchange ADF metric", ADFmetric) + + # * Illness - result: -5.33416661870624 | report: -5.406 + ADFmetric = archADF(root_path="./dataset/illness/",data_path="national_illness.csv") + print("Illness ADF metric", ADFmetric) + + # * ETTm2 - result: -5.663628743471695 | report: -6.225 + ADFmetric = archADF(root_path="./dataset/ETT-small/",data_path="ETTm2.csv") + print("ETTm2 ADF metric", ADFmetric) + + # * Electricity - result: -8.44485821939281 | report: -8.483 + ADFmetric = archADF(root_path="./dataset/electricity/",data_path="electricity.csv") + print("Electricity ADF metric", ADFmetric) + + # * Traffic - result: -15.020978067839014 | report: -15.046 + ADFmetric = archADF(root_path="./dataset/traffic/",data_path="traffic.csv") + print("Traffic ADF metric", ADFmetric) + + # * Weather - result: -26.681433085204866 | report: -26.661 + ADFmetric = archADF(root_path="./dataset/weather/",data_path="weather.csv") + print("Weather ADF metric", ADFmetric) + + + # print(ADFmetric) + + # mean_ADFmetric = ADFmetric[:,0].mean() + # print(mean_ADFmetric) \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/utils/augmentation.py b/utils/augmentation.py new file mode 100755 index 0000000..e574f35 --- /dev/null +++ b/utils/augmentation.py @@ -0,0 +1,434 @@ +import numpy as np +from tqdm import tqdm + +def jitter(x, sigma=0.03): + # https://arxiv.org/pdf/1706.00527.pdf + return x + np.random.normal(loc=0., scale=sigma, size=x.shape) + + +def scaling(x, sigma=0.1): + # https://arxiv.org/pdf/1706.00527.pdf + factor = np.random.normal(loc=1., scale=sigma, size=(x.shape[0],x.shape[2])) + return np.multiply(x, factor[:,np.newaxis,:]) + +def rotation(x): + x = np.array(x) + flip = np.random.choice([-1, 1], size=(x.shape[0],x.shape[2])) + rotate_axis = np.arange(x.shape[2]) + np.random.shuffle(rotate_axis) + return flip[:,np.newaxis,:] * x[:,:,rotate_axis] + +def permutation(x, max_segments=5, seg_mode="equal"): + orig_steps = np.arange(x.shape[1]) + + num_segs = np.random.randint(1, max_segments, size=(x.shape[0])) + + ret = np.zeros_like(x) + for i, pat in enumerate(x): + if num_segs[i] > 1: + if seg_mode == "random": + split_points = np.random.choice(x.shape[1]-2, num_segs[i]-1, replace=False) + split_points.sort() + splits = np.split(orig_steps, split_points) + else: + splits = np.array_split(orig_steps, num_segs[i]) + warp = np.concatenate(np.random.permutation(splits)).ravel() + # ? Question: What is the point of making segments? + # for i in range(len(splits)): + # permute = np.random.permutation(splits[i]) + + + ret[i] = pat[warp] + else: + ret[i] = pat + return ret + +def magnitude_warp(x, sigma=0.2, knot=4): + from scipy.interpolate import CubicSpline + orig_steps = np.arange(x.shape[1]) + + random_warps = np.random.normal(loc=1.0, scale=sigma, size=(x.shape[0], knot+2, x.shape[2])) + warp_steps = (np.ones((x.shape[2],1))*(np.linspace(0, x.shape[1]-1., num=knot+2))).T + ret = np.zeros_like(x) + for i, pat in enumerate(x): + warper = np.array([CubicSpline(warp_steps[:,dim], random_warps[i,:,dim])(orig_steps) for dim in range(x.shape[2])]).T + ret[i] = pat * warper + + return ret + +def time_warp(x, sigma=0.2, knot=4): + from scipy.interpolate import CubicSpline + orig_steps = np.arange(x.shape[1]) + + random_warps = np.random.normal(loc=1.0, scale=sigma, size=(x.shape[0], knot+2, x.shape[2])) + warp_steps = (np.ones((x.shape[2],1))*(np.linspace(0, x.shape[1]-1., num=knot+2))).T + + ret = np.zeros_like(x) + for i, pat in enumerate(x): + for dim in range(x.shape[2]): + time_warp = CubicSpline(warp_steps[:,dim], warp_steps[:,dim] * random_warps[i,:,dim])(orig_steps) + scale = (x.shape[1]-1)/time_warp[-1] + ret[i,:,dim] = np.interp(orig_steps, np.clip(scale*time_warp, 0, x.shape[1]-1), pat[:,dim]).T + return ret + +def window_slice(x, reduce_ratio=0.9): + # https://halshs.archives-ouvertes.fr/halshs-01357973/document + target_len = np.ceil(reduce_ratio*x.shape[1]).astype(int) + if target_len >= x.shape[1]: + return x + starts = np.random.randint(low=0, high=x.shape[1]-target_len, size=(x.shape[0])).astype(int) + ends = (target_len + starts).astype(int) + + ret = np.zeros_like(x) + for i, pat in enumerate(x): + for dim in range(x.shape[2]): + ret[i,:,dim] = np.interp(np.linspace(0, target_len, num=x.shape[1]), np.arange(target_len), pat[starts[i]:ends[i],dim]).T + return ret + +def window_warp(x, window_ratio=0.1, scales=[0.5, 2.]): + # https://halshs.archives-ouvertes.fr/halshs-01357973/document + warp_scales = np.random.choice(scales, x.shape[0]) + warp_size = np.ceil(window_ratio*x.shape[1]).astype(int) + window_steps = np.arange(warp_size) + + window_starts = np.random.randint(low=1, high=x.shape[1]-warp_size-1, size=(x.shape[0])).astype(int) + window_ends = (window_starts + warp_size).astype(int) + + ret = np.zeros_like(x) + for i, pat in enumerate(x): + for dim in range(x.shape[2]): + start_seg = pat[:window_starts[i],dim] + window_seg = np.interp(np.linspace(0, warp_size-1, num=int(warp_size*warp_scales[i])), window_steps, pat[window_starts[i]:window_ends[i],dim]) + end_seg = pat[window_ends[i]:,dim] + warped = np.concatenate((start_seg, window_seg, end_seg)) + ret[i,:,dim] = np.interp(np.arange(x.shape[1]), np.linspace(0, x.shape[1]-1., num=warped.size), warped).T + return ret + +def spawner(x, labels, sigma=0.05, verbose=0): + # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6983028/ + # use verbose=-1 to turn off warnings + # use verbose=1 to print out figures + + import utils.dtw as dtw + random_points = np.random.randint(low=1, high=x.shape[1]-1, size=x.shape[0]) + window = np.ceil(x.shape[1] / 10.).astype(int) + orig_steps = np.arange(x.shape[1]) + l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels + + ret = np.zeros_like(x) + # for i, pat in enumerate(tqdm(x)): + for i, pat in enumerate(x): + # guarentees that same one isnt selected + choices = np.delete(np.arange(x.shape[0]), i) + # remove ones of different classes + choices = np.where(l[choices] == l[i])[0] + if choices.size > 0: + random_sample = x[np.random.choice(choices)] + # SPAWNER splits the path into two randomly + path1 = dtw.dtw(pat[:random_points[i]], random_sample[:random_points[i]], dtw.RETURN_PATH, slope_constraint="symmetric", window=window) + path2 = dtw.dtw(pat[random_points[i]:], random_sample[random_points[i]:], dtw.RETURN_PATH, slope_constraint="symmetric", window=window) + combined = np.concatenate((np.vstack(path1), np.vstack(path2+random_points[i])), axis=1) + if verbose: + # print(random_points[i]) + dtw_value, cost, DTW_map, path = dtw.dtw(pat, random_sample, return_flag = dtw.RETURN_ALL, slope_constraint=slope_constraint, window=window) + dtw.draw_graph1d(cost, DTW_map, path, pat, random_sample) + dtw.draw_graph1d(cost, DTW_map, combined, pat, random_sample) + mean = np.mean([pat[combined[0]], random_sample[combined[1]]], axis=0) + for dim in range(x.shape[2]): + ret[i,:,dim] = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=mean.shape[0]), mean[:,dim]).T + else: + # if verbose > -1: + # print("There is only one pattern of class {}, skipping pattern average".format(l[i])) + ret[i,:] = pat + return jitter(ret, sigma=sigma) + +def wdba(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True, verbose=0): + # https://ieeexplore.ieee.org/document/8215569 + # use verbose = -1 to turn off warnings + # slope_constraint is for DTW. "symmetric" or "asymmetric" + x = np.array(x) + import utils.dtw as dtw + + if use_window: + window = np.ceil(x.shape[1] / 10.).astype(int) + else: + window = None + orig_steps = np.arange(x.shape[1]) + l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels + + ret = np.zeros_like(x) + # for i in tqdm(range(ret.shape[0])): + for i in range(ret.shape[0]): + # get the same class as i + choices = np.where(l == l[i])[0] + if choices.size > 0: + # pick random intra-class pattern + k = min(choices.size, batch_size) + random_prototypes = x[np.random.choice(choices, k, replace=False)] + + # calculate dtw between all + dtw_matrix = np.zeros((k, k)) + for p, prototype in enumerate(random_prototypes): + for s, sample in enumerate(random_prototypes): + if p == s: + dtw_matrix[p, s] = 0. + else: + dtw_matrix[p, s] = dtw.dtw(prototype, sample, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window) + + # get medoid + medoid_id = np.argsort(np.sum(dtw_matrix, axis=1))[0] + nearest_order = np.argsort(dtw_matrix[medoid_id]) + medoid_pattern = random_prototypes[medoid_id] + + # start weighted DBA + average_pattern = np.zeros_like(medoid_pattern) + weighted_sums = np.zeros((medoid_pattern.shape[0])) + for nid in nearest_order: + if nid == medoid_id or dtw_matrix[medoid_id, nearest_order[1]] == 0.: + average_pattern += medoid_pattern + weighted_sums += np.ones_like(weighted_sums) + else: + path = dtw.dtw(medoid_pattern, random_prototypes[nid], dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window) + dtw_value = dtw_matrix[medoid_id, nid] + warped = random_prototypes[nid, path[1]] + weight = np.exp(np.log(0.5)*dtw_value/dtw_matrix[medoid_id, nearest_order[1]]) + average_pattern[path[0]] += weight * warped + weighted_sums[path[0]] += weight + + ret[i,:] = average_pattern / weighted_sums[:,np.newaxis] + else: + # if verbose > -1: + # print("There is only one pattern of class {}, skipping pattern average".format(l[i])) + ret[i,:] = x[i] + return ret + +# Proposed + +def random_guided_warp(x, labels, slope_constraint="symmetric", use_window=True, dtw_type="normal", verbose=0): + # use verbose = -1 to turn off warnings + # slope_constraint is for DTW. "symmetric" or "asymmetric" + # dtw_type is for shapeDTW or DTW. "normal" or "shape" + + import utils.dtw as dtw + + if use_window: + window = np.ceil(x.shape[1] / 10.).astype(int) + else: + window = None + orig_steps = np.arange(x.shape[1]) + l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels + + ret = np.zeros_like(x) + # for i, pat in enumerate(tqdm(x)): + for i, pat in enumerate(x): + # guarentees that same one isnt selected + choices = np.delete(np.arange(x.shape[0]), i) + # remove ones of different classes + choices = np.where(l[choices] == l[i])[0] + if choices.size > 0: + # pick random intra-class pattern + random_prototype = x[np.random.choice(choices)] + + if dtw_type == "shape": + path = dtw.shape_dtw(random_prototype, pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window) + else: + path = dtw.dtw(random_prototype, pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window) + + # Time warp + warped = pat[path[1]] + for dim in range(x.shape[2]): + ret[i,:,dim] = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), warped[:,dim]).T + else: + # if verbose > -1: + # print("There is only one pattern of class {}, skipping timewarping".format(l[i])) + ret[i,:] = pat + return ret + +def random_guided_warp_shape(x, labels, slope_constraint="symmetric", use_window=True): + return random_guided_warp(x, labels, slope_constraint, use_window, dtw_type="shape") + +def discriminative_guided_warp(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True, dtw_type="normal", use_variable_slice=True, verbose=0): + # use verbose = -1 to turn off warnings + # slope_constraint is for DTW. "symmetric" or "asymmetric" + # dtw_type is for shapeDTW or DTW. "normal" or "shape" + + import utils.dtw as dtw + + if use_window: + window = np.ceil(x.shape[1] / 10.).astype(int) + else: + window = None + orig_steps = np.arange(x.shape[1]) + l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels + + positive_batch = np.ceil(batch_size / 2).astype(int) + negative_batch = np.floor(batch_size / 2).astype(int) + + ret = np.zeros_like(x) + warp_amount = np.zeros(x.shape[0]) + # for i, pat in enumerate(tqdm(x)): + for i, pat in enumerate(x): + # guarentees that same one isnt selected + choices = np.delete(np.arange(x.shape[0]), i) + + # remove ones of different classes + positive = np.where(l[choices] == l[i])[0] + negative = np.where(l[choices] != l[i])[0] + + if positive.size > 0 and negative.size > 0: + pos_k = min(positive.size, positive_batch) + neg_k = min(negative.size, negative_batch) + positive_prototypes = x[np.random.choice(positive, pos_k, replace=False)] + negative_prototypes = x[np.random.choice(negative, neg_k, replace=False)] + + # vector embedding and nearest prototype in one + pos_aves = np.zeros((pos_k)) + neg_aves = np.zeros((pos_k)) + if dtw_type == "shape": + for p, pos_prot in enumerate(positive_prototypes): + for ps, pos_samp in enumerate(positive_prototypes): + if p != ps: + pos_aves[p] += (1./(pos_k-1.))*dtw.shape_dtw(pos_prot, pos_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window) + for ns, neg_samp in enumerate(negative_prototypes): + neg_aves[p] += (1./neg_k)*dtw.shape_dtw(pos_prot, neg_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window) + selected_id = np.argmax(neg_aves - pos_aves) + path = dtw.shape_dtw(positive_prototypes[selected_id], pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window) + else: + for p, pos_prot in enumerate(positive_prototypes): + for ps, pos_samp in enumerate(positive_prototypes): + if p != ps: + pos_aves[p] += (1./(pos_k-1.))*dtw.dtw(pos_prot, pos_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window) + for ns, neg_samp in enumerate(negative_prototypes): + neg_aves[p] += (1./neg_k)*dtw.dtw(pos_prot, neg_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window) + selected_id = np.argmax(neg_aves - pos_aves) + path = dtw.dtw(positive_prototypes[selected_id], pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window) + + # Time warp + warped = pat[path[1]] + warp_path_interp = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), path[1]) + warp_amount[i] = np.sum(np.abs(orig_steps-warp_path_interp)) + for dim in range(x.shape[2]): + ret[i,:,dim] = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), warped[:,dim]).T + else: + # if verbose > -1: + # print("There is only one pattern of class {}".format(l[i])) + ret[i,:] = pat + warp_amount[i] = 0. + if use_variable_slice: + max_warp = np.max(warp_amount) + if max_warp == 0: + # unchanged + ret = window_slice(ret, reduce_ratio=0.9) + else: + for i, pat in enumerate(ret): + # Variable Sllicing + ret[i] = window_slice(pat[np.newaxis,:,:], reduce_ratio=0.9+0.1*warp_amount[i]/max_warp)[0] + return ret + +def discriminative_guided_warp_shape(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True): + return discriminative_guided_warp(x, labels, batch_size, slope_constraint, use_window, dtw_type="shape") + + +def run_augmentation(x, y, args): + print("Augmenting %s"%args.data) + np.random.seed(args.seed) + x_aug = x + y_aug = y + if args.augmentation_ratio > 0: + augmentation_tags = "%d"%args.augmentation_ratio + for n in range(args.augmentation_ratio): + x_temp, augmentation_tags = augment(x, y, args) + x_aug = np.append(x_aug, x_temp, axis=0) + y_aug = np.append(y_aug, y, axis=0) + print("Round %d: %s done"%(n, augmentation_tags)) + if args.extra_tag: + augmentation_tags += "_"+args.extra_tag + else: + augmentation_tags = args.extra_tag + return x_aug, y_aug, augmentation_tags + +def run_augmentation_single(x, y, args): + # print("Augmenting %s"%args.data) + np.random.seed(args.seed) + + x_aug = x + y_aug = y + + + if len(x.shape)<3: + # Augmenting on the entire series: using the input data as "One Big Batch" + # Before - (sequence_length, num_channels) + # After - (1, sequence_length, num_channels) + # Note: the 'sequence_length' here is actually the length of the entire series + x_input = x[np.newaxis,:] + elif len(x.shape)==3: + # Augmenting on the batch series: keep current dimension (batch_size, sequence_length, num_channels) + x_input = x + else: + raise ValueError("Input must be (batch_size, sequence_length, num_channels) dimensional") + + if args.augmentation_ratio > 0: + augmentation_tags = "%d"%args.augmentation_ratio + for n in range(args.augmentation_ratio): + x_aug, augmentation_tags = augment(x_input, y, args) + # print("Round %d: %s done"%(n, augmentation_tags)) + if args.extra_tag: + augmentation_tags += "_"+args.extra_tag + else: + augmentation_tags = args.extra_tag + + if(len(x.shape)<3): + # Reverse to two-dimensional in whole series augmentation scenario + x_aug = x_aug.squeeze(0) + return x_aug, y_aug, augmentation_tags + + +def augment(x, y, args): + import utils.augmentation as aug + augmentation_tags = "" + if args.jitter: + x = aug.jitter(x) + augmentation_tags += "_jitter" + if args.scaling: + x = aug.scaling(x) + augmentation_tags += "_scaling" + if args.rotation: + x = aug.rotation(x) + augmentation_tags += "_rotation" + if args.permutation: + x = aug.permutation(x) + augmentation_tags += "_permutation" + if args.randompermutation: + x = aug.permutation(x, seg_mode="random") + augmentation_tags += "_randomperm" + if args.magwarp: + x = aug.magnitude_warp(x) + augmentation_tags += "_magwarp" + if args.timewarp: + x = aug.time_warp(x) + augmentation_tags += "_timewarp" + if args.windowslice: + x = aug.window_slice(x) + augmentation_tags += "_windowslice" + if args.windowwarp: + x = aug.window_warp(x) + augmentation_tags += "_windowwarp" + if args.spawner: + x = aug.spawner(x, y) + augmentation_tags += "_spawner" + if args.dtwwarp: + x = aug.random_guided_warp(x, y) + augmentation_tags += "_rgw" + if args.shapedtwwarp: + x = aug.random_guided_warp_shape(x, y) + augmentation_tags += "_rgws" + if args.wdba: + x = aug.wdba(x, y) + augmentation_tags += "_wdba" + if args.discdtw: + x = aug.discriminative_guided_warp(x, y) + augmentation_tags += "_dgw" + if args.discsdtw: + x = aug.discriminative_guided_warp_shape(x, y) + augmentation_tags += "_dgws" + return x, augmentation_tags diff --git a/utils/dtw.py b/utils/dtw.py new file mode 100755 index 0000000..941eae8 --- /dev/null +++ b/utils/dtw.py @@ -0,0 +1,223 @@ +__author__ = 'Brian Iwana' + +import numpy as np +import math +import sys + +RETURN_VALUE = 0 +RETURN_PATH = 1 +RETURN_ALL = -1 + +# Core DTW +def _traceback(DTW, slope_constraint): + i, j = np.array(DTW.shape) - 1 + p, q = [i-1], [j-1] + + if slope_constraint == "asymmetric": + while (i > 1): + tb = np.argmin((DTW[i-1, j], DTW[i-1, j-1], DTW[i-1, j-2])) + + if (tb == 0): + i = i - 1 + elif (tb == 1): + i = i - 1 + j = j - 1 + elif (tb == 2): + i = i - 1 + j = j - 2 + + p.insert(0, i-1) + q.insert(0, j-1) + elif slope_constraint == "symmetric": + while (i > 1 or j > 1): + tb = np.argmin((DTW[i-1, j-1], DTW[i-1, j], DTW[i, j-1])) + + if (tb == 0): + i = i - 1 + j = j - 1 + elif (tb == 1): + i = i - 1 + elif (tb == 2): + j = j - 1 + + p.insert(0, i-1) + q.insert(0, j-1) + else: + sys.exit("Unknown slope constraint %s"%slope_constraint) + + return (np.array(p), np.array(q)) + +def dtw(prototype, sample, return_flag = RETURN_VALUE, slope_constraint="asymmetric", window=None): + """ Computes the DTW of two sequences. + :param prototype: np array [0..b] + :param sample: np array [0..t] + :param extended: bool + """ + p = prototype.shape[0] + assert p != 0, "Prototype empty!" + s = sample.shape[0] + assert s != 0, "Sample empty!" + + if window is None: + window = s + + cost = np.full((p, s), np.inf) + for i in range(p): + start = max(0, i-window) + end = min(s, i+window)+1 + cost[i,start:end]=np.linalg.norm(sample[start:end] - prototype[i], axis=1) + + DTW = _cummulative_matrix(cost, slope_constraint, window) + + if return_flag == RETURN_ALL: + return DTW[-1,-1], cost, DTW[1:,1:], _traceback(DTW, slope_constraint) + elif return_flag == RETURN_PATH: + return _traceback(DTW, slope_constraint) + else: + return DTW[-1,-1] + +def _cummulative_matrix(cost, slope_constraint, window): + p = cost.shape[0] + s = cost.shape[1] + + # Note: DTW is one larger than cost and the original patterns + DTW = np.full((p+1, s+1), np.inf) + + DTW[0, 0] = 0.0 + + if slope_constraint == "asymmetric": + for i in range(1, p+1): + if i <= window+1: + DTW[i,1] = cost[i-1,0] + min(DTW[i-1,0], DTW[i-1,1]) + for j in range(max(2, i-window), min(s, i+window)+1): + DTW[i,j] = cost[i-1,j-1] + min(DTW[i-1,j-2], DTW[i-1,j-1], DTW[i-1,j]) + elif slope_constraint == "symmetric": + for i in range(1, p+1): + for j in range(max(1, i-window), min(s, i+window)+1): + DTW[i,j] = cost[i-1,j-1] + min(DTW[i-1,j-1], DTW[i,j-1], DTW[i-1,j]) + else: + sys.exit("Unknown slope constraint %s"%slope_constraint) + + return DTW + +def shape_dtw(prototype, sample, return_flag = RETURN_VALUE, slope_constraint="asymmetric", window=None, descr_ratio=0.05): + """ Computes the shapeDTW of two sequences. + :param prototype: np array [0..b] + :param sample: np array [0..t] + :param extended: bool + """ + # shapeDTW + # https://www.sciencedirect.com/science/article/pii/S0031320317303710 + + p = prototype.shape[0] + assert p != 0, "Prototype empty!" + s = sample.shape[0] + assert s != 0, "Sample empty!" + + if window is None: + window = s + + p_feature_len = np.clip(np.round(p * descr_ratio), 5, 100).astype(int) + s_feature_len = np.clip(np.round(s * descr_ratio), 5, 100).astype(int) + + # padding + p_pad_front = (np.ceil(p_feature_len / 2.)).astype(int) + p_pad_back = (np.floor(p_feature_len / 2.)).astype(int) + s_pad_front = (np.ceil(s_feature_len / 2.)).astype(int) + s_pad_back = (np.floor(s_feature_len / 2.)).astype(int) + + prototype_pad = np.pad(prototype, ((p_pad_front, p_pad_back), (0, 0)), mode="edge") + sample_pad = np.pad(sample, ((s_pad_front, s_pad_back), (0, 0)), mode="edge") + p_p = prototype_pad.shape[0] + s_p = sample_pad.shape[0] + + cost = np.full((p, s), np.inf) + for i in range(p): + for j in range(max(0, i-window), min(s, i+window)): + cost[i, j] = np.linalg.norm(sample_pad[j:j+s_feature_len] - prototype_pad[i:i+p_feature_len]) + + DTW = _cummulative_matrix(cost, slope_constraint=slope_constraint, window=window) + + if return_flag == RETURN_ALL: + return DTW[-1,-1], cost, DTW[1:,1:], _traceback(DTW, slope_constraint) + elif return_flag == RETURN_PATH: + return _traceback(DTW, slope_constraint) + else: + return DTW[-1,-1] + +# Draw helpers +def draw_graph2d(cost, DTW, path, prototype, sample): + import matplotlib.pyplot as plt + plt.figure(figsize=(12, 8)) + # plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01) + + #cost + plt.subplot(2, 3, 1) + plt.imshow(cost.T, cmap=plt.cm.gray, interpolation='none', origin='lower') + plt.plot(path[0], path[1], 'y') + plt.xlim((-0.5, cost.shape[0]-0.5)) + plt.ylim((-0.5, cost.shape[0]-0.5)) + + #dtw + plt.subplot(2, 3, 2) + plt.imshow(DTW.T, cmap=plt.cm.gray, interpolation='none', origin='lower') + plt.plot(path[0]+1, path[1]+1, 'y') + plt.xlim((-0.5, DTW.shape[0]-0.5)) + plt.ylim((-0.5, DTW.shape[0]-0.5)) + + #prototype + plt.subplot(2, 3, 4) + plt.plot(prototype[:,0], prototype[:,1], 'b-o') + + #connection + plt.subplot(2, 3, 5) + for i in range(0,path[0].shape[0]): + plt.plot([prototype[path[0][i],0], sample[path[1][i],0]],[prototype[path[0][i],1], sample[path[1][i],1]], 'y-') + plt.plot(sample[:,0], sample[:,1], 'g-o') + plt.plot(prototype[:,0], prototype[:,1], 'b-o') + + #sample + plt.subplot(2, 3, 6) + plt.plot(sample[:,0], sample[:,1], 'g-o') + + plt.tight_layout() + plt.show() + +def draw_graph1d(cost, DTW, path, prototype, sample): + import matplotlib.pyplot as plt + plt.figure(figsize=(12, 8)) + # plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01) + p_steps = np.arange(prototype.shape[0]) + s_steps = np.arange(sample.shape[0]) + + #cost + plt.subplot(2, 3, 1) + plt.imshow(cost.T, cmap=plt.cm.gray, interpolation='none', origin='lower') + plt.plot(path[0], path[1], 'y') + plt.xlim((-0.5, cost.shape[0]-0.5)) + plt.ylim((-0.5, cost.shape[0]-0.5)) + + #dtw + plt.subplot(2, 3, 2) + plt.imshow(DTW.T, cmap=plt.cm.gray, interpolation='none', origin='lower') + plt.plot(path[0]+1, path[1]+1, 'y') + plt.xlim((-0.5, DTW.shape[0]-0.5)) + plt.ylim((-0.5, DTW.shape[0]-0.5)) + + #prototype + plt.subplot(2, 3, 4) + plt.plot(p_steps, prototype[:,0], 'b-o') + + #connection + plt.subplot(2, 3, 5) + for i in range(0,path[0].shape[0]): + plt.plot([path[0][i], path[1][i]],[prototype[path[0][i],0], sample[path[1][i],0]], 'y-') + plt.plot(p_steps, sample[:,0], 'g-o') + plt.plot(s_steps, prototype[:,0], 'b-o') + + #sample + plt.subplot(2, 3, 6) + plt.plot(s_steps, sample[:,0], 'g-o') + + plt.tight_layout() + plt.show() \ No newline at end of file diff --git a/utils/dtw_metric.py b/utils/dtw_metric.py new file mode 100755 index 0000000..5ab39bf --- /dev/null +++ b/utils/dtw_metric.py @@ -0,0 +1,156 @@ +from numpy import array, zeros, full, argmin, inf, ndim +from scipy.spatial.distance import cdist +from math import isinf + + +def dtw(x, y, dist, warp=1, w=inf, s=1.0): + """ + Computes Dynamic Time Warping (DTW) of two sequences. + + :param array x: N1*M array + :param array y: N2*M array + :param func dist: distance used as cost measure + :param int warp: how many shifts are computed. + :param int w: window size limiting the maximal distance between indices of matched entries |i,j|. + :param float s: weight applied on off-diagonal moves of the path. As s gets larger, the warping path is increasingly biased towards the diagonal + Returns the minimum distance, the cost matrix, the accumulated cost matrix, and the wrap path. + """ + assert len(x) + assert len(y) + assert isinf(w) or (w >= abs(len(x) - len(y))) + assert s > 0 + r, c = len(x), len(y) + if not isinf(w): + D0 = full((r + 1, c + 1), inf) + for i in range(1, r + 1): + D0[i, max(1, i - w):min(c + 1, i + w + 1)] = 0 + D0[0, 0] = 0 + else: + D0 = zeros((r + 1, c + 1)) + D0[0, 1:] = inf + D0[1:, 0] = inf + D1 = D0[1:, 1:] # view + for i in range(r): + for j in range(c): + if (isinf(w) or (max(0, i - w) <= j <= min(c, i + w))): + D1[i, j] = dist(x[i], y[j]) + C = D1.copy() + jrange = range(c) + for i in range(r): + if not isinf(w): + jrange = range(max(0, i - w), min(c, i + w + 1)) + for j in jrange: + min_list = [D0[i, j]] + for k in range(1, warp + 1): + i_k = min(i + k, r) + j_k = min(j + k, c) + min_list += [D0[i_k, j] * s, D0[i, j_k] * s] + D1[i, j] += min(min_list) + if len(x) == 1: + path = zeros(len(y)), range(len(y)) + elif len(y) == 1: + path = range(len(x)), zeros(len(x)) + else: + path = _traceback(D0) + return D1[-1, -1], C, D1, path + + +def accelerated_dtw(x, y, dist, warp=1): + """ + Computes Dynamic Time Warping (DTW) of two sequences in a faster way. + Instead of iterating through each element and calculating each distance, + this uses the cdist function from scipy (https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html) + + :param array x: N1*M array + :param array y: N2*M array + :param string or func dist: distance parameter for cdist. When string is given, cdist uses optimized functions for the distance metrics. + If a string is passed, the distance function can be 'braycurtis', 'canberra', 'chebyshev', 'cityblock', 'correlation', 'cosine', 'dice', 'euclidean', 'hamming', 'jaccard', 'kulsinski', 'mahalanobis', 'matching', 'minkowski', 'rogerstanimoto', 'russellrao', 'seuclidean', 'sokalmichener', 'sokalsneath', 'sqeuclidean', 'wminkowski', 'yule'. + :param int warp: how many shifts are computed. + Returns the minimum distance, the cost matrix, the accumulated cost matrix, and the wrap path. + """ + assert len(x) + assert len(y) + if ndim(x) == 1: + x = x.reshape(-1, 1) + if ndim(y) == 1: + y = y.reshape(-1, 1) + r, c = len(x), len(y) + D0 = zeros((r + 1, c + 1)) + D0[0, 1:] = inf + D0[1:, 0] = inf + D1 = D0[1:, 1:] + D0[1:, 1:] = cdist(x, y, dist) + C = D1.copy() + for i in range(r): + for j in range(c): + min_list = [D0[i, j]] + for k in range(1, warp + 1): + min_list += [D0[min(i + k, r), j], + D0[i, min(j + k, c)]] + D1[i, j] += min(min_list) + if len(x) == 1: + path = zeros(len(y)), range(len(y)) + elif len(y) == 1: + path = range(len(x)), zeros(len(x)) + else: + path = _traceback(D0) + return D1[-1, -1], C, D1, path + + +def _traceback(D): + i, j = array(D.shape) - 2 + p, q = [i], [j] + while (i > 0) or (j > 0): + tb = argmin((D[i, j], D[i, j + 1], D[i + 1, j])) + if tb == 0: + i -= 1 + j -= 1 + elif tb == 1: + i -= 1 + else: # (tb == 2): + j -= 1 + p.insert(0, i) + q.insert(0, j) + return array(p), array(q) + + +if __name__ == '__main__': + w = inf + s = 1.0 + if 1: # 1-D numeric + from sklearn.metrics.pairwise import manhattan_distances + x = [0, 0, 1, 1, 2, 4, 2, 1, 2, 0] + y = [1, 1, 1, 2, 2, 2, 2, 3, 2, 0] + dist_fun = manhattan_distances + w = 1 + # s = 1.2 + elif 0: # 2-D numeric + from sklearn.metrics.pairwise import euclidean_distances + x = [[0, 0], [0, 1], [1, 1], [1, 2], [2, 2], [4, 3], [2, 3], [1, 1], [2, 2], [0, 1]] + y = [[1, 0], [1, 1], [1, 1], [2, 1], [4, 3], [4, 3], [2, 3], [3, 1], [1, 2], [1, 0]] + dist_fun = euclidean_distances + else: # 1-D list of strings + from nltk.metrics.distance import edit_distance + # x = ['we', 'shelled', 'clams', 'for', 'the', 'chowder'] + # y = ['class', 'too'] + x = ['i', 'soon', 'found', 'myself', 'muttering', 'to', 'the', 'walls'] + y = ['see', 'drown', 'himself'] + # x = 'we talked about the situation'.split() + # y = 'we talked about the situation'.split() + dist_fun = edit_distance + dist, cost, acc, path = dtw(x, y, dist_fun, w=w, s=s) + + # Vizualize + from matplotlib import pyplot as plt + plt.imshow(cost.T, origin='lower', cmap=plt.cm.Reds, interpolation='nearest') + plt.plot(path[0], path[1], '-o') # relation + plt.xticks(range(len(x)), x) + plt.yticks(range(len(y)), y) + plt.xlabel('x') + plt.ylabel('y') + plt.axis('tight') + if isinf(w): + plt.title('Minimum distance: {}, slope weight: {}'.format(dist, s)) + else: + plt.title('Minimum distance: {}, window widht: {}, slope weight: {}'.format(dist, w, s)) + plt.show() \ No newline at end of file diff --git a/utils/losses.py b/utils/losses.py new file mode 100755 index 0000000..21438e7 --- /dev/null +++ b/utils/losses.py @@ -0,0 +1,89 @@ +# This source code is provided for the purposes of scientific reproducibility +# under the following limited license from Element AI Inc. The code is an +# implementation of the N-BEATS model (Oreshkin et al., N-BEATS: Neural basis +# expansion analysis for interpretable time series forecasting, +# https://arxiv.org/abs/1905.10437). The copyright to the source code is +# licensed under the Creative Commons - Attribution-NonCommercial 4.0 +# International license (CC BY-NC 4.0): +# https://creativecommons.org/licenses/by-nc/4.0/. Any commercial use (whether +# for the benefit of third parties or internally in production) requires an +# explicit license. The subject-matter of the N-BEATS model and associated +# materials are the property of Element AI Inc. and may be subject to patent +# protection. No license to patents is granted hereunder (whether express or +# implied). Copyright © 2020 Element AI Inc. All rights reserved. + +""" +Loss functions for PyTorch. +""" + +import torch as t +import torch.nn as nn +import numpy as np +import pdb + + +def divide_no_nan(a, b): + """ + a/b where the resulted NaN or Inf are replaced by 0. + """ + result = a / b + result[result != result] = .0 + result[result == np.inf] = .0 + return result + + +class mape_loss(nn.Module): + def __init__(self): + super(mape_loss, self).__init__() + + def forward(self, insample: t.Tensor, freq: int, + forecast: t.Tensor, target: t.Tensor, mask: t.Tensor) -> t.float: + """ + MAPE loss as defined in: https://en.wikipedia.org/wiki/Mean_absolute_percentage_error + + :param forecast: Forecast values. Shape: batch, time + :param target: Target values. Shape: batch, time + :param mask: 0/1 mask. Shape: batch, time + :return: Loss value + """ + weights = divide_no_nan(mask, target) + return t.mean(t.abs((forecast - target) * weights)) + + +class smape_loss(nn.Module): + def __init__(self): + super(smape_loss, self).__init__() + + def forward(self, insample: t.Tensor, freq: int, + forecast: t.Tensor, target: t.Tensor, mask: t.Tensor) -> t.float: + """ + sMAPE loss as defined in https://robjhyndman.com/hyndsight/smape/ (Makridakis 1993) + + :param forecast: Forecast values. Shape: batch, time + :param target: Target values. Shape: batch, time + :param mask: 0/1 mask. Shape: batch, time + :return: Loss value + """ + return 200 * t.mean(divide_no_nan(t.abs(forecast - target), + t.abs(forecast.data) + t.abs(target.data)) * mask) + + +class mase_loss(nn.Module): + def __init__(self): + super(mase_loss, self).__init__() + + def forward(self, insample: t.Tensor, freq: int, + forecast: t.Tensor, target: t.Tensor, mask: t.Tensor) -> t.float: + """ + MASE loss as defined in "Scaled Errors" https://robjhyndman.com/papers/mase.pdf + + :param insample: Insample values. Shape: batch, time_i + :param freq: Frequency value + :param forecast: Forecast values. Shape: batch, time_o + :param target: Target values. Shape: batch, time_o + :param mask: 0/1 mask. Shape: batch, time_o + :return: Loss value + """ + masep = t.mean(t.abs(insample[:, freq:] - insample[:, :-freq]), dim=1) + masked_masep_inv = divide_no_nan(mask, masep[:, None]) + return t.mean(t.abs(target - forecast) * masked_masep_inv) diff --git a/utils/m4_summary.py b/utils/m4_summary.py new file mode 100755 index 0000000..acd50fe --- /dev/null +++ b/utils/m4_summary.py @@ -0,0 +1,140 @@ +# This source code is provided for the purposes of scientific reproducibility +# under the following limited license from Element AI Inc. The code is an +# implementation of the N-BEATS model (Oreshkin et al., N-BEATS: Neural basis +# expansion analysis for interpretable time series forecasting, +# https://arxiv.org/abs/1905.10437). The copyright to the source code is +# licensed under the Creative Commons - Attribution-NonCommercial 4.0 +# International license (CC BY-NC 4.0): +# https://creativecommons.org/licenses/by-nc/4.0/. Any commercial use (whether +# for the benefit of third parties or internally in production) requires an +# explicit license. The subject-matter of the N-BEATS model and associated +# materials are the property of Element AI Inc. and may be subject to patent +# protection. No license to patents is granted hereunder (whether express or +# implied). Copyright 2020 Element AI Inc. All rights reserved. + +""" +M4 Summary +""" +from collections import OrderedDict + +import numpy as np +import pandas as pd + +from data_provider.m4 import M4Dataset +from data_provider.m4 import M4Meta +import os + + +def group_values(values, groups, group_name): + return np.array([v[~np.isnan(v)] for v in values[groups == group_name]]) + + +def mase(forecast, insample, outsample, frequency): + return np.mean(np.abs(forecast - outsample)) / np.mean(np.abs(insample[:-frequency] - insample[frequency:])) + + +def smape_2(forecast, target): + denom = np.abs(target) + np.abs(forecast) + # divide by 1.0 instead of 0.0, in case when denom is zero the enumerator will be 0.0 anyway. + denom[denom == 0.0] = 1.0 + return 200 * np.abs(forecast - target) / denom + + +def mape(forecast, target): + denom = np.abs(target) + # divide by 1.0 instead of 0.0, in case when denom is zero the enumerator will be 0.0 anyway. + denom[denom == 0.0] = 1.0 + return 100 * np.abs(forecast - target) / denom + + +class M4Summary: + def __init__(self, file_path, root_path): + self.file_path = file_path + self.training_set = M4Dataset.load(training=True, dataset_file=root_path) + self.test_set = M4Dataset.load(training=False, dataset_file=root_path) + self.naive_path = os.path.join(root_path, 'submission-Naive2.csv') + + def evaluate(self): + """ + Evaluate forecasts using M4 test dataset. + + :param forecast: Forecasts. Shape: timeseries, time. + :return: sMAPE and OWA grouped by seasonal patterns. + """ + grouped_owa = OrderedDict() + + naive2_forecasts = pd.read_csv(self.naive_path).values[:, 1:].astype(np.float32) + naive2_forecasts = np.array([v[~np.isnan(v)] for v in naive2_forecasts]) + + model_mases = {} + naive2_smapes = {} + naive2_mases = {} + grouped_smapes = {} + grouped_mapes = {} + for group_name in M4Meta.seasonal_patterns: + file_name = self.file_path + group_name + "_forecast.csv" + if os.path.exists(file_name): + model_forecast = pd.read_csv(file_name).values + + naive2_forecast = group_values(naive2_forecasts, self.test_set.groups, group_name) + target = group_values(self.test_set.values, self.test_set.groups, group_name) + # all timeseries within group have same frequency + frequency = self.training_set.frequencies[self.test_set.groups == group_name][0] + insample = group_values(self.training_set.values, self.test_set.groups, group_name) + + model_mases[group_name] = np.mean([mase(forecast=model_forecast[i], + insample=insample[i], + outsample=target[i], + frequency=frequency) for i in range(len(model_forecast))]) + naive2_mases[group_name] = np.mean([mase(forecast=naive2_forecast[i], + insample=insample[i], + outsample=target[i], + frequency=frequency) for i in range(len(model_forecast))]) + + naive2_smapes[group_name] = np.mean(smape_2(naive2_forecast, target)) + grouped_smapes[group_name] = np.mean(smape_2(forecast=model_forecast, target=target)) + grouped_mapes[group_name] = np.mean(mape(forecast=model_forecast, target=target)) + + grouped_smapes = self.summarize_groups(grouped_smapes) + grouped_mapes = self.summarize_groups(grouped_mapes) + grouped_model_mases = self.summarize_groups(model_mases) + grouped_naive2_smapes = self.summarize_groups(naive2_smapes) + grouped_naive2_mases = self.summarize_groups(naive2_mases) + for k in grouped_model_mases.keys(): + grouped_owa[k] = (grouped_model_mases[k] / grouped_naive2_mases[k] + + grouped_smapes[k] / grouped_naive2_smapes[k]) / 2 + + def round_all(d): + return dict(map(lambda kv: (kv[0], np.round(kv[1], 3)), d.items())) + + return round_all(grouped_smapes), round_all(grouped_owa), round_all(grouped_mapes), round_all( + grouped_model_mases) + + def summarize_groups(self, scores): + """ + Re-group scores respecting M4 rules. + :param scores: Scores per group. + :return: Grouped scores. + """ + scores_summary = OrderedDict() + + def group_count(group_name): + return len(np.where(self.test_set.groups == group_name)[0]) + + weighted_score = {} + for g in ['Yearly', 'Quarterly', 'Monthly']: + weighted_score[g] = scores[g] * group_count(g) + scores_summary[g] = scores[g] + + others_score = 0 + others_count = 0 + for g in ['Weekly', 'Daily', 'Hourly']: + others_score += scores[g] * group_count(g) + others_count += group_count(g) + weighted_score['Others'] = others_score + scores_summary['Others'] = others_score / others_count + + average = np.sum(list(weighted_score.values())) / len(self.test_set.groups) + scores_summary['Average'] = average + + return scores_summary diff --git a/utils/masking.py b/utils/masking.py new file mode 100755 index 0000000..a19cbf6 --- /dev/null +++ b/utils/masking.py @@ -0,0 +1,26 @@ +import torch + + +class TriangularCausalMask(): + def __init__(self, B, L, device="cpu"): + mask_shape = [B, 1, L, L] + with torch.no_grad(): + self._mask = torch.triu(torch.ones(mask_shape, dtype=torch.bool), diagonal=1).to(device) + + @property + def mask(self): + return self._mask + + +class ProbMask(): + def __init__(self, B, H, L, index, scores, device="cpu"): + _mask = torch.ones(L, scores.shape[-1], dtype=torch.bool).to(device).triu(1) + _mask_ex = _mask[None, None, :].expand(B, H, L, scores.shape[-1]) + indicator = _mask_ex[torch.arange(B)[:, None, None], + torch.arange(H)[None, :, None], + index, :].to(device) + self._mask = indicator.view(scores.shape).to(device) + + @property + def mask(self): + return self._mask diff --git a/utils/metrics.py b/utils/metrics.py new file mode 100755 index 0000000..ccab908 --- /dev/null +++ b/utils/metrics.py @@ -0,0 +1,41 @@ +import numpy as np + + +def RSE(pred, true): + return np.sqrt(np.sum((true - pred) ** 2)) / np.sqrt(np.sum((true - true.mean()) ** 2)) + + +def CORR(pred, true): + u = ((true - true.mean(0)) * (pred - pred.mean(0))).sum(0) + d = np.sqrt(((true - true.mean(0)) ** 2 * (pred - pred.mean(0)) ** 2).sum(0)) + return (u / d).mean(-1) + + +def MAE(pred, true): + return np.mean(np.abs(true - pred)) + + +def MSE(pred, true): + return np.mean((true - pred) ** 2) + + +def RMSE(pred, true): + return np.sqrt(MSE(pred, true)) + + +def MAPE(pred, true): + return np.mean(np.abs((true - pred) / true)) + + +def MSPE(pred, true): + return np.mean(np.square((true - pred) / true)) + + +def metric(pred, true): + mae = MAE(pred, true) + mse = MSE(pred, true) + rmse = RMSE(pred, true) + mape = MAPE(pred, true) + mspe = MSPE(pred, true) + + return mae, mse, rmse, mape, mspe diff --git a/utils/print_args.py b/utils/print_args.py new file mode 100755 index 0000000..446d81b --- /dev/null +++ b/utils/print_args.py @@ -0,0 +1,58 @@ +def print_args(args): + print("\033[1m" + "Basic Config" + "\033[0m") + print(f' {"Task Name:":<20}{args.task_name:<20}{"Is Training:":<20}{args.is_training:<20}') + print(f' {"Model ID:":<20}{args.model_id:<20}{"Model:":<20}{args.model:<20}') + print() + + print("\033[1m" + "Data Loader" + "\033[0m") + print(f' {"Data:":<20}{args.data:<20}{"Root Path:":<20}{args.root_path:<20}') + print(f' {"Data Path:":<20}{args.data_path:<20}{"Features:":<20}{args.features:<20}') + print(f' {"Target:":<20}{args.target:<20}{"Freq:":<20}{args.freq:<20}') + print(f' {"Checkpoints:":<20}{args.checkpoints:<20}') + print() + + if args.task_name in ['long_term_forecast', 'short_term_forecast']: + print("\033[1m" + "Forecasting Task" + "\033[0m") + print(f' {"Seq Len:":<20}{args.seq_len:<20}{"Label Len:":<20}{args.label_len:<20}') + print(f' {"Pred Len:":<20}{args.pred_len:<20}{"Seasonal Patterns:":<20}{args.seasonal_patterns:<20}') + print(f' {"Inverse:":<20}{args.inverse:<20}') + print() + + if args.task_name == 'imputation': + print("\033[1m" + "Imputation Task" + "\033[0m") + print(f' {"Mask Rate:":<20}{args.mask_rate:<20}') + print() + + if args.task_name == 'anomaly_detection': + print("\033[1m" + "Anomaly Detection Task" + "\033[0m") + print(f' {"Anomaly Ratio:":<20}{args.anomaly_ratio:<20}') + print() + + print("\033[1m" + "Model Parameters" + "\033[0m") + print(f' {"Top k:":<20}{args.top_k:<20}{"Num Kernels:":<20}{args.num_kernels:<20}') + print(f' {"Enc In:":<20}{args.enc_in:<20}{"Dec In:":<20}{args.dec_in:<20}') + print(f' {"C Out:":<20}{args.c_out:<20}{"d model:":<20}{args.d_model:<20}') + print(f' {"n heads:":<20}{args.n_heads:<20}{"e layers:":<20}{args.e_layers:<20}') + print(f' {"d layers:":<20}{args.d_layers:<20}{"d FF:":<20}{args.d_ff:<20}') + print(f' {"Moving Avg:":<20}{args.moving_avg:<20}{"Factor:":<20}{args.factor:<20}') + print(f' {"Distil:":<20}{args.distil:<20}{"Dropout:":<20}{args.dropout:<20}') + print(f' {"Embed:":<20}{args.embed:<20}{"Activation:":<20}{args.activation:<20}') + print() + + print("\033[1m" + "Run Parameters" + "\033[0m") + print(f' {"Num Workers:":<20}{args.num_workers:<20}{"Itr:":<20}{args.itr:<20}') + print(f' {"Train Epochs:":<20}{args.train_epochs:<20}{"Batch Size:":<20}{args.batch_size:<20}') + print(f' {"Patience:":<20}{args.patience:<20}{"Learning Rate:":<20}{args.learning_rate:<20}') + print(f' {"Des:":<20}{args.des:<20}{"Loss:":<20}{args.loss:<20}') + print(f' {"Lradj:":<20}{args.lradj:<20}{"Use Amp:":<20}{args.use_amp:<20}') + print() + + print("\033[1m" + "GPU" + "\033[0m") + print(f' {"Use GPU:":<20}{args.use_gpu:<20}{"GPU:":<20}{args.gpu:<20}') + print(f' {"Use Multi GPU:":<20}{args.use_multi_gpu:<20}{"Devices:":<20}{args.devices:<20}') + print() + + print("\033[1m" + "De-stationary Projector Params" + "\033[0m") + p_hidden_dims_str = ', '.join(map(str, args.p_hidden_dims)) + print(f' {"P Hidden Dims:":<20}{p_hidden_dims_str:<20}{"P Hidden Layers:":<20}{args.p_hidden_layers:<20}') + print() diff --git a/utils/timefeatures.py b/utils/timefeatures.py new file mode 100755 index 0000000..7c12972 --- /dev/null +++ b/utils/timefeatures.py @@ -0,0 +1,148 @@ +# From: gluonts/src/gluonts/time_feature/_base.py +# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# A copy of the License is located at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from typing import List + +import numpy as np +import pandas as pd +from pandas.tseries import offsets +from pandas.tseries.frequencies import to_offset + + +class TimeFeature: + def __init__(self): + pass + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + pass + + def __repr__(self): + return self.__class__.__name__ + "()" + + +class SecondOfMinute(TimeFeature): + """Minute of hour encoded as value between [-0.5, 0.5]""" + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + return index.second / 59.0 - 0.5 + + +class MinuteOfHour(TimeFeature): + """Minute of hour encoded as value between [-0.5, 0.5]""" + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + return index.minute / 59.0 - 0.5 + + +class HourOfDay(TimeFeature): + """Hour of day encoded as value between [-0.5, 0.5]""" + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + return index.hour / 23.0 - 0.5 + + +class DayOfWeek(TimeFeature): + """Hour of day encoded as value between [-0.5, 0.5]""" + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + return index.dayofweek / 6.0 - 0.5 + + +class DayOfMonth(TimeFeature): + """Day of month encoded as value between [-0.5, 0.5]""" + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + return (index.day - 1) / 30.0 - 0.5 + + +class DayOfYear(TimeFeature): + """Day of year encoded as value between [-0.5, 0.5]""" + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + return (index.dayofyear - 1) / 365.0 - 0.5 + + +class MonthOfYear(TimeFeature): + """Month of year encoded as value between [-0.5, 0.5]""" + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + return (index.month - 1) / 11.0 - 0.5 + + +class WeekOfYear(TimeFeature): + """Week of year encoded as value between [-0.5, 0.5]""" + + def __call__(self, index: pd.DatetimeIndex) -> np.ndarray: + return (index.isocalendar().week - 1) / 52.0 - 0.5 + + +def time_features_from_frequency_str(freq_str: str) -> List[TimeFeature]: + """ + Returns a list of time features that will be appropriate for the given frequency string. + Parameters + ---------- + freq_str + Frequency string of the form [multiple][granularity] such as "12H", "5min", "1D" etc. + """ + + features_by_offsets = { + offsets.YearEnd: [], + offsets.QuarterEnd: [MonthOfYear], + offsets.MonthEnd: [MonthOfYear], + offsets.Week: [DayOfMonth, WeekOfYear], + offsets.Day: [DayOfWeek, DayOfMonth, DayOfYear], + offsets.BusinessDay: [DayOfWeek, DayOfMonth, DayOfYear], + offsets.Hour: [HourOfDay, DayOfWeek, DayOfMonth, DayOfYear], + offsets.Minute: [ + MinuteOfHour, + HourOfDay, + DayOfWeek, + DayOfMonth, + DayOfYear, + ], + offsets.Second: [ + SecondOfMinute, + MinuteOfHour, + HourOfDay, + DayOfWeek, + DayOfMonth, + DayOfYear, + ], + } + + offset = to_offset(freq_str) + + for offset_type, feature_classes in features_by_offsets.items(): + if isinstance(offset, offset_type): + return [cls() for cls in feature_classes] + + supported_freq_msg = f""" + Unsupported frequency {freq_str} + The following frequencies are supported: + Y - yearly + alias: A + M - monthly + W - weekly + D - daily + B - business days + H - hourly + T - minutely + alias: min + S - secondly + """ + raise RuntimeError(supported_freq_msg) + + +def time_features(dates, freq='h'): + return np.vstack([feat(dates) for feat in time_features_from_frequency_str(freq)]) diff --git a/utils/tools.py b/utils/tools.py new file mode 100755 index 0000000..03ef974 --- /dev/null +++ b/utils/tools.py @@ -0,0 +1,120 @@ +import os + +import numpy as np +import torch +import matplotlib.pyplot as plt +import pandas as pd +import math + +plt.switch_backend('agg') + + +def adjust_learning_rate(optimizer, epoch, args): + # lr = args.learning_rate * (0.2 ** (epoch // 2)) + if args.lradj == 'type1': + lr_adjust = {epoch: args.learning_rate * (0.5 ** ((epoch - 1) // 1))} + elif args.lradj == 'type2': + lr_adjust = { + 2: 5e-5, 4: 1e-5, 6: 5e-6, 8: 1e-6, + 10: 5e-7, 15: 1e-7, 20: 5e-8 + } + elif args.lradj == 'type3': + lr_adjust = {epoch: args.learning_rate if epoch < 3 else args.learning_rate * (0.9 ** ((epoch - 3) // 1))} + elif args.lradj == "cosine": + lr_adjust = {epoch: args.learning_rate /2 * (1 + math.cos(epoch / args.train_epochs * math.pi))} + if epoch in lr_adjust.keys(): + lr = lr_adjust[epoch] + for param_group in optimizer.param_groups: + param_group['lr'] = lr + print('Updating learning rate to {}'.format(lr)) + + +class EarlyStopping: + def __init__(self, patience=7, verbose=False, delta=0): + self.patience = patience + self.verbose = verbose + self.counter = 0 + self.best_score = None + self.early_stop = False + self.val_loss_min = np.Inf + self.delta = delta + + def __call__(self, val_loss, model, path): + score = -val_loss + if self.best_score is None: + self.best_score = score + self.save_checkpoint(val_loss, model, path) + elif score < self.best_score + self.delta: + self.counter += 1 + print(f'EarlyStopping counter: {self.counter} out of {self.patience}') + if self.counter >= self.patience: + self.early_stop = True + else: + self.best_score = score + self.save_checkpoint(val_loss, model, path) + self.counter = 0 + + def save_checkpoint(self, val_loss, model, path): + if self.verbose: + print(f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}). Saving model ...') + torch.save(model.state_dict(), path + '/' + 'checkpoint.pth') + self.val_loss_min = val_loss + + +class dotdict(dict): + """dot.notation access to dictionary attributes""" + __getattr__ = dict.get + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + + +class StandardScaler(): + def __init__(self, mean, std): + self.mean = mean + self.std = std + + def transform(self, data): + return (data - self.mean) / self.std + + def inverse_transform(self, data): + return (data * self.std) + self.mean + + +def visual(true, preds=None, name='./pic/test.pdf'): + """ + Results visualization + """ + plt.figure() + if preds is not None: + plt.plot(preds, label='Prediction', linewidth=2) + plt.plot(true, label='GroundTruth', linewidth=2) + plt.legend() + plt.savefig(name, bbox_inches='tight') + + +def adjustment(gt, pred): + anomaly_state = False + for i in range(len(gt)): + if gt[i] == 1 and pred[i] == 1 and not anomaly_state: + anomaly_state = True + for j in range(i, 0, -1): + if gt[j] == 0: + break + else: + if pred[j] == 0: + pred[j] = 1 + for j in range(i, len(gt)): + if gt[j] == 0: + break + else: + if pred[j] == 0: + pred[j] = 1 + elif gt[i] == 0: + anomaly_state = False + if anomaly_state: + pred[i] = 1 + return gt, pred + + +def cal_accuracy(y_pred, y_true): + return np.mean(y_pred == y_true)