From 93b343e0b11da7e9af11d51817642f0f50448cd4 Mon Sep 17 00:00:00 2001 From: S22 <864453277@qq.com> Date: Tue, 3 Sep 2024 19:13:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20version=200.4.0=20(#141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Modify data processing: standardize data formats, add metadata, refactor dataset classes, and remove data normalization operations. 2. Add a Scaler class: manage data normalization and denormalization in an extensible way. 3. Delete the loss folder and merge its contents into metrics. 4. Modify the Runner: streamline training and inference interfaces, add post-training inference functionality, and add the ability to save inference results and evaluation metrics. 5. Add an Examples folder: provide all configuration options with explanations, typical configuration files, and an example model structure (MLP). 6. Modify training and inference scripts. 7. Add detailed tutorials. 8, Refine code details. 9.Update requirements.txt. 10. Update README.md. --- .gitignore | 4 +- .pylintrc | 6 +- README.md | 149 +--- baselines/AGCRN/METR-LA.py | 158 ++-- baselines/AGCRN/PEMS-BAY.py | 158 ++-- baselines/AGCRN/PEMS03.py | 158 ++-- baselines/AGCRN/PEMS04.py | 158 ++-- baselines/AGCRN/PEMS07.py | 158 ++-- baselines/AGCRN/PEMS08.py | 160 ++-- baselines/AGCRN/run.sh | 7 - baselines/Autoformer/ETTh1.py | 158 ++-- baselines/Autoformer/ETTh2.py | 158 ++-- baselines/Autoformer/ETTm1.py | 160 ++-- baselines/Autoformer/ETTm2.py | 160 ++-- baselines/Autoformer/Electricity.py | 169 +++-- baselines/Autoformer/ExchangeRate.py | 164 +++-- baselines/Autoformer/PEMS04.py | 131 ---- baselines/Autoformer/PEMS04_LTSF.py | 155 ++++ baselines/Autoformer/PEMS08.py | 131 ---- baselines/Autoformer/PEMS08_LTSF.py | 155 ++++ baselines/Autoformer/Weather.py | 162 ++-- baselines/Autoformer/run.sh | 10 - baselines/BGSLF/BGSLF_METR-LA.py | 143 ---- baselines/BGSLF/arch/__init__.py | 1 - baselines/BGSLF/arch/cell.py | 208 ------ baselines/BGSLF/arch/model.py | 258 ------- baselines/Crossformer/ETTh1.py | 155 ++-- baselines/Crossformer/ETTh2.py | 117 --- baselines/Crossformer/ETTm1.py | 163 +++-- baselines/Crossformer/ETTm2.py | 120 --- baselines/Crossformer/Electricity.py | 156 ++-- baselines/Crossformer/ExchangeRate.py | 117 --- baselines/Crossformer/PEMS04.py | 119 --- baselines/Crossformer/PEMS04_LTSF.py | 145 ++++ baselines/Crossformer/PEMS08.py | 119 --- baselines/Crossformer/PEMS08_LTSF.py | 145 ++++ baselines/Crossformer/Weather.py | 157 ++-- baselines/Crossformer/arch/attn.py | 6 +- baselines/Crossformer/arch/cross_decoder.py | 8 +- baselines/Crossformer/arch/cross_embed.py | 2 +- baselines/Crossformer/arch/cross_encoder.py | 12 +- .../Crossformer/arch/crossformer_arch.py | 4 +- baselines/Crossformer/run.sh | 10 - baselines/D2STGNN/METR-LA.py | 169 +++-- baselines/D2STGNN/PEMS-BAY.py | 169 +++-- baselines/D2STGNN/PEMS03.py | 171 +++-- baselines/D2STGNN/PEMS04.py | 171 +++-- baselines/D2STGNN/PEMS07.py | 171 +++-- baselines/D2STGNN/PEMS08.py | 171 +++-- baselines/D2STGNN/arch/d2stgnn_arch.py | 2 +- .../arch/dynamic_graph_conv/utils/mask.py | 2 +- baselines/D2STGNN/run.sh | 7 - baselines/DCRNN/METR-LA.py | 173 +++-- baselines/DCRNN/PEMS-BAY.py | 175 +++-- baselines/DCRNN/PEMS03.py | 169 +++-- baselines/DCRNN/PEMS04.py | 175 +++-- baselines/DCRNN/PEMS07.py | 171 +++-- baselines/DCRNN/PEMS08.py | 171 +++-- baselines/DCRNN/run.sh | 7 - baselines/DGCRN/METR-LA.py | 169 +++-- baselines/DGCRN/PEMS-BAY.py | 170 +++-- baselines/DGCRN/PEMS03.py | 169 +++-- baselines/DGCRN/PEMS04.py | 169 +++-- baselines/DGCRN/PEMS07.py | 169 +++-- baselines/DGCRN/PEMS08.py | 169 +++-- baselines/DGCRN/run.sh | 7 - baselines/DGCRN/runner/dgcrn_runner.py | 2 +- baselines/DLinear/ETTh1.py | 151 ++-- baselines/DLinear/ETTh2.py | 151 ++-- baselines/DLinear/ETTm1.py | 151 ++-- baselines/DLinear/ETTm2.py | 151 ++-- baselines/DLinear/Electricity.py | 151 ++-- baselines/DLinear/ExchangeRate.py | 151 ++-- baselines/DLinear/METR-LA.py | 109 --- baselines/DLinear/PEMS-BAY.py | 109 --- baselines/DLinear/PEMS04.py | 109 --- baselines/DLinear/PEMS04_LTSF.py | 138 ++++ baselines/DLinear/PEMS08.py | 109 --- baselines/DLinear/PEMS08_LTSF.py | 138 ++++ baselines/DLinear/Weather.py | 151 ++-- baselines/DLinear/run.sh | 10 - baselines/DSFormer/ETTh1.py | 158 ++-- baselines/DSFormer/ETTh2.py | 158 ++-- baselines/DSFormer/ETTm1.py | 161 ++-- baselines/DSFormer/ETTm2.py | 160 ++-- baselines/DSFormer/Electricity.py | 158 ++-- baselines/DSFormer/ExchangeRate.py | 159 ++-- baselines/DSFormer/Illness.py | 163 +++-- baselines/DSFormer/METR-LA.py | 118 --- baselines/DSFormer/PEMS04.py | 118 --- baselines/DSFormer/PEMS04_LTSF.py | 144 ++++ baselines/DSFormer/PEMS08.py | 117 --- baselines/DSFormer/PEMS08_LTSF.py | 144 ++++ baselines/DSFormer/Traffic.py | 166 +++-- baselines/DSFormer/Weather.py | 165 +++-- baselines/DSFormer/run.sh | 12 - baselines/DeepAR/ETTh1.py | 159 ++-- baselines/DeepAR/ETTm1.py | 159 ++-- baselines/DeepAR/Electricity.py | 159 ++-- baselines/DeepAR/ExchangeRate.py | 160 ++-- baselines/DeepAR/METR-LA.py | 104 --- baselines/DeepAR/PEMS-BAY.py | 104 --- baselines/DeepAR/PEMS03.py | 104 --- baselines/DeepAR/PEMS04.py | 163 +++-- baselines/DeepAR/PEMS04_LTSF.py | 169 +++-- baselines/DeepAR/PEMS07.py | 104 --- baselines/DeepAR/PEMS08.py | 165 +++-- baselines/DeepAR/PEMS08_LTSF.py | 167 +++-- baselines/DeepAR/Weather.py | 159 ++-- baselines/DeepAR/run.sh | 15 - baselines/DeepAR/runner/deepar_runner.py | 104 ++- baselines/DeepAR_M4/M4.py | 108 --- baselines/DeepAR_M4/arch/__init__.py | 1 - baselines/DeepAR_M4/arch/deepar.py | 120 --- baselines/DeepAR_M4/arch/distributions.py | 22 - baselines/DeepAR_M4/loss/__init__.py | 1 - baselines/DeepAR_M4/loss/gaussian.py | 36 - baselines/DeepAR_M4/runner/__init__.py | 1 - baselines/DeepAR_M4/runner/deepar_runner.py | 141 ---- baselines/FEDformer/ETTh1.py | 160 ++-- baselines/FEDformer/ETTh2.py | 161 ++-- baselines/FEDformer/ETTm1.py | 162 ++-- baselines/FEDformer/ETTm2.py | 164 +++-- baselines/FEDformer/Electricity.py | 160 ++-- baselines/FEDformer/ExchangeRate.py | 162 ++-- baselines/FEDformer/PEMS04.py | 134 ---- baselines/FEDformer/PEMS04_LTSF.py | 156 ++++ baselines/FEDformer/PEMS08.py | 134 ---- baselines/FEDformer/PEMS08_LTSF.py | 156 ++++ baselines/FEDformer/Weather.py | 160 ++-- baselines/FEDformer/arch/fedformer_arch.py | 2 +- .../FEDformer/arch/fourier_correlation.py | 4 +- baselines/FEDformer/arch/utils.py | 62 +- baselines/FEDformer/run.sh | 10 - baselines/GMSDR/METR-LA.py | 130 ---- baselines/GMSDR/PEMS-BAY.py | 130 ---- baselines/GMSDR/PEMS03.py | 130 ---- baselines/GMSDR/PEMS04.py | 130 ---- baselines/GMSDR/PEMS07.py | 130 ---- baselines/GMSDR/PEMS08.py | 130 ---- baselines/GMSDR/arch/__init__.py | 3 - baselines/GMSDR/arch/gmsdr_arch.py | 164 ----- baselines/GMSDR/arch/gmsdr_cell.py | 184 ----- baselines/GMSDR/run.sh | 7 - baselines/GTS/METR-LA.py | 182 +++-- baselines/GTS/PEMS-BAY.py | 182 +++-- baselines/GTS/PEMS03.py | 182 +++-- baselines/GTS/PEMS04.py | 182 +++-- baselines/GTS/PEMS07.py | 182 +++-- baselines/GTS/PEMS08.py | 182 +++-- baselines/GTS/arch/gts_arch.py | 4 +- baselines/GTS/run.sh | 7 - baselines/GWNet/METR-LA.py | 163 +++-- baselines/GWNet/PEMS-BAY.py | 163 +++-- baselines/GWNet/PEMS03.py | 165 +++-- baselines/GWNet/PEMS04.py | 165 +++-- baselines/GWNet/PEMS07.py | 165 +++-- baselines/GWNet/PEMS08.py | 165 +++-- baselines/GWNet/run.sh | 7 - baselines/HI/HI_METR-LA_in96_out96.py | 106 --- baselines/HI/METR-LA.py | 138 ++++ baselines/HI/arch/hi_arch.py | 2 +- baselines/Informer/ETTh1.py | 160 ++-- baselines/Informer/ETTh2.py | 162 ++-- baselines/Informer/ETTm1.py | 162 ++-- baselines/Informer/ETTm2.py | 163 +++-- baselines/Informer/Electricity.py | 160 ++-- baselines/Informer/ExchangeRate.py | 160 ++-- baselines/Informer/METR-LA.py | 136 ---- baselines/Informer/PEMS-BAY.py | 136 ---- baselines/Informer/PEMS04.py | 136 ---- baselines/Informer/PEMS04_LTSF.py | 160 ++++ baselines/Informer/PEMS08.py | 136 ---- baselines/Informer/PEMS08_LTSF.py | 160 ++++ baselines/Informer/Weather.py | 162 ++-- baselines/Informer/arch/informer_arch.py | 8 +- baselines/Informer/arch/masking.py | 2 +- baselines/Informer/run.sh | 10 - baselines/LSTM/CA.py | 117 --- baselines/LSTM/GBA.py | 117 --- baselines/LSTM/GLA.py | 117 --- baselines/LSTM/SD.py | 117 --- baselines/LSTM/arch/__init__.py | 1 - baselines/LSTM/arch/lstm_arch.py | 49 -- baselines/LightGBM/README.md | 5 - baselines/LightGBM/Weather.py | 21 - baselines/LightGBM/evaluate.py | 80 -- baselines/LightGBM/evaluate_ar.py | 96 --- baselines/LightGBM/evaluate_m4_ar.py | 90 --- baselines/LightGBM/run.sh | 16 - baselines/Linear/ETTh1.py | 108 --- baselines/Linear/ETTh2.py | 108 --- baselines/Linear/ETTm1.py | 108 --- baselines/Linear/ETTm2.py | 108 --- baselines/Linear/Electricity.py | 108 --- baselines/Linear/ExchangeRate.py | 108 --- baselines/Linear/METR-LA.py | 109 --- baselines/Linear/PEMS-BAY.py | 109 --- baselines/Linear/PEMS04.py | 109 --- baselines/Linear/PEMS08.py | 109 --- baselines/Linear/Weather.py | 108 --- baselines/Linear/arch/__init__.py | 1 - baselines/Linear/arch/linear.py | 30 - baselines/Linear/run.sh | 10 - baselines/MLP/M4.py | 105 --- baselines/MLP/MLP_METR-LA.py | 113 --- baselines/MLP/mlp_arch.py | 25 - baselines/MTGNN/METR-LA.py | 173 +++-- baselines/MTGNN/PEMS-BAY.py | 173 +++-- baselines/MTGNN/PEMS03.py | 173 +++-- baselines/MTGNN/PEMS04.py | 173 +++-- baselines/MTGNN/PEMS07.py | 173 +++-- baselines/MTGNN/PEMS08.py | 173 +++-- baselines/MTGNN/run.sh | 7 - baselines/MTGNN/runner/mtgnn_runner.py | 40 +- baselines/MegaCRN/METR-LA.py | 148 ++++ baselines/MegaCRN/MegaCRN_METR-LA.py | 114 --- baselines/MegaCRN/arch/megacrn_arch.py | 28 +- baselines/MegaCRN/loss/loss.py | 2 +- baselines/NBeats/ETTh1.py | 162 ++-- baselines/NBeats/ETTm1.py | 162 ++-- baselines/NBeats/Electricity.py | 161 ++-- baselines/NBeats/ExchangeRate.py | 162 ++-- baselines/NBeats/METR-LA.py | 116 --- baselines/NBeats/PEMS-BAY.py | 116 --- baselines/NBeats/PEMS03.py | 116 --- baselines/NBeats/PEMS04.py | 116 --- baselines/NBeats/PEMS04_LTSF.py | 166 +++-- baselines/NBeats/PEMS07.py | 116 --- baselines/NBeats/PEMS08.py | 116 --- baselines/NBeats/PEMS08_LTSF.py | 166 +++-- baselines/NBeats/Weather.py | 162 ++-- baselines/NBeats/arch/nbeats.py | 6 +- baselines/NBeats/run.sh | 15 - baselines/NBeats_M4/M4.py | 120 --- baselines/NBeats_M4/arch/__init__.py | 1 - baselines/NBeats_M4/arch/nbeats.py | 197 ----- baselines/NHiTS/ETTm2.py | 165 +++-- baselines/NHiTS/arch/nhits.py | 2 +- baselines/NLinear/ETTh1.py | 150 ++-- baselines/NLinear/ETTh2.py | 150 ++-- baselines/NLinear/ETTm1.py | 150 ++-- baselines/NLinear/ETTm2.py | 150 ++-- baselines/NLinear/Electricity.py | 150 ++-- baselines/NLinear/ExchangeRate.py | 150 ++-- baselines/NLinear/METR-LA.py | 109 --- baselines/NLinear/PEMS-BAY.py | 109 --- baselines/NLinear/PEMS04.py | 109 --- baselines/NLinear/PEMS04_LTSF.py | 137 ++++ baselines/NLinear/PEMS08.py | 109 --- baselines/NLinear/PEMS08_LTSF.py | 137 ++++ baselines/NLinear/Weather.py | 150 ++-- baselines/NLinear/run.sh | 10 - baselines/PatchTST/ETTh1.py | 162 ++-- baselines/PatchTST/ETTh2.py | 162 ++-- baselines/PatchTST/ETTm1.py | 162 ++-- baselines/PatchTST/ETTm2.py | 174 +++-- baselines/PatchTST/Electricity.py | 164 +++-- baselines/PatchTST/ExchangeRate.py | 162 ++-- baselines/PatchTST/M4.py | 119 --- baselines/PatchTST/PEMS04.py | 130 ---- baselines/PatchTST/PEMS04_LTSF.py | 154 ++++ baselines/PatchTST/PEMS08.py | 130 ---- baselines/PatchTST/PEMS08_LTSF.py | 154 ++++ baselines/PatchTST/Weather.py | 162 ++-- baselines/PatchTST/arch/patchtst_arch.py | 22 +- baselines/PatchTST/arch/patchtst_backbone.py | 48 +- baselines/PatchTST/arch/patchtst_layers.py | 12 +- baselines/PatchTST/run.sh | 10 - baselines/Pyraformer/ETTh1.py | 167 +++-- baselines/Pyraformer/ETTh2.py | 165 +++-- baselines/Pyraformer/ETTm1.py | 169 +++-- baselines/Pyraformer/ETTm2.py | 169 +++-- baselines/Pyraformer/Electricity.py | 168 +++-- baselines/Pyraformer/ExchangeRate.py | 166 +++-- baselines/Pyraformer/PEMS04.py | 126 ---- baselines/Pyraformer/PEMS04_LTSF.py | 157 ++++ baselines/Pyraformer/PEMS08.py | 126 ---- baselines/Pyraformer/PEMS08_LTSF.py | 157 ++++ baselines/Pyraformer/Weather.py | 166 +++-- baselines/Pyraformer/run.sh | 10 - baselines/STAEformer/METR-LA.py | 168 +++-- baselines/STAEformer/PEMS-BAY.py | 168 +++-- baselines/STAEformer/PEMS03.py | 172 +++-- baselines/STAEformer/PEMS04.py | 172 +++-- baselines/STAEformer/PEMS07.py | 174 +++-- baselines/STAEformer/PEMS08.py | 172 +++-- baselines/STAEformer/run.sh | 7 - baselines/STEP/README.md | 3 +- baselines/STEP/STEP_METR-LA.py | 178 +++-- baselines/STEP/STEP_PEMS-BAY.py | 160 ---- baselines/STEP/STEP_PEMS03.py | 155 ---- baselines/STEP/STEP_PEMS04.py | 155 ---- baselines/STEP/STEP_PEMS07.py | 155 ---- baselines/STEP/STEP_PEMS08.py | 155 ---- baselines/STEP/TSFormer_METR-LA.py | 152 ++++ .../STEP/{step_arch => arch}/__init__.py | 0 .../discrete_graph_learning.py | 4 +- .../graphwavenet/__init__.py | 0 .../{step_arch => arch}/graphwavenet/model.py | 0 .../STEP/{step_arch => arch}/similarity.py | 0 baselines/STEP/{step_arch => arch}/step.py | 26 +- .../{step_arch => arch}/tsformer/__init__.py | 0 .../STEP/{step_arch => arch}/tsformer/mask.py | 0 .../{step_arch => arch}/tsformer/patch.py | 0 .../tsformer/positional_encoding.py | 0 .../tsformer/transformer_layers.py | 0 .../{step_arch => arch}/tsformer/tsformer.py | 2 +- .../STEP/{step_loss => loss}/__init__.py | 0 .../STEP/{step_loss => loss}/step_loss.py | 2 +- baselines/STEP/runner/__init__.py | 3 + .../tsformer_runner.py} | 48 +- baselines/STEP/step_data/__init__.py | 4 - .../STEP/step_data/forecasting_dataset.py | 80 -- .../STEP/step_data/pretraining_dataset.py | 1 - baselines/STEP/step_runner/__init__.py | 3 - baselines/STGCN/METR-LA.py | 165 +++-- baselines/STGCN/PEMS-BAY.py | 165 +++-- baselines/STGCN/PEMS03.py | 167 +++-- baselines/STGCN/PEMS04.py | 167 +++-- baselines/STGCN/PEMS07.py | 167 +++-- baselines/STGCN/PEMS08.py | 169 +++-- baselines/STGCN/run.sh | 7 - baselines/STGODE/METR-LA.py | 166 +++-- baselines/STGODE/PEMS-BAY.py | 166 +++-- baselines/STGODE/PEMS04.py | 166 +++-- baselines/STGODE/PEMS08.py | 166 +++-- baselines/STGODE/generate_matrices.py | 14 +- baselines/STID/CA.py | 165 +++-- baselines/STID/ETTh1.py | 164 +++-- baselines/STID/ETTh2.py | 170 +++-- baselines/STID/ETTm1.py | 166 +++-- baselines/STID/ETTm2.py | 166 +++-- baselines/STID/Electricity.py | 167 +++-- baselines/STID/ExchangeRate.py | 162 ++-- baselines/STID/GBA.py | 162 ++-- baselines/STID/GLA.py | 162 ++-- baselines/STID/Illness.py | 161 ++-- baselines/STID/METR-LA.py | 160 ++-- baselines/STID/PEMS-BAY.py | 160 ++-- baselines/STID/PEMS03.py | 160 ++-- baselines/STID/PEMS04.py | 160 ++-- baselines/STID/PEMS07.py | 160 ++-- baselines/STID/PEMS08.py | 160 ++-- baselines/STID/SD.py | 164 +++-- baselines/STID/Traffic.py | 162 ++-- baselines/STID/Weather.py | 160 ++-- baselines/STID/run.sh | 8 - baselines/STID_M4/M4.py | 115 --- baselines/STID_M4/arch/__init__.py | 3 - baselines/STID_M4/arch/mlp.py | 29 - baselines/STID_M4/arch/stid_arch.py | 108 --- baselines/STNorm/METR-LA.py | 156 ++-- baselines/STNorm/PEMS-BAY.py | 156 ++-- baselines/STNorm/PEMS03.py | 156 ++-- baselines/STNorm/PEMS04.py | 156 ++-- baselines/STNorm/PEMS07.py | 156 ++-- baselines/STNorm/PEMS08.py | 156 ++-- baselines/STNorm/run.sh | 7 - baselines/STWave/METR-LA.py | 157 ++-- baselines/STWave/PEMS-BAY.py | 157 ++-- baselines/STWave/PEMS03.py | 157 ++-- baselines/STWave/PEMS04.py | 157 ++-- baselines/STWave/PEMS07.py | 157 ++-- baselines/STWave/PEMS08.py | 159 ++-- baselines/STWave/arch/stwave_arch.py | 26 +- baselines/STWave/loss.py | 2 +- baselines/STWave/run.sh | 7 - baselines/StemGNN/METR-LA.py | 163 +++-- baselines/StemGNN/PEMS-BAY.py | 165 +++-- baselines/StemGNN/PEMS03.py | 167 +++-- baselines/StemGNN/PEMS04.py | 169 +++-- baselines/StemGNN/PEMS07.py | 167 +++-- baselines/StemGNN/PEMS08.py | 167 +++-- baselines/StemGNN/run.sh | 7 - baselines/TimesNet/ETTh1.py | 158 ++-- baselines/TimesNet/ETTh2.py | 158 ++-- baselines/TimesNet/ETTm1.py | 160 ++-- baselines/TimesNet/ETTm2.py | 160 ++-- baselines/TimesNet/Electricity.py | 168 +++-- baselines/TimesNet/ExchangeRate.py | 162 ++-- baselines/TimesNet/Weather.py | 162 ++-- baselines/TimesNet/run.sh | 10 - baselines/Triformer/ETTh1.py | 168 +++-- baselines/Triformer/ETTh2.py | 168 +++-- baselines/Triformer/ETTm1.py | 168 +++-- baselines/Triformer/ETTm2.py | 168 +++-- baselines/Triformer/Electricity.py | 170 +++-- baselines/Triformer/ExchangeRate.py | 168 +++-- baselines/Triformer/PEMS04.py | 109 --- baselines/Triformer/PEMS04_LTSF.py | 132 ++++ baselines/Triformer/PEMS08.py | 108 --- baselines/Triformer/PEMS08_LTSF.py | 132 ++++ baselines/Triformer/Weather.py | 168 +++-- baselines/Triformer/run.sh | 10 - baselines/WaveNet/ETTh1.py | 107 --- baselines/WaveNet/ETTh2.py | 107 --- baselines/WaveNet/METR-LA.py | 142 ++++ basicts/__init__.py | 6 +- basicts/archs/example_arch.py | 25 - basicts/data/__init__.py | 14 +- basicts/data/base_dataset.py | 100 +++ basicts/data/dataset_zoo/m4_dataset.py | 84 --- .../data/dataset_zoo/simple_tsf_dataset.py | 73 -- basicts/data/registry.py | 3 - basicts/data/simple_tsf_dataset.py | 124 ++++ basicts/data/transform.py | 127 ---- basicts/launcher.py | 132 +++- basicts/losses/__init__.py | 3 - basicts/losses/losses.py | 118 --- basicts/metrics/__init__.py | 22 +- basicts/metrics/mae.py | 38 + basicts/metrics/mape.py | 53 ++ basicts/metrics/mse.py | 38 + basicts/metrics/rmse.py | 25 + basicts/metrics/wape.py | 27 +- basicts/runners/__init__.py | 6 +- basicts/runners/base_m4_runner.py | 335 --------- basicts/runners/base_runner.py | 181 +++-- basicts/runners/base_tsf_runner.py | 692 +++++++++++------- basicts/runners/runner_zoo/m4_tsf_runner.py | 79 -- .../runners/runner_zoo/simple_tsf_runner.py | 97 ++- basicts/scaler/__init__.py | 9 + basicts/scaler/base_scaler.py | 47 ++ basicts/scaler/min_max_scaler.py | 94 +++ basicts/scaler/z_score_scaler.py | 102 +++ basicts/utils/__init__.py | 17 +- basicts/utils/adjacent_matrix_norm.py | 110 +-- basicts/utils/logging.py | 15 - basicts/utils/m4.py | 221 ------ basicts/utils/misc.py | 82 +-- basicts/utils/serialization.py | 144 ++-- datasets/README.md | 42 +- examples/arch.py | 52 ++ examples/complete_config.py | 213 ++++++ examples/regular_config.py | 116 +++ experiments/evaluate.py | 23 + experiments/inference.py | 44 -- experiments/run_m4.py | 54 -- experiments/train.py | 12 +- requirements.txt | 18 +- .../generate_training_data.py | 231 +++--- .../CA/generate_training_data.py | 275 +++---- .../ETTh1/generate_training_data.py | 226 +++--- .../ETTh2/generate_training_data.py | 187 +++-- .../ETTm1/generate_training_data.py | 225 +++--- .../ETTm2/generate_training_data.py | 187 +++-- .../Electricity/generate_training_data.py | 227 +++--- .../ExchangeRate/generate_training_data.py | 225 +++--- .../GBA/generate_training_data.py | 207 ++++-- .../GLA/generate_training_data.py | 207 ++++-- .../Gaussian/generate_training_data.py | 182 ++--- .../Gaussian/simulate_data.py | 20 +- .../Illness/generate_training_data.py | 228 +++--- .../M4/generate_training_data.py | 107 --- .../METR-LA/generate_training_data.py | 259 +++---- .../PEMS-BAY/generate_training_data.py | 259 +++---- .../PEMS03/generate_training_data.py | 255 +++---- .../PEMS04/generate_training_data.py | 255 +++---- .../PEMS07/generate_training_data.py | 255 +++---- .../PEMS08/generate_training_data.py | 255 +++---- .../Pulse/generate_training_data.py | 183 ++--- .../data_preparation/Pulse/simulate_data.py | 30 +- .../SD/generate_training_data.py | 206 ++++-- .../Traffic/generate_training_data.py | 234 +++--- .../Weather/generate_training_data.py | 225 +++--- scripts/data_preparation/run.sh | 43 +- tutorial/config_design.md | 40 + tutorial/dataset_design.md | 69 ++ tutorial/figures/DatasetDesign.jpeg | Bin 0 -> 144113 bytes tutorial/figures/DesignConvention.jpeg | Bin 0 -> 274687 bytes tutorial/getting_started.md | 177 +++++ tutorial/metrics_design.md | 66 ++ tutorial/model_design.md | 41 ++ tutorial/overall_design.md | 33 + tutorial/runner_design.md | 111 +++ tutorial/scaler_design.md | 44 ++ 477 files changed, 26539 insertions(+), 29642 deletions(-) delete mode 100644 baselines/AGCRN/run.sh delete mode 100644 baselines/Autoformer/PEMS04.py create mode 100644 baselines/Autoformer/PEMS04_LTSF.py delete mode 100644 baselines/Autoformer/PEMS08.py create mode 100644 baselines/Autoformer/PEMS08_LTSF.py delete mode 100644 baselines/Autoformer/run.sh delete mode 100644 baselines/BGSLF/BGSLF_METR-LA.py delete mode 100644 baselines/BGSLF/arch/__init__.py delete mode 100644 baselines/BGSLF/arch/cell.py delete mode 100644 baselines/BGSLF/arch/model.py delete mode 100644 baselines/Crossformer/ETTh2.py delete mode 100644 baselines/Crossformer/ETTm2.py delete mode 100644 baselines/Crossformer/ExchangeRate.py delete mode 100644 baselines/Crossformer/PEMS04.py create mode 100644 baselines/Crossformer/PEMS04_LTSF.py delete mode 100644 baselines/Crossformer/PEMS08.py create mode 100644 baselines/Crossformer/PEMS08_LTSF.py delete mode 100644 baselines/Crossformer/run.sh delete mode 100644 baselines/D2STGNN/run.sh delete mode 100644 baselines/DCRNN/run.sh delete mode 100644 baselines/DGCRN/run.sh delete mode 100644 baselines/DLinear/METR-LA.py delete mode 100644 baselines/DLinear/PEMS-BAY.py delete mode 100644 baselines/DLinear/PEMS04.py create mode 100644 baselines/DLinear/PEMS04_LTSF.py delete mode 100644 baselines/DLinear/PEMS08.py create mode 100644 baselines/DLinear/PEMS08_LTSF.py delete mode 100644 baselines/DLinear/run.sh delete mode 100644 baselines/DSFormer/METR-LA.py delete mode 100644 baselines/DSFormer/PEMS04.py create mode 100644 baselines/DSFormer/PEMS04_LTSF.py delete mode 100644 baselines/DSFormer/PEMS08.py create mode 100644 baselines/DSFormer/PEMS08_LTSF.py delete mode 100644 baselines/DSFormer/run.sh delete mode 100644 baselines/DeepAR/METR-LA.py delete mode 100644 baselines/DeepAR/PEMS-BAY.py delete mode 100644 baselines/DeepAR/PEMS03.py delete mode 100644 baselines/DeepAR/PEMS07.py delete mode 100644 baselines/DeepAR/run.sh delete mode 100644 baselines/DeepAR_M4/M4.py delete mode 100644 baselines/DeepAR_M4/arch/__init__.py delete mode 100644 baselines/DeepAR_M4/arch/deepar.py delete mode 100644 baselines/DeepAR_M4/arch/distributions.py delete mode 100644 baselines/DeepAR_M4/loss/__init__.py delete mode 100644 baselines/DeepAR_M4/loss/gaussian.py delete mode 100644 baselines/DeepAR_M4/runner/__init__.py delete mode 100644 baselines/DeepAR_M4/runner/deepar_runner.py delete mode 100644 baselines/FEDformer/PEMS04.py create mode 100644 baselines/FEDformer/PEMS04_LTSF.py delete mode 100644 baselines/FEDformer/PEMS08.py create mode 100644 baselines/FEDformer/PEMS08_LTSF.py delete mode 100644 baselines/FEDformer/run.sh delete mode 100644 baselines/GMSDR/METR-LA.py delete mode 100644 baselines/GMSDR/PEMS-BAY.py delete mode 100644 baselines/GMSDR/PEMS03.py delete mode 100644 baselines/GMSDR/PEMS04.py delete mode 100644 baselines/GMSDR/PEMS07.py delete mode 100644 baselines/GMSDR/PEMS08.py delete mode 100644 baselines/GMSDR/arch/__init__.py delete mode 100644 baselines/GMSDR/arch/gmsdr_arch.py delete mode 100644 baselines/GMSDR/arch/gmsdr_cell.py delete mode 100644 baselines/GMSDR/run.sh delete mode 100644 baselines/GTS/run.sh delete mode 100644 baselines/GWNet/run.sh delete mode 100644 baselines/HI/HI_METR-LA_in96_out96.py create mode 100644 baselines/HI/METR-LA.py delete mode 100644 baselines/Informer/METR-LA.py delete mode 100644 baselines/Informer/PEMS-BAY.py delete mode 100644 baselines/Informer/PEMS04.py create mode 100644 baselines/Informer/PEMS04_LTSF.py delete mode 100644 baselines/Informer/PEMS08.py create mode 100644 baselines/Informer/PEMS08_LTSF.py delete mode 100644 baselines/Informer/run.sh delete mode 100644 baselines/LSTM/CA.py delete mode 100644 baselines/LSTM/GBA.py delete mode 100644 baselines/LSTM/GLA.py delete mode 100644 baselines/LSTM/SD.py delete mode 100644 baselines/LSTM/arch/__init__.py delete mode 100644 baselines/LSTM/arch/lstm_arch.py delete mode 100644 baselines/LightGBM/README.md delete mode 100644 baselines/LightGBM/Weather.py delete mode 100644 baselines/LightGBM/evaluate.py delete mode 100644 baselines/LightGBM/evaluate_ar.py delete mode 100644 baselines/LightGBM/evaluate_m4_ar.py delete mode 100644 baselines/LightGBM/run.sh delete mode 100644 baselines/Linear/ETTh1.py delete mode 100644 baselines/Linear/ETTh2.py delete mode 100644 baselines/Linear/ETTm1.py delete mode 100644 baselines/Linear/ETTm2.py delete mode 100644 baselines/Linear/Electricity.py delete mode 100644 baselines/Linear/ExchangeRate.py delete mode 100644 baselines/Linear/METR-LA.py delete mode 100644 baselines/Linear/PEMS-BAY.py delete mode 100644 baselines/Linear/PEMS04.py delete mode 100644 baselines/Linear/PEMS08.py delete mode 100644 baselines/Linear/Weather.py delete mode 100644 baselines/Linear/arch/__init__.py delete mode 100644 baselines/Linear/arch/linear.py delete mode 100644 baselines/Linear/run.sh delete mode 100644 baselines/MLP/M4.py delete mode 100644 baselines/MLP/MLP_METR-LA.py delete mode 100644 baselines/MLP/mlp_arch.py delete mode 100644 baselines/MTGNN/run.sh create mode 100644 baselines/MegaCRN/METR-LA.py delete mode 100644 baselines/MegaCRN/MegaCRN_METR-LA.py delete mode 100644 baselines/NBeats/METR-LA.py delete mode 100644 baselines/NBeats/PEMS-BAY.py delete mode 100644 baselines/NBeats/PEMS03.py delete mode 100644 baselines/NBeats/PEMS04.py delete mode 100644 baselines/NBeats/PEMS07.py delete mode 100644 baselines/NBeats/PEMS08.py delete mode 100644 baselines/NBeats/run.sh delete mode 100644 baselines/NBeats_M4/M4.py delete mode 100644 baselines/NBeats_M4/arch/__init__.py delete mode 100644 baselines/NBeats_M4/arch/nbeats.py delete mode 100644 baselines/NLinear/METR-LA.py delete mode 100644 baselines/NLinear/PEMS-BAY.py delete mode 100644 baselines/NLinear/PEMS04.py create mode 100644 baselines/NLinear/PEMS04_LTSF.py delete mode 100644 baselines/NLinear/PEMS08.py create mode 100644 baselines/NLinear/PEMS08_LTSF.py delete mode 100644 baselines/NLinear/run.sh delete mode 100644 baselines/PatchTST/M4.py delete mode 100644 baselines/PatchTST/PEMS04.py create mode 100644 baselines/PatchTST/PEMS04_LTSF.py delete mode 100644 baselines/PatchTST/PEMS08.py create mode 100644 baselines/PatchTST/PEMS08_LTSF.py delete mode 100644 baselines/PatchTST/run.sh delete mode 100644 baselines/Pyraformer/PEMS04.py create mode 100644 baselines/Pyraformer/PEMS04_LTSF.py delete mode 100644 baselines/Pyraformer/PEMS08.py create mode 100644 baselines/Pyraformer/PEMS08_LTSF.py delete mode 100644 baselines/Pyraformer/run.sh delete mode 100644 baselines/STAEformer/run.sh delete mode 100644 baselines/STEP/STEP_PEMS-BAY.py delete mode 100644 baselines/STEP/STEP_PEMS03.py delete mode 100644 baselines/STEP/STEP_PEMS04.py delete mode 100644 baselines/STEP/STEP_PEMS07.py delete mode 100644 baselines/STEP/STEP_PEMS08.py create mode 100644 baselines/STEP/TSFormer_METR-LA.py rename baselines/STEP/{step_arch => arch}/__init__.py (100%) rename baselines/STEP/{step_arch => arch}/discrete_graph_learning.py (97%) rename baselines/STEP/{step_arch => arch}/graphwavenet/__init__.py (100%) rename baselines/STEP/{step_arch => arch}/graphwavenet/model.py (100%) rename baselines/STEP/{step_arch => arch}/similarity.py (100%) rename baselines/STEP/{step_arch => arch}/step.py (68%) rename baselines/STEP/{step_arch => arch}/tsformer/__init__.py (100%) rename baselines/STEP/{step_arch => arch}/tsformer/mask.py (100%) rename baselines/STEP/{step_arch => arch}/tsformer/patch.py (100%) rename baselines/STEP/{step_arch => arch}/tsformer/positional_encoding.py (100%) rename baselines/STEP/{step_arch => arch}/tsformer/transformer_layers.py (100%) rename baselines/STEP/{step_arch => arch}/tsformer/tsformer.py (98%) rename baselines/STEP/{step_loss => loss}/__init__.py (100%) rename baselines/STEP/{step_loss => loss}/step_loss.py (93%) create mode 100644 baselines/STEP/runner/__init__.py rename baselines/STEP/{step_runner/step_runner.py => runner/tsformer_runner.py} (53%) delete mode 100644 baselines/STEP/step_data/__init__.py delete mode 100644 baselines/STEP/step_data/forecasting_dataset.py delete mode 100644 baselines/STEP/step_data/pretraining_dataset.py delete mode 100644 baselines/STEP/step_runner/__init__.py delete mode 100644 baselines/STGCN/run.sh delete mode 100644 baselines/STID/run.sh delete mode 100644 baselines/STID_M4/M4.py delete mode 100644 baselines/STID_M4/arch/__init__.py delete mode 100644 baselines/STID_M4/arch/mlp.py delete mode 100644 baselines/STID_M4/arch/stid_arch.py delete mode 100644 baselines/STNorm/run.sh delete mode 100644 baselines/STWave/run.sh delete mode 100644 baselines/StemGNN/run.sh delete mode 100644 baselines/TimesNet/run.sh delete mode 100644 baselines/Triformer/PEMS04.py create mode 100644 baselines/Triformer/PEMS04_LTSF.py delete mode 100644 baselines/Triformer/PEMS08.py create mode 100644 baselines/Triformer/PEMS08_LTSF.py delete mode 100644 baselines/Triformer/run.sh delete mode 100644 baselines/WaveNet/ETTh1.py delete mode 100644 baselines/WaveNet/ETTh2.py create mode 100644 baselines/WaveNet/METR-LA.py delete mode 100644 basicts/archs/example_arch.py create mode 100644 basicts/data/base_dataset.py delete mode 100644 basicts/data/dataset_zoo/m4_dataset.py delete mode 100644 basicts/data/dataset_zoo/simple_tsf_dataset.py delete mode 100644 basicts/data/registry.py create mode 100644 basicts/data/simple_tsf_dataset.py delete mode 100644 basicts/data/transform.py delete mode 100644 basicts/losses/__init__.py delete mode 100644 basicts/losses/losses.py create mode 100644 basicts/metrics/mae.py create mode 100644 basicts/metrics/mape.py create mode 100644 basicts/metrics/mse.py create mode 100644 basicts/metrics/rmse.py delete mode 100644 basicts/runners/base_m4_runner.py delete mode 100644 basicts/runners/runner_zoo/m4_tsf_runner.py create mode 100644 basicts/scaler/__init__.py create mode 100644 basicts/scaler/base_scaler.py create mode 100644 basicts/scaler/min_max_scaler.py create mode 100644 basicts/scaler/z_score_scaler.py delete mode 100644 basicts/utils/logging.py delete mode 100644 basicts/utils/m4.py create mode 100644 examples/arch.py create mode 100644 examples/complete_config.py create mode 100644 examples/regular_config.py create mode 100644 experiments/evaluate.py delete mode 100644 experiments/inference.py delete mode 100644 experiments/run_m4.py delete mode 100644 scripts/data_preparation/M4/generate_training_data.py create mode 100644 tutorial/config_design.md create mode 100644 tutorial/dataset_design.md create mode 100644 tutorial/figures/DatasetDesign.jpeg create mode 100644 tutorial/figures/DesignConvention.jpeg create mode 100644 tutorial/getting_started.md create mode 100644 tutorial/metrics_design.md create mode 100644 tutorial/model_design.md create mode 100644 tutorial/overall_design.md create mode 100644 tutorial/runner_design.md create mode 100644 tutorial/scaler_design.md diff --git a/.gitignore b/.gitignore index ede67f56..96cc9655 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ __pycache__/ .vscode/ checkpoints/ -datasets/raw_data +datasets/ todo.md gpu_task.py cmd.sh @@ -14,11 +14,11 @@ cmd.sh *.pkl *.h5 *.pt -core* *.p *.pickle *.pyc *.txt +*.core *.py[cod] *$py.class diff --git a/.pylintrc b/.pylintrc index ca0cd856..093ba6ab 100644 --- a/.pylintrc +++ b/.pylintrc @@ -430,6 +430,6 @@ valid-metaclass-classmethod-first-arg=mcs # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=StandardError, - Exception, - BaseException \ No newline at end of file +overgeneral-exceptions=builtins.StandardError, + builtins.Exception, + builtins.BaseException diff --git a/README.md b/README.md index 1baf1a0c..6a7563d5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
-

A Standard and Fair Time Series Forecasting Benchmark and Toolkit.

+

A Fair and Scalable Time Series Forecasting Benchmark and Toolkit.

--- @@ -15,7 +15,23 @@ -$\text{BasicTS}^{+}$ (**Basic** **T**ime **S**eries **P**lus) is an enhanced benchmark and toolbox designed for time series forecasting. $\text{BasicTS}^{+}$ evolved from its predecessor, [BasicTS](https://github.com/zezhishao/BasicTS/blob/v1/README.md), and now has robust support for spatial-temporal forecasting and long time-series forecasting as well as more general tasks, such as M4 competition. For brevity and consistency, we will interchangeably refer to this project as $\text{BasicTS}^{+}$ and $\text{BasicTS}$. +
+ +🎉 [**Getting Started**](./tutorial/getting_started.md) **|** +💡 [**Overall Design**](./tutorial/overall_design.md) + +📦 [**Dataset**](./tutorial/dataset_design.md) **|** +🛠️ [**Scaler**](./tutorial/scaler_design.md) **|** +🧠 [**Model**](./tutorial/model_design.md) **|** +📉 [**Metrics**](./tutorial/metrics_design.md) **|** +🏃‍♂️ [**Runner**](./tutorial/runner_design.md) **|** +📜 [**Config**](./tutorial/config_design.md.md) **|** +📜 [**Baselines**](./baselines/) + +
+ + +$\text{BasicTS}^{+}$ (**Basic** **T**ime **S**eries **P**lus) is an enhanced benchmark and toolbox designed for time series forecasting. $\text{BasicTS}^{+}$ evolved from its predecessor, [BasicTS](https://github.com/zezhishao/BasicTS/blob/v1/README.md), and now has robust support for spatial-temporal forecasting and long time-series forecasting as well as more general tasks. For brevity and consistency, we will interchangeably refer to this project as $\text{BasicTS}^{+}$ and $\text{BasicTS}$. On the one hand, BasicTS utilizes a ***unified and standard pipeline*** to give a ***fair and exhaustive*** reproduction and comparison of popular deep learning-based models. @@ -23,16 +39,16 @@ On the other hand, BasicTS provides users with ***easy-to-use and extensible int We are collecting **TODOs** and **HOWTOs**, if you need more features (*e.g.* more datasets or baselines) or have any questions, please feel free to create an issue or leave a comment [here](https://github.com/zezhishao/BasicTS/issues/95). -If you find this repository useful for your work, please cite it as [such](./citation.bib): - -```LaTeX -@article{shao2023exploring, - title={Exploring Progress in Multivariate Time Series Forecasting: Comprehensive Benchmarking and Heterogeneity Analysis}, - author={Shao, Zezhi and Wang, Fei and Xu, Yongjun and Wei, Wei and Yu, Chengqing and Zhang, Zhao and Yao, Di and Jin, Guangyin and Cao, Xin and Cong, Gao and others}, - journal={arXiv preprint arXiv:2310.06119}, - year={2023} -} -``` +> [!IMPORTANT] +> If you find this repository useful for your work, please cite it as [such](./citation.bib): +> ```LaTeX +> @article{shao2023exploring, +> title={Exploring Progress in Multivariate Time Series Forecasting: Comprehensive Benchmarking and Heterogeneity Analysis}, +> author={Shao, Zezhi and Wang, Fei and Xu, Yongjun and Wei, Wei and Yu, Chengqing and Zhang, Zhao and Yao, Di and Jin, Guangyin and Cao, Xin and Cong, Gao and others}, +> journal={arXiv preprint arXiv:2310.06119}, +> year={2023} +> } +> ``` ## ✨ Highlighted Features @@ -81,108 +97,14 @@ BasicTS implements a wealth of models, including classic models, spatial-tempora - DCRNN, Graph WaveNet, MTGNN, STID, D2STGNN, STEP, DGCRN, DGCRN, STNorm, AGCRN, GTS, StemGNN, MegaCRN, STGCN, STWave, STAEformer, GMSDR, ... - Informer, Autoformer, FEDformer, Pyraformer, DLinear, NLinear, Triformer, Crossformer, ... -## 💿 Dependencies - -
- Preliminaries - - -### OS - -We recommend using BasicTS on Linux systems (*e.g.* Ubuntu and CentOS). -Other systems (*e.g.*, Windows and macOS) have not been tested. - -### Python - -Python >= 3.6 (recommended >= 3.9). - -[Miniconda](https://docs.conda.io/en/latest/miniconda.html) or [Anaconda](https://www.anaconda.com/) are recommended to create a virtual python environment. - -### Other Dependencies -
- -BasicTS is built based on PyTorch and [EasyTorch](https://github.com/cnstark/easytorch). -You can install PyTorch following the instruction in [PyTorch](https://pytorch.org/get-started/locally/). For example: - -```bash -pip install torch==1.10.0+cu111 torchvision==0.11.0+cu111 torchaudio==0.10.0 -f https://download.pytorch.org/whl/torch_stable.html -``` - -**After ensuring** that PyTorch is installed correctly, you can install other dependencies via: - -```bash -pip install -r requirements.txt -``` - -### Warning - -BasicTS is built on PyTorch 1.9.1 or 1.10.0, while other versions have not been tested. - - -## 🎯 Getting Started of Developing with BasicTS - -### Preparing Data - -- **Clone BasicTS** +## 🚀 Installation and Quick Start - ```bash - cd /path/to/your/project - git clone https://github.com/zezhishao/BasicTS.git - ``` +For detailed instructions, please refer to the [Getting Started](./tutorial/getting_started.md) tutorial. -- **Download Raw Data** - - You can download all the raw datasets at [Google Drive](https://drive.google.com/drive/folders/14EJVODCU48fGK0FkyeVom_9lETh80Yjp) or [Baidu Yun](https://pan.baidu.com/s/10gOPtlC9M4BEjx89VD1Vbw)(password: 6v0a), and unzip them to `datasets/raw_data/`. - -- **Pre-process Data** - - ```bash - cd /path/to/your/project - python scripts/data_preparation/${DATASET_NAME}/generate_training_data.py - ``` - - Replace `${DATASET_NAME}` with one of `METR-LA`, `PEMS-BAY`, `PEMS03`, `PEMS04`, `PEMS07`, `PEMS08`, or any other supported dataset. The processed data will be placed in `datasets/${DATASET_NAME}`. - - -### 3 Steps to Evaluate Your Model - -- **Define Your Model Architecture** - - The `forward` function needs to follow the conventions of BasicTS. You can find an example of the Multi-Layer Perceptron (`MLP`) model in [baselines/MLP/mlp_arch.py](baselines/MLP/mlp_arch.py) - -- **Define Your Runner for Your Model** (Optional) - - BasicTS provides a unified and standard pipeline in `basicts.runner.BaseTimeSeriesForecastingRunner`. - Nevertheless, you still need to define the specific forward process (the `forward` function in the **runner**). - Fortunately, BasicTS also provides such an implementation in `basicts.runner.SimpleTimeSeriesForecastingRunner`, which can cover most of the situations. - The runner for the `MLP` model can also use this built-in runner. - You can also find more runners in `basicts.runners.runner_zoo` to learn more about the runner design. - -- **Configure your Configuration File** - - You can configure all the details of the pipeline and hyperparameters in a configuration file, *i.e.*, **everything is based on config**. - The configuration file is a `.py` file, in which you can import your model and runner and set all the options. BasicTS uses `EasyDict` to serve as a parameter container, which is extensible and flexible to use. - An example of the configuration file for the `MLP` model on the `METR-LA` dataset can be found in [baselines/MLP/MLP_METR-LA.py](baselines/MLP/MLP_METR-LA.py) - -### Run It! - -- **Reproducing Built-in Models** - - BasicTS provides a wealth of built-in models. You can reproduce these models by running the following command: - - ```bash - python experiments/train.py -c baselines/${MODEL_NAME}/${DATASET_NAME}.py --gpus '0' - ``` - - Replace `${DATASET_NAME}` and `${MODEL_NAME}` with any supported models and datasets. For example, you can run Graph WaveNet on METR-LA dataset by: - - ```bash - python experiments/train.py -c baselines/GWNet/METR-LA.py --gpus '0' - ``` - -- **Customized Your Own Model** +## 📉 Main Results - [Example: Multi-Layer Perceptron (MLP)](baselines/MLP) +See the paper *[Exploring Progress in Multivariate Time Series Forecasting: +Comprehensive Benchmarking and Heterogeneity Analysis](https://arxiv.org/pdf/2310.06119.pdf).* ## Contributors ✨ @@ -212,11 +134,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! -## 📉 Main Results - -See the paper *[Exploring Progress in Multivariate Time Series Forecasting: -Comprehensive Benchmarking and Heterogeneity Analysis](https://arxiv.org/pdf/2310.06119.pdf).* - ## 🔗 Acknowledgement BasicTS is developed based on [EasyTorch](https://github.com/cnstark/easytorch), an easy-to-use and powerful open-source neural network training framework. diff --git a/baselines/AGCRN/METR-LA.py b/baselines/AGCRN/METR-LA.py index 571f87a5..c0f14c3c 100644 --- a/baselines/AGCRN/METR-LA.py +++ b/baselines/AGCRN/METR-LA.py @@ -1,39 +1,29 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import AGCRN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "AGCRN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "AGCRN" -CFG.MODEL.ARCH = AGCRN -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = AGCRN +MODEL_PARAM = { "num_nodes" : 207, "input_dim" : 2, "rnn_units" : 64, @@ -44,63 +34,95 @@ "embed_dim" : 10, "cheb_k" : 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} +############################## Metrics Configuration ############################## -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) ) -# train data +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = {'lr': 0.003} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/AGCRN/PEMS-BAY.py b/baselines/AGCRN/PEMS-BAY.py index eb6a05a6..8225872b 100644 --- a/baselines/AGCRN/PEMS-BAY.py +++ b/baselines/AGCRN/PEMS-BAY.py @@ -1,39 +1,29 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import AGCRN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "AGCRN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "AGCRN" -CFG.MODEL.ARCH = AGCRN -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = AGCRN +MODEL_PARAM = { "num_nodes" : 325, "input_dim" : 2, "rnn_units" : 64, @@ -44,63 +34,95 @@ "embed_dim" : 10, "cheb_k" : 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} +############################## Metrics Configuration ############################## -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) ) -# train data +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = {'lr': 0.003} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/AGCRN/PEMS03.py b/baselines/AGCRN/PEMS03.py index 5e045633..a48c8444 100644 --- a/baselines/AGCRN/PEMS03.py +++ b/baselines/AGCRN/PEMS03.py @@ -1,39 +1,29 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import AGCRN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "AGCRN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "AGCRN" -CFG.MODEL.ARCH = AGCRN -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = AGCRN +MODEL_PARAM = { "num_nodes" : 358, "input_dim" : 1, "rnn_units" : 64, @@ -44,63 +34,95 @@ "embed_dim" : 10, "cheb_k" : 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} +############################## Metrics Configuration ############################## -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) ) -# train data +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = {'lr': 0.003} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/AGCRN/PEMS04.py b/baselines/AGCRN/PEMS04.py index 28a1d337..de4dc827 100644 --- a/baselines/AGCRN/PEMS04.py +++ b/baselines/AGCRN/PEMS04.py @@ -1,39 +1,29 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import AGCRN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "AGCRN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "AGCRN" -CFG.MODEL.ARCH = AGCRN -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = AGCRN +MODEL_PARAM = { "num_nodes" : 307, "input_dim" : 1, "rnn_units" : 64, @@ -44,63 +34,95 @@ "embed_dim" : 10, "cheb_k" : 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} +############################## Metrics Configuration ############################## -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) ) -# train data +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = {'lr': 0.003} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/AGCRN/PEMS07.py b/baselines/AGCRN/PEMS07.py index 632a9924..e0e53acc 100644 --- a/baselines/AGCRN/PEMS07.py +++ b/baselines/AGCRN/PEMS07.py @@ -1,39 +1,29 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import AGCRN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "AGCRN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "AGCRN" -CFG.MODEL.ARCH = AGCRN -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = AGCRN +MODEL_PARAM = { "num_nodes" : 883, "input_dim" : 1, "rnn_units" : 64, @@ -44,63 +34,95 @@ "embed_dim" : 10, "cheb_k" : 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} +############################## Metrics Configuration ############################## -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) ) -# train data +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = {'lr': 0.003} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/AGCRN/PEMS08.py b/baselines/AGCRN/PEMS08.py index afb12562..d463f776 100644 --- a/baselines/AGCRN/PEMS08.py +++ b/baselines/AGCRN/PEMS08.py @@ -1,39 +1,29 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import AGCRN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "AGCRN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "AGCRN" -CFG.MODEL.ARCH = AGCRN -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = AGCRN +MODEL_PARAM = { "num_nodes" : 170, "input_dim" : 1, "rnn_units" : 64, @@ -41,66 +31,98 @@ "horizon" : 12, "num_layers": 2, "default_graph": True, - "embed_dim" : 2, + "embed_dim" : 10, "cheb_k" : 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} +############################## Metrics Configuration ############################## -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) ) -# train data +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = {'lr': 0.003} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/AGCRN/run.sh b/baselines/AGCRN/run.sh deleted file mode 100644 index 2927c384..00000000 --- a/baselines/AGCRN/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/AGCRN/METR-LA.py --gpus '0' -python experiments/train.py -c baselines/AGCRN/PEMS-BAY.py --gpus '0' -python experiments/train.py -c baselines/AGCRN/PEMS03.py --gpus '0' -python experiments/train.py -c baselines/AGCRN/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/AGCRN/PEMS07.py --gpus '0' -python experiments/train.py -c baselines/AGCRN/PEMS08.py --gpus '0' diff --git a/baselines/Autoformer/ETTh1.py b/baselines/Autoformer/ETTh1.py index c4002f1c..a34ea2dc 100644 --- a/baselines/Autoformer/ETTh1.py +++ b/baselines/Autoformer/ETTh1.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Autoformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "factor": 3, # attn factor "d_model": 512, "moving_avg": 25, # window size of moving average. This is a CRUCIAL hyper-parameter. @@ -59,74 +48,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/ETTh2.py b/baselines/Autoformer/ETTh2.py index b7842e3c..82fec232 100644 --- a/baselines/Autoformer/ETTh2.py +++ b/baselines/Autoformer/ETTh2.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Autoformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "factor": 3, # attn factor "d_model": 512, "moving_avg": 25, # window size of moving average. This is a CRUCIAL hyper-parameter. @@ -59,74 +48,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/ETTm1.py b/baselines/Autoformer/ETTm1.py index 251c008b..726ee87b 100644 --- a/baselines/Autoformer/ETTm1.py +++ b/baselines/Autoformer/ETTm1.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Autoformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 192 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "factor": 3, # attn factor "d_model": 512, "moving_avg": 25, # window size of moving average. This is a CRUCIAL hyper-parameter. @@ -54,79 +43,112 @@ "embed": "timeF", # [timeF, fixed, learned] "activation": "gelu", "num_time_features": 4, # number of used time features - "time_of_day_size": 24 * 4, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/ETTm2.py b/baselines/Autoformer/ETTm2.py index cd562d50..04505b92 100644 --- a/baselines/Autoformer/ETTm2.py +++ b/baselines/Autoformer/ETTm2.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Autoformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "factor": 3, # attn factor "d_model": 512, "moving_avg": 25, # window size of moving average. This is a CRUCIAL hyper-parameter. @@ -54,79 +43,112 @@ "embed": "timeF", # [timeF, fixed, learned] "activation": "gelu", "num_time_features": 4, # number of used time features - "time_of_day_size": 24 * 4, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/Electricity.py b/baselines/Autoformer/Electricity.py index 4b104df7..2068f3d4 100644 --- a/baselines/Autoformer/Electricity.py +++ b/baselines/Autoformer/Electricity.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Autoformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity Consumption" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer NUM_NODES = 321 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "factor": 3, # attn factor "d_model": 512, "moving_avg": 25, # window size of moving average. This is a CRUCIAL hyper-parameter. @@ -59,64 +48,106 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25, 50], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/ExchangeRate.py b/baselines/Autoformer/ExchangeRate.py index b9e93c66..d298705a 100644 --- a/baselines/Autoformer/ExchangeRate.py +++ b/baselines/Autoformer/ExchangeRate.py @@ -1,47 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Autoformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer NUM_NODES = 8 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "factor": 3, # attn factor "d_model": 512, "moving_avg": 25, # window size of moving average. This is a CRUCIAL hyper-parameter. @@ -59,71 +49,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mse +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/PEMS04.py b/baselines/Autoformer/PEMS04.py deleted file mode 100644 index a6b91f81..00000000 --- a/baselines/Autoformer/PEMS04.py +++ /dev/null @@ -1,131 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import Autoformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer -NUM_NODES = 307 -CFG.MODEL.PARAM = EasyDict( - { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length - "moving_avg": 65, # window size of moving average. This is a CRUCIAL hyper-parameter. - "output_attention": False, - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "d_model": 512, - "embed": "timeF", # [timeF, fixed, learned] - "dropout": 0.05, - "factor": 6, # attn factor - "n_heads": 8, - "d_ff": 2048, - "activation": "gelu", - "e_layers": 2, # num of encoder layers - "d_layers": 1, # num of decoder layers - "num_time_features": 2, # number of used time features - "time_of_day_size": 288, - "day_of_week_size": 7, - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0005, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Autoformer/PEMS04_LTSF.py b/baselines/Autoformer/PEMS04_LTSF.py new file mode 100644 index 00000000..9efffbc7 --- /dev/null +++ b/baselines/Autoformer/PEMS04_LTSF.py @@ -0,0 +1,155 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Autoformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 720 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer +NUM_NODES = 307 +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length + "moving_avg": 65, # window size of moving average. This is a CRUCIAL hyper-parameter. + "output_attention": False, + "enc_in": NUM_NODES, # num nodes + "dec_in": NUM_NODES, + "c_out": NUM_NODES, + "d_model": 512, + "embed": "timeF", # [timeF, fixed, learned] + "dropout": 0.05, + "factor": 6, # attn factor + "n_heads": 8, + "d_ff": 2048, + "activation": "gelu", + "e_layers": 2, # num of encoder layers + "d_layers": 1, # num of decoder layers + "num_time_features": 2, # number of used time features + "time_of_day_size": 288, + "day_of_week_size": 7, + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0005, + "weight_decay": 0.0005, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25, 50], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/PEMS08.py b/baselines/Autoformer/PEMS08.py deleted file mode 100644 index 098621a7..00000000 --- a/baselines/Autoformer/PEMS08.py +++ /dev/null @@ -1,131 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import Autoformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer -NUM_NODES = 170 -CFG.MODEL.PARAM = EasyDict( - { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length - "moving_avg": 65, # window size of moving average. This is a CRUCIAL hyper-parameter. - "output_attention": False, - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "d_model": 512, - "embed": "timeF", # [timeF, fixed, learned] - "dropout": 0.05, - "factor": 6, # attn factor - "n_heads": 8, - "d_ff": 2048, - "activation": "gelu", - "e_layers": 2, # num of encoder layers - "d_layers": 1, # num of decoder layers - "num_time_features": 2, # number of used time features - "time_of_day_size": 288, - "day_of_week_size": 7, - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0005, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Autoformer/PEMS08_LTSF.py b/baselines/Autoformer/PEMS08_LTSF.py new file mode 100644 index 00000000..1dfe44b0 --- /dev/null +++ b/baselines/Autoformer/PEMS08_LTSF.py @@ -0,0 +1,155 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Autoformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer +NUM_NODES = 170 +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length + "moving_avg": 65, # window size of moving average. This is a CRUCIAL hyper-parameter. + "output_attention": False, + "enc_in": NUM_NODES, # num nodes + "dec_in": NUM_NODES, + "c_out": NUM_NODES, + "d_model": 512, + "embed": "timeF", # [timeF, fixed, learned] + "dropout": 0.05, + "factor": 6, # attn factor + "n_heads": 8, + "d_ff": 2048, + "activation": "gelu", + "e_layers": 2, # num of encoder layers + "d_layers": 1, # num of decoder layers + "num_time_features": 2, # number of used time features + "time_of_day_size": 288, + "day_of_week_size": 7, + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0005, + "weight_decay": 0.0005, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25, 50], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/Weather.py b/baselines/Autoformer/Weather.py index 7c7f00ff..ea10cf90 100644 --- a/baselines/Autoformer/Weather.py +++ b/baselines/Autoformer/Weather.py @@ -1,47 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Autoformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Autoformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather Data" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Autoformer" -CFG.MODEL.ARCH = Autoformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Autoformer NUM_NODES = 21 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "factor": 3, # attn factor "d_model": 512, "moving_avg": 25, # window size of moving average. This is a CRUCIAL hyper-parameter. @@ -59,71 +49,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.00001, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Autoformer/run.sh b/baselines/Autoformer/run.sh deleted file mode 100644 index e1aa2578..00000000 --- a/baselines/Autoformer/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -# #!/bin/bash -python experiments/train.py -c baselines/Autoformer/ETTh1.py --gpus '1' -python experiments/train.py -c baselines/Autoformer/ETTh2.py --gpus '1' -python experiments/train.py -c baselines/Autoformer/ETTm1.py --gpus '1' -python experiments/train.py -c baselines/Autoformer/ETTm2.py --gpus '1' -python experiments/train.py -c baselines/Autoformer/Electricity.py --gpus '1' -python experiments/train.py -c baselines/Autoformer/ExchangeRate.py --gpus '1' -python experiments/train.py -c baselines/Autoformer/Weather.py --gpus '1' -python experiments/train.py -c baselines/Autoformer/PEMS04.py --gpus '1' -python experiments/train.py -c baselines/Autoformer/PEMS08.py --gpus '1' diff --git a/baselines/BGSLF/BGSLF_METR-LA.py b/baselines/BGSLF/BGSLF_METR-LA.py deleted file mode 100644 index b6db0f3d..00000000 --- a/baselines/BGSLF/BGSLF_METR-LA.py +++ /dev/null @@ -1,143 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.utils.serialization import load_pkl -from basicts.losses import masked_mae - -from .arch import BGSLF - -CFG = EasyDict() - -# GTS does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "BGSLF model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "BGSLF" -CFG.MODEL.ARCH = BGSLF -node_feats_full = load_pkl("datasets/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["processed_data"][..., 0] -train_index_list = load_pkl("datasets/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["train"] -node_feats = node_feats_full[:train_index_list[-1][-1], ...] -CFG.MODEL.PARAM = { - "node_feas": torch.Tensor(node_feats), - "temperature": 0.5, - "args": EasyDict({ - "device": torch.device("cuda:0"), - "cl_decay_steps": 2000, - "filter_type": "dual_random_walk", - "horizon": 12, - "feas_dim": 1, - "input_dim": 2, - "ll_decay": 0, - "num_nodes": 207, - "max_diffusion_step": 2, - "num_rnn_layers": 1, - "output_dim": 1, - "rnn_units": 64, - "seq_len": 12, - "use_curriculum_learning": True, - "embedding_size": 256, - "kernel_size": 12, - "freq": 288, - "requires_graph": 2 - }) - -} -CFG.MODEL.SETUP_GRAPH = True -CFG.MODEL.FORWARD_FEATURES = [0, 1] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.003, - "eps": 1e-3 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [20, 40], - "gamma": 0.1 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/BGSLF/arch/__init__.py b/baselines/BGSLF/arch/__init__.py deleted file mode 100644 index 72d76943..00000000 --- a/baselines/BGSLF/arch/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .model import BGSLF \ No newline at end of file diff --git a/baselines/BGSLF/arch/cell.py b/baselines/BGSLF/arch/cell.py deleted file mode 100644 index 166cdb94..00000000 --- a/baselines/BGSLF/arch/cell.py +++ /dev/null @@ -1,208 +0,0 @@ -import numpy as np -import torch -from torch.autograd import Function - -class SSU(Function): # Smooth Sparse Units(光滑的稀疏单元) "SSU":"su" - - @staticmethod - def forward(ctx, input, alpha, epsilon): # ctx:"context" input:x_tensor - - # t = f(1-x)/f(x) x>0 - t = torch.where((input > 0.) & (input < 1.), torch.exp(1. / input - 1. / (1 - input)), torch.zeros_like(input)) - # tx = 1/pow(x,2) + 1/pow(1-x,2) - tx = torch.where(t > 0., 1. / pow(input, 2) + 1. / pow(1 - input, 2), torch.zeros_like(input)) - - output = torch.where(input <= 0., torch.zeros_like(input), input) - output = torch.where(input >= 1., torch.ones_like(input), output) - output = torch.where(t > 0., alpha / (alpha + t), output) - - ctx.save_for_backward(t, tx, output, alpha, epsilon) - - return output - - @staticmethod - def backward(ctx, grad_output): - # 链式法则: dloss / dx = (dloss / doutput) * (doutput / dx) - # dloss / doutput就是输入的参数grad_output - - t, tx, output, alpha, epsilon = ctx.saved_tensors - - grad_input = alpha * pow(output, 2) * t * tx * grad_output.clone() - - sup = alpha * epsilon / (1 - epsilon) - inf = alpha * (1 - epsilon) / epsilon - grad_input[t > inf] = grad_output[t > inf] - grad_input[(t < sup) & (t > 0)] = grad_output[(t < sup) & (t > 0)] - - return grad_input, None, None - - -def SmoothSparseUnit(x, alpha, epsilon=0.05): - alpha = torch.tensor(alpha) - epsilon = torch.tensor(epsilon) - return SSU.apply(x, alpha, epsilon) - - -class LayerParams: - def __init__(self, rnn_network: torch.nn.Module, layer_type: str, device): - self._rnn_network = rnn_network - self._params_dict = {} - self._biases_dict = {} - self._type = layer_type - self.device = device - - def get_weights(self, shape): - if shape not in self._params_dict: - nn_param = torch.nn.Parameter(torch.empty(*shape, device=self.device)) - torch.nn.init.xavier_normal_(nn_param) - self._params_dict[shape] = nn_param - self._rnn_network.register_parameter('{}_weight_{}'.format(self._type, str(shape)), - nn_param) - return self._params_dict[shape] - - def get_biases(self, length, bias_start=0.0): - if length not in self._biases_dict: - biases = torch.nn.Parameter(torch.empty(length, device=self.device)) - torch.nn.init.constant_(biases, bias_start) - self._biases_dict[length] = biases - self._rnn_network.register_parameter('{}_biases_{}'.format(self._type, str(length)), - biases) - - return self._biases_dict[length] - - -class DCGRUCell(torch.nn.Module): - def __init__(self, num_units, max_diffusion_step, num_nodes, nonlinearity='tanh', - filter_type="laplacian", use_gc_for_ru=True, device='cuda'): - """ - :param num_units: - :param adj_mx: - :param max_diffusion_step: - :param num_nodes: - :param nonlinearity: - :param filter_type: "laplacian", "random_walk", "dual_random_walk". - :param use_gc_for_ru: whether to use Graph convolution to calculate the reset and update gates. - """ - - super().__init__() - self._activation = torch.tanh if nonlinearity == 'tanh' else torch.relu - # support other nonlinearities up here? - self._num_nodes = num_nodes - self._num_units = num_units - self._max_diffusion_step = max_diffusion_step - self._supports = [] - self._use_gc_for_ru = use_gc_for_ru - - self.device = device - self._fc_params = LayerParams(self, 'fc', self.device) - self._gconv_params = LayerParams(self, 'gconv', self.device) - - @staticmethod - def _build_sparse_matrix(L): - L = L.tocoo() - indices = np.column_stack((L.row, L.col)) - # this is to ensure row-major ordering to equal torch.sparse.sparse_reorder(L) - indices = indices[np.lexsort((indices[:, 0], indices[:, 1]))] - L = torch.sparse_coo_tensor(indices.T, L.data, L.shape, device=self.device) - return L - - def _calculate_random_walk_matrix(self, adj_mx): - - # tf.Print(adj_mx, [adj_mx], message="This is adj: ") - - adj_mx = adj_mx + torch.eye(int(adj_mx.shape[0])).to(self.device) - d = torch.sum(adj_mx, 1) - d_inv = 1. / d - d_inv = torch.where(torch.isinf(d_inv), torch.zeros(d_inv.shape).to(self.device), d_inv) - d_mat_inv = torch.diag(d_inv) - random_walk_mx = torch.mm(d_mat_inv, adj_mx) - return random_walk_mx - - def forward(self, inputs, hx, adj): - """Gated recurrent unit (GRU) with Graph Convolution. - :param inputs: (B, num_nodes * input_dim) - :param hx: (B, num_nodes * rnn_units) - :return - - Output: A `2-D` tensor with shape `(B, num_nodes * rnn_units)`. - """ - adj_mx = self._calculate_random_walk_matrix(adj).t() - output_size = 2 * self._num_units - if self._use_gc_for_ru: - fn = self._gconv - else: - fn = self._fc - value = torch.sigmoid(fn(inputs, adj_mx, hx, output_size, bias_start=1.0)) - value = torch.reshape(value, (-1, self._num_nodes, output_size)) - r, u = torch.split(tensor=value, split_size_or_sections=self._num_units, dim=-1) - r = torch.reshape(r, (-1, self._num_nodes * self._num_units)) - u = torch.reshape(u, (-1, self._num_nodes * self._num_units)) - - c = self._gconv(inputs, adj_mx, r * hx, self._num_units) - if self._activation is not None: - c = self._activation(c) - - new_state = u * hx + (1.0 - u) * c - return new_state - - @staticmethod - def _concat(x, x_): - x_ = x_.unsqueeze(0) - return torch.cat([x, x_], dim=0) - - def _fc(self, inputs, state, output_size, bias_start=0.0): - batch_size = inputs.shape[0] - inputs = torch.reshape(inputs, (batch_size * self._num_nodes, -1)) - state = torch.reshape(state, (batch_size * self._num_nodes, -1)) - inputs_and_state = torch.cat([inputs, state], dim=-1) - input_size = inputs_and_state.shape[-1] - weights = self._fc_params.get_weights((input_size, output_size)) - value = torch.sigmoid(torch.matmul(inputs_and_state, weights)) - biases = self._fc_params.get_biases(output_size, bias_start) - value += biases - return value - - def _gconv(self, inputs, adj_mx, state, output_size, bias_start=0.0): - # Reshape input and state to (batch_size, num_nodes, input_dim/state_dim) - batch_size = inputs.shape[0] - inputs = torch.reshape(inputs, (batch_size, self._num_nodes, -1)) - state = torch.reshape(state, (batch_size, self._num_nodes, -1)) - inputs_and_state = torch.cat([inputs, state], dim=2) - input_size = inputs_and_state.size(2) - - x = inputs_and_state - x0 = x.permute(1, 2, 0) # (num_nodes, total_arg_size, batch_size) - x0 = torch.reshape(x0, shape=[self._num_nodes, input_size * batch_size]) - x = torch.unsqueeze(x0, 0) - - if self._max_diffusion_step == 0: - pass - else: - x1 = torch.mm(adj_mx, x0) - x = self._concat(x, x1) - - for k in range(2, self._max_diffusion_step + 1): - x2 = 2 * torch.mm(adj_mx, x1) - x0 - x = self._concat(x, x2) - x1, x0 = x2, x1 - ''' - Option: - for support in self._supports: - x1 = torch.sparse.mm(support, x0) - x = self._concat(x, x1) - for k in range(2, self._max_diffusion_step + 1): - x2 = 2 * torch.sparse.mm(support, x1) - x0 - x = self._concat(x, x2) - x1, x0 = x2, x1 - ''' - num_matrices = self._max_diffusion_step + 1 # Adds for x itself. - x = torch.reshape(x, shape=[num_matrices, self._num_nodes, input_size, batch_size]) - x = x.permute(3, 1, 2, 0) # (batch_size, num_nodes, input_size, order) - x = torch.reshape(x, shape=[batch_size * self._num_nodes, input_size * num_matrices]) - - weights = self._gconv_params.get_weights((input_size * num_matrices, output_size)) - x = torch.matmul(x, weights) # (batch_size * self._num_nodes, output_size) - - biases = self._gconv_params.get_biases(output_size, bias_start) - x += biases - # Reshape res back to 2D: (batch_size, num_node, state_dim) -> (batch_size, num_node * state_dim) - return torch.reshape(x, [batch_size, self._num_nodes * output_size]) diff --git a/baselines/BGSLF/arch/model.py b/baselines/BGSLF/arch/model.py deleted file mode 100644 index 042ae85f..00000000 --- a/baselines/BGSLF/arch/model.py +++ /dev/null @@ -1,258 +0,0 @@ -import torch -import torch.nn as nn -from torch.nn import functional as F -from .cell import DCGRUCell, SmoothSparseUnit -import numpy as np - -def count_parameters(model): - return sum(p.numel() for p in model.parameters() if p.requires_grad) - -def cosine_similarity_torch(x1, x2=None, eps=1e-8): - x2 = x1 if x2 is None else x2 - w1 = x1.norm(p=2, dim=1, keepdim=True) - w2 = w1 if x2 is x1 else x2.norm(p=2, dim=1, keepdim=True) - return torch.mm(x1, x2.t()) / (w1 * w2.t()).clamp(min=eps) - -class Adjacency_generator(nn.Module): - def __init__(self, embedding_size, num_nodes, time_series, kernel_size, freq, requires_graph, seq_len, feas_dim, input_dim, device, reduction_ratio=16): - super(Adjacency_generator, self).__init__() - self.freq = freq - self.kernel_size = kernel_size - self.num_nodes = num_nodes - self.embedding = embedding_size - self.time_series = time_series - self.seq_len = seq_len - self.feas_dim = feas_dim - self.input_dim = input_dim - self.segm = int((self.time_series.shape[0]-1) // self.freq) - self.graphs = requires_graph - self.delta_series = torch.zeros_like(self.time_series).to(device) - self.conv1d = nn.Conv1d(in_channels=self.segm * self.feas_dim, out_channels=self.graphs, kernel_size=kernel_size, padding=0) - self.fc_1 = nn.Linear(self.freq - self.kernel_size + 1, self.embedding) - self.fc_2 = nn.Linear(self.embedding, self.embedding // reduction_ratio) - self.fc_3 = nn.Linear(self.embedding // reduction_ratio, self.num_nodes) - self.process() - self.device = device - - def process(self): - - for i in range(self.time_series.shape[0]): - if i == 0: - self.delta_series[i] = self.time_series[i] - else: - self.delta_series[i] = self.time_series[i]-self.time_series[i-1] - times = [] - for i in range(self.segm): - time_seg = self.delta_series[i*self.freq + 1 : (i+1)*self.freq + 1] - times.append(time_seg) - - t = torch.stack(times, dim=0).reshape(self.segm, self.freq, self.num_nodes, self.feas_dim) # (num_segment, freq, num_nodes, feas_dim) - self.t = t - - def forward(self, node_feas): # input: (seq_len, batch_size, num_sensor * input_dim) - t = self.t.permute(2, 0, 3, 1) - self.times = t.reshape(self.num_nodes, -1, self.freq) - mid_input = self.conv1d(self.times).permute(1,0,2) # (graphs, num_nodes, freq-kernel_size+1) - mid_output = torch.stack([F.relu(self.fc_1(mid_input[i,...])) for i in range(self.graphs)], dim=0) - out_put = F.relu(self.fc_2(mid_output)) - output = torch.sigmoid(self.fc_3(out_put)) - # cos_similarity - max_similarity = -999999 - seq_len = node_feas.shape[0] - batch_size = node_feas.shape[1] - node_feas = node_feas.reshape(seq_len, batch_size, self.num_nodes, -1) - node_feas = node_feas.permute(1, 2, 3, 0) - node_feas = node_feas.reshape(batch_size, self.num_nodes, -1) - nodes_feature = torch.zeros(node_feas.shape[1], node_feas.shape[2]).to(self.device) - for i in range(node_feas.shape[0]): - nodes_feature += node_feas[i, ...] - node_feas = torch.matmul(nodes_feature, nodes_feature.T) - select = -1 - for graph_idx in range(output.shape[0]): - x_1 = node_feas.reshape(1, -1) - x_2 = output[graph_idx, :, :].reshape(1, -1) - similarity = cosine_similarity_torch(x_1, x_2, eps=1e-20) - if similarity > max_similarity: - max_similarity = similarity - select = graph_idx - - return output[select] - -class Seq2SeqAttrs: - def __init__(self, args): - #self.adj_mx = adj_mx - self.max_diffusion_step = args.max_diffusion_step - self.cl_decay_steps = args.cl_decay_steps - self.filter_type = args.filter_type - self.num_nodes = args.num_nodes - self.num_rnn_layers = args.num_rnn_layers - self.rnn_units = args.rnn_units - self.hidden_state_size = self.num_nodes * self.rnn_units - - -class EncoderModel(nn.Module, Seq2SeqAttrs): - def __init__(self, args): - nn.Module.__init__(self) - Seq2SeqAttrs.__init__(self, args) - self.input_dim = args.input_dim - self.seq_len = args.seq_len # for the encoder - self.device = args.device - self.dcgru_layers = nn.ModuleList( - [DCGRUCell(self.rnn_units, self.max_diffusion_step, self.num_nodes, - filter_type=self.filter_type, device=self.device) for _ in range(self.num_rnn_layers)]) - - - def forward(self, inputs, adj, hidden_state=None): - """ - Encoder forward pass. - :param inputs: shape (batch_size, self.num_nodes * self.input_dim) - :param hidden_state: (num_layers, batch_size, self.hidden_state_size) - optional, zeros if not provided - :return: output: # shape (batch_size, self.hidden_state_size) - hidden_state # shape (num_layers, batch_size, self.hidden_state_size) - (lower indices mean lower layers) - """ - batch_size, _ = inputs.size() - if hidden_state is None: - hidden_state = torch.zeros((self.num_rnn_layers, batch_size, self.hidden_state_size), - device=self.device) - hidden_states = [] - output = inputs - for layer_num, dcgru_layer in enumerate(self.dcgru_layers): - next_hidden_state = dcgru_layer(output, hidden_state[layer_num], adj) - hidden_states.append(next_hidden_state) - output = next_hidden_state - - return output, torch.stack(hidden_states) # runs in O(num_layers) so not too slow - - -class DecoderModel(nn.Module, Seq2SeqAttrs): - def __init__(self, args): - # super().__init__(is_training, adj_mx, **model_kwargs) - nn.Module.__init__(self) - Seq2SeqAttrs.__init__(self, args) - self.output_dim = args.output_dim - self.horizon = args.horizon # for the decoder - self.projection_layer = nn.Linear(self.rnn_units, self.output_dim) - self.device = args.device - self.dcgru_layers = nn.ModuleList( - [DCGRUCell(self.rnn_units, self.max_diffusion_step, self.num_nodes, - filter_type=self.filter_type, device=self.device) for _ in range(self.num_rnn_layers)]) - - def forward(self, inputs, adj, hidden_state=None): - """ - :param inputs: shape (batch_size, self.num_nodes * self.output_dim) - :param hidden_state: (num_layers, batch_size, self.hidden_state_size) - optional, zeros if not provided - :return: output: # shape (batch_size, self.num_nodes * self.output_dim) - hidden_state # shape (num_layers, batch_size, self.hidden_state_size) - (lower indices mean lower layers) - """ - hidden_states = [] - output = inputs - for layer_num, dcgru_layer in enumerate(self.dcgru_layers): - next_hidden_state = dcgru_layer(output, hidden_state[layer_num], adj) - hidden_states.append(next_hidden_state) - output = next_hidden_state - - projected = self.projection_layer(output.view(-1, self.rnn_units)) - output = projected.view(-1, self.num_nodes * self.output_dim) - - return output, torch.stack(hidden_states) - - -class BGSLF(nn.Module, Seq2SeqAttrs): - """ - Paper: - Balanced Spatial-Temporal Graph Structure Learning for Multivariate Time Series Forecasting: A Trade-off between Efficiency and Flexibility - https://proceedings.mlr.press/v189/chen23a.html - - Official Codes: - https://github.com/onceCWJ/BGSLF - """ - def __init__(self, node_feas, temperature, args): - super().__init__() - Seq2SeqAttrs.__init__(self, args) - self.args = args - self.encoder_model = EncoderModel(args) - self.decoder_model = DecoderModel(args) - self.cl_decay_steps = args.cl_decay_steps - self.use_curriculum_learning = args.use_curriculum_learning - self.temperature = temperature - self.embedding_size = args.embedding_size - self.seq_len = args.seq_len - self.feas_dim = args.feas_dim - self.input_dim = args.input_dim - self.kernel_size = args.kernel_size - self.freq = args.freq - self.requires_graph = args.requires_graph - self.Adjacency_generator = Adjacency_generator(embedding_size=self.embedding_size, num_nodes = self.num_nodes, time_series = node_feas, - kernel_size=self.kernel_size, freq=self.freq, requires_graph = self.requires_graph, - seq_len=self.seq_len, feas_dim=self.feas_dim, input_dim = self.input_dim, device = args.device) - self.device = args.device - - - def _compute_sampling_threshold(self, batches_seen): - return self.cl_decay_steps / ( - self.cl_decay_steps + np.exp(batches_seen / self.cl_decay_steps)) - - def encoder(self, inputs, adj): - """ - Encoder forward pass - :param inputs: shape (seq_len, batch_size, num_sensor * input_dim) - :return: encoder_hidden_state: (num_layers, batch_size, self.hidden_state_size) - """ - encoder_hidden_state = None - for t in range(self.args.seq_len): - _, encoder_hidden_state = self.encoder_model(inputs[t], adj, encoder_hidden_state) - - return encoder_hidden_state - - def decoder(self, encoder_hidden_state, adj, labels=None, batches_seen=None): - """ - Decoder forward pass - :param encoder_hidden_state: (num_layers, batch_size, self.hidden_state_size) - :param labels: (self.horizon, batch_size, self.num_nodes * self.output_dim) [optional, not exist for inference] - :param batches_seen: global step [optional, not exist for inference] - :return: output: (self.horizon, batch_size, self.num_nodes * self.output_dim) - """ - batch_size = encoder_hidden_state.size(1) - go_symbol = torch.zeros((batch_size, self.num_nodes * self.decoder_model.output_dim), - device=self.device) - decoder_hidden_state = encoder_hidden_state - decoder_input = go_symbol - - outputs = [] - - for t in range(self.decoder_model.horizon): - decoder_output, decoder_hidden_state = self.decoder_model(decoder_input, adj, - decoder_hidden_state) - decoder_input = decoder_output - outputs.append(decoder_output) - if self.training and self.use_curriculum_learning: - c = np.random.uniform(0, 1) - if c < self._compute_sampling_threshold(batches_seen): - decoder_input = labels[t] - outputs = torch.stack(outputs) - return outputs - - def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool, **kwargs) -> torch.Tensor: - # history_data (torch.Tensor): shape [B, L, N, C] - batch_size, seq_len, num_nodes, input_dim = history_data.shape - inputs = history_data.permute(1, 0, 2, 3).contiguous().view(seq_len, -1, num_nodes * input_dim) - if future_data is not None: - labels = future_data[..., [0]].permute(1, 0, 2, 3).contiguous().view(seq_len, -1, num_nodes * 1) - else: - labels = None - - adj = SmoothSparseUnit(self.Adjacency_generator(inputs), 1, 0.02) - # adj = F.relu(self.Adjacency_generator(inputs)) - - encoder_hidden_state = self.encoder(inputs, adj) - # print("Encoder complete, starting decoder") - outputs = self.decoder(encoder_hidden_state, adj, labels, batches_seen=batch_seen) - # print("Decoder complete") - if batch_seen == 0: - print( "Total trainable parameters {}".format(count_parameters(self))) - - return outputs.permute(1, 0, 2).unsqueeze(-1) diff --git a/baselines/Crossformer/ETTh1.py b/baselines/Crossformer/ETTh1.py index 546d9aad..a9cb7e83 100644 --- a/baselines/Crossformer/ETTh1.py +++ b/baselines/Crossformer/ETTh1.py @@ -1,43 +1,34 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Crossformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 720 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Crossformer NUM_NODES = 7 -CFG.MODEL.PARAM = { +MODEL_PARAM = { "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, + "in_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "seg_len": 24, "win_size": 2, # default parameters @@ -49,69 +40,103 @@ "dropout": 0.2, "baseline": False } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.00005 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 5], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Crossformer/ETTh2.py b/baselines/Crossformer/ETTh2.py deleted file mode 100644 index 61f7e907..00000000 --- a/baselines/Crossformer/ETTh2.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import Crossformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer -NUM_NODES = 7 -CFG.MODEL.PARAM = { - "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, - "seg_len": 24, - "win_size": 2, - # default parameters - "factor": 10, - "d_model": 256, - "d_ff": 512, - "n_heads": 4, - "e_layers": 3, - "dropout": 0.2, - "baseline": False -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.00001 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 5], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Crossformer/ETTm1.py b/baselines/Crossformer/ETTm1.py index a47a7a1b..1ac2bccf 100644 --- a/baselines/Crossformer/ETTm1.py +++ b/baselines/Crossformer/ETTm1.py @@ -1,45 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Crossformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Crossformer NUM_NODES = 7 -CFG.MODEL.PARAM = { +MODEL_PARAM = { "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, + "in_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "seg_len": 12, - "win_size": 2, + "win_size": 3, # default parameters "factor": 10, "d_model": 256, @@ -49,72 +39,103 @@ "dropout": 0.2, "baseline": False } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0005 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1], + "milestones": [1, 5], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Crossformer/ETTm2.py b/baselines/Crossformer/ETTm2.py deleted file mode 100644 index 35f7612e..00000000 --- a/baselines/Crossformer/ETTm2.py +++ /dev/null @@ -1,120 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import Crossformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer -NUM_NODES = 7 -CFG.MODEL.PARAM = { - "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, - "seg_len": 12, - "win_size": 2, - # default parameters - "factor": 10, - "d_model": 256, - "d_ff": 512, - "n_heads": 4, - "e_layers": 3, - "dropout": 0.2, - "baseline": False -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.00005 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Crossformer/Electricity.py b/baselines/Crossformer/Electricity.py index 9545897f..718bc5e2 100644 --- a/baselines/Crossformer/Electricity.py +++ b/baselines/Crossformer/Electricity.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Crossformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity" -CFG.DATASET_INPUT_LEN = 192 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Crossformer NUM_NODES = 321 -CFG.MODEL.PARAM = { +MODEL_PARAM = { "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, + "in_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "seg_len": 24, "win_size": 2, # default parameters @@ -49,69 +39,103 @@ "dropout": 0.2, "baseline": False } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 20, 40, 60, 80, 100, 150], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 200 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 64 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Crossformer/ExchangeRate.py b/baselines/Crossformer/ExchangeRate.py deleted file mode 100644 index 75d6ba76..00000000 --- a/baselines/Crossformer/ExchangeRate.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import Crossformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer -NUM_NODES = 8 -CFG.MODEL.PARAM = { - "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, - "seg_len": 24, - "win_size": 2, - # default parameters - "factor": 10, - "d_model": 64, - "d_ff": 128, - "n_heads": 2, - "e_layers": 3, - "dropout": 0.2, - "baseline": False -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 5], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 20 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Crossformer/PEMS04.py b/baselines/Crossformer/PEMS04.py deleted file mode 100644 index 2fad132b..00000000 --- a/baselines/Crossformer/PEMS04.py +++ /dev/null @@ -1,119 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import Crossformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 192 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer -NUM_NODES = 307 -CFG.MODEL.PARAM = { - "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, - "seg_len": 24, - "win_size": 2, - # default parameters - "factor": 10, - "d_model": 256, - "d_ff": 512, - "n_heads": 4, - "e_layers": 3, - "dropout": 0.2, - "baseline": False -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0002, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 5], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 8 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Crossformer/PEMS04_LTSF.py b/baselines/Crossformer/PEMS04_LTSF.py new file mode 100644 index 00000000..572472df --- /dev/null +++ b/baselines/Crossformer/PEMS04_LTSF.py @@ -0,0 +1,145 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Crossformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 192 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Crossformer +NUM_NODES = 307 +MODEL_PARAM = { + "data_dim": NUM_NODES, + "in_len": INPUT_LEN, + "out_len": OUTPUT_LEN, + "seg_len": 24, + "win_size": 2, + # default parameters + "factor": 10, + "d_model": 256, + "d_ff": 512, + "n_heads": 4, + "e_layers": 3, + "dropout": 0.2, + "baseline": False +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0002, + "weight_decay": 0.0005, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 5], + "gamma": 0.5 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Crossformer/PEMS08.py b/baselines/Crossformer/PEMS08.py deleted file mode 100644 index efba2c1e..00000000 --- a/baselines/Crossformer/PEMS08.py +++ /dev/null @@ -1,119 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import Crossformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer -NUM_NODES = 170 -CFG.MODEL.PARAM = { - "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, - "seg_len": 24, - "win_size": 2, - # default parameters - "factor": 10, - "d_model": 256, - "d_ff": 512, - "n_heads": 4, - "e_layers": 3, - "dropout": 0.2, - "baseline": False -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0002, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 5], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 8 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Crossformer/PEMS08_LTSF.py b/baselines/Crossformer/PEMS08_LTSF.py new file mode 100644 index 00000000..d0d404f4 --- /dev/null +++ b/baselines/Crossformer/PEMS08_LTSF.py @@ -0,0 +1,145 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Crossformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Crossformer +NUM_NODES = 170 +MODEL_PARAM = { + "data_dim": NUM_NODES, + "in_len": INPUT_LEN, + "out_len": OUTPUT_LEN, + "seg_len": 24, + "win_size": 2, + # default parameters + "factor": 10, + "d_model": 256, + "d_ff": 512, + "n_heads": 4, + "e_layers": 3, + "dropout": 0.2, + "baseline": False +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0002, + "weight_decay": 0.0005, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 5], + "gamma": 0.5 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Crossformer/Weather.py b/baselines/Crossformer/Weather.py index d096ee9a..ec45d99f 100644 --- a/baselines/Crossformer/Weather.py +++ b/baselines/Crossformer/Weather.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Crossformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Crossformer" -CFG.MODEL.ARCH = Crossformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Crossformer NUM_NODES = 21 -CFG.MODEL.PARAM = { +MODEL_PARAM = { "data_dim": NUM_NODES, - "in_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, + "in_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "seg_len": 24, "win_size": 2, # default parameters @@ -49,70 +39,103 @@ "dropout": 0.2, "baseline": False } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.00005, - "weight_decay": 0.0005, + "lr": 0.00005 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 5], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Crossformer/arch/attn.py b/baselines/Crossformer/arch/attn.py index 2c77ab0b..80e4c0cc 100644 --- a/baselines/Crossformer/arch/attn.py +++ b/baselines/Crossformer/arch/attn.py @@ -14,7 +14,7 @@ def __init__(self, scale=None, attention_dropout=0.1): super(FullAttention, self).__init__() self.scale = scale self.dropout = nn.Dropout(attention_dropout) - + def forward(self, queries, keys, values): B, L, H, E = queries.shape _, S, _, D = values.shape @@ -23,7 +23,7 @@ def forward(self, queries, keys, values): scores = torch.einsum("blhe,bshe->bhls", queries, keys) A = self.dropout(torch.softmax(scale * scores, dim=-1)) V = torch.einsum("bhls,bshd->blhd", A, values) - + return V.contiguous() @@ -77,7 +77,7 @@ def __init__(self, seg_num, factor, d_model, n_heads, d_ff = None, dropout=0.1): self.dim_sender = AttentionLayer(d_model, n_heads, dropout = dropout) self.dim_receiver = AttentionLayer(d_model, n_heads, dropout = dropout) self.router = nn.Parameter(torch.randn(seg_num, factor, d_model)) - + self.dropout = nn.Dropout(dropout) self.norm1 = nn.LayerNorm(d_model) diff --git a/baselines/Crossformer/arch/cross_decoder.py b/baselines/Crossformer/arch/cross_decoder.py index c4bb910e..5e981498 100644 --- a/baselines/Crossformer/arch/cross_decoder.py +++ b/baselines/Crossformer/arch/cross_decoder.py @@ -11,7 +11,7 @@ class DecoderLayer(nn.Module): def __init__(self, seg_len, d_model, n_heads, d_ff=None, dropout=0.1, out_seg_num = 10, factor = 10): super(DecoderLayer, self).__init__() self.self_attention = TwoStageAttentionLayer(out_seg_num, factor, d_model, n_heads, \ - d_ff, dropout) + d_ff, dropout) self.cross_attention = AttentionLayer(d_model, n_heads, dropout = dropout) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) @@ -30,7 +30,7 @@ def forward(self, x, cross): batch = x.shape[0] x = self.self_attention(x) x = rearrange(x, 'b ts_d out_seg_num d_model -> (b ts_d) out_seg_num d_model') - + cross = rearrange(cross, 'b ts_d in_seg_num d_model -> (b ts_d) in_seg_num d_model') tmp = self.cross_attention( x, cross, cross, @@ -39,7 +39,7 @@ def forward(self, x, cross): y = x = self.norm1(x) y = self.MLP1(y) dec_output = self.norm2(x+y) - + dec_output = rearrange(dec_output, '(b ts_d) seg_dec_num d_model -> b ts_d seg_dec_num d_model', b = batch) layer_predict = self.linear_pred(dec_output) layer_predict = rearrange(layer_predict, 'b out_d seg_num seg_len -> b (out_d seg_num) seg_len') @@ -73,7 +73,7 @@ def forward(self, x, cross): else: final_predict = final_predict + layer_predict i += 1 - + final_predict = rearrange(final_predict, 'b (out_d seg_num) seg_len -> b (seg_num seg_len) out_d', out_d = ts_d) return final_predict diff --git a/baselines/Crossformer/arch/cross_embed.py b/baselines/Crossformer/arch/cross_embed.py index c81ddc0a..138d071b 100644 --- a/baselines/Crossformer/arch/cross_embed.py +++ b/baselines/Crossformer/arch/cross_embed.py @@ -18,5 +18,5 @@ def forward(self, x): x_segment = rearrange(x, 'b (seg_num seg_len) d -> (b d seg_num) seg_len', seg_len = self.seg_len) x_embed = self.linear(x_segment) x_embed = rearrange(x_embed, '(b d seg_num) d_model -> b d seg_num d_model', b = batch, d = ts_dim) - + return x_embed \ No newline at end of file diff --git a/baselines/Crossformer/arch/cross_encoder.py b/baselines/Crossformer/arch/cross_encoder.py index 0b602064..aa0827ae 100644 --- a/baselines/Crossformer/arch/cross_encoder.py +++ b/baselines/Crossformer/arch/cross_encoder.py @@ -53,22 +53,22 @@ def __init__(self, win_size, d_model, n_heads, d_ff, depth, dropout, \ self.merge_layer = SegMerging(d_model, win_size, nn.LayerNorm) else: self.merge_layer = None - + self.encode_layers = nn.ModuleList() for i in range(depth): self.encode_layers.append(TwoStageAttentionLayer(seg_num, factor, d_model, n_heads, \ d_ff, dropout)) - + def forward(self, x): _, ts_dim, _, _ = x.shape if self.merge_layer is not None: x = self.merge_layer(x) - + for layer in self.encode_layers: - x = layer(x) - + x = layer(x) + return x class Encoder(nn.Module): @@ -89,7 +89,7 @@ def __init__(self, e_blocks, win_size, d_model, n_heads, d_ff, block_depth, drop def forward(self, x): encode_x = [] encode_x.append(x) - + for block in self.encode_blocks: x = block(x) encode_x.append(x) diff --git a/baselines/Crossformer/arch/crossformer_arch.py b/baselines/Crossformer/arch/crossformer_arch.py index 6df37b00..28d1f2b4 100644 --- a/baselines/Crossformer/arch/crossformer_arch.py +++ b/baselines/Crossformer/arch/crossformer_arch.py @@ -36,7 +36,7 @@ def __init__(self, data_dim, in_len, out_len, seg_len, win_size = 4, # Encoder self.encoder = Encoder(e_layers, win_size, d_model, n_heads, d_ff, block_depth = 1, \ dropout = dropout,in_seg_num = (self.pad_in_len // seg_len), factor = factor) - + # Decoder self.dec_pos_embedding = nn.Parameter(torch.randn(1, data_dim, (self.pad_out_len // seg_len), d_model)) self.decoder = Decoder(seg_len, e_layers + 1, d_model, n_heads, d_ff, dropout, \ @@ -55,7 +55,7 @@ def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_s x_seq = self.enc_value_embedding(x_seq) x_seq += self.enc_pos_embedding x_seq = self.pre_norm(x_seq) - + enc_out = self.encoder(x_seq) dec_in = repeat(self.dec_pos_embedding, 'b ts_d l d -> (repeat b) ts_d l d', repeat = batch_size) diff --git a/baselines/Crossformer/run.sh b/baselines/Crossformer/run.sh deleted file mode 100644 index 4bcfc8fe..00000000 --- a/baselines/Crossformer/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/Crossformer/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/Crossformer/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/Crossformer/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/Crossformer/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/Crossformer/Electricity.py --gpus '0' -python experiments/train.py -c baselines/Crossformer/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/Crossformer/Weather.py --gpus '0' -python experiments/train.py -c baselines/Crossformer/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/Crossformer/PEMS08.py --gpus '0' diff --git a/baselines/D2STGNN/METR-LA.py b/baselines/D2STGNN/METR-LA.py index 716c8e75..d068db2b 100644 --- a/baselines/D2STGNN/METR-LA.py +++ b/baselines/D2STGNN/METR-LA.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import D2STGNN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "D2STGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "D2STGNN" -CFG.MODEL.ARCH = D2STGNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = D2STGNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_feat": 1, "num_hidden": 32, "dropout": 0.1, @@ -54,12 +43,71 @@ "time_in_day_size": 288, "day_in_week_size": 7, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { @@ -67,66 +115,43 @@ "weight_decay": 1.0e-5, "eps": 1.0e-8 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 30, 38, 46, 54, 62, 70, 80], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# curriculum learning +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 6 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 32 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 32 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/D2STGNN/PEMS-BAY.py b/baselines/D2STGNN/PEMS-BAY.py index 26f4ef34..3ec11d41 100644 --- a/baselines/D2STGNN/PEMS-BAY.py +++ b/baselines/D2STGNN/PEMS-BAY.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import D2STGNN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "D2STGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "D2STGNN" -CFG.MODEL.ARCH = D2STGNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = D2STGNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_feat": 1, "num_hidden": 32, "dropout": 0.1, @@ -54,12 +43,71 @@ "time_in_day_size": 288, "day_in_week_size": 7, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { @@ -67,66 +115,43 @@ "weight_decay": 1.0e-5, "eps": 1.0e-8 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 30, 38, 46, 54, 62, 70, 80], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# curriculum learning +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 30 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 32 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 32 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/D2STGNN/PEMS03.py b/baselines/D2STGNN/PEMS03.py index 4f3abd2e..919b58ce 100644 --- a/baselines/D2STGNN/PEMS03.py +++ b/baselines/D2STGNN/PEMS03.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import D2STGNN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "D2STGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "D2STGNN" -CFG.MODEL.ARCH = D2STGNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = D2STGNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_feat": 1, "num_hidden": 32, "dropout": 0.1, @@ -54,12 +43,71 @@ "time_in_day_size": 288, "day_in_week_size": 7, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { @@ -67,66 +115,43 @@ "weight_decay": 1.0e-5, "eps": 1.0e-8 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 30, 38, 46, 54, 150], + "milestones": [1, 30, 38, 46, 54, 62, 70, 80], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 30 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/D2STGNN/PEMS04.py b/baselines/D2STGNN/PEMS04.py index d9669ce8..f2da9873 100644 --- a/baselines/D2STGNN/PEMS04.py +++ b/baselines/D2STGNN/PEMS04.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import D2STGNN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "D2STGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "D2STGNN" -CFG.MODEL.ARCH = D2STGNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = D2STGNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_feat": 1, "num_hidden": 32, "dropout": 0.1, @@ -54,12 +43,71 @@ "time_in_day_size": 288, "day_in_week_size": 7, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { @@ -67,66 +115,43 @@ "weight_decay": 1.0e-5, "eps": 1.0e-8 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 30, 38, 46, 54, 150], + "milestones": [1, 30, 38, 46, 54, 62, 70, 80], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 30 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/D2STGNN/PEMS07.py b/baselines/D2STGNN/PEMS07.py index 261ab315..5a360bc8 100644 --- a/baselines/D2STGNN/PEMS07.py +++ b/baselines/D2STGNN/PEMS07.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import D2STGNN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "D2STGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "D2STGNN" -CFG.MODEL.ARCH = D2STGNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = D2STGNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_feat": 1, "num_hidden": 32, "dropout": 0.1, @@ -54,12 +43,71 @@ "time_in_day_size": 288, "day_in_week_size": 7, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { @@ -67,66 +115,43 @@ "weight_decay": 1.0e-5, "eps": 1.0e-8 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 30, 38, 46, 54, 150], + "milestones": [1, 30, 38, 46, 54, 62, 70, 80], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 8 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 30 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/D2STGNN/PEMS08.py b/baselines/D2STGNN/PEMS08.py index cc860ba2..065e7980 100644 --- a/baselines/D2STGNN/PEMS08.py +++ b/baselines/D2STGNN/PEMS08.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import D2STGNN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "D2STGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "D2STGNN" -CFG.MODEL.ARCH = D2STGNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = D2STGNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_feat": 1, "num_hidden": 32, "dropout": 0.1, @@ -54,12 +43,71 @@ "time_in_day_size": 288, "day_in_week_size": 7, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { @@ -67,66 +115,43 @@ "weight_decay": 1.0e-5, "eps": 1.0e-8 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 30, 38, 46, 54, 150], + "milestones": [1, 30, 38, 46, 54, 62, 70, 80], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 30 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/D2STGNN/arch/d2stgnn_arch.py b/baselines/D2STGNN/arch/d2stgnn_arch.py index 52d8c4cc..99230425 100644 --- a/baselines/D2STGNN/arch/d2stgnn_arch.py +++ b/baselines/D2STGNN/arch/d2stgnn_arch.py @@ -145,7 +145,7 @@ def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_s Args: history_data (Tensor): Input data with shape: [B, L, N, C] - + Returns: torch.Tensor: outputs with shape [B, L, N, C] """ diff --git a/baselines/D2STGNN/arch/dynamic_graph_conv/utils/mask.py b/baselines/D2STGNN/arch/dynamic_graph_conv/utils/mask.py index 1f3b6eca..04e429c8 100644 --- a/baselines/D2STGNN/arch/dynamic_graph_conv/utils/mask.py +++ b/baselines/D2STGNN/arch/dynamic_graph_conv/utils/mask.py @@ -5,7 +5,7 @@ class Mask(nn.Module): def __init__(self, **model_args): super().__init__() self.mask = model_args['adjs'] - + def _mask(self, index, adj): mask = self.mask[index] + torch.ones_like(self.mask[index]) * 1e-7 return mask.to(adj.device) * adj diff --git a/baselines/D2STGNN/run.sh b/baselines/D2STGNN/run.sh deleted file mode 100644 index 680dbff7..00000000 --- a/baselines/D2STGNN/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/D2STGNN/METR-LA.py --gpus '2' -python experiments/train.py -c baselines/D2STGNN/PEMS-BAY.py --gpus '2' -python experiments/train.py -c baselines/D2STGNN/PEMS03.py --gpus '2' -python experiments/train.py -c baselines/D2STGNN/PEMS04.py --gpus '2' -python experiments/train.py -c baselines/D2STGNN/PEMS07.py --gpus '2' -python experiments/train.py -c baselines/D2STGNN/PEMS08.py --gpus '2' diff --git a/baselines/DCRNN/METR-LA.py b/baselines/DCRNN/METR-LA.py index 0af9a7d6..654d28e4 100644 --- a/baselines/DCRNN/METR-LA.py +++ b/baselines/DCRNN/METR-LA.py @@ -1,50 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DCRNN -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "DCRNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DCRNN" -CFG.MODEL.ARCH = DCRNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DCRNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "horizon": 12, "input_dim": 2, @@ -57,74 +40,112 @@ "adj_mx": [torch.tensor(i) for i in adj_mx], "use_curriculum_learning": True } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] CFG.MODEL.SETUP_GRAPH = True -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.01, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 30, 40, 50], "gamma": 0.1 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DCRNN/PEMS-BAY.py b/baselines/DCRNN/PEMS-BAY.py index 6c1c494e..23cba59d 100644 --- a/baselines/DCRNN/PEMS-BAY.py +++ b/baselines/DCRNN/PEMS-BAY.py @@ -1,50 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DCRNN -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "DCRNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DCRNN" -CFG.MODEL.ARCH = DCRNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DCRNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "horizon": 12, "input_dim": 2, @@ -57,74 +40,112 @@ "adj_mx": [torch.tensor(i) for i in adj_mx], "use_curriculum_learning": True } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.01, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 30, 40, 50], "gamma": 0.1 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DCRNN/PEMS03.py b/baselines/DCRNN/PEMS03.py index 4dd2fbb4..870be996 100644 --- a/baselines/DCRNN/PEMS03.py +++ b/baselines/DCRNN/PEMS03.py @@ -1,50 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DCRNN -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "DCRNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DCRNN" -CFG.MODEL.ARCH = DCRNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DCRNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "horizon": 12, "input_dim": 2, @@ -57,74 +40,108 @@ "adj_mx": [torch.tensor(i) for i in adj_mx], "use_curriculum_learning": True } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] CFG.MODEL.SETUP_GRAPH = True -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.003, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [80], "gamma": 0.3 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DCRNN/PEMS04.py b/baselines/DCRNN/PEMS04.py index efd87640..4741babd 100644 --- a/baselines/DCRNN/PEMS04.py +++ b/baselines/DCRNN/PEMS04.py @@ -1,50 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DCRNN -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "DCRNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DCRNN" -CFG.MODEL.ARCH = DCRNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DCRNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "horizon": 12, "input_dim": 2, @@ -57,74 +40,112 @@ "adj_mx": [torch.tensor(i) for i in adj_mx], "use_curriculum_learning": True } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.003, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [80], "gamma": 0.3 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DCRNN/PEMS07.py b/baselines/DCRNN/PEMS07.py index 4ba3d280..e68909c0 100644 --- a/baselines/DCRNN/PEMS07.py +++ b/baselines/DCRNN/PEMS07.py @@ -1,50 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DCRNN -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "DCRNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DCRNN" -CFG.MODEL.ARCH = DCRNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DCRNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "horizon": 12, "input_dim": 2, @@ -57,74 +40,108 @@ "adj_mx": [torch.tensor(i) for i in adj_mx], "use_curriculum_learning": True } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.003, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [80], "gamma": 0.3 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DCRNN/PEMS08.py b/baselines/DCRNN/PEMS08.py index 5f63afef..caceeca2 100644 --- a/baselines/DCRNN/PEMS08.py +++ b/baselines/DCRNN/PEMS08.py @@ -1,50 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DCRNN -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "DCRNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DCRNN" -CFG.MODEL.ARCH = DCRNN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DCRNN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "horizon": 12, "input_dim": 2, @@ -57,74 +40,108 @@ "adj_mx": [torch.tensor(i) for i in adj_mx], "use_curriculum_learning": True } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.003, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [80], "gamma": 0.3 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DCRNN/run.sh b/baselines/DCRNN/run.sh deleted file mode 100644 index 679b7b88..00000000 --- a/baselines/DCRNN/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/DCRNN/METR-LA.py --gpus '1' -python experiments/train.py -c baselines/DCRNN/PEMS-BAY.py --gpus '1' -python experiments/train.py -c baselines/DCRNN/PEMS03.py --gpus '1' -python experiments/train.py -c baselines/DCRNN/PEMS04.py --gpus '1' -python experiments/train.py -c baselines/DCRNN/PEMS07.py --gpus '1' -python experiments/train.py -c baselines/DCRNN/PEMS08.py --gpus '1' diff --git a/baselines/DGCRN/METR-LA.py b/baselines/DGCRN/METR-LA.py index 592384b3..26412749 100644 --- a/baselines/DGCRN/METR-LA.py +++ b/baselines/DGCRN/METR-LA.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DGCRN from .runner import DGCRNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DGCRN model configuration" -CFG.RUNNER = DGCRNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DGCRN" -CFG.MODEL.ARCH = DGCRN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DGCRN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_depth": 2, "num_nodes": 207, "predefined_A": [torch.Tensor(_) for _ in adj_mx], @@ -53,78 +43,117 @@ "rnn_size": 64, "hyperGNN_dim": 16 } +NUM_EPOCHS = 150 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = DGCRNRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## -# ================= optim ================= # +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0.0001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones":[100, 150], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 150 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -## curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 6 -CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DGCRN/PEMS-BAY.py b/baselines/DGCRN/PEMS-BAY.py index b89d3e79..cbc32f1c 100644 --- a/baselines/DGCRN/PEMS-BAY.py +++ b/baselines/DGCRN/PEMS-BAY.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DGCRN from .runner import DGCRNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DGCRN model configuration" -CFG.RUNNER = DGCRNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DGCRN" -CFG.MODEL.ARCH = DGCRN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DGCRN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_depth": 2, "num_nodes": 325, "predefined_A": [torch.Tensor(_) for _ in adj_mx], @@ -53,78 +43,118 @@ "rnn_size": 64, "hyperGNN_dim": 16 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = DGCRNRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## -# ================= optim ================= # +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0.0001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones":[100, 150], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -## curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 6 -CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +CFG.MODEL.DDP_FIND_UNUSED_PARAMETERS = True # Controls the `find_unused_parameters parameter` of `torch.nn.parallel.DistributedDataParallel`. In distributed computing, if there are unused parameters in the forward process, PyTorch usually raises a RuntimeError. In such cases, this parameter should be set to True. -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DGCRN/PEMS03.py b/baselines/DGCRN/PEMS03.py index cff5df78..fab37c3c 100644 --- a/baselines/DGCRN/PEMS03.py +++ b/baselines/DGCRN/PEMS03.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DGCRN from .runner import DGCRNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DGCRN model configuration" -CFG.RUNNER = DGCRNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DGCRN" -CFG.MODEL.ARCH = DGCRN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DGCRN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_depth": 2, "num_nodes": 358, "predefined_A": [torch.Tensor(_) for _ in adj_mx], @@ -53,78 +43,117 @@ "rnn_size": 64, "hyperGNN_dim": 16 } +NUM_EPOCHS = 150 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = DGCRNRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## -# ================= optim ================= # +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0.0001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones":[100, 150], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 150 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -## curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 6 -CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DGCRN/PEMS04.py b/baselines/DGCRN/PEMS04.py index 76646d97..b7ce7e92 100644 --- a/baselines/DGCRN/PEMS04.py +++ b/baselines/DGCRN/PEMS04.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DGCRN from .runner import DGCRNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DGCRN model configuration" -CFG.RUNNER = DGCRNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DGCRN" -CFG.MODEL.ARCH = DGCRN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DGCRN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_depth": 2, "num_nodes": 307, "predefined_A": [torch.Tensor(_) for _ in adj_mx], @@ -53,78 +43,117 @@ "rnn_size": 64, "hyperGNN_dim": 16 } +NUM_EPOCHS = 150 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = DGCRNRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## -# ================= optim ================= # +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0.0001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones":[100, 150], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 150 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -## curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 6 -CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DGCRN/PEMS07.py b/baselines/DGCRN/PEMS07.py index 25d68517..97f93698 100644 --- a/baselines/DGCRN/PEMS07.py +++ b/baselines/DGCRN/PEMS07.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DGCRN from .runner import DGCRNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DGCRN model configuration" -CFG.RUNNER = DGCRNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DGCRN" -CFG.MODEL.ARCH = DGCRN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DGCRN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_depth": 2, "num_nodes": 883, "predefined_A": [torch.Tensor(_) for _ in adj_mx], @@ -53,78 +43,117 @@ "rnn_size": 64, "hyperGNN_dim": 16 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = DGCRNRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0.0001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones":[100, 150], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 32 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 24 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -## curriculum learning +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 6 -CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DGCRN/PEMS08.py b/baselines/DGCRN/PEMS08.py index 25a56f6d..3fced5c1 100644 --- a/baselines/DGCRN/PEMS08.py +++ b/baselines/DGCRN/PEMS08.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import DGCRN from .runner import DGCRNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DGCRN model configuration" -CFG.RUNNER = DGCRNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DGCRN" -CFG.MODEL.ARCH = DGCRN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DGCRN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_depth": 2, "num_nodes": 170, "predefined_A": [torch.Tensor(_) for _ in adj_mx], @@ -53,78 +43,117 @@ "rnn_size": 64, "hyperGNN_dim": 16 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = DGCRNRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## -# ================= optim ================= # +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0.0001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones":[100, 150], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -## curriculum learning +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} +# Curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 6 -CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DGCRN/run.sh b/baselines/DGCRN/run.sh deleted file mode 100644 index 1b108cb2..00000000 --- a/baselines/DGCRN/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/DGCRN/METR-LA.py --gpus '2' -python experiments/train.py -c baselines/DGCRN/PEMS-BAY.py --gpus '2' -python experiments/train.py -c baselines/DGCRN/PEMS03.py --gpus '2' -python experiments/train.py -c baselines/DGCRN/PEMS04.py --gpus '2' -python experiments/train.py -c baselines/DGCRN/PEMS07.py --gpus '2' -python experiments/train.py -c baselines/DGCRN/PEMS08.py --gpus '2' diff --git a/baselines/DGCRN/runner/dgcrn_runner.py b/baselines/DGCRN/runner/dgcrn_runner.py index 29bd4485..4f31375f 100644 --- a/baselines/DGCRN/runner/dgcrn_runner.py +++ b/baselines/DGCRN/runner/dgcrn_runner.py @@ -20,7 +20,7 @@ def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: b """ # preprocess - future_data, history_data = data + future_data, history_data = data['target'], data['inputs'] history_data = self.to_running_device(history_data) # B, L, N, C future_data = self.to_running_device(future_data) # B, L, N, C batch_size, length, num_nodes, _ = future_data.shape diff --git a/baselines/DLinear/ETTh1.py b/baselines/DLinear/ETTh1.py index b96bdff2..c63b67e3 100644 --- a/baselines/DLinear/ETTh1.py +++ b/baselines/DLinear/ETTh1.py @@ -1,108 +1,135 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 7 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/ETTh2.py b/baselines/DLinear/ETTh2.py index 8e712a34..c6536f29 100644 --- a/baselines/DLinear/ETTh2.py +++ b/baselines/DLinear/ETTh2.py @@ -1,108 +1,135 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 7 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/ETTm1.py b/baselines/DLinear/ETTm1.py index 40751085..7ebeda41 100644 --- a/baselines/DLinear/ETTm1.py +++ b/baselines/DLinear/ETTm1.py @@ -1,108 +1,135 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 7 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/ETTm2.py b/baselines/DLinear/ETTm2.py index 725ed0cb..51404298 100644 --- a/baselines/DLinear/ETTm2.py +++ b/baselines/DLinear/ETTm2.py @@ -1,108 +1,135 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 7 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/Electricity.py b/baselines/DLinear/Electricity.py index 8769b4f9..5993ac67 100644 --- a/baselines/DLinear/Electricity.py +++ b/baselines/DLinear/Electricity.py @@ -1,108 +1,135 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 321 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 321 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/ExchangeRate.py b/baselines/DLinear/ExchangeRate.py index ac0dffc8..244d7b9d 100644 --- a/baselines/DLinear/ExchangeRate.py +++ b/baselines/DLinear/ExchangeRate.py @@ -1,108 +1,135 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 8 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 8 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/METR-LA.py b/baselines/DLinear/METR-LA.py deleted file mode 100644 index 0f6b77b5..00000000 --- a/baselines/DLinear/METR-LA.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import DLinear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 207 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/DLinear/PEMS-BAY.py b/baselines/DLinear/PEMS-BAY.py deleted file mode 100644 index e80091e2..00000000 --- a/baselines/DLinear/PEMS-BAY.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import DLinear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 325 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/DLinear/PEMS04.py b/baselines/DLinear/PEMS04.py deleted file mode 100644 index 9ae9282c..00000000 --- a/baselines/DLinear/PEMS04.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import DLinear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 307 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/DLinear/PEMS04_LTSF.py b/baselines/DLinear/PEMS04_LTSF.py new file mode 100644 index 00000000..38367c0d --- /dev/null +++ b/baselines/DLinear/PEMS04_LTSF.py @@ -0,0 +1,138 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import DLinear + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 307 +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.002, + "weight_decay": 0.0001, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/PEMS08.py b/baselines/DLinear/PEMS08.py deleted file mode 100644 index da1ff5af..00000000 --- a/baselines/DLinear/PEMS08.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import DLinear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 170 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/DLinear/PEMS08_LTSF.py b/baselines/DLinear/PEMS08_LTSF.py new file mode 100644 index 00000000..321c8d4c --- /dev/null +++ b/baselines/DLinear/PEMS08_LTSF.py @@ -0,0 +1,138 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import DLinear + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 170 +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.002, + "weight_decay": 0.0001, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/Weather.py b/baselines/DLinear/Weather.py index ef82560c..354a3fe3 100644 --- a/baselines/DLinear/Weather.py +++ b/baselines/DLinear/Weather.py @@ -1,108 +1,135 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "individual": False, + "enc_in": 21 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DLinear" -CFG.MODEL.ARCH = DLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 21 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DLinear/run.sh b/baselines/DLinear/run.sh deleted file mode 100644 index 910b0972..00000000 --- a/baselines/DLinear/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/DLinear/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/DLinear/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/DLinear/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/DLinear/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/DLinear/Electricity.py --gpus '0' -python experiments/train.py -c baselines/DLinear/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/DLinear/Weather.py --gpus '0' -python experiments/train.py -c baselines/DLinear/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/DLinear/PEMS08.py --gpus '0' diff --git a/baselines/DSFormer/ETTh1.py b/baselines/DSFormer/ETTh1.py index 1b7bc9e9..1c597552 100644 --- a/baselines/DSFormer/ETTh1.py +++ b/baselines/DSFormer/ETTh1.py @@ -1,42 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 3407 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 7 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 1, "dropout": 0.15, @@ -45,73 +35,107 @@ "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,5,15,25,50], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 + 'max_norm': 3.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/ETTh2.py b/baselines/DSFormer/ETTh2.py index 45c91c83..d3be0578 100644 --- a/baselines/DSFormer/ETTh2.py +++ b/baselines/DSFormer/ETTh2.py @@ -1,42 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 7 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 1, "dropout": 0.15, @@ -45,73 +35,107 @@ "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,5,15,25,50], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 + 'max_norm': 3.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/ETTm1.py b/baselines/DSFormer/ETTm1.py index 2066b78f..6cc63708 100644 --- a/baselines/DSFormer/ETTm1.py +++ b/baselines/DSFormer/ETTm1.py @@ -1,42 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 7 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 1, "dropout": 0.15, @@ -45,72 +35,107 @@ "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002 + "lr": 0.002, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,5,15,25,50], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 + 'max_norm': 3.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/ETTm2.py b/baselines/DSFormer/ETTm2.py index 2956fe67..7173f7e8 100644 --- a/baselines/DSFormer/ETTm2.py +++ b/baselines/DSFormer/ETTm2.py @@ -1,42 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 3407 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 7 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 1, "dropout": 0.15, @@ -45,73 +35,107 @@ "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,5,15,25,50], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 + 'max_norm': 3.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 32 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/Electricity.py b/baselines/DSFormer/Electricity.py index b99f8ed6..da2a4a31 100644 --- a/baselines/DSFormer/Electricity.py +++ b/baselines/DSFormer/Electricity.py @@ -1,42 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 3407 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 321 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 1, "dropout": 0.15, @@ -45,73 +35,107 @@ "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,15,25,50,75,100], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 + 'max_norm': 3.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/ExchangeRate.py b/baselines/DSFormer/ExchangeRate.py index a62e163e..fbfb8e6f 100644 --- a/baselines/DSFormer/ExchangeRate.py +++ b/baselines/DSFormer/ExchangeRate.py @@ -1,42 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 8 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 1, "dropout": 0.15, @@ -45,72 +36,106 @@ "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,5,15,25,50], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 + 'max_norm': 3.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/Illness.py b/baselines/DSFormer/Illness.py index 22a1827d..7657d620 100644 --- a/baselines/DSFormer/Illness.py +++ b/baselines/DSFormer/Illness.py @@ -1,42 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Illness" -CFG.DATASET_TYPE = "Illness" -CFG.DATASET_INPUT_LEN = 60 -CFG.DATASET_OUTPUT_LEN = 60 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 3407 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Illness' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 7 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 2, "dropout": 0.3, @@ -45,69 +35,106 @@ "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002 + "lr": 0.002, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,5,15,25,50,75,100], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 3.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 60] + +# Evaluation parameters +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/METR-LA.py b/baselines/DSFormer/METR-LA.py deleted file mode 100644 index c64ab583..00000000 --- a/baselines/DSFormer/METR-LA.py +++ /dev/null @@ -1,118 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import DSFormer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "traffic speed" -CFG.DATASET_INPUT_LEN = 288 -CFG.DATASET_OUTPUT_LEN = 288 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer -NUM_NODES = 207 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, - "num_id": NUM_NODES, - "num_layer": 1, - "dropout": 0.15, - "muti_head": 2, - "num_samp": 3, - "IF_node": True, - "IF_REVIN":True -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,10,25,50,75,100], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 32 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 32 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 48, 96, 144, 192, 288] diff --git a/baselines/DSFormer/PEMS04.py b/baselines/DSFormer/PEMS04.py deleted file mode 100644 index f0dd8b56..00000000 --- a/baselines/DSFormer/PEMS04.py +++ /dev/null @@ -1,118 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import DSFormer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer -NUM_NODES = 307 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, - "num_id": NUM_NODES, - "num_layer": 1, - "dropout": 0.3, - "muti_head": 2, - "num_samp": 3, - "IF_node": True, - "IF_REVIN":True -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,10,25,50,75,100], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/DSFormer/PEMS04_LTSF.py b/baselines/DSFormer/PEMS04_LTSF.py new file mode 100644 index 00000000..3b919560 --- /dev/null +++ b/baselines/DSFormer/PEMS04_LTSF.py @@ -0,0 +1,144 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import DSFormer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer +NUM_NODES = 307 +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, + "num_id": NUM_NODES, + "num_layer": 1, + "dropout": 0.3, + "muti_head": 2, + "num_samp": 3, + "IF_node": True, + "IF_REVIN":True +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.002, + "weight_decay": 0.0001, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 10, 25, 50, 75, 100], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 32 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/PEMS08.py b/baselines/DSFormer/PEMS08.py deleted file mode 100644 index 4326dedd..00000000 --- a/baselines/DSFormer/PEMS08.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import DSFormer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Crossformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer -NUM_NODES = 170 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, - "num_id": NUM_NODES, - "num_layer": 1, - "dropout": 0.3, - "muti_head": 2, - "num_samp": 3, - "IF_node": True, - "IF_REVIN":True -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,10,25,50,75,100], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/DSFormer/PEMS08_LTSF.py b/baselines/DSFormer/PEMS08_LTSF.py new file mode 100644 index 00000000..00e5717d --- /dev/null +++ b/baselines/DSFormer/PEMS08_LTSF.py @@ -0,0 +1,144 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import DSFormer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer +NUM_NODES = 170 +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, + "num_id": NUM_NODES, + "num_layer": 1, + "dropout": 0.3, + "muti_head": 2, + "num_samp": 3, + "IF_node": True, + "IF_REVIN":True +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.002, + "weight_decay": 0.0001, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 10, 25, 50, 75, 100], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 32 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/Traffic.py b/baselines/DSFormer/Traffic.py index 773039d5..a568c60b 100644 --- a/baselines/DSFormer/Traffic.py +++ b/baselines/DSFormer/Traffic.py @@ -1,42 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Traffic" -CFG.DATASET_TYPE = "Traffic" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Traffic' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 862 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 1, "dropout": 0.3, @@ -45,73 +35,107 @@ "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1,5,15,25,50,75,100], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 + 'max_norm': 3.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 32 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 32 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96,192, 288, 336] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/Weather.py b/baselines/DSFormer/Weather.py index 65ab24f8..2d2d7de0 100644 --- a/baselines/DSFormer/Weather.py +++ b/baselines/DSFormer/Weather.py @@ -1,116 +1,141 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DSFormer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DSFormer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DSFormer" -CFG.MODEL.ARCH = DSFormer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DSFormer NUM_NODES = 21 -CFG.MODEL.PARAM = { - "Input_len": CFG.DATASET_INPUT_LEN, - "out_len": CFG.DATASET_OUTPUT_LEN, +MODEL_PARAM = { + "Input_len": INPUT_LEN, + "out_len": OUTPUT_LEN, "num_id": NUM_NODES, "num_layer": 1, - "dropout": 0.15, - "muti_head": 2, - "num_samp": 2, + "dropout": 0.3, + "muti_head": 1, + "num_samp": 3, "IF_node": True, "IF_REVIN":True } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [5,15,25,50,75,100], + "milestones": [1, 5, 15, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 3.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DSFormer/run.sh b/baselines/DSFormer/run.sh deleted file mode 100644 index 0f28e838..00000000 --- a/baselines/DSFormer/run.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/DSFormer/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/Electricity.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/Weather.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/Illness.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/Traffic.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/DSFormer/PEMS08.py --gpus '0' diff --git a/baselines/DeepAR/ETTh1.py b/baselines/DeepAR/ETTh1.py index 94f336f5..400203e0 100644 --- a/baselines/DeepAR/ETTh1.py +++ b/baselines/DeepAR/ETTh1.py @@ -1,103 +1,128 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DeepAR +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 7 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 7 - } +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.003, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/ETTm1.py b/baselines/DeepAR/ETTm1.py index 00533a5c..190b2fbe 100644 --- a/baselines/DeepAR/ETTm1.py +++ b/baselines/DeepAR/ETTm1.py @@ -1,103 +1,128 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DeepAR +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 7 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 7 - } +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.003, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/Electricity.py b/baselines/DeepAR/Electricity.py index b4eed750..21107948 100644 --- a/baselines/DeepAR/Electricity.py +++ b/baselines/DeepAR/Electricity.py @@ -1,103 +1,128 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DeepAR +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 321 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 321 - } +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.003, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 15 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/ExchangeRate.py b/baselines/DeepAR/ExchangeRate.py index 2c174d3f..9dae540a 100644 --- a/baselines/DeepAR/ExchangeRate.py +++ b/baselines/DeepAR/ExchangeRate.py @@ -1,103 +1,129 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DeepAR +NUM_NODES = 8 +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 7 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 8 - } +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.003, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 15 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/METR-LA.py b/baselines/DeepAR/METR-LA.py deleted file mode 100644 index 61184559..00000000 --- a/baselines/DeepAR/METR-LA.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset - -from .arch import DeepAR -from .runner import DeepARRunner -from .loss import gaussian_loss - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" -CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 207 -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gaussian_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/DeepAR/PEMS-BAY.py b/baselines/DeepAR/PEMS-BAY.py deleted file mode 100644 index 22c44e4d..00000000 --- a/baselines/DeepAR/PEMS-BAY.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset - -from .arch import DeepAR -from .runner import DeepARRunner -from .loss import gaussian_loss - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" -CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 325 -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gaussian_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/DeepAR/PEMS03.py b/baselines/DeepAR/PEMS03.py deleted file mode 100644 index b97b0f1e..00000000 --- a/baselines/DeepAR/PEMS03.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset - -from .arch import DeepAR -from .runner import DeepARRunner -from .loss import gaussian_loss - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" -CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 358 -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gaussian_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/DeepAR/PEMS04.py b/baselines/DeepAR/PEMS04.py index a74cc390..adf0e7c6 100644 --- a/baselines/DeepAR/PEMS04.py +++ b/baselines/DeepAR/PEMS04.py @@ -1,104 +1,129 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture anad parameters +MODEL_ARCH = DeepAR +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 307 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= model ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 307 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.003, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 32 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 16 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [3, 6, 12] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/PEMS04_LTSF.py b/baselines/DeepAR/PEMS04_LTSF.py index 51e015a0..69218ce3 100644 --- a/baselines/DeepAR/PEMS04_LTSF.py +++ b/baselines/DeepAR/PEMS04_LTSF.py @@ -1,100 +1,131 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 96 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture anad parameters +MODEL_ARCH = DeepAR +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 307 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= model ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 307 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 24 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 32 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 16 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/PEMS07.py b/baselines/DeepAR/PEMS07.py deleted file mode 100644 index be72c230..00000000 --- a/baselines/DeepAR/PEMS07.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset - -from .arch import DeepAR -from .runner import DeepARRunner -from .loss import gaussian_loss - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" -CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 883 -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gaussian_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/DeepAR/PEMS08.py b/baselines/DeepAR/PEMS08.py index 47e0f57b..7cd05e68 100644 --- a/baselines/DeepAR/PEMS08.py +++ b/baselines/DeepAR/PEMS08.py @@ -1,104 +1,129 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture anad parameters +MODEL_ARCH = DeepAR +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 170 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 170 - } +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.003, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 16 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 16 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [3, 6, 12] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/PEMS08_LTSF.py b/baselines/DeepAR/PEMS08_LTSF.py index 37776690..44af96a1 100644 --- a/baselines/DeepAR/PEMS08_LTSF.py +++ b/baselines/DeepAR/PEMS08_LTSF.py @@ -1,100 +1,131 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 96 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture anad parameters +MODEL_ARCH = DeepAR +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 170 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= model ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 170 - } +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.003, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 16 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 16 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/Weather.py b/baselines/DeepAR/Weather.py index 65bf134a..2202f655 100644 --- a/baselines/DeepAR/Weather.py +++ b/baselines/DeepAR/Weather.py @@ -1,103 +1,128 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import DeepAR from .runner import DeepARRunner from .loss import gaussian_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = DeepAR +MODEL_PARAM = { + 'cov_feat_size' : 2, + 'embedding_size' : 32, + 'hidden_size' : 64, + 'num_layers': 3, + 'use_ts_id' : True, + 'id_feat_size': 32, + 'num_nodes': 21 + } +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "DeepAR model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = DeepARRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "DeepAR" -CFG.MODEL.ARCH = DeepAR -CFG.MODEL.PARAM = { - "cov_feat_size" : 2, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : True, - "id_feat_size": 32, - "num_nodes": 21 - } +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = gaussian_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.003, +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr':0.003, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 15 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/DeepAR/run.sh b/baselines/DeepAR/run.sh deleted file mode 100644 index d8c1b259..00000000 --- a/baselines/DeepAR/run.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# python experiments/train.py -c baselines/DeepAR/METR-LA.py --gpus '0' -# python experiments/train.py -c baselines/DeepAR/PEMS-BAY.py --gpus '0' -# python experiments/train.py -c baselines/DeepAR/PEMS03.py --gpus '0' -# python experiments/train.py -c baselines/DeepAR/PEMS04.py --gpus '0' -# python experiments/train.py -c baselines/DeepAR/PEMS07.py --gpus '0' -# python experiments/train.py -c baselines/DeepAR/PEMS08.py --gpus '0' - -# python experiments/train.py -c baselines/DeepAR/ETTh1.py --gpus '0' -# python experiments/train.py -c baselines/DeepAR/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/DeepAR/Electricity.py --gpus '0' -python experiments/train.py -c baselines/DeepAR/Weather.py --gpus '0' -python experiments/train.py -c baselines/DeepAR/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/DeepAR/PEMS04_LTSF.py --gpus '0' -python experiments/train.py -c baselines/DeepAR/PEMS08_LTSF.py --gpus '0' \ No newline at end of file diff --git a/baselines/DeepAR/runner/deepar_runner.py b/baselines/DeepAR/runner/deepar_runner.py index 275a30b5..6f132ba8 100644 --- a/baselines/DeepAR/runner/deepar_runner.py +++ b/baselines/DeepAR/runner/deepar_runner.py @@ -1,6 +1,10 @@ -from typing import Dict +import os +import json +from typing import Dict, Optional + import torch -from basicts.data.registry import SCALER_REGISTRY +import numpy as np +from tqdm import tqdm from easytorch.utils.dist import master_only from basicts.runners import BaseTimeSeriesForecastingRunner @@ -11,7 +15,7 @@ def __init__(self, cfg: dict): super().__init__(cfg) self.forward_features = cfg["MODEL"].get("FORWARD_FEATURES", None) self.target_features = cfg["MODEL"].get("TARGET_FEATURES", None) - self.output_seq_len = cfg["DATASET_OUTPUT_LEN"] + self.output_seq_len = cfg["DATASET"]["PARAM"]["output_len"] def select_input_features(self, data: torch.Tensor) -> torch.Tensor: """Select input features and reshape data to fit the target model. @@ -42,55 +46,75 @@ def select_target_features(self, data: torch.Tensor) -> torch.Tensor: data = data[:, :, :, self.target_features] return data - def rescale_data(self, input_data: Dict) -> Dict: - """Rescale data. + def postprocessing(self, input_data: Dict) -> Dict: + """Postprocess data. Args: - data (Dict): Dict of data to be re-scaled. + input_data (Dict): Dictionary containing data to be processed. Returns: - Dict: Dict re-scaled data. + Dict: Processed data. """ - if self.if_rescale: - input_data["inputs"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["inputs"], **self.scaler["args"]) - input_data["prediction"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["prediction"], **self.scaler["args"]) - input_data["target"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["target"], **self.scaler["args"]) + if self.scaler is not None and self.scaler.rescale: + input_data['prediction'] = self.scaler.inverse_transform(input_data['prediction']) + input_data['target'] = self.scaler.inverse_transform(input_data['target']) + input_data['inputs'] = self.scaler.inverse_transform(input_data['inputs']) if "mus" in input_data.keys(): - input_data["mus"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["mus"], **self.scaler["args"]) + input_data['mus'] = self.scaler.inverse_transform(input_data['mus']) if "sigmas" in input_data.keys(): - input_data["sigmas"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["sigmas"], **self.scaler["args"]) + input_data['sigmas'] = self.scaler.inverse_transform(input_data['sigmas']) + # TODO: add more postprocessing steps as needed. return input_data @torch.no_grad() @master_only - def test(self): - """Evaluate the model. - + def test(self, train_epoch: Optional[int] = None, save_metrics: bool = False, save_results: bool = False) -> Dict: + """Test process. + Args: - train_epoch (int, optional): current epoch if in training process. + train_epoch (Optional[int]): Current epoch if in training process. + save_metrics (bool): Save the test metrics. Defaults to False. + save_results (bool): Save the test results. Defaults to False. """ - # test loop - prediction =[] - target = [] - inputs = [] - for _, data in enumerate(self.test_data_loader): + prediction, target, inputs = [], [], [] + + for data in tqdm(self.test_data_loader): + data = self.preprocessing(data) forward_return = self.forward(data, epoch=None, iter_num=None, train=False) + forward_return = self.postprocessing(forward_return) + if not self.if_evaluate_on_gpu: - forward_return["prediction"] = forward_return["prediction"].detach().cpu() - forward_return["target"] = forward_return["target"].detach().cpu() - forward_return["inputs"] = forward_return["inputs"].detach().cpu() - prediction.append(forward_return["prediction"]) - target.append(forward_return["target"]) - inputs.append(forward_return["inputs"]) + forward_return['prediction'] = forward_return['prediction'].detach().cpu() + forward_return['target'] = forward_return['target'].detach().cpu() + forward_return['inputs'] = forward_return['inputs'].detach().cpu() + + prediction.append(forward_return['prediction']) + target.append(forward_return['target']) + inputs.append(forward_return['inputs']) + prediction = torch.cat(prediction, dim=0) target = torch.cat(target, dim=0) inputs = torch.cat(inputs, dim=0) - # re-scale data - returns_all = self.rescale_data({"prediction": prediction[:, -self.output_seq_len:, :, :], "target": target[:, -self.output_seq_len:, :, :], "inputs": inputs}) - # evaluate - self.evaluate(returns_all) + + returns_all = {'prediction': prediction[:, -self.output_seq_len:, :, :], + 'target': target[:, -self.output_seq_len:, :, :], + 'inputs': inputs} + metrics_results = self.compute_evaluation_metrics(returns_all) + + # save + if save_results: + # save returns_all to self.ckpt_save_dir/test_results.npz + test_results = {k: v.cpu().numpy() for k, v in returns_all.items()} + np.savez(os.path.join(self.ckpt_save_dir, 'test_results.npz'), **test_results) + + if save_metrics: + # save metrics_results to self.ckpt_save_dir/test_metrics.json + with open(os.path.join(self.ckpt_save_dir, 'test_metrics.json'), 'w') as f: + json.dump(metrics_results, f, indent=4) + + return returns_all def forward(self, data: tuple, epoch:int = None, iter_num: int = None, train:bool = True, **kwargs) -> tuple: """feed forward process for train, val, and test. Note that the outputs are NOT re-scaled. @@ -105,16 +129,18 @@ def forward(self, data: tuple, epoch:int = None, iter_num: int = None, train:boo dict: keys that must be included: inputs, prediction, target """ - # preprocess - future_data, history_data = data - history_data = self.to_running_device(history_data) # B, L, N, C - future_data = self.to_running_device(future_data) # B, L, N, C - + # Preprocess input data + future_data, history_data = data['target'], data['inputs'] + history_data = self.to_running_device(history_data) # Shape: [B, L, N, C] + future_data = self.to_running_device(future_data) # Shape: [B, L, N, C] + + # Select input features history_data = self.select_input_features(history_data) future_data_4_dec = self.select_input_features(future_data) - # model forward - model_return = self.model(history_data=history_data, future_data=future_data_4_dec, batch_seen=iter_num, epoch=epoch, train=train) + # Forward pass through the model + model_return = self.model(history_data=history_data, future_data=future_data_4_dec, + batch_seen=iter_num, epoch=epoch, train=train) # parse model return if isinstance(model_return, torch.Tensor): model_return = {"prediction": model_return} diff --git a/baselines/DeepAR_M4/M4.py b/baselines/DeepAR_M4/M4.py deleted file mode 100644 index e529685b..00000000 --- a/baselines/DeepAR_M4/M4.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import M4ForecastingDataset - -from .arch import DeepAR -from .loss import gaussian_loss -from .runner import DeepARRunner - -def get_cfg(seasonal_pattern): - assert seasonal_pattern in ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] - prediction_len = {"Yearly": 6, "Quarterly": 8, "Monthly": 18, "Weekly": 13, "Daily": 14, "Hourly": 48}[seasonal_pattern] - num_nodes = {"Yearly": 23000, "Quarterly": 24000, "Monthly": 48000, "Weekly": 359, "Daily": 4227, "Hourly": 414}[seasonal_pattern] - history_size = 2 - history_len = history_size * prediction_len - - CFG = EasyDict() - - # ================= general ================= # - CFG.DESCRIPTION = "DeepAR M4" - CFG.RUNNER = DeepARRunner - CFG.DATASET_CLS = M4ForecastingDataset - CFG.DATASET_NAME = "M4_" + seasonal_pattern - CFG.DATASET_INPUT_LEN = history_len - CFG.DATASET_OUTPUT_LEN = prediction_len - CFG.GPU_NUM = 1 - - # ================= environment ================= # - CFG.ENV = EasyDict() - CFG.ENV.SEED = 1 - CFG.ENV.CUDNN = EasyDict() - CFG.ENV.CUDNN.ENABLED = True - - # ================= model ================= # - CFG.MODEL = EasyDict() - CFG.MODEL.NAME = "DeepAR" - CFG.MODEL.ARCH = DeepAR - CFG.MODEL.PARAM = { - "cov_feat_size" : 0, - "embedding_size" : 32, - "hidden_size" : 64, - "num_layers": 3, - "use_ts_id" : False, - "id_feat_size": None, - "num_nodes": None - } - CFG.MODEL.FORWARD_FEATURES = [0] # values, node id - CFG.MODEL.TARGET_FEATURES = [0] - - # ================= optim ================= # - CFG.TRAIN = EasyDict() - CFG.TRAIN.LOSS = gaussian_loss - CFG.TRAIN.OPTIM = EasyDict() - CFG.TRAIN.OPTIM.TYPE = "Adam" - CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0005, - "weight_decay": 0.0001, - } - CFG.TRAIN.LR_SCHEDULER = EasyDict() - CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" - CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 80], - "gamma": 0.5 - } - - # ================= train ================= # - CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 - } - CFG.TRAIN.NUM_EPOCHS = 100 - CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) - ) - # train data - CFG.TRAIN.DATA = EasyDict() - # read data - CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TRAIN.DATA.BATCH_SIZE = 64 - CFG.TRAIN.DATA.PREFETCH = False - CFG.TRAIN.DATA.SHUFFLE = True - CFG.TRAIN.DATA.NUM_WORKERS = 2 - CFG.TRAIN.DATA.PIN_MEMORY = False - - # ================= test ================= # - CFG.TEST = EasyDict() - CFG.TEST.INTERVAL = 1 - # test data - CFG.TEST.DATA = EasyDict() - # read data - CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TEST.DATA.BATCH_SIZE = 64 - CFG.TEST.DATA.PREFETCH = False - CFG.TEST.DATA.SHUFFLE = False - CFG.TEST.DATA.NUM_WORKERS = 2 - CFG.TEST.DATA.PIN_MEMORY = False - - # ================= evaluate ================= # - CFG.EVAL = EasyDict() - CFG.EVAL.HORIZONS = [] - CFG.EVAL.SAVE_PATH = os.path.abspath(__file__ + "/..") - - return CFG diff --git a/baselines/DeepAR_M4/arch/__init__.py b/baselines/DeepAR_M4/arch/__init__.py deleted file mode 100644 index 6ec10582..00000000 --- a/baselines/DeepAR_M4/arch/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .deepar import DeepAR \ No newline at end of file diff --git a/baselines/DeepAR_M4/arch/deepar.py b/baselines/DeepAR_M4/arch/deepar.py deleted file mode 100644 index d18c7a66..00000000 --- a/baselines/DeepAR_M4/arch/deepar.py +++ /dev/null @@ -1,120 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F - -from .distributions import Gaussian - - -class DeepAR(nn.Module): - """ - Paper: DeepAR: Probabilistic Forecasting with Autoregressive Recurrent Networks; Link: https://arxiv.org/abs/1704.04110; Ref Code: https://github.com/jingw2/demand_forecast, https://github.com/husnejahan/DeepAR-pytorch, https://github.com/arrigonialberto86/deepar. - """ - - def __init__(self, cov_feat_size, embedding_size, hidden_size, num_layers, use_ts_id, id_feat_size=0, num_nodes=0) -> None: - """Init DeepAR. - - Args: - cov_feat_size (int): covariate feature size (e.g. time in day, day in week, etc.). - embedding_size (int): output size of the input embedding layer. - hidden_size (int): hidden size of the LSTM. - num_layers (int): number of LSTM layers. - use_ts_id (bool): whether to use time series id to construct spatial id embedding as additional features. - id_feat_size (int, optional): size of the spatial id embedding. Defaults to 0. - num_nodes (int, optional): number of nodes. Defaults to 0. - """ - super().__init__() - self.use_ts_id = use_ts_id - # input embedding layer - self.input_embed = nn.Linear(1, embedding_size) - # spatial id embedding layer - if use_ts_id: - assert id_feat_size > 0, "id_feat_size must be greater than 0 if use_ts_id is True" - assert num_nodes > 0, "num_nodes must be greater than 0 if use_ts_id is True" - self.id_feat = nn.Parameter(torch.empty(num_nodes, id_feat_size)) - nn.init.xavier_uniform_(self.id_feat) - else: - id_feat_size = 0 - # the LSTM layer - self.encoder = nn.LSTM(embedding_size+cov_feat_size+id_feat_size, hidden_size, num_layers, bias=True, batch_first=True) - # the likelihood function - self.likelihood_layer = Gaussian(hidden_size, 1) - - def gaussian_sample(self, mu, sigma): - """Sampling. - - Args: - mu (torch.Tensor): mean values of distributions. - sigma (torch.Tensor): std values of distributions. - """ - mu = mu.squeeze(1) - sigma = sigma.squeeze(1) - gaussian = torch.distributions.Normal(mu, sigma) - ypred = gaussian.sample([1]).squeeze(0) - return ypred - - def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, train: bool, history_mask: torch.Tensor, future_mask: torch.Tensor, **kwargs) -> torch.Tensor: - """Feed forward of DeepAR. - Reference code: https://github.com/jingw2/demand_forecast/blob/master/deepar.py - - Args: - history_data (torch.Tensor): history data. [B, L, N, C]. - future_data (torch.Tensor): future data. [B, L, N, C]. - train (bool): is training or not. - """ - mask = torch.cat([history_mask, future_mask], dim=1).unsqueeze(-1)[:, 1:, ...] - # mask = torch.where(mask == 0, torch.ones_like(mask) * 1e-5, mask) - # mask = torch.ones_like(mask) - # nornalization - means = history_data.mean(1, keepdim=True).detach() - stdev = torch.sqrt(torch.var(history_data, dim=1, keepdim=True, unbiased=False) + 1e-5) - history_data_normed = history_data - means - history_data_normed /= stdev - future_data_normed = future_data - means - future_data_normed /= stdev - - history_next = None - preds = [] - mus = [] - sigmas = [] - len_in, len_out = history_data.shape[1], future_data.shape[1] - B, _, N, C = history_data.shape - input_feat_full_normed = torch.cat([history_data_normed[:, :, :, 0:1], future_data_normed[:, :, :, 0:1]], dim=1) # B, L_in+L_out, N, 1 - input_feat_full = torch.cat([history_data[:, :, :, 0:1], future_data[:, :, :, 0:1]], dim=1) # B, L_in+L_out, N, 1 - - for t in range(1, len_in + len_out): - if not (t > len_in and not train): # not in the decoding stage when inferecing - history_next = input_feat_full_normed[:, t-1:t, :, 0:1] - embed_feat = self.input_embed(history_next) - # 检查nan - assert not torch.isnan(history_next).any(), "history_next中存在nan" - assert not torch.isnan(self.input_embed.weight).any(), "embed_feat中存在nan" - assert not torch.isnan(self.input_embed.bias).any(), "embed_feat中存在nan" - assert not torch.isnan(embed_feat).any(), "embed_feat中存在nan" - encoder_input = embed_feat - # lstm - B, _, N, C = encoder_input.shape # _ is 1 - encoder_input = encoder_input.transpose(1, 2).reshape(B * N, -1, C) - _, (h, c) = self.encoder(encoder_input) if t == 1 else self.encoder(encoder_input, (h, c)) - # distribution proj - mu, sigma = self.likelihood_layer(h[-1, :, :]) - history_next = self.gaussian_sample(mu, sigma).view(B, N).view(B, 1, N, 1) - mus.append(mu.view(B, N, 1).unsqueeze(1)) - sigmas.append(sigma.view(B, N, 1).unsqueeze(1)) - preds.append(history_next) - assert not torch.isnan(history_next).any() - - preds = torch.concat(preds, dim=1) - mus = torch.concat(mus, dim=1) - sigmas = torch.concat(sigmas, dim=1) - reals = input_feat_full[:, -preds.shape[1]:, :, :] - - # 检查mus和sigmas中是否存在nan - assert not torch.isnan(mus).any(), "mus中存在nan" - assert not torch.isnan(sigmas).any(), "sigmas中存在nan" - - # denormalization - preds = preds * stdev + means - mus = mus * stdev + means - sigmas = sigmas * stdev + means - - return {"prediction": preds * mask, "target": reals * mask, "mus": mus, "sigmas": sigmas, "mask_prior": mask} diff --git a/baselines/DeepAR_M4/arch/distributions.py b/baselines/DeepAR_M4/arch/distributions.py deleted file mode 100644 index 0c84d512..00000000 --- a/baselines/DeepAR_M4/arch/distributions.py +++ /dev/null @@ -1,22 +0,0 @@ -import torch -import torch.nn as nn - - -class Gaussian(nn.Module): - - def __init__(self, hidden_size, output_size): - """ - Gaussian Likelihood Supports Continuous Data - Args: - input_size (int): hidden h_{i,t} column size - output_size (int): embedding size - """ - super(Gaussian, self).__init__() - self.mu_layer = nn.Linear(hidden_size, output_size) - self.sigma_layer = nn.Linear(hidden_size, output_size) - - def forward(self, h): - sigma_t = torch.log(1 + torch.exp(self.sigma_layer(h))) + 1e-6 - sigma_t = sigma_t.squeeze(0) - mu_t = self.mu_layer(h).squeeze(0) - return mu_t, sigma_t diff --git a/baselines/DeepAR_M4/loss/__init__.py b/baselines/DeepAR_M4/loss/__init__.py deleted file mode 100644 index 9b08b8a3..00000000 --- a/baselines/DeepAR_M4/loss/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .gaussian import gaussian_loss \ No newline at end of file diff --git a/baselines/DeepAR_M4/loss/gaussian.py b/baselines/DeepAR_M4/loss/gaussian.py deleted file mode 100644 index 9cf53631..00000000 --- a/baselines/DeepAR_M4/loss/gaussian.py +++ /dev/null @@ -1,36 +0,0 @@ -import torch -import numpy as np -from basicts.metrics import masked_mae - -def masked_mae_loss(prediction, target, pred_len, null_val = np.nan): - prediction = prediction[:, -pred_len:, :, :] - target = target[:, -pred_len:, :, :] - return masked_mae(prediction, target, null_val) - -def gaussian_loss(prediction, target, mus, sigmas, mask_prior, null_val = np.nan): - """Masked gaussian loss. Kindly note that the gaussian loss is calculated based on mu, sigma, and target. The prediction is sampled from N(mu, sigma), and is not used in the loss calculation (it will be used in the metrics calculation). - - Args: - prediction (torch.Tensor): prediction of model. [B, L, N, 1]. - target (torch.Tensor): ground truth. [B, L, N, 1]. - mus (torch.Tensor): the mean of gaussian distribution. [B, L, N, 1]. - sigmas (torch.Tensor): the std of gaussian distribution. [B, L, N, 1] - null_val (optional): null value. Defaults to np.nan. - """ - # mask - if np.isnan(null_val): - mask = ~torch.isnan(target) - else: - eps = 5e-5 - mask = ~torch.isclose(target, torch.tensor(null_val).expand_as(target).to(target.device), atol=eps, rtol=0.) - mask = mask.float() - mask /= torch.mean((mask)) - mask = torch.where(torch.isnan(mask), torch.zeros_like(mask), mask) - - distribution = torch.distributions.Normal(mus, sigmas) - likelihood = distribution.log_prob(target) - likelihood = likelihood * mask - likelihood = likelihood * mask_prior - assert not torch.isnan(likelihood).any(), "likelihood中存在nan" - loss_g = -torch.mean(likelihood) - return loss_g diff --git a/baselines/DeepAR_M4/runner/__init__.py b/baselines/DeepAR_M4/runner/__init__.py deleted file mode 100644 index 1e41b855..00000000 --- a/baselines/DeepAR_M4/runner/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .deepar_runner import DeepARRunner diff --git a/baselines/DeepAR_M4/runner/deepar_runner.py b/baselines/DeepAR_M4/runner/deepar_runner.py deleted file mode 100644 index ad5425f5..00000000 --- a/baselines/DeepAR_M4/runner/deepar_runner.py +++ /dev/null @@ -1,141 +0,0 @@ -from typing import Dict -import torch -from basicts.data.registry import SCALER_REGISTRY -from easytorch.utils.dist import master_only - -from basicts.runners.base_m4_runner import BaseM4Runner -from basicts.metrics import masked_mae -from basicts.utils import partial -from ..loss.gaussian import masked_mae_loss - -class DeepARRunner(BaseM4Runner): - def __init__(self, cfg: dict): - super().__init__(cfg) - self.forward_features = cfg["MODEL"].get("FORWARD_FEATURES", None) - self.target_features = cfg["MODEL"].get("TARGET_FEATURES", None) - self.output_seq_len = cfg["DATASET_OUTPUT_LEN"] - self.metrics = cfg.get("METRICS", {"loss": self.loss, "real_mae": partial(masked_mae_loss, pred_len=self.output_seq_len), "full_mae": masked_mae}) - - def select_input_features(self, data: torch.Tensor) -> torch.Tensor: - """Select input features and reshape data to fit the target model. - - Args: - data (torch.Tensor): input history data, shape [B, L, N, C]. - - Returns: - torch.Tensor: reshaped data - """ - - # select feature using self.forward_features - if self.forward_features is not None: - data = data[:, :, :, self.forward_features] - return data - - def select_target_features(self, data: torch.Tensor) -> torch.Tensor: - """Select target features and reshape data back to the BasicTS framework - - Args: - data (torch.Tensor): prediction of the model with arbitrary shape. - - Returns: - torch.Tensor: reshaped data with shape [B, L, N, C] - """ - - # select feature using self.target_features - data = data[:, :, :, self.target_features] - return data - - def rescale_data(self, input_data: Dict) -> Dict: - """Rescale data. - - Args: - data (Dict): Dict of data to be re-scaled. - - Returns: - Dict: Dict re-scaled data. - """ - - if self.if_rescale: - input_data["inputs"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["inputs"], **self.scaler["args"]) - input_data["prediction"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["prediction"], **self.scaler["args"]) - input_data["target"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["target"], **self.scaler["args"]) - if "mus" in input_data.keys(): - input_data["mus"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["mus"], **self.scaler["args"]) - if "sigmas" in input_data.keys(): - input_data["sigmas"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["sigmas"], **self.scaler["args"]) - return input_data - - def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: bool = True, **kwargs) -> tuple: - """Feed forward process for train, val, and test. Note that the outputs are NOT re-scaled. - - Args: - data (tuple): (future_data, history_data, future_mask, history_mask). - epoch (int, optional): epoch number. Defaults to None. - iter_num (int, optional): iteration number. Defaults to None. - train (bool, optional): if in the training process. Defaults to True. - - Returns: - tuple: (prediction, real_value) - """ - - # preprocess - future_data, history_data, future_mask, history_mask = data - history_data = self.to_running_device(history_data) # B, L, 1, C - future_data = self.to_running_device(future_data) # B, L, 1, C - history_mask = self.to_running_device(history_mask) # B, L, 1 - future_mask = self.to_running_device(future_mask) # B, L, 1 - - batch_size, length, num_nodes, _ = future_data.shape - - history_data = self.select_input_features(history_data) - future_data_4_dec = self.select_input_features(future_data) - - # model forward - model_return = self.model(history_data=history_data, future_data=future_data_4_dec, history_mask=history_mask, future_mask=future_mask, batch_seen=iter_num, epoch=epoch, train=train) - if isinstance(model_return, torch.Tensor): model_return = {"prediction": model_return * future_mask.unsqueeze(-1)} - if "inputs" not in model_return: model_return["inputs"] = self.select_target_features(history_data) - if "target" not in model_return: model_return["target"] = self.select_target_features(future_data * future_mask.unsqueeze(-1)) - return model_return - - @torch.no_grad() - @master_only - def test(self): - """Evaluate the model. - - Args: - train_epoch (int, optional): current epoch if in training process. - """ - - # TODO: fix OOM: especially when inputs, targets, and predictions are saved at the same time. - # test loop - prediction =[] - target = [] - inputs = [] - mus = [] - sigmas = [] - mask_priors = [] - for _, data in enumerate(self.test_data_loader): - forward_return = self.forward(data, epoch=None, iter_num=None, train=False) - if not self.if_evaluate_on_gpu: - forward_return["prediction"] = forward_return["prediction"].detach().cpu() - forward_return["target"] = forward_return["target"].detach().cpu() - forward_return["inputs"] = forward_return["inputs"].detach().cpu() - forward_return["mus"] = forward_return["mus"].detach().cpu() - forward_return["sigmas"] = forward_return["sigmas"].detach().cpu() - forward_return["mask_prior"] = forward_return["mask_prior"].detach().cpu() - prediction.append(forward_return["prediction"]) - target.append(forward_return["target"]) - inputs.append(forward_return["inputs"]) - mus.append(forward_return["mus"]) - sigmas.append(forward_return["sigmas"]) - mask_priors.append(forward_return["mask_prior"]) - prediction = torch.cat(prediction, dim=0) - target = torch.cat(target, dim=0) - inputs = torch.cat(inputs, dim=0) - mus = torch.cat(mus, dim=0) - sigmas = torch.cat(sigmas, dim=0) - mask_priors = torch.cat(mask_priors, dim=0) - # re-scale data - returns_all = self.rescale_data({"prediction": prediction[:, -self.output_seq_len:, :, :], "target": target[:, -self.output_seq_len:, :, :], "inputs": inputs, "mus": mus[:, -self.output_seq_len:, :, :], "sigmas": sigmas[:, -self.output_seq_len:, :, :], "mask_prior": mask_priors[:, -self.output_seq_len:, :, :]}) - # evaluate - self.save_prediction(returns_all) diff --git a/baselines/FEDformer/ETTh1.py b/baselines/FEDformer/ETTh1.py index ff69c80f..25abd8f9 100644 --- a/baselines/FEDformer/ETTh1.py +++ b/baselines/FEDformer/ETTh1.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import FEDformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ "d_model": 512, "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] "moving_avg": 24, # window size of moving average @@ -64,64 +53,99 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0001 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/ETTh2.py b/baselines/FEDformer/ETTh2.py index d55b4a3e..076b2a97 100644 --- a/baselines/FEDformer/ETTh2.py +++ b/baselines/FEDformer/ETTh2.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import FEDformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ "d_model": 512, "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] "moving_avg": 25, # window size of moving average @@ -64,13 +53,70 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { @@ -83,53 +129,30 @@ "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # - -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/ETTm1.py b/baselines/FEDformer/ETTm1.py index 2ea5c979..efdaf04d 100644 --- a/baselines/FEDformer/ETTm1.py +++ b/baselines/FEDformer/ETTm1.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import FEDformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 192 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ "d_model": 512, "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] "moving_avg": 24, # window size of moving average @@ -59,69 +48,104 @@ "cross_activation": "tanh", # mwt cross atention activation function tanh or softmax "activation": "gelu", "num_time_features": 4, # number of used time features - "time_of_day_size": 96, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0001 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/ETTm2.py b/baselines/FEDformer/ETTm2.py index ae9ab0e8..afeacf1b 100644 --- a/baselines/FEDformer/ETTm2.py +++ b/baselines/FEDformer/ETTm2.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import FEDformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ "d_model": 512, "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] "moving_avg": 24, # window size of moving average @@ -59,69 +48,104 @@ "cross_activation": "tanh", # mwt cross atention activation function tanh or softmax "activation": "gelu", "num_time_features": 4, # number of used time features - "time_of_day_size": 96, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0002 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/Electricity.py b/baselines/FEDformer/Electricity.py index 86abdf00..107a552d 100644 --- a/baselines/FEDformer/Electricity.py +++ b/baselines/FEDformer/Electricity.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import FEDformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity Consumption" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer NUM_NODES = 321 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ "d_model": 512, "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] "moving_avg": 24, # window size of moving average @@ -64,64 +53,99 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0001 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/ExchangeRate.py b/baselines/FEDformer/ExchangeRate.py index b0c28d0a..335849e0 100644 --- a/baselines/FEDformer/ExchangeRate.py +++ b/baselines/FEDformer/ExchangeRate.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import FEDformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer NUM_NODES = 8 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ "d_model": 512, "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] "moving_avg": 24, # window size of moving average @@ -59,69 +48,104 @@ "cross_activation": "tanh", # mwt cross atention activation function tanh or softmax "activation": "gelu", "num_time_features": 4, # number of used time features - "time_of_day_size": 96, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0001 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/PEMS04.py b/baselines/FEDformer/PEMS04.py deleted file mode 100644 index 155d1f73..00000000 --- a/baselines/FEDformer/PEMS04.py +++ /dev/null @@ -1,134 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import FEDformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer -NUM_NODES = 307 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ - "d_model": 512, - "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] - "moving_avg": 24, # window size of moving average - "n_heads": 8, - "e_layers": 2, # num of encoder layers - "d_layers": 1, # num of decoder layers - "d_ff": 2048, - "dropout": 0.05, - "output_attention": False, - "embed": "timeF", # [timeF, fixed, learned] - "mode_select": "random", # for FEDformer, there are two mode selection method, options: [random, low] - "modes": 64, # modes to be selected random 64 - "base": "legendre", # mwt base - "L": 3, # ignore level - "cross_activation": "tanh", # mwt cross atention activation function tanh or softmax - "activation": "gelu", - "num_time_features": 2, # number of used time features - "time_of_day_size": 288, - "day_of_week_size": 7 - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0005, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/FEDformer/PEMS04_LTSF.py b/baselines/FEDformer/PEMS04_LTSF.py new file mode 100644 index 00000000..18de037d --- /dev/null +++ b/baselines/FEDformer/PEMS04_LTSF.py @@ -0,0 +1,156 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import FEDformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 720 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer +NUM_NODES = 307 +MODEL_PARAM = { + "enc_in": NUM_NODES, # num nodes + "dec_in": NUM_NODES, + "c_out": NUM_NODES, + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ + "d_model": 512, + "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] + "moving_avg": 24, # window size of moving average + "n_heads": 8, + "e_layers": 2, # num of encoder layers + "d_layers": 1, # num of decoder layers + "d_ff": 2048, + "dropout": 0.05, + "output_attention": False, + "embed": "timeF", # [timeF, fixed, learned] + "mode_select": "random", # for FEDformer, there are two mode selection method, options: [random, low] + "modes": 64, # modes to be selected random 64 + "base": "legendre", # mwt base + "L": 3, # ignore level + "cross_activation": "tanh", # mwt cross atention activation function tanh or softmax + "activation": "gelu", + "num_time_features": 2, # number of used time features + "time_of_day_size": 288, + "day_of_week_size": 7 + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0005, + "weight_decay": 0.0005, +} +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 50], + "gamma": 0.5 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/PEMS08.py b/baselines/FEDformer/PEMS08.py deleted file mode 100644 index 3f661d15..00000000 --- a/baselines/FEDformer/PEMS08.py +++ /dev/null @@ -1,134 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import FEDformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer -NUM_NODES = 170 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ - "d_model": 512, - "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] - "moving_avg": 24, # window size of moving average - "n_heads": 8, - "e_layers": 2, # num of encoder layers - "d_layers": 1, # num of decoder layers - "d_ff": 2048, - "dropout": 0.05, - "output_attention": False, - "embed": "timeF", # [timeF, fixed, learned] - "mode_select": "random", # for FEDformer, there are two mode selection method, options: [random, low] - "modes": 64, # modes to be selected random 64 - "base": "legendre", # mwt base - "L": 3, # ignore level - "cross_activation": "tanh", # mwt cross atention activation function tanh or softmax - "activation": "gelu", - "num_time_features": 2, # number of used time features - "time_of_day_size": 288, - "day_of_week_size": 7 - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0005, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/FEDformer/PEMS08_LTSF.py b/baselines/FEDformer/PEMS08_LTSF.py new file mode 100644 index 00000000..3f4f47bf --- /dev/null +++ b/baselines/FEDformer/PEMS08_LTSF.py @@ -0,0 +1,156 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import FEDformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 720 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer +NUM_NODES = 170 +MODEL_PARAM = { + "enc_in": NUM_NODES, # num nodes + "dec_in": NUM_NODES, + "c_out": NUM_NODES, + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ + "d_model": 512, + "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] + "moving_avg": 24, # window size of moving average + "n_heads": 8, + "e_layers": 2, # num of encoder layers + "d_layers": 1, # num of decoder layers + "d_ff": 2048, + "dropout": 0.05, + "output_attention": False, + "embed": "timeF", # [timeF, fixed, learned] + "mode_select": "random", # for FEDformer, there are two mode selection method, options: [random, low] + "modes": 64, # modes to be selected random 64 + "base": "legendre", # mwt base + "L": 3, # ignore level + "cross_activation": "tanh", # mwt cross atention activation function tanh or softmax + "activation": "gelu", + "num_time_features": 2, # number of used time features + "time_of_day_size": 288, + "day_of_week_size": 7 + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0005, + "weight_decay": 0.0005, +} +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 50], + "gamma": 0.5 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/Weather.py b/baselines/FEDformer/Weather.py index d1f8fd9e..06e06d21 100644 --- a/baselines/FEDformer/Weather.py +++ b/baselines/FEDformer/Weather.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import FEDformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "FEDformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather Data" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "FEDformer" -CFG.MODEL.ARCH = FEDformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = FEDformer NUM_NODES = 21 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length\ "d_model": 512, "version": "Fourier", # for FEDformer, there are two versions to choose, options: [Fourier, Wavelets] "moving_avg": 24, # window size of moving average @@ -64,64 +53,99 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0001 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/FEDformer/arch/fedformer_arch.py b/baselines/FEDformer/arch/fedformer_arch.py index d9210ed7..fc509db1 100644 --- a/baselines/FEDformer/arch/fedformer_arch.py +++ b/baselines/FEDformer/arch/fedformer_arch.py @@ -28,7 +28,7 @@ def __init__(self, **model_args): self.pred_len = int(model_args["pred_len"]) self.output_attention = model_args["output_attention"] - + self.time_of_day_size = model_args.get("time_of_day_size", None) self.day_of_week_size = model_args.get("day_of_week_size", None) self.day_of_month_size = model_args.get("day_of_month_size", None) diff --git a/baselines/FEDformer/arch/fourier_correlation.py b/baselines/FEDformer/arch/fourier_correlation.py index 5e19d97f..7d570117 100644 --- a/baselines/FEDformer/arch/fourier_correlation.py +++ b/baselines/FEDformer/arch/fourier_correlation.py @@ -31,7 +31,7 @@ def __init__(self, in_channels, out_channels, seq_len, modes=0, mode_select_meth print('fourier enhanced block used!') """ 1D Fourier block. It performs representation learning on frequency domain, - it does FFT, linear transform, and Inverse FFT. + it does FFT, linear transform, and Inverse FFT. """ # get modes on frequency domain self.index = get_frequency_modes( @@ -71,7 +71,7 @@ def __init__(self, in_channels, out_channels, seq_len_q, seq_len_kv, modes=64, m super(FourierCrossAttention, self).__init__() print(' fourier enhanced cross attention used!') """ - 1D Fourier Cross Attention layer. It does FFT, linear transform, attention mechanism and Inverse FFT. + 1D Fourier Cross Attention layer. It does FFT, linear transform, attention mechanism and Inverse FFT. """ self.activation = activation self.in_channels = in_channels diff --git a/baselines/FEDformer/arch/utils.py b/baselines/FEDformer/arch/utils.py index abad3835..b72718dc 100644 --- a/baselines/FEDformer/arch/utils.py +++ b/baselines/FEDformer/arch/utils.py @@ -23,7 +23,7 @@ def phi_(phi_c, x, lb = 0, ub = 1): def get_phi_psi(k, base): - + x = Symbol('x') phi_coeff = np.zeros((k,k)) phi_2x_coeff = np.zeros((k,k)) @@ -33,7 +33,7 @@ def get_phi_psi(k, base): phi_coeff[ki,:ki+1] = np.flip(np.sqrt(2*ki+1) * np.array(coeff_).astype(np.float64)) coeff_ = Poly(legendre(ki, 4*x-1), x).all_coeffs() phi_2x_coeff[ki,:ki+1] = np.flip(np.sqrt(2) * np.sqrt(2*ki+1) * np.array(coeff_).astype(np.float64)) - + psi1_coeff = np.zeros((k, k)) psi2_coeff = np.zeros((k, k)) for ki in range(k): @@ -73,7 +73,7 @@ def get_phi_psi(k, base): phi = [np.poly1d(np.flip(phi_coeff[i,:])) for i in range(k)] psi1 = [np.poly1d(np.flip(psi1_coeff[i,:])) for i in range(k)] psi2 = [np.poly1d(np.flip(psi2_coeff[i,:])) for i in range(k)] - + elif base == 'chebyshev': for ki in range(k): if ki == 0: @@ -84,9 +84,9 @@ def get_phi_psi(k, base): phi_coeff[ki,:ki+1] = np.flip(2/np.sqrt(np.pi) * np.array(coeff_).astype(np.float64)) coeff_ = Poly(chebyshevt(ki, 4*x-1), x).all_coeffs() phi_2x_coeff[ki,:ki+1] = np.flip(np.sqrt(2) * 2 / np.sqrt(np.pi) * np.array(coeff_).astype(np.float64)) - + phi = [partial(phi_, phi_coeff[i,:]) for i in range(k)] - + x = Symbol('x') kUse = 2*k roots = Poly(chebyshevt(kUse, 2*x-1)).all_roots() @@ -94,7 +94,7 @@ def get_phi_psi(k, base): # x_m[x_m==0.5] = 0.5 + 1e-8 # add small noise to avoid the case of 0.5 belonging to both phi(2x) and phi(2x-1) # not needed for our purpose here, we use even k always to avoid wm = np.pi / kUse / 2 - + psi1_coeff = np.zeros((k, k)) psi2_coeff = np.zeros((k, k)) @@ -109,7 +109,7 @@ def get_phi_psi(k, base): psi2_coeff[ki,:] -= proj_ * phi_coeff[i,:] for j in range(ki): - proj_ = (wm * psi1[j](x_m) * np.sqrt(2) * phi[ki](2*x_m)).sum() + proj_ = (wm * psi1[j](x_m) * np.sqrt(2) * phi[ki](2*x_m)).sum() psi1_coeff[ki,:] -= proj_ * psi1_coeff[j,:] psi2_coeff[ki,:] -= proj_ * psi2_coeff[j,:] @@ -127,19 +127,19 @@ def get_phi_psi(k, base): psi1[ki] = partial(phi_, psi1_coeff[ki,:], lb = 0, ub = 0.5+1e-16) psi2[ki] = partial(phi_, psi2_coeff[ki,:], lb = 0.5+1e-16, ub = 1) - + return phi, psi1, psi2 def get_filter(base, k): - + def psi(psi1, psi2, i, inp): mask = (inp<=0.5) * 1.0 return psi1[i](inp) * mask + psi2[i](inp) * (1-mask) - + if base not in ['legendre', 'chebyshev']: raise Exception('Base not supported') - + x = Symbol('x') H0 = np.zeros((k,k)) H1 = np.zeros((k,k)) @@ -152,17 +152,17 @@ def psi(psi1, psi2, i, inp): roots = Poly(legendre(k, 2*x-1)).all_roots() x_m = np.array([rt.evalf(20) for rt in roots]).astype(np.float64) wm = 1/k/legendreDer(k,2*x_m-1)/eval_legendre(k-1,2*x_m-1) - + for ki in range(k): for kpi in range(k): H0[ki, kpi] = 1/np.sqrt(2) * (wm * phi[ki](x_m/2) * phi[kpi](x_m)).sum() G0[ki, kpi] = 1/np.sqrt(2) * (wm * psi(psi1, psi2, ki, x_m/2) * phi[kpi](x_m)).sum() H1[ki, kpi] = 1/np.sqrt(2) * (wm * phi[ki]((x_m+1)/2) * phi[kpi](x_m)).sum() G1[ki, kpi] = 1/np.sqrt(2) * (wm * psi(psi1, psi2, ki, (x_m+1)/2) * phi[kpi](x_m)).sum() - + PHI0 = np.eye(k) PHI1 = np.eye(k) - + elif base == 'chebyshev': x = Symbol('x') kUse = 2*k @@ -181,7 +181,7 @@ def psi(psi1, psi2, i, inp): PHI0[ki, kpi] = (wm * phi[ki](2*x_m) * phi[kpi](2*x_m)).sum() * 2 PHI1[ki, kpi] = (wm * phi[ki](2*x_m-1) * phi[kpi](2*x_m-1)).sum() * 2 - + PHI0[np.abs(PHI0)<1e-8] = 0 PHI1[np.abs(PHI1)<1e-8] = 0 @@ -189,57 +189,57 @@ def psi(psi1, psi2, i, inp): H1[np.abs(H1)<1e-8] = 0 G0[np.abs(G0)<1e-8] = 0 G1[np.abs(G1)<1e-8] = 0 - + return H0, H1, G0, G1, PHI0, PHI1 def train(model, train_loader, optimizer, epoch, device, verbose = 0, lossFn = None, lr_schedule=None, post_proc = lambda args: args): - + if lossFn is None: lossFn = nn.MSELoss() model.train() - + total_loss = 0. for batch_idx, (data, target) in enumerate(train_loader): - + bs = len(data) data, target = data.to(device), target.to(device) optimizer.zero_grad() - + output = model(data) - + target = post_proc(target) output = post_proc(output) loss = lossFn(output.view(bs, -1), target.view(bs, -1)) - + loss.backward() optimizer.step() total_loss += loss.sum().item() if lr_schedule is not None: lr_schedule.step() - + if verbose>0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, batch_idx * len(data), len(train_loader.dataset), 100. * batch_idx / len(train_loader), loss.item())) - + return total_loss/len(train_loader.dataset) def test(model, test_loader, device, verbose=0, lossFn=None, post_proc = lambda args: args): - + model.eval() if lossFn is None: lossFn = nn.MSELoss() - - + + total_loss = 0. predictions = [] - + with torch.no_grad(): for data, target in test_loader: bs = len(data) @@ -247,10 +247,10 @@ def test(model, test_loader, device, verbose=0, lossFn=None, data, target = data.to(device), target.to(device) output = model(data) output = post_proc(output) - + loss = lossFn(output.view(bs, -1), target.view(bs, -1)) total_loss += loss.sum().item() - + return total_loss/len(test_loader.dataset) @@ -346,7 +346,7 @@ def decode(self, x): x = (x - self.b)/self.a x = x.view(s) return x - + class LpLoss(object): def __init__(self, d=2, p=2, size_average=True, reduction=True): super(LpLoss, self).__init__() diff --git a/baselines/FEDformer/run.sh b/baselines/FEDformer/run.sh deleted file mode 100644 index 3b656702..00000000 --- a/baselines/FEDformer/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/FEDformer/ETTh1.py --gpus '2' -python experiments/train.py -c baselines/FEDformer/ETTh2.py --gpus '2' -python experiments/train.py -c baselines/FEDformer/ETTm1.py --gpus '2' -python experiments/train.py -c baselines/FEDformer/ETTm2.py --gpus '2' -python experiments/train.py -c baselines/FEDformer/Electricity.py --gpus '2' -python experiments/train.py -c baselines/FEDformer/ExchangeRate.py --gpus '2' -python experiments/train.py -c baselines/FEDformer/Weather.py --gpus '2' -python experiments/train.py -c baselines/FEDformer/PEMS04.py --gpus '2' -python experiments/train.py -c baselines/FEDformer/PEMS08.py --gpus '2' diff --git a/baselines/GMSDR/METR-LA.py b/baselines/GMSDR/METR-LA.py deleted file mode 100644 index 468a5203..00000000 --- a/baselines/GMSDR/METR-LA.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import GMSDR - -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GMSDR model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GMSDR" -CFG.MODEL.ARCH = GMSDR -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + - "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { - "horizon": 12, - "input_dim": 1, - "max_diffusion_step": 1, - "num_nodes": 207, - "num_rnn_layers": 2, - "output_dim": 1, - "rnn_units": 64, - "seq_len": 12, - "adj_mx": [torch.tensor(i) for i in adj_mx], - "pre_k": 6, - "pre_v": 1, -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.SETUP_GRAPH = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0015, - "eps": 1e-3 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [30, 50, 70, 80], - "gamma": 0.2 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/GMSDR/PEMS-BAY.py b/baselines/GMSDR/PEMS-BAY.py deleted file mode 100644 index 107750f6..00000000 --- a/baselines/GMSDR/PEMS-BAY.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import GMSDR - -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GMSDR model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GMSDR" -CFG.MODEL.ARCH = GMSDR -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + - "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { - "horizon": 12, - "input_dim": 1, - "max_diffusion_step": 1, - "num_nodes": 325, - "num_rnn_layers": 2, - "output_dim": 1, - "rnn_units": 64, - "seq_len": 12, - "adj_mx": [torch.tensor(i) for i in adj_mx], - "pre_k": 6, - "pre_v": 1, -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.SETUP_GRAPH = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0015, - "eps": 1e-3 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [30, 50, 70, 80], - "gamma": 0.2 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/GMSDR/PEMS03.py b/baselines/GMSDR/PEMS03.py deleted file mode 100644 index 2e21be03..00000000 --- a/baselines/GMSDR/PEMS03.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import GMSDR - -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GMSDR model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GMSDR" -CFG.MODEL.ARCH = GMSDR -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + - "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { - "horizon": 12, - "input_dim": 1, - "max_diffusion_step": 1, - "num_nodes": 358, - "num_rnn_layers": 2, - "output_dim": 1, - "rnn_units": 64, - "seq_len": 12, - "adj_mx": [torch.tensor(i) for i in adj_mx], - "pre_k": 6, - "pre_v": 1, -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.SETUP_GRAPH = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0015, - "eps": 1e-3 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [30, 50, 70, 80], - "gamma": 0.2 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/GMSDR/PEMS04.py b/baselines/GMSDR/PEMS04.py deleted file mode 100644 index 43f2a010..00000000 --- a/baselines/GMSDR/PEMS04.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import GMSDR - -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GMSDR model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GMSDR" -CFG.MODEL.ARCH = GMSDR -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + - "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { - "horizon": 12, - "input_dim": 1, - "max_diffusion_step": 1, - "num_nodes": 307, - "num_rnn_layers": 2, - "output_dim": 1, - "rnn_units": 64, - "seq_len": 12, - "adj_mx": [torch.tensor(i) for i in adj_mx], - "pre_k": 6, - "pre_v": 1, -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.SETUP_GRAPH = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0015, - "eps": 1e-3 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [30, 50, 70, 80], - "gamma": 0.2 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/GMSDR/PEMS07.py b/baselines/GMSDR/PEMS07.py deleted file mode 100644 index 2249225a..00000000 --- a/baselines/GMSDR/PEMS07.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import GMSDR - -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GMSDR model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GMSDR" -CFG.MODEL.ARCH = GMSDR -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + - "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { - "horizon": 12, - "input_dim": 1, - "max_diffusion_step": 1, - "num_nodes": 883, - "num_rnn_layers": 2, - "output_dim": 1, - "rnn_units": 64, - "seq_len": 12, - "adj_mx": [torch.tensor(i) for i in adj_mx], - "pre_k": 6, - "pre_v": 1, -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.SETUP_GRAPH = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0015, - "eps": 1e-3 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [30, 50, 70, 80], - "gamma": 0.2 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/GMSDR/PEMS08.py b/baselines/GMSDR/PEMS08.py deleted file mode 100644 index edd34231..00000000 --- a/baselines/GMSDR/PEMS08.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import GMSDR - -CFG = EasyDict() - -# DCRNN does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GMSDR model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GMSDR" -CFG.MODEL.ARCH = GMSDR -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + - "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { - "horizon": 12, - "input_dim": 1, - "max_diffusion_step": 1, - "num_nodes": 170, - "num_rnn_layers": 2, - "output_dim": 1, - "rnn_units": 64, - "seq_len": 12, - "adj_mx": [torch.tensor(i) for i in adj_mx], - "pre_k": 6, - "pre_v": 1, -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.SETUP_GRAPH = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0015, - "eps": 1e-3 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [30, 50, 70, 80], - "gamma": 0.2 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/GMSDR/arch/__init__.py b/baselines/GMSDR/arch/__init__.py deleted file mode 100644 index a56c811c..00000000 --- a/baselines/GMSDR/arch/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .gmsdr_arch import GMSDR - -__all__ = ['GMSDR'] \ No newline at end of file diff --git a/baselines/GMSDR/arch/gmsdr_arch.py b/baselines/GMSDR/arch/gmsdr_arch.py deleted file mode 100644 index 65936375..00000000 --- a/baselines/GMSDR/arch/gmsdr_arch.py +++ /dev/null @@ -1,164 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn - -from .gmsdr_cell import GMSDRCell - -def count_parameters(model): - return sum(p.numel() for p in model.parameters() if p.requires_grad) - - -class Seq2SeqAttrs: - def __init__(self, adj_mx, **model_kwargs): - self.adj_mx = adj_mx - self.max_diffusion_step = int(model_kwargs.get('max_diffusion_step', 2)) - self.num_nodes = int(model_kwargs.get('num_nodes', 1)) - self.num_rnn_layers = int(model_kwargs.get('num_rnn_layers', 1)) - self.rnn_units = int(model_kwargs.get('rnn_units')) - self.hidden_state_size = self.num_nodes * self.rnn_units - self.pre_k = int(model_kwargs.get('pre_k', 1)) - self.pre_v = int(model_kwargs.get('pre_v', 1)) - self.input_dim = int(model_kwargs.get('input_dim', 1)) - self.output_dim = int(model_kwargs.get('output_dim', 1)) - - -class EncoderModel(nn.Module, Seq2SeqAttrs): - def __init__(self, adj_mx, **model_kwargs): - nn.Module.__init__(self) - Seq2SeqAttrs.__init__(self, adj_mx, **model_kwargs) - self.input_dim = int(model_kwargs.get('input_dim', 1)) - self.seq_len = int(model_kwargs.get('seq_len')) # for the encoder - self.mlp = nn.Linear(self.input_dim, self.rnn_units) - self.gmsdr_layers = nn.ModuleList( - [GMSDRCell(self.rnn_units, self.input_dim, adj_mx, self.max_diffusion_step, self.num_nodes, self.pre_k, self.pre_v) for _ in range(self.num_rnn_layers)]) - - def forward(self, inputs, hx_k): - """ - Encoder forward pass. - - :param inputs: shape (batch_size, self.num_nodes * self.input_dim) - :param hx_k: (num_layers, batch_size, pre_k, self.num_nodes, self.rnn_units) - optional, zeros if not provided - :return: output: # shape (batch_size, self.hidden_state_size) - hx_k # shape (num_layers, batch_size, pre_k, self.num_nodes, self.rnn_units) - (lower indices mean lower layers) - """ - hx_ks = [] - batch = inputs.shape[0] - x = inputs.reshape(batch, self.num_nodes, self.input_dim) - output = self.mlp(x).view(batch, -1) - for layer_num, dcgru_layer in enumerate(self.gmsdr_layers): - next_hidden_state, new_hx_k = dcgru_layer(output, hx_k[layer_num]) - hx_ks.append(new_hx_k) - output = next_hidden_state - return output, torch.stack(hx_ks) - - -class DecoderModel(nn.Module, Seq2SeqAttrs): - def __init__(self, adj_mx, **model_kwargs): - nn.Module.__init__(self) - Seq2SeqAttrs.__init__(self, adj_mx, **model_kwargs) - self.output_dim = int(model_kwargs.get('output_dim', 1)) - self.horizon = int(model_kwargs.get('horizon', 12)) # for the decoder - self.projection_layer = nn.Linear(self.rnn_units, self.output_dim) - self.gmsdr_layers = nn.ModuleList( - [GMSDRCell(self.rnn_units, self.rnn_units, adj_mx, self.max_diffusion_step, self.num_nodes, self.pre_k, self.pre_v - ) for _ in range(self.num_rnn_layers)]) - - def forward(self, inputs, hx_k): - """ - Decoder forward pass. - - :param inputs: shape (batch_size, self.num_nodes * self.output_dim) - :param hx_k: (num_layers, batch_size, pre_k, num_nodes, rnn_units) - optional, zeros if not provided - :return: output: # shape (batch_size, self.num_nodes * self.output_dim) - hidden_state # shape (num_layers, batch_size, self.hidden_state_size) - (lower indices mean lower layers) - """ - hx_ks = [] - output = inputs - for layer_num, dcgru_layer in enumerate(self.gmsdr_layers): - next_hidden_state, new_hx_k = dcgru_layer(output, hx_k[layer_num]) - hx_ks.append(new_hx_k) - output = next_hidden_state - - projected = self.projection_layer(output.view(-1, self.rnn_units)) - output = projected.view(-1, self.num_nodes * self.output_dim) - - return output, torch.stack(hx_ks) - - -class GMSDR(nn.Module, Seq2SeqAttrs): - """ - Paper: MSDR: Multi-Step Dependency Relation Networks for Spatial Temporal Forecasting - Link: https://dl.acm.org/doi/abs/10.1145/3534678.3539397 - Reference Code: https://github.com/dcliu99/MSDR - """ - def __init__(self, adj_mx, **model_kwargs): - super().__init__() - Seq2SeqAttrs.__init__(self, adj_mx, **model_kwargs) - self.encoder_model = EncoderModel(adj_mx, **model_kwargs) - self.decoder_model = DecoderModel(adj_mx, **model_kwargs) - self.out = nn.Linear(self.rnn_units, self.decoder_model.output_dim) - - def encoder(self, inputs): - """ - encoder forward pass on t time steps - :param inputs: shape (seq_len, batch_size, num_sensor * input_dim) - :return: hx_k: (num_layers, batch_size, pre_k, num_sensor, rnn_units) - """ - hx_k = torch.zeros(self.num_rnn_layers, inputs.shape[1], self.pre_k, self.num_nodes, self.rnn_units, - device=inputs.device) - outputs = [] - for t in range(self.encoder_model.seq_len): - output, hx_k = self.encoder_model(inputs[t], hx_k) - outputs.append(output) - return torch.stack(outputs), hx_k - - def decoder(self, inputs, hx_k, labels=None, batches_seen=None): - """ - Decoder forward pass - :param inputs: (seq_len, batch_size, num_sensor * rnn_units) - :param hx_k: (num_layers, batch_size, pre_k, num_sensor, rnn_units) - :param labels: (self.horizon, batch_size, self.num_nodes * self.output_dim) [optional, not exist for inference] - :param batches_seen: global step [optional, not exist for inference] - :return: output: (self.horizon, batch_size, self.num_nodes * self.output_dim) - """ - decoder_hx_k = hx_k - decoder_input = inputs - - outputs = [] - for t in range(self.decoder_model.horizon): - decoder_output, decoder_hx_k = self.decoder_model(decoder_input[t], - decoder_hx_k) - outputs.append(decoder_output) - outputs = torch.stack(outputs) - return outputs - - def Loss_l2(self): - base_params = dict(self.named_parameters()) - loss_l2 = 0 - count = 0 - for key,value in base_params.items(): - if 'bias' not in key: - loss_l2 += torch.sum(value**2) - count += value.nelement() - return loss_l2 - - def forward(self, history_data, future_data=None, batch_seen=None, **kwargs): - """ - seq2seq forward pass - :param inputs: shape (seq_len, batch_size, num_sensor * input_dim) - :param labels: shape (horizon, batch_size, num_sensor * output) - :param batches_seen: batches seen till now - :return: output: (self.horizon, batch_size, self.num_nodes * self.output_dim) - """ - inputs = history_data.transpose(0,1).reshape(history_data.shape[1],history_data.shape[0],-1) - encoder_outputs, hx_k = self.encoder(inputs) - outputs = self.decoder(encoder_outputs, hx_k, future_data, batches_seen=batch_seen) - if batch_seen == 0: - print( - "Total trainable parameters {}".format(count_parameters(self)) - ) - return outputs.transpose(0,1).reshape(history_data.shape[0],history_data.shape[1],history_data.shape[2],-1) \ No newline at end of file diff --git a/baselines/GMSDR/arch/gmsdr_cell.py b/baselines/GMSDR/arch/gmsdr_cell.py deleted file mode 100644 index 0926a495..00000000 --- a/baselines/GMSDR/arch/gmsdr_cell.py +++ /dev/null @@ -1,184 +0,0 @@ -import numpy as np -import torch -from torch import nn, Tensor -import torch.nn.functional as F - - -class Seq2SeqAttrs: - def __init__(self, adj_mx, **model_kwargs): - self.adj_mx = adj_mx - self.max_diffusion_step = int(model_kwargs.get('max_diffusion_step', 2)) - self.num_nodes = int(model_kwargs.get('num_nodes', 1)) - self.num_rnn_layers = int(model_kwargs.get('num_rnn_layers', 1)) - self.rnn_units = int(model_kwargs.get('rnn_units')) - self.hidden_state_size = self.num_nodes * self.rnn_units - self.pre_k = int(model_kwargs.get('pre_k', 1)) - self.pre_v = int(model_kwargs.get('pre_v', 1)) - self.input_dim = int(model_kwargs.get('input_dim', 2)) - self.output_dim = int(model_kwargs.get('output_dim', 1)) - - -class LayerParams: - def __init__(self, rnn_network: torch.nn.Module, layer_type: str): - self._rnn_network = rnn_network - self._params_dict = {} - self._biases_dict = {} - self._type = layer_type - - def get_weights(self, shape): - if shape not in self._params_dict: - nn_param = torch.nn.Parameter(torch.empty(*shape)) - torch.nn.init.xavier_normal_(nn_param) - self._params_dict[shape] = nn_param - self._rnn_network.register_parameter('{}_weight_{}'.format(self._type, str(shape)), - nn_param) - return self._params_dict[shape] - - def get_biases(self, length, bias_start=0.0): - if length not in self._biases_dict: - biases = torch.nn.Parameter(torch.empty(length)) - torch.nn.init.constant_(biases, bias_start) - self._biases_dict[length] = biases - self._rnn_network.register_parameter('{}_biases_{}'.format(self._type, str(length)), - biases) - - return self._biases_dict[length] - - -class GMSDRCell(torch.nn.Module): - def __init__(self, num_units, input_dim, adj_mx, max_diffusion_step, num_nodes, pre_k, pre_v, nonlinearity='tanh', - use_gc_for_ru=True): - """ - - :param num_units: - :param adj_mx: - :param max_diffusion_step: - :param num_nodes: - :param nonlinearity: - :param filter_type: "laplacian", "random_walk", "dual_random_walk". - :param use_gc_for_ru: whether to use Graph convolution to calculate the reset and update gates. - """ - - super().__init__() - self._activation = torch.tanh if nonlinearity == 'tanh' else torch.relu - # support other nonlinearities up here? - self._num_nodes = num_nodes - self._num_units = num_units - self._max_diffusion_step = max_diffusion_step - self._supports = [] - self._use_gc_for_ru = use_gc_for_ru - self.pre_k = pre_k - self.pre_v = pre_v - self.input_dim = input_dim - self.nodevec1 = nn.Parameter(torch.randn(num_nodes, 10), requires_grad=True) - self.nodevec2 = nn.Parameter(torch.randn(10, num_nodes), requires_grad=True) - - # supports = [] - # if filter_type == "laplacian": - # supports.append(utils.calculate_scaled_laplacian(adj_mx, lambda_max=None)) - # elif filter_type == "random_walk": - # supports.append(utils.calculate_random_walk_matrix(adj_mx).T) - # elif filter_type == "dual_random_walk": - # supports.append(utils.calculate_random_walk_matrix(adj_mx).T) - # supports.append(utils.calculate_random_walk_matrix(adj_mx.T).T) - # else: - # supports.append(utils.calculate_scaled_laplacian(adj_mx)) - # for support in supports: - # self._supports.append(support) - # # self._supports.append(self._build_sparse_matrix(support)) - self._supports = adj_mx - - self._fc_params = LayerParams(self, 'fc') - self._gconv_params = LayerParams(self, 'gconv') - self.W = nn.Parameter(torch.zeros(self._num_units, self._num_units), requires_grad=True) - self.b = nn.Parameter(torch.zeros(num_nodes, self._num_units), requires_grad=True) - self.R = nn.Parameter(torch.zeros(pre_k, num_nodes, self._num_units), requires_grad=True) - self.attlinear = nn.Linear(num_nodes * self._num_units, 1) - - # @staticmethod - # def _build_sparse_matrix(L): - # L = L.tocoo() - # indices = np.column_stack((L.row, L.col)) - # # this is to ensure row-major ordering to equal torch.sparse.sparse_reorder(L) - # indices = indices[np.lexsort((indices[:, 0], indices[:, 1]))] - # L = torch.sparse_coo_tensor(indices.T, L.data, L.shape, device=device) - # return L - - def forward(self, inputs, hx_k): - """Gated recurrent unit (GRU) with Graph Convolution. - :param inputs: (B, num_nodes * input_dim) - :param hx_k: (B, pre_k, num_nodes, rnn_units) - - :return - - Output: A `2-D` tensor with shape `(B, num_nodes * rnn_units)`. - """ - bs, k, n, d = hx_k.shape - preH = hx_k[:, -1:] - for i in range(1, self.pre_v): - preH = torch.cat([preH, hx_k[:, -(i + 1):-i]], -1) - preH = preH.reshape(bs, n, d * self.pre_v) - self.adp = F.softmax(F.relu(torch.mm(self.nodevec1, self.nodevec2)), dim=1) - convInput = F.leaky_relu_(self._gconv(inputs, preH, d, bias_start=1.0)) - new_states = hx_k + self.R.unsqueeze(0) - output = torch.matmul(convInput, self.W) + self.b.unsqueeze(0) + self.attention(new_states) - output = output.unsqueeze(1) - x = hx_k[:, 1:k] - hx_k = torch.cat([x, output], dim=1) - output = output.reshape(bs, n * d) - return output, hx_k - - @staticmethod - def _concat(x, x_): - x_ = x_.unsqueeze(0) - return torch.cat([x, x_], dim=0) - - def _gconv(self, inputs, state, output_size, bias_start=0.0): - # input / state: (batch_size, num_nodes, input_dim/state_dim) - batch_size = inputs.shape[0] - inputs = torch.reshape(inputs, (batch_size, self._num_nodes, -1)) - state = torch.reshape(state, (batch_size, self._num_nodes, -1)) - inputs_and_state = torch.cat([inputs, state], dim=2) - input_size = inputs_and_state.size(2) - - x = inputs_and_state - x0 = x.permute(1, 2, 0) # (num_nodes, total_arg_size, batch_size) - x0 = torch.reshape(x0, shape=[self._num_nodes, input_size * batch_size]) - x = torch.unsqueeze(x0, 0) - if self._max_diffusion_step == 0: - pass - else: - for support in self._supports: - x1 = torch.mm(support.to(x0.device), x0) - x = self._concat(x, x1) - - for k in range(2, self._max_diffusion_step + 1): - x2 = 2 * torch.mm(support.to(x0.device), x1) - x0 - x = self._concat(x, x2) - x1, x0 = x2, x1 - x1 = self.adp.mm(x0) - x = self._concat(x, x1) - for k in range(2, self._max_diffusion_step + 1): - x2 = self.adp.mm(x1) - x0 - x = self._concat(x, x2) - x1, x0 = x2, x1 - num_matrices = (len(self._supports) + 1) * self._max_diffusion_step + 1 - # num_matrices = (len(self._supports)) * self._max_diffusion_step + 1 - x = torch.reshape(x, shape=[num_matrices, self._num_nodes, input_size, batch_size]) - x = x.permute(3, 1, 2, 0) # (batch_size, num_nodes, input_size, order) - x = torch.reshape(x, shape=[batch_size * self._num_nodes, input_size * num_matrices]) - - weights = self._gconv_params.get_weights((input_size * num_matrices, output_size)).to(x.device) - x = torch.matmul(x, weights) # (batch_size * self._num_nodes, output_size) - - biases = self._gconv_params.get_biases(output_size, bias_start).to(x.device) - x += biases - # Reshape res back to 2D: (batch_size, num_node, state_dim) -> (batch_size, num_node * state_dim) - return torch.reshape(x, [batch_size, self._num_nodes, output_size]) - - def attention(self, inputs: Tensor): - bs, k, n, d = inputs.size() - x = inputs.reshape(bs, k, -1) - out = self.attlinear(x) - weight = F.softmax(out, dim=1) - outputs = (x * weight).sum(dim=1).reshape(bs, n, d) - return outputs \ No newline at end of file diff --git a/baselines/GMSDR/run.sh b/baselines/GMSDR/run.sh deleted file mode 100644 index 2f46b383..00000000 --- a/baselines/GMSDR/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/GMSDR/METR-LA.py --gpus '4' -python experiments/train.py -c baselines/GMSDR/PEMS-BAY.py --gpus '4' -python experiments/train.py -c baselines/GMSDR/PEMS03.py --gpus '4' -python experiments/train.py -c baselines/GMSDR/PEMS04.py --gpus '4' -python experiments/train.py -c baselines/GMSDR/PEMS07.py --gpus '4' -python experiments/train.py -c baselines/GMSDR/PEMS08.py --gpus '4' \ No newline at end of file diff --git a/baselines/GTS/METR-LA.py b/baselines/GTS/METR-LA.py index 5a704ecb..2ef88276 100644 --- a/baselines/GTS/METR-LA.py +++ b/baselines/GTS/METR-LA.py @@ -1,50 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils.serialization import load_pkl from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_dataset_desc, \ + load_adj, load_dataset_data from .arch import GTS -from .loss import gts_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GTS +node_feats = load_dataset_data(DATA_NAME) +train_len = int(node_feats.shape[0] * TRAIN_VAL_TEST_RATIO[0]) +node_feats = node_feats[:train_len, ..., 0] -# GTS does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GTS model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GTS" -CFG.MODEL.ARCH = GTS -node_feats_full = load_pkl("datasets/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["processed_data"][..., 0] -train_index_list = load_pkl("datasets/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["train"] -node_feats = node_feats_full[:train_index_list[-1][-1], ...] -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "filter_type": "dual_random_walk", "horizon": 12, @@ -57,79 +43,117 @@ "rnn_units": 64, "seq_len": 12, "use_curriculum_learning": True, - "dim_fc": 383664, + "dim_fc": 383552, "node_feats": node_feats, "temp": 0.5, "k": 10 } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gts_loss +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.005, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 40], "gamma": 0.1 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GTS/PEMS-BAY.py b/baselines/GTS/PEMS-BAY.py index eb3d424c..32a1caf4 100644 --- a/baselines/GTS/PEMS-BAY.py +++ b/baselines/GTS/PEMS-BAY.py @@ -1,50 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils.serialization import load_pkl from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_dataset_desc, \ + load_dataset_data from .arch import GTS -from .loss import gts_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GTS +node_feats = load_dataset_data(DATA_NAME) +train_len = int(node_feats.shape[0] * TRAIN_VAL_TEST_RATIO[0]) +node_feats = node_feats[:train_len, ..., 0] -# GTS does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GTS model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GTS" -CFG.MODEL.ARCH = GTS -node_feats_full = load_pkl("datasets/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["processed_data"][..., 0] -train_index_list = load_pkl("datasets/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["train"] -node_feats = node_feats_full[:train_index_list[-1][-1], ...] -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "filter_type": "dual_random_walk", "horizon": 12, @@ -57,79 +43,117 @@ "rnn_units": 128, "seq_len": 12, "use_curriculum_learning": True, - "dim_fc": 583520, + "dim_fc": 583408, "node_feats": node_feats, "temp": 0.5, "k": 30 } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gts_loss +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 30], "gamma": 0.1 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GTS/PEMS03.py b/baselines/GTS/PEMS03.py index bdb1aef6..3ed23a9a 100644 --- a/baselines/GTS/PEMS03.py +++ b/baselines/GTS/PEMS03.py @@ -1,50 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils.serialization import load_pkl from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_dataset_desc, \ + load_dataset_data from .arch import GTS -from .loss import gts_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GTS +node_feats = load_dataset_data(DATA_NAME) +train_len = int(node_feats.shape[0] * TRAIN_VAL_TEST_RATIO[0]) +node_feats = node_feats[:train_len, ..., 0] -# GTS does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GTS model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GTS" -CFG.MODEL.ARCH = GTS -node_feats_full = load_pkl("datasets/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["processed_data"][..., 0] -train_index_list = load_pkl("datasets/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["train"] -node_feats = node_feats_full[:train_index_list[-1][-1], ...] -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "filter_type": "dual_random_walk", "horizon": 12, @@ -57,79 +43,117 @@ "rnn_units": 64, "seq_len": 12, "use_curriculum_learning": True, - "dim_fc": 251456, + "dim_fc": 251296, "node_feats": node_feats, "temp": 0.5, "k": 30 } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gts_loss +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 30], "gamma": 0.1 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GTS/PEMS04.py b/baselines/GTS/PEMS04.py index cbe27243..76193215 100644 --- a/baselines/GTS/PEMS04.py +++ b/baselines/GTS/PEMS04.py @@ -1,50 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils.serialization import load_pkl from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_dataset_desc, \ + load_dataset_data from .arch import GTS -from .loss import gts_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GTS +node_feats = load_dataset_data(DATA_NAME) +train_len = int(node_feats.shape[0] * TRAIN_VAL_TEST_RATIO[0]) +node_feats = node_feats[:train_len, ..., 0] -# GTS does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GTS model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GTS" -CFG.MODEL.ARCH = GTS -node_feats_full = load_pkl("datasets/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["processed_data"][..., 0] -train_index_list = load_pkl("datasets/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["train"] -node_feats = node_feats_full[:train_index_list[-1][-1], ...] -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "filter_type": "dual_random_walk", "horizon": 12, @@ -57,79 +43,117 @@ "rnn_units": 64, "seq_len": 12, "use_curriculum_learning": True, - "dim_fc": 162976, + "dim_fc": 162832, "node_feats": node_feats, "temp": 0.5, "k": 30 } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gts_loss +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 30], "gamma": 0.1 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GTS/PEMS07.py b/baselines/GTS/PEMS07.py index 1f5d7ad7..3b4cd985 100644 --- a/baselines/GTS/PEMS07.py +++ b/baselines/GTS/PEMS07.py @@ -1,50 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils.serialization import load_pkl from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_dataset_desc, \ + load_dataset_data from .arch import GTS -from .loss import gts_loss -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GTS +node_feats = load_dataset_data(DATA_NAME) +train_len = int(node_feats.shape[0] * TRAIN_VAL_TEST_RATIO[0]) +node_feats = node_feats[:train_len, ..., 0] -# GTS does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) - -# ================= general ================= # -CFG.DESCRIPTION = "GTS model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GTS" -CFG.MODEL.ARCH = GTS -node_feats_full = load_pkl("datasets/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["processed_data"][..., 0] -train_index_list = load_pkl("datasets/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["train"] -node_feats = node_feats_full[:train_index_list[-1][-1], ...] -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "filter_type": "dual_random_walk", "horizon": 12, @@ -57,79 +43,117 @@ "rnn_units": 64, "seq_len": 12, "use_curriculum_learning": True, - "dim_fc": 270816, + "dim_fc": 270656, "node_feats": node_feats, "temp": 0.5, "k": 30 } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True + +############################## Metrics Configuration ############################## -# ================= optim ================= # +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gts_loss +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 30], "gamma": 0.1 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GTS/PEMS08.py b/baselines/GTS/PEMS08.py index 0e8ebf29..dc0f09ee 100644 --- a/baselines/GTS/PEMS08.py +++ b/baselines/GTS/PEMS08.py @@ -1,50 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch +import random from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils.serialization import load_pkl from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_dataset_desc, \ + load_dataset_data from .arch import GTS -from .loss import gts_loss - -CFG = EasyDict() -# GTS does not allow to load parameters since it creates parameters in the first iteration -resume = False -if not resume: - import random - _ = random.randint(-1e6, 1e6) +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GTS +node_feats = load_dataset_data(DATA_NAME) +train_len = int(node_feats.shape[0] * TRAIN_VAL_TEST_RATIO[0]) +node_feats = node_feats[:train_len, ..., 0] -# ================= general ================= # -CFG.DESCRIPTION = "GTS model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG._ = _ -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GTS" -CFG.MODEL.ARCH = GTS -node_feats_full = load_pkl("datasets/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["processed_data"][..., 0] -train_index_list = load_pkl("datasets/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN, CFG.get("RESCALE", True)))["train"] -node_feats = node_feats_full[:train_index_list[-1][-1], ...] -CFG.MODEL.PARAM = { +MODEL_PARAM = { "cl_decay_steps": 2000, "filter_type": "dual_random_walk", "horizon": 12, @@ -57,79 +43,117 @@ "rnn_units": 64, "seq_len": 12, "use_curriculum_learning": True, - "dim_fc": 171280, + "dim_fc": 171120, "node_feats": node_feats, "temp": 0.5, "k": 30 } -CFG.MODEL.SETUP_GRAPH = True +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +# DCRNN does not allow to load parameters since it creates parameters in the first iteration +CFG._ = random.randint(-1e6, 1e6) + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] +CFG.MODEL.SETUP_GRAPH = True -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = gts_loss +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "eps": 1e-3 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 30], "gamma": 0.1 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GTS/arch/gts_arch.py b/baselines/GTS/arch/gts_arch.py index 6d0a3f96..8be92acb 100644 --- a/baselines/GTS/arch/gts_arch.py +++ b/baselines/GTS/arch/gts_arch.py @@ -243,7 +243,7 @@ def forward(self, history_data, future_data=None, batch_seen=None, epoch=None, * :param batch_seen: batches seen till now :return: output: (self.horizon, batch_size, self.num_nodes * self.output_dim) """ - + # reshape data batch_size, length, num_nodes, channels = history_data.shape history_data = history_data.reshape(batch_size, length, num_nodes * channels) # [B, L, N*C] @@ -253,7 +253,7 @@ def forward(self, history_data, future_data=None, batch_seen=None, epoch=None, * batch_size, length, num_nodes, channels = future_data.shape future_data = future_data.reshape(batch_size, length, num_nodes * channels) # [B, L, N*C] future_data = future_data.transpose(0, 1) # [L, B, N*C] - + # GTS inputs = history_data labels = future_data diff --git a/baselines/GTS/run.sh b/baselines/GTS/run.sh deleted file mode 100644 index a989d902..00000000 --- a/baselines/GTS/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/GTS/METR-LA.py --gpus '1' -python experiments/train.py -c baselines/GTS/PEMS-BAY.py --gpus '1' -python experiments/train.py -c baselines/GTS/PEMS03.py --gpus '1' -python experiments/train.py -c baselines/GTS/PEMS04.py --gpus '1' -python experiments/train.py -c baselines/GTS/PEMS07.py --gpus '1' -python experiments/train.py -c baselines/GTS/PEMS08.py --gpus '1' diff --git a/baselines/GWNet/METR-LA.py b/baselines/GWNet/METR-LA.py index 1d5939e7..09c70607 100644 --- a/baselines/GWNet/METR-LA.py +++ b/baselines/GWNet/METR-LA.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import GraphWaveNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GWNet" -CFG.MODEL.ARCH = GraphWaveNet -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GraphWaveNet +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes": 207, "supports": [torch.tensor(i) for i in adj_mx], "dropout": 0.3, @@ -54,73 +43,109 @@ "blocks": 4, "layers": 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GWNet/PEMS-BAY.py b/baselines/GWNet/PEMS-BAY.py index 3bbb0ba1..c2f71fae 100644 --- a/baselines/GWNet/PEMS-BAY.py +++ b/baselines/GWNet/PEMS-BAY.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import GraphWaveNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GWNet" -CFG.MODEL.ARCH = GraphWaveNet -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GraphWaveNet +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes": 325, "supports": [torch.tensor(i) for i in adj_mx], "dropout": 0.3, @@ -54,73 +43,109 @@ "blocks": 4, "layers": 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GWNet/PEMS03.py b/baselines/GWNet/PEMS03.py index d43c10e2..fefbd0ee 100644 --- a/baselines/GWNet/PEMS03.py +++ b/baselines/GWNet/PEMS03.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import GraphWaveNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GWNet" -CFG.MODEL.ARCH = GraphWaveNet -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GraphWaveNet +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes": 358, "supports": [torch.tensor(i) for i in adj_mx], "dropout": 0.3, @@ -54,73 +43,109 @@ "blocks": 4, "layers": 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GWNet/PEMS04.py b/baselines/GWNet/PEMS04.py index 309642cc..8525e87d 100644 --- a/baselines/GWNet/PEMS04.py +++ b/baselines/GWNet/PEMS04.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import GraphWaveNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GWNet" -CFG.MODEL.ARCH = GraphWaveNet -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GraphWaveNet +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes": 307, "supports": [torch.tensor(i) for i in adj_mx], "dropout": 0.3, @@ -54,73 +43,109 @@ "blocks": 4, "layers": 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GWNet/PEMS07.py b/baselines/GWNet/PEMS07.py index acd8312a..9aa808e9 100644 --- a/baselines/GWNet/PEMS07.py +++ b/baselines/GWNet/PEMS07.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import GraphWaveNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GWNet" -CFG.MODEL.ARCH = GraphWaveNet -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GraphWaveNet +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes": 883, "supports": [torch.tensor(i) for i in adj_mx], "dropout": 0.3, @@ -54,73 +43,109 @@ "blocks": 4, "layers": 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# validating data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GWNet/PEMS08.py b/baselines/GWNet/PEMS08.py index 05a3ceb1..d9dbbcd3 100644 --- a/baselines/GWNet/PEMS08.py +++ b/baselines/GWNet/PEMS08.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import GraphWaveNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "GWNet" -CFG.MODEL.ARCH = GraphWaveNet -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = GraphWaveNet +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes": 170, "supports": [torch.tensor(i) for i in adj_mx], "dropout": 0.3, @@ -54,73 +43,109 @@ "blocks": 4, "layers": 2 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/GWNet/run.sh b/baselines/GWNet/run.sh deleted file mode 100644 index e2e08381..00000000 --- a/baselines/GWNet/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/GWNet/METR-LA.py --gpus '1' -python experiments/train.py -c baselines/GWNet/PEMS-BAY.py --gpus '1' -python experiments/train.py -c baselines/GWNet/PEMS03.py --gpus '1' -python experiments/train.py -c baselines/GWNet/PEMS04.py --gpus '1' -python experiments/train.py -c baselines/GWNet/PEMS07.py --gpus '1' -python experiments/train.py -c baselines/GWNet/PEMS08.py --gpus '1' diff --git a/baselines/HI/HI_METR-LA_in96_out96.py b/baselines/HI/HI_METR-LA_in96_out96.py deleted file mode 100644 index 3aa48ffb..00000000 --- a/baselines/HI/HI_METR-LA_in96_out96.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import NoBPRunner - -from .arch import HINetwork - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "HI model configuration" -CFG.RUNNER = NoBPRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "HINetwork" -CFG.MODEL.ARCH = HINetwork -CFG.MODEL.PARAM = { - "input_length": CFG.DATASET_INPUT_LEN, - "output_length": CFG.DATASET_OUTPUT_LEN -} -CFG.MODEL.FORWARD_FEATURES = [0, 1] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.005, - "weight_decay": 1.0e-5, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [50], - "gamma": 0.1 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 1 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.HORIZONS = [12, 24, 48, 96] -CFG.TEST.INTERVAL = 1 -# validating data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False diff --git a/baselines/HI/METR-LA.py b/baselines/HI/METR-LA.py new file mode 100644 index 00000000..b2e4daa3 --- /dev/null +++ b/baselines/HI/METR-LA.py @@ -0,0 +1,138 @@ +import os +import sys +import torch +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import NoBPRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj + +from .arch import HINetwork + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = HINetwork +adj_mx, _ = load_adj("datasets/" + DATA_NAME + + "/adj_mx.pkl", "doubletransition") +MODEL_PARAM = { + "input_length": INPUT_LEN, + "output_length": OUTPUT_LEN +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = NoBPRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.002, + "weight_decay": 0.0001, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 50], + "gamma": 0.5 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/HI/arch/hi_arch.py b/baselines/HI/arch/hi_arch.py index cfc240e1..21459a58 100644 --- a/baselines/HI/arch/hi_arch.py +++ b/baselines/HI/arch/hi_arch.py @@ -10,7 +10,7 @@ class HINetwork(nn.Module): Paper: Historical Inertia: A Neglected but Powerful Baseline for Long Sequence Time-series Forecasting Link: https://arxiv.org/abs/2103.16349 """ - + def __init__(self, input_length: int, output_length: int, channel=None, reverse=False): """ Init HI. diff --git a/baselines/Informer/ETTh1.py b/baselines/Informer/ETTh1.py index 2b94a53a..858b4217 100644 --- a/baselines/Informer/ETTh1.py +++ b/baselines/Informer/ETTh1.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Informer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ "factor": 3, # probsparse attn factor "d_model": 512, "n_heads": 8, @@ -62,74 +51,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/ETTh2.py b/baselines/Informer/ETTh2.py index f8fba75f..2b5bf185 100644 --- a/baselines/Informer/ETTh2.py +++ b/baselines/Informer/ETTh2.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Informer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ "factor": 5, # probsparse attn factor "d_model": 512, "n_heads": 8, @@ -62,74 +51,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], + "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/ETTm1.py b/baselines/Informer/ETTm1.py index b8cf20a3..79bdc4d8 100644 --- a/baselines/Informer/ETTm1.py +++ b/baselines/Informer/ETTm1.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Informer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ "factor": 3, # probsparse attn factor "d_model": 512, "n_heads": 8, @@ -57,79 +46,112 @@ "distil": True, # whether to use distilling in encoder, using this argument means not using distilling "mix": True, # use mix attention in generative decoder "num_time_features": 4, # number of used time features [time_of_day, day_of_week, day_of_month, day_of_year] - "time_of_day_size": 24, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/ETTm2.py b/baselines/Informer/ETTm2.py index 7b8e7dc5..985e4b7d 100644 --- a/baselines/Informer/ETTm2.py +++ b/baselines/Informer/ETTm2.py @@ -1,47 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Informer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ "factor": 3, # probsparse attn factor "d_model": 512, "n_heads": 8, @@ -57,79 +47,112 @@ "distil": True, # whether to use distilling in encoder, using this argument means not using distilling "mix": True, # use mix attention in generative decoder "num_time_features": 4, # number of used time features [time_of_day, day_of_week, day_of_month, day_of_year] - "time_of_day_size": 24, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/Electricity.py b/baselines/Informer/Electricity.py index 7053127a..0caf522c 100644 --- a/baselines/Informer/Electricity.py +++ b/baselines/Informer/Electricity.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Informer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity Consumption" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer NUM_NODES = 321 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ "factor": 3, # probsparse attn factor "d_model": 512, "n_heads": 8, @@ -61,64 +50,97 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, + "weight_decay": 0.0005, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/ExchangeRate.py b/baselines/Informer/ExchangeRate.py index de6bae8f..e6644ff0 100644 --- a/baselines/Informer/ExchangeRate.py +++ b/baselines/Informer/ExchangeRate.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Informer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer NUM_NODES = 8 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ "factor": 3, # probsparse attn factor "d_model": 512, "n_heads": 8, @@ -62,74 +51,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/METR-LA.py b/baselines/Informer/METR-LA.py deleted file mode 100644 index 11e00f2b..00000000 --- a/baselines/Informer/METR-LA.py +++ /dev/null @@ -1,136 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import Informer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 96 # not tested yet -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer -NUM_NODES = 207 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ - "factor": 3, # probsparse attn factor - "d_model": 512, - "n_heads": 8, - "e_layers": 2, # num of encoder layers - # "e_layers": [4, 2, 1], # for InformerStack - "d_layers": 1, # num of decoder layers - "d_ff": 2048, - "dropout": 0.05, - "attn": 'prob', # attention used in encoder, options:[prob, full] - "embed": "timeF", # [timeF, fixed, learned] - "activation": "gelu", - "output_attention": False, - "distil": True, # whether to use distilling in encoder, using this argument means not using distilling - "mix": True, # use mix attention in generative decoder - "num_time_features": 2, # number of used time features [time_of_day, day_of_week, day_of_month, day_of_year] - "time_of_day_size": 288, - "day_of_week_size": 7, - "day_of_month_size": 31, - "day_of_year_size": 366 - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Informer/PEMS-BAY.py b/baselines/Informer/PEMS-BAY.py deleted file mode 100644 index a45af96e..00000000 --- a/baselines/Informer/PEMS-BAY.py +++ /dev/null @@ -1,136 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import Informer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 96 # not tested yet -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer -NUM_NODES = 325 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ - "factor": 3, # probsparse attn factor - "d_model": 512, - "n_heads": 8, - "e_layers": 2, # num of encoder layers - # "e_layers": [4, 2, 1], # for InformerStack - "d_layers": 1, # num of decoder layers - "d_ff": 2048, - "dropout": 0.05, - "attn": 'prob', # attention used in encoder, options:[prob, full] - "embed": "timeF", # [timeF, fixed, learned] - "activation": "gelu", - "output_attention": False, - "distil": True, # whether to use distilling in encoder, using this argument means not using distilling - "mix": True, # use mix attention in generative decoder - "num_time_features": 2, # number of used time features [time_of_day, day_of_week, day_of_month, day_of_year] - "time_of_day_size": 288, - "day_of_week_size": 7, - "day_of_month_size": 31, - "day_of_year_size": 366 - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Informer/PEMS04.py b/baselines/Informer/PEMS04.py deleted file mode 100644 index 9905a4cb..00000000 --- a/baselines/Informer/PEMS04.py +++ /dev/null @@ -1,136 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import Informer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer -NUM_NODES = 307 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ - "factor": 3, # probsparse attn factor - "d_model": 512, - "n_heads": 8, - "e_layers": 2, # num of encoder layers - # "e_layers": [4, 2, 1], # for InformerStack - "d_layers": 1, # num of decoder layers - "d_ff": 2048, - "dropout": 0.05, - "attn": 'prob', # attention used in encoder, options:[prob, full] - "embed": "timeF", # [timeF, fixed, learned] - "activation": "gelu", - "output_attention": False, - "distil": True, # whether to use distilling in encoder, using this argument means not using distilling - "mix": True, # use mix attention in generative decoder - "num_time_features": 2, # number of used time features [time_of_day, day_of_week, day_of_month, day_of_year] - "time_of_day_size": 288, - "day_of_week_size": 7, - "day_of_month_size": 31, - "day_of_year_size": 366 - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Informer/PEMS04_LTSF.py b/baselines/Informer/PEMS04_LTSF.py new file mode 100644 index 00000000..d54aa976 --- /dev/null +++ b/baselines/Informer/PEMS04_LTSF.py @@ -0,0 +1,160 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Informer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 96 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer +NUM_NODES = 307 +MODEL_PARAM = { + "enc_in": NUM_NODES, # num nodes + "dec_in": NUM_NODES, + "c_out": NUM_NODES, + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ + "factor": 3, # probsparse attn factor + "d_model": 512, + "n_heads": 8, + "e_layers": 2, # num of encoder layers + # "e_layers": [4, 2, 1], # for InformerStack + "d_layers": 1, # num of decoder layers + "d_ff": 2048, + "dropout": 0.05, + "attn": 'prob', # attention used in encoder, options:[prob, full] + "embed": "timeF", # [timeF, fixed, learned] + "activation": "gelu", + "output_attention": False, + "distil": True, # whether to use distilling in encoder, using this argument means not using distilling + "mix": True, # use mix attention in generative decoder + "num_time_features": 2, # number of used time features [time_of_day, day_of_week, day_of_month, day_of_year] + "time_of_day_size": 288, + "day_of_week_size": 7, + "day_of_month_size": 31, + "day_of_year_size": 366 + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.002, + "weight_decay": 0.0005, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25, 50], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/PEMS08.py b/baselines/Informer/PEMS08.py deleted file mode 100644 index ee8d7ce3..00000000 --- a/baselines/Informer/PEMS08.py +++ /dev/null @@ -1,136 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import Informer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 96 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer -NUM_NODES = 170 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ - "factor": 3, # probsparse attn factor - "d_model": 512, - "n_heads": 8, - "e_layers": 2, # num of encoder layers - # "e_layers": [4, 2, 1], # for InformerStack - "d_layers": 1, # num of decoder layers - "d_ff": 2048, - "dropout": 0.05, - "attn": 'prob', # attention used in encoder, options:[prob, full] - "embed": "timeF", # [timeF, fixed, learned] - "activation": "gelu", - "output_attention": False, - "distil": True, # whether to use distilling in encoder, using this argument means not using distilling - "mix": True, # use mix attention in generative decoder - "num_time_features": 2, # number of used time features [time_of_day, day_of_week, day_of_month, day_of_year] - "time_of_day_size": 288, - "day_of_week_size": 7, - "day_of_month_size": 31, - "day_of_year_size": 366 - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Informer/PEMS08_LTSF.py b/baselines/Informer/PEMS08_LTSF.py new file mode 100644 index 00000000..cce2a7e5 --- /dev/null +++ b/baselines/Informer/PEMS08_LTSF.py @@ -0,0 +1,160 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Informer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 96 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer +NUM_NODES = 170 +MODEL_PARAM = { + "enc_in": NUM_NODES, # num nodes + "dec_in": NUM_NODES, + "c_out": NUM_NODES, + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ + "factor": 3, # probsparse attn factor + "d_model": 512, + "n_heads": 8, + "e_layers": 2, # num of encoder layers + # "e_layers": [4, 2, 1], # for InformerStack + "d_layers": 1, # num of decoder layers + "d_ff": 2048, + "dropout": 0.05, + "attn": 'prob', # attention used in encoder, options:[prob, full] + "embed": "timeF", # [timeF, fixed, learned] + "activation": "gelu", + "output_attention": False, + "distil": True, # whether to use distilling in encoder, using this argument means not using distilling + "mix": True, # use mix attention in generative decoder + "num_time_features": 2, # number of used time features [time_of_day, day_of_week, day_of_month, day_of_year] + "time_of_day_size": 288, + "day_of_week_size": 7, + "day_of_month_size": 31, + "day_of_year_size": 366 + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.002, + "weight_decay": 0.0005, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25, 50], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/Weather.py b/baselines/Informer/Weather.py index fb26d8bb..ead04e90 100644 --- a/baselines/Informer/Weather.py +++ b/baselines/Informer/Weather.py @@ -1,47 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Informer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Informer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 192 # the best in {96, 192, 336, 720} -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Informer" -CFG.MODEL.ARCH = Informer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Informer NUM_NODES = 21 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "label_len": CFG.DATASET_INPUT_LEN/2, # start token length used in decoder - "out_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length\ + "seq_len": INPUT_LEN, # input sequence length + "label_len": INPUT_LEN/2, # start token length used in decoder + "out_len": OUTPUT_LEN, # prediction sequence length\ "factor": 3, # probsparse attn factor "d_model": 512, "n_heads": 8, @@ -62,74 +51,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], + "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Informer/arch/informer_arch.py b/baselines/Informer/arch/informer_arch.py index 60cbb53b..d0368c64 100644 --- a/baselines/Informer/arch/informer_arch.py +++ b/baselines/Informer/arch/informer_arch.py @@ -77,7 +77,7 @@ def __init__(self, enc_in, dec_in, c_out, seq_len, label_len, out_len, # self.end_conv1 = nn.Conv1d(in_channels=label_len+out_len, out_channels=out_len, kernel_size=1, bias=True) # self.end_conv2 = nn.Conv1d(in_channels=d_model, out_channels=c_out, kernel_size=1, bias=True) self.projection = nn.Linear(d_model, c_out, bias=True) - + def forward_xformer(self, x_enc: torch.Tensor, x_mark_enc: torch.Tensor, x_dec: torch.Tensor, x_mark_dec: torch.Tensor, enc_self_mask: torch.Tensor=None, dec_self_mask: torch.Tensor=None, dec_enc_mask: torch.Tensor=None) -> torch.Tensor: """Feed forward of Informer. Kindly note that `enc_self_mask`, `dec_self_mask`, and `dec_enc_mask` are not actually used in Informer. @@ -101,9 +101,9 @@ def forward_xformer(self, x_enc: torch.Tensor, x_mark_enc: torch.Tensor, x_dec: dec_out = self.dec_embedding(x_dec, x_mark_dec) dec_out = self.decoder(dec_out, enc_out, x_mask=dec_self_mask, cross_mask=dec_enc_mask) dec_out = self.projection(dec_out) - + return dec_out[:, -self.pred_len:, :].unsqueeze(-1) # [B, L, N, C] - + def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool, **kwargs) -> torch.Tensor: """ @@ -179,7 +179,7 @@ def __init__(self, enc_in, dec_in, c_out, seq_len, label_len, out_len, # self.end_conv1 = nn.Conv1d(in_channels=label_len+out_len, out_channels=out_len, kernel_size=1, bias=True) # self.end_conv2 = nn.Conv1d(in_channels=d_model, out_channels=c_out, kernel_size=1, bias=True) self.projection = nn.Linear(d_model, c_out, bias=True) - + def forward_xformer(self, x_enc: torch.Tensor, x_mark_enc: torch.Tensor, x_dec: torch.Tensor, x_mark_dec: torch.Tensor, enc_self_mask: torch.Tensor=None, dec_self_mask: torch.Tensor=None, dec_enc_mask: torch.Tensor=None) -> torch.Tensor: """Feed forward of Informer. Kindly note that `enc_self_mask`, `dec_self_mask`, and `dec_enc_mask` are not actually used in Informer. diff --git a/baselines/Informer/arch/masking.py b/baselines/Informer/arch/masking.py index 7fd479e0..8ac5942f 100644 --- a/baselines/Informer/arch/masking.py +++ b/baselines/Informer/arch/masking.py @@ -18,7 +18,7 @@ def __init__(self, B, H, L, index, scores, device="cpu"): torch.arange(H)[None, :, None], index, :].to(device) self._mask = indicator.view(scores.shape).to(device) - + @property def mask(self): return self._mask \ No newline at end of file diff --git a/baselines/Informer/run.sh b/baselines/Informer/run.sh deleted file mode 100644 index db4bd1fa..00000000 --- a/baselines/Informer/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/Informer/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/Informer/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/Informer/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/Informer/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/Informer/Electricity.py --gpus '0' -python experiments/train.py -c baselines/Informer/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/Informer/Weather.py --gpus '0' -python experiments/train.py -c baselines/Informer/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/Informer/PEMS08.py --gpus '0' diff --git a/baselines/LSTM/CA.py b/baselines/LSTM/CA.py deleted file mode 100644 index a041ee43..00000000 --- a/baselines/LSTM/CA.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import LSTM - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "CA" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "LSTM" -CFG.MODEL.ARCH = LSTM -CFG.MODEL.PARAM = { - # input_dim, embed_dim, hidden_dim, end_dim, num_layer, dropout, horizon - "input_dim": 2, - "embed_dim": 32, - "hidden_dim": 64, - "end_dim": 512, - "num_layer": 2, - "dropout": 0.1, - "horizon": 12 - } -CFG.MODEL.FORWARD_FEATURES = [0, 1] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/LSTM/GBA.py b/baselines/LSTM/GBA.py deleted file mode 100644 index 7a002c06..00000000 --- a/baselines/LSTM/GBA.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import LSTM - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "GBA" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "LSTM" -CFG.MODEL.ARCH = LSTM -CFG.MODEL.PARAM = { - # input_dim, embed_dim, hidden_dim, end_dim, num_layer, dropout, horizon - "input_dim": 2, - "embed_dim": 32, - "hidden_dim": 64, - "end_dim": 512, - "num_layer": 2, - "dropout": 0.1, - "horizon": 12 - } -CFG.MODEL.FORWARD_FEATURES = [0, 1] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/LSTM/GLA.py b/baselines/LSTM/GLA.py deleted file mode 100644 index 7ba651b9..00000000 --- a/baselines/LSTM/GLA.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import LSTM - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "GLA" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "LSTM" -CFG.MODEL.ARCH = LSTM -CFG.MODEL.PARAM = { - # input_dim, embed_dim, hidden_dim, end_dim, num_layer, dropout, horizon - "input_dim": 2, - "embed_dim": 32, - "hidden_dim": 64, - "end_dim": 512, - "num_layer": 2, - "dropout": 0.1, - "horizon": 12 - } -CFG.MODEL.FORWARD_FEATURES = [0, 1] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/LSTM/SD.py b/baselines/LSTM/SD.py deleted file mode 100644 index 4d5690b0..00000000 --- a/baselines/LSTM/SD.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj - -from .arch import LSTM - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Graph WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "SD" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "LSTM" -CFG.MODEL.ARCH = LSTM -CFG.MODEL.PARAM = { - # input_dim, embed_dim, hidden_dim, end_dim, num_layer, dropout, horizon - "input_dim": 2, - "embed_dim": 32, - "hidden_dim": 64, - "end_dim": 512, - "num_layer": 2, - "dropout": 0.1, - "horizon": 12 - } -CFG.MODEL.FORWARD_FEATURES = [0, 1] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/LSTM/arch/__init__.py b/baselines/LSTM/arch/__init__.py deleted file mode 100644 index 77096196..00000000 --- a/baselines/LSTM/arch/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .lstm_arch import LSTM \ No newline at end of file diff --git a/baselines/LSTM/arch/lstm_arch.py b/baselines/LSTM/arch/lstm_arch.py deleted file mode 100644 index 456e21d4..00000000 --- a/baselines/LSTM/arch/lstm_arch.py +++ /dev/null @@ -1,49 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F - -class LSTM(nn.Module): - def __init__(self, input_dim, embed_dim, hidden_dim, end_dim, num_layer, dropout, horizon): - """Init LSTM. - - Args: - input_dim (int): number of input features. - embed_dim (int): dimension of the input embedding layer (a linear layer). - hidden_dim (int): hidden size in LSTM. - end_dim (int): hidden dimension of the output linear layer. - num_layer (int): number of layers in LSTM. - dropout (float): dropout rate. - horizon (int): number of time steps to be predicted. - """ - super(LSTM, self).__init__() - self.start_conv = nn.Conv2d(in_channels=input_dim, - out_channels=embed_dim, - kernel_size=(1,1)) - - self.lstm = nn.LSTM(input_size=embed_dim, hidden_size=hidden_dim, num_layers=num_layer, batch_first=True, dropout=dropout) - - self.end_linear1 = nn.Linear(hidden_dim, end_dim) - self.end_linear2 = nn.Linear(end_dim, horizon) - - def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool, **kwargs) -> torch.Tensor: - """Feedforward function of LSTM. - - Args: - history_data (torch.Tensor): shape [B, L, N, C] - - Returns: - torch.Tensor: [B, L, N, 1] - """ - x = history_data.transpose(1, 3) - b, c, n, l = x.shape - - x = x.transpose(1,2).reshape(b*n, c, 1, l) - x = self.start_conv(x).squeeze().transpose(1, 2) - - out, _ = self.lstm(x) - x = out[:, -1, :] - - x = F.relu(self.end_linear1(x)) - x = self.end_linear2(x) - x = x.reshape(b, n, l, 1).transpose(1, 2) - return x diff --git a/baselines/LightGBM/README.md b/baselines/LightGBM/README.md deleted file mode 100644 index 01f25e8b..00000000 --- a/baselines/LightGBM/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Requirements: - -``` -pip install lightgbm -``` diff --git a/baselines/LightGBM/Weather.py b/baselines/LightGBM/Weather.py deleted file mode 100644 index a7931ec2..00000000 --- a/baselines/LightGBM/Weather.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys -sys.path.append(os.path.abspath(__file__ + "/..")) - -# from evaluate_ar import evaluate -from evaluate import evaluate - -import numpy as np - -# construct configs -dataset_name = "Weather" -input_len = 336 -output_len = 336 -gpu_num = 1 -null_val = np.nan -train_data_dir = "datasets/" + dataset_name -rescale = True -batch_size = 128 # only used for collecting data -project_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -# print(evaluate(project_dir, train_data_dir, input_len, output_len, rescale, null_val, batch_size, patch_len=1)) -print(evaluate(project_dir, train_data_dir, input_len, output_len, rescale, null_val, batch_size)) diff --git a/baselines/LightGBM/evaluate.py b/baselines/LightGBM/evaluate.py deleted file mode 100644 index 73cdc87c..00000000 --- a/baselines/LightGBM/evaluate.py +++ /dev/null @@ -1,80 +0,0 @@ -import torch -import lightgbm as lgb -import os -import sys -sys.path.append("/workspace/S22/BasicTS") -from basicts.utils import load_pkl -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mae, masked_rmse, masked_mape, masked_wape -from basicts.data import SCALER_REGISTRY - - -def evaluate(project_dir, train_data_dir, input_len, output_len, rescale, null_val, batch_size): - - # construct dataset - data_file_path = project_dir + "/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale) - index_file_path = project_dir + "/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale) - - train_set = TimeSeriesForecastingDataset(data_file_path, index_file_path, mode="train") - train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=True) - - valid_set = TimeSeriesForecastingDataset(data_file_path, index_file_path, mode="valid") - valid_loader = torch.utils.data.DataLoader(valid_set, batch_size=batch_size, shuffle=False) - - test_set = TimeSeriesForecastingDataset(data_file_path, index_file_path, mode="test") - test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size, shuffle=False) - - # training & validation - Xs_train = [] - Ys_train = [] - Xs_valid = [] - Ys_valid = [] - Xs_test = [] - Ys_test = [] - - for i, (target, data) in enumerate(train_loader): - B, L, N, C = data.shape - data = data.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - target = target.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - Xs_train.append(data) - Ys_train.append(target) - - for i, (target, data) in enumerate(valid_loader): - B, L, N, C = data.shape - data = data.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - target = target.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - Xs_valid.append(data) - Ys_valid.append(target) - - for i, (target, data) in enumerate(test_loader): - B, L, N, C = data.shape - data = data.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - target = target.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - Xs_test.append(data) - Ys_test.append(target) - - Xs_train = torch.cat(Xs_train, dim=0).numpy() - Ys_train = torch.cat(Ys_train, dim=0).numpy() - Xs_valid = torch.cat(Xs_valid, dim=0).numpy() - Ys_valid = torch.cat(Ys_valid, dim=0).numpy() - Xs_test = torch.cat(Xs_test, dim=0).numpy() - Ys_test = torch.cat(Ys_test, dim=0).numpy() - - # direct forecasting - from sklearn.multioutput import MultiOutputRegressor - model = MultiOutputRegressor(lgb.LGBMRegressor(), n_jobs = -1) - model.fit(Xs_train, Ys_train) - # inference - preds_test = model.predict(Xs_test) - print(preds_test.shape) - # rescale - scaler = load_pkl(project_dir + "/{0}/scaler_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale)) - preds_test = torch.Tensor(preds_test).view(-1, N, output_len).transpose(1, 2).unsqueeze(-1) - Ys_test = torch.Tensor(Ys_test).view(-1, N, output_len).transpose(1, 2).unsqueeze(-1) - prediction = SCALER_REGISTRY.get(scaler["func"])(preds_test, **scaler["args"]) - real_value = SCALER_REGISTRY.get(scaler["func"])(Ys_test, **scaler["args"]) - # print results - print("MAE: ", masked_mae(prediction, real_value, null_val).item()) - print("RMSE: ", masked_rmse(prediction, real_value, null_val).item()) - print("MAPE: {:.2f}%".format(masked_mape(prediction, real_value, null_val) * 100)) - print("WAPE: {:.2f}%".format(masked_wape(prediction, real_value, null_val) * 100)) diff --git a/baselines/LightGBM/evaluate_ar.py b/baselines/LightGBM/evaluate_ar.py deleted file mode 100644 index 8042a847..00000000 --- a/baselines/LightGBM/evaluate_ar.py +++ /dev/null @@ -1,96 +0,0 @@ -import torch -import lightgbm as lgb -import os -import sys -sys.path.append("/workspace/S22/BasicTS") -import numpy as np -from tqdm import tqdm -from basicts.utils import load_pkl -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mae, masked_rmse, masked_mape, masked_wape -from basicts.data import SCALER_REGISTRY - - -def evaluate(project_dir, train_data_dir, input_len, output_len, rescale, null_val, batch_size, patch_len, down_sampling=1): - assert output_len % patch_len == 0 - num_steps = output_len // patch_len - # construct dataset - data_file_path = project_dir + "/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale) - index_file_path = project_dir + "/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale) - - train_set = TimeSeriesForecastingDataset(data_file_path, index_file_path, mode="train") - train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=True) - - valid_set = TimeSeriesForecastingDataset(data_file_path, index_file_path, mode="valid") - valid_loader = torch.utils.data.DataLoader(valid_set, batch_size=batch_size, shuffle=False) - - test_set = TimeSeriesForecastingDataset(data_file_path, index_file_path, mode="test") - test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size, shuffle=False) - - # training & validation - Xs_train = [] - Ys_train = [] - Xs_valid = [] - Ys_valid = [] - Xs_test = [] - Ys_test = [] - - for i, (target, data) in enumerate(train_loader): - B, L, N, C = data.shape - data = data.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - B, L, N, C = target.shape - target = target.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - Xs_train.append(data) - Ys_train.append(target) - - Xs_train = torch.cat(Xs_train, dim=0).numpy()[::down_sampling, :] - Ys_train = torch.cat(Ys_train, dim=0).numpy()[::down_sampling, :][:, :patch_len] - print("Xs_train: ", Xs_train.shape) - - # direct forecasting - from sklearn.multioutput import MultiOutputRegressor - model = MultiOutputRegressor(lgb.LGBMRegressor(), n_jobs = -1) - model.fit(Xs_train, Ys_train) - - import pickle - # sange model - with open("model.pkl", "wb") as f: - pickle.dump(model, f) - - for i, (target, data) in enumerate(test_loader): - B, L, N, C = data.shape - data = data.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - B, L, N, C = target.shape - target = target.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - Xs_test.append(data) - Ys_test.append(target) - - Xs_test = torch.cat(Xs_test, dim=0).numpy() - Ys_test = torch.cat(Ys_test, dim=0).numpy() - print("Xs_test: ", Xs_test.shape) - - # inference - preds_test = [] - input_data = Xs_test - - for i in tqdm(range(num_steps)): - # Predict the next step - pred_step = model.predict(input_data) - preds_test.append(pred_step) - # Update input_data to include predicted step for next prediction - input_data = np.concatenate([input_data[:, patch_len:], pred_step[:, :]], axis=1) - # concat preds_test - # preds_test = np.vstack(preds_test).T - preds_test = np.concatenate(preds_test, axis=1) - - # rescale - scaler = load_pkl(project_dir + "/{0}/scaler_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale)) - preds_test = torch.Tensor(preds_test).view(-1, N, output_len).transpose(1, 2).unsqueeze(-1) - Ys_test = torch.Tensor(Ys_test).view(-1, N, output_len).transpose(1, 2).unsqueeze(-1) - prediction = SCALER_REGISTRY.get(scaler["func"])(preds_test, **scaler["args"]) - real_value = SCALER_REGISTRY.get(scaler["func"])(Ys_test, **scaler["args"]) - # print results - print("MAE: ", masked_mae(prediction, real_value, null_val).item()) - print("RMSE: ", masked_rmse(prediction, real_value, null_val).item()) - print("MAPE: {:.2f}%".format(masked_mape(prediction, real_value, null_val) * 100)) - print("WAPE: {:.2f}%".format(masked_wape(prediction, real_value, null_val) * 100)) diff --git a/baselines/LightGBM/evaluate_m4_ar.py b/baselines/LightGBM/evaluate_m4_ar.py deleted file mode 100644 index 03768745..00000000 --- a/baselines/LightGBM/evaluate_m4_ar.py +++ /dev/null @@ -1,90 +0,0 @@ -import torch -import lightgbm as lgb -import os -import sys -sys.path.append("/workspace/S22/BasicTS") -import numpy as np -from tqdm import tqdm -from basicts.utils import load_pkl -from basicts.data import M4ForecastingDataset -from basicts.metrics import masked_mae, masked_rmse, masked_mape, masked_wape -from basicts.data import SCALER_REGISTRY - - -def evaluate(project_dir, train_data_dir, input_len, output_len, rescale, null_val, batch_size, patch_len, down_sampling=1, seasonal_pattern=None): - assert output_len % patch_len == 0 - num_steps = output_len // patch_len - # construct dataset - data_file_path = project_dir + "/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale) - mask_file_path = project_dir + "/{0}/mask_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale) - index_file_path = project_dir + "/{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format(train_data_dir, input_len, output_len, rescale) - - train_set = M4ForecastingDataset(data_file_path, index_file_path, mask_file_path, mode="train") - train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=True) - - test_set = M4ForecastingDataset(data_file_path, index_file_path, mask_file_path, mode="test") - test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size, shuffle=False) - - # training & validation - Xs_train = [] - Ys_train = [] - Xs_test = [] - Ys_test = [] - - for i, (target, data, future_mask, history_mask) in enumerate(train_loader): - B, L, N, C = data.shape - data = data.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - B, L, N, C = target.shape - target = target.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - Xs_train.append(data) - Ys_train.append(target) - - Xs_train = torch.cat(Xs_train, dim=0).numpy()[::down_sampling, :] - Ys_train = torch.cat(Ys_train, dim=0).numpy()[::down_sampling, :][:, :patch_len] - print("Xs_train: ", Xs_train.shape) - - # direct forecasting - from sklearn.multioutput import MultiOutputRegressor - model = MultiOutputRegressor(lgb.LGBMRegressor(), n_jobs = -1) - model.fit(Xs_train, Ys_train) - - for i, (target, data, future_mask, history_mask) in enumerate(test_loader): - B, L, N, C = data.shape - data = data.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - B, L, N, C = target.shape - target = target.transpose(1, 2).reshape(B*N, L, C)[:, :, 0] - Xs_test.append(data) - Ys_test.append(target) - - Xs_test = torch.cat(Xs_test, dim=0).numpy() - Ys_test = torch.cat(Ys_test, dim=0).numpy() - print("Xs_test: ", Xs_test.shape) - - # inference - preds_test = [] - input_data = Xs_test - - for i in tqdm(range(num_steps)): - # Predict the next step - pred_step = model.predict(input_data) - preds_test.append(pred_step) - # Update input_data to include predicted step for next prediction - input_data = np.concatenate([input_data[:, patch_len:], pred_step[:, :]], axis=1) - # concat preds_test - # preds_test = np.vstack(preds_test).T - preds_test = np.concatenate(preds_test, axis=1) - - # rescale - preds_test = torch.Tensor(preds_test).view(-1, N, output_len).transpose(1, 2).unsqueeze(-1) - Ys_test = torch.Tensor(Ys_test).view(-1, N, output_len).transpose(1, 2).unsqueeze(-1) - prediction = preds_test - real_value = Ys_test - np.save("/workspace/S22/BasicTS/baselines/LightGBM/M4_{0}.npy".format(seasonal_pattern), prediction.unsqueeze(-1).unsqueeze(-1).numpy()) - - # print results - print("MAE: ", masked_mae(prediction, real_value, null_val).item()) - print("RMSE: ", masked_rmse(prediction, real_value, null_val).item()) - print("MAPE: {:.2f}%".format(masked_mape(prediction, real_value, null_val) * 100)) - print("WAPE: {:.2f}%".format(masked_wape(prediction, real_value, null_val) * 100)) - # save - \ No newline at end of file diff --git a/baselines/LightGBM/run.sh b/baselines/LightGBM/run.sh deleted file mode 100644 index a0724000..00000000 --- a/baselines/LightGBM/run.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# python baselines/LightGBM/METR-LA.py -# python baselines/LightGBM/PEMS-BAY.py -# python baselines/LightGBM/PEMS03.py -# python baselines/LightGBM/PEMS04.py -# python baselines/LightGBM/PEMS07.py -# python baselines/LightGBM/PEMS08.py - -# python baselines/LightGBM/ETTh1.py -# python baselines/LightGBM/ETTm1.py -# python baselines/LightGBM/Weather.py -# python baselines/LightGBM/PEMS08_ltsf.py -# python baselines/LightGBM/PEMS04_ltsf.py - -python baselines/LightGBM/Electricity.py -python baselines/LightGBM/ExchangeRate.py diff --git a/baselines/Linear/ETTh1.py b/baselines/Linear/ETTh1.py deleted file mode 100644 index 727cd4a0..00000000 --- a/baselines/Linear/ETTh1.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/ETTh2.py b/baselines/Linear/ETTh2.py deleted file mode 100644 index 21bcd8ad..00000000 --- a/baselines/Linear/ETTh2.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/ETTm1.py b/baselines/Linear/ETTm1.py deleted file mode 100644 index ab4f84d7..00000000 --- a/baselines/Linear/ETTm1.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/ETTm2.py b/baselines/Linear/ETTm2.py deleted file mode 100644 index 36b9499e..00000000 --- a/baselines/Linear/ETTm2.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/Electricity.py b/baselines/Linear/Electricity.py deleted file mode 100644 index 866d094b..00000000 --- a/baselines/Linear/Electricity.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 321 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/ExchangeRate.py b/baselines/Linear/ExchangeRate.py deleted file mode 100644 index 85ac8426..00000000 --- a/baselines/Linear/ExchangeRate.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 8 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/METR-LA.py b/baselines/Linear/METR-LA.py deleted file mode 100644 index f6312927..00000000 --- a/baselines/Linear/METR-LA.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 207 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/PEMS-BAY.py b/baselines/Linear/PEMS-BAY.py deleted file mode 100644 index 0882494c..00000000 --- a/baselines/Linear/PEMS-BAY.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 325 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/PEMS04.py b/baselines/Linear/PEMS04.py deleted file mode 100644 index 9af3b46b..00000000 --- a/baselines/Linear/PEMS04.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 307 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/PEMS08.py b/baselines/Linear/PEMS08.py deleted file mode 100644 index 815f59aa..00000000 --- a/baselines/Linear/PEMS08.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 170 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/Weather.py b/baselines/Linear/Weather.py deleted file mode 100644 index e856e0b3..00000000 --- a/baselines/Linear/Weather.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import Linear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Linear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Linear" -CFG.MODEL.ARCH = Linear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 21 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Linear/arch/__init__.py b/baselines/Linear/arch/__init__.py deleted file mode 100644 index b632e725..00000000 --- a/baselines/Linear/arch/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .linear import Linear \ No newline at end of file diff --git a/baselines/Linear/arch/linear.py b/baselines/Linear/arch/linear.py deleted file mode 100644 index 67d74d9e..00000000 --- a/baselines/Linear/arch/linear.py +++ /dev/null @@ -1,30 +0,0 @@ -import torch -import torch.nn as nn - -class Linear(nn.Module): - """ - Paper: Are Transformers Effective for Time Series Forecasting? - Link: https://arxiv.org/abs/2205.13504 - Official Code: https://github.com/cure-lab/DLinear - """ - - def __init__(self, **model_args): - super(Linear, self).__init__() - self.seq_len = model_args["seq_len"] - self.pred_len = model_args["pred_len"] - self.Linear = nn.Linear(self.seq_len, self.pred_len) - - def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool, **kwargs) -> torch.Tensor: - """Feed forward of Linear. - - Args: - history_data (torch.Tensor): history data with shape [B, L, N, C] - - Returns: - torch.Tensor: prediction with shape [B, L, N, C] - """ - - assert history_data.shape[-1] == 1 # only use the target feature - history_data = history_data[..., 0] # B, L, N - prediction = self.Linear(history_data.permute(0, 2, 1)).permute(0, 2, 1).unsqueeze(-1) # B, L, N, 1 - return prediction diff --git a/baselines/Linear/run.sh b/baselines/Linear/run.sh deleted file mode 100644 index bacf125c..00000000 --- a/baselines/Linear/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/Linear/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/Linear/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/Linear/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/Linear/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/Linear/Electricity.py --gpus '0' -python experiments/train.py -c baselines/Linear/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/Linear/Weather.py --gpus '0' -python experiments/train.py -c baselines/Linear/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/Linear/PEMS08.py --gpus '0' diff --git a/baselines/MLP/M4.py b/baselines/MLP/M4.py deleted file mode 100644 index 11e70ca7..00000000 --- a/baselines/MLP/M4.py +++ /dev/null @@ -1,105 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import M4ForecastingRunner -from basicts.losses import masked_mae -from basicts.data import M4ForecastingDataset - -from .mlp_arch import MultiLayerPerceptron - -def get_cfg(seasonal_pattern): - assert seasonal_pattern in ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] - prediction_len = {"Yearly": 6, "Quarterly": 8, "Monthly": 18, "Weekly": 13, "Daily": 14, "Hourly": 48}[seasonal_pattern] - history_size = 2 - history_len = history_size * prediction_len - - CFG = EasyDict() - - # ================= general ================= # - CFG.DESCRIPTION = "Multi-layer perceptron model configuration " - CFG.RUNNER = M4ForecastingRunner - CFG.DATASET_CLS = M4ForecastingDataset - CFG.DATASET_NAME = "M4_" + seasonal_pattern - CFG.DATASET_INPUT_LEN = history_len - CFG.DATASET_OUTPUT_LEN = prediction_len - CFG.GPU_NUM = 1 - - # ================= environment ================= # - CFG.ENV = EasyDict() - CFG.ENV.SEED = 1 - CFG.ENV.CUDNN = EasyDict() - CFG.ENV.CUDNN.ENABLED = True - - # ================= model ================= # - CFG.MODEL = EasyDict() - CFG.MODEL.NAME = "MultiLayerPerceptron" - CFG.MODEL.ARCH = MultiLayerPerceptron - CFG.MODEL.PARAM = { - "history_seq_len": CFG.DATASET_INPUT_LEN, - "prediction_seq_len": CFG.DATASET_OUTPUT_LEN, - "hidden_dim": 32 - } - CFG.MODEL.FORWARD_FEATURES = [0] - CFG.MODEL.TARGET_FEATURES = [0] - - # ================= optim ================= # - CFG.TRAIN = EasyDict() - CFG.TRAIN.LOSS = masked_mae - CFG.TRAIN.OPTIM = EasyDict() - CFG.TRAIN.OPTIM.TYPE = "Adam" - CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 1.0e-5, - "eps": 1.0e-8 - } - CFG.TRAIN.LR_SCHEDULER = EasyDict() - CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" - CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 30, 38, 46, 54, 62, 70, 80], - "gamma": 0.5 - } - - # ================= train ================= # - CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 - } - CFG.TRAIN.NUM_EPOCHS = 5 - CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) - ) - # train data - CFG.TRAIN.DATA = EasyDict() - # read data - CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TRAIN.DATA.BATCH_SIZE = 32 - CFG.TRAIN.DATA.PREFETCH = False - CFG.TRAIN.DATA.SHUFFLE = True - CFG.TRAIN.DATA.NUM_WORKERS = 2 - CFG.TRAIN.DATA.PIN_MEMORY = False - - # ================= test ================= # - CFG.TEST = EasyDict() - CFG.TEST.INTERVAL = CFG.TRAIN.NUM_EPOCHS - # evluation - # test data - CFG.TEST.DATA = EasyDict() - # read data - CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TEST.DATA.BATCH_SIZE = 32 - CFG.TEST.DATA.PREFETCH = False - CFG.TEST.DATA.SHUFFLE = False - CFG.TEST.DATA.NUM_WORKERS = 2 - CFG.TEST.DATA.PIN_MEMORY = False - - # ================= evaluate ================= # - CFG.EVAL = EasyDict() - CFG.EVAL.HORIZONS = [] - CFG.EVAL.SAVE_PATH = os.path.abspath(__file__ + "/..") - - return CFG diff --git a/baselines/MLP/MLP_METR-LA.py b/baselines/MLP/MLP_METR-LA.py deleted file mode 100644 index 5394c89f..00000000 --- a/baselines/MLP/MLP_METR-LA.py +++ /dev/null @@ -1,113 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .mlp_arch import MultiLayerPerceptron - - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Multi-layer perceptron model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "MultiLayerPerceptron" -CFG.MODEL.ARCH = MultiLayerPerceptron -CFG.MODEL.PARAM = { - "history_seq_len": CFG.DATASET_INPUT_LEN, - "prediction_seq_len": CFG.DATASET_OUTPUT_LEN, - "hidden_dim": 32 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 1.0e-5, - "eps": 1.0e-8 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 30, 38, 46, 54, 62, 70, 80], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 32 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 32 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/MLP/mlp_arch.py b/baselines/MLP/mlp_arch.py deleted file mode 100644 index d2edba0d..00000000 --- a/baselines/MLP/mlp_arch.py +++ /dev/null @@ -1,25 +0,0 @@ -import torch -from torch import nn - -class MultiLayerPerceptron(nn.Module): - """Two fully connected layer.""" - - def __init__(self, history_seq_len: int, prediction_seq_len: int, hidden_dim: int): - super().__init__() - self.fc1 = nn.Linear(history_seq_len, hidden_dim) - self.fc2 = nn.Linear(hidden_dim, prediction_seq_len) - self.act = nn.ReLU() - - def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool, **kwargs) -> torch.Tensor: - """Feedforward function of AGCRN. - - Args: - history_data (torch.Tensor): inputs with shape [B, L, N, C]. - - Returns: - torch.Tensor: outputs with shape [B, L, N, C] - """ - - history_data = history_data[..., 0].transpose(1, 2) # B, N, L - prediction = self.fc2(self.act(self.fc1(history_data))).transpose(1, 2) # B, L, N - return prediction.unsqueeze(-1) # B, L, N, C \ No newline at end of file diff --git a/baselines/MTGNN/METR-LA.py b/baselines/MTGNN/METR-LA.py index fbc66766..b4b96486 100644 --- a/baselines/MTGNN/METR-LA.py +++ b/baselines/MTGNN/METR-LA.py @@ -1,49 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import MTGNN from .runner import MTGNNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "MTGNN model configuration" -CFG.RUNNER = MTGNNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "MTGNN" -CFG.MODEL.ARCH = MTGNN +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = MTGNN buildA_true = True num_nodes = 207 if buildA_true: # self-learned adjacency matrix adj_mx = None else: # use predefined adjacency matrix - _, adj_mx = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "doubletransition") + _, adj_mx = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") adj_mx = torch.tensor(adj_mx)-torch.eye(num_nodes) - -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_true" : True, "buildA_true": buildA_true, "gcn_depth": 2, @@ -65,77 +53,112 @@ "tanhalpha":3, "layer_norm_affline":True } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config ' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = MTGNNRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args -CFG.TRAIN.CUSTOM.STEP_SIZE = 100 -CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes -CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 - -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} ## curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +# ================= train ================= # +CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args +CFG.TRAIN.CUSTOM.STEP_SIZE = 100 +CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes +CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/MTGNN/PEMS-BAY.py b/baselines/MTGNN/PEMS-BAY.py index 2edc8356..38e4290b 100644 --- a/baselines/MTGNN/PEMS-BAY.py +++ b/baselines/MTGNN/PEMS-BAY.py @@ -1,49 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import MTGNN from .runner import MTGNNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "MTGNN model configuration" -CFG.RUNNER = MTGNNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "MTGNN" -CFG.MODEL.ARCH = MTGNN +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = MTGNN buildA_true = True num_nodes = 325 if buildA_true: # self-learned adjacency matrix adj_mx = None else: # use predefined adjacency matrix - _, adj_mx = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "doubletransition") + _, adj_mx = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") adj_mx = torch.tensor(adj_mx)-torch.eye(num_nodes) - -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_true" : True, "buildA_true": buildA_true, "gcn_depth": 2, @@ -65,77 +53,112 @@ "tanhalpha":3, "layer_norm_affline":True } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config ' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = MTGNNRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args -CFG.TRAIN.CUSTOM.STEP_SIZE = 100 -CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes -CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 - -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} ## curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +# ================= train ================= # +CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args +CFG.TRAIN.CUSTOM.STEP_SIZE = 100 +CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes +CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/MTGNN/PEMS03.py b/baselines/MTGNN/PEMS03.py index f9e4e4d9..7f1acff8 100644 --- a/baselines/MTGNN/PEMS03.py +++ b/baselines/MTGNN/PEMS03.py @@ -1,49 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import MTGNN from .runner import MTGNNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "MTGNN model configuration" -CFG.RUNNER = MTGNNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "MTGNN" -CFG.MODEL.ARCH = MTGNN +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = MTGNN buildA_true = True num_nodes = 358 if buildA_true: # self-learned adjacency matrix adj_mx = None else: # use predefined adjacency matrix - _, adj_mx = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "doubletransition") + _, adj_mx = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") adj_mx = torch.tensor(adj_mx)-torch.eye(num_nodes) - -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_true" : True, "buildA_true": buildA_true, "gcn_depth": 2, @@ -65,77 +53,112 @@ "tanhalpha":3, "layer_norm_affline":True } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config ' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = MTGNNRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args -CFG.TRAIN.CUSTOM.STEP_SIZE = 100 -CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes -CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 - -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} ## curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +# ================= train ================= # +CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args +CFG.TRAIN.CUSTOM.STEP_SIZE = 100 +CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes +CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/MTGNN/PEMS04.py b/baselines/MTGNN/PEMS04.py index cd6d0284..09788bbc 100644 --- a/baselines/MTGNN/PEMS04.py +++ b/baselines/MTGNN/PEMS04.py @@ -1,49 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import MTGNN from .runner import MTGNNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "MTGNN model configuration" -CFG.RUNNER = MTGNNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "MTGNN" -CFG.MODEL.ARCH = MTGNN +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = MTGNN buildA_true = True num_nodes = 307 if buildA_true: # self-learned adjacency matrix adj_mx = None else: # use predefined adjacency matrix - _, adj_mx = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "doubletransition") + _, adj_mx = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") adj_mx = torch.tensor(adj_mx)-torch.eye(num_nodes) - -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_true" : True, "buildA_true": buildA_true, "gcn_depth": 2, @@ -65,77 +53,112 @@ "tanhalpha":3, "layer_norm_affline":True } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config ' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = MTGNNRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args -CFG.TRAIN.CUSTOM.STEP_SIZE = 100 -CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes -CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 - -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} ## curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +# ================= train ================= # +CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args +CFG.TRAIN.CUSTOM.STEP_SIZE = 100 +CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes +CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/MTGNN/PEMS07.py b/baselines/MTGNN/PEMS07.py index b6862571..f6fcdaea 100644 --- a/baselines/MTGNN/PEMS07.py +++ b/baselines/MTGNN/PEMS07.py @@ -1,49 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import MTGNN from .runner import MTGNNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "MTGNN model configuration" -CFG.RUNNER = MTGNNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "MTGNN" -CFG.MODEL.ARCH = MTGNN +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = MTGNN buildA_true = True num_nodes = 883 if buildA_true: # self-learned adjacency matrix adj_mx = None else: # use predefined adjacency matrix - _, adj_mx = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "doubletransition") + _, adj_mx = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") adj_mx = torch.tensor(adj_mx)-torch.eye(num_nodes) - -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_true" : True, "buildA_true": buildA_true, "gcn_depth": 2, @@ -65,77 +53,112 @@ "tanhalpha":3, "layer_norm_affline":True } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config ' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = MTGNNRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args -CFG.TRAIN.CUSTOM.STEP_SIZE = 100 -CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes -CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 - -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} ## curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +# ================= train ================= # +CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args +CFG.TRAIN.CUSTOM.STEP_SIZE = 100 +CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes +CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/MTGNN/PEMS08.py b/baselines/MTGNN/PEMS08.py index 27c86d31..705bd58c 100644 --- a/baselines/MTGNN/PEMS08.py +++ b/baselines/MTGNN/PEMS08.py @@ -1,49 +1,37 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import MTGNN from .runner import MTGNNRunner -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "MTGNN model configuration" -CFG.RUNNER = MTGNNRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "MTGNN" -CFG.MODEL.ARCH = MTGNN +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = MTGNN buildA_true = True num_nodes = 170 if buildA_true: # self-learned adjacency matrix adj_mx = None else: # use predefined adjacency matrix - _, adj_mx = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "doubletransition") + _, adj_mx = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "doubletransition") adj_mx = torch.tensor(adj_mx)-torch.eye(num_nodes) - -CFG.MODEL.PARAM = { +MODEL_PARAM = { "gcn_true" : True, "buildA_true": buildA_true, "gcn_depth": 2, @@ -65,77 +53,112 @@ "tanhalpha":3, "layer_norm_affline":True } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config ' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = MTGNNRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args -CFG.TRAIN.CUSTOM.STEP_SIZE = 100 -CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes -CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 - -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} ## curriculum learning CFG.TRAIN.CL = EasyDict() CFG.TRAIN.CL.WARM_EPOCHS = 0 CFG.TRAIN.CL.CL_EPOCHS = 3 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 +# ================= train ================= # +CFG.TRAIN.CUSTOM = EasyDict() # MTGNN custom training args +CFG.TRAIN.CUSTOM.STEP_SIZE = 100 +CFG.TRAIN.CUSTOM.NUM_NODES = num_nodes +CFG.TRAIN.CUSTOM.NUM_SPLIT = 1 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/MTGNN/run.sh b/baselines/MTGNN/run.sh deleted file mode 100644 index 529202f2..00000000 --- a/baselines/MTGNN/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/MTGNN/METR-LA.py --gpus '3' -python experiments/train.py -c baselines/MTGNN/PEMS-BAY.py --gpus '3' -python experiments/train.py -c baselines/MTGNN/PEMS03.py --gpus '3' -python experiments/train.py -c baselines/MTGNN/PEMS04.py --gpus '3' -python experiments/train.py -c baselines/MTGNN/PEMS07.py --gpus '3' -python experiments/train.py -c baselines/MTGNN/PEMS08.py --gpus '3' diff --git a/baselines/MTGNN/runner/mtgnn_runner.py b/baselines/MTGNN/runner/mtgnn_runner.py index c6129dd2..04af4bc6 100644 --- a/baselines/MTGNN/runner/mtgnn_runner.py +++ b/baselines/MTGNN/runner/mtgnn_runner.py @@ -3,10 +3,10 @@ import torch import numpy as np -from basicts.runners import BaseTimeSeriesForecastingRunner +from basicts.runners import SimpleTimeSeriesForecastingRunner -class MTGNNRunner(BaseTimeSeriesForecastingRunner): +class MTGNNRunner(SimpleTimeSeriesForecastingRunner): def __init__(self, cfg: dict): super().__init__(cfg) self.forward_features = cfg["MODEL"].get("FORWARD_FEATURES", None) @@ -47,22 +47,10 @@ def select_target_features(self, data: torch.Tensor) -> torch.Tensor: return data def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: bool = True, **kwargs) -> tuple: - """Feed forward process for train, val, and test. Note that the outputs are NOT re-scaled. - - Args: - data (tuple): data (future data, history data). [B, L, N, C] for each of them - epoch (int, optional): epoch number. Defaults to None. - iter_num (int, optional): iteration number. Defaults to None. - train (bool, optional): if in the training process. Defaults to True. - - Returns: - tuple: (prediction, real_value). [B, L, N, C] for each of them. - """ - if train: - future_data, history_data, idx = data + future_data, history_data, idx = data['target'], data['inputs'], data['idx'] else: - future_data, history_data = data + future_data, history_data = data['target'], data['inputs'] idx = None history_data = self.to_running_device(history_data) # B, L, N, C @@ -83,18 +71,6 @@ def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: b return model_return def train_iters(self, epoch: int, iter_index: int, data: Union[torch.Tensor, Tuple]) -> torch.Tensor: - """It must be implement to define training detail. - - If it returns `loss`, the function ```self.backward``` will be called. - - Args: - epoch (int): current epoch. - iter_index (int): current iter. - data (torch.Tensor or tuple): Data provided by DataLoader - - Returns: - loss (torch.Tensor) - """ if iter_index % self.step_size == 0: self.perm = np.random.permutation(range(self.num_nodes)) @@ -106,7 +82,11 @@ def train_iters(self, epoch: int, iter_index: int, data: Union[torch.Tensor, Tup else: idx = self.perm[j * num_sub:] idx = torch.tensor(idx) - future_data, history_data = data - data = future_data[:, :, idx, :], history_data[:, :, idx, :], idx + future_data, history_data = data['target'][:, :, idx, :], data['inputs'][:, :, idx, :] + data = { + 'target': future_data, + 'inputs': history_data, + 'idx': idx + } loss = super().train_iters(epoch, iter_index, data) self.backward(loss) diff --git a/baselines/MegaCRN/METR-LA.py b/baselines/MegaCRN/METR-LA.py new file mode 100644 index 00000000..677915c5 --- /dev/null +++ b/baselines/MegaCRN/METR-LA.py @@ -0,0 +1,148 @@ +import os +import sys +import torch +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import MegaCRN +from .loss import megacrn_loss + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = MegaCRN +MODEL_PARAM = { + "num_nodes": 207, + "input_dim": 1, + "output_dim": 1, + "horizon": 12, + "rnn_units": 64, + "num_layers":1, + "cheb_k":3, + "ycov_dim":1, + "mem_num":20, + "mem_dim":64, + "cl_decay_steps":2000, + "use_curriculum_learning":True +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = megacrn_loss +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.01, + "eps": 1e-3 +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [50, 100], + "gamma": 0.1 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True +# \ No newline at end of file diff --git a/baselines/MegaCRN/MegaCRN_METR-LA.py b/baselines/MegaCRN/MegaCRN_METR-LA.py deleted file mode 100644 index a2d533de..00000000 --- a/baselines/MegaCRN/MegaCRN_METR-LA.py +++ /dev/null @@ -1,114 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner - -from .arch import MegaCRN -from .loss import megacrn_loss - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "MegaCRN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "MegaCRN" -CFG.MODEL.ARCH = MegaCRN -CFG.MODEL.PARAM = { - "num_nodes": 207, - "input_dim": 1, - "output_dim": 1, - "horizon": 12, - "rnn_units": 64, - "num_layers":1, - "cheb_k":3, - "ycov_dim":1, - "mem_num":20, - "mem_dim":64, - "cl_decay_steps":2000, - "use_curriculum_learning":True -} -CFG.MODEL.SETUP_GRAPH = True -CFG.MODEL.FORWARD_FEATURES = [0, 1] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = megacrn_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.01, - "eps": 1e-3 -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [50, 100], - "gamma": 0.1 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/MegaCRN/arch/megacrn_arch.py b/baselines/MegaCRN/arch/megacrn_arch.py index 4d68269f..d49dd857 100644 --- a/baselines/MegaCRN/arch/megacrn_arch.py +++ b/baselines/MegaCRN/arch/megacrn_arch.py @@ -12,9 +12,9 @@ def __init__(self, dim_in, dim_out, cheb_k): self.bias = nn.Parameter(torch.FloatTensor(dim_out)) nn.init.xavier_normal_(self.weights) nn.init.constant_(self.bias, val=0) - + def forward(self, x, supports): - x_g = [] + x_g = [] support_set = [] for support in supports: support_ks = [torch.eye(support.shape[0]).to(support.device), support] @@ -26,7 +26,7 @@ def forward(self, x, supports): x_g = torch.cat(x_g, dim=-1) # B, N, 2 * cheb_k * dim_in x_gconv = torch.einsum('bni,io->bno', x_g, self.weights) + self.bias # b, N, dim_out return x_gconv - + class AGCRNCell(nn.Module): def __init__(self, node_num, dim_in, dim_out, cheb_k): super(AGCRNCell, self).__init__() @@ -49,7 +49,7 @@ def forward(self, x, state, supports): def init_hidden_state(self, batch_size): return torch.zeros(batch_size, self.node_num, self.hidden_dim) - + class ADCRNN_Encoder(nn.Module): def __init__(self, node_num, dim_in, dim_out, cheb_k, num_layers): super(ADCRNN_Encoder, self).__init__() @@ -81,7 +81,7 @@ def forward(self, x, init_state, supports): #output_hidden: the last state for each layer: (num_layers, B, N, hidden_dim) #return current_inputs, torch.stack(output_hidden, dim=0) return current_inputs, output_hidden - + def init_hidden(self, batch_size): init_states = [] for i in range(self.num_layers): @@ -150,7 +150,7 @@ def __init__(self, num_nodes, input_dim, output_dim, horizon, rnn_units, num_lay self.ycov_dim = ycov_dim self.cl_decay_steps = cl_decay_steps self.use_curriculum_learning = use_curriculum_learning - + # memory self.mem_num = mem_num self.mem_dim = mem_dim @@ -158,14 +158,14 @@ def __init__(self, num_nodes, input_dim, output_dim, horizon, rnn_units, num_lay # encoder self.encoder = ADCRNN_Encoder(self.num_nodes, self.input_dim, self.rnn_units, self.cheb_k, self.num_layers) - + # deocoder self.decoder_dim = self.rnn_units + self.mem_dim self.decoder = ADCRNN_Decoder(self.num_nodes, self.output_dim + self.ycov_dim, self.decoder_dim, self.cheb_k, self.num_layers) # output self.proj = nn.Sequential(nn.Linear(self.decoder_dim, self.output_dim, bias=True)) - + def compute_sampling_threshold(self, batches_seen): return self.cl_decay_steps / (self.cl_decay_steps + np.exp(batches_seen / self.cl_decay_steps)) @@ -178,7 +178,7 @@ def construct_memory(self): for param in memory_dict.values(): nn.init.xavier_normal_(param) return memory_dict - + def query_memory(self, h_t:torch.Tensor): query = torch.matmul(h_t, self.memory['Wq']) # (B, N, d) att_score = torch.softmax(torch.matmul(query, self.memory['Memory'].t()), dim=-1) # alpha: (B, N, M) @@ -187,7 +187,7 @@ def query_memory(self, h_t:torch.Tensor): pos = self.memory['Memory'][ind[:, :, 0]] # B, N, d neg = self.memory['Memory'][ind[:, :, 1]] # B, N, d return value, query, pos, neg - + def forward(self, history_data, future_data, batch_seen=None, epoch=None, **kwargs): # def forward(self, x, y_cov, labels=None, batches_seen=None): x = history_data[..., [0]] @@ -201,11 +201,11 @@ def forward(self, history_data, future_data, batch_seen=None, epoch=None, **kwar supports = [g1, g2] init_state = self.encoder.init_hidden(x.shape[0]) h_en, state_en = self.encoder(x, init_state, supports) # B, T, N, hidden - h_t = h_en[:, -1, :, :] # B, N, hidden (last state) - + h_t = h_en[:, -1, :, :] # B, N, hidden (last state) + h_att, query, pos, neg = self.query_memory(h_t) h_t = torch.cat([h_t, h_att], dim=-1) - + ht_list = [h_t]*self.num_layers go = torch.zeros((x.shape[0], self.num_nodes, self.output_dim), device=x.device) out = [] @@ -218,5 +218,5 @@ def forward(self, history_data, future_data, batch_seen=None, epoch=None, **kwar if c < self.compute_sampling_threshold(batch_seen): go = labels[:, t, ...] output = torch.stack(out, dim=1) - + return {'prediction': output, 'query': query, 'pos': pos, 'neg': neg} diff --git a/baselines/MegaCRN/loss/loss.py b/baselines/MegaCRN/loss/loss.py index b7653ee5..54efb144 100644 --- a/baselines/MegaCRN/loss/loss.py +++ b/baselines/MegaCRN/loss/loss.py @@ -1,5 +1,5 @@ from torch import nn -from basicts.losses import masked_mae +from basicts.metrics import masked_mae def megacrn_loss(prediction, target, query, pos, neg, null_val): diff --git a/baselines/NBeats/ETTh1.py b/baselines/NBeats/ETTh1.py index f7472eb4..d043c759 100644 --- a/baselines/NBeats/ETTh1.py +++ b/baselines/NBeats/ETTh1.py @@ -1,54 +1,104 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NBeats -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NBeats +NUM_NODES = 7 +MODEL_PARAM = { "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "output_size": OUTPUT_LEN, "layer_size": 512, "layers": 4, "stacks": 10 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0, "eps":1.0e-8, @@ -60,52 +110,30 @@ "milestones":[20, 40, 60, 80], "gamma":0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True +CFG.TRAIN.DATA.BATCH_SIZE = 32 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NBeats/ETTm1.py b/baselines/NBeats/ETTm1.py index 87aaac50..fc8d483f 100644 --- a/baselines/NBeats/ETTm1.py +++ b/baselines/NBeats/ETTm1.py @@ -1,54 +1,104 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NBeats -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NBeats +NUM_NODES = 7 +MODEL_PARAM = { "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "output_size": OUTPUT_LEN, "layer_size": 512, "layers": 4, "stacks": 10 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0, "eps":1.0e-8, @@ -60,52 +110,30 @@ "milestones":[20, 40, 60, 80], "gamma":0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True +CFG.TRAIN.DATA.BATCH_SIZE = 32 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NBeats/Electricity.py b/baselines/NBeats/Electricity.py index 92b6d07d..ce935a84 100644 --- a/baselines/NBeats/Electricity.py +++ b/baselines/NBeats/Electricity.py @@ -1,54 +1,104 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NBeats -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NBeats +NUM_NODES = 321 +MODEL_PARAM = { "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "output_size": OUTPUT_LEN, "layer_size": 512, "layers": 4, "stacks": 10 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0, "eps":1.0e-8, @@ -60,57 +110,30 @@ "milestones":[20, 40, 60, 80], "gamma":0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = True +CFG.TRAIN.DATA.BATCH_SIZE = 64 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NBeats/ExchangeRate.py b/baselines/NBeats/ExchangeRate.py index 6c8fcdec..7b9b1e25 100644 --- a/baselines/NBeats/ExchangeRate.py +++ b/baselines/NBeats/ExchangeRate.py @@ -1,54 +1,104 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NBeats -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NBeats +NUM_NODES = 8 +MODEL_PARAM = { "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "output_size": OUTPUT_LEN, "layer_size": 512, "layers": 4, "stacks": 10 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0, "eps":1.0e-8, @@ -60,52 +110,30 @@ "milestones":[20, 40, 60, 80], "gamma":0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True +CFG.TRAIN.DATA.BATCH_SIZE = 32 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NBeats/METR-LA.py b/baselines/NBeats/METR-LA.py deleted file mode 100644 index 28a60b14..00000000 --- a/baselines/NBeats/METR-LA.py +++ /dev/null @@ -1,116 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NBeats - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { - "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, - "layer_size": 128, - "layers": 4, - "stacks": 10 - } -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.001, - "weight_decay":0, - "eps":1.0e-8, - "betas":(0.9, 0.95) -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[20, 40, 60, 80], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = True -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6 ,12] diff --git a/baselines/NBeats/PEMS-BAY.py b/baselines/NBeats/PEMS-BAY.py deleted file mode 100644 index f99146b1..00000000 --- a/baselines/NBeats/PEMS-BAY.py +++ /dev/null @@ -1,116 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NBeats - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { - "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, - "layer_size": 512, - "layers": 4, - "stacks": 10 - } -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.001, - "weight_decay":0, - "eps":1.0e-8, - "betas":(0.9, 0.95) -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[20, 40, 60, 80], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6 ,12] diff --git a/baselines/NBeats/PEMS03.py b/baselines/NBeats/PEMS03.py deleted file mode 100644 index de5fd489..00000000 --- a/baselines/NBeats/PEMS03.py +++ /dev/null @@ -1,116 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NBeats - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { - "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, - "layer_size": 512, - "layers": 4, - "stacks": 10 - } -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.001, - "weight_decay":0, - "eps":1.0e-8, - "betas":(0.9, 0.95) -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[20, 40, 60, 80], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6 ,12] diff --git a/baselines/NBeats/PEMS04.py b/baselines/NBeats/PEMS04.py deleted file mode 100644 index 04c749e1..00000000 --- a/baselines/NBeats/PEMS04.py +++ /dev/null @@ -1,116 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NBeats - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { - "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, - "layer_size": 512, - "layers": 4, - "stacks": 10 - } -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.001, - "weight_decay":0, - "eps":1.0e-8, - "betas":(0.9, 0.95) -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[20, 40, 60, 80], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6 ,12] diff --git a/baselines/NBeats/PEMS04_LTSF.py b/baselines/NBeats/PEMS04_LTSF.py index 79415e9e..b148b154 100644 --- a/baselines/NBeats/PEMS04_LTSF.py +++ b/baselines/NBeats/PEMS04_LTSF.py @@ -1,55 +1,107 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NBeats -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NBeats +NUM_NODES = 307 +MODEL_PARAM = { "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "output_size": OUTPUT_LEN, "layer_size": 512, "layers": 4, "stacks": 10 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0, "eps":1.0e-8, @@ -61,52 +113,30 @@ "milestones":[20, 40, 60, 80], "gamma":0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True +CFG.TRAIN.DATA.BATCH_SIZE = 32 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NBeats/PEMS07.py b/baselines/NBeats/PEMS07.py deleted file mode 100644 index f820cd14..00000000 --- a/baselines/NBeats/PEMS07.py +++ /dev/null @@ -1,116 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NBeats - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { - "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, - "layer_size": 512, - "layers": 4, - "stacks": 10 - } -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.001, - "weight_decay":0, - "eps":1.0e-8, - "betas":(0.9, 0.95) -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[20, 40, 60, 80], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = True -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6 ,12] diff --git a/baselines/NBeats/PEMS08.py b/baselines/NBeats/PEMS08.py deleted file mode 100644 index 5cc51c98..00000000 --- a/baselines/NBeats/PEMS08.py +++ /dev/null @@ -1,116 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NBeats - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { - "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, - "layer_size": 512, - "layers": 4, - "stacks": 10 - } -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.001, - "weight_decay":0, - "eps":1.0e-8, - "betas":(0.9, 0.95) -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[20, 40, 60, 80], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/NBeats/PEMS08_LTSF.py b/baselines/NBeats/PEMS08_LTSF.py index d6334a83..f4e6595c 100644 --- a/baselines/NBeats/PEMS08_LTSF.py +++ b/baselines/NBeats/PEMS08_LTSF.py @@ -1,55 +1,107 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NBeats -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NBeats +NUM_NODES = 170 +MODEL_PARAM = { "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "output_size": OUTPUT_LEN, "layer_size": 512, "layers": 4, "stacks": 10 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0, "eps":1.0e-8, @@ -61,52 +113,30 @@ "milestones":[20, 40, 60, 80], "gamma":0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True +CFG.TRAIN.DATA.BATCH_SIZE = 32 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NBeats/Weather.py b/baselines/NBeats/Weather.py index 09f4af20..2ff91985 100644 --- a/baselines/NBeats/Weather.py +++ b/baselines/NBeats/Weather.py @@ -1,54 +1,104 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NBeats -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NBeats" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NBeats" -CFG.MODEL.ARCH = NBeats -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NBeats +NUM_NODES = 8 +MODEL_PARAM = { "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "output_size": OUTPUT_LEN, "layer_size": 512, "layers": 4, "stacks": 10 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr":0.001, "weight_decay":0, "eps":1.0e-8, @@ -60,52 +110,30 @@ "milestones":[20, 40, 60, 80], "gamma":0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True +CFG.TRAIN.DATA.BATCH_SIZE = 64 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NBeats/arch/nbeats.py b/baselines/NBeats/arch/nbeats.py index 5676e88f..8c856423 100644 --- a/baselines/NBeats/arch/nbeats.py +++ b/baselines/NBeats/arch/nbeats.py @@ -92,7 +92,7 @@ def __init__(self, input_size: int, type: str, output_size: int, **kwargs): seasonality_layers = kwargs["seasonality_layers"] seasonality_layer_size = kwargs["seasonality_layer_size"] num_of_harmonics = kwargs["num_of_harmonics"] - + trend_block = NBeatsBlock(input_size=input_size, theta_size=2 * (degree_of_polynomial + 1), basis_function=TrendBasis(degree_of_polynomial=degree_of_polynomial, @@ -115,9 +115,9 @@ def forward(self, history_data: t.Tensor, **kwargs) -> t.Tensor: B, L, N, C = history_data.shape history_data = history_data[..., [0]].transpose(1, 2) # [B, N, L, 1] history_data = history_data.reshape(B*N, L, 1) - + x = history_data.squeeze() - + residuals = x.flip(dims=(1,)) forecast = x[:, -1:] for i, block in enumerate(self.blocks): diff --git a/baselines/NBeats/run.sh b/baselines/NBeats/run.sh deleted file mode 100644 index 259cba60..00000000 --- a/baselines/NBeats/run.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/NBeats/METR-LA.py --gpus '1' -python experiments/train.py -c baselines/NBeats/PEMS-BAY.py --gpus '1' -python experiments/train.py -c baselines/NBeats/PEMS03.py --gpus '1' -python experiments/train.py -c baselines/NBeats/PEMS04.py --gpus '1' -python experiments/train.py -c baselines/NBeats/PEMS07.py --gpus '1' -python experiments/train.py -c baselines/NBeats/PEMS08.py --gpus '1' - -python experiments/train.py -c baselines/NBeats/ETTh1.py --gpus '1' -python experiments/train.py -c baselines/NBeats/ETTm1.py --gpus '1' -python experiments/train.py -c baselines/NBeats/Electricity.py --gpus '1' -python experiments/train.py -c baselines/NBeats/Weather.py --gpus '1' -python experiments/train.py -c baselines/NBeats/ExchangeRate.py --gpus '1' -python experiments/train.py -c baselines/NBeats/PEMS04_LTSF.py --gpus '1' -python experiments/train.py -c baselines/NBeats/PEMS08_LTSF.py --gpus '1' diff --git a/baselines/NBeats_M4/M4.py b/baselines/NBeats_M4/M4.py deleted file mode 100644 index f347957b..00000000 --- a/baselines/NBeats_M4/M4.py +++ /dev/null @@ -1,120 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae -from basicts.data import M4ForecastingDataset -from basicts.runners import M4ForecastingRunner - -from .arch import NBeats - -def get_cfg(seasonal_pattern): - assert seasonal_pattern in ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] - prediction_len = {"Yearly": 6, "Quarterly": 8, "Monthly": 18, "Weekly": 13, "Daily": 14, "Hourly": 48}[seasonal_pattern] - num_nodes = {"Yearly": 23000, "Quarterly": 24000, "Monthly": 48000, "Weekly": 359, "Daily": 4227, "Hourly": 414}[seasonal_pattern] - history_size = 2 - history_len = history_size * prediction_len - - CFG = EasyDict() - - # ================= general ================= # - CFG.DESCRIPTION = "Multi-layer perceptron model configuration " - CFG.RUNNER = M4ForecastingRunner - CFG.DATASET_CLS = M4ForecastingDataset - CFG.DATASET_NAME = "M4_" + seasonal_pattern - CFG.DATASET_INPUT_LEN = history_len - CFG.DATASET_OUTPUT_LEN = prediction_len - CFG.GPU_NUM = 1 - - # ================= environment ================= # - CFG.ENV = EasyDict() - CFG.ENV.SEED = 1 - CFG.ENV.CUDNN = EasyDict() - CFG.ENV.CUDNN.ENABLED = True - - # ================= model ================= # - CFG.MODEL = EasyDict() - CFG.MODEL.NAME = "NBeats" - CFG.MODEL.ARCH = NBeats - CFG.MODEL.PARAM = { - "type": "generic", - "input_size": CFG.DATASET_INPUT_LEN, - "output_size": CFG.DATASET_OUTPUT_LEN, - "layer_size": 512, - "layers": 4, - "stacks": 30 - } - # CFG.MODEL.PARAM = { - # "type": "interpretable", - # "input_size": CFG.DATASET_INPUT_LEN, - # "output_size": CFG.DATASET_OUTPUT_LEN, - # "seasonality_layer_size": 2048, - # "seasonality_blocks": 3, - # "seasonality_layers": 4, - # "trend_layer_size": 256, - # "degree_of_polynomial": 2, - # "trend_blocks": 3, - # "trend_layers": 4, - # "num_of_harmonics": 1 - # } - CFG.MODEL.FORWARD_FEATURES = [0] - CFG.MODEL.TARGET_FEATURES = [0] - - # ================= optim ================= # - CFG.TRAIN = EasyDict() - CFG.TRAIN.LOSS = masked_mae - CFG.TRAIN.OPTIM = EasyDict() - CFG.TRAIN.OPTIM.TYPE = "Adam" - CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, - } - CFG.TRAIN.LR_SCHEDULER = EasyDict() - CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" - CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 80], - "gamma": 0.5 - } - - # ================= train ================= # - CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 - } - CFG.TRAIN.NUM_EPOCHS = 52 - CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) - ) - # train data - CFG.TRAIN.DATA = EasyDict() - # read data - CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TRAIN.DATA.BATCH_SIZE = 64 - CFG.TRAIN.DATA.PREFETCH = False - CFG.TRAIN.DATA.SHUFFLE = True - CFG.TRAIN.DATA.NUM_WORKERS = 2 - CFG.TRAIN.DATA.PIN_MEMORY = False - - # ================= test ================= # - CFG.TEST = EasyDict() - CFG.TEST.INTERVAL = 52 - # test data - CFG.TEST.DATA = EasyDict() - # read data - CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TEST.DATA.BATCH_SIZE = 64 - CFG.TEST.DATA.PREFETCH = False - CFG.TEST.DATA.SHUFFLE = False - CFG.TEST.DATA.NUM_WORKERS = 2 - CFG.TEST.DATA.PIN_MEMORY = False - - # ================= evaluate ================= # - CFG.EVAL = EasyDict() - CFG.EVAL.HORIZONS = [] - CFG.EVAL.SAVE_PATH = os.path.abspath(__file__ + "/..") - - return CFG diff --git a/baselines/NBeats_M4/arch/__init__.py b/baselines/NBeats_M4/arch/__init__.py deleted file mode 100644 index b2fe3c8e..00000000 --- a/baselines/NBeats_M4/arch/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .nbeats import NBeats diff --git a/baselines/NBeats_M4/arch/nbeats.py b/baselines/NBeats_M4/arch/nbeats.py deleted file mode 100644 index a2d328f5..00000000 --- a/baselines/NBeats_M4/arch/nbeats.py +++ /dev/null @@ -1,197 +0,0 @@ -# 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. - -# Modified from: - -""" -N-BEATS Model. -""" -from typing import Tuple - -import numpy as np -import torch as t - - -class NBeatsBlock(t.nn.Module): - """ - N-BEATS block which takes a basis function as an argument. - """ - def __init__(self, - input_size, - theta_size: int, - basis_function: t.nn.Module, - layers: int, - layer_size: int): - """ - N-BEATS block. - - :param input_size: Insample size. - :param theta_size: Number of parameters for the basis function. - :param basis_function: Basis function which takes the parameters and produces backcast and forecast. - :param layers: Number of layers. - :param layer_size: Layer size. - """ - super().__init__() - self.layers = t.nn.ModuleList([t.nn.Linear(in_features=input_size, out_features=layer_size)] + - [t.nn.Linear(in_features=layer_size, out_features=layer_size) - for _ in range(layers - 1)]) - self.basis_parameters = t.nn.Linear(in_features=layer_size, out_features=theta_size) - self.basis_function = basis_function - - def forward(self, x: t.Tensor) -> Tuple[t.Tensor, t.Tensor]: - block_input = x - for layer in self.layers: - block_input = t.relu(layer(block_input)) - basis_parameters = self.basis_parameters(block_input) - return self.basis_function(basis_parameters) - - -class NBeats(t.nn.Module): - """ - Paper: N-BEATS: Neural basis expansion analysis for interpretable time series forecasting - Link: https://arxiv.org/abs/1905.10437 - Official Code: - https://github.com/ServiceNow/N-BEATS - https://github.com/philipperemy/n-beats - """ - - def __init__(self, input_size: int, type: str, output_size: int, **kwargs): - super().__init__() - assert type in ["generic", "interpretable"], "Unknown type of N-Beats model" - if type == "generic": - # input_size: int, output_size: int, - # stacks: int, layers: int, layer_size: int - stacks = kwargs["stacks"] - layers = kwargs["layers"] - layer_size = kwargs["layer_size"] - self.blocks = t.nn.ModuleList([NBeatsBlock(input_size=input_size, - theta_size=input_size + output_size, - basis_function=GenericBasis(backcast_size=input_size, - forecast_size=output_size), - layers=layers, - layer_size=layer_size) - for _ in range(stacks)]) - pass - else: - trend_blocks = kwargs["trend_blocks"] - trend_layers = kwargs["trend_layers"] - trend_layer_size = kwargs["trend_layer_size"] - degree_of_polynomial = kwargs["degree_of_polynomial"] - seasonality_blocks = kwargs["seasonality_blocks"] - seasonality_layers = kwargs["seasonality_layers"] - seasonality_layer_size = kwargs["seasonality_layer_size"] - num_of_harmonics = kwargs["num_of_harmonics"] - - trend_block = NBeatsBlock(input_size=input_size, - theta_size=2 * (degree_of_polynomial + 1), - basis_function=TrendBasis(degree_of_polynomial=degree_of_polynomial, - backcast_size=input_size, - forecast_size=output_size), - layers=trend_layers, - layer_size=trend_layer_size) - seasonality_block = NBeatsBlock(input_size=input_size, - theta_size=4 * int( - np.ceil(num_of_harmonics / 2 * output_size) - (num_of_harmonics - 1)), - basis_function=SeasonalityBasis(harmonics=num_of_harmonics, - backcast_size=input_size, - forecast_size=output_size), - layers=seasonality_layers, - layer_size=seasonality_layer_size) - self.blocks = t.nn.ModuleList( - [trend_block for _ in range(trend_blocks)] + [seasonality_block for _ in range(seasonality_blocks)]) - - def forward(self, history_data: t.Tensor, history_mask: t.Tensor, **kwargs) -> t.Tensor: - x = history_data.squeeze() - input_mask = history_mask.squeeze() - - residuals = x.flip(dims=(1,)) - input_mask = input_mask.flip(dims=(1,)) - forecast = x[:, -1:] - for i, block in enumerate(self.blocks): - backcast, block_forecast = block(residuals) - residuals = (residuals - backcast) * input_mask - forecast = forecast + block_forecast - forecast = forecast.unsqueeze(-1).unsqueeze(-1) - return forecast - - -class GenericBasis(t.nn.Module): - """ - Generic basis function. - """ - def __init__(self, backcast_size: int, forecast_size: int): - super().__init__() - self.backcast_size = backcast_size - self.forecast_size = forecast_size - - def forward(self, theta: t.Tensor): - return theta[:, :self.backcast_size], theta[:, -self.forecast_size:] - - -class TrendBasis(t.nn.Module): - """ - Polynomial function to model trend. - """ - def __init__(self, degree_of_polynomial: int, backcast_size: int, forecast_size: int): - super().__init__() - self.polynomial_size = degree_of_polynomial + 1 # degree of polynomial with constant term - self.backcast_time = t.nn.Parameter( - t.tensor(np.concatenate([np.power(np.arange(backcast_size, dtype=np.float) / backcast_size, i)[None, :] - for i in range(self.polynomial_size)]), dtype=t.float32), - requires_grad=False) - self.forecast_time = t.nn.Parameter( - t.tensor(np.concatenate([np.power(np.arange(forecast_size, dtype=np.float) / forecast_size, i)[None, :] - for i in range(self.polynomial_size)]), dtype=t.float32), requires_grad=False) - - def forward(self, theta: t.Tensor): - backcast = t.einsum('bp,pt->bt', theta[:, self.polynomial_size:], self.backcast_time) - forecast = t.einsum('bp,pt->bt', theta[:, :self.polynomial_size], self.forecast_time) - return backcast, forecast - - -class SeasonalityBasis(t.nn.Module): - """ - Harmonic functions to model seasonality. - """ - def __init__(self, harmonics: int, backcast_size: int, forecast_size: int): - super().__init__() - self.frequency = np.append(np.zeros(1, dtype=np.float32), - np.arange(harmonics, harmonics / 2 * forecast_size, - dtype=np.float32) / harmonics)[None, :] - backcast_grid = -2 * np.pi * ( - np.arange(backcast_size, dtype=np.float32)[:, None] / forecast_size) * self.frequency - forecast_grid = 2 * np.pi * ( - np.arange(forecast_size, dtype=np.float32)[:, None] / forecast_size) * self.frequency - self.backcast_cos_template = t.nn.Parameter(t.tensor(np.transpose(np.cos(backcast_grid)), dtype=t.float32), - requires_grad=False) - self.backcast_sin_template = t.nn.Parameter(t.tensor(np.transpose(np.sin(backcast_grid)), dtype=t.float32), - requires_grad=False) - self.forecast_cos_template = t.nn.Parameter(t.tensor(np.transpose(np.cos(forecast_grid)), dtype=t.float32), - requires_grad=False) - self.forecast_sin_template = t.nn.Parameter(t.tensor(np.transpose(np.sin(forecast_grid)), dtype=t.float32), - requires_grad=False) - - def forward(self, theta: t.Tensor): - params_per_harmonic = theta.shape[1] // 4 - backcast_harmonics_cos = t.einsum('bp,pt->bt', theta[:, 2 * params_per_harmonic:3 * params_per_harmonic], - self.backcast_cos_template) - backcast_harmonics_sin = t.einsum('bp,pt->bt', theta[:, 3 * params_per_harmonic:], self.backcast_sin_template) - backcast = backcast_harmonics_sin + backcast_harmonics_cos - forecast_harmonics_cos = t.einsum('bp,pt->bt', - theta[:, :params_per_harmonic], self.forecast_cos_template) - forecast_harmonics_sin = t.einsum('bp,pt->bt', theta[:, params_per_harmonic:2 * params_per_harmonic], - self.forecast_sin_template) - forecast = forecast_harmonics_sin + forecast_harmonics_cos - - return backcast, forecast diff --git a/baselines/NHiTS/ETTm2.py b/baselines/NHiTS/ETTm2.py index 0b212118..446d9565 100644 --- a/baselines/NHiTS/ETTm2.py +++ b/baselines/NHiTS/ETTm2.py @@ -1,41 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NHiTS -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NHiTS Config " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 1680 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NHiTS" -CFG.MODEL.ARCH = NHiTS -CFG.MODEL.PARAM = { - "context_length": CFG.DATASET_INPUT_LEN, - "prediction_length": CFG.DATASET_OUTPUT_LEN, +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NHiTS +NUM_NODES = 7 +MODEL_PARAM = { + "context_length": INPUT_LEN, + "prediction_length": OUTPUT_LEN, "output_size": 1, "n_blocks": [1, 1, 1], "n_layers": [2, 2, 2, 2, 2, 2, 2, 2], @@ -43,16 +34,74 @@ "pooling_sizes": [8, 8, 8], "downsample_frequencies": [24, 12, 1] } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.001, +CFG.TRAIN.OPTIM.PARAM = { + "lr":0.0005, "weight_decay":0, "eps":1.0e-8, "betas":(0.9, 0.95) @@ -63,56 +112,30 @@ "milestones":[20, 40, 60, 80], "gamma":0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "./checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 128 -CFG.TRAIN.DATA.PREFETCH = True +CFG.TRAIN.DATA.BATCH_SIZE = 32 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 4 -CFG.TRAIN.DATA.PIN_MEMORY = True -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 128 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 4 -CFG.VAL.DATA.PIN_MEMORY = True +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = os.path.join("./datasets", CFG.DATASET_NAME) -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 128 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 4 -CFG.TEST.DATA.PIN_MEMORY = True +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [6, 12, 24] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NHiTS/arch/nhits.py b/baselines/NHiTS/arch/nhits.py index 5375d02c..3b455f0b 100644 --- a/baselines/NHiTS/arch/nhits.py +++ b/baselines/NHiTS/arch/nhits.py @@ -35,7 +35,7 @@ def __init__(self, context_length: int, prediction_length: int, output_size: int dropout: float=0.0, activation: str="ReLU", initialization: str="lecun_normal", batch_normalization: bool=False, shared_weights: bool=False, naive_level: bool=True): super().__init__() - + self.prediction_length = prediction_length self.context_length = context_length self.output_size = output_size diff --git a/baselines/NLinear/ETTh1.py b/baselines/NLinear/ETTh1.py index 75f96b7c..8c8b3874 100644 --- a/baselines/NLinear/ETTh1.py +++ b/baselines/NLinear/ETTh1.py @@ -1,108 +1,134 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 7 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/ETTh2.py b/baselines/NLinear/ETTh2.py index ce5dfd1b..3ba42e65 100644 --- a/baselines/NLinear/ETTh2.py +++ b/baselines/NLinear/ETTh2.py @@ -1,108 +1,134 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 7 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/ETTm1.py b/baselines/NLinear/ETTm1.py index 4d70dc31..ac2327bd 100644 --- a/baselines/NLinear/ETTm1.py +++ b/baselines/NLinear/ETTm1.py @@ -1,108 +1,134 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 7 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/ETTm2.py b/baselines/NLinear/ETTm2.py index 6f8d9707..afa88731 100644 --- a/baselines/NLinear/ETTm2.py +++ b/baselines/NLinear/ETTm2.py @@ -1,108 +1,134 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 7 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 7 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/Electricity.py b/baselines/NLinear/Electricity.py index 46a0d74e..15533710 100644 --- a/baselines/NLinear/Electricity.py +++ b/baselines/NLinear/Electricity.py @@ -1,108 +1,134 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 321 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 321 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/ExchangeRate.py b/baselines/NLinear/ExchangeRate.py index e1180fa3..863dc10e 100644 --- a/baselines/NLinear/ExchangeRate.py +++ b/baselines/NLinear/ExchangeRate.py @@ -1,108 +1,134 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 8 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 8 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/METR-LA.py b/baselines/NLinear/METR-LA.py deleted file mode 100644 index 02b4945c..00000000 --- a/baselines/NLinear/METR-LA.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NLinear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 207 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/NLinear/PEMS-BAY.py b/baselines/NLinear/PEMS-BAY.py deleted file mode 100644 index 4e14c765..00000000 --- a/baselines/NLinear/PEMS-BAY.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NLinear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 325 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/NLinear/PEMS04.py b/baselines/NLinear/PEMS04.py deleted file mode 100644 index 2107ab66..00000000 --- a/baselines/NLinear/PEMS04.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NLinear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 307 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/NLinear/PEMS04_LTSF.py b/baselines/NLinear/PEMS04_LTSF.py new file mode 100644 index 00000000..8a3a91c8 --- /dev/null +++ b/baselines/NLinear/PEMS04_LTSF.py @@ -0,0 +1,137 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import NLinear + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 307 +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0003, + "weight_decay": 0.0001, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/PEMS08.py b/baselines/NLinear/PEMS08.py deleted file mode 100644 index 9968e6aa..00000000 --- a/baselines/NLinear/PEMS08.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.data import TimeSeriesForecastingDataset -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae - -from .arch import NLinear - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 170 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/NLinear/PEMS08_LTSF.py b/baselines/NLinear/PEMS08_LTSF.py new file mode 100644 index 00000000..5286adba --- /dev/null +++ b/baselines/NLinear/PEMS08_LTSF.py @@ -0,0 +1,137 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import NLinear + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 170 +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0003, + "weight_decay": 0.0001, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/Weather.py b/baselines/NLinear/Weather.py index 8e661bac..6a4b7cb3 100644 --- a/baselines/NLinear/Weather.py +++ b/baselines/NLinear/Weather.py @@ -1,108 +1,134 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.metrics import masked_mse, masked_mae +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import NLinear -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = NLinear +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "pred_len": OUTPUT_LEN, + "enc_in": 21 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "NLinear model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) -# ================= model ================= # +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "NLinear" -CFG.MODEL.ARCH = NLinear -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "pred_len": CFG.DATASET_OUTPUT_LEN, - "individual": False, - "enc_in": 21 -} +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0003, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/NLinear/run.sh b/baselines/NLinear/run.sh deleted file mode 100644 index b291051c..00000000 --- a/baselines/NLinear/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/NLinear/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/NLinear/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/NLinear/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/NLinear/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/NLinear/Electricity.py --gpus '0' -python experiments/train.py -c baselines/NLinear/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/NLinear/Weather.py --gpus '0' -python experiments/train.py -c baselines/NLinear/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/NLinear/PEMS08.py --gpus '0' diff --git a/baselines/PatchTST/ETTh1.py b/baselines/PatchTST/ETTh1.py index 2f799111..d16fccfd 100644 --- a/baselines/PatchTST/ETTh1.py +++ b/baselines/PatchTST/ETTh1.py @@ -1,44 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import PatchTST -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length "e_layers": 3, # num of encoder layers "n_heads": 4, "d_model": 16, @@ -55,75 +44,108 @@ "subtract_last": 0, # 0: subtract mean; 1: subtract last "decomposition": 0, # decomposition; True 1 False 0 "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, - "weight_decay": 0.0005, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/ETTh2.py b/baselines/PatchTST/ETTh2.py index 49ab3975..1113541e 100644 --- a/baselines/PatchTST/ETTh2.py +++ b/baselines/PatchTST/ETTh2.py @@ -1,44 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import PatchTST -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length "e_layers": 3, # num of encoder layers "n_heads": 16, "d_model": 128, @@ -55,75 +44,108 @@ "subtract_last": 0, # 0: subtract mean; 1: subtract last "decomposition": 0, # decomposition; True 1 False 0 "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, - "weight_decay": 0.0005, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/ETTm1.py b/baselines/PatchTST/ETTm1.py index eb6d60b3..acce10ed 100644 --- a/baselines/PatchTST/ETTm1.py +++ b/baselines/PatchTST/ETTm1.py @@ -1,44 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import PatchTST -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length "e_layers": 3, # num of encoder layers "n_heads": 16, "d_model": 128, @@ -55,75 +44,108 @@ "subtract_last": 0, # 0: subtract mean; 1: subtract last "decomposition": 0, # decomposition; True 1 False 0 "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, - "weight_decay": 0.0005, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/ETTm2.py b/baselines/PatchTST/ETTm2.py index 30bd3c73..4befeef2 100644 --- a/baselines/PatchTST/ETTm2.py +++ b/baselines/PatchTST/ETTm2.py @@ -1,50 +1,39 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import PatchTST -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length "e_layers": 3, # num of encoder layers - "n_heads": 4, - "d_model": 16, - "d_ff": 128, - "dropout": 0.3, - "fc_dropout": 0.3, + "n_heads": 16, + "d_model": 128, + "d_ff": 256, + "dropout": 0.2, + "fc_dropout": 0.2, "head_dropout": 0.0, "patch_len": 16, "stride": 8, @@ -55,75 +44,108 @@ "subtract_last": 0, # 0: subtract mean; 1: subtract last "decomposition": 0, # decomposition; True 1 False 0 "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, - "weight_decay": 0.0005, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25], + "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/Electricity.py b/baselines/PatchTST/Electricity.py index a80d8bf0..280ab228 100644 --- a/baselines/PatchTST/Electricity.py +++ b/baselines/PatchTST/Electricity.py @@ -1,44 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import PatchTST -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity Consumption" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST NUM_NODES = 321 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length "e_layers": 3, # num of encoder layers "n_heads": 16, "d_model": 128, @@ -55,75 +44,108 @@ "subtract_last": 0, # 0: subtract mean; 1: subtract last "decomposition": 0, # decomposition; True 1 False 0 "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, - "weight_decay": 0.0005, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/ExchangeRate.py b/baselines/PatchTST/ExchangeRate.py index e6b11725..f291b082 100644 --- a/baselines/PatchTST/ExchangeRate.py +++ b/baselines/PatchTST/ExchangeRate.py @@ -1,44 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import PatchTST -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST NUM_NODES = 8 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length "e_layers": 3, # num of encoder layers "n_heads": 4, "d_model": 16, @@ -55,75 +44,108 @@ "subtract_last": 0, # 0: subtract mean; 1: subtract last "decomposition": 0, # decomposition; True 1 False 0 "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, - "weight_decay": 0.0005, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/M4.py b/baselines/PatchTST/M4.py deleted file mode 100644 index edf115a9..00000000 --- a/baselines/PatchTST/M4.py +++ /dev/null @@ -1,119 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae -from basicts.data import M4ForecastingDataset -from basicts.runners import M4ForecastingRunner - -from .arch import PatchTST - -def get_cfg(seasonal_pattern): - assert seasonal_pattern in ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] - prediction_len = {"Yearly": 6, "Quarterly": 8, "Monthly": 18, "Weekly": 13, "Daily": 14, "Hourly": 48}[seasonal_pattern] - history_size = 2 - history_len = history_size * prediction_len - - CFG = EasyDict() - - # ================= general ================= # - CFG.DESCRIPTION = "Multi-layer perceptron model configuration " - CFG.RUNNER = M4ForecastingRunner - CFG.DATASET_CLS = M4ForecastingDataset - CFG.DATASET_NAME = "M4_" + seasonal_pattern - CFG.DATASET_INPUT_LEN = history_len - CFG.DATASET_OUTPUT_LEN = prediction_len - CFG.GPU_NUM = 1 - - # ================= environment ================= # - CFG.ENV = EasyDict() - CFG.ENV.SEED = 1 - CFG.ENV.CUDNN = EasyDict() - CFG.ENV.CUDNN.ENABLED = True - - # ================= model ================= # - CFG.MODEL = EasyDict() - CFG.MODEL.NAME = "PatchTST" - CFG.MODEL.ARCH = PatchTST - CFG.MODEL.PARAM = { - "enc_in": 1, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length - "e_layers": 3, # num of encoder layers - "n_heads": 4, - "d_model": 16, - "d_ff": 128, - "dropout": 0.3, - "fc_dropout": 0.3, - "head_dropout": 0.0, - "patch_len": 2, - "stride": 1, - "individual": 0, # individual head; True 1 False 0 - "padding_patch": "end", # None: None; end: padding on the end - "revin": 1, # RevIN; True 1 False 0 - "affine": 0, # RevIN-affine; True 1 False 0 - "subtract_last": 0, # 0: subtract mean; 1: subtract last - "decomposition": 0, # decomposition; True 1 False 0 - "kernel_size": 2, # decomposition-kernel - } - CFG.MODEL.FORWARD_FEATURES = [0] - CFG.MODEL.TARGET_FEATURES = [0] - - # ================= optim ================= # - CFG.TRAIN = EasyDict() - CFG.TRAIN.LOSS = masked_mae - CFG.TRAIN.OPTIM = EasyDict() - CFG.TRAIN.OPTIM.TYPE = "Adam" - CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, - } - CFG.TRAIN.LR_SCHEDULER = EasyDict() - CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" - CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 80], - "gamma": 0.5 - } - - # ================= train ================= # - CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 - } - CFG.TRAIN.NUM_EPOCHS = 100 - CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) - ) - # train data - CFG.TRAIN.DATA = EasyDict() - # read data - CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TRAIN.DATA.BATCH_SIZE = 64 - CFG.TRAIN.DATA.PREFETCH = False - CFG.TRAIN.DATA.SHUFFLE = True - CFG.TRAIN.DATA.NUM_WORKERS = 2 - CFG.TRAIN.DATA.PIN_MEMORY = False - - # ================= test ================= # - CFG.TEST = EasyDict() - CFG.TEST.INTERVAL = 5 - # test data - CFG.TEST.DATA = EasyDict() - # read data - CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TEST.DATA.BATCH_SIZE = 64 - CFG.TEST.DATA.PREFETCH = False - CFG.TEST.DATA.SHUFFLE = False - CFG.TEST.DATA.NUM_WORKERS = 2 - CFG.TEST.DATA.PIN_MEMORY = False - - # ================= evaluate ================= # - CFG.EVAL = EasyDict() - CFG.EVAL.HORIZONS = [] - CFG.EVAL.SAVE_PATH = os.path.abspath(__file__ + "/..") - - return CFG diff --git a/baselines/PatchTST/PEMS04.py b/baselines/PatchTST/PEMS04.py deleted file mode 100644 index 39570896..00000000 --- a/baselines/PatchTST/PEMS04.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import PatchTST - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST -NUM_NODES = 307 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length - "e_layers": 3, # num of encoder layers - "n_heads": 16, - "d_model": 128, - "d_ff": 256, - "dropout": 0.2, - "fc_dropout": 0.2, - "head_dropout": 0.0, - "patch_len": 32, - "stride": 16, - "individual": 0, # individual head; True 1 False 0 - "padding_patch": "end", # None: None; end: padding on the end - "revin": 1, # RevIN; True 1 False 0 - "affine": 0, # RevIN-affine; True 1 False 0 - "subtract_last": 0, # 0: subtract mean; 1: subtract last - "decomposition": 0, # decomposition; True 1 False 0 - "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.001, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/PatchTST/PEMS04_LTSF.py b/baselines/PatchTST/PEMS04_LTSF.py new file mode 100644 index 00000000..e49bafab --- /dev/null +++ b/baselines/PatchTST/PEMS04_LTSF.py @@ -0,0 +1,154 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import PatchTST + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST +NUM_NODES = 307 +MODEL_PARAM = { + "enc_in": NUM_NODES, # num nodes + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length + "e_layers": 3, # num of encoder layers + "n_heads": 16, + "d_model": 128, + "d_ff": 256, + "dropout": 0.2, + "fc_dropout": 0.2, + "head_dropout": 0.0, + "patch_len": 32, + "stride": 16, + "individual": 0, # individual head; True 1 False 0 + "padding_patch": "end", # None: None; end: padding on the end + "revin": 1, # RevIN; True 1 False 0 + "affine": 0, # RevIN-affine; True 1 False 0 + "subtract_last": 0, # 0: subtract mean; 1: subtract last + "decomposition": 0, # decomposition; True 1 False 0 + "kernel_size": 25, # decomposition-kernel + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.001, + "weight_decay": 0.0005, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25, 50], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/PEMS08.py b/baselines/PatchTST/PEMS08.py deleted file mode 100644 index b592025c..00000000 --- a/baselines/PatchTST/PEMS08.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import PatchTST - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 720 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST -NUM_NODES = 307 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length - "e_layers": 3, # num of encoder layers - "n_heads": 16, - "d_model": 128, - "d_ff": 256, - "dropout": 0.2, - "fc_dropout": 0.2, - "head_dropout": 0.0, - "patch_len": 32, - "stride": 16, - "individual": 0, # individual head; True 1 False 0 - "padding_patch": "end", # None: None; end: padding on the end - "revin": 1, # RevIN; True 1 False 0 - "affine": 0, # RevIN-affine; True 1 False 0 - "subtract_last": 0, # 0: subtract mean; 1: subtract last - "decomposition": 0, # decomposition; True 1 False 0 - "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.001, - "weight_decay": 0.0005, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/PatchTST/PEMS08_LTSF.py b/baselines/PatchTST/PEMS08_LTSF.py new file mode 100644 index 00000000..aa578450 --- /dev/null +++ b/baselines/PatchTST/PEMS08_LTSF.py @@ -0,0 +1,154 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import PatchTST + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST +NUM_NODES = 170 +MODEL_PARAM = { + "enc_in": NUM_NODES, # num nodes + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length + "e_layers": 3, # num of encoder layers + "n_heads": 16, + "d_model": 128, + "d_ff": 256, + "dropout": 0.2, + "fc_dropout": 0.2, + "head_dropout": 0.0, + "patch_len": 32, + "stride": 16, + "individual": 0, # individual head; True 1 False 0 + "padding_patch": "end", # None: None; end: padding on the end + "revin": 1, # RevIN; True 1 False 0 + "affine": 0, # RevIN-affine; True 1 False 0 + "subtract_last": 0, # 0: subtract mean; 1: subtract last + "decomposition": 0, # decomposition; True 1 False 0 + "kernel_size": 25, # decomposition-kernel + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.001, + "weight_decay": 0.0005, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25, 50], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/Weather.py b/baselines/PatchTST/Weather.py index f84d7363..30557f44 100644 --- a/baselines/PatchTST/Weather.py +++ b/baselines/PatchTST/Weather.py @@ -1,44 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import PatchTST -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "PatchTST model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "PatchTST" -CFG.MODEL.ARCH = PatchTST +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = PatchTST NUM_NODES = 21 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes - "seq_len": CFG.DATASET_INPUT_LEN, # input sequence length - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length + "seq_len": INPUT_LEN, # input sequence length + "pred_len": OUTPUT_LEN, # prediction sequence length "e_layers": 3, # num of encoder layers "n_heads": 16, "d_model": 128, @@ -55,75 +44,108 @@ "subtract_last": 0, # 0: subtract mean; 1: subtract last "decomposition": 0, # decomposition; True 1 False 0 "kernel_size": 25, # decomposition-kernel - } -) -CFG.MODEL.FORWARD_FEATURES = [0] # [raw_data, time_of_day, day_of_week, day_of_month, day_of_year] +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, - "weight_decay": 0.0005, + "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/PatchTST/arch/patchtst_arch.py b/baselines/PatchTST/arch/patchtst_arch.py index d3d2ceec..076d1b3e 100644 --- a/baselines/PatchTST/arch/patchtst_arch.py +++ b/baselines/PatchTST/arch/patchtst_arch.py @@ -24,14 +24,14 @@ def __init__(self, enc_in, seq_len, pred_len, e_layers, n_heads, d_model, d_ff, pre_norm:bool=False, store_attn:bool=False, pe:str='zeros', learn_pe:bool=True, pretrain_head:bool=False, head_type = 'flatten', verbose:bool=False, **kwargs): - + super().__init__() # load parameters c_in = enc_in context_window = seq_len target_window = pred_len - + n_layers = e_layers n_heads = n_heads d_model = d_model @@ -39,21 +39,21 @@ def __init__(self, enc_in, seq_len, pred_len, e_layers, n_heads, d_model, d_ff, dropout = dropout fc_dropout = fc_dropout head_dropout = head_dropout - - individual = individual + individual = individual + patch_len = patch_len stride = stride padding_patch = padding_patch - + revin = revin affine = affine subtract_last = subtract_last - + decomposition = decomposition kernel_size = kernel_size - - + + # model self.decomposition = decomposition if self.decomposition: @@ -83,8 +83,8 @@ def __init__(self, enc_in, seq_len, pred_len, e_layers, n_heads, d_model, d_ff, pe=pe, learn_pe=learn_pe, fc_dropout=fc_dropout, head_dropout=head_dropout, padding_patch = padding_patch, pretrain_head=pretrain_head, head_type=head_type, individual=individual, revin=revin, affine=affine, subtract_last=subtract_last, verbose=verbose, **kwargs) - - + + def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool, **kwargs) -> torch.Tensor: """Feed forward of PatchTST. @@ -96,7 +96,7 @@ def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_s """ assert history_data.shape[-1] == 1 # only use the target feature x = history_data[..., 0] # B, L, N - + if self.decomposition: res_init, trend_init = self.decomp_module(x) res_init, trend_init = res_init.permute(0,2,1), trend_init.permute(0,2,1) # x: [Batch, Channel, Input length] diff --git a/baselines/PatchTST/arch/patchtst_backbone.py b/baselines/PatchTST/arch/patchtst_backbone.py index bc588c4d..7cfeec31 100644 --- a/baselines/PatchTST/arch/patchtst_backbone.py +++ b/baselines/PatchTST/arch/patchtst_backbone.py @@ -19,13 +19,13 @@ def __init__(self, c_in:int, context_window:int, target_window:int, patch_len:in pe:str='zeros', learn_pe:bool=True, fc_dropout:float=0., head_dropout = 0, padding_patch = None, pretrain_head:bool=False, head_type = 'flatten', individual = False, revin = True, affine = True, subtract_last = False, verbose:bool=False, **kwargs): - + super().__init__() - + # RevIn self.revin = revin if self.revin: self.revin_layer = RevIN(c_in, affine=affine, subtract_last=subtract_last) - + # Patching self.patch_len = patch_len self.stride = stride @@ -34,7 +34,7 @@ def __init__(self, c_in:int, context_window:int, target_window:int, patch_len:in if padding_patch == 'end': # can be modified to general case self.padding_patch_layer = nn.ReplicationPad1d((0, stride)) patch_num += 1 - + # Backbone self.backbone = TSTiEncoder(c_in, patch_num=patch_num, patch_len=patch_len, max_seq_len=max_seq_len, n_layers=n_layers, d_model=d_model, n_heads=n_heads, d_k=d_k, d_v=d_v, d_ff=d_ff, @@ -53,32 +53,32 @@ def __init__(self, c_in:int, context_window:int, target_window:int, patch_len:in self.head = self.create_pretrain_head(self.head_nf, c_in, fc_dropout) # custom head passed as a partial func with all its kwargs elif head_type == 'flatten': self.head = Flatten_Head(self.individual, self.n_vars, self.head_nf, target_window, head_dropout=head_dropout) - + def forward(self, z): # z: [bs x nvars x seq_len] # norm if self.revin: z = z.permute(0,2,1) z = self.revin_layer(z, 'norm') z = z.permute(0,2,1) - + # do patching if self.padding_patch == 'end': z = self.padding_patch_layer(z) z = z.unfold(dimension=-1, size=self.patch_len, step=self.stride) # z: [bs x nvars x patch_num x patch_len] z = z.permute(0,1,3,2) # z: [bs x nvars x patch_len x patch_num] - + # model z = self.backbone(z) # z: [bs x nvars x d_model x patch_num] z = self.head(z) # z: [bs x nvars x target_window] - + # denorm if self.revin: z = z.permute(0,2,1) z = self.revin_layer(z, 'denorm') z = z.permute(0,2,1) return z - + def create_pretrain_head(self, head_nf, vars, dropout): return nn.Sequential(nn.Dropout(dropout), nn.Conv1d(head_nf, vars, 1) @@ -88,10 +88,10 @@ def create_pretrain_head(self, head_nf, vars, dropout): class Flatten_Head(nn.Module): def __init__(self, individual, n_vars, nf, target_window, head_dropout=0): super().__init__() - + self.individual = individual self.n_vars = n_vars - + if self.individual: self.linears = nn.ModuleList() self.dropouts = nn.ModuleList() @@ -104,7 +104,7 @@ def __init__(self, individual, n_vars, nf, target_window, head_dropout=0): self.flatten = nn.Flatten(start_dim=-2) self.linear = nn.Linear(nf, target_window) self.dropout = nn.Dropout(head_dropout) - + def forward(self, x): # x: [bs x nvars x d_model x patch_num] if self.individual: x_out = [] @@ -119,23 +119,23 @@ def forward(self, x): # x: [bs x nvars x d_model x = self.linear(x) x = self.dropout(x) return x - - + + class TSTiEncoder(nn.Module): #i means channel-independent def __init__(self, c_in, patch_num, patch_len, max_seq_len=1024, n_layers=3, d_model=128, n_heads=16, d_k=None, d_v=None, d_ff=256, norm='BatchNorm', attn_dropout=0., dropout=0., act="gelu", store_attn=False, key_padding_mask='auto', padding_var=None, attn_mask=None, res_attention=True, pre_norm=False, pe='zeros', learn_pe=True, verbose=False, **kwargs): - - + + super().__init__() - + self.patch_num = patch_num self.patch_len = patch_len - + # Input encoding q_len = patch_num self.W_P = nn.Linear(patch_len, d_model) # Eq 1: projection of feature vectors onto a d-dim vector space @@ -151,9 +151,9 @@ def __init__(self, c_in, patch_num, patch_len, max_seq_len=1024, self.encoder = TSTEncoder(q_len, d_model, n_heads, d_k=d_k, d_v=d_v, d_ff=d_ff, norm=norm, attn_dropout=attn_dropout, dropout=dropout, pre_norm=pre_norm, activation=act, res_attention=res_attention, n_layers=n_layers, store_attn=store_attn) - + def forward(self, x) -> Tensor: # x: [bs x nvars x patch_len x patch_num] - + n_vars = x.shape[1] # Input encoding x = x.permute(0,1,3,2) # x: [bs x nvars x patch_num x patch_len] @@ -166,11 +166,11 @@ def forward(self, x) -> Tensor: # x z = self.encoder(u) # z: [bs * nvars x patch_num x d_model] z = torch.reshape(z, (-1,n_vars,z.shape[-2],z.shape[-1])) # z: [bs x nvars x patch_num x d_model] z = z.permute(0,1,3,2) # z: [bs x nvars x d_model x patch_num] - - return z - - + return z + + + # Cell class TSTEncoder(nn.Module): def __init__(self, q_len, d_model, n_heads, d_k=None, d_v=None, d_ff=None, diff --git a/baselines/PatchTST/arch/patchtst_layers.py b/baselines/PatchTST/arch/patchtst_layers.py index 945d315a..aeb2df72 100644 --- a/baselines/PatchTST/arch/patchtst_layers.py +++ b/baselines/PatchTST/arch/patchtst_layers.py @@ -10,14 +10,14 @@ def forward(self, x): if self.contiguous: return x.transpose(*self.dims).contiguous() else: return x.transpose(*self.dims) - + def get_activation_fn(activation): if callable(activation): return activation() elif activation.lower() == "relu": return nn.ReLU() elif activation.lower() == "gelu": return nn.GELU() raise ValueError(f'{activation} is not available. You can use "relu", "gelu", or a callable') - - + + # decomposition class moving_avg(nn.Module): @@ -51,9 +51,9 @@ def forward(self, x): moving_mean = self.moving_avg(x) res = x - moving_mean return res, moving_mean - - - + + + # pos_encoding def PositionalEncoding(q_len, d_model, normalize=True): diff --git a/baselines/PatchTST/run.sh b/baselines/PatchTST/run.sh deleted file mode 100644 index 5802fb23..00000000 --- a/baselines/PatchTST/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/PatchTST/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/PatchTST/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/PatchTST/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/PatchTST/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/PatchTST/Electricity.py --gpus '0' -python experiments/train.py -c baselines/PatchTST/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/PatchTST/Weather.py --gpus '0' -python experiments/train.py -c baselines/PatchTST/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/PatchTST/PEMS08.py --gpus '0' diff --git a/baselines/Pyraformer/ETTh1.py b/baselines/Pyraformer/ETTh1.py index 53d34407..b4b76630 100644 --- a/baselines/Pyraformer/ETTh1.py +++ b/baselines/Pyraformer/ETTh1.py @@ -1,48 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -import torch +sys.path.append(os.path.abspath(__file__ + '/../../..')) -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Pyraformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, "d_model": 512, "d_inner_hid": 512, "d_k": 128, @@ -64,65 +52,106 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/ETTh2.py b/baselines/Pyraformer/ETTh2.py index 98d7090c..27d503e3 100644 --- a/baselines/Pyraformer/ETTh2.py +++ b/baselines/Pyraformer/ETTh2.py @@ -1,48 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -import torch +sys.path.append(os.path.abspath(__file__ + '/../../..')) -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Pyraformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 192 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, "d_model": 512, "d_inner_hid": 512, "d_k": 128, @@ -64,65 +52,106 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL -# ================= optim ================= # +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/ETTm1.py b/baselines/Pyraformer/ETTm1.py index c4b1e07a..e0eea048 100644 --- a/baselines/Pyraformer/ETTm1.py +++ b/baselines/Pyraformer/ETTm1.py @@ -1,48 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -import torch +sys.path.append(os.path.abspath(__file__ + '/../../..')) -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Pyraformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, "d_model": 512, "d_inner_hid": 512, "d_k": 128, @@ -59,70 +47,111 @@ "use_tvm": False, "embed": "DataEmbedding", "num_time_features": 4, - "time_of_day_size": 24, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/ETTm2.py b/baselines/Pyraformer/ETTm2.py index 97e9af10..92d49079 100644 --- a/baselines/Pyraformer/ETTm2.py +++ b/baselines/Pyraformer/ETTm2.py @@ -1,48 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -import torch +sys.path.append(os.path.abspath(__file__ + '/../../..')) -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Pyraformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 192 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, "d_model": 512, "d_inner_hid": 512, "d_k": 128, @@ -59,70 +47,111 @@ "use_tvm": False, "embed": "DataEmbedding", "num_time_features": 4, - "time_of_day_size": 24, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/Electricity.py b/baselines/Pyraformer/Electricity.py index a36cb9f1..efd8a170 100644 --- a/baselines/Pyraformer/Electricity.py +++ b/baselines/Pyraformer/Electricity.py @@ -1,48 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -import torch +sys.path.append(os.path.abspath(__file__ + '/../../..')) -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Pyraformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity Consumption" -CFG.DATASET_INPUT_LEN = 192 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer NUM_NODES = 321 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, "d_model": 512, "d_inner_hid": 512, "d_k": 128, @@ -59,69 +47,111 @@ "use_tvm": False, "embed": "DataEmbedding", "num_time_features": 4, - "time_of_day_size": 144, + "time_of_day_size": 24, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/ExchangeRate.py b/baselines/Pyraformer/ExchangeRate.py index cf40c382..19db416a 100644 --- a/baselines/Pyraformer/ExchangeRate.py +++ b/baselines/Pyraformer/ExchangeRate.py @@ -1,48 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -import torch +sys.path.append(os.path.abspath(__file__ + '/../../..')) -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Pyraformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 192 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer NUM_NODES = 8 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, "d_model": 512, "d_inner_hid": 512, "d_k": 128, @@ -64,64 +52,106 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/PEMS04.py b/baselines/Pyraformer/PEMS04.py deleted file mode 100644 index edd265f6..00000000 --- a/baselines/Pyraformer/PEMS04.py +++ /dev/null @@ -1,126 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -import torch - -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import Pyraformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer -NUM_NODES = 307 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, - "d_model": 512, - "d_inner_hid": 512, - "d_k": 128, - "d_v": 128, - "d_bottleneck": 128, - "n_head": 4, - "n_layer": 4, - "dropout": 0.05, - "decoder": "FC", # FC or attention - "window_size": "[2, 2, 2]", - "inner_size": 5, - "CSCM": "Bottleneck_Construct", - "truncate": False, - "use_tvm": False, - "embed": "DataEmbedding", - "num_time_features": 2, - "time_of_day_size": 288, - "day_of_week_size": 7, - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0005 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Pyraformer/PEMS04_LTSF.py b/baselines/Pyraformer/PEMS04_LTSF.py new file mode 100644 index 00000000..76e1de63 --- /dev/null +++ b/baselines/Pyraformer/PEMS04_LTSF.py @@ -0,0 +1,157 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Pyraformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 96 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer +NUM_NODES = 307 +MODEL_PARAM = { + "enc_in": NUM_NODES, # num nodes + "dec_in": NUM_NODES, + "c_out": NUM_NODES, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, + "d_model": 512, + "d_inner_hid": 512, + "d_k": 128, + "d_v": 128, + "d_bottleneck": 128, + "n_head": 4, + "n_layer": 4, + "dropout": 0.05, + "decoder": "FC", # FC or attention + "window_size": "[2, 2, 2]", + "inner_size": 5, + "CSCM": "Bottleneck_Construct", + "truncate": False, + "use_tvm": False, + "embed": "DataEmbedding", + "num_time_features": 2, + "time_of_day_size": 288, + "day_of_week_size": 7, + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0005 +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/PEMS08.py b/baselines/Pyraformer/PEMS08.py deleted file mode 100644 index c7fd9f21..00000000 --- a/baselines/Pyraformer/PEMS08.py +++ /dev/null @@ -1,126 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -import torch - -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae - -from .arch import Pyraformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer -NUM_NODES = 170 -CFG.MODEL.PARAM = EasyDict( - { - "enc_in": NUM_NODES, # num nodes - "dec_in": NUM_NODES, - "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, - "d_model": 512, - "d_inner_hid": 512, - "d_k": 128, - "d_v": 128, - "d_bottleneck": 128, - "n_head": 4, - "n_layer": 4, - "dropout": 0.05, - "decoder": "FC", # FC or attention - "window_size": "[2, 2, 2]", - "inner_size": 5, - "CSCM": "Bottleneck_Construct", - "truncate": False, - "use_tvm": False, - "embed": "DataEmbedding", - "num_time_features": 2, - "time_of_day_size": 288, - "day_of_week_size": 7, - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0005 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Pyraformer/PEMS08_LTSF.py b/baselines/Pyraformer/PEMS08_LTSF.py new file mode 100644 index 00000000..8c72c447 --- /dev/null +++ b/baselines/Pyraformer/PEMS08_LTSF.py @@ -0,0 +1,157 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Pyraformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +INPUT_LEN = 96 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer +NUM_NODES = 170 +MODEL_PARAM = { + "enc_in": NUM_NODES, # num nodes + "dec_in": NUM_NODES, + "c_out": NUM_NODES, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, + "d_model": 512, + "d_inner_hid": 512, + "d_k": 128, + "d_v": 128, + "d_bottleneck": 128, + "n_head": 4, + "n_layer": 4, + "dropout": 0.05, + "decoder": "FC", # FC or attention + "window_size": "[2, 2, 2]", + "inner_size": 5, + "CSCM": "Bottleneck_Construct", + "truncate": False, + "use_tvm": False, + "embed": "DataEmbedding", + "num_time_features": 2, + "time_of_day_size": 288, + "day_of_week_size": 7, + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0005 +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/Weather.py b/baselines/Pyraformer/Weather.py index f4ae15b6..c661e32c 100644 --- a/baselines/Pyraformer/Weather.py +++ b/baselines/Pyraformer/Weather.py @@ -1,48 +1,36 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -import torch +sys.path.append(os.path.abspath(__file__ + '/../../..')) -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Pyraformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Pyraformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather Data" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Pyraformer" -CFG.MODEL.ARCH = Pyraformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 # better results than regular_settings['INPUT_LEN'] (336) +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Pyraformer NUM_NODES = 21 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "enc_in": NUM_NODES, # num nodes "dec_in": NUM_NODES, "c_out": NUM_NODES, - "input_size": CFG.DATASET_INPUT_LEN, - "predict_step": CFG.DATASET_OUTPUT_LEN, + "input_size": INPUT_LEN, + "predict_step": OUTPUT_LEN, "d_model": 512, "d_inner_hid": 512, "d_k": 128, @@ -64,64 +52,106 @@ "day_of_month_size": 31, "day_of_year_size": 366 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Pyraformer/run.sh b/baselines/Pyraformer/run.sh deleted file mode 100644 index b1e61c57..00000000 --- a/baselines/Pyraformer/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/Pyraformer/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/Pyraformer/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/Pyraformer/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/Pyraformer/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/Pyraformer/Electricity.py --gpus '0' -python experiments/train.py -c baselines/Pyraformer/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/Pyraformer/Weather.py --gpus '0' -python experiments/train.py -c baselines/Pyraformer/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/Pyraformer/PEMS08.py --gpus '0' diff --git a/baselines/STAEformer/METR-LA.py b/baselines/STAEformer/METR-LA.py index b6b02c83..bd7690b6 100644 --- a/baselines/STAEformer/METR-LA.py +++ b/baselines/STAEformer/METR-LA.py @@ -1,112 +1,132 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STAEformer -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STAEformer -# ================= general ================= # -CFG.DESCRIPTION = "STAEformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STAEformer" -CFG.MODEL.ARCH = STAEformer -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") -adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes" : 207 } -CFG.MODEL.FORWARD_FEATURES = [0,1,2] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 25], "gamma": 0.1 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 30 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STAEformer/PEMS-BAY.py b/baselines/STAEformer/PEMS-BAY.py index 9a5030cb..8ce154c5 100644 --- a/baselines/STAEformer/PEMS-BAY.py +++ b/baselines/STAEformer/PEMS-BAY.py @@ -1,112 +1,132 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STAEformer -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STAEformer + +MODEL_PARAM = { + "num_nodes" : 325 +} +NUM_EPOCHS = 100 -# ================= general ================= # -CFG.DESCRIPTION = "STAEformer model configuration" +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STAEformer" -CFG.MODEL.ARCH = STAEformer -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") -adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { - "num_nodes" : 207 -} -CFG.MODEL.FORWARD_FEATURES = [0,1,2] +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [20, 25], "gamma": 0.1 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 30 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STAEformer/PEMS03.py b/baselines/STAEformer/PEMS03.py index d38d1f52..2a7db9cd 100644 --- a/baselines/STAEformer/PEMS03.py +++ b/baselines/STAEformer/PEMS03.py @@ -1,112 +1,132 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STAEformer -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STAEformer -# ================= general ================= # -CFG.DESCRIPTION = "STAEformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STAEformer" -CFG.MODEL.ARCH = STAEformer -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") -adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes" : 358 } -CFG.MODEL.FORWARD_FEATURES = [0,1,2] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, - "weight_decay": 0.0015, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [25, 45, 65], + "milestones": [20, 25], "gamma": 0.1 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 70 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STAEformer/PEMS04.py b/baselines/STAEformer/PEMS04.py index c8a2c827..c6e3b126 100644 --- a/baselines/STAEformer/PEMS04.py +++ b/baselines/STAEformer/PEMS04.py @@ -1,112 +1,132 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STAEformer -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STAEformer -# ================= general ================= # -CFG.DESCRIPTION = "STAEformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STAEformer" -CFG.MODEL.ARCH = STAEformer -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") -adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes" : 307 } -CFG.MODEL.FORWARD_FEATURES = [0,1,2] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, - "weight_decay": 0.0015, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [25, 45, 65], + "milestones": [20, 25], "gamma": 0.1 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 70 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STAEformer/PEMS07.py b/baselines/STAEformer/PEMS07.py index 0c3994a6..664ff3cb 100644 --- a/baselines/STAEformer/PEMS07.py +++ b/baselines/STAEformer/PEMS07.py @@ -1,112 +1,132 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STAEformer -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STAEformer -# ================= general ================= # -CFG.DESCRIPTION = "STAEformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STAEformer" -CFG.MODEL.ARCH = STAEformer -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") -adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes" : 883 } -CFG.MODEL.FORWARD_FEATURES = [0,1,2] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, - "weight_decay": 0.0015, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [25, 45, 65], + "milestones": [20, 25], "gamma": 0.1 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 70 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 8 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STAEformer/PEMS08.py b/baselines/STAEformer/PEMS08.py index 4a3a0c45..0ffcd096 100644 --- a/baselines/STAEformer/PEMS08.py +++ b/baselines/STAEformer/PEMS08.py @@ -1,112 +1,132 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STAEformer -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STAEformer -# ================= general ================= # -CFG.DESCRIPTION = "STAEformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STAEformer" -CFG.MODEL.ARCH = STAEformer -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") -adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "num_nodes" : 170 } -CFG.MODEL.FORWARD_FEATURES = [0,1,2] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001, - "weight_decay": 0.0015, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [25, 45, 65], + "milestones": [20, 25], "gamma": 0.1 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# "max_norm": 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 70 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STAEformer/run.sh b/baselines/STAEformer/run.sh deleted file mode 100644 index 1e92166e..00000000 --- a/baselines/STAEformer/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/STAEformer/METR-LA.py --gpus '0' -python experiments/train.py -c baselines/STAEformer/PEMS-BAY.py --gpus '0' -python experiments/train.py -c baselines/STAEformer/PEMS03.py --gpus '0' -python experiments/train.py -c baselines/STAEformer/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/STAEformer/PEMS07.py --gpus '0' -python experiments/train.py -c baselines/STAEformer/PEMS08.py --gpus '0' diff --git a/baselines/STEP/README.md b/baselines/STEP/README.md index f8b79f8c..eae8c956 100644 --- a/baselines/STEP/README.md +++ b/baselines/STEP/README.md @@ -1,2 +1 @@ -STEP requires a pre-trained TSFormer model. You can download them from [here](https://github.com/zezhishao/STEP/tree/github/tsformer_ckpt) and place them in the `./ckpts/` folder. -In addition, STEP requires `timm` package. You can install it by `pip install timm`. +STEP requires `timm` package. You can install it by `pip install timm`. diff --git a/baselines/STEP/STEP_METR-LA.py b/baselines/STEP/STEP_METR-LA.py index 1eb513ff..fb359074 100644 --- a/baselines/STEP/STEP_METR-LA.py +++ b/baselines/STEP/STEP_METR-LA.py @@ -1,49 +1,36 @@ import os import sys - - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.utils.serialization import load_adj - -from .step_arch import STEP -from .step_runner import STEPRunner -from .step_loss import step_loss -from .step_data import ForecastingDataset - +sys.path.append(os.path.abspath(__file__ + '/../../..')) -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STEP(METR-LA) configuration" -CFG.RUNNER = STEPRunner -CFG.DATASET_CLS = ForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.DATASET_ARGS = { - "seq_len": 288 * 7 - } -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.scaler import ZScoreScaler +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.utils import get_regular_settings -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True +from .arch import STEP +from .loss import step_loss -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STEP" -CFG.MODEL.ARCH = STEP - -CFG.MODEL.PARAM = { - "dataset_name": CFG.DATASET_NAME, - "pre_trained_tsformer_path": "baselines/STEP/ckpts/TSFormer_METR-LA.pt", +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN_SHORT = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 288 * 7 +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STEP +MODEL_PARAM = { + "dataset_name": DATA_NAME, + "pre_trained_tsformer_path": "checkpoints/TSFormer/METR-LA_100_2016_12/de9f10ca8535dbe99fb71072aab848ce/TSFormer_best_val_MAE.pt", + "short_term_len": INPUT_LEN_SHORT, + "long_term_len": INPUT_LEN, "tsformer_args": { "patch_size":12, "in_channel":1, @@ -75,49 +62,95 @@ "layers" : 2 }, "dgl_args": { - "dataset_name": CFG.DATASET_NAME, + "dataset_name": DATA_NAME, "k": 10, - "input_seq_len": CFG.DATASET_INPUT_LEN, - "output_seq_len": CFG.DATASET_OUTPUT_LEN + "input_seq_len": INPUT_LEN, + "output_seq_len": OUTPUT_LEN } } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 2 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] CFG.MODEL.DDP_FIND_UNUSED_PARAMETERS = True -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = step_loss +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { +CFG.TRAIN.OPTIM.PARAM = { "lr":0.005, "weight_decay":1.0e-5, "eps":1.0e-8, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { +CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones":[1, 18, 36, 54, 72], "gamma":0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True CFG.TRAIN.DATA.NUM_WORKERS = 2 CFG.TRAIN.DATA.PIN_MEMORY = True @@ -127,35 +160,26 @@ CFG.TRAIN.CL.CL_EPOCHS = 6 CFG.TRAIN.CL.PREDICTION_LENGTH = 12 -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 32 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False CFG.VAL.DATA.NUM_WORKERS = 2 CFG.VAL.DATA.PIN_MEMORY = True -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# evluation -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 32 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False CFG.TEST.DATA.NUM_WORKERS = 2 CFG.TEST.DATA.PIN_MEMORY = True -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = range(1, 13) # 1, 2, ..., 12 +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STEP/STEP_PEMS-BAY.py b/baselines/STEP/STEP_PEMS-BAY.py deleted file mode 100644 index b0b0bbe4..00000000 --- a/baselines/STEP/STEP_PEMS-BAY.py +++ /dev/null @@ -1,160 +0,0 @@ -import os -import sys - - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.utils.serialization import load_adj - -from .step_arch import STEP -from .step_runner import STEPRunner -from .step_loss import step_loss -from .step_data import ForecastingDataset - - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STEP(PEMS-BAY) configuration" -CFG.RUNNER = STEPRunner -CFG.DATASET_CLS = ForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.DATASET_ARGS = { - "seq_len": 288 * 7 - } -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STEP" -CFG.MODEL.ARCH = STEP -CFG.MODEL.PARAM = { - "dataset_name": CFG.DATASET_NAME, - "pre_trained_tsformer_path": "baselines/STEP/ckpts/TSFormer_PEMS-BAY.pt", - "tsformer_args": { - "patch_size":12, - "in_channel":1, - "embed_dim":96, - "num_heads":4, - "mlp_ratio":4, - "dropout":0.1, - "num_token":288 * 7 / 12, - "mask_ratio":0.75, - "encoder_depth":4, - "decoder_depth":1, - "mode":"forecasting" - }, - "backend_args": { - "num_nodes" : 325, - "support_len" : 2, - "dropout" : 0.3, - "gcn_bool" : True, - "addaptadj" : True, - "aptinit" : None, - "in_dim" : 2, - "out_dim" : 12, - "residual_channels" : 32, - "dilation_channels" : 32, - "skip_channels" : 256, - "end_channels" : 512, - "kernel_size" : 2, - "blocks" : 4, - "layers" : 2 - }, - "dgl_args": { - "dataset_name": CFG.DATASET_NAME, - "k": 10, - "input_seq_len": CFG.DATASET_INPUT_LEN, - "output_seq_len": CFG.DATASET_OUTPUT_LEN - } -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.DDP_FIND_UNUSED_PARAMETERS = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = step_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.001, - "weight_decay":1.0e-5, - "eps":1.0e-8, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[1, 18, 36, 54, 72], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = True -# curriculum learning -CFG.TRAIN.CL = EasyDict() -CFG.TRAIN.CL.WARM_EPOCHS = 30 -CFG.TRAIN.CL.CL_EPOCHS = 3 -CFG.TRAIN.CL.PREDICTION_LENGTH = 12 - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 32 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 32 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/STEP/STEP_PEMS03.py b/baselines/STEP/STEP_PEMS03.py deleted file mode 100644 index b41812ed..00000000 --- a/baselines/STEP/STEP_PEMS03.py +++ /dev/null @@ -1,155 +0,0 @@ -import os -import sys - - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.utils.serialization import load_adj - -from .step_arch import STEP -from .step_runner import STEPRunner -from .step_loss import step_loss -from .step_data import ForecastingDataset - - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STEP(PEMS03) configuration" -CFG.RUNNER = STEPRunner -CFG.DATASET_CLS = ForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.DATASET_ARGS = { - "seq_len": 288 * 7 * 2 - } -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STEP" -CFG.MODEL.ARCH = STEP -CFG.MODEL.PARAM = { - "dataset_name": CFG.DATASET_NAME, - "pre_trained_tsformer_path": "baselines/STEP/ckpts/TSFormer_PEMS03.pt", - "tsformer_args": { - "patch_size":12, - "in_channel":1, - "embed_dim":96, - "num_heads":4, - "mlp_ratio":4, - "dropout":0.1, - "num_token":288 * 7 * 2 / 12, - "mask_ratio":0.75, - "encoder_depth":4, - "decoder_depth":1, - "mode":"forecasting" - }, - "backend_args": { - "num_nodes" : 358, - "support_len" : 2, - "dropout" : 0.3, - "gcn_bool" : True, - "addaptadj" : True, - "aptinit" : None, - "in_dim" : 2, - "out_dim" : 12, - "residual_channels" : 32, - "dilation_channels" : 32, - "skip_channels" : 256, - "end_channels" : 512, - "kernel_size" : 2, - "blocks" : 4, - "layers" : 2 - }, - "dgl_args": { - "dataset_name": CFG.DATASET_NAME, - "k": 10, - "input_seq_len": CFG.DATASET_INPUT_LEN, - "output_seq_len": CFG.DATASET_OUTPUT_LEN - } -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.DDP_FIND_UNUSED_PARAMETERS = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = step_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.002, - "weight_decay":1.0e-5, - "eps":1.0e-8, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[1, 18, 36, 54, 72], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 8 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 8 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 8 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/STEP/STEP_PEMS04.py b/baselines/STEP/STEP_PEMS04.py deleted file mode 100644 index 62150592..00000000 --- a/baselines/STEP/STEP_PEMS04.py +++ /dev/null @@ -1,155 +0,0 @@ -import os -import sys - - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.utils.serialization import load_adj - -from .step_arch import STEP -from .step_runner import STEPRunner -from .step_loss import step_loss -from .step_data import ForecastingDataset - - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STEP(PEMS04) configuration" -CFG.RUNNER = STEPRunner -CFG.DATASET_CLS = ForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.DATASET_ARGS = { - "seq_len": 288 * 7 * 2 - } -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STEP" -CFG.MODEL.ARCH = STEP -CFG.MODEL.PARAM = { - "dataset_name": CFG.DATASET_NAME, - "pre_trained_tsformer_path": "baselines/STEP/ckpts/TSFormer_PEMS04.pt", - "tsformer_args": { - "patch_size":12, - "in_channel":1, - "embed_dim":96, - "num_heads":4, - "mlp_ratio":4, - "dropout":0.1, - "num_token":288 * 7 * 2 / 12, - "mask_ratio":0.75, - "encoder_depth":4, - "decoder_depth":1, - "mode":"forecasting" - }, - "backend_args": { - "num_nodes" : 307, - "support_len" : 2, - "dropout" : 0.3, - "gcn_bool" : True, - "addaptadj" : True, - "aptinit" : None, - "in_dim" : 2, - "out_dim" : 12, - "residual_channels" : 32, - "dilation_channels" : 32, - "skip_channels" : 256, - "end_channels" : 512, - "kernel_size" : 2, - "blocks" : 4, - "layers" : 2 - }, - "dgl_args": { - "dataset_name": CFG.DATASET_NAME, - "k": 10, - "input_seq_len": CFG.DATASET_INPUT_LEN, - "output_seq_len": CFG.DATASET_OUTPUT_LEN - } -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.DDP_FIND_UNUSED_PARAMETERS = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = step_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.002, - "weight_decay":1.0e-5, - "eps":1.0e-8, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[1, 18, 36, 54, 72], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 8 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 8 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 8 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/STEP/STEP_PEMS07.py b/baselines/STEP/STEP_PEMS07.py deleted file mode 100644 index 8c113d53..00000000 --- a/baselines/STEP/STEP_PEMS07.py +++ /dev/null @@ -1,155 +0,0 @@ -import os -import sys - - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.utils.serialization import load_adj - -from .step_arch import STEP -from .step_runner import STEPRunner -from .step_loss import step_loss -from .step_data import ForecastingDataset - - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STEP(PEMS07) configuration" -CFG.RUNNER = STEPRunner -CFG.DATASET_CLS = ForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.DATASET_ARGS = { - "seq_len": 288 * 7 - } -CFG.GPU_NUM = 2 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STEP" -CFG.MODEL.ARCH = STEP -CFG.MODEL.PARAM = { - "dataset_name": CFG.DATASET_NAME, - "pre_trained_tsformer_path": "baselines/STEP/ckpts/TSFormer_PEMS07.pt", - "tsformer_args": { - "patch_size":12, - "in_channel":1, - "embed_dim":96, - "num_heads":4, - "mlp_ratio":4, - "dropout":0.1, - "num_token":288 * 7 / 12, - "mask_ratio":0.75, - "encoder_depth":4, - "decoder_depth":1, - "mode":"forecasting" - }, - "backend_args": { - "num_nodes" : 883, - "support_len" : 2, - "dropout" : 0.3, - "gcn_bool" : True, - "addaptadj" : True, - "aptinit" : None, - "in_dim" : 2, - "out_dim" : 12, - "residual_channels" : 32, - "dilation_channels" : 32, - "skip_channels" : 256, - "end_channels" : 512, - "kernel_size" : 2, - "blocks" : 4, - "layers" : 2 - }, - "dgl_args": { - "dataset_name": CFG.DATASET_NAME, - "k": 10, - "input_seq_len": CFG.DATASET_INPUT_LEN, - "output_seq_len": CFG.DATASET_OUTPUT_LEN - } -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.DDP_FIND_UNUSED_PARAMETERS = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = step_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.002, - "weight_decay":1.0e-5, - "eps":1.0e-8, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[1, 18, 36, 54, 72], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -CFG.TRAIN.NULL_VAL = 0.0 -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 8 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 8 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 100 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 8 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/STEP/STEP_PEMS08.py b/baselines/STEP/STEP_PEMS08.py deleted file mode 100644 index d2cc5a9b..00000000 --- a/baselines/STEP/STEP_PEMS08.py +++ /dev/null @@ -1,155 +0,0 @@ -import os -import sys - - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.utils.serialization import load_adj - -from .step_arch import STEP -from .step_runner import STEPRunner -from .step_loss import step_loss -from .step_data import ForecastingDataset - - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STEP(PEMS08) configuration" -CFG.RUNNER = STEPRunner -CFG.DATASET_CLS = ForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.DATASET_ARGS = { - "seq_len": 288 * 7 * 2 - } -CFG.GPU_NUM = 4 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STEP" -CFG.MODEL.ARCH = STEP -CFG.MODEL.PARAM = { - "dataset_name": CFG.DATASET_NAME, - "pre_trained_tsformer_path": "baselines/STEP/ckpts/TSFormer_PEMS08.pt", - "tsformer_args": { - "patch_size":12, - "in_channel":1, - "embed_dim":96, - "num_heads":4, - "mlp_ratio":4, - "dropout":0.1, - "num_token":288 * 7 * 2 / 12, - "mask_ratio":0.75, - "encoder_depth":4, - "decoder_depth":1, - "mode":"forecasting" - }, - "backend_args": { - "num_nodes" : 170, - "support_len" : 2, - "dropout" : 0.3, - "gcn_bool" : True, - "addaptadj" : True, - "aptinit" : None, - "in_dim" : 2, - "out_dim" : 12, - "residual_channels" : 32, - "dilation_channels" : 32, - "skip_channels" : 256, - "end_channels" : 512, - "kernel_size" : 2, - "blocks" : 4, - "layers" : 2 - }, - "dgl_args": { - "dataset_name": CFG.DATASET_NAME, - "k": 10, - "input_seq_len": CFG.DATASET_INPUT_LEN, - "output_seq_len": CFG.DATASET_OUTPUT_LEN - } -} -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] -CFG.MODEL.DDP_FIND_UNUSED_PARAMETERS = True - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = step_loss -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.002, - "weight_decay":1.0e-5, - "eps":1.0e-8, -} -CFG.TRAIN.LR_SCHEDULER = EasyDict() -CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" -CFG.TRAIN.LR_SCHEDULER.PARAM= { - "milestones":[1, 18, 36, 54, 72], - "gamma":0.5 -} - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 3.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 8 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = True - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 8 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = True - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# evluation -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 8 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = True - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] diff --git a/baselines/STEP/TSFormer_METR-LA.py b/baselines/STEP/TSFormer_METR-LA.py new file mode 100644 index 00000000..5dd8a776 --- /dev/null +++ b/baselines/STEP/TSFormer_METR-LA.py @@ -0,0 +1,152 @@ +import os +import sys +import torch +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.scaler import ZScoreScaler +from basicts.data import TimeSeriesForecastingDataset +from basicts.utils import get_regular_settings + +from .arch import TSFormer +from .runner import TSFormerRunner + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 288 * 7 +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = TSFormer +MODEL_PARAM = { + "patch_size":12, + "in_channel":1, + "embed_dim":96, + "num_heads":4, + "mlp_ratio":4, + "dropout":0.1, + "num_token":288 * 7 / 12, + "mask_ratio":0.75, + "encoder_depth":4, + "decoder_depth":1, + "mode":"pre-train" +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = TSFormerRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr":0.0005, + "weight_decay":0, + "eps":1.0e-8, + "betas":(0.9, 0.95) +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones":[50], + "gamma":0.5 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 8 +CFG.TRAIN.DATA.SHUFFLE = True +CFG.TRAIN.DATA.NUM_WORKERS = 2 +CFG.TRAIN.DATA.PIN_MEMORY = True +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 8 +CFG.VAL.DATA.NUM_WORKERS = 2 +CFG.VAL.DATA.PIN_MEMORY = True + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 8 +CFG.TEST.DATA.NUM_WORKERS = 2 +CFG.TEST.DATA.PIN_MEMORY = True + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STEP/step_arch/__init__.py b/baselines/STEP/arch/__init__.py similarity index 100% rename from baselines/STEP/step_arch/__init__.py rename to baselines/STEP/arch/__init__.py diff --git a/baselines/STEP/step_arch/discrete_graph_learning.py b/baselines/STEP/arch/discrete_graph_learning.py similarity index 97% rename from baselines/STEP/step_arch/discrete_graph_learning.py rename to baselines/STEP/arch/discrete_graph_learning.py index 91bdab00..dfbc40c0 100644 --- a/baselines/STEP/step_arch/discrete_graph_learning.py +++ b/baselines/STEP/arch/discrete_graph_learning.py @@ -3,7 +3,7 @@ import numpy as np from torch import nn import torch.nn.functional as F -from basicts.utils import load_pkl +from basicts.utils import load_dataset_data from .similarity import batch_cosine_similarity, batch_dot_similarity @@ -54,7 +54,7 @@ def __init__(self, dataset_name, k, input_seq_len, output_seq_len): self.k = k # the "k" of knn graph self.num_nodes = {"METR-LA": 207, "PEMS04": 307, "PEMS03": 358, "PEMS-BAY": 325, "PEMS07": 883, "PEMS08": 170}[dataset_name] self.train_length = {"METR-LA": 23990, "PEMS04": 13599, "PEMS03": 15303, "PEMS07": 16513, "PEMS-BAY": 36482, "PEMS08": 14284}[dataset_name] - self.node_feats = torch.from_numpy(load_pkl("datasets/" + dataset_name + "/data_in_{0}_out_{1}_rescale_True.pkl".format(input_seq_len, output_seq_len))["processed_data"]).float()[:self.train_length, :, 0] + self.node_feats = torch.from_numpy(load_dataset_data(dataset_name))[:self.train_length, :, 0] # CNN for global feature extraction ## for the dimension, see https://github.com/zezhishao/STEP/issues/1#issuecomment-1191640023 diff --git a/baselines/STEP/step_arch/graphwavenet/__init__.py b/baselines/STEP/arch/graphwavenet/__init__.py similarity index 100% rename from baselines/STEP/step_arch/graphwavenet/__init__.py rename to baselines/STEP/arch/graphwavenet/__init__.py diff --git a/baselines/STEP/step_arch/graphwavenet/model.py b/baselines/STEP/arch/graphwavenet/model.py similarity index 100% rename from baselines/STEP/step_arch/graphwavenet/model.py rename to baselines/STEP/arch/graphwavenet/model.py diff --git a/baselines/STEP/step_arch/similarity.py b/baselines/STEP/arch/similarity.py similarity index 100% rename from baselines/STEP/step_arch/similarity.py rename to baselines/STEP/arch/similarity.py diff --git a/baselines/STEP/step_arch/step.py b/baselines/STEP/arch/step.py similarity index 68% rename from baselines/STEP/step_arch/step.py rename to baselines/STEP/arch/step.py index 13c71df3..41022c39 100644 --- a/baselines/STEP/step_arch/step.py +++ b/baselines/STEP/arch/step.py @@ -9,8 +9,12 @@ class STEP(nn.Module): """Pre-training Enhanced Spatial-temporal Graph Neural Network for Multivariate Time Series Forecasting""" - def __init__(self, dataset_name, pre_trained_tsformer_path, tsformer_args, backend_args, dgl_args): + def __init__(self, dataset_name, pre_trained_tsformer_path, short_term_len, long_term_len, tsformer_args, backend_args, dgl_args): super().__init__() + + self.short_term_len = short_term_len + self.long_term_len = long_term_len + self.dataset_name = dataset_name self.pre_trained_tsformer_path = pre_trained_tsformer_path @@ -34,25 +38,11 @@ def load_pre_trained_model(self): for param in self.tsformer.parameters(): param.requires_grad = False - def forward(self, history_data: torch.Tensor, long_history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, **kwargs) -> torch.Tensor: - """Feed forward of STEP. - - Args: - history_data (torch.Tensor): Short-term historical data. shape: [B, L, N, 3] - long_history_data (torch.Tensor): Long-term historical data. shape: [B, L * P, N, 3] - future_data (torch.Tensor): future data - batch_seen (int): number of batches that have been seen - epoch (int): number of epochs - - Returns: - torch.Tensor: prediction with shape [B, N, L]. - torch.Tensor: the Bernoulli distribution parameters with shape [B, N, N]. - torch.Tensor: the kNN graph with shape [B, N, N], which is used to guide the training of the dependency graph. - """ + def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, **kwargs) -> torch.Tensor: # reshape - short_term_history = history_data # [B, L, N, 1] - long_term_history = long_history_data + long_term_history = history_data # [B, L, N, 1] + short_term_history = history_data[:, -self.short_term_len:, :, :] # STEP batch_size, _, num_nodes, _ = short_term_history.shape diff --git a/baselines/STEP/step_arch/tsformer/__init__.py b/baselines/STEP/arch/tsformer/__init__.py similarity index 100% rename from baselines/STEP/step_arch/tsformer/__init__.py rename to baselines/STEP/arch/tsformer/__init__.py diff --git a/baselines/STEP/step_arch/tsformer/mask.py b/baselines/STEP/arch/tsformer/mask.py similarity index 100% rename from baselines/STEP/step_arch/tsformer/mask.py rename to baselines/STEP/arch/tsformer/mask.py diff --git a/baselines/STEP/step_arch/tsformer/patch.py b/baselines/STEP/arch/tsformer/patch.py similarity index 100% rename from baselines/STEP/step_arch/tsformer/patch.py rename to baselines/STEP/arch/tsformer/patch.py diff --git a/baselines/STEP/step_arch/tsformer/positional_encoding.py b/baselines/STEP/arch/tsformer/positional_encoding.py similarity index 100% rename from baselines/STEP/step_arch/tsformer/positional_encoding.py rename to baselines/STEP/arch/tsformer/positional_encoding.py diff --git a/baselines/STEP/step_arch/tsformer/transformer_layers.py b/baselines/STEP/arch/tsformer/transformer_layers.py similarity index 100% rename from baselines/STEP/step_arch/tsformer/transformer_layers.py rename to baselines/STEP/arch/tsformer/transformer_layers.py diff --git a/baselines/STEP/step_arch/tsformer/tsformer.py b/baselines/STEP/arch/tsformer/tsformer.py similarity index 98% rename from baselines/STEP/step_arch/tsformer/tsformer.py rename to baselines/STEP/arch/tsformer/tsformer.py index df54192a..2b57e4ae 100644 --- a/baselines/STEP/step_arch/tsformer/tsformer.py +++ b/baselines/STEP/arch/tsformer/tsformer.py @@ -185,7 +185,7 @@ def forward(self, history_data: torch.Tensor, future_data: torch.Tensor = None, reconstruction_full = self.decoding(hidden_states_unmasked, masked_token_index) # for subsequent loss computing reconstruction_masked_tokens, label_masked_tokens = self.get_reconstructed_masked_tokens(reconstruction_full, history_data, unmasked_token_index, masked_token_index) - return reconstruction_masked_tokens, label_masked_tokens + return reconstruction_masked_tokens.unsqueeze(-1), label_masked_tokens.unsqueeze(-1) else: hidden_states_full, _, _ = self.encoding(history_data, mask=False) return hidden_states_full diff --git a/baselines/STEP/step_loss/__init__.py b/baselines/STEP/loss/__init__.py similarity index 100% rename from baselines/STEP/step_loss/__init__.py rename to baselines/STEP/loss/__init__.py diff --git a/baselines/STEP/step_loss/step_loss.py b/baselines/STEP/loss/step_loss.py similarity index 93% rename from baselines/STEP/step_loss/step_loss.py rename to baselines/STEP/loss/step_loss.py index 86c009b0..8716b9a5 100644 --- a/baselines/STEP/step_loss/step_loss.py +++ b/baselines/STEP/loss/step_loss.py @@ -1,6 +1,6 @@ import numpy as np from torch import nn -from basicts.losses import masked_mae +from basicts.metrics import masked_mae def step_loss(prediction, target, pred_adj, prior_adj, gsl_coefficient, null_val=np.nan): # graph structure learning loss diff --git a/baselines/STEP/runner/__init__.py b/baselines/STEP/runner/__init__.py new file mode 100644 index 00000000..59fa81bc --- /dev/null +++ b/baselines/STEP/runner/__init__.py @@ -0,0 +1,3 @@ +from .tsformer_runner import TSFormerRunner + +__all__ = ["TSFormerRunner"] diff --git a/baselines/STEP/step_runner/step_runner.py b/baselines/STEP/runner/tsformer_runner.py similarity index 53% rename from baselines/STEP/step_runner/step_runner.py rename to baselines/STEP/runner/tsformer_runner.py index c41f928f..ea199c8f 100644 --- a/baselines/STEP/step_runner/step_runner.py +++ b/baselines/STEP/runner/tsformer_runner.py @@ -1,10 +1,12 @@ -import torch +from typing import Optional, Dict +import torch +from tqdm import tqdm +from easytorch.utils.dist import master_only from basicts.runners import BaseTimeSeriesForecastingRunner -from basicts.metrics import masked_mae, masked_rmse, masked_mape -class STEPRunner(BaseTimeSeriesForecastingRunner): +class TSFormerRunner(BaseTimeSeriesForecastingRunner): def __init__(self, cfg: dict): super().__init__(cfg) self.forward_features = cfg["MODEL"].get("FORWARD_FEATURES", None) @@ -52,20 +54,34 @@ def forward(self, data: tuple, epoch:int = None, iter_num: int = None, train:boo tuple: (prediction, real_value) """ - # preprocess - future_data, history_data, long_history_data = data - history_data = self.to_running_device(history_data) # B, L, N, C - long_history_data = self.to_running_device(long_history_data) # B, L, N, C - future_data = self.to_running_device(future_data) # B, L, N, C + # Preprocess input data + future_data, history_data = data['target'], data['inputs'] + history_data = self.to_running_device(history_data) # Shape: [B, L, N, C] + future_data = self.to_running_device(future_data) # Shape: [B, L, N, C] + batch_size, length, num_nodes, _ = future_data.shape + # Select input features history_data = self.select_input_features(history_data) - long_history_data = self.select_input_features(long_history_data) # feed forward - model_return = self.model(history_data=history_data, long_history_data=long_history_data, future_data=None, batch_seen=iter_num, epoch=epoch) - - # parse model return - if isinstance(model_return, torch.Tensor): model_return = {"prediction": model_return} - model_return["inputs"] = self.select_target_features(history_data) - model_return["target"] = self.select_target_features(future_data) - return model_return + reconstruction_masked_tokens, label_masked_tokens = self.model(history_data=history_data, future_data=None, batch_seen=iter_num, epoch=epoch) + results = {'prediction': reconstruction_masked_tokens, 'target': label_masked_tokens, 'inputs': history_data} + return results + + @torch.no_grad() + @master_only + def test(self, train_epoch: Optional[int] = None, save_metrics: bool = False, save_results: bool = False) -> Dict: + + for data in tqdm(self.test_data_loader): + data = self.preprocessing(data) + forward_return = self.forward(data=data, epoch=None, iter_num=None, train=False) + # re-scale data + forward_return = self.postprocessing(forward_return) + # metrics + if not self.if_evaluate_on_gpu: + forward_return['target'] = forward_return['target'].detach().cpu() + forward_return['prediction'] = forward_return['prediction'].detach().cpu() + + for metric_name, metric_func in self.metrics.items(): + metric_item = metric_func(forward_return['prediction'], forward_return['target'], null_val=self.null_val) + self.update_epoch_meter("test_"+metric_name, metric_item.item()) diff --git a/baselines/STEP/step_data/__init__.py b/baselines/STEP/step_data/__init__.py deleted file mode 100644 index bc0cdb04..00000000 --- a/baselines/STEP/step_data/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .pretraining_dataset import PretrainingDataset -from .forecasting_dataset import ForecastingDataset - -__all__ = ["PretrainingDataset", "ForecastingDataset"] diff --git a/baselines/STEP/step_data/forecasting_dataset.py b/baselines/STEP/step_data/forecasting_dataset.py deleted file mode 100644 index 99a83771..00000000 --- a/baselines/STEP/step_data/forecasting_dataset.py +++ /dev/null @@ -1,80 +0,0 @@ -import os - -import torch -from torch.utils.data import Dataset -from basicts.utils import load_pkl - - -class ForecastingDataset(Dataset): - """Time series forecasting dataset.""" - - def __init__(self, data_file_path: str, index_file_path: str, mode: str, seq_len:int) -> None: - """Init the dataset in the forecasting stage. - - Args: - data_file_path (str): data file path. - index_file_path (str): index file path. - mode (str): train, valid, or test. - seq_len (int): the length of long term historical data. - """ - - super().__init__() - assert mode in ["train", "valid", "test"], "error mode" - self._check_if_file_exists(data_file_path, index_file_path) - # read raw data (normalized) - data = load_pkl(data_file_path) - processed_data = data["processed_data"] - self.data = torch.from_numpy(processed_data).float() - # read index - self.index = load_pkl(index_file_path)[mode] - # length of long term historical data - self.seq_len = seq_len - # mask - self.mask = torch.zeros(self.seq_len, self.data.shape[1], self.data.shape[2]) - - def _check_if_file_exists(self, data_file_path: str, index_file_path: str): - """Check if data file and index file exist. - - Args: - data_file_path (str): data file path - index_file_path (str): index file path - - Raises: - FileNotFoundError: no data file - FileNotFoundError: no index file - """ - - if not os.path.isfile(data_file_path): - raise FileNotFoundError("BasicTS can not find data file {0}".format(data_file_path)) - if not os.path.isfile(index_file_path): - raise FileNotFoundError("BasicTS can not find index file {0}".format(index_file_path)) - - def __getitem__(self, index: int) -> tuple: - """Get a sample. - - Args: - index (int): the iteration index (not the self.index) - - Returns: - tuple: (future_data, history_data), where the shape of each is L x N x C. - """ - - idx = list(self.index[index]) - - history_data = self.data[idx[0]:idx[1]] # 12 - future_data = self.data[idx[1]:idx[2]] # 12 - if idx[1] - self.seq_len < 0: - long_history_data = self.mask - else: - long_history_data = self.data[idx[1] - self.seq_len:idx[1]] # 11 - - return future_data, history_data, long_history_data - - def __len__(self): - """Dataset length - - Returns: - int: dataset length - """ - - return len(self.index) diff --git a/baselines/STEP/step_data/pretraining_dataset.py b/baselines/STEP/step_data/pretraining_dataset.py deleted file mode 100644 index 258f0b78..00000000 --- a/baselines/STEP/step_data/pretraining_dataset.py +++ /dev/null @@ -1 +0,0 @@ -from basicts.data import TimeSeriesForecastingDataset as PretrainingDataset diff --git a/baselines/STEP/step_runner/__init__.py b/baselines/STEP/step_runner/__init__.py deleted file mode 100644 index dfd4ad9c..00000000 --- a/baselines/STEP/step_runner/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .step_runner import STEPRunner - -__all__ = ["STEPRunner"] diff --git a/baselines/STGCN/METR-LA.py b/baselines/STGCN/METR-LA.py index e1bcfa63..56441496 100644 --- a/baselines/STGCN/METR-LA.py +++ b/baselines/STGCN/METR-LA.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STGCN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGCN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGCN" -CFG.MODEL.ARCH = STGCN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGCN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "normlap") adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "Ks" : 3, "Kt" : 3, "blocks" : [[1], [64, 16, 64], [64, 16, 64], [128, 128], [12]], @@ -49,73 +38,105 @@ "bias": True, "droprate" : 0.5 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGCN/PEMS-BAY.py b/baselines/STGCN/PEMS-BAY.py index 70efabe1..1e10d9e3 100644 --- a/baselines/STGCN/PEMS-BAY.py +++ b/baselines/STGCN/PEMS-BAY.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STGCN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGCN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGCN" -CFG.MODEL.ARCH = STGCN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGCN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "normlap") adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "Ks" : 3, "Kt" : 3, "blocks" : [[1], [64, 16, 64], [64, 16, 64], [128, 128], [12]], @@ -49,73 +38,105 @@ "bias": True, "droprate" : 0.5 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGCN/PEMS03.py b/baselines/STGCN/PEMS03.py index a46305e3..37ec01bb 100644 --- a/baselines/STGCN/PEMS03.py +++ b/baselines/STGCN/PEMS03.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STGCN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGCN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGCN" -CFG.MODEL.ARCH = STGCN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGCN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "normlap") adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "Ks" : 3, "Kt" : 3, "blocks" : [[1], [64, 16, 64], [64, 16, 64], [128, 128], [12]], @@ -49,73 +38,105 @@ "bias": True, "droprate" : 0.5 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGCN/PEMS04.py b/baselines/STGCN/PEMS04.py index bd15da31..33b4d8e6 100644 --- a/baselines/STGCN/PEMS04.py +++ b/baselines/STGCN/PEMS04.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STGCN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGCN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGCN" -CFG.MODEL.ARCH = STGCN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGCN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "normlap") adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "Ks" : 3, "Kt" : 3, "blocks" : [[1], [64, 16, 64], [64, 16, 64], [128, 128], [12]], @@ -49,73 +38,105 @@ "bias": True, "droprate" : 0.5 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGCN/PEMS07.py b/baselines/STGCN/PEMS07.py index 60b8e3e6..6ca0b66d 100644 --- a/baselines/STGCN/PEMS07.py +++ b/baselines/STGCN/PEMS07.py @@ -1,43 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STGCN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGCN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGCN" -CFG.MODEL.ARCH = STGCN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGCN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "normlap") adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { +MODEL_PARAM = { "Ks" : 3, "Kt" : 3, "blocks" : [[1], [64, 16, 64], [64, 16, 64], [128, 128], [12]], @@ -49,73 +38,105 @@ "bias": True, "droprate" : 0.5 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGCN/PEMS08.py b/baselines/STGCN/PEMS08.py index 26f6ba17..b09ae01c 100644 --- a/baselines/STGCN/PEMS08.py +++ b/baselines/STGCN/PEMS08.py @@ -1,44 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STGCN -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGCN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGCN" -CFG.MODEL.ARCH = STGCN -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + "/adj_mx.pkl", "normlap") +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGCN +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "normlap") adj_mx = torch.Tensor(adj_mx[0]) -CFG.MODEL.PARAM = { - "Ks" : 3, +MODEL_PARAM = { + "Ks" : 3, "Kt" : 3, "blocks" : [[1], [64, 16, 64], [64, 16, 64], [128, 128], [12]], "T" : 12, @@ -49,73 +38,105 @@ "bias": True, "droprate" : 0.5 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 -} -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGCN/run.sh b/baselines/STGCN/run.sh deleted file mode 100644 index a84201f3..00000000 --- a/baselines/STGCN/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/STGCN/METR-LA.py --gpus '0' -python experiments/train.py -c baselines/STGCN/PEMS-BAY.py --gpus '0' -python experiments/train.py -c baselines/STGCN/PEMS03.py --gpus '0' -python experiments/train.py -c baselines/STGCN/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/STGCN/PEMS07.py --gpus '0' -python experiments/train.py -c baselines/STGCN/PEMS08.py --gpus '0' diff --git a/baselines/STGODE/METR-LA.py b/baselines/STGODE/METR-LA.py index 1efc8319..b2d2df59 100644 --- a/baselines/STGODE/METR-LA.py +++ b/baselines/STGODE/METR-LA.py @@ -1,44 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj -from .generate_matrices import generate_dtw_spa_matrix +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STGODE +from .generate_matrices import generate_dtw_spa_matrix -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGODE model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGODE" -CFG.MODEL.ARCH = STGODE -# read -A_se_wave, A_sp_wave = generate_dtw_spa_matrix(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN) -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGODE +A_se_wave, A_sp_wave = generate_dtw_spa_matrix(DATA_NAME) +MODEL_PARAM = { "num_nodes": 207, "num_features": 3, "num_timesteps_input": 12, @@ -46,73 +34,109 @@ "A_sp_hat" : A_sp_wave, "A_se_hat" : A_se_wave } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "StepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "step_size": 50, "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGODE/PEMS-BAY.py b/baselines/STGODE/PEMS-BAY.py index b93ebea8..8546c100 100644 --- a/baselines/STGODE/PEMS-BAY.py +++ b/baselines/STGODE/PEMS-BAY.py @@ -1,44 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj -from .generate_matrices import generate_dtw_spa_matrix +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STGODE +from .generate_matrices import generate_dtw_spa_matrix -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGODE model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic Speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGODE" -CFG.MODEL.ARCH = STGODE -# read -A_se_wave, A_sp_wave = generate_dtw_spa_matrix(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN) -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGODE +A_se_wave, A_sp_wave = generate_dtw_spa_matrix(DATA_NAME) +MODEL_PARAM = { "num_nodes": 325, "num_features": 3, "num_timesteps_input": 12, @@ -46,73 +34,109 @@ "A_sp_hat" : A_sp_wave, "A_se_hat" : A_se_wave } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "StepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "step_size": 50, "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGODE/PEMS04.py b/baselines/STGODE/PEMS04.py index 4423e118..ea98cf9d 100644 --- a/baselines/STGODE/PEMS04.py +++ b/baselines/STGODE/PEMS04.py @@ -1,44 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj -from .generate_matrices import generate_dtw_spa_matrix +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STGODE +from .generate_matrices import generate_dtw_spa_matrix -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGODE model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGODE" -CFG.MODEL.ARCH = STGODE -# read -A_se_wave, A_sp_wave = generate_dtw_spa_matrix(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN) -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGODE +A_se_wave, A_sp_wave = generate_dtw_spa_matrix(DATA_NAME) +MODEL_PARAM = { "num_nodes": 307, "num_features": 3, "num_timesteps_input": 12, @@ -46,73 +34,109 @@ "A_sp_hat" : A_sp_wave, "A_se_hat" : A_se_wave } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "StepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "step_size": 50, "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGODE/PEMS08.py b/baselines/STGODE/PEMS08.py index 5c82ee25..ffd0ba56 100644 --- a/baselines/STGODE/PEMS08.py +++ b/baselines/STGODE/PEMS08.py @@ -1,44 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae -from basicts.utils import load_adj -from .generate_matrices import generate_dtw_spa_matrix +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STGODE +from .generate_matrices import generate_dtw_spa_matrix -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STGODE model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STGODE" -CFG.MODEL.ARCH = STGODE -# read -A_se_wave, A_sp_wave = generate_dtw_spa_matrix(CFG.DATASET_NAME, CFG.DATASET_INPUT_LEN, CFG.DATASET_OUTPUT_LEN) -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STGODE +A_se_wave, A_sp_wave = generate_dtw_spa_matrix(DATA_NAME) +MODEL_PARAM = { "num_nodes": 170, "num_features": 3, "num_timesteps_input": 12, @@ -46,73 +34,109 @@ "A_sp_hat" : A_sp_wave, "A_se_hat" : A_se_wave } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "StepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "step_size": 50, "gamma": 0.5 } - -# ================= train ================= # +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings CFG.TRAIN.CLIP_GRAD_PARAM = { "max_norm": 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STGODE/generate_matrices.py b/baselines/STGODE/generate_matrices.py index cfd808a1..d4876cb4 100644 --- a/baselines/STGODE/generate_matrices.py +++ b/baselines/STGODE/generate_matrices.py @@ -12,7 +12,7 @@ from tqdm import tqdm from fastdtw import fastdtw -from basicts.utils.serialization import load_pkl +from basicts.utils.serialization import load_pkl, load_dataset_data def get_normalized_adj(A): @@ -29,7 +29,7 @@ def get_normalized_adj(A): return torch.from_numpy(A_reg.astype(np.float32)) -def generate_dtw_spa_matrix(dataset_name, in_len, out_len, sigma1=0.1, thres1=0.6, sigma2=10, thres2=0.5, re_scale=True): +def generate_dtw_spa_matrix(dataset_name, sigma1=0.1, thres1=0.6, sigma2=10, thres2=0.5): """read data, generate spatial adjacency matrix and semantic adjacency matrix by dtw Args: @@ -45,9 +45,7 @@ def generate_dtw_spa_matrix(dataset_name, in_len, out_len, sigma1=0.1, thres1=0. """ # original STGODE use the full time series to generate the matrices, which is not reasonable since the test set is not available in real world - data_file = "./datasets/{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format(dataset_name, in_len, out_len, re_scale) - with open(data_file, 'rb') as f: - data = pickle.load(f)["processed_data"] + data = load_dataset_data(dataset_name=dataset_name) num_node = data.shape[1] if not os.path.exists('{0}/{1}_dtw_distance.npy'.format(os.path.abspath(__file__ + "/.."), dataset_name)): print("generate dtw distance matrix") @@ -75,6 +73,7 @@ def generate_dtw_spa_matrix(dataset_name, in_len, out_len, sigma1=0.1, thres1=0. # STGODE provides the scripts to generate spatial matrix for PEMS03, PEMS04, PEMS07, PEMS08 # For other datasets, we use the original spatial matrix. if dataset_name in ["PEMS03", "PEMS04", "PEMS07", "PEMS08"]: + print("STGODE generate spatial matrix based on the raw data. Please ensure the raw data is placed in the correct path `datasets/raw_data/$DATASET_NAME/$DATASET_NAME.csv.") if not os.path.exists('{0}/{1}_spatial_distance.npy'.format(os.path.abspath(__file__ + "/.."), dataset_name)): graph_csv_file_path = "./datasets/raw_data/{0}/{0}.csv".format(dataset_name) with open(graph_csv_file_path, 'r') as fp: @@ -111,4 +110,7 @@ def generate_dtw_spa_matrix(dataset_name, in_len, out_len, sigma1=0.1, thres1=0. if __name__ == "__main__": parser = argparse.ArgumentParser() - generate_dtw_spa_matrix("PEMS04", 12, 12, re_scale=True) + # generate_dtw_spa_matrix("PEMS04") + # generate_dtw_spa_matrix("PEMS08") + generate_dtw_spa_matrix("PEMS-BAY") + generate_dtw_spa_matrix("METR-LA") diff --git a/baselines/STID/CA.py b/baselines/STID/CA.py index 3020395e..bef0a901 100644 --- a/baselines/STID/CA.py +++ b/baselines/STID/CA.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "CA" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'CA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 8600, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 4, "if_node": True, "node_dim": 128, @@ -49,74 +40,108 @@ "time_of_day_size": 96, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic flow, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic flow +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 30, 60, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 64 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 50 -# test data +CFG.TEST.INTERVAL = 1 CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.USE_GPU = False -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/ETTh1.py b/baselines/STID/ETTh1.py index 9939ea26..5b91ceee 100644 --- a/baselines/STID/ETTh1.py +++ b/baselines/STID/ETTh1.py @@ -1,46 +1,38 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 7, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, - "node_dim": 32, + "node_dim": 8, "if_T_i_D": True, "if_D_i_W": True, "temp_dim_tid": 8, @@ -48,73 +40,107 @@ "time_of_day_size": 24, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 + "milestones": [1, 3, 5], + "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] \ No newline at end of file + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/ETTh2.py b/baselines/STID/ETTh2.py index 83f9dbc5..fba18116 100644 --- a/baselines/STID/ETTh2.py +++ b/baselines/STID/ETTh2.py @@ -1,46 +1,38 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 7, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, - "node_dim": 32, + "node_dim": 8, "if_T_i_D": True, "if_D_i_W": True, "temp_dim_tid": 8, @@ -48,73 +40,107 @@ "time_of_day_size": 24, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 + "milestones": [1, 3, 5], + "gamma": 0.1 } - -# ================= train ================= # -# CFG.TRAIN.CLIP_GRAD_PARAM = { -# 'max_norm': 5.0 -# } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] \ No newline at end of file + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/ETTm1.py b/baselines/STID/ETTm1.py index fd7f7b2a..3bf4c702 100644 --- a/baselines/STID/ETTm1.py +++ b/baselines/STID/ETTm1.py @@ -1,120 +1,146 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 7, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, - "node_dim": 32, + "node_dim": 8, "if_T_i_D": True, "if_D_i_W": True, "temp_dim_tid": 8, "temp_dim_diw": 8, - "time_of_day_size": 24, + "time_of_day_size": 24 * 4, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 + "milestones": [1, 3, 5], + "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] \ No newline at end of file + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/ETTm2.py b/baselines/STID/ETTm2.py index 39903ad5..c6a5ceda 100644 --- a/baselines/STID/ETTm2.py +++ b/baselines/STID/ETTm2.py @@ -1,120 +1,146 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 7, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, - "node_dim": 32, + "node_dim": 8, "if_T_i_D": True, "if_D_i_W": True, "temp_dim_tid": 8, "temp_dim_diw": 8, - "time_of_day_size": 24, + "time_of_day_size": 24 * 4, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 + "milestones": [1, 3, 5], + "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] \ No newline at end of file + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/Electricity.py b/baselines/STID/Electricity.py index 1f6588e8..994f448b 100644 --- a/baselines/STID/Electricity.py +++ b/baselines/STID/Electricity.py @@ -1,46 +1,38 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 321, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, - "node_dim": 32, + "node_dim": 8, "if_T_i_D": True, "if_D_i_W": True, "temp_dim_tid": 8, @@ -48,74 +40,107 @@ "time_of_day_size": 24, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], + "milestones": [1, 3, 5], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 16 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False +CFG.VAL.DATA.BATCH_SIZE = 64 -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 16 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## -# ================= evaluate ================= # CFG.EVAL = EasyDict() -# CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] -CFG.EVAL.HORIZONS = [12, 24, 48, 96] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/ExchangeRate.py b/baselines/STID/ExchangeRate.py index 0eba3dfa..73161ef7 100644 --- a/baselines/STID/ExchangeRate.py +++ b/baselines/STID/ExchangeRate.py @@ -1,43 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 8, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, "node_dim": 32, @@ -48,73 +40,107 @@ "time_of_day_size": 1, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0003, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 + "milestones": [1, 3, 5], + "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] \ No newline at end of file + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/GBA.py b/baselines/STID/GBA.py index 73e9a37c..964e6925 100644 --- a/baselines/STID/GBA.py +++ b/baselines/STID/GBA.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "GBA" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'CA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 2352, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 4, "if_node": True, "node_dim": 128, @@ -49,73 +40,108 @@ "time_of_day_size": 96, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic flow, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic flow +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 30, 60, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 64 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/GLA.py b/baselines/STID/GLA.py index 405b1776..7e997b05 100644 --- a/baselines/STID/GLA.py +++ b/baselines/STID/GLA.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "GLA" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'CA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 3834, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 4, "if_node": True, "node_dim": 128, @@ -49,73 +40,108 @@ "time_of_day_size": 96, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic flow, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic flow +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 30, 60, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 64 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/Illness.py b/baselines/STID/Illness.py index c8632cff..257b773f 100644 --- a/baselines/STID/Illness.py +++ b/baselines/STID/Illness.py @@ -1,43 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Illness" -CFG.DATASET_TYPE = "Illness" -CFG.DATASET_INPUT_LEN = 168 -CFG.DATASET_OUTPUT_LEN = 96 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Illness' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 7, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, "node_dim": 32, @@ -48,73 +40,106 @@ "time_of_day_size": 1, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0002, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 + "milestones": [1, 3, 5], + "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] + +# Evaluation parameters +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/METR-LA.py b/baselines/STID/METR-LA.py index bbd0fcc3..e92acb2a 100644 --- a/baselines/STID/METR-LA.py +++ b/baselines/STID/METR-LA.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 207, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 3, "if_node": True, "node_dim": 32, @@ -49,73 +40,108 @@ "time_of_day_size": 288, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic speed, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic speed +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/PEMS-BAY.py b/baselines/STID/PEMS-BAY.py index 0f54edc8..53203fd4 100644 --- a/baselines/STID/PEMS-BAY.py +++ b/baselines/STID/PEMS-BAY.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 325, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 3, "if_node": True, "node_dim": 32, @@ -49,73 +40,108 @@ "time_of_day_size": 288, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic speed, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic speed +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/PEMS03.py b/baselines/STID/PEMS03.py index 332774b9..d70fe32d 100644 --- a/baselines/STID/PEMS03.py +++ b/baselines/STID/PEMS03.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 358, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 3, "if_node": True, "node_dim": 32, @@ -49,73 +40,108 @@ "time_of_day_size": 288, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic flow, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic flow +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/PEMS04.py b/baselines/STID/PEMS04.py index 07d3a92f..d3133f96 100644 --- a/baselines/STID/PEMS04.py +++ b/baselines/STID/PEMS04.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 307, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 3, "if_node": True, "node_dim": 32, @@ -49,73 +40,108 @@ "time_of_day_size": 288, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic flow, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic flow +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/PEMS07.py b/baselines/STID/PEMS07.py index 8892fba6..6eddd65c 100644 --- a/baselines/STID/PEMS07.py +++ b/baselines/STID/PEMS07.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 883, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 3, "if_node": True, "node_dim": 32, @@ -49,73 +40,108 @@ "time_of_day_size": 288, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic flow, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic flow +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/PEMS08.py b/baselines/STID/PEMS08.py index 73cf0c02..fee28d8d 100644 --- a/baselines/STID/PEMS08.py +++ b/baselines/STID/PEMS08.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 170, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 3, "if_node": True, "node_dim": 32, @@ -49,73 +40,108 @@ "time_of_day_size": 288, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic flow, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic flow +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/SD.py b/baselines/STID/SD.py index 47b54aaf..9586c124 100644 --- a/baselines/STID/SD.py +++ b/baselines/STID/SD.py @@ -1,44 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "SD" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'SD' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 716, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 3, "embed_dim": 32, - "output_len": CFG.DATASET_OUTPUT_LEN, + "output_len": OUTPUT_LEN, "num_layer": 4, "if_node": True, "node_dim": 64, @@ -49,73 +40,110 @@ "time_of_day_size": 96, "day_of_week_size": 7 } -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # traffic flow, time in day -CFG.MODEL.TARGET_FEATURES = [0] # traffic flow +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) -# ================= optim ================= # +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 30, 60, 80], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 32 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 64 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False +# Early stopping +CFG.TRAIN.EARLY_STOPPING_PATIENCE = 10 # Early stopping patience. Default: None. If not specified, the early stopping will not be used. -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = False # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/Traffic.py b/baselines/STID/Traffic.py index 827d5f68..4fe41115 100644 --- a/baselines/STID/Traffic.py +++ b/baselines/STID/Traffic.py @@ -1,43 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Traffic" -CFG.DATASET_TYPE = "Traffic" -CFG.DATASET_INPUT_LEN = 168 -CFG.DATASET_OUTPUT_LEN = 96 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Traffic' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 862, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, "node_dim": 32, @@ -45,76 +37,110 @@ "if_D_i_W": True, "temp_dim_tid": 8, "temp_dim_diw": 8, - "time_of_day_size": 1, + "time_of_day_size": 24, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0002, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 + "milestones": [1, 3, 5], + "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/Weather.py b/baselines/STID/Weather.py index a4170ce2..63af02a8 100644 --- a/baselines/STID/Weather.py +++ b/baselines/STID/Weather.py @@ -1,43 +1,35 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.losses import masked_mae, masked_mse +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import STID -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STID model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STID" -CFG.MODEL.ARCH = STID -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STID +MODEL_PARAM = { "num_nodes": 21, - "input_len": CFG.DATASET_INPUT_LEN, + "input_len": INPUT_LEN, "input_dim": 1, - "embed_dim": 1024, - "output_len": CFG.DATASET_OUTPUT_LEN, + "embed_dim": 2048, + "output_len": OUTPUT_LEN, "num_layer": 1, "if_node": True, "node_dim": 32, @@ -48,73 +40,107 @@ "time_of_day_size": 144, "day_of_week_size": 7 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0002, + "lr": 0.0005, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 25, 50], - "gamma": 0.5 + "milestones": [1, 3, 5], + "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STID/run.sh b/baselines/STID/run.sh deleted file mode 100644 index 75941b9e..00000000 --- a/baselines/STID/run.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/STID/METR-LA.py --gpus '0' -python experiments/train.py -c baselines/STID/PEMS-BAY.py --gpus '0' -python experiments/train.py -c baselines/STID/PEMS03.py --gpus '0' -python experiments/train.py -c baselines/STID/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/STID/PEMS07.py --gpus '0' -python experiments/train.py -c baselines/STID/PEMS08.py --gpus '0' - diff --git a/baselines/STID_M4/M4.py b/baselines/STID_M4/M4.py deleted file mode 100644 index d845540d..00000000 --- a/baselines/STID_M4/M4.py +++ /dev/null @@ -1,115 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.losses import masked_mae -from basicts.data import M4ForecastingDataset -from basicts.runners import M4ForecastingRunner - -from .arch import STID - -def get_cfg(seasonal_pattern): - assert seasonal_pattern in ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] - prediction_len = {"Yearly": 6, "Quarterly": 8, "Monthly": 18, "Weekly": 13, "Daily": 14, "Hourly": 48}[seasonal_pattern] - num_nodes = {"Yearly": 23000, "Quarterly": 24000, "Monthly": 48000, "Weekly": 359, "Daily": 4227, "Hourly": 414}[seasonal_pattern] - history_size = 2 - history_len = history_size * prediction_len - - CFG = EasyDict() - - # ================= general ================= # - CFG.DESCRIPTION = "Multi-layer perceptron model configuration" - CFG.RUNNER = M4ForecastingRunner - CFG.DATASET_CLS = M4ForecastingDataset - CFG.DATASET_NAME = "M4_" + seasonal_pattern - CFG.DATASET_INPUT_LEN = history_len - CFG.DATASET_OUTPUT_LEN = prediction_len - CFG.GPU_NUM = 1 - - # ================= environment ================= # - CFG.ENV = EasyDict() - CFG.ENV.SEED = 1 - CFG.ENV.CUDNN = EasyDict() - CFG.ENV.CUDNN.ENABLED = True - - # ================= model ================= # - CFG.MODEL = EasyDict() - CFG.MODEL.NAME = "STID" - CFG.MODEL.ARCH = STID - CFG.MODEL.PARAM = { - "num_nodes": num_nodes, - "input_len": CFG.DATASET_INPUT_LEN, - "input_dim": 1, - "embed_dim": 256 if seasonal_pattern not in ["Yearly", "Quarterly", "Monthly"] else 128, - "output_len": CFG.DATASET_OUTPUT_LEN, - "num_layer": 4, - "if_node": True, - "node_dim": 16, - "if_T_i_D": False, # no temporal features in M4 - "if_D_i_W": False, - "temp_dim_tid": 32, - "temp_dim_diw": 32, - "time_of_day_size": 288, - "day_of_week_size": 7 - } - CFG.MODEL.FORWARD_FEATURES = [0, 1] # values, node id - CFG.MODEL.TARGET_FEATURES = [0] - - # ================= optim ================= # - CFG.TRAIN = EasyDict() - CFG.TRAIN.LOSS = masked_mae - CFG.TRAIN.OPTIM = EasyDict() - CFG.TRAIN.OPTIM.TYPE = "Adam" - CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, - } - CFG.TRAIN.LR_SCHEDULER = EasyDict() - CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" - CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 80], - "gamma": 0.5 - } - - # ================= train ================= # - CFG.TRAIN.CLIP_GRAD_PARAM = { - 'max_norm': 5.0 - } - CFG.TRAIN.NUM_EPOCHS = 99 - CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) - ) - # train data - CFG.TRAIN.DATA = EasyDict() - # read data - CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TRAIN.DATA.BATCH_SIZE = 64 - CFG.TRAIN.DATA.PREFETCH = False - CFG.TRAIN.DATA.SHUFFLE = True - CFG.TRAIN.DATA.NUM_WORKERS = 2 - CFG.TRAIN.DATA.PIN_MEMORY = False - - # ================= test ================= # - CFG.TEST = EasyDict() - CFG.TEST.INTERVAL = CFG.TRAIN.NUM_EPOCHS - # test data - CFG.TEST.DATA = EasyDict() - # read data - CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME - # dataloader args, optional - CFG.TEST.DATA.BATCH_SIZE = 64 - CFG.TEST.DATA.PREFETCH = False - CFG.TEST.DATA.SHUFFLE = False - CFG.TEST.DATA.NUM_WORKERS = 2 - CFG.TEST.DATA.PIN_MEMORY = False - - # ================= evaluate ================= # - CFG.EVAL = EasyDict() - CFG.EVAL.HORIZONS = [] - CFG.EVAL.SAVE_PATH = os.path.abspath(__file__ + "/..") - - return CFG diff --git a/baselines/STID_M4/arch/__init__.py b/baselines/STID_M4/arch/__init__.py deleted file mode 100644 index 64b16477..00000000 --- a/baselines/STID_M4/arch/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .stid_arch import STID - -__all__ = ["STID"] diff --git a/baselines/STID_M4/arch/mlp.py b/baselines/STID_M4/arch/mlp.py deleted file mode 100644 index 17fccbc1..00000000 --- a/baselines/STID_M4/arch/mlp.py +++ /dev/null @@ -1,29 +0,0 @@ -import torch -from torch import nn - - -class MultiLayerPerceptron(nn.Module): - """Multi-Layer Perceptron with residual links.""" - - def __init__(self, input_dim, hidden_dim) -> None: - super().__init__() - self.fc1 = nn.Conv2d( - in_channels=input_dim, out_channels=hidden_dim, kernel_size=(1, 1), bias=True) - self.fc2 = nn.Conv2d( - in_channels=hidden_dim, out_channels=hidden_dim, kernel_size=(1, 1), bias=True) - self.act = nn.ReLU() - self.drop = nn.Dropout(p=0.15) - - def forward(self, input_data: torch.Tensor) -> torch.Tensor: - """Feed forward of MLP. - - Args: - input_data (torch.Tensor): input data with shape [B, D, N] - - Returns: - torch.Tensor: latent repr - """ - - hidden = self.fc2(self.drop(self.act(self.fc1(input_data)))) # MLP - hidden = hidden + input_data # residual - return hidden diff --git a/baselines/STID_M4/arch/stid_arch.py b/baselines/STID_M4/arch/stid_arch.py deleted file mode 100644 index 3899376a..00000000 --- a/baselines/STID_M4/arch/stid_arch.py +++ /dev/null @@ -1,108 +0,0 @@ -import torch -from torch import nn - -from .mlp import MultiLayerPerceptron - - -class STID(nn.Module): - """ - Paper: Spatial-Temporal Identity: A Simple yet Effective Baseline for Multivariate Time Series Forecasting - Link: https://arxiv.org/abs/2208.05233 - Official Code: https://github.com/zezhishao/STID - """ - - def __init__(self, **model_args): - super().__init__() - # attributes - self.num_nodes = model_args["num_nodes"] - self.node_dim = model_args["node_dim"] - self.input_len = model_args["input_len"] - self.input_dim = model_args["input_dim"] - self.embed_dim = model_args["embed_dim"] - self.output_len = model_args["output_len"] - self.num_layer = model_args["num_layer"] - self.temp_dim_tid = model_args["temp_dim_tid"] - self.temp_dim_diw = model_args["temp_dim_diw"] - self.time_of_day_size = model_args["time_of_day_size"] - self.day_of_week_size = model_args["day_of_week_size"] - - self.if_time_in_day = model_args["if_T_i_D"] - self.if_day_in_week = model_args["if_D_i_W"] - self.if_spatial = model_args["if_node"] - - # spatial embeddings - if self.if_spatial: - self.node_emb = nn.Parameter( - torch.empty(self.num_nodes, self.node_dim)) - nn.init.xavier_uniform_(self.node_emb) - # temporal embeddings - if self.if_time_in_day: - self.time_in_day_emb = nn.Parameter( - torch.empty(self.time_of_day_size, self.temp_dim_tid)) - nn.init.xavier_uniform_(self.time_in_day_emb) - if self.if_day_in_week: - self.day_in_week_emb = nn.Parameter( - torch.empty(self.day_of_week_size, self.temp_dim_diw)) - nn.init.xavier_uniform_(self.day_in_week_emb) - - # embedding layer - self.time_series_emb_layer = nn.Conv2d( - in_channels=self.input_dim * self.input_len, out_channels=self.embed_dim, kernel_size=(1, 1), bias=True) - - # encoding - self.hidden_dim = self.embed_dim+self.node_dim * \ - int(self.if_spatial)+self.temp_dim_tid*int(self.if_day_in_week) + \ - self.temp_dim_diw*int(self.if_time_in_day) - self.encoder = nn.Sequential( - *[MultiLayerPerceptron(self.hidden_dim, self.hidden_dim) for _ in range(self.num_layer)]) - - # regression - self.regression_layer = nn.Conv2d( - in_channels=self.hidden_dim, out_channels=self.output_len, kernel_size=(1, 1), bias=True) - - def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool, **kwargs) -> torch.Tensor: - """Feed forward of STID. - - Args: - history_data (torch.Tensor): history data with shape [B, L, N, C] - - Returns: - torch.Tensor: prediction with shape [B, L, N, C] - """ - - # prepare data - input_data = history_data[..., range(self.input_dim)] - node_id = history_data[:, 0, 0, 1].type(torch.int64) # the second dimension is node ids - # no temporal features in M4 (M4 dataset only provides fake date, which is unreliable to extract temporal features) - time_in_day_emb = None - day_in_week_emb = None - node_embed = self.node_emb[node_id] - - # time series embedding - batch_size, _, num_nodes, _ = input_data.shape - input_data = input_data.transpose(1, 2).contiguous() - input_data = input_data.view( - batch_size, num_nodes, -1).transpose(1, 2).unsqueeze(-1) - time_series_emb = self.time_series_emb_layer(input_data) - - node_emb = [] - if self.if_spatial: - # expand node embeddings - node_emb.append(node_embed.unsqueeze(-1).unsqueeze(-1)) - # temporal embeddings - tem_emb = [] - if time_in_day_emb is not None: - tem_emb.append(time_in_day_emb.transpose(1, 2).unsqueeze(-1)) - if day_in_week_emb is not None: - tem_emb.append(day_in_week_emb.transpose(1, 2).unsqueeze(-1)) - - # concate all embeddings - hidden = torch.cat([time_series_emb] + node_emb + tem_emb, dim=1) - - # encoding - hidden = self.encoder(hidden) - - # regression - prediction = self.regression_layer(hidden) - - return prediction diff --git a/baselines/STNorm/METR-LA.py b/baselines/STNorm/METR-LA.py index 4394dcfa..f788e729 100644 --- a/baselines/STNorm/METR-LA.py +++ b/baselines/STNorm/METR-LA.py @@ -1,39 +1,30 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STNorm -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STNorm model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STNorm" -CFG.MODEL.ARCH = STNorm -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STNorm +MODEL_PARAM = { "num_nodes" : 207, "tnorm_bool": True, "snorm_bool": True, @@ -44,73 +35,108 @@ "blocks" : 4, "layers" : 2, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STNorm/PEMS-BAY.py b/baselines/STNorm/PEMS-BAY.py index f62e1dee..77521a9e 100644 --- a/baselines/STNorm/PEMS-BAY.py +++ b/baselines/STNorm/PEMS-BAY.py @@ -1,39 +1,30 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STNorm -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STNorm model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STNorm" -CFG.MODEL.ARCH = STNorm -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STNorm +MODEL_PARAM = { "num_nodes" : 325, "tnorm_bool": True, "snorm_bool": True, @@ -44,73 +35,108 @@ "blocks" : 4, "layers" : 2, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STNorm/PEMS03.py b/baselines/STNorm/PEMS03.py index 2427a09d..eb228613 100644 --- a/baselines/STNorm/PEMS03.py +++ b/baselines/STNorm/PEMS03.py @@ -1,39 +1,30 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STNorm -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STNorm model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STNorm" -CFG.MODEL.ARCH = STNorm -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STNorm +MODEL_PARAM = { "num_nodes" : 358, "tnorm_bool": True, "snorm_bool": True, @@ -44,73 +35,108 @@ "blocks" : 4, "layers" : 2, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STNorm/PEMS04.py b/baselines/STNorm/PEMS04.py index decb1bc5..07674792 100644 --- a/baselines/STNorm/PEMS04.py +++ b/baselines/STNorm/PEMS04.py @@ -1,39 +1,30 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STNorm -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STNorm model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STNorm" -CFG.MODEL.ARCH = STNorm -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STNorm +MODEL_PARAM = { "num_nodes" : 307, "tnorm_bool": True, "snorm_bool": True, @@ -44,73 +35,108 @@ "blocks" : 4, "layers" : 2, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STNorm/PEMS07.py b/baselines/STNorm/PEMS07.py index af56452a..f80d40b9 100644 --- a/baselines/STNorm/PEMS07.py +++ b/baselines/STNorm/PEMS07.py @@ -1,39 +1,30 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STNorm -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STNorm model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STNorm" -CFG.MODEL.ARCH = STNorm -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STNorm +MODEL_PARAM = { "num_nodes" : 883, "tnorm_bool": True, "snorm_bool": True, @@ -44,73 +35,108 @@ "blocks" : 4, "layers" : 2, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STNorm/PEMS08.py b/baselines/STNorm/PEMS08.py index 7839b639..9dfd4f89 100644 --- a/baselines/STNorm/PEMS08.py +++ b/baselines/STNorm/PEMS08.py @@ -1,39 +1,30 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STNorm -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STNorm model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STNorm" -CFG.MODEL.ARCH = STNorm -CFG.MODEL.PARAM = { +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = STNorm +MODEL_PARAM = { "num_nodes" : 170, "tnorm_bool": True, "snorm_bool": True, @@ -44,73 +35,108 @@ "blocks" : 4, "layers" : 2, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.002, "weight_decay": 0.0001, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STNorm/run.sh b/baselines/STNorm/run.sh deleted file mode 100644 index 2e33cf70..00000000 --- a/baselines/STNorm/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/STNorm/METR-LA.py --gpus '0' -python experiments/train.py -c baselines/STNorm/PEMS-BAY.py --gpus '0' -python experiments/train.py -c baselines/STNorm/PEMS03.py --gpus '0' -python experiments/train.py -c baselines/STNorm/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/STNorm/PEMS07.py --gpus '0' -python experiments/train.py -c baselines/STNorm/PEMS08.py --gpus '0' diff --git a/baselines/STWave/METR-LA.py b/baselines/STWave/METR-LA.py index 54680fec..adb4d593 100644 --- a/baselines/STWave/METR-LA.py +++ b/baselines/STWave/METR-LA.py @@ -2,21 +2,23 @@ import sys import numpy as np import scipy.sparse as sp +from easydict import EasyDict import math from scipy.sparse import csr_matrix from scipy.sparse.csgraph import dijkstra # TODO: remove it when basicts can be installed by pip sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STWave from .loss import stwave_masked_mae + def laplacian(W): """Return the Laplacian of the weight matrix.""" # Degree matrix. @@ -48,34 +50,22 @@ def loadGraph(adj_mx, hs, ls): adj_gat = np.argpartition(dist_matrix, sampled_nodes_number, -1)[:, :sampled_nodes_number] return adj_gat, graphwave - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STWave model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STWave" -CFG.MODEL.ARCH = STWave -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "original") adjgat, gwv = loadGraph(_, 128, 1) -CFG.MODEL.PARAM = { +MODEL_ARCH = STWave +MODEL_PARAM = { "input_dim": 1, "hidden_size": 128, "layers": 2, @@ -89,72 +79,107 @@ def loadGraph(adj_mx, hs, ls): "wave_type": "db1", "wave_levels": 1, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = stwave_masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [80, 90, 95], "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STWave/PEMS-BAY.py b/baselines/STWave/PEMS-BAY.py index 43af0755..34d4f1a4 100644 --- a/baselines/STWave/PEMS-BAY.py +++ b/baselines/STWave/PEMS-BAY.py @@ -2,21 +2,23 @@ import sys import numpy as np import scipy.sparse as sp +from easydict import EasyDict import math from scipy.sparse import csr_matrix from scipy.sparse.csgraph import dijkstra # TODO: remove it when basicts can be installed by pip sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STWave from .loss import stwave_masked_mae + def laplacian(W): """Return the Laplacian of the weight matrix.""" # Degree matrix. @@ -48,34 +50,22 @@ def loadGraph(adj_mx, hs, ls): adj_gat = np.argpartition(dist_matrix, sampled_nodes_number, -1)[:, :sampled_nodes_number] return adj_gat, graphwave - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STWave model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STWave" -CFG.MODEL.ARCH = STWave -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "original") adjgat, gwv = loadGraph(_, 128, 1) -CFG.MODEL.PARAM = { +MODEL_ARCH = STWave +MODEL_PARAM = { "input_dim": 1, "hidden_size": 128, "layers": 2, @@ -89,72 +79,107 @@ def loadGraph(adj_mx, hs, ls): "wave_type": "db1", "wave_levels": 1, } +NUM_EPOCHS = 12 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = stwave_masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [9, 10], "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 12 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STWave/PEMS03.py b/baselines/STWave/PEMS03.py index 5fb51dfe..3ebf2b08 100644 --- a/baselines/STWave/PEMS03.py +++ b/baselines/STWave/PEMS03.py @@ -2,21 +2,23 @@ import sys import numpy as np import scipy.sparse as sp +from easydict import EasyDict import math from scipy.sparse import csr_matrix from scipy.sparse.csgraph import dijkstra # TODO: remove it when basicts can be installed by pip sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STWave from .loss import stwave_masked_mae + def laplacian(W): """Return the Laplacian of the weight matrix.""" # Degree matrix. @@ -46,34 +48,22 @@ def loadGraph(adj_mx, hs, ls): adj_gat = np.argpartition(dist_matrix, sampled_nodes_number, -1)[:, :sampled_nodes_number] return adj_gat, graphwave - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STWave model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STWave" -CFG.MODEL.ARCH = STWave -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "original") adjgat, gwv = loadGraph(_, 128, 1) -CFG.MODEL.PARAM = { +MODEL_ARCH = STWave +MODEL_PARAM = { "input_dim": 1, "hidden_size": 128, "layers": 2, @@ -87,72 +77,107 @@ def loadGraph(adj_mx, hs, ls): "wave_type": "sym2", "wave_levels": 1, } +NUM_EPOCHS = 50 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = stwave_masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [30, 40, 45], "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STWave/PEMS04.py b/baselines/STWave/PEMS04.py index ba877ff5..6ac6637e 100644 --- a/baselines/STWave/PEMS04.py +++ b/baselines/STWave/PEMS04.py @@ -2,21 +2,23 @@ import sys import numpy as np import scipy.sparse as sp +from easydict import EasyDict import math from scipy.sparse import csr_matrix from scipy.sparse.csgraph import dijkstra # TODO: remove it when basicts can be installed by pip sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STWave from .loss import stwave_masked_mae + def laplacian(W): """Return the Laplacian of the weight matrix.""" # Degree matrix. @@ -46,34 +48,22 @@ def loadGraph(adj_mx, hs, ls): adj_gat = np.argpartition(dist_matrix, sampled_nodes_number, -1)[:, :sampled_nodes_number] return adj_gat, graphwave - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STWave model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STWave" -CFG.MODEL.ARCH = STWave -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "original") adjgat, gwv = loadGraph(_, 128, 1) -CFG.MODEL.PARAM = { +MODEL_ARCH = STWave +MODEL_PARAM = { "input_dim": 1, "hidden_size": 128, "layers": 2, @@ -87,72 +77,107 @@ def loadGraph(adj_mx, hs, ls): "wave_type": "sym2", "wave_levels": 1, } +NUM_EPOCHS = 80 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = stwave_masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [65, 70, 75], "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 80 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STWave/PEMS07.py b/baselines/STWave/PEMS07.py index e8d32f69..a15e84c3 100644 --- a/baselines/STWave/PEMS07.py +++ b/baselines/STWave/PEMS07.py @@ -2,21 +2,23 @@ import sys import numpy as np import scipy.sparse as sp +from easydict import EasyDict import math from scipy.sparse import csr_matrix from scipy.sparse.csgraph import dijkstra # TODO: remove it when basicts can be installed by pip sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STWave from .loss import stwave_masked_mae + def laplacian(W): """Return the Laplacian of the weight matrix.""" # Degree matrix. @@ -46,34 +48,22 @@ def loadGraph(adj_mx, hs, ls): adj_gat = np.argpartition(dist_matrix, sampled_nodes_number, -1)[:, :sampled_nodes_number] return adj_gat, graphwave - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STWave model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STWave" -CFG.MODEL.ARCH = STWave -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "original") adjgat, gwv = loadGraph(_, 128, 1) -CFG.MODEL.PARAM = { +MODEL_ARCH = STWave +MODEL_PARAM = { "input_dim": 1, "hidden_size": 128, "layers": 2, @@ -87,72 +77,107 @@ def loadGraph(adj_mx, hs, ls): "wave_type": "sym2", "wave_levels": 1, } +NUM_EPOCHS = 110 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = stwave_masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [100, 105], "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 110 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STWave/PEMS08.py b/baselines/STWave/PEMS08.py index dd4af92b..6bed806a 100644 --- a/baselines/STWave/PEMS08.py +++ b/baselines/STWave/PEMS08.py @@ -2,21 +2,23 @@ import sys import numpy as np import scipy.sparse as sp +from easydict import EasyDict import math from scipy.sparse import csr_matrix from scipy.sparse.csgraph import dijkstra # TODO: remove it when basicts can be installed by pip sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.utils import load_adj +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj from .arch import STWave from .loss import stwave_masked_mae + def laplacian(W): """Return the Laplacian of the weight matrix.""" # Degree matrix. @@ -46,34 +48,22 @@ def loadGraph(adj_mx, hs, ls): adj_gat = np.argpartition(dist_matrix, sampled_nodes_number, -1)[:, :sampled_nodes_number] return adj_gat, graphwave - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "STWave model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "STWave" -CFG.MODEL.ARCH = STWave -adj_mx, _ = load_adj("datasets/" + CFG.DATASET_NAME + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +adj_mx, _ = load_adj("datasets/" + DATA_NAME + "/adj_mx.pkl", "original") adjgat, gwv = loadGraph(_, 128, 1) -CFG.MODEL.PARAM = { +MODEL_ARCH = STWave +MODEL_PARAM = { "input_dim": 1, "hidden_size": 128, "layers": 2, @@ -87,72 +77,107 @@ def loadGraph(adj_mx, hs, ls): "wave_type": "coif1", "wave_levels": 2, } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = stwave_masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.001 } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [80, 90, 95], + "milestones": [100, 105], "gamma": 0.1 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { - "max_norm": 5.0 + 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/STWave/arch/stwave_arch.py b/baselines/STWave/arch/stwave_arch.py index 104987d3..f9747781 100644 --- a/baselines/STWave/arch/stwave_arch.py +++ b/baselines/STWave/arch/stwave_arch.py @@ -177,7 +177,7 @@ def __init__(self, hidden_size, kernel_size=2, dropout=0.2, levels=1): layers += [nn.Sequential(self.conv, self.chomp, self.relu, self.dropout)] self.tcn = nn.Sequential(*layers) - + def forward(self, xh): xh = self.tcn(xh.transpose(1,3)).transpose(1,3) return xh @@ -208,7 +208,7 @@ def forward(self, xl, xh, te, Mask=True): valueh = torch.relu(self.vhfc(xh)).permute(0,2,1,3) attentionh = torch.matmul(query, keyh) # [B,N,T,T] - + if Mask: batch_size = xl.shape[0] num_steps = xl.shape[1] @@ -237,26 +237,26 @@ def __init__(self, hidden_size, log_samples, adj_gat, graphwave): super(dualEncoder, self).__init__() self.tcn = temporalConvNet(hidden_size) self.tatt = temporalAttention(hidden_size) - + self.ssal = sparseSpatialAttention(hidden_size, log_samples) self.ssah = sparseSpatialAttention(hidden_size, log_samples) - + eigvalue = torch.from_numpy(graphwave[0].astype(np.float32)) self.eigvalue = nn.Parameter(eigvalue, requires_grad=True) self.eigvec = torch.from_numpy(graphwave[1].astype(np.float32)).transpose(0,1).unsqueeze(-1) self.adj = torch.from_numpy(adj_gat) - + def forward(self, xl, xh, te): xl = self.tatt(xl, te) xh = self.tcn(xh) - + spa_statesl = self.ssal(xl, self.adj.to(xl.device), self.eigvec.to(xl.device), self.eigvalue.to(xl.device)) spa_statesh = self.ssah(xh, self.adj.to(xl.device), self.eigvec.to(xl.device), self.eigvalue.to(xl.device)) xl = spa_statesl + xl xh = spa_statesh + xh - - return xl, xh + return xl, xh + class STWave(nn.Module): """ Paper: When Spatio-Temporal Meet Wavelets: Disentangled Traffic Forecasting via Efficient Spectral Graph Attention Networks @@ -272,10 +272,10 @@ def __init__(self, input_dim, hidden_size, layers, seq_len, horizon, log_samples self.dual_encoder = nn.ModuleList([dualEncoder(hidden_size, log_samples, adj_gat, graphwave) for i in range(layers)]) self.adaptive_fusion = adaptiveFusion(hidden_size) - + self.pre_l = nn.Conv2d(seq_len, horizon, (1,1)) self.pre_h = nn.Conv2d(seq_len, horizon, (1,1)) - + self.end_emb = FeedForward([hidden_size, hidden_size, input_dim]) self.end_emb_l = FeedForward([hidden_size, hidden_size, input_dim]) @@ -304,17 +304,17 @@ def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_s for enc in self.dual_encoder: xl, xh = enc(xl, xh, TE[:,:xl.shape[1],:,:]) - + hat_y_l = self.pre_l(xl) hat_y_h = self.pre_h(xh) hat_y = self.adaptive_fusion(hat_y_l, hat_y_h, TE[:,xl.shape[1]:,:,:]) hat_y, hat_y_l = self.end_emb(hat_y), self.end_emb_l(hat_y_l) - + if self.training: label_yl, _ = disentangle(future_data[...,0:1].cpu().numpy(), self.wt, self.wl) return torch.cat([hat_y, hat_y_l, label_yl.to(x.device)], -1) - + return hat_y diff --git a/baselines/STWave/loss.py b/baselines/STWave/loss.py index 35f63f7e..18d4fdbf 100644 --- a/baselines/STWave/loss.py +++ b/baselines/STWave/loss.py @@ -1,7 +1,7 @@ import torch import numpy as np -from basicts.losses import masked_mae +from basicts.metrics import masked_mae def stwave_masked_mae(prediction: list, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: diff --git a/baselines/STWave/run.sh b/baselines/STWave/run.sh deleted file mode 100644 index 61fd7ca0..00000000 --- a/baselines/STWave/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/STWave/METR-LA.py --gpus '0' -python experiments/train.py -c baselines/STWave/PEMS-BAY.py --gpus '0' -python experiments/train.py -c baselines/STWave/PEMS03.py --gpus '0' -python experiments/train.py -c baselines/STWave/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/STWave/PEMS07.py --gpus '0' -python experiments/train.py -c baselines/STWave/PEMS08.py --gpus '0' diff --git a/baselines/StemGNN/METR-LA.py b/baselines/StemGNN/METR-LA.py index b1ced724..dd268794 100644 --- a/baselines/StemGNN/METR-LA.py +++ b/baselines/StemGNN/METR-LA.py @@ -1,40 +1,31 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj -"""Different from the official code, we use Adam as the optimizer and MAE as the loss function since they bring better performance.""" from .arch import StemGNN -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = StemGNN -# ================= general ================= # -CFG.DESCRIPTION = "StemGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "METR-LA" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "StemGNN" -CFG.MODEL.ARCH = StemGNN -CFG.MODEL.PARAM = { +MODEL_PARAM = { "units": 207, "stack_cnt": 2, "time_step": 12, @@ -43,69 +34,105 @@ "dropout_rate": 0.5, "leaky_rate": 0.2 } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.0004 +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/StemGNN/PEMS-BAY.py b/baselines/StemGNN/PEMS-BAY.py index b5fc7a12..6d6e8c5e 100644 --- a/baselines/StemGNN/PEMS-BAY.py +++ b/baselines/StemGNN/PEMS-BAY.py @@ -1,41 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj -"""Different from the official code, we use Adam as the optimizer and MAE as the loss function since they bring better performance.""" from .arch import StemGNN -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS-BAY' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = StemGNN -# ================= general ================= # -CFG.DESCRIPTION = "StemGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS-BAY" -CFG.DATASET_TYPE = "Traffic speed" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "StemGNN" -CFG.MODEL.ARCH = StemGNN -CFG.MODEL.PARAM = { - "units": 325, +MODEL_PARAM = { + "units": 325, "stack_cnt": 2, "time_step": 12, "multi_layer": 5, @@ -43,69 +34,105 @@ "dropout_rate": 0.5, "leaky_rate": 0.2 } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.0004 +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/StemGNN/PEMS03.py b/baselines/StemGNN/PEMS03.py index c276d6bf..0af8bf5a 100644 --- a/baselines/StemGNN/PEMS03.py +++ b/baselines/StemGNN/PEMS03.py @@ -1,41 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj -"""Different from the official code, we use Adam as the optimizer and MAE as the loss function since they bring better performance.""" from .arch import StemGNN -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS03' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = StemGNN -# ================= general ================= # -CFG.DESCRIPTION = "StemGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS03" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "StemGNN" -CFG.MODEL.ARCH = StemGNN -CFG.MODEL.PARAM = { - "units": 358, +MODEL_PARAM = { + "units": 358, "stack_cnt": 2, "time_step": 12, "multi_layer": 5, @@ -43,69 +34,105 @@ "dropout_rate": 0.5, "leaky_rate": 0.2 } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.002 +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/StemGNN/PEMS04.py b/baselines/StemGNN/PEMS04.py index dfaad1a4..bb87639e 100644 --- a/baselines/StemGNN/PEMS04.py +++ b/baselines/StemGNN/PEMS04.py @@ -1,41 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj -"""Different from the official code, we use MAE as the loss function since they bring better performance.""" from .arch import StemGNN -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = StemGNN -# ================= general ================= # -CFG.DESCRIPTION = "StemGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "StemGNN" -CFG.MODEL.ARCH = StemGNN -CFG.MODEL.PARAM = { - "units": 307, +MODEL_PARAM = { + "units": 307, "stack_cnt": 2, "time_step": 12, "multi_layer": 5, @@ -43,69 +34,105 @@ "dropout_rate": 0.5, "leaky_rate": 0.2 } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "RMSprop" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.002 +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/StemGNN/PEMS07.py b/baselines/StemGNN/PEMS07.py index 4ff7e80a..000f0a49 100644 --- a/baselines/StemGNN/PEMS07.py +++ b/baselines/StemGNN/PEMS07.py @@ -1,41 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj -"""Different from the official code, we use Adam as the optimizer and MAE as the loss function since they bring better performance.""" from .arch import StemGNN -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS07' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = StemGNN -# ================= general ================= # -CFG.DESCRIPTION = "StemGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS07" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "StemGNN" -CFG.MODEL.ARCH = StemGNN -CFG.MODEL.PARAM = { - "units": 883, +MODEL_PARAM = { + "units": 883, "stack_cnt": 2, "time_step": 12, "multi_layer": 5, @@ -43,69 +34,105 @@ "dropout_rate": 0.5, "leaky_rate": 0.2 } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.002 +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/StemGNN/PEMS08.py b/baselines/StemGNN/PEMS08.py index 6a1d7c96..5f5dd3dc 100644 --- a/baselines/StemGNN/PEMS08.py +++ b/baselines/StemGNN/PEMS08.py @@ -1,41 +1,32 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) +import torch from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj -"""Different from the official code, we use Adam as the optimizer and MAE as the loss function since they bring better performance.""" from .arch import StemGNN -CFG = EasyDict() +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = StemGNN -# ================= general ================= # -CFG.DESCRIPTION = "StemGNN model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic flow" -CFG.DATASET_INPUT_LEN = 12 -CFG.DATASET_OUTPUT_LEN = 12 -CFG.GPU_NUM = 1 -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "StemGNN" -CFG.MODEL.ARCH = StemGNN -CFG.MODEL.PARAM = { - "units": 170, +MODEL_PARAM = { + "units": 170, "stack_cnt": 2, "time_step": 12, "multi_layer": 5, @@ -43,69 +34,105 @@ "dropout_rate": 0.5, "leaky_rate": 0.2 } -CFG.MODEL.FORWARD_FEATURES = [0] +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM= { - "lr":0.002 +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0004, + "weight_decay": 0.0003, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { - "milestones": [1, 50, 100], + "milestones": [1, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 16 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [3, 6, 12] + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/StemGNN/run.sh b/baselines/StemGNN/run.sh deleted file mode 100644 index 25cb8053..00000000 --- a/baselines/StemGNN/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/StemGNN/METR-LA.py --gpus '0' -python experiments/train.py -c baselines/StemGNN/PEMS-BAY.py --gpus '0' -python experiments/train.py -c baselines/StemGNN/PEMS03.py --gpus '0' -python experiments/train.py -c baselines/StemGNN/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/StemGNN/PEMS07.py --gpus '0' -python experiments/train.py -c baselines/StemGNN/PEMS08.py --gpus '0' diff --git a/baselines/TimesNet/ETTh1.py b/baselines/TimesNet/ETTh1.py index 343372fc..5d37971c 100644 --- a/baselines/TimesNet/ETTh1.py +++ b/baselines/TimesNet/ETTh1.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import TimesNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "TimesNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "TimesNet" -CFG.MODEL.ARCH = TimesNet +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = TimesNet NUM_NODES = 7 -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN // 2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN // 2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "enc_in": NUM_NODES, # num nodes "c_out": NUM_NODES, "top_k": 5, # attn factor @@ -52,74 +42,108 @@ "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 - } +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/TimesNet/ETTh2.py b/baselines/TimesNet/ETTh2.py index 20a0c2b3..594dee42 100644 --- a/baselines/TimesNet/ETTh2.py +++ b/baselines/TimesNet/ETTh2.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import TimesNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "TimesNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "TimesNet" -CFG.MODEL.ARCH = TimesNet +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = TimesNet NUM_NODES = 7 -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN // 2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN // 2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "enc_in": NUM_NODES, # num nodes "c_out": NUM_NODES, "top_k": 5, # attn factor @@ -52,74 +42,108 @@ "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 - } +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/TimesNet/ETTm1.py b/baselines/TimesNet/ETTm1.py index 451de593..61fed8bf 100644 --- a/baselines/TimesNet/ETTm1.py +++ b/baselines/TimesNet/ETTm1.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import TimesNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "TimesNet model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "TimesNet" -CFG.MODEL.ARCH = TimesNet +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = TimesNet NUM_NODES = 7 -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN // 2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN // 2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "enc_in": NUM_NODES, # num nodes "c_out": NUM_NODES, "top_k": 5, # attn factor @@ -48,78 +38,112 @@ "embed": "timeF", # [timeF, fixed, learned] "dropout": 0.05, "num_time_features": 4, # number of used time features - "time_of_day_size": 24 * 4, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 - } +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/TimesNet/ETTm2.py b/baselines/TimesNet/ETTm2.py index 9cf9a233..b6e313c1 100644 --- a/baselines/TimesNet/ETTm2.py +++ b/baselines/TimesNet/ETTm2.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import TimesNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "TimesNet model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "TimesNet" -CFG.MODEL.ARCH = TimesNet +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = TimesNet NUM_NODES = 7 -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN // 2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN // 2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "enc_in": NUM_NODES, # num nodes "c_out": NUM_NODES, "top_k": 5, # attn factor @@ -48,78 +38,112 @@ "embed": "timeF", # [timeF, fixed, learned] "dropout": 0.05, "num_time_features": 4, # number of used time features - "time_of_day_size": 24 * 4, + "time_of_day_size": 24*4, "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 - } +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # CFG.TRAIN.CLIP_GRAD_PARAM = { 'max_norm': 5.0 } -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/TimesNet/Electricity.py b/baselines/TimesNet/Electricity.py index 24cfbd75..0eb57a9f 100644 --- a/baselines/TimesNet/Electricity.py +++ b/baselines/TimesNet/Electricity.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import TimesNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "TimesNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity Consumption" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "TimesNet" -CFG.MODEL.ARCH = TimesNet +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = TimesNet NUM_NODES = 321 -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN // 2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN // 2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "enc_in": NUM_NODES, # num nodes "c_out": NUM_NODES, "top_k": 5, @@ -53,63 +43,107 @@ "day_of_month_size": 31, "day_of_year_size": 366 } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mse +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0002, + "weight_decay": 0.0005, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25, 50], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 10 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/TimesNet/ExchangeRate.py b/baselines/TimesNet/ExchangeRate.py index 163ee044..354681ad 100644 --- a/baselines/TimesNet/ExchangeRate.py +++ b/baselines/TimesNet/ExchangeRate.py @@ -1,43 +1,34 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import TimesNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "TimesNet model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "TimesNet" -CFG.MODEL.ARCH = TimesNet +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +INPUT_LEN = 96 +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = TimesNet NUM_NODES = 8 -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN // 2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN // 2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "enc_in": NUM_NODES, # num nodes "c_out": NUM_NODES, "top_k": 5, # attn factor @@ -52,71 +43,108 @@ "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 - } +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.0002, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/TimesNet/Weather.py b/baselines/TimesNet/Weather.py index 94297a12..8b846b0d 100644 --- a/baselines/TimesNet/Weather.py +++ b/baselines/TimesNet/Weather.py @@ -1,43 +1,33 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mse, masked_mae +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import TimesNet -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "TimesNet model configuration " -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather Data" -CFG.DATASET_INPUT_LEN = 96 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "TimesNet" -CFG.MODEL.ARCH = TimesNet -NUM_NODES = 21 -CFG.MODEL.PARAM = { - "seq_len": CFG.DATASET_INPUT_LEN, - "label_len": CFG.DATASET_INPUT_LEN // 2, # start token length used in decoder - "pred_len": CFG.DATASET_OUTPUT_LEN, # prediction sequence length +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = TimesNet +NUM_NODES = 8 +MODEL_PARAM = { + "seq_len": INPUT_LEN, + "label_len": INPUT_LEN // 2, # start token length used in decoder + "pred_len": OUTPUT_LEN, # prediction sequence length "enc_in": NUM_NODES, # num nodes "c_out": NUM_NODES, "top_k": 5, # attn factor @@ -52,72 +42,108 @@ "day_of_week_size": 7, "day_of_month_size": 31, "day_of_year_size": 366 - } +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2, 3, 4] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { "lr": 0.00001, "weight_decay": 0.0005, } +# Learning rate scheduler settings CFG.TRAIN.LR_SCHEDULER = EasyDict() CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" CFG.TRAIN.LR_SCHEDULER.PARAM = { "milestones": [1, 25, 50], "gamma": 0.5 } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/TimesNet/run.sh b/baselines/TimesNet/run.sh deleted file mode 100644 index 9df02f42..00000000 --- a/baselines/TimesNet/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -python experiments/train.py -c baselines/TimesNet/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/TimesNet/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/TimesNet/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/TimesNet/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/TimesNet/Electricity.py --gpus '0' -python experiments/train.py -c baselines/TimesNet/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/TimesNet/Weather.py --gpus '0' -python experiments/train.py -c baselines/TimesNet/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/TimesNet/PEMS08.py --gpus '0' diff --git a/baselines/Triformer/ETTh1.py b/baselines/Triformer/ETTh1.py index c7627e97..aa67dff4 100644 --- a/baselines/Triformer/ETTh1.py +++ b/baselines/Triformer/ETTh1.py @@ -1,108 +1,140 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Triformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, "input_dim": 3, # default parameters described in the paper "channels": 32, "patch_sizes": [7, 4, 3, 2, 2], "mem_dim": 5 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0002, + "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/ETTh2.py b/baselines/Triformer/ETTh2.py index 4f436621..c4df760e 100644 --- a/baselines/Triformer/ETTh2.py +++ b/baselines/Triformer/ETTh2.py @@ -1,108 +1,140 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Triformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTh2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, "input_dim": 3, # default parameters described in the paper "channels": 32, "patch_sizes": [7, 4, 3, 2, 2], "mem_dim": 5 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0002, + "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/ETTm1.py b/baselines/Triformer/ETTm1.py index 4ef501cc..a39c29d1 100644 --- a/baselines/Triformer/ETTm1.py +++ b/baselines/Triformer/ETTm1.py @@ -1,108 +1,140 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Triformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm1' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, "input_dim": 3, # default parameters described in the paper "channels": 32, "patch_sizes": [7, 4, 3, 2, 2], "mem_dim": 5 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0002, + "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/ETTm2.py b/baselines/Triformer/ETTm2.py index e0463423..06b6a096 100644 --- a/baselines/Triformer/ETTm2.py +++ b/baselines/Triformer/ETTm2.py @@ -1,108 +1,140 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Triformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTm2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ETTm2' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer NUM_NODES = 7 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, "input_dim": 3, # default parameters described in the paper "channels": 32, "patch_sizes": [7, 4, 3, 2, 2], "mem_dim": 5 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0002, + "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/Electricity.py b/baselines/Triformer/Electricity.py index 4c91746d..4bd08a52 100644 --- a/baselines/Triformer/Electricity.py +++ b/baselines/Triformer/Electricity.py @@ -1,108 +1,140 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Triformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Electricity" -CFG.DATASET_TYPE = "Electricity Consumption" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Electricity' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer NUM_NODES = 321 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, "input_dim": 3, # default parameters described in the paper "channels": 32, "patch_sizes": [7, 4, 3, 2, 2], "mem_dim": 5 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0002 + "lr": 0.0002, + "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 14 -CFG.TRAIN.DATA.PREFETCH = False +CFG.TRAIN.DATA.BATCH_SIZE = 64 CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/ExchangeRate.py b/baselines/Triformer/ExchangeRate.py index 95652075..b7406cff 100644 --- a/baselines/Triformer/ExchangeRate.py +++ b/baselines/Triformer/ExchangeRate.py @@ -1,108 +1,140 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Triformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ExchangeRate" -CFG.DATASET_TYPE = "Exchange Rate" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'ExchangeRate' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer NUM_NODES = 8 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, "input_dim": 3, # default parameters described in the paper "channels": 32, "patch_sizes": [7, 4, 3, 2, 2], "mem_dim": 5 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0002, + "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/PEMS04.py b/baselines/Triformer/PEMS04.py deleted file mode 100644 index f2dbc000..00000000 --- a/baselines/Triformer/PEMS04.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mae, masked_mse - -from .arch import Triformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS04" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False -CFG.NULL_VAL = 0.0 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer -NUM_NODES = 307 -CFG.MODEL.PARAM = EasyDict( - { - "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, - "input_dim": 3, - # default parameters described in the paper - "channels": 32, - "patch_sizes": [7, 4, 3, 2, 2], - "mem_dim": 5 - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 8 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 32 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 32 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Triformer/PEMS04_LTSF.py b/baselines/Triformer/PEMS04_LTSF.py new file mode 100644 index 00000000..7f3b4925 --- /dev/null +++ b/baselines/Triformer/PEMS04_LTSF.py @@ -0,0 +1,132 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_rmse, masked_mape +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Triformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS04' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer +NUM_NODES = 307 +MODEL_PARAM = { + "num_nodes": NUM_NODES, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, + "input_dim": 3, + # default parameters described in the paper + "channels": 32, + "patch_sizes": [7, 4, 3, 2, 2], + "mem_dim": 5 + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0001 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/PEMS08.py b/baselines/Triformer/PEMS08.py deleted file mode 100644 index e0b477ee..00000000 --- a/baselines/Triformer/PEMS08.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.metrics import masked_mae, masked_mse - -from .arch import Triformer - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "PEMS08" -CFG.DATASET_TYPE = "Traffic Flow" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer -NUM_NODES = 170 -CFG.MODEL.PARAM = EasyDict( - { - "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, - "input_dim": 3, - # default parameters described in the paper - "channels": 32, - "patch_sizes": [7, 4, 3, 2, 2], - "mem_dim": 5 - } -) -CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 100 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 16 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/Triformer/PEMS08_LTSF.py b/baselines/Triformer/PEMS08_LTSF.py new file mode 100644 index 00000000..86b2780f --- /dev/null +++ b/baselines/Triformer/PEMS08_LTSF.py @@ -0,0 +1,132 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_rmse, masked_mape +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings + +from .arch import Triformer + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +# INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +# OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output +INPUT_LEN = 336 # LTSF +OUTPUT_LEN = 336 # LTSF +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer +NUM_NODES = 170 +MODEL_PARAM = { + "num_nodes": NUM_NODES, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, + "input_dim": 3, + # default parameters described in the paper + "channels": 32, + "patch_sizes": [7, 4, 3, 2, 2], + "mem_dim": 5 + } +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.0001 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/Weather.py b/baselines/Triformer/Weather.py index 4cfb6ab0..dd678a4b 100644 --- a/baselines/Triformer/Weather.py +++ b/baselines/Triformer/Weather.py @@ -1,108 +1,140 @@ import os import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset +sys.path.append(os.path.abspath(__file__ + '/../../..')) + from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings from .arch import Triformer -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "Triformer model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "Weather" -CFG.DATASET_TYPE = "Weather" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 -# CFG.RESCALE = False - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 0 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "Triformer" -CFG.MODEL.ARCH = Triformer +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'Weather' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = Triformer NUM_NODES = 21 -CFG.MODEL.PARAM = EasyDict( - { +MODEL_PARAM = { "num_nodes": NUM_NODES, - "lag": CFG.DATASET_INPUT_LEN, - "horizon": CFG.DATASET_OUTPUT_LEN, + "lag": INPUT_LEN, + "horizon": OUTPUT_LEN, "input_dim": 3, # default parameters described in the paper "channels": 32, "patch_sizes": [7, 4, 3, 2, 2], "mem_dim": 5 } -) +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] CFG.MODEL.TARGET_FEATURES = [0] -# ================= optim ================= # +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) CFG.TRAIN.LOSS = masked_mae +# Optimizer settings CFG.TRAIN.OPTIM = EasyDict() CFG.TRAIN.OPTIM.TYPE = "Adam" CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.0001 + "lr": 0.0002, + "weight_decay": 0.0001, } - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 50 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - 'checkpoints', - '_'.join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 25], + "gamma": 0.5 +} +CFG.TRAIN.CLIP_GRAD_PARAM = { + 'max_norm': 5.0 +} +# Train data loader settings CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False -# ================= validate ================= # +############################## Validation Configuration ############################## CFG.VAL = EasyDict() CFG.VAL.INTERVAL = 1 -# validating data CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False -# ================= test ================= # +############################## Test Configuration ############################## CFG.TEST = EasyDict() CFG.TEST.INTERVAL = 1 -# test data CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = 'datasets/' + CFG.DATASET_NAME -# dataloader args, optional CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False -# ================= evaluate ================= # +############################## Evaluation Configuration ############################## + CFG.EVAL = EasyDict() + +# Evaluation parameters CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/baselines/Triformer/run.sh b/baselines/Triformer/run.sh deleted file mode 100644 index cda838f5..00000000 --- a/baselines/Triformer/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -# # !/bin/bash -python experiments/train.py -c baselines/Triformer/ETTh1.py --gpus '0' -python experiments/train.py -c baselines/Triformer/ETTh2.py --gpus '0' -python experiments/train.py -c baselines/Triformer/ETTm1.py --gpus '0' -python experiments/train.py -c baselines/Triformer/ETTm2.py --gpus '0' -python experiments/train.py -c baselines/Triformer/Electricity.py --gpus '0' -python experiments/train.py -c baselines/Triformer/ExchangeRate.py --gpus '0' -python experiments/train.py -c baselines/Triformer/Weather.py --gpus '0' -python experiments/train.py -c baselines/Triformer/PEMS04.py --gpus '0' -python experiments/train.py -c baselines/Triformer/PEMS08.py --gpus '0' diff --git a/baselines/WaveNet/ETTh1.py b/baselines/WaveNet/ETTh1.py deleted file mode 100644 index 13ad3579..00000000 --- a/baselines/WaveNet/ETTh1.py +++ /dev/null @@ -1,107 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae - -from .arch import WaveNet - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh1" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "WaveNet" -CFG.MODEL.ARCH = WaveNet -CFG.MODEL.PARAM = { - "in_dim": 1, - "out_dim": CFG.DATASET_OUTPUT_LEN, - "residual_channels": 16, - "dilation_channels": 16, - "skip_channels": 64, - "end_channels": 128, - "kernel_size": 12, - "blocks": 6, - "layers": 3 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 20 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/WaveNet/ETTh2.py b/baselines/WaveNet/ETTh2.py deleted file mode 100644 index b4149854..00000000 --- a/baselines/WaveNet/ETTh2.py +++ /dev/null @@ -1,107 +0,0 @@ -import os -import sys - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../..")) -import torch -from easydict import EasyDict -from basicts.runners import SimpleTimeSeriesForecastingRunner -from basicts.data import TimeSeriesForecastingDataset -from basicts.losses import masked_mae - -from .arch import WaveNet - -CFG = EasyDict() - -# ================= general ================= # -CFG.DESCRIPTION = "WaveNet model configuration" -CFG.RUNNER = SimpleTimeSeriesForecastingRunner -CFG.DATASET_CLS = TimeSeriesForecastingDataset -CFG.DATASET_NAME = "ETTh2" -CFG.DATASET_TYPE = "Electricity Transformer Temperature" -CFG.DATASET_INPUT_LEN = 336 -CFG.DATASET_OUTPUT_LEN = 336 -CFG.GPU_NUM = 1 - -# ================= environment ================= # -CFG.ENV = EasyDict() -CFG.ENV.SEED = 1 -CFG.ENV.CUDNN = EasyDict() -CFG.ENV.CUDNN.ENABLED = True - -# ================= model ================= # -CFG.MODEL = EasyDict() -CFG.MODEL.NAME = "WaveNet" -CFG.MODEL.ARCH = WaveNet -CFG.MODEL.PARAM = { - "in_dim": 1, - "out_dim": CFG.DATASET_OUTPUT_LEN, - "residual_channels": 16, - "dilation_channels": 16, - "skip_channels": 64, - "end_channels": 128, - "kernel_size": 12, - "blocks": 6, - "layers": 3 -} -CFG.MODEL.FORWARD_FEATURES = [0] -CFG.MODEL.TARGET_FEATURES = [0] - -# ================= optim ================= # -CFG.TRAIN = EasyDict() -CFG.TRAIN.LOSS = masked_mae -CFG.TRAIN.OPTIM = EasyDict() -CFG.TRAIN.OPTIM.TYPE = "Adam" -CFG.TRAIN.OPTIM.PARAM = { - "lr": 0.002, - "weight_decay": 0.0001, -} - -# ================= train ================= # -CFG.TRAIN.NUM_EPOCHS = 20 -CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( - "checkpoints", - "_".join([CFG.MODEL.NAME, str(CFG.TRAIN.NUM_EPOCHS)]) -) -# train data -CFG.TRAIN.DATA = EasyDict() -# read data -CFG.TRAIN.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TRAIN.DATA.BATCH_SIZE = 64 -CFG.TRAIN.DATA.PREFETCH = False -CFG.TRAIN.DATA.SHUFFLE = True -CFG.TRAIN.DATA.NUM_WORKERS = 2 -CFG.TRAIN.DATA.PIN_MEMORY = False - -# ================= validate ================= # -CFG.VAL = EasyDict() -CFG.VAL.INTERVAL = 1 -# validating data -CFG.VAL.DATA = EasyDict() -# read data -CFG.VAL.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.VAL.DATA.BATCH_SIZE = 64 -CFG.VAL.DATA.PREFETCH = False -CFG.VAL.DATA.SHUFFLE = False -CFG.VAL.DATA.NUM_WORKERS = 2 -CFG.VAL.DATA.PIN_MEMORY = False - -# ================= test ================= # -CFG.TEST = EasyDict() -CFG.TEST.INTERVAL = 1 -# test data -CFG.TEST.DATA = EasyDict() -# read data -CFG.TEST.DATA.DIR = "datasets/" + CFG.DATASET_NAME -# dataloader args, optional -CFG.TEST.DATA.BATCH_SIZE = 64 -CFG.TEST.DATA.PREFETCH = False -CFG.TEST.DATA.SHUFFLE = False -CFG.TEST.DATA.NUM_WORKERS = 2 -CFG.TEST.DATA.PIN_MEMORY = False - -# ================= evaluate ================= # -CFG.EVAL = EasyDict() -CFG.EVAL.HORIZONS = [12, 24, 48, 96, 192, 288, 336] diff --git a/baselines/WaveNet/METR-LA.py b/baselines/WaveNet/METR-LA.py new file mode 100644 index 00000000..a72e5030 --- /dev/null +++ b/baselines/WaveNet/METR-LA.py @@ -0,0 +1,142 @@ +import os +import sys +import torch +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings, load_adj + +from .arch import WaveNet + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'METR-LA' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = WaveNet +MODEL_PARAM = { + "in_dim": 1, + "out_dim": OUTPUT_LEN, + "residual_channels": 16, + "dilation_channels": 16, + "skip_channels": 64, + "end_channels": 128, + "kernel_size": 12, + "blocks": 6, + "layers": 3 +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MSE': masked_mse + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = "Adam" +CFG.TRAIN.OPTIM.PARAM = { + "lr": 0.002, + "weight_decay": 0.0001, +} +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() +CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR" +CFG.TRAIN.LR_SCHEDULER.PARAM = { + "milestones": [1, 50], + "gamma": 0.5 +} +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 16 +CFG.TRAIN.DATA.SHUFFLE = True +# Gradient clipping settings +CFG.TRAIN.CLIP_GRAD_PARAM = { + "max_norm": 5.0 +} + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/basicts/__init__.py b/basicts/__init__.py index db1ee888..e136d21c 100644 --- a/basicts/__init__.py +++ b/basicts/__init__.py @@ -1,6 +1,6 @@ -from .launcher import launch_training, launch_runner +from .launcher import launch_training, launch_evaluation from .runners import BaseRunner -__version__ = "0.3.12" +__version__ = '0.4.0' -__all__ = ["__version__", "launch_training", "launch_runner", "BaseRunner"] +__all__ = ['__version__', 'launch_training', 'launch_evaluation', 'BaseRunner'] diff --git a/basicts/archs/example_arch.py b/basicts/archs/example_arch.py deleted file mode 100644 index 6f1fd90e..00000000 --- a/basicts/archs/example_arch.py +++ /dev/null @@ -1,25 +0,0 @@ -import torch -from torch import nn - -class MultiLayerPerceptron(nn.Module): - """Two fully connected layer.""" - - def __init__(self, history_seq_len: int, prediction_seq_len: int, hidden_dim: int): - super().__init__() - self.fc1 = nn.Linear(history_seq_len, hidden_dim) - self.fc2 = nn.Linear(hidden_dim, prediction_seq_len) - self.act = nn.ReLU() - - def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool, **kwargs) -> torch.Tensor: - """Feedforward function of MLP. - - Args: - history_data (torch.Tensor): inputs with shape [B, L, N, C]. - - Returns: - torch.Tensor: outputs with shape [B, L, N, C] - """ - - history_data = history_data[..., 0].transpose(1, 2) # B, N, L - prediction = self.fc2(self.act(self.fc1(history_data))).transpose(1, 2) # B, L, N - return prediction.unsqueeze(-1) # B, L, N, C diff --git a/basicts/data/__init__.py b/basicts/data/__init__.py index b0b48d8c..60a24036 100644 --- a/basicts/data/__init__.py +++ b/basicts/data/__init__.py @@ -1,12 +1,4 @@ -import os +from .base_dataset import BaseDataset +from .simple_tsf_dataset import TimeSeriesForecastingDataset -from ..utils.misc import scan_modules -from .registry import SCALER_REGISTRY -from .dataset_zoo.simple_tsf_dataset import TimeSeriesForecastingDataset -from .dataset_zoo.m4_dataset import M4ForecastingDataset - -__all__ = ["SCALER_REGISTRY", "TimeSeriesForecastingDataset", "M4ForecastingDataset"] - -# fix bugs on Windows systems and on jupyter -project_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -scan_modules(project_dir, __file__, ["__init__.py", "registry.py"], ["dataset_zoo/", ".ipynb_checkpoints/"]) +__all__ = ['BaseDataset', 'TimeSeriesForecastingDataset'] diff --git a/basicts/data/base_dataset.py b/basicts/data/base_dataset.py new file mode 100644 index 00000000..ffb27d9c --- /dev/null +++ b/basicts/data/base_dataset.py @@ -0,0 +1,100 @@ +from typing import List +from dataclasses import dataclass +import numpy as np +from torch.utils.data import Dataset + + +@dataclass +class BaseDataset(Dataset): + """ + An abstract base class for creating datasets for time series forecasting in PyTorch. + + This class provides a structured template for defining custom datasets by specifying methods + to load data and descriptions, and to access individual samples. It is designed to be subclassed + with specific implementations for different types of time series data. + + Attributes: + dataset_name (str): The name of the dataset which is used for identifying the dataset uniquely. + train_val_test_ratio (List[float]): Ratios for splitting the dataset into training, validation, + and testing sets respectively. Each value in the list should sum to 1.0. + mode (str): Operational mode of the dataset. Valid values are "train", "valid", or "test". + input_len (int): The length of the input sequence, i.e., the number of historical data points used. + output_len (int): The length of the output sequence, i.e., the number of future data points predicted. + overlap (bool): Flag to indicate whether the splits between training, validation, and testing can overlap. + Defaults to True but can be set to False to enforce non-overlapping data in different sets. + """ + + dataset_name: str + train_val_test_ratio: List[float] + mode: str + input_len: int + output_len: int + overlap: bool = True + + def _load_description(self) -> dict: + """ + Abstract method to load a dataset's description from a file or source. + + This method should be implemented by subclasses to load and return the dataset's metadata, + such as its shape, range, or other relevant properties, typically from a JSON or similar file. + + Returns: + dict: A dictionary containing the dataset's metadata. + + Raises: + NotImplementedError: If the method has not been implemented by a subclass. + """ + + raise NotImplementedError("Subclasses must implement this method.") + + def _load_data(self) -> np.ndarray: + """ + Abstract method to load the dataset and organize it based on the specified mode. + + This method should be implemented by subclasses to load actual time series data into an array, + handling any necessary preprocessing and partitioning according to the specified `mode`. + + Returns: + np.ndarray: The loaded and appropriately split dataset array. + + Raises: + NotImplementedError: If the method has not been implemented by a subclass. + """ + + raise NotImplementedError("Subclasses must implement this method.") + + def __len__(self) -> int: + """ + Abstract method to get the total number of samples available in the dataset. + + This method should be implemented by subclasses to calculate and return the total number of valid + samples available for training, validation, or testing based on the configuration and dataset size. + + Returns: + int: The total number of samples. + + Raises: + NotImplementedError: If the method has not been implemented by a subclass. + """ + + raise NotImplementedError("Subclasses must implement this method.") + + def __getitem__(self, idx: int) -> dict: + """ + Abstract method to retrieve a single sample from the dataset. + + This method should be implemented by subclasses to access and return a specific sample from the dataset, + given an index. It should handle the slicing of input and output sequences according to the defined + `input_len` and `output_len`. + + Args: + idx (int): The index of the sample to retrieve. + + Returns: + dict: A dictionary containing the input sequence ('inputs') and output sequence ('target'). + + Raises: + NotImplementedError: If the method has not been implemented by a subclass. + """ + + raise NotImplementedError("Subclasses must implement this method.") diff --git a/basicts/data/dataset_zoo/m4_dataset.py b/basicts/data/dataset_zoo/m4_dataset.py deleted file mode 100644 index ba534efd..00000000 --- a/basicts/data/dataset_zoo/m4_dataset.py +++ /dev/null @@ -1,84 +0,0 @@ -import os -import random - -import torch -from torch.utils.data import Dataset - -from ...utils import load_pkl - - -class M4ForecastingDataset(Dataset): - """ - BasicTS tries its best to follow the commonly-used processing approaches of M4 dataset, while also providing more flexible interfaces. - M4 dataset differs from general MTS datasets in the following aspects: - - M4 dataset is a univariate time series dataset, which does not sample in a synchronized manner. - In the state-of-the-art M4 prediction solutions, NBeats [1], the authors first sample ids of the time series and then randomly sample the time series data for each time series. - - Padding and masking are used to make training more flexible and robust. - - There is no normalization in M4 dataset. - - There is no validation dataset in M4 dataset. - - The test data is the last sample of each time series. - - The future sequence length is fixed for different subsets. - - Reference: - [1] N-BEATS: Neural basis expansion analysis for interpretable time series forecasting - [2] https://github.com/ServiceNow/N-BEATS/blob/master/common/sampler.py - """ - - def __init__(self, data_file_path: str, index_file_path: str, mask_file_path: str, mode: str) -> None: - super().__init__() - assert mode in ["train", "test"], "error mode" - self._check_if_file_exists(data_file_path, index_file_path, mask_file_path) - # read raw data (normalized) - self.data = load_pkl(data_file_path)[mode] # padded data: List[List] - self.mask = load_pkl(mask_file_path)[mode] # padded mask: List[List] - # read index - self.index = load_pkl(index_file_path)[mode] # train/test index of each time series: List[List] - - def _check_if_file_exists(self, data_file_path: str, index_file_path: str, mask_file_path: str): - """Check if data file and index file exist. - - Args: - data_file_path (str): data file path - index_file_path (str): index file path - - Raises: - FileNotFoundError: no data file - FileNotFoundError: no index file - """ - - if not os.path.isfile(data_file_path): - raise FileNotFoundError("BasicTS can not find data file {0}".format(data_file_path)) - if not os.path.isfile(index_file_path): - raise FileNotFoundError("BasicTS can not find index file {0}".format(index_file_path)) - if not os.path.isfile(mask_file_path): - raise FileNotFoundError("BasicTS can not find mask file {0}".format(mask_file_path)) - - def __getitem__(self, ts_id: int) -> tuple: - """Get a sample. - - Args: - ts_id (int): the iteration index, i.e., the time series id (not the self.index). - - Returns: - tuple: future_data, history_data, future_mask, history_mask, where the shape of data is L x C and mask is L. - """ - - ts_idxs = list(self.index[ts_id]) - # random select a time series sample - idx = ts_idxs[random.randint(0, len(ts_idxs)-1)] - - history_data = torch.Tensor(self.data[ts_id][idx[0]:idx[1]]).unsqueeze(1).float() - future_data = torch.Tensor(self.data[ts_id][idx[1]:idx[2]]).unsqueeze(1).float() - history_mask = torch.Tensor(self.mask[ts_id][idx[0]:idx[1]]).unsqueeze(1).float() - future_mask = torch.Tensor(self.mask[ts_id][idx[1]:idx[2]]).unsqueeze(1).float() - - return future_data, history_data, future_mask, history_mask - - def __len__(self): - """Dataset length (=number of time series) - - Returns: - int: dataset length - """ - - return len(self.data) diff --git a/basicts/data/dataset_zoo/simple_tsf_dataset.py b/basicts/data/dataset_zoo/simple_tsf_dataset.py deleted file mode 100644 index 2fa38d7a..00000000 --- a/basicts/data/dataset_zoo/simple_tsf_dataset.py +++ /dev/null @@ -1,73 +0,0 @@ -import os - -import torch -from torch.utils.data import Dataset - -from ...utils import load_pkl - - -class TimeSeriesForecastingDataset(Dataset): - """Time series forecasting dataset.""" - - def __init__(self, data_file_path: str, index_file_path: str, mode: str) -> None: - super().__init__() - assert mode in ["train", "valid", "test"], "error mode" - self._check_if_file_exists(data_file_path, index_file_path) - # read raw data (normalized) - data = load_pkl(data_file_path) - processed_data = data["processed_data"] - self.data = torch.from_numpy(processed_data).float() - # read index - self.index = load_pkl(index_file_path)[mode] - - def _check_if_file_exists(self, data_file_path: str, index_file_path: str): - """Check if data file and index file exist. - - Args: - data_file_path (str): data file path - index_file_path (str): index file path - - Raises: - FileNotFoundError: no data file - FileNotFoundError: no index file - """ - - if not os.path.isfile(data_file_path): - raise FileNotFoundError("BasicTS can not find data file {0}".format(data_file_path)) - if not os.path.isfile(index_file_path): - raise FileNotFoundError("BasicTS can not find index file {0}".format(index_file_path)) - - def __getitem__(self, index: int) -> tuple: - """Get a sample. - - Args: - index (int): the iteration index (not the self.index) - - Returns: - tuple: (future_data, history_data), where the shape of each is L x N x C. - """ - - idx = list(self.index[index]) - if isinstance(idx[0], int): - # continuous index - history_data = self.data[idx[0]:idx[1]] - future_data = self.data[idx[1]:idx[2]] - else: - # discontinuous index or custom index - # NOTE: current time $t$ should not included in the index[0] - history_index = idx[0] # list - assert idx[1] not in history_index, "current time t should not included in the idx[0]" - history_index.append(idx[1]) - history_data = self.data[history_index] - future_data = self.data[idx[1], idx[2]] - - return future_data, history_data - - def __len__(self): - """Dataset length - - Returns: - int: dataset length - """ - - return len(self.index) diff --git a/basicts/data/registry.py b/basicts/data/registry.py deleted file mode 100644 index 826969df..00000000 --- a/basicts/data/registry.py +++ /dev/null @@ -1,3 +0,0 @@ -from easytorch.utils.registry import Registry - -SCALER_REGISTRY = Registry("Scaler") diff --git a/basicts/data/simple_tsf_dataset.py b/basicts/data/simple_tsf_dataset.py new file mode 100644 index 00000000..231696eb --- /dev/null +++ b/basicts/data/simple_tsf_dataset.py @@ -0,0 +1,124 @@ +import json +from typing import List + +import numpy as np + +from .base_dataset import BaseDataset + + +class TimeSeriesForecastingDataset(BaseDataset): + """ + A dataset class for time series forecasting problems, handling the loading, parsing, and partitioning + of time series data into training, validation, and testing sets based on provided ratios. + + This class supports configurations where sequences may or may not overlap, accommodating scenarios + where time series data is drawn from continuous periods or distinct episodes, affecting how + the data is split into batches for model training or evaluation. + + Attributes: + data_file_path (str): Path to the file containing the time series data. + description_file_path (str): Path to the JSON file containing the description of the dataset. + data (np.ndarray): The loaded time series data array, split according to the specified mode. + description (dict): Metadata about the dataset, such as shape and other properties. + """ + + def __init__(self, dataset_name: str, train_val_test_ratio: List[float], mode: str, input_len: int, output_len: int, overlap: bool = True) -> None: + """ + Initializes the TimeSeriesForecastingDataset by setting up paths, loading data, and + preparing it according to the specified configurations. + + Args: + dataset_name (str): The name of the dataset. + train_val_test_ratio (List[float]): Ratios for splitting the dataset into train, validation, and test sets. + Each value should be a float between 0 and 1, and their sum should ideally be 1. + mode (str): The operation mode of the dataset. Valid values are 'train', 'valid', or 'test'. + input_len (int): The length of the input sequence (number of historical points). + output_len (int): The length of the output sequence (number of future points to predict). + overlap (bool): Flag to determine if training/validation/test splits should overlap. + Defaults to True. Set to False for strictly non-overlapping periods. + + Raises: + AssertionError: If `mode` is not one of ['train', 'valid', 'test']. + """ + assert mode in ['train', 'valid', 'test'], f"Invalid mode: {mode}. Must be one of ['train', 'valid', 'test']." + super().__init__(dataset_name, train_val_test_ratio, mode, input_len, output_len, overlap) + + self.data_file_path = f'datasets/{dataset_name}/data.dat' + self.description_file_path = f'datasets/{dataset_name}/desc.json' + self.description = self._load_description() + self.data = self._load_data() + + def _load_description(self) -> dict: + """ + Loads the description of the dataset from a JSON file. + + Returns: + dict: A dictionary containing metadata about the dataset, such as its shape and other properties. + + Raises: + FileNotFoundError: If the description file is not found. + json.JSONDecodeError: If there is an error decoding the JSON data. + """ + + try: + with open(self.description_file_path, 'r') as f: + return json.load(f) + except FileNotFoundError as e: + raise FileNotFoundError(f'Description file not found: {self.description_file_path}') from e + except json.JSONDecodeError as e: + raise ValueError(f'Error decoding JSON file: {self.description_file_path}') from e + + def _load_data(self) -> np.ndarray: + """ + Loads the time series data from a file and splits it according to the selected mode. + + Returns: + np.ndarray: The data array for the specified mode (train, validation, or test). + + Raises: + ValueError: If there is an issue with loading the data file or if the data shape is not as expected. + """ + + try: + data = np.memmap(self.data_file_path, dtype='float32', mode='r', shape=tuple(self.description['shape'])) + except (FileNotFoundError, ValueError) as e: + raise ValueError(f'Error loading data file: {self.data_file_path}') from e + + total_len = len(data) + train_len = int(total_len * self.train_val_test_ratio[0]) + valid_len = int(total_len * self.train_val_test_ratio[1]) + + if self.mode == 'train': + offset = self.output_len if self.overlap else 0 + return data[:train_len + offset].copy() + elif self.mode == 'valid': + offset_left = self.input_len - 1 if self.overlap else 0 + offset_right = self.output_len if self.overlap else 0 + return data[train_len - offset_left : train_len + valid_len + offset_right].copy() + else: # self.mode == 'test' + offset = self.input_len - 1 if self.overlap else 0 + return data[train_len + valid_len - offset:].copy() + + def __getitem__(self, index: int) -> dict: + """ + Retrieves a sample from the dataset at the specified index, considering both the input and output lengths. + + Args: + index (int): The index of the desired sample in the dataset. + + Returns: + dict: A dictionary containing 'inputs' and 'target', where both are slices of the dataset corresponding to + the historical input data and future prediction data, respectively. + """ + history_data = self.data[index:index + self.input_len] + future_data = self.data[index + self.input_len:index + self.input_len + self.output_len] + return {'inputs': history_data, 'target': future_data} + + def __len__(self) -> int: + """ + Calculates the total number of samples available in the dataset, adjusted for the lengths of input and output sequences. + + Returns: + int: The number of valid samples that can be drawn from the dataset, based on the configurations of input and output lengths. + """ + return len(self.data) - self.input_len - self.output_len + 1 diff --git a/basicts/data/transform.py b/basicts/data/transform.py deleted file mode 100644 index 1f7d9655..00000000 --- a/basicts/data/transform.py +++ /dev/null @@ -1,127 +0,0 @@ -import pickle - -import torch -import numpy as np - -from .registry import SCALER_REGISTRY - - -@SCALER_REGISTRY.register() -def standard_transform(data: np.array, output_dir: str, train_index: list, history_seq_len: int, future_seq_len: int, norm_each_channel: int = False) -> np.array: - """Standard normalization. - - Args: - data (np.array): raw time series data. - output_dir (str): output dir path. - train_index (list): train index. - history_seq_len (int): historical sequence length. - future_seq_len (int): future sequence length. - norm_each_channel (bool): whether to normalization each channel. - - Returns: - np.array: normalized raw time series data. - """ - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - # data: L, N, C, C=1 - data_train = data[:train_index[-1][1], ...] - if norm_each_channel: - mean, std = data_train.mean(axis=0, keepdims=True), data_train.std(axis=0, keepdims=True) - else: - mean, std = data_train[..., 0].mean(), data_train[..., 0].std() - - print("mean (training data):", mean) - print("std (training data):", std) - scaler = {} - scaler["func"] = re_standard_transform.__name__ - scaler["args"] = {"mean": mean, "std": std} - # label to identify the scaler for different settings. - with open(output_dir + "/scaler_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(scaler, f) - - def normalize(x): - return (x - mean) / std - - data_norm = normalize(data) - return data_norm - - -@SCALER_REGISTRY.register() -def re_standard_transform(data: torch.Tensor, **kwargs) -> torch.Tensor: - """Standard re-transformation. - - Args: - data (torch.Tensor): input data. - - Returns: - torch.Tensor: re-scaled data. - """ - - mean, std = kwargs["mean"], kwargs["std"] - if isinstance(mean, np.ndarray): - mean = torch.from_numpy(mean).type_as(data).to(data.device).unsqueeze(0) - std = torch.from_numpy(std).type_as(data).to(data.device).unsqueeze(0) - data = data * std - data = data + mean - return data - - -@SCALER_REGISTRY.register() -def min_max_transform(data: np.array, output_dir: str, train_index: list, history_seq_len: int, future_seq_len: int) -> np.array: - """Min-max normalization. - - Args: - data (np.array): raw time series data. - output_dir (str): output dir path. - train_index (list): train index. - history_seq_len (int): historical sequence length. - future_seq_len (int): future sequence length. - - Returns: - np.array: normalized raw time series data. - """ - - # L, N, C, C=1 - data_train = data[:train_index[-1][1], ...] - - min_value = data_train.min(axis=(0, 1), keepdims=False)[0] - max_value = data_train.max(axis=(0, 1), keepdims=False)[0] - - print("min: (training data)", min_value) - print("max: (training data)", max_value) - scaler = {} - scaler["func"] = re_min_max_transform.__name__ - scaler["args"] = {"min_value": min_value, "max_value": max_value} - # label to identify the scaler for different settings. - # To be fair, only one transformation can be implemented per dataset. - # TODO: Therefore we (for now) do not distinguish between the data produced by the different transformation methods. - with open(output_dir + "/scaler_in_{0}_out_{1}.pkl".format(history_seq_len, future_seq_len), "wb") as f: - pickle.dump(scaler, f) - - def normalize(x): - # ref: - # https://github.com/guoshnBJTU/ASTGNN/blob/f0f8c2f42f76cc3a03ea26f233de5961c79c9037/lib/utils.py#L17 - x = 1. * (x - min_value) / (max_value - min_value) - x = 2. * x - 1. - return x - - data_norm = normalize(data) - return data_norm - - -@SCALER_REGISTRY.register() -def re_min_max_transform(data: torch.Tensor, **kwargs) -> torch.Tensor: - """Standard re-min-max transform. - - Args: - data (torch.Tensor): input data. - - Returns: - torch.Tensor: re-scaled data. - """ - - min_value, max_value = kwargs["min_value"], kwargs["max_value"] - # ref: - # https://github.com/guoshnBJTU/ASTGNN/blob/f0f8c2f42f76cc3a03ea26f233de5961c79c9037/lib/utils.py#L23 - data = (data + 1.) / 2. - data = 1. * data * (max_value - min_value) + min_value - return data diff --git a/basicts/launcher.py b/basicts/launcher.py index b2149818..7b61237c 100644 --- a/basicts/launcher.py +++ b/basicts/launcher.py @@ -1,32 +1,118 @@ -from packaging import version -from typing import Callable, Dict, Union, Tuple - +import os +from typing import Dict, Union, Optional +import traceback import easytorch +from easytorch.utils import get_logger, set_visible_devices +from easytorch.config import init_cfg +from easytorch.device import set_device_type + +def evaluation_func(cfg: Dict, + ckpt_path: str = None, + batch_size: Optional[int] = None, + strict: bool = True) -> None: + """ + Starts the evaluation process. + + This function performs the following steps: + 1. Initializes the runner specified in the configuration (`cfg`). + 2. Sets up logging for the evaluation process. + 3. Loads the model checkpoint. + 4. Executes the test pipeline using the initialized runner. + Args: + cfg (Dict): EasyTorch configuration dictionary. + ckpt_path (str): Path to the model checkpoint. If not provided, the best model checkpoint is loaded automatically. + batch_size (Optional[int]): Batch size for evaluation. If not specified, + it should be defined in the config. Defaults to None. + strict (bool): Enforces that the checkpoint keys match the model. Defaults to True. + + Raises: + Exception: Catches any exception, logs the traceback, and re-raises it. + """ -def launch_runner(cfg: Union[Dict, str], fn: Callable, args: Tuple = (), device_type: str = "gpu", devices: str = None): - easytorch_version = easytorch.__version__ - if version.parse(easytorch_version) >= version.parse("1.3"): - easytorch.launch_runner(cfg=cfg, fn=fn, args=args, device_type=device_type, devices=devices) - else: - easytorch.launch_runner(cfg=cfg, fn=fn, args=args, gpus=devices) + # initialize the runner + logger = get_logger('easytorch-launcher') + logger.info(f"Initializing runner '{cfg['RUNNER']}'") + runner = cfg['RUNNER'](cfg) -def launch_training(cfg: Union[Dict, str], gpus: str = None, node_rank: int = 0): - """Extended easytorch launch_training. + # initialize the logger for the runner + runner.init_logger(logger_name='easytorch-evaluation', log_file_name='evaluation_log') + + try: + # set batch size if provided + if batch_size is not None: + cfg.TEST.DATA.BATCH_SIZE = batch_size + else: + assert 'BATCH_SIZE' in cfg.TEST.DATA, 'Batch size must be specified either in the config or as an argument.' + + # load the model checkpoint + if ckpt_path is None or not os.path.exists(ckpt_path): + ckpt_path_auto = os.path.join(runner.ckpt_save_dir, '{}_best_val_{}.pt'.format(runner.model_name, runner.target_metrics.replace('/', '_'))) + logger.info(f'Checkpoint file not found at {ckpt_path}. Loading the best model checkpoint `{ckpt_path_auto}` automatically.') + if not os.path.exists(ckpt_path_auto): + raise FileNotFoundError(f'Checkpoint file not found at {ckpt_path}') + runner.load_model(ckpt_path=ckpt_path_auto, strict=strict) + else: + logger.info(f'Loading model checkpoint from {ckpt_path}') + runner.load_model(ckpt_path=ckpt_path, strict=strict) + + # start the evaluation pipeline + runner.test_pipeline(cfg=cfg, save_metrics=True, save_results=True) + + except BaseException as e: + # log the exception and re-raise it + runner.logger.error(traceback.format_exc()) + raise e + +def launch_evaluation(cfg: Union[Dict, str], + ckpt_path: str, + device_type: str = 'gpu', + gpus: Optional[str] = None, + batch_size: Optional[int] = None) -> None: + """ + Launches the evaluation process using EasyTorch. Args: - cfg (Union[Dict, str]): Easytorch config. - gpus (str): set ``CUDA_VISIBLE_DEVICES`` environment variable. - node_rank (int): Rank of the current node. + cfg (Union[Dict, str]): EasyTorch configuration as a dictionary or a path to a config file. + ckpt_path (str): Path to the model checkpoint. + device_type (str, optional): Device type to use ('cpu' or 'gpu'). Defaults to 'gpu'. + gpus (Optional[str]): GPU device IDs to use. Defaults to None (use all available GPUs). + batch_size (Optional[int]): Batch size for evaluation. Defaults to None (use value from config). + + Raises: + AssertionError: If the batch size is not specified in either the config or as an argument. + """ + + logger = get_logger('easytorch-launcher') + logger.info('Launching EasyTorch evaluation.') + + # initialize the configuration + cfg = init_cfg(cfg, save=True) + + # set the device type (CPU, GPU, or MLU) + set_device_type(device_type) + + # set the visible GPUs if the device type is not CPU + if device_type != 'cpu': + set_visible_devices(gpus) + + # run the evaluation process + evaluation_func(cfg, ckpt_path, batch_size) + +def launch_training(cfg: Union[Dict, str], + gpus: Optional[str] = None, + node_rank: int = 0) -> None: + """ + Launches the training process using EasyTorch. + + Args: + cfg (Union[Dict, str]): EasyTorch configuration as a dictionary or a path to a config file. + gpus (Optional[str]): GPU device IDs to use. Defaults to None (use all available GPUs). + node_rank (int, optional): Rank of the current node in distributed training. Defaults to 0. """ - # pre-processing of some possible future features, such as: - # registering model, runners. - # config checking + # placeholder for potential pre-processing steps (e.g., model registration, config validation) pass - # launch training based on easytorch - easytorch_version = easytorch.__version__ - if version.parse(easytorch_version) >= version.parse("1.3"): - easytorch.launch_training(cfg=cfg, devices=gpus, node_rank=node_rank) - else: - easytorch.launch_training(cfg=cfg, gpus=gpus, node_rank=node_rank) + + # launch the training process + easytorch.launch_training(cfg=cfg, devices=gpus, node_rank=node_rank) diff --git a/basicts/losses/__init__.py b/basicts/losses/__init__.py deleted file mode 100644 index eb9fdea0..00000000 --- a/basicts/losses/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .losses import l1_loss, l2_loss, masked_mae, masked_mape, masked_rmse, masked_mse - -__all__ = ["l1_loss", "l2_loss", "masked_mae", "masked_mape", "masked_rmse", "masked_mse"] diff --git a/basicts/losses/losses.py b/basicts/losses/losses.py deleted file mode 100644 index 6757bc24..00000000 --- a/basicts/losses/losses.py +++ /dev/null @@ -1,118 +0,0 @@ -from typing import Optional - -import numpy as np -import torch -import torch.nn.functional as F - - -def l1_loss(prediction: torch.Tensor, target: torch._tensor, size_average: Optional[bool] = None, reduce: Optional[bool] = None, reduction: str = "mean") -> torch.Tensor: - """unmasked mae.""" - - return F.l1_loss(prediction, target, size_average=size_average, reduce=reduce, reduction=reduction) - - -def l2_loss(prediction: torch.Tensor, target: torch.Tensor, size_average: Optional[bool] = None, reduce: Optional[bool] = None, reduction: str = "mean") -> torch.Tensor: - """unmasked mse""" - - return F.mse_loss(prediction, target, size_average=size_average, reduce=reduce, reduction=reduction) - - -def masked_mae(prediction: torch.Tensor, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: - """Masked mean absolute error. - - Args: - prediction (torch.Tensor): predicted values - target (torch.Tensor): labels - null_val (float, optional): null value. Defaults to np.nan. - - Returns: - torch.Tensor: masked mean absolute error - """ - - if np.isnan(null_val): - mask = ~torch.isnan(target) - else: - eps = 5e-5 - mask = ~torch.isclose(target, torch.tensor(null_val).expand_as(target).to(target.device), atol=eps, rtol=0.) - mask = mask.float() - mask /= torch.mean((mask)) - mask = torch.nan_to_num(mask) - loss = torch.abs(prediction-target) - loss = loss * mask - loss = torch.nan_to_num(loss) - return torch.mean(loss) - - -def masked_mse(prediction: torch.Tensor, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: - """Masked mean squared error. - - Args: - prediction (torch.Tensor): predicted values - target (torch.Tensor): labels - null_val (float, optional): null value. Defaults to np.nan. - - Returns: - torch.Tensor: masked mean squared error - """ - - if np.isnan(null_val): - mask = ~torch.isnan(target) - else: - eps = 5e-5 - mask = ~torch.isclose(target, torch.tensor(null_val).expand_as(target).to(target.device), atol=eps, rtol=0.) - mask = mask.float() - mask /= torch.mean((mask)) - mask = torch.nan_to_num(mask) - loss = (prediction-target)**2 - loss = loss * mask - loss = torch.nan_to_num(loss) - return torch.mean(loss) - - -def masked_rmse(prediction: torch.Tensor, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: - """root mean squared error. - - Args: - prediction (torch.Tensor): predicted values - target (torch.Tensor): labels - null_val (float, optional): null value . Defaults to np.nan. - - Returns: - torch.Tensor: root mean squared error - """ - - return torch.sqrt(masked_mse(prediction=prediction, target=target, null_val=null_val)) - - -def masked_mape(prediction: torch.Tensor, target: torch.Tensor, null_val: float = 0.0) -> torch.Tensor: - """Masked mean absolute percentage error. - - Args: - prediction (torch.Tensor): predicted values - target (torch.Tensor): labels - null_val (float, optional): null value. - In the mape metric, null_val is set to 0.0 and by all default. - We keep this parameter for consistency, but we do not allow it to be changed. - - Returns: - torch.Tensor: masked mean absolute percentage error - """ - assert null_val == 0.0, "In the mape metric, null_val is set to 0.0 and by all default. \ - This parameter is kept for consistency, but it is not allowed to be changed." - - # delete small values to avoid abnormal results - target = torch.where(torch.abs(target) < 1e-4, torch.zeros_like(target), target) - - # nan mask - nan_mask = ~torch.isnan(target) - # zero mask - eps = 5e-5 - zero_mask = ~torch.isclose(target, torch.tensor(null_val).expand_as(target).to(target.device), atol=eps, rtol=0.) - - mask = (nan_mask & zero_mask).float() - mask /= torch.mean((mask)) - mask = torch.nan_to_num(mask) - loss = torch.abs(torch.abs(prediction-target)/target) - loss = loss * mask - loss = torch.nan_to_num(loss) - return torch.mean(loss) diff --git a/basicts/metrics/__init__.py b/basicts/metrics/__init__.py index c3839017..eaa7da28 100644 --- a/basicts/metrics/__init__.py +++ b/basicts/metrics/__init__.py @@ -1,2 +1,22 @@ -from ..losses import * +from .mae import masked_mae +from .mse import masked_mse +from .rmse import masked_rmse +from .mape import masked_mape from .wape import masked_wape + +ALL_METRICS = { + 'MAE': masked_mae, + 'MSE': masked_mse, + 'RMSE': masked_rmse, + 'MAPE': masked_mape, + 'WAPE': masked_wape + } + +__all__ = [ + 'masked_mae', + 'masked_mse', + 'masked_rmse', + 'masked_mape', + 'masked_wape', + 'ALL_METRICS' +] diff --git a/basicts/metrics/mae.py b/basicts/metrics/mae.py new file mode 100644 index 00000000..d9163b86 --- /dev/null +++ b/basicts/metrics/mae.py @@ -0,0 +1,38 @@ +import torch +import numpy as np + +def masked_mae(prediction: torch.Tensor, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: + """ + Calculate the Masked Mean Absolute Error (MAE) between the predicted and target values, + while ignoring the entries in the target tensor that match the specified null value. + + This function is particularly useful for scenarios where the dataset contains missing or irrelevant + values (denoted by `null_val`) that should not contribute to the loss calculation. It effectively + masks these values to ensure they do not skew the error metrics. + + Args: + prediction (torch.Tensor): The predicted values as a tensor. + target (torch.Tensor): The ground truth values as a tensor with the same shape as `prediction`. + null_val (float, optional): The value considered as null or missing in the `target` tensor. + Default is `np.nan`. The function will mask all `NaN` values in the target. + + Returns: + torch.Tensor: A scalar tensor representing the masked mean absolute error. + + """ + + if np.isnan(null_val): + mask = ~torch.isnan(target) + else: + eps = 5e-5 + mask = ~torch.isclose(target, torch.tensor(null_val).expand_as(target).to(target.device), atol=eps, rtol=0.0) + + mask = mask.float() + mask /= torch.mean(mask) # Normalize mask to avoid bias in the loss due to the number of valid entries + mask = torch.nan_to_num(mask) # Replace any NaNs in the mask with zero + + loss = torch.abs(prediction - target) + loss = loss * mask # Apply the mask to the loss + loss = torch.nan_to_num(loss) # Replace any NaNs in the loss with zero + + return torch.mean(loss) diff --git a/basicts/metrics/mape.py b/basicts/metrics/mape.py new file mode 100644 index 00000000..5a9ce6ce --- /dev/null +++ b/basicts/metrics/mape.py @@ -0,0 +1,53 @@ +import torch +import numpy as np + +def masked_mape(prediction: torch.Tensor, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: + """ + Calculate the Masked Mean Absolute Percentage Error (MAPE) between predicted and target values, + ignoring entries that are either zero or match the specified null value in the target tensor. + + This function is particularly useful for time series or regression tasks where the target values may + contain zeros or missing values, which could otherwise distort the error calculation. The function + applies a mask to ensure these entries do not affect the resulting MAPE. + + Args: + prediction (torch.Tensor): The predicted values as a tensor. + target (torch.Tensor): The ground truth values as a tensor with the same shape as `prediction`. + null_val (float, optional): The value considered as null or missing in the `target` tensor. + Defaults to `np.nan`. The function will mask all `NaN` values in the target. + + Returns: + torch.Tensor: A scalar tensor representing the masked mean absolute percentage error. + + Details: + - The function creates two masks: + 1. `zero_mask`: This mask excludes entries in the `target` tensor that are close to zero, + since division by zero or near-zero values would result in extremely large or undefined errors. + 2. `null_mask`: This mask excludes entries in the `target` tensor that match the specified `null_val`. + If `null_val` is `np.nan`, the mask will exclude `NaN` values using `torch.isnan`. + + - The final mask is the intersection of `zero_mask` and `null_mask`, ensuring that only valid, non-zero, + and non-null values contribute to the MAPE calculation. + """ + + # mask to exclude zero values in the target + zero_mask = ~torch.isclose(target, torch.tensor(0.0).to(target.device), atol=5e-5) + + # mask to exclude null values in the target + if np.isnan(null_val): + null_mask = ~torch.isnan(target) + else: + eps = 5e-5 + null_mask = ~torch.isclose(target, torch.tensor(null_val).to(target.device), atol=eps) + + # combine zero and null masks + mask = (zero_mask & null_mask).float() + + mask /= torch.mean(mask) + mask = torch.nan_to_num(mask) + + loss = torch.abs((prediction - target) / target) + loss *= mask + loss = torch.nan_to_num(loss) + + return torch.mean(loss) diff --git a/basicts/metrics/mse.py b/basicts/metrics/mse.py new file mode 100644 index 00000000..dde0fdad --- /dev/null +++ b/basicts/metrics/mse.py @@ -0,0 +1,38 @@ +import torch +import numpy as np + +def masked_mse(prediction: torch.Tensor, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: + """ + Calculate the Masked Mean Squared Error (MSE) between predicted and target values, + while ignoring the entries in the target tensor that match the specified null value. + + This function is useful for scenarios where the dataset contains missing or irrelevant values + (denoted by `null_val`) that should not contribute to the loss calculation. The function applies + a mask to these values, ensuring they do not affect the error metric. + + Args: + prediction (torch.Tensor): The predicted values as a tensor. + target (torch.Tensor): The ground truth values as a tensor with the same shape as `prediction`. + null_val (float, optional): The value considered as null or missing in the `target` tensor. + Defaults to `np.nan`. The function will mask all `NaN` values in the target. + + Returns: + torch.Tensor: A scalar tensor representing the masked mean squared error. + + """ + + if np.isnan(null_val): + mask = ~torch.isnan(target) + else: + eps = 5e-5 + mask = ~torch.isclose(target, torch.tensor(null_val).to(target.device), atol=eps) + + mask = mask.float() + mask /= torch.mean(mask) # Normalize mask to maintain unbiased MSE calculation + mask = torch.nan_to_num(mask) # Replace any NaNs in the mask with zero + + loss = (prediction - target) ** 2 # Compute squared error + loss *= mask # Apply mask to the loss + loss = torch.nan_to_num(loss) # Replace any NaNs in the loss with zero + + return torch.mean(loss) # Return the mean of the masked loss diff --git a/basicts/metrics/rmse.py b/basicts/metrics/rmse.py new file mode 100644 index 00000000..06c62fb3 --- /dev/null +++ b/basicts/metrics/rmse.py @@ -0,0 +1,25 @@ +import torch +import numpy as np + +from .mse import masked_mse + +def masked_rmse(prediction: torch.Tensor, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: + """ + Calculate the Masked Root Mean Squared Error (RMSE) between predicted and target values, + ignoring entries in the target tensor that match the specified null value. + + This function is useful for evaluating model performance on datasets where some target values + may be missing or irrelevant (denoted by `null_val`). The RMSE provides a measure of the average + magnitude of errors, accounting only for the valid, non-null entries. + + Args: + prediction (torch.Tensor): The predicted values as a tensor. + target (torch.Tensor): The ground truth values as a tensor with the same shape as `prediction`. + null_val (float, optional): The value considered as null or missing in the `target` tensor. + Defaults to `np.nan`. The function will ignore all `NaN` values in the target. + + Returns: + torch.Tensor: A scalar tensor representing the masked root mean squared error. + """ + + return torch.sqrt(masked_mse(prediction=prediction, target=target, null_val=null_val)) diff --git a/basicts/metrics/wape.py b/basicts/metrics/wape.py index a423b1bc..ddaf4b8e 100644 --- a/basicts/metrics/wape.py +++ b/basicts/metrics/wape.py @@ -1,29 +1,36 @@ import torch import numpy as np - def masked_wape(prediction: torch.Tensor, target: torch.Tensor, null_val: float = np.nan) -> torch.Tensor: - """Masked weighted absolute percentage error (WAPE) + """ + Calculate the Masked Weighted Absolute Percentage Error (WAPE) between predicted and target values, + ignoring entries in the target tensor that match the specified null value. + + WAPE is a useful metric for measuring the average error relative to the magnitude of the target values, + making it particularly suitable for comparing errors across datasets or time series with different scales. Args: - prediction (torch.Tensor): predicted values - target (torch.Tensor): labels - null_val (float, optional): null value. Defaults to np.nan. + prediction (torch.Tensor): The predicted values as a tensor. + target (torch.Tensor): The ground truth values as a tensor with the same shape as `prediction`. + null_val (float, optional): The value considered as null or missing in the `target` tensor. + Defaults to `np.nan`. The function will mask all `NaN` values in the target. Returns: - torch.Tensor: masked mean absolute error + torch.Tensor: A scalar tensor representing the masked weighted absolute percentage error. """ if np.isnan(null_val): mask = ~torch.isnan(target) else: eps = 5e-5 - mask = ~torch.isclose(target, torch.tensor(null_val).expand_as(target).to(target.device), atol=eps, rtol=0.) + mask = ~torch.isclose(target, torch.tensor(null_val).to(target.device), atol=eps) + mask = mask.float() prediction, target = prediction * mask, target * mask - + prediction = torch.nan_to_num(prediction) target = torch.nan_to_num(target) - loss = torch.sum(torch.abs(prediction-target)) / (torch.sum(torch.abs(target)) + 5e-5) - return torch.mean(loss) + loss = torch.sum(torch.abs(prediction - target)) / (torch.sum(torch.abs(target)) + 5e-5) + + return loss diff --git a/basicts/runners/__init__.py b/basicts/runners/__init__.py index 66d0c0ef..3badb7b1 100644 --- a/basicts/runners/__init__.py +++ b/basicts/runners/__init__.py @@ -2,8 +2,6 @@ from .base_tsf_runner import BaseTimeSeriesForecastingRunner from .runner_zoo.simple_tsf_runner import SimpleTimeSeriesForecastingRunner from .runner_zoo.no_bp_runner import NoBPRunner -from .runner_zoo.m4_tsf_runner import M4ForecastingRunner -__all__ = ["BaseRunner", "BaseTimeSeriesForecastingRunner", - "SimpleTimeSeriesForecastingRunner", "NoBPRunner", - "M4ForecastingRunner"] +__all__ = ['BaseRunner', 'BaseTimeSeriesForecastingRunner', + 'SimpleTimeSeriesForecastingRunner', 'NoBPRunner'] diff --git a/basicts/runners/base_m4_runner.py b/basicts/runners/base_m4_runner.py deleted file mode 100644 index 1723247e..00000000 --- a/basicts/runners/base_m4_runner.py +++ /dev/null @@ -1,335 +0,0 @@ -import math -import inspect -import functools -from typing import Tuple, Union, Dict - -import torch -import numpy as np -from easydict import EasyDict -from easytorch.utils.dist import master_only - -from .base_runner import BaseRunner -from ..data import SCALER_REGISTRY - - -class BaseM4Runner(BaseRunner): - """ - Runner for M4 dataset. - - There is no validation set. - - On training end, we inference on the test set and save the prediction results. - - No metrics (but the loss). Since the evaluation is not done in this runner, thus no metrics are needed. - """ - - def __init__(self, cfg: dict): - super().__init__(cfg) - self.dataset_name = cfg["DATASET_NAME"] - assert "M4" in self.dataset_name, "M4Runner only supports M4 dataset." - # different datasets have different null_values, e.g., 0.0 or np.nan. - self.null_val = cfg.get("NULL_VAL", np.nan) # consist with metric functions - self.dataset_type = cfg.get("DATASET_TYPE", " ") - self.if_rescale = None # no normalization in M4 dataset, so no need to rescale - - # setup graph - self.need_setup_graph = cfg["MODEL"].get("SETUP_GRAPH", False) - - # define loss - self.loss = cfg["TRAIN"]["LOSS"] - # define metric - self.metrics = cfg.get("METRICS", {"loss": self.loss}) - # curriculum learning for output. Note that this is different from the CL in Seq2Seq archs. - self.cl_param = cfg["TRAIN"].get("CL", None) - if self.cl_param is not None: - self.warm_up_epochs = cfg["TRAIN"].CL.get("WARM_EPOCHS", 0) - self.cl_epochs = cfg["TRAIN"].CL.get("CL_EPOCHS") - self.prediction_length = cfg["TRAIN"].CL.get("PREDICTION_LENGTH") - self.cl_step_size = cfg["TRAIN"].CL.get("STEP_SIZE", 1) - # evaluation - self.if_evaluate_on_gpu = cfg.get("EVAL", EasyDict()).get("USE_GPU", True) # evaluate on gpu or cpu (gpu is faster but may cause OOM) - self.evaluation_horizons = [_ - 1 for _ in cfg.get("EVAL", EasyDict()).get("HORIZONS", range(1, 13))] - assert len(self.evaluation_horizons) == 0 or min(self.evaluation_horizons) >= 0, "The horizon should start counting from 1." - self.save_path = cfg.get("EVAL", EasyDict()).get("SAVE_PATH") # save path for inference results, should not be None - - def build_train_dataset(self, cfg: dict): - """Build train dataset - - Args: - cfg (dict): config - - Returns: - train dataset (Dataset) - """ - data_file_path = "{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TRAIN"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", None)) - index_file_path = "{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TRAIN"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", None)) - mask_file_path = "{0}/mask_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TRAIN"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", None)) - - # build dataset args - dataset_args = cfg.get("DATASET_ARGS", {}) - # three necessary arguments, data file path, corresponding index file path, and mode (train, valid, or test) - dataset_args["data_file_path"] = data_file_path - dataset_args["index_file_path"] = index_file_path - dataset_args["mask_file_path"] = mask_file_path - dataset_args["mode"] = "train" - - dataset = cfg["DATASET_CLS"](**dataset_args) - print("train len: {0}".format(len(dataset))) - - batch_size = cfg["TRAIN"]["DATA"]["BATCH_SIZE"] - self.iter_per_epoch = math.ceil(len(dataset) / batch_size) - - return dataset - - @staticmethod - def build_test_dataset(cfg: dict): - """Build val dataset - - Args: - cfg (dict): config - - Returns: - train dataset (Dataset) - """ - data_file_path = "{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TEST"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", None)) - index_file_path = "{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TEST"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", None)) - mask_file_path = "{0}/mask_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TEST"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", None)) - - # build dataset args - dataset_args = cfg.get("DATASET_ARGS", {}) - # three necessary arguments, data file path, corresponding index file path, and mode (train, valid, or test) - dataset_args["data_file_path"] = data_file_path - dataset_args["index_file_path"] = index_file_path - dataset_args["mask_file_path"] = mask_file_path - dataset_args["mode"] = "test" - - dataset = cfg["DATASET_CLS"](**dataset_args) - - return dataset - - def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: bool = True, **kwargs) -> tuple: - """Feed forward process for train, val, and test. Note that the outputs are NOT re-scaled. - - Args: - data (tuple): data (future data, history ata). - epoch (int, optional): epoch number. Defaults to None. - iter_num (int, optional): iteration number. Defaults to None. - train (bool, optional): if in the training process. Defaults to True. - - Returns: - tuple: (prediction, real_value) - """ - raise NotImplementedError() - - def setup_graph(self, cfg: dict, train: bool): - """Setup all parameters and the computation graph. - Implementation of many works (e.g., DCRNN, GTS) acts like TensorFlow, which creates parameters in the first feedforward process. - - Args: - cfg (dict): config - train (bool): training or inferencing - """ - - dataloader = self.build_test_data_loader(cfg=cfg) if not train else self.build_train_data_loader(cfg=cfg) - data = next(enumerate(dataloader))[1] # get the first batch - self.forward(data=data, epoch=1, iter_num=0, train=train) - - def count_parameters(self): - """Count the number of parameters in the model.""" - - num_parameters = sum(p.numel() for p in self.model.parameters() if p.requires_grad) - self.logger.info("Number of parameters: {0}".format(num_parameters)) - - def init_training(self, cfg: dict): - """Initialize training. - - Including loss, training meters, etc. - - Args: - cfg (dict): config - """ - - # setup graph - if self.need_setup_graph: - self.setup_graph(cfg=cfg, train=True) - self.need_setup_graph = False - # init training - super().init_training(cfg) - # count parameters - self.count_parameters() - for key, _ in self.metrics.items(): - self.register_epoch_meter("train_"+key, "train", "{:.6f}") - - def init_test(self, cfg: dict): - """Initialize test. - - Including test meters, etc. - - Args: - cfg (dict): config - """ - - if self.need_setup_graph: - self.setup_graph(cfg=cfg, train=False) - self.need_setup_graph = False - super().init_test(cfg) - for key, _ in self.metrics.items(): - self.register_epoch_meter("test_"+key, "test", "{:.6f}") - - def curriculum_learning(self, epoch: int = None) -> int: - """Calculate task level in curriculum learning. - - Args: - epoch (int, optional): current epoch if in training process, else None. Defaults to None. - - Returns: - int: task level - """ - - if epoch is None: - return self.prediction_length - epoch -= 1 - # generate curriculum length - if epoch < self.warm_up_epochs: - # still warm up - cl_length = self.prediction_length - else: - _ = ((epoch - self.warm_up_epochs) // self.cl_epochs + 1) * self.cl_step_size - cl_length = min(_, self.prediction_length) - return cl_length - - def metric_forward(self, metric_func, args) -> torch.Tensor: - """Computing metrics. - - Args: - metric_func (function, functools.partial): metric function. - args (Dict): arguments for metrics computation. - - Returns: - torch.Tensor: metric value. - """ - covariate_names = inspect.signature(metric_func).parameters.keys() - args = {k: v for k, v in args.items() if k in covariate_names} - - if isinstance(metric_func, functools.partial): - # support partial function - # users can define their partial function in the config file - # e.g., functools.partial(masked_mase, freq="4", null_val=np.nan) - if "null_val" in covariate_names and "null_val" not in metric_func.keywords: # if null_val is required but not provided - args["null_val"] = self.null_val - metric_item = metric_func(**args) - elif callable(metric_func): - # is a function - # filter out keys that are not in function arguments - metric_item = metric_func(**args, null_val=self.null_val) - else: - raise TypeError("Unknown metric type: {0}".format(type(metric_func))) - return metric_item - - def train_iters(self, epoch: int, iter_index: int, data: Union[torch.Tensor, Tuple]) -> torch.Tensor: - """Training details. - - Args: - data (Union[torch.Tensor, Tuple]): Data provided by DataLoader - epoch (int): current epoch. - iter_index (int): current iter. - - Returns: - loss (torch.Tensor) - """ - - iter_num = (epoch-1) * self.iter_per_epoch + iter_index - forward_return = self.forward(data=data, epoch=epoch, iter_num=iter_num, train=True) - # re-scale data - forward_return = self.rescale_data(forward_return) - # loss - if self.cl_param: - cl_length = self.curriculum_learning(epoch=epoch) - forward_return["prediction"] = forward_return["prediction"][:, :cl_length, :, :] - forward_return["target"] = forward_return["target"][:, :cl_length, :, :] - loss = self.metric_forward(self.loss, forward_return) - # metrics - for metric_name, metric_func in self.metrics.items(): - metric_item = self.metric_forward(metric_func, forward_return) - self.update_epoch_meter("train_"+metric_name, metric_item.item()) - return loss - - def save_prediction(self, returns_all): - """Evaluate the model on test data. - - Args: - returns_all (Dict): must contain keys: inputs, prediction, target - """ - prediction = returns_all["prediction"].detach().cpu().numpy() - loss = self.metric_forward(self.loss, returns_all) - self.update_epoch_meter("test_loss", loss.item()) - # save prediction as self.save_path/self.dataset_name.npy - np.save("{0}/{1}.npy".format(self.save_path, self.dataset_name), prediction) - - @torch.no_grad() - @master_only - def test(self): - """Evaluate the model. - - Args: - train_epoch (int, optional): current epoch if in training process. - """ - - # TODO: fix OOM: especially when inputs, targets, and predictions are saved at the same time. - # test loop - prediction =[] - target = [] - inputs = [] - for _, data in enumerate(self.test_data_loader): - forward_return = self.forward(data, epoch=None, iter_num=None, train=False) - if not self.if_evaluate_on_gpu: - forward_return["prediction"] = forward_return["prediction"].detach().cpu() - forward_return["target"] = forward_return["target"].detach().cpu() - forward_return["inputs"] = forward_return["inputs"].detach().cpu() - prediction.append(forward_return["prediction"]) - target.append(forward_return["target"]) - inputs.append(forward_return["inputs"]) - prediction = torch.cat(prediction, dim=0) - target = torch.cat(target, dim=0) - inputs = torch.cat(inputs, dim=0) - # re-scale data - returns_all = self.rescale_data({"prediction": prediction, "target": target, "inputs": inputs}) - # evaluate - self.save_prediction(returns_all) - - def rescale_data(self, input_data: Dict) -> Dict: - """Rescale data. - - Args: - data (Dict): Dict of data to be re-scaled. - - Returns: - Dict: Dict re-scaled data. - """ - - if self.if_rescale: - input_data["prediction"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["prediction"], **self.scaler["args"]) - input_data["target"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["target"], **self.scaler["args"]) - input_data["inputs"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["inputs"], **self.scaler["args"]) - return input_data diff --git a/basicts/runners/base_runner.py b/basicts/runners/base_runner.py index f827f9e4..c478e4b7 100644 --- a/basicts/runners/base_runner.py +++ b/basicts/runners/base_runner.py @@ -1,10 +1,12 @@ +import os import time -from typing import Dict +from typing import Dict, Optional -import setproctitle import torch +import setproctitle from torch import nn from torch.utils.data import DataLoader + from easytorch import Runner from easytorch.utils import master_only from easytorch.core.data_loader import build_data_loader @@ -12,130 +14,148 @@ class BaseRunner(Runner): """ - An expanded easytorch runner for benchmarking time series models. - - Support test loader and test process. + An extended EasyTorch Runner for benchmarking time series models. + + This class provides support for a test data loader and a test process in addition to the standard + training and validation processes. """ - def __init__(self, cfg: dict): - """Init + def __init__(self, cfg: Dict) -> None: + """ + Initialize the BaseRunner. Args: - cfg (dict): all in one configurations + cfg (Dict): Configuration dictionary containing all relevant settings. """ super().__init__(cfg) - # validate every `val_interval` epoch - self.val_interval = cfg["VAL"].get("INTERVAL", 1) if hasattr(cfg, "VAL") else None - # test every `test_interval` epoch - self.test_interval = cfg["TEST"].get("INTERVAL", 1) if hasattr(cfg, "TEST") else None + # validate every `val_interval` epochs if configured + self.val_interval = cfg.get('VAL', {}).get('INTERVAL', 1) + # test every `test_interval` epochs if configured + self.test_interval = cfg.get('TEST', {}).get('INTERVAL', 1) - # declare data loader + # declare data loaders self.train_data_loader = None self.val_data_loader = None self.test_data_loader = None - # fit higher easy-torch version - if not hasattr(self,"to_running_device"): + # ensure compatibility with higher versions of EasyTorch + if not hasattr(self, 'to_running_device'): from easytorch.device import to_device self.to_running_device = to_device - # set proctitle - proctitle_name = "{0}({1})".format(cfg["MODEL"].get( - "NAME", " "), cfg.get("DATASET_NAME", " ")) - setproctitle.setproctitle("{0}@BasicTS".format(proctitle_name)) + # set process title + proctitle_name = f"{cfg['MODEL'].get('NAME')}({cfg.get('DATASET', {}).get('NAME', 'Unknown Dataset')})" + setproctitle.setproctitle(f'{proctitle_name}@BasicTS') @staticmethod def define_model(cfg: Dict) -> nn.Module: - return cfg["MODEL"]["ARCH"](**cfg.MODEL.PARAM) + """ + Define the model architecture based on the configuration. - def init_training(self, cfg: dict): - """Initialize training and support test dataloader. + Args: + cfg (Dict): Configuration dictionary containing model settings. + + Returns: + nn.Module: The model architecture. + """ + + return cfg['MODEL']['ARCH'](**cfg['MODEL']['PARAM']) + + def init_training(self, cfg: Dict) -> None: + """ + Initialize training, including support for the test data loader. Args: - cfg (dict): config + cfg (Dict): Configuration dictionary. """ super().init_training(cfg) - # init test - if hasattr(cfg, "TEST"): + if hasattr(cfg, 'TEST'): self.init_test(cfg) @master_only - def init_test(self, cfg: dict): - """Initialize test. + def init_test(self, cfg: Dict) -> None: + """ + Initialize the test data loader and related settings. Args: - cfg (dict): config + cfg (Dict): Configuration dictionary. """ - self.test_interval = cfg["TEST"].get("INTERVAL", 1) + self.test_interval = cfg['TEST'].get('INTERVAL', 1) self.test_data_loader = self.build_test_data_loader(cfg) - self.register_epoch_meter("test_time", "test", "{:.2f} (s)", plt=False) + self.register_epoch_meter('test_time', 'test', '{:.2f} (s)', plt=False) - def build_test_data_loader(self, cfg: dict) -> DataLoader: - """Build val dataset and dataloader. - Build dataset by calling ```self.build_train_dataset```, - build dataloader by calling ```build_data_loader```. + def build_test_data_loader(self, cfg: Dict) -> DataLoader: + """ + Build the test data loader. Args: - cfg (dict): config + cfg (Dict): Configuration dictionary. Returns: - val data loader (DataLoader) + DataLoader: The test data loader. """ dataset = self.build_test_dataset(cfg) - return build_data_loader(dataset, cfg["TEST"]["DATA"]) + return build_data_loader(dataset, cfg['TEST']['DATA']) @staticmethod - def build_test_dataset(cfg: dict): - """It can be implemented to a build dataset for test. + def build_test_dataset(cfg: Dict): + """ + Build the test dataset. Args: - cfg (dict): config + cfg (Dict): Configuration dictionary. Returns: - val dataset (Dataset) + Dataset: The test dataset. + + Raises: + NotImplementedError: Must be implemented in a subclass. """ - raise NotImplementedError() + raise NotImplementedError('build_test_dataset method must be implemented.') - # support test process - def on_epoch_end(self, epoch: int): - """Callback at the end of an epoch. + def on_epoch_end(self, epoch: int) -> None: + """ + Callback at the end of each epoch to handle validation and testing. Args: - epoch (int): current epoch. + epoch (int): The current epoch number. """ - # print train meters - self.print_epoch_meters("train") - # tensorboard plt meters - self.plt_epoch_meters("train", epoch) - # validate + # print training meters + self.print_epoch_meters('train') + # plot training meters to TensorBoard + self.plt_epoch_meters('train', epoch) + # perform validation if configured if self.val_data_loader is not None and epoch % self.val_interval == 0: self.validate(train_epoch=epoch) - # test + # perform testing if configured if self.test_data_loader is not None and epoch % self.test_interval == 0: self.test_pipeline(train_epoch=epoch) - # save model + # save the model checkpoint self.save_model(epoch) - # reset meters + # reset epoch meters self.reset_epoch_meters() @torch.no_grad() @master_only - def test_pipeline(self, cfg: dict = None, train_epoch: int = None): - """The whole test process. + def test_pipeline(self, cfg: Optional[Dict] = None, train_epoch: Optional[int] = None, save_metrics: bool = False, save_results: bool = False) -> None: + """ + The complete test process. Args: - cfg (dict, optional): config - train_epoch (int, optional): current epoch if in training process. + cfg (Dict, optional): Configuration dictionary. Defaults to None. + train_epoch (int, optional): Current epoch during training. Defaults to None. + save_metrics (bool, optional): Save the test metrics. Defaults to False. + save_results (bool, optional): Save the test results. Defaults to False. """ - # init test if not in training process - if train_epoch is None: + if train_epoch is None and cfg is not None: self.init_test(cfg) self.on_test_start() @@ -143,38 +163,47 @@ def test_pipeline(self, cfg: dict = None, train_epoch: int = None): test_start_time = time.time() self.model.eval() - # test - self.test() + # execute the test process + self.test(train_epoch=train_epoch, save_results=save_results, save_metrics=save_metrics) test_end_time = time.time() - self.update_epoch_meter("test_time", test_end_time - test_start_time) - # print test meters - self.print_epoch_meters("test") + self.update_epoch_meter('test_time', test_end_time - test_start_time) + + self.print_epoch_meters('test') if train_epoch is not None: - # tensorboard plt meters - self.plt_epoch_meters("test", train_epoch // self.test_interval) + self.plt_epoch_meters('test', train_epoch // self.test_interval) + + # logging here for intuitiveness + if save_results: + self.logger.info(f'Test results saved to {os.path.join(self.ckpt_save_dir, "test_results.npz")}.') + if save_metrics: + self.logger.info(f'Test metrics saved to {os.path.join(self.ckpt_save_dir, "test_metrics.json")}.') self.on_test_end() @master_only - def on_test_start(self): - """Callback at the start of testing. - """ + def on_test_start(self) -> None: + """Callback at the start of testing.""" pass @master_only - def on_test_end(self): - """Callback at the end of testing. - """ + def on_test_end(self) -> None: + """Callback at the end of testing.""" pass - def test(self, train_epoch: int = None): - """It can be implemented to define testing details. + def test(self, train_epoch: Optional[int] = None, save_metrics: bool = False, save_results: bool = False) -> None: + """ + Define the details of the testing process. Args: - train_epoch (int, optional): current epoch if in training process. + train_epoch (int, optional): Current epoch during training. Defaults to None. + save_metrics (bool, optional): Save the test metrics. Defaults to False. + save_results (bool, optional): Save the test results. Defaults to False. + + Raises: + NotImplementedError: Must be implemented in a subclass. """ - raise NotImplementedError() + raise NotImplementedError('test method must be implemented.') diff --git a/basicts/runners/base_tsf_runner.py b/basicts/runners/base_tsf_runner.py index d2b4ad93..93d5cceb 100644 --- a/basicts/runners/base_tsf_runner.py +++ b/basicts/runners/base_tsf_runner.py @@ -1,267 +1,339 @@ +import os +import json import math +import time import inspect import functools from typing import Tuple, Union, Optional, Dict import torch import numpy as np +from tqdm import tqdm from easydict import EasyDict -from easytorch.utils.dist import master_only +from easytorch.core.checkpoint import save_ckpt +from easytorch.utils.data_prefetcher import DevicePrefetcher +from easytorch.utils import TimePredictor, get_local_rank, is_master, master_only +from torch.nn.parallel import DistributedDataParallel as DDP from .base_runner import BaseRunner -from ..data import SCALER_REGISTRY -from ..utils import load_pkl from ..metrics import masked_mae, masked_mape, masked_rmse, masked_wape, masked_mse class BaseTimeSeriesForecastingRunner(BaseRunner): """ - Runner for multivariate time series forecasting datasets. + Runner for multivariate time series forecasting tasks. + Features: - - Evaluate at pre-defined horizons (1~12 as default) and overall. - - Metrics: MAE, RMSE, MAPE. Allow customization. The best model is the one with the smallest mae at validation. - - Support setup_graph for the models acting like tensorflow. - - Loss: MAE (masked_mae) as default. Allow customization. - - Support curriculum learning. + - Supports evaluation at pre-defined horizons (optional) and overall performance assessment. + - Metrics: MAE, RMSE, MAPE, WAPE, and MSE. Customizable. The best model is selected based on the smallest MAE on the validation set. + - Supports `setup_graph` for models that operate similarly to TensorFlow. + - Default loss function is MAE (masked_mae), but it can be customized. + - Supports curriculum learning. - Users only need to implement the `forward` function. + + Customization: + - Model: + - Args: + - history_data (torch.Tensor): Historical data with shape [B, L, N, C], + where B is the batch size, L is the sequence length, N is the number of nodes, + and C is the number of features. + - future_data (torch.Tensor or None): Future data with shape [B, L, N, C]. + Can be None if there is no future data available. + - batch_seen (int): The number of batches seen so far. + - epoch (int): The current epoch number. + - train (bool): Indicates whether the model is in training mode. + - Return: + - Dict or torch.Tensor: + - If returning a Dict, it must contain the 'prediction' key. Other keys are optional and will be passed to the loss and metric functions. + - If returning a torch.Tensor, it should represent the model's predictions, with shape [B, L, N, C]. + + - Loss & Metrics (optional): + - Args: + - prediction (torch.Tensor): Model's predictions, with shape [B, L, N, C]. + - target (torch.Tensor): Ground truth data, with shape [B, L, N, C]. + - null_val (float): The value representing missing data in the dataset. + - Other args (optional): Additional arguments will be matched with keys in the model's return dictionary, if applicable. + - Return: + - torch.Tensor: The computed loss or metric value. + + - Dataset (optional): + - Return: The returned data will be passed to the `forward` function as the `data` argument. """ def __init__(self, cfg: Dict): super().__init__(cfg) - self.dataset_name = cfg["DATASET_NAME"] - # different datasets have different null_values, e.g., 0.0 or np.nan. - self.null_val = cfg.get("NULL_VAL", np.nan) # consist with metric functions - self.dataset_type = cfg.get("DATASET_TYPE", " ") - self.if_rescale = cfg.get("RESCALE", True) # if rescale data when calculating loss or metrics, default as True - - # setup graph - self.need_setup_graph = cfg["MODEL"].get("SETUP_GRAPH", False) - - # read scaler for re-normalization - self.scaler = load_pkl("{0}/scaler_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TRAIN"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", True))) - # define loss - self.loss = cfg["TRAIN"]["LOSS"] - # define metric - self.metrics = cfg.get("METRICS", {"MAE": masked_mae, "RMSE": masked_rmse, "MAPE": masked_mape, "WAPE": masked_wape, "MSE": masked_mse}) - # TODO: use loss as the metric - self.target_metrics = cfg.get("TARGET_METRICS", "MAE") - # curriculum learning for output. Note that this is different from the CL in Seq2Seq archs. - self.cl_param = cfg["TRAIN"].get("CL", None) + + # setup graph flag + self.need_setup_graph = cfg['MODEL'].get('SETUP_GRAPH', False) + + # initialize scaler + self.scaler = self.build_scaler(cfg) + + # define loss function + self.loss = cfg['TRAIN']['LOSS'] + + # define metrics + self.metrics = cfg.get('METRICS', {}).get('FUNCS', { + 'MAE': masked_mae, + 'RMSE': masked_rmse, + 'MAPE': masked_mape, + 'WAPE': masked_wape, + 'MSE': masked_mse + }) + self.target_metrics = cfg.get('METRICS', {}).get('TARGET', 'MAE') + self.metrics_best = cfg.get('METRICS', {}).get('BEST', 'min') + assert self.target_metrics in self.metrics, f'Target metric {self.target_metrics} not found in metrics.' + assert self.metrics_best in ['min', 'max'], f'Invalid best metric {self.metrics_best}.' + # handle null values in datasets, e.g., 0.0 or np.nan. + self.null_val = cfg.get('METRICS', {}).get('NULL_VAL', np.nan) + + # support early stopping + # NOTE: If the project has been stopped early and its configuration is rerun, + # training will resume from the last saved checkpoint. + # This feature is designed primarily for the convenience of users, + # allowing them to continue training seamlessly after an interruption. + self.early_stopping_patience = cfg.get('TRAIN', {}).get('EARLY_STOPPING_PATIENCE', None) + self.current_patience = self.early_stopping_patience + assert self.early_stopping_patience is None or self.early_stopping_patience > 0, 'Early stopping patience must be a positive integer.' + + # curriculum learning setup + self.cl_param = cfg['TRAIN'].get('CL', None) if self.cl_param is not None: - self.warm_up_epochs = cfg["TRAIN"].CL.get("WARM_EPOCHS", 0) - self.cl_epochs = cfg["TRAIN"].CL.get("CL_EPOCHS") - self.prediction_length = cfg["TRAIN"].CL.get("PREDICTION_LENGTH") - self.cl_step_size = cfg["TRAIN"].CL.get("STEP_SIZE", 1) - # evaluation - self.if_evaluate_on_gpu = cfg.get("EVAL", EasyDict()).get("USE_GPU", True) # evaluate on gpu or cpu (gpu is faster but may cause OOM) - self.evaluation_horizons = [_ - 1 for _ in cfg.get("EVAL", EasyDict()).get("HORIZONS", [])] - assert len(self.evaluation_horizons) == 0 or min(self.evaluation_horizons) >= 0, "The horizon should start counting from 1." + self.warm_up_epochs = cfg['TRAIN'].CL.get('WARM_EPOCHS', 0) + self.cl_epochs = cfg['TRAIN'].CL.get('CL_EPOCHS') + self.prediction_length = cfg['TRAIN'].CL.get('PREDICTION_LENGTH') + self.cl_step_size = cfg['TRAIN'].CL.get('STEP_SIZE', 1) + + # Eealuation settings + self.if_evaluate_on_gpu = cfg.get('EVAL', EasyDict()).get('USE_GPU', True) + self.evaluation_horizons = [_ - 1 for _ in cfg.get('EVAL', EasyDict()).get('HORIZONS', [])] + assert len(self.evaluation_horizons) == 0 or min(self.evaluation_horizons) >= 0, 'The horizon should start counting from 1.' + + def build_scaler(self, cfg: Dict): + """Build scaler. + + Args: + cfg (Dict): Configuration. + + Returns: + Scaler instance or None if no scaler is declared. + """ + + if 'SCALER' in cfg: + return cfg['SCALER']['TYPE'](**cfg['SCALER']['PARAM']) + return None def setup_graph(self, cfg: Dict, train: bool): """Setup all parameters and the computation graph. - Implementation of many works (e.g., DCRNN, GTS) acts like TensorFlow, which creates parameters in the first feedforward process. + + Some models (e.g., DCRNN, GTS) require creating parameters during the first forward pass, similar to TensorFlow. Args: - cfg (Dict): config - train (bool): training or inferencing + cfg (Dict): Configuration. + train (bool): Whether the setup is for training or inference. """ dataloader = self.build_test_data_loader(cfg=cfg) if not train else self.build_train_data_loader(cfg=cfg) - data = next(enumerate(dataloader))[1] # get the first batch + data = next(iter(dataloader)) # get the first batch self.forward(data=data, epoch=1, iter_num=0, train=train) def count_parameters(self): """Count the number of parameters in the model.""" num_parameters = sum(p.numel() for p in self.model.parameters() if p.requires_grad) - self.logger.info("Number of parameters: {0}".format(num_parameters)) + self.logger.info(f'Number of parameters: {num_parameters}') def init_training(self, cfg: Dict): - """Initialize training. - - Including loss, training meters, etc. + """Initialize training components, including loss, meters, etc. Args: - cfg (Dict): config + cfg (Dict): Configuration. """ - # setup graph if self.need_setup_graph: self.setup_graph(cfg=cfg, train=True) self.need_setup_graph = False - # init training + super().init_training(cfg) - # count parameters self.count_parameters() - for key, _ in self.metrics.items(): - self.register_epoch_meter("train_"+key, "train", "{:.6f}") - def init_validation(self, cfg: Dict): - """Initialize validation. + for key in self.metrics: + self.register_epoch_meter(f'train_{key}', 'train', '{:.4f}') - Including validation meters, etc. + def init_validation(self, cfg: Dict): + """Initialize validation components, including meters. Args: - cfg (Dict): config + cfg (Dict): Configuration. """ super().init_validation(cfg) - for key, _ in self.metrics.items(): - self.register_epoch_meter("val_"+key, "val", "{:.6f}") + for key in self.metrics: + self.register_epoch_meter(f'val_{key}', 'val', '{:.4f}') def init_test(self, cfg: Dict): - """Initialize test. - - Including test meters, etc. + """Initialize test components, including meters. Args: - cfg (Dict): config + cfg (Dict): Configuration. """ if self.need_setup_graph: self.setup_graph(cfg=cfg, train=False) self.need_setup_graph = False + super().init_test(cfg) - for key, _ in self.metrics.items(): - self.register_epoch_meter("test_"+key, "test", "{:.6f}") + for key in self.metrics: + self.register_epoch_meter(f'test_{key}', 'test', '{:.4f}') def build_train_dataset(self, cfg: Dict): - """Build train dataset - - There are two types of preprocessing methods in BasicTS, - 1. Normalize across the WHOLE dataset. - 2. Normalize on EACH channel (i.e., calculate the mean and std of each channel). - - The reason why there are two different preprocessing methods is that each channel of the dataset may have a different value range. - 1. Normalizing the WHOLE data set will preserve the relative size relationship between channels. - Larger channels usually produce larger loss values, so more attention will be paid to these channels when optimizing the model. - Therefore, this approach will achieve better performance when we evaluate on the rescaled dataset. - For example, when evaluating rescaled data for two channels with values in the range [0, 1], [9000, 10000], the prediction on channel [0,1] is trivial. - 2. Normalizing each channel will eliminate the gap in value range between channels. - For example, a channel with a value in the range [0, 1] may be as important as a channel with a value in the range [9000, 10000]. - In this case we need to normalize each channel and evaluate without rescaling. - - There is no absolute good or bad distinction between the above two situations, - and the decision needs to be made based on actual requirements or academic research habits. - For example, the first approach is often adopted in the field of Spatial-Temporal Forecasting (STF). - The second approach is often adopted in the field of Long-term Time Series Forecasting (LTSF). - - To avoid confusion for users and facilitate them to obtain results comparable to existing studies, we - automatically select data based on the cfg.get("RESCALE") flag (default to True). - if_rescale == True: use the data that is normalized across the WHOLE dataset - if_rescale == False: use the data that is normalized on EACH channel + """Build the training dataset. Args: - cfg (Dict): config + cfg (Dict): Configuration. Returns: - train dataset (Dataset) + Dataset: The constructed training dataset. """ - data_file_path = "{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TRAIN"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", True)) - index_file_path = "{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TRAIN"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", True)) - - # build dataset args - dataset_args = cfg.get("DATASET_ARGS", {}) - # three necessary arguments, data file path, corresponding index file path, and mode (train, valid, or test) - dataset_args["data_file_path"] = data_file_path - dataset_args["index_file_path"] = index_file_path - dataset_args["mode"] = "train" - - dataset = cfg["DATASET_CLS"](**dataset_args) - print("train len: {0}".format(len(dataset))) - - batch_size = cfg["TRAIN"]["DATA"]["BATCH_SIZE"] - self.iter_per_epoch = math.ceil(len(dataset) / batch_size) + + if 'DATASET' not in cfg: + # TODO: support building different datasets for training, validation, and test. (not tested) + dataset = cfg['TRAIN']['DATA']['DATASET']['TYPE'](**cfg['TRAIN']['DATA']['DATASET']['PARAM']) + self.logger.info(f'Train dataset length: {len(dataset)}') + batch_size = cfg['TRAIN']['DATA']['BATCH_SIZE'] + self.iter_per_epoch = math.ceil(len(dataset) / batch_size) + else: + dataset = cfg['DATASET']['TYPE'](mode='train', **cfg['DATASET']['PARAM']) + self.logger.info(f'Train dataset length: {len(dataset)}') + batch_size = cfg['TRAIN']['DATA']['BATCH_SIZE'] + self.iter_per_epoch = math.ceil(len(dataset) / batch_size) return dataset @staticmethod def build_val_dataset(cfg: Dict): - """Build val dataset + """Build the validation dataset. Args: - cfg (Dict): config + cfg (Dict): Configuration. Returns: - validation dataset (Dataset) + Dataset: The constructed validation dataset. """ - # see build_train_dataset for details - data_file_path = "{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["VAL"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", True)) - index_file_path = "{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["VAL"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", True)) - - # build dataset args - dataset_args = cfg.get("DATASET_ARGS", {}) - # three necessary arguments, data file path, corresponding index file path, and mode (train, valid, or test) - dataset_args["data_file_path"] = data_file_path - dataset_args["index_file_path"] = index_file_path - dataset_args["mode"] = "valid" - - dataset = cfg["DATASET_CLS"](**dataset_args) - print("val len: {0}".format(len(dataset))) + + if 'DATASET' not in cfg: + # TODO: support building different datasets for training, validation, and test. (not tested) + dataset = cfg['VAL']['DATA']['DATASET']['TYPE'](**cfg['VAL']['DATA']['DATASET']['PARAM']) + print(f'VAlidation dataset length: {len(dataset)}') + else: + dataset = cfg['DATASET']['TYPE'](mode='valid', **cfg['DATASET']['PARAM']) + print(f'Validation dataset length: {len(dataset)}') return dataset @staticmethod def build_test_dataset(cfg: Dict): - """Build val dataset + """Build the test dataset. Args: - cfg (Dict): config + cfg (Dict): Configuration. Returns: - train dataset (Dataset) + Dataset: The constructed test dataset. """ - data_file_path = "{0}/data_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TEST"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", True)) - index_file_path = "{0}/index_in_{1}_out_{2}_rescale_{3}.pkl".format( - cfg["TEST"]["DATA"]["DIR"], - cfg["DATASET_INPUT_LEN"], - cfg["DATASET_OUTPUT_LEN"], - cfg.get("RESCALE", True)) - - # build dataset args - dataset_args = cfg.get("DATASET_ARGS", {}) - # three necessary arguments, data file path, corresponding index file path, and mode (train, valid, or test) - dataset_args["data_file_path"] = data_file_path - dataset_args["index_file_path"] = index_file_path - dataset_args["mode"] = "test" - - dataset = cfg["DATASET_CLS"](**dataset_args) - print("test len: {0}".format(len(dataset))) + + if 'DATASET' not in cfg: + # TODO: support building different datasets for training, validation, and test. (not tested) + dataset = cfg['TEST']['DATA']['DATASET']['TYPE'](**cfg['TEST']['DATA']['DATASET']['PARAM']) + print(f'Test dataset length: {len(dataset)}') + else: + dataset = cfg['DATASET']['TYPE'](mode='test', **cfg['DATASET']['PARAM']) + print(f'Test dataset length: {len(dataset)}') return dataset + def train(self, cfg: Dict): + """Train model. + + Train process: + [init_training] + for in train_epoch + [on_epoch_start] + for in train iters + [train_iters] + [on_epoch_end] ------> Epoch Val: val every n epoch + [on_validating_start] + for in val iters + val iter + [on_validating_end] + [on_training_end] + + Args: + cfg (Dict): config + """ + + self.init_training(cfg) + + # train time predictor + train_time_predictor = TimePredictor(self.start_epoch, self.num_epochs) + + # training loop + epoch_index = 0 + for epoch_index in range(self.start_epoch, self.num_epochs): + # early stopping + if self.early_stopping_patience is not None and self.current_patience <= 0: + self.logger.info('Early stopping.') + break + + epoch = epoch_index + 1 + self.on_epoch_start(epoch) + epoch_start_time = time.time() + # start training + self.model.train() + + # tqdm process bar + if cfg.get('TRAIN.DATA.DEVICE_PREFETCH', False): + data_loader = DevicePrefetcher(self.train_data_loader) + else: + data_loader = self.train_data_loader + data_loader = tqdm(data_loader) if get_local_rank() == 0 else data_loader + + # data loop + for iter_index, data in enumerate(data_loader): + loss = self.train_iters(epoch, iter_index, data) + if loss is not None: + self.backward(loss) + # update lr_scheduler + if self.scheduler is not None: + self.scheduler.step() + + epoch_end_time = time.time() + # epoch time + self.update_epoch_meter('train_time', epoch_end_time - epoch_start_time) + self.on_epoch_end(epoch) + + expected_end_time = train_time_predictor.get_expected_end_time(epoch) + + # estimate training finish time + if epoch < self.num_epochs: + self.logger.info('The estimated training finish time is {}'.format( + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expected_end_time)))) + + # log training finish time + self.logger.info('The training finished at {}'.format( + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + )) + + self.on_training_end(cfg=cfg, train_epoch=epoch_index + 1) + def curriculum_learning(self, epoch: int = None) -> int: - """Calculate task level in curriculum learning. + """Calculate task level for curriculum learning. Args: - epoch (int, optional): current epoch if in training process, else None. Defaults to None. + epoch (int, optional): Current epoch if in training process; None otherwise. Defaults to None. Returns: - int: task level + int: Task level for the current epoch. """ if epoch is None: @@ -269,186 +341,282 @@ def curriculum_learning(self, epoch: int = None) -> int: epoch -= 1 # generate curriculum length if epoch < self.warm_up_epochs: - # still warm up + # still in warm-up phase cl_length = self.prediction_length else: - _ = ((epoch - self.warm_up_epochs) // self.cl_epochs + 1) * self.cl_step_size - cl_length = min(_, self.prediction_length) + progress = ((epoch - self.warm_up_epochs) // self.cl_epochs + 1) * self.cl_step_size + cl_length = min(progress, self.prediction_length) return cl_length - def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: bool = True, **kwargs) -> tuple: - """Feed forward process for train, val, and test. Note that the outputs are NOT re-scaled. + def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: bool = True, **kwargs) -> Dict: + """ + Performs the forward pass for training, validation, and testing. + Note: The outputs are not re-scaled. Args: - data (tuple): data (future data, history data). [B, L, N, C] for each of them - epoch (int, optional): epoch number. Defaults to None. - iter_num (int, optional): iteration number. Defaults to None. - train (bool, optional): if in the training process. Defaults to True. + data (Dict): A dictionary containing 'target' (future data) and 'inputs' (history data) (normalized by self.scaler). + epoch (int, optional): Current epoch number. Defaults to None. + iter_num (int, optional): Current iteration number. Defaults to None. + train (bool, optional): Indicates whether the forward pass is for training. Defaults to True. Returns: - Dict: must contain keys: inputs, prediction, target + Dict: A dictionary containing the keys: + - 'inputs': Selected input features. + - 'prediction': Model predictions. + - 'target': Selected target features. + + Raises: + AssertionError: If the shape of the model output does not match [B, L, N]. """ raise NotImplementedError() - def metric_forward(self, metric_func, args) -> torch.Tensor: - """Computing metrics. + def metric_forward(self, metric_func, args: Dict) -> torch.Tensor: + """Compute metrics using the given metric function. Args: - metric_func (function, functools.partial): metric function. - args (Dict): arguments for metrics computation. + metric_func (function or functools.partial): Metric function. + args (Dict): Arguments for metrics computation. Returns: - torch.Tensor: metric value. + torch.Tensor: Computed metric value. """ + covariate_names = inspect.signature(metric_func).parameters.keys() args = {k: v for k, v in args.items() if k in covariate_names} if isinstance(metric_func, functools.partial): - # support partial function - # users can define their partial function in the config file - # e.g., functools.partial(masked_mase, freq="4", null_val=np.nan) - if "null_val" in metric_func.keywords: # null_val is provided - # assert self.null_val is None, "Null_val is provided in metric function. The CFG.NULL_VAL should not be set." - pass # error when using multiple metrics, some of which require null_val and some do not - elif "null_val" in covariate_names: # null_val is required but not provided - args["null_val"] = self.null_val + if 'null_val' not in metric_func.keywords and 'null_val' in covariate_names: # null_val is required but not provided + args['null_val'] = self.null_val metric_item = metric_func(**args) elif callable(metric_func): - # is a function - # filter out keys that are not in function arguments - if "null_val" in covariate_names: # null_val is required - args["null_val"] = self.null_val + if 'null_val' in covariate_names: # null_val is required + args['null_val'] = self.null_val metric_item = metric_func(**args) else: - raise TypeError("Unknown metric type: {0}".format(type(metric_func))) + raise TypeError(f'Unknown metric type: {type(metric_func)}') return metric_item - def rescale_data(self, input_data: Dict) -> Dict: - """Rescale data. + def preprocessing(self, input_data: Dict) -> Dict: + """Preprocess data. + + Args: + input_data (Dict): Dictionary containing data to be processed. + + Returns: + Dict: Processed data. + """ + + if self.scaler is not None: + input_data['target'] = self.scaler.transform(input_data['target']) + input_data['inputs'] = self.scaler.transform(input_data['inputs']) + # TODO: add more preprocessing steps as needed. + return input_data + + def postprocessing(self, input_data: Dict) -> Dict: + """Postprocess data. Args: - data (Dict): Dict of data to be re-scaled. + input_data (Dict): Dictionary containing data to be processed. Returns: - Dict: Dict re-scaled data. + Dict: Processed data. """ - if self.if_rescale: - input_data["prediction"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["prediction"], **self.scaler["args"]) - input_data["target"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["target"], **self.scaler["args"]) - input_data["inputs"] = SCALER_REGISTRY.get(self.scaler["func"])(input_data["inputs"], **self.scaler["args"]) + if self.scaler is not None and self.scaler.rescale: + input_data['prediction'] = self.scaler.inverse_transform(input_data['prediction']) + input_data['target'] = self.scaler.inverse_transform(input_data['target']) + input_data['inputs'] = self.scaler.inverse_transform(input_data['inputs']) + # TODO: add more postprocessing steps as needed. return input_data def train_iters(self, epoch: int, iter_index: int, data: Union[torch.Tensor, Tuple]) -> torch.Tensor: - """Training details. + """Training iteration process. Args: - data (Union[torch.Tensor, Tuple]): Data provided by DataLoader - epoch (int): current epoch. - iter_index (int): current iter. + epoch (int): Current epoch. + iter_index (int): Current iteration index. + data (Union[torch.Tensor, Tuple]): Data provided by DataLoader. Returns: - loss (torch.Tensor) + torch.Tensor: Loss value. """ - iter_num = (epoch-1) * self.iter_per_epoch + iter_index + iter_num = (epoch - 1) * self.iter_per_epoch + iter_index + data = self.preprocessing(data) forward_return = self.forward(data=data, epoch=epoch, iter_num=iter_num, train=True) - # re-scale data - forward_return = self.rescale_data(forward_return) - # loss + forward_return = self.postprocessing(forward_return) + if self.cl_param: cl_length = self.curriculum_learning(epoch=epoch) - forward_return["prediction"] = forward_return["prediction"][:, :cl_length, :, :] - forward_return["target"] = forward_return["target"][:, :cl_length, :, :] + forward_return['prediction'] = forward_return['prediction'][:, :cl_length, :, :] + forward_return['target'] = forward_return['target'][:, :cl_length, :, :] loss = self.metric_forward(self.loss, forward_return) - # metrics + for metric_name, metric_func in self.metrics.items(): metric_item = self.metric_forward(metric_func, forward_return) - self.update_epoch_meter("train_"+metric_name, metric_item.item()) + self.update_epoch_meter(f'train_{metric_name}', metric_item.item()) return loss def val_iters(self, iter_index: int, data: Union[torch.Tensor, Tuple]): - """Validation details. + """Validation iteration process. Args: - iter_index (int): current iter. - data (Union[torch.Tensor, Tuple]): Data provided by DataLoader + iter_index (int): Current iteration index. + data (Union[torch.Tensor, Tuple]): Data provided by DataLoader. """ + data = self.preprocessing(data) forward_return = self.forward(data=data, epoch=None, iter_num=iter_index, train=False) - # re-scale data - forward_return = self.rescale_data(forward_return) - # metrics + forward_return = self.postprocessing(forward_return) + for metric_name, metric_func in self.metrics.items(): metric_item = self.metric_forward(metric_func, forward_return) - self.update_epoch_meter("val_"+metric_name, metric_item.item()) + self.update_epoch_meter(f'val_{metric_name}', metric_item.item()) - def evaluate(self, returns_all): - """Evaluate the model on test data. + def compute_evaluation_metrics(self, returns_all: Dict): + """Compute metrics for evaluating model performance during the test process. Args: - returns_all (Dict): must contain keys: inputs, prediction, target + returns_all (Dict): Must contain keys: inputs, prediction, target. """ - # test performance of different horizon + metrics_results = {} for i in self.evaluation_horizons: - # For horizon i, only calculate the metrics **at that time** slice here. - pred = returns_all["prediction"][:, i, :, :] - real = returns_all["target"][:, i, :, :] - # metrics - metric_repr = "" + pred = returns_all['prediction'][:, i, :, :] + real = returns_all['target'][:, i, :, :] + + metrics_results[f'horizon_{i + 1}'] = {} + metric_repr = '' for metric_name, metric_func in self.metrics.items(): - if metric_name.lower() == "mase": continue # MASE needs to be calculated after all horizons - metric_item = self.metric_forward(metric_func, {"prediction": pred, "target": real}) - metric_repr += ", Test {0}: {1:.6f}".format(metric_name, metric_item.item()) - log = "Evaluate best model on test data for horizon {:d}" + metric_repr - log = log.format(i+1) - self.logger.info(log) - # test performance overall + if metric_name.lower() == 'mase': + continue # MASE needs to be calculated after all horizons + metric_item = self.metric_forward(metric_func, {'prediction': pred, 'target': real}) + metric_repr += f', Test {metric_name}: {metric_item.item():.4f}' + metrics_results[f'horizon_{i + 1}'][metric_name] = metric_item.item() + self.logger.info(f'Evaluate best model on test data for horizon {i + 1}{metric_repr}') + + metrics_results['overall'] = {} for metric_name, metric_func in self.metrics.items(): metric_item = self.metric_forward(metric_func, returns_all) - self.update_epoch_meter("test_"+metric_name, metric_item.item()) + self.update_epoch_meter(f'test_{metric_name}', metric_item.item()) + metrics_results['overall'][metric_name] = metric_item.item() + + return metrics_results @torch.no_grad() @master_only - def test(self): - """Evaluate the model. - + def test(self, train_epoch: Optional[int] = None, save_metrics: bool = False, save_results: bool = False) -> Dict: + """Test process. + Args: - train_epoch (int, optional): current epoch if in training process. + train_epoch (Optional[int]): Current epoch if in training process. + save_metrics (bool): Save the test metrics. Defaults to False. + save_results (bool): Save the test results. Defaults to False. """ - # TODO: fix OOM: especially when inputs, targets, and predictions are saved at the same time. - # test loop - prediction =[] - target = [] - inputs = [] - for _, data in enumerate(self.test_data_loader): + prediction, target, inputs = [], [], [] + + for data in tqdm(self.test_data_loader): + data = self.preprocessing(data) forward_return = self.forward(data, epoch=None, iter_num=None, train=False) + forward_return = self.postprocessing(forward_return) + if not self.if_evaluate_on_gpu: - forward_return["prediction"] = forward_return["prediction"].detach().cpu() - forward_return["target"] = forward_return["target"].detach().cpu() - forward_return["inputs"] = forward_return["inputs"].detach().cpu() - prediction.append(forward_return["prediction"]) - target.append(forward_return["target"]) - inputs.append(forward_return["inputs"]) + forward_return['prediction'] = forward_return['prediction'].detach().cpu() + forward_return['target'] = forward_return['target'].detach().cpu() + forward_return['inputs'] = forward_return['inputs'].detach().cpu() + + prediction.append(forward_return['prediction']) + target.append(forward_return['target']) + inputs.append(forward_return['inputs']) + prediction = torch.cat(prediction, dim=0) target = torch.cat(target, dim=0) inputs = torch.cat(inputs, dim=0) - # re-scale data - returns_all = self.rescale_data({"prediction": prediction, "target": target, "inputs": inputs}) - # evaluate - self.evaluate(returns_all) + + returns_all = {'prediction': prediction, 'target': target, 'inputs': inputs} + metrics_results = self.compute_evaluation_metrics(returns_all) + + # save + if save_results: + # save returns_all to self.ckpt_save_dir/test_results.npz + test_results = {k: v.cpu().numpy() for k, v in returns_all.items()} + np.savez(os.path.join(self.ckpt_save_dir, 'test_results.npz'), **test_results) + + if save_metrics: + # save metrics_results to self.ckpt_save_dir/test_metrics.json + with open(os.path.join(self.ckpt_save_dir, 'test_metrics.json'), 'w') as f: + json.dump(metrics_results, f, indent=4) + return returns_all @master_only def on_validating_end(self, train_epoch: Optional[int]): - """Callback at the end of validating. + """Callback at the end of the validation process. Args: - train_epoch (Optional[int]): current epoch if in training process. + train_epoch (Optional[int]): Current epoch if in training process. """ - + greater_best = not self.metrics_best == 'min' if train_epoch is not None: - self.save_best_model(train_epoch, "val_" + self.target_metrics, greater_best=False) + self.save_best_model(train_epoch, 'val_' + self.target_metrics, greater_best=greater_best) + + @master_only + def save_best_model(self, epoch: int, metric_name: str, greater_best: bool = True): + """Save the best model while training. + + Examples: + >>> def on_validating_end(self, train_epoch: Optional[int]): + >>> if train_epoch is not None: + >>> self.save_best_model(train_epoch, 'val/loss', greater_best=False) + + Args: + epoch (int): current epoch. + metric_name (str): metric name used to measure the model, must be registered in `epoch_meter`. + greater_best (bool, optional): `True` means greater value is best, such as `acc` + `False` means lower value is best, such as `loss`. Defaults to True. + """ + + metric = self.meter_pool.get_avg(metric_name) + best_metric = self.best_metrics.get(metric_name) + if best_metric is None or (metric > best_metric if greater_best else metric < best_metric): + self.best_metrics[metric_name] = metric + model = self.model.module if isinstance(self.model, DDP) else self.model + ckpt_dict = { + 'epoch': epoch, + 'model_state_dict': model.state_dict(), + 'optim_state_dict': self.optim.state_dict(), + 'best_metrics': self.best_metrics + } + ckpt_path = os.path.join( + self.ckpt_save_dir, + '{}_best_{}.pt'.format(self.model_name, metric_name.replace('/', '_')) + ) + save_ckpt(ckpt_dict, ckpt_path, self.logger) + self.current_patience = self.early_stopping_patience # reset patience + else: + if self.early_stopping_patience is not None: + self.current_patience -= 1 + + def on_training_end(self, cfg: Dict, train_epoch: Optional[int] = None): + """Callback at the end of the training process. + + Args: + cfg (Dict): Configuration. + train_epoch (Optional[int]): End epoch if in training process. + """ + + if is_master(): + # close tensorboard writer + self.tensorboard_writer.close() + + if hasattr(cfg, 'TEST'): + # evaluate the best model on the test set + best_model_path = os.path.join( + self.ckpt_save_dir, + '{}_best_val_{}.pt'.format(self.model_name, self.target_metrics.replace('/', '_')) + ) + self.logger.info('Evaluating the best model on the test set.') + self.load_model(ckpt_path=best_model_path, strict=True) + self.test_pipeline(cfg=cfg, train_epoch=train_epoch, save_metrics=True, save_results=True) diff --git a/basicts/runners/runner_zoo/m4_tsf_runner.py b/basicts/runners/runner_zoo/m4_tsf_runner.py deleted file mode 100644 index 5bc07e31..00000000 --- a/basicts/runners/runner_zoo/m4_tsf_runner.py +++ /dev/null @@ -1,79 +0,0 @@ -import torch - -from ..base_m4_runner import BaseM4Runner - - -class M4ForecastingRunner(BaseM4Runner): - """Simple Runner: select forward features and target features. This runner can cover most cases.""" - - def __init__(self, cfg: dict): - super().__init__(cfg) - self.forward_features = cfg["MODEL"].get("FORWARD_FEATURES", None) - self.target_features = cfg["MODEL"].get("TARGET_FEATURES", None) - - def select_input_features(self, data: torch.Tensor) -> torch.Tensor: - """Select input features. - - Args: - data (torch.Tensor): input history data, shape [B, L, N, C] - - Returns: - torch.Tensor: reshaped data - """ - - # select feature using self.forward_features - if self.forward_features is not None: - data = data[:, :, :, self.forward_features] - return data - - def select_target_features(self, data: torch.Tensor) -> torch.Tensor: - """Select target feature. - - Args: - data (torch.Tensor): prediction of the model with arbitrary shape. - - Returns: - torch.Tensor: reshaped data with shape [B, L, N, C] - """ - - # select feature using self.target_features - data = data[:, :, :, self.target_features] - return data - - def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: bool = True, **kwargs) -> tuple: - """Feed forward process for train, val, and test. Note that the outputs are NOT re-scaled. - - Args: - data (tuple): (future_data, history_data, future_mask, history_mask). - epoch (int, optional): epoch number. Defaults to None. - iter_num (int, optional): iteration number. Defaults to None. - train (bool, optional): if in the training process. Defaults to True. - - Returns: - tuple: (prediction, real_value) - """ - - # preprocess - future_data, history_data, future_mask, history_mask = data - history_data = self.to_running_device(history_data) # B, L, 1, C - future_data = self.to_running_device(future_data) # B, L, 1, C - history_mask = self.to_running_device(history_mask) # B, L, 1 - future_mask = self.to_running_device(future_mask) # B, L, 1 - - batch_size, length, num_nodes, _ = future_data.shape - - history_data = self.select_input_features(history_data) - if train: - future_data_4_dec = self.select_input_features(future_data) - else: - future_data_4_dec = self.select_input_features(future_data) - # only use the temporal features - future_data_4_dec[..., 0] = torch.empty_like(future_data_4_dec[..., 0]) - - # model forward - model_return = self.model(history_data=history_data, future_data=future_data_4_dec, history_mask=history_mask, future_mask=future_mask, batch_seen=iter_num, epoch=epoch, train=train) - if isinstance(model_return, torch.Tensor): model_return = {"prediction": model_return * future_mask.unsqueeze(-1)} - if "inputs" not in model_return: model_return["inputs"] = self.select_target_features(history_data) - if "target" not in model_return: model_return["target"] = self.select_target_features(future_data * future_mask.unsqueeze(-1)) - - return model_return diff --git a/basicts/runners/runner_zoo/simple_tsf_runner.py b/basicts/runners/runner_zoo/simple_tsf_runner.py index 4965ceaa..7281e150 100644 --- a/basicts/runners/runner_zoo/simple_tsf_runner.py +++ b/basicts/runners/runner_zoo/simple_tsf_runner.py @@ -1,79 +1,100 @@ -import torch +from typing import Dict +import torch from ..base_tsf_runner import BaseTimeSeriesForecastingRunner - class SimpleTimeSeriesForecastingRunner(BaseTimeSeriesForecastingRunner): - """Simple Runner: select forward features and target features. This runner can cover most cases.""" + """ + A Simple Runner for Time Series Forecasting: + Selects forward and target features. This runner is designed to handle most cases. + + Args: + cfg (Dict): Configuration dictionary. + """ + + def __init__(self, cfg: Dict): - def __init__(self, cfg: dict): super().__init__(cfg) - self.forward_features = cfg["MODEL"].get("FORWARD_FEATURES", None) - self.target_features = cfg["MODEL"].get("TARGET_FEATURES", None) + self.forward_features = cfg['MODEL'].get('FORWARD_FEATURES', None) + self.target_features = cfg['MODEL'].get('TARGET_FEATURES', None) def select_input_features(self, data: torch.Tensor) -> torch.Tensor: - """Select input features. + """ + Selects input features based on the forward features specified in the configuration. Args: - data (torch.Tensor): input history data, shape [B, L, N, C] + data (torch.Tensor): Input history data with shape [B, L, N, C]. Returns: - torch.Tensor: reshaped data + torch.Tensor: Data with selected features. """ - # select feature using self.forward_features if self.forward_features is not None: data = data[:, :, :, self.forward_features] return data def select_target_features(self, data: torch.Tensor) -> torch.Tensor: - """Select target feature. + """ + Selects target features based on the target features specified in the configuration. Args: - data (torch.Tensor): prediction of the model with arbitrary shape. + data (torch.Tensor): Model prediction data with arbitrary shape. Returns: - torch.Tensor: reshaped data with shape [B, L, N, C] + torch.Tensor: Data with selected target features and shape [B, L, N, C]. """ - # select feature using self.target_features data = data[:, :, :, self.target_features] return data - def forward(self, data: tuple, epoch: int = None, iter_num: int = None, train: bool = True, **kwargs) -> tuple: - """Feed forward process for train, val, and test. Note that the outputs are NOT re-scaled. + def forward(self, data: Dict, epoch: int = None, iter_num: int = None, train: bool = True, **kwargs) -> Dict: + """ + Performs the forward pass for training, validation, and testing. Args: - data (tuple): data (future data, history ata). - epoch (int, optional): epoch number. Defaults to None. - iter_num (int, optional): iteration number. Defaults to None. - train (bool, optional): if in the training process. Defaults to True. + data (Dict): A dictionary containing 'target' (future data) and 'inputs' (history data) (normalized by self.scaler). + epoch (int, optional): Current epoch number. Defaults to None. + iter_num (int, optional): Current iteration number. Defaults to None. + train (bool, optional): Indicates whether the forward pass is for training. Defaults to True. Returns: - dict: keys that must be included: inputs, prediction, target + Dict: A dictionary containing the keys: + - 'inputs': Selected input features. + - 'prediction': Model predictions. + - 'target': Selected target features. + + Raises: + AssertionError: If the shape of the model output does not match [B, L, N]. """ - # preprocess - future_data, history_data = data - history_data = self.to_running_device(history_data) # B, L, N, C - future_data = self.to_running_device(future_data) # B, L, N, C + # Preprocess input data + future_data, history_data = data['target'], data['inputs'] + history_data = self.to_running_device(history_data) # Shape: [B, L, N, C] + future_data = self.to_running_device(future_data) # Shape: [B, L, N, C] batch_size, length, num_nodes, _ = future_data.shape + # Select input features history_data = self.select_input_features(history_data) - if train: - future_data_4_dec = self.select_input_features(future_data) - else: - future_data_4_dec = self.select_input_features(future_data) - # only use the temporal features + future_data_4_dec = self.select_input_features(future_data) + + if not train: + # For non-training phases, use only temporal features future_data_4_dec[..., 0] = torch.empty_like(future_data_4_dec[..., 0]) - # model forward - model_return = self.model(history_data=history_data, future_data=future_data_4_dec, batch_seen=iter_num, epoch=epoch, train=train) + # Forward pass through the model + model_return = self.model(history_data=history_data, future_data=future_data_4_dec, + batch_seen=iter_num, epoch=epoch, train=train) + + # Parse model return + if isinstance(model_return, torch.Tensor): + model_return = {'prediction': model_return} + if 'inputs' not in model_return: + model_return['inputs'] = self.select_target_features(history_data) + if 'target' not in model_return: + model_return['target'] = self.select_target_features(future_data) + + # Ensure the output shape is correct + assert list(model_return['prediction'].shape)[:3] == [batch_size, length, num_nodes], \ + "The shape of the output is incorrect. Ensure it matches [B, L, N, C]." - # parse model return - if isinstance(model_return, torch.Tensor): model_return = {"prediction": model_return} - if "inputs" not in model_return: model_return["inputs"] = self.select_target_features(history_data) - if "target" not in model_return: model_return["target"] = self.select_target_features(future_data) - assert list(model_return["prediction"].shape)[:3] == [batch_size, length, num_nodes], \ - "error shape of the output, edit the forward function to reshape it to [B, L, N, C]" return model_return diff --git a/basicts/scaler/__init__.py b/basicts/scaler/__init__.py new file mode 100644 index 00000000..fd8b961a --- /dev/null +++ b/basicts/scaler/__init__.py @@ -0,0 +1,9 @@ +from .base_scaler import BaseScaler +from .z_score_scaler import ZScoreScaler +from .min_max_scaler import MinMaxScaler + +__all__ = [ + 'BaseScaler', + 'ZScoreScaler', + 'MinMaxScaler' +] diff --git a/basicts/scaler/base_scaler.py b/basicts/scaler/base_scaler.py new file mode 100644 index 00000000..9ea9ccfc --- /dev/null +++ b/basicts/scaler/base_scaler.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass + +import torch + + +@dataclass +class BaseScaler: + """ + BaseScaler is an abstract class for data scaling and normalization methods. + + Attributes: + dataset_name (str): The name of the dataset, used to load the data. + train_ratio (float): Ratio of the data to be used for training, for fitting the scaler. + norm_each_channel (bool): Flag indicating whether to normalize each channel separately. + rescale (bool): Flag indicating whether to apply rescaling. + """ + + dataset_name: str + train_ratio: float + norm_each_channel: bool + rescale: bool + + def transform(self, input_data: torch.Tensor) -> torch.Tensor: + """ + Apply the scaling transformation to the input data. + + Args: + input_data (torch.Tensor): Input data to be transformed. + + Returns: + torch.Tensor: Scaled data. + """ + + raise NotImplementedError("Subclasses should implement this method.") + + def inverse_transform(self, input_data: torch.Tensor) -> torch.Tensor: + """ + Apply the inverse scaling transformation to the input data. + + Args: + input_data (torch.Tensor): Input data to be transformed back. + + Returns: + torch.Tensor: Original scale data. + """ + + raise NotImplementedError("Subclasses should implement this method.") diff --git a/basicts/scaler/min_max_scaler.py b/basicts/scaler/min_max_scaler.py new file mode 100644 index 00000000..b060ce08 --- /dev/null +++ b/basicts/scaler/min_max_scaler.py @@ -0,0 +1,94 @@ +import json + +import torch +import numpy as np + +from .base_scaler import BaseScaler + + +class MinMaxScaler(BaseScaler): + """ + MinMaxScaler performs min-max normalization on the dataset, scaling the data to a specified range + (typically [0, 1] or [-1, 1]). + + Attributes: + min (np.ndarray): The minimum values of the training data used for normalization. + If `norm_each_channel` is True, this is an array of minimum values, one for each channel. Otherwise, it's a single scalar. + max (np.ndarray): The maximum values of the training data used for normalization. + If `norm_each_channel` is True, this is an array of maximum values, one for each channel. Otherwise, it's a single scalar. + target_channel (int): The specific channel (feature) to which normalization is applied. + By default, it is set to 0, indicating the first channel. + """ + + def __init__(self, dataset_name: str, train_ratio: float, norm_each_channel: bool = True, rescale: bool = True): + """ + Initialize the MinMaxScaler by loading the dataset and fitting the scaler to the training data. + + The scaler computes the minimum and maximum values from the training data, which are then used + to normalize the data during the `transform` operation. + + Args: + dataset_name (str): The name of the dataset used to load the data. + train_ratio (float): The ratio of the dataset to be used for training. The scaler is fitted on this portion of the data. + norm_each_channel (bool): Flag indicating whether to normalize each channel separately. + If True, the min and max values are computed for each channel independently. Defaults to True. + rescale (bool): Flag indicating whether to apply rescaling after normalization. + This flag is included for consistency with the base class but is typically True in min-max scaling. + """ + + super().__init__(dataset_name, train_ratio, norm_each_channel, rescale) + self.target_channel = 0 # assuming normalization on the first channel + + # load dataset description and data + description_file_path = f'datasets/{dataset_name}/desc.json' + with open(description_file_path, 'r') as f: + description = json.load(f) + data_file_path = f'datasets/{dataset_name}/data.dat' + data = np.memmap(data_file_path, dtype='float32', mode='r', shape=tuple(description['shape'])) + + # split data into training set based on the train_ratio + train_size = int(len(data) * train_ratio) + train_data = data[:train_size, :, self.target_channel].copy() + + # compute minimum and maximum values for normalization + if norm_each_channel: + self.min = np.min(train_data, axis=0, keepdims=True) + self.max = np.max(train_data, axis=0, keepdims=True) + else: + self.min = np.min(train_data) + self.max = np.max(train_data) + + def transform(self, input_data: torch.Tensor) -> torch.Tensor: + """ + Apply min-max normalization to the input data. + + This method normalizes the input data using the minimum and maximum values computed from the training data. + The normalization is applied only to the specified `target_channel`. + + Args: + input_data (torch.Tensor): The input data to be normalized. + + Returns: + torch.Tensor: The normalized data with the same shape as the input. + """ + + input_data[..., self.target_channel] = (input_data[..., self.target_channel] - self.min) / (self.max - self.min) + return input_data + + def inverse_transform(self, input_data: torch.Tensor) -> torch.Tensor: + """ + Reverse the min-max normalization to recover the original data scale. + + This method transforms the normalized data back to its original scale using the minimum and maximum + values computed from the training data. This is useful for interpreting model outputs or for further analysis + in the original data scale. + + Args: + input_data (torch.Tensor): The normalized data to be transformed back. + + Returns: + torch.Tensor: The data transformed back to its original scale. + """ + + input_data[..., self.target_channel] = input_data[..., self.target_channel] * (self.max - self.min) + self.min + return input_data diff --git a/basicts/scaler/z_score_scaler.py b/basicts/scaler/z_score_scaler.py new file mode 100644 index 00000000..bdb58782 --- /dev/null +++ b/basicts/scaler/z_score_scaler.py @@ -0,0 +1,102 @@ +import json + +import torch +import numpy as np + +from .base_scaler import BaseScaler + + +class ZScoreScaler(BaseScaler): + """ + ZScoreScaler performs Z-score normalization on the dataset, transforming the data to have a mean of zero + and a standard deviation of one. This is commonly used in preprocessing to normalize data, ensuring that + each feature contributes equally to the model. + + Attributes: + mean (np.ndarray): The mean of the training data used for normalization. + If `norm_each_channel` is True, this is an array of means, one for each channel. Otherwise, it's a single scalar. + std (np.ndarray): The standard deviation of the training data used for normalization. + If `norm_each_channel` is True, this is an array of standard deviations, one for each channel. Otherwise, it's a single scalar. + target_channel (int): The specific channel (feature) to which normalization is applied. + By default, it is set to 0, indicating the first channel. + """ + + def __init__(self, dataset_name: str, train_ratio: float, norm_each_channel: bool, rescale: bool): + """ + Initialize the ZScoreScaler by loading the dataset and fitting the scaler to the training data. + + The scaler computes the mean and standard deviation from the training data, which is then used to + normalize the data during the `transform` operation. + + Args: + dataset_name (str): The name of the dataset used to load the data. + train_ratio (float): The ratio of the dataset to be used for training. The scaler is fitted on this portion of the data. + norm_each_channel (bool): Flag indicating whether to normalize each channel separately. + If True, the mean and standard deviation are computed for each channel independently. + rescale (bool): Flag indicating whether to apply rescaling after normalization. This flag is included for consistency with + the base class but is not directly used in Z-score normalization. + """ + + super().__init__(dataset_name, train_ratio, norm_each_channel, rescale) + self.target_channel = 0 # assuming normalization on the first channel + + # load dataset description and data + description_file_path = f'datasets/{dataset_name}/desc.json' + with open(description_file_path, 'r') as f: + description = json.load(f) + data_file_path = f'datasets/{dataset_name}/data.dat' + data = np.memmap(data_file_path, dtype='float32', mode='r', shape=tuple(description['shape'])) + + # split data into training set based on the train_ratio + train_size = int(len(data) * train_ratio) + train_data = data[:train_size, :, self.target_channel].copy() + + # compute mean and standard deviation + if norm_each_channel: + self.mean = np.mean(train_data, axis=0, keepdims=True) + self.std = np.std(train_data, axis=0, keepdims=True) + self.std[self.std == 0] = 1.0 # prevent division by zero by setting std to 1 where it's 0 + else: + self.mean = np.mean(train_data) + self.std = np.std(train_data) + if self.std == 0: + self.std = 1.0 # prevent division by zero by setting std to 1 where it's 0 + + def transform(self, input_data: torch.Tensor) -> torch.Tensor: + """ + Apply Z-score normalization to the input data. + + This method normalizes the input data using the mean and standard deviation computed from the training data. + The normalization is applied only to the specified `target_channel`. + + Args: + input_data (torch.Tensor): The input data to be normalized. + + Returns: + torch.Tensor: The normalized data with the same shape as the input. + """ + + input_data[..., self.target_channel] = (input_data[..., self.target_channel] - self.mean) / self.std + return input_data + + def inverse_transform(self, input_data: torch.Tensor) -> torch.Tensor: + """ + Reverse the Z-score normalization to recover the original data scale. + + This method transforms the normalized data back to its original scale using the mean and standard deviation + computed from the training data. This is useful for interpreting model outputs or for further analysis in the original data scale. + + Args: + input_data (torch.Tensor): The normalized data to be transformed back. + + Returns: + torch.Tensor: The data transformed back to its original scale. + """ + + if isinstance(self.mean, np.ndarray): + self.mean = torch.tensor(self.mean, device=input_data.device) + self.std = torch.tensor(self.std, device=input_data.device) + # Clone the input data to prevent in-place modification (which is not allowed in PyTorch) + input_data = input_data.clone() + input_data[..., self.target_channel] = input_data[..., self.target_channel] * self.std + self.mean + return input_data diff --git a/basicts/utils/__init__.py b/basicts/utils/__init__.py index fd82be27..fda27ef6 100644 --- a/basicts/utils/__init__.py +++ b/basicts/utils/__init__.py @@ -1,10 +1,11 @@ -from .serialization import load_adj, load_pkl, dump_pkl, load_node2vec_emb -from .misc import clock, check_nan_inf, remove_nan_inf -from .misc import partial_func as partial -from .m4 import m4_summary from .xformer import data_transformation_4_xformer +from .serialization import load_adj, load_pkl, dump_pkl, \ + load_dataset_data, get_regular_settings, load_dataset_desc +from .misc import clock, check_nan_inf, remove_nan_inf, \ + partial_func as partial -__all__ = ["load_adj", "load_pkl", "dump_pkl", - "load_node2vec_emb", "clock", "check_nan_inf", - "remove_nan_inf", "data_transformation_4_xformer", - "partial", "m4_summary"] +__all__ = ['load_adj', 'load_pkl', 'dump_pkl', + 'clock', 'check_nan_inf', + 'remove_nan_inf', 'data_transformation_4_xformer', + 'partial', 'get_regular_settings', + 'load_dataset_data', 'load_dataset_desc'] diff --git a/basicts/utils/adjacent_matrix_norm.py b/basicts/utils/adjacent_matrix_norm.py index 417d4ac6..36d13218 100644 --- a/basicts/utils/adjacent_matrix_norm.py +++ b/basicts/utils/adjacent_matrix_norm.py @@ -4,99 +4,103 @@ def calculate_symmetric_normalized_laplacian(adj: np.ndarray) -> np.matrix: - """Calculate yymmetric normalized laplacian. - Assuming unnormalized laplacian matrix is `L = D - A`, - then symmetric normalized laplacian matrix is: - `L^{Sym} = D^-1/2 L D^-1/2 = D^-1/2 (D-A) D^-1/2 = I - D^-1/2 A D^-1/2` - For node `i` and `j` where `i!=j`, L^{sym}_{ij} <=0. + """ + Calculate the symmetric normalized Laplacian. + + The symmetric normalized Laplacian matrix is given by: + L^{Sym} = I - D^{-1/2} A D^{-1/2}, where L is the unnormalized Laplacian, + D is the degree matrix, and A is the adjacency matrix. Args: - adj (np.ndarray): Adjacent matrix A + adj (np.ndarray): Adjacency matrix A. Returns: - np.matrix: Symmetric normalized laplacian L^{Sym} + np.matrix: Symmetric normalized Laplacian L^{Sym}. """ adj = sp.coo_matrix(adj) - degree = np.array(adj.sum(1)) - # diagonals of D^{-1/2} - degree_inv_sqrt = np.power(degree, -0.5).flatten() - degree_inv_sqrt[np.isinf(degree_inv_sqrt)] = 0. - matrix_degree_inv_sqrt = sp.diags(degree_inv_sqrt) # D^{-1/2} - symmetric_normalized_laplacian = sp.eye( - adj.shape[0]) - matrix_degree_inv_sqrt.dot(adj).dot(matrix_degree_inv_sqrt).tocoo() - return symmetric_normalized_laplacian + degree = np.array(adj.sum(1)).flatten() + degree_inv_sqrt = np.power(degree, -0.5) + degree_inv_sqrt[np.isinf(degree_inv_sqrt)] = 0.0 + matrix_degree_inv_sqrt = sp.diags(degree_inv_sqrt) + laplacian = sp.eye(adj.shape[0]) - matrix_degree_inv_sqrt.dot(adj).dot(matrix_degree_inv_sqrt).tocoo() + return laplacian def calculate_scaled_laplacian(adj: np.ndarray, lambda_max: int = 2, undirected: bool = True) -> np.matrix: - """Re-scaled the eigenvalue to [-1, 1] by scaled the normalized laplacian matrix for chebyshev pol. - According to `2017 ICLR GCN`, the lambda max is set to 2, and the graph is set to undirected. - Note that rescale the laplacian matrix is equal to rescale the eigenvalue matrix. - `L_{scaled} = (2 / lambda_max * L) - I` + """ + Scale the normalized Laplacian for use in Chebyshev polynomials. + + Rescale the Laplacian matrix such that its eigenvalues are within the range [-1, 1]. Args: - adj (np.ndarray): Adjacent matrix A - lambda_max (int, optional): Defaults to 2. - undirected (bool, optional): Defaults to True. + adj (np.ndarray): Adjacency matrix A. + lambda_max (int, optional): Maximum eigenvalue, defaults to 2. + undirected (bool, optional): If True, treats the graph as undirected, defaults to True. Returns: - np.matrix: The rescaled laplacian matrix. + np.matrix: Scaled Laplacian matrix. """ if undirected: - adj = np.maximum.reduce([adj, adj.T]) - laplacian_matrix = calculate_symmetric_normalized_laplacian(adj) - if lambda_max is None: # manually cal the max lambda - lambda_max, _ = linalg.eigsh(laplacian_matrix, 1, which='LM') + adj = np.maximum(adj, adj.T) + + laplacian = calculate_symmetric_normalized_laplacian(adj) + + if lambda_max is None: + lambda_max, _ = linalg.eigsh(laplacian, 1, which='LM') lambda_max = lambda_max[0] - laplacian_matrix = sp.csr_matrix(laplacian_matrix) - num_nodes, _ = laplacian_matrix.shape - identity_matrix = sp.identity( - num_nodes, format='csr', dtype=laplacian_matrix.dtype) - laplacian_res = (2 / lambda_max * laplacian_matrix) - identity_matrix - return laplacian_res + laplacian = sp.csr_matrix(laplacian) + identity = sp.identity(laplacian.shape[0], format='csr', dtype=laplacian.dtype) + + scaled_laplacian = (2 / lambda_max) * laplacian - identity + return scaled_laplacian def calculate_symmetric_message_passing_adj(adj: np.ndarray) -> np.matrix: - """Calculate the renormalized message passing adj in `GCN`. - A = A + I - return D^{-1/2} A D^{-1/2} + """ + Calculate the renormalized message-passing adjacency matrix as proposed in GCN. + + The message-passing adjacency matrix is defined as A' = D^{-1/2} (A + I) D^{-1/2}. Args: - adj (np.ndarray): Adjacent matrix A + adj (np.ndarray): Adjacency matrix A. Returns: - np.matrix: Renormalized message passing adj in `GCN`. + np.matrix: Renormalized message-passing adjacency matrix. """ - # add self loop - adj = adj + np.diag(np.ones(adj.shape[0], dtype=np.float32)) - # print("calculating the renormalized message passing adj, please ensure that self-loop has added to adj.") + adj = adj + np.eye(adj.shape[0], dtype=np.float32) adj = sp.coo_matrix(adj) - row_sum = np.array(adj.sum(1)) - d_inv_sqrt = np.power(row_sum, -0.5).flatten() - d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. + + row_sum = np.array(adj.sum(1)).flatten() + d_inv_sqrt = np.power(row_sum, -0.5) + d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0.0 + d_mat_inv_sqrt = sp.diags(d_inv_sqrt) - mp_adj = d_mat_inv_sqrt.dot(adj).transpose().dot( - d_mat_inv_sqrt).astype(np.float32) - return mp_adj + mp_adj = d_mat_inv_sqrt.dot(adj).transpose().dot(d_mat_inv_sqrt).astype(np.float32) + return mp_adj def calculate_transition_matrix(adj: np.ndarray) -> np.matrix: - """Calculate the transition matrix `P` proposed in DCRNN and Graph WaveNet. - P = D^{-1}A = A/rowsum(A) + """ + Calculate the transition matrix as proposed in DCRNN and Graph WaveNet. + + The transition matrix is defined as P = D^{-1} A, where D is the degree matrix. Args: - adj (np.ndarray): Adjacent matrix A + adj (np.ndarray): Adjacency matrix A. Returns: - np.matrix: Transition matrix P + np.matrix: Transition matrix P. """ adj = sp.coo_matrix(adj) row_sum = np.array(adj.sum(1)).flatten() - d_inv = np.power(row_sum, -1).flatten() - d_inv[np.isinf(d_inv)] = 0. + d_inv = np.power(row_sum, -1) + d_inv[np.isinf(d_inv)] = 0.0 + d_mat = sp.diags(d_inv) prob_matrix = d_mat.dot(adj).astype(np.float32).todense() + return prob_matrix diff --git a/basicts/utils/logging.py b/basicts/utils/logging.py deleted file mode 100644 index 58e6157a..00000000 --- a/basicts/utils/logging.py +++ /dev/null @@ -1,15 +0,0 @@ -import logging -from easytorch.utils.logging import logger_initialized - - -def clear_loggers(): - for logger_name in logger_initialized: - # logging.getLogger(logger_name).handlers.clear() - logger = logging.getLogger(logger_name) - # disable the logger - # logger.disabled = True - # remove handlers - for handler in logger.handlers: - handler.close() - logger.handlers.clear() - logger_initialized.clear() diff --git a/basicts/utils/m4.py b/basicts/utils/m4.py deleted file mode 100644 index 9644968f..00000000 --- a/basicts/utils/m4.py +++ /dev/null @@ -1,221 +0,0 @@ -# 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. - -# Modified from https://github.com/ServiceNow/N-BEATS - -""" -M4 Summary -""" -import os -from glob import glob -from dataclasses import dataclass -from collections import OrderedDict - -import numpy as np -import pandas as pd - -Forecast = np.ndarray -Target = np.ndarray - - -@dataclass() -class M4Dataset: - ids: np.ndarray - groups: np.ndarray - frequencies: np.ndarray - horizons: np.ndarray - values: np.ndarray - - @staticmethod - def load(info_file_path: str = None, data: np.array = None) -> "M4Dataset": - """ - Load cached dataset. - - :param training: Load training part if training is True, test part otherwise. - """ - m4_info = pd.read_csv(info_file_path) - ids = m4_info.M4id.values - groups = m4_info.SP.values - frequencies = m4_info.Frequency.values - horizons = m4_info.Horizon.values - values = data - return M4Dataset(ids=ids, groups=groups, frequencies=frequencies, horizons=horizons, values=values) - -def mase(forecast: Forecast, insample: np.ndarray, outsample: Target, frequency: int) -> np.ndarray: - """ - MASE loss as defined in "Scaled Errors" https://robjhyndman.com/papers/mase.pdf - - :param forecast: Forecast values. Shape: batch, time_o - :param insample: Insample values. Shape: batch, time_i - :param outsample: Target values. Shape: batch, time_o - :param frequency: Frequency value - :return: Same shape array with error calculated for each time step - """ - return np.mean(np.abs(forecast - outsample)) / np.mean(np.abs(insample[:-frequency] - insample[frequency:])) - - -def smape_2(forecast: Forecast, target: Target) -> np.ndarray: - """ - 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 - :return: Same shape array with sMAPE calculated for each time step of each timeseries. - """ - 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 group_values(values: np.ndarray, groups: np.ndarray, group_name: str) -> np.ndarray: - """ - Filter values array by group indices and clean it from NaNs. - - :param values: Values to filter. - :param groups: Timeseries groups. - :param group_name: Group name to filter by. - :return: Filtered and cleaned timeseries. - """ - return np.array([v[~np.isnan(v)] for v in values[groups == group_name]], dtype=object) - - -class M4Summary: - def __init__(self, info_file_path, train_values, test_values, naive_forecast_file_path): - self.training_set = M4Dataset.load(info_file_path, train_values) - self.test_set = M4Dataset.load(info_file_path, test_values) - self.naive_forecast_file_path = naive_forecast_file_path - - def evaluate(self, forecast: np.ndarray): - """ - Evaluate forecasts using M4 test dataset. - - :param forecast: Forecasts. Shape: timeseries, time. - :return: sMAPE and OWA grouped by seasonal patterns. - """ - forecast = np.array([v[~np.isnan(v)] for v in forecast], dtype=object) - - grouped_smapes = {group_name: - np.mean(smape_2(forecast=group_values(values=forecast, - groups=self.test_set.groups, - group_name=group_name), - target=group_values(values=self.test_set.values, - groups=self.test_set.groups, - group_name=group_name))) - for group_name in np.unique(self.test_set.groups)} - grouped_smapes = self.summarize_groups(grouped_smapes) - - grouped_owa = OrderedDict() - - naive2_forecasts = pd.read_csv(self.naive_forecast_file_path).values[:, 1:].astype(np.float32) - naive2_forecasts = np.array([v[~np.isnan(v)] for v in naive2_forecasts], dtype=object) - - model_mases = {} - naive2_smapes = {} - naive2_mases = {} - for group_name in np.unique(self.test_set.groups): - model_forecast = group_values(forecast, self.test_set.groups, group_name) - 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_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_model_mases), round_all(grouped_owa) - - 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 - - -def m4_summary(save_dir, project_dir): - """Summary evaluation for M4 dataset. - - Args: - save_dir (str): Directory where prediction results are saved. All "{save_dir}/M4_{seasonal pattern}.npy" should exist. - Seasonal patterns = ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] - project_dir (str): Project directory. The M4 raw data should be in "{project_dir}/datasets/raw_data/M4". - """ - seasonal_patterns = ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] # the order cannot be changed - data_dir = project_dir + "/datasets/raw_data/M4" - info_file_path = data_dir + "/M4-info.csv" - - m4_info = pd.read_csv(info_file_path) - m4_ids = m4_info.M4id.values - def build_cache(files: str) -> None: - timeseries_dict = OrderedDict(list(zip(m4_ids, [[]] * len(m4_ids)))) - for train_csv in glob(os.path.join(data_dir, files)): - dataset = pd.read_csv(train_csv) - dataset.set_index(dataset.columns[0], inplace=True) - for m4id, row in dataset.iterrows(): - values = row.values - timeseries_dict[m4id] = values[~np.isnan(values)] - return np.array(list(timeseries_dict.values()), dtype=object) - - print("Building cache for M4 dataset...") - # read prediction and ground truth - prediction = [] - for seasonal_pattern in seasonal_patterns: - prediction.extend(np.load(save_dir + "/M4_{0}.npy".format(seasonal_pattern))) - prediction = np.array(prediction, dtype=object) - train_values = build_cache("*-train.csv") - test_values = build_cache("*-test.csv") - print("Summarizing M4 dataset...") - summary = M4Summary(info_file_path, train_values, test_values, data_dir + "/submission-Naive2.csv") - results = pd.DataFrame(summary.evaluate(prediction), index=["SMAPE", "MASE", "OWA"]) - return results diff --git a/basicts/utils/misc.py b/basicts/utils/misc.py index 4bec6919..dc74959a 100644 --- a/basicts/utils/misc.py +++ b/basicts/utils/misc.py @@ -1,94 +1,66 @@ -import os import time -import importlib -from typing import List from functools import partial import torch -from easytorch.utils.misc import scan_dir -def scan_modules(work_dir: str, file_dir: str, exclude_files: List[str] = None, exclude_dirs: List[str] = None): - """ - overwrite easytorch.utils.scan_modeuls: automatically scan and import modules for registry, and exclude some files and dirs. +class partial_func(partial): """ - module_dir = os.path.dirname(os.path.abspath(file_dir)) - import_prefix = module_dir[module_dir.find(work_dir) + len(work_dir) + 1:].replace("/", ".").replace("\\", ".") - - if exclude_files is None: - exclude_files = [] - if exclude_dirs is None: - exclude_dirs = [] - - # get all file names, and remove the files in exclude_files - model_file_names = [ - v[:v.find(".py")].replace("/", ".").replace("\\", ".") \ - for v in scan_dir(module_dir, suffix="py", recursive=True) if v not in exclude_files - ] - - # remove the files in exclude_dirs. TODO: use os.path to check - for exclude_dir in exclude_dirs: - exclude_dir = exclude_dir.replace("/", ".").replace("\\", ".") - model_file_names = [file_name for file_name in model_file_names if exclude_dir not in file_name] + Custom partial function class that provides a cleaner string representation. - # import all modules - return [importlib.import_module(f"{import_prefix}.{file_name}") for file_name in model_file_names] - - -class partial_func(partial): - """partial class. - __str__ in functools.partial contains the address of the function, which changes randomly and will disrupt easytorch's md5 calculation. + This prevents the address of the function from being included, which can cause issues with hashing. """ def __str__(self): - return "partial({}, {})".format(self.func.__name__, self.keywords) + return f"partial({self.func.__name__}, {self.keywords})" def clock(func): - """clock decorator""" - def clocked(*args, **kw): - """decorator for clock""" + """ + Decorator to measure the execution time of a function. + + This decorator prints the time taken for a function to execute. + """ + + def clocked(*args, **kwargs): t0 = time.perf_counter() - result = func(*args, **kw) + result = func(*args, **kwargs) elapsed = time.perf_counter() - t0 - name = func.__name__ - print("%s: %0.8fs..." % (name, elapsed)) + print(f"{func.__name__}: {elapsed:.8f}s") return result return clocked - def check_nan_inf(tensor: torch.Tensor, raise_ex: bool = True) -> tuple: - """check nan and in in tensor + """ + Check for NaN or Inf values in a tensor. Args: - tensor (torch.Tensor): Tensor - raise_ex (bool, optional): If raise exceptions. Defaults to True. + tensor (torch.Tensor): Input tensor to check. + raise_ex (bool, optional): Whether to raise an exception if NaN or Inf values are found. Defaults to True. Raises: - Exception: If raise_ex is True and there are nans or infs in tensor, then raise Exception. + ValueError: If raise_ex is True and NaN or Inf values are found. Returns: - dict: {'nan': bool, 'inf': bool} - bool: if exist nan or if + tuple: A dictionary indicating presence of NaN and Inf values, and a boolean indicating whether either is present. """ - # nan nan = torch.any(torch.isnan(tensor)) - # inf inf = torch.any(torch.isinf(tensor)) - # raise + if raise_ex and (nan or inf): - raise Exception({"nan": nan, "inf": inf}) - return {"nan": nan, "inf": inf}, nan or inf + raise ValueError({"nan": nan, "inf": inf}) + return {"nan": nan, "inf": inf}, nan or inf -def remove_nan_inf(tensor: torch.Tensor): - """remove nan and inf in tensor +def remove_nan_inf(tensor: torch.Tensor) -> torch.Tensor: + """ + Remove NaN and Inf values from a tensor by replacing them with zeros. Args: - tensor (torch.Tensor): input tensor + tensor (torch.Tensor): Input tensor. Returns: - torch.Tensor: output tensor + torch.Tensor: Tensor with NaN and Inf values replaced by zeros. """ tensor = torch.where(torch.isnan(tensor), torch.zeros_like(tensor), tensor) diff --git a/basicts/utils/serialization.py b/basicts/utils/serialization.py index 60c015c8..1c57a916 100644 --- a/basicts/utils/serialization.py +++ b/basicts/utils/serialization.py @@ -1,103 +1,129 @@ +import json import pickle - -import torch import numpy as np +from .adjacent_matrix_norm import ( + calculate_scaled_laplacian, + calculate_symmetric_normalized_laplacian, + calculate_symmetric_message_passing_adj, + calculate_transition_matrix +) + + +def get_regular_settings(dataset_name: str) -> dict: + """ + Get the regular settings for a dataset. + + Args: + dataset_name (str): Name of the dataset. + + Returns: + dict: Regular settings for the dataset. + """ + + # read json file: datasets/dataset_name/desc.json + desc = load_dataset_desc(dataset_name) + regular_settings = desc['regular_settings'] + return regular_settings + +def load_dataset_desc(dataset_name: str) -> str: + """ + Get the description of a dataset. + + Args: + dataset_name (str): Name of the dataset. + + Returns: + str: Description of the dataset. + """ -from .adjacent_matrix_norm import calculate_scaled_laplacian, calculate_symmetric_normalized_laplacian, calculate_symmetric_message_passing_adj, calculate_transition_matrix + # read json file: datasets/dataset_name/desc.json + with open(f'datasets/{dataset_name}/desc.json', 'r') as f: + desc = json.load(f) + return desc +def load_dataset_data(dataset_name: str) -> np.ndarray: + """ + Load data from a .dat file (memmap) via numpy. + + Args: + dataset_name (str): Path to the .dat file. + + Returns: + np.ndarray: Loaded data. + """ + + shape = load_dataset_desc(dataset_name)['shape'] + dat_file_path = f'datasets/{dataset_name}/data.dat' + data = np.memmap(dat_file_path, mode='r', dtype=np.float32, shape=tuple(shape)).copy() + return data def load_pkl(pickle_file: str) -> object: - """Load pickle data. + """ + Load data from a pickle file. Args: - pickle_file (str): file path + pickle_file (str): Path to the pickle file. Returns: - object: loaded objected + object: Loaded object from the pickle file. """ try: - with open(pickle_file, "rb") as f: + with open(pickle_file, 'rb') as f: pickle_data = pickle.load(f) except UnicodeDecodeError: - with open(pickle_file, "rb") as f: - pickle_data = pickle.load(f, encoding="latin1") + with open(pickle_file, 'rb') as f: + pickle_data = pickle.load(f, encoding='latin1') except Exception as e: - print("Unable to load data ", pickle_file, ":", e) + print(f'Unable to load data from {pickle_file}: {e}') raise return pickle_data - def dump_pkl(obj: object, file_path: str): - """Dumplicate pickle data. + """ + Save an object to a pickle file. Args: - obj (object): object - file_path (str): file path + obj (object): Object to save. + file_path (str): Path to the file. """ - with open(file_path, "wb") as f: + with open(file_path, 'wb') as f: pickle.dump(obj, f) - def load_adj(file_path: str, adj_type: str): - """load adjacency matrix. + """ + Load and preprocess an adjacency matrix. Args: - file_path (str): file path - adj_type (str): adjacency matrix type + file_path (str): Path to the file containing the adjacency matrix. + adj_type (str): Type of adjacency matrix preprocessing. Returns: - list of numpy.matrix: list of preproceesed adjacency matrices - np.ndarray: raw adjacency matrix + list: List of processed adjacency matrices. + np.ndarray: Raw adjacency matrix. """ try: - # METR and PEMS_BAY _, _, adj_mx = load_pkl(file_path) except ValueError: - # PEMS04 adj_mx = load_pkl(file_path) - if adj_type == "scalap": + + if adj_type == 'scalap': adj = [calculate_scaled_laplacian(adj_mx).astype(np.float32).todense()] - elif adj_type == "normlap": - adj = [calculate_symmetric_normalized_laplacian( - adj_mx).astype(np.float32).todense()] - elif adj_type == "symnadj": - adj = [calculate_symmetric_message_passing_adj( - adj_mx).astype(np.float32).todense()] - elif adj_type == "transition": + elif adj_type == 'normlap': + adj = [calculate_symmetric_normalized_laplacian(adj_mx).astype(np.float32).todense()] + elif adj_type == 'symnadj': + adj = [calculate_symmetric_message_passing_adj(adj_mx).astype(np.float32).todense()] + elif adj_type == 'transition': adj = [calculate_transition_matrix(adj_mx).T] - elif adj_type == "doubletransition": + elif adj_type == 'doubletransition': adj = [calculate_transition_matrix(adj_mx).T, calculate_transition_matrix(adj_mx.T).T] - elif adj_type == "identity": + elif adj_type == 'identity': adj = [np.diag(np.ones(adj_mx.shape[0])).astype(np.float32)] - elif adj_type == "original": + elif adj_type == 'original': adj = [adj_mx] else: - error = 0 - assert error, "adj type not defined" - return adj, adj_mx + raise ValueError('Undefined adjacency matrix type.') - -def load_node2vec_emb(file_path: str) -> torch.Tensor: - """load node2vec embedding - - Args: - file_path (str): file path - - Returns: - torch.Tensor: node2vec embedding - """ - - # spatial embedding - with open(file_path, mode="r") as f: - lines = f.readlines() - temp = lines[0].split(" ") - num_vertex, dims = int(temp[0]), int(temp[1]) - spatial_embeddings = torch.zeros((num_vertex, dims), dtype=torch.float32) - for line in lines[1:]: - temp = line.split(" ") - index = int(temp[0]) - spatial_embeddings[index] = torch.Tensor([float(ch) for ch in temp[1:]]) - return spatial_embeddings + return adj, adj_mx diff --git a/datasets/README.md b/datasets/README.md index dd4601be..cefa2d7d 100644 --- a/datasets/README.md +++ b/datasets/README.md @@ -150,7 +150,6 @@ Visibility, DryBulbFarenheit, DryBulbCelsius, WetBulbFarenheit, DewPointFarenhei **Description**: PEMS0X is a series of traffic flow dataset, including PEMS03, PEMS04, PEMS07, and PEMS08. X represents the code of district where the data is collected. The traffic information is recorded at the rate of every 5 minutes. Similar to METR-LA and PEMS-BAY, PEMS0X also includes a sensor graph to indicate dependencies between sensors. The details of the computation of the adjacency matrix can be found in the [ASTGCN](https://ojs.aaai.org/index.php/AAAI/article/view/3881/3759). - **Period**: - PEMS03: 2018/09/01 -> 2018/11/30 @@ -186,10 +185,8 @@ Visibility, DryBulbFarenheit, DryBulbCelsius, WetBulbFarenheit, DewPointFarenhei **Description**: LargeST is a series of large scale traffic flow datasets, including CA, GLA, GBA, SD. Similar to METR-LA and PEMS-BAY, PEMS0X also includes a sensor graph to indicate dependencies between sensors. Moreover, LargeST also includes a meta data graph of each node. Following the original paper, we use the data from 2019. The details of largeST can be found in the [LargeST: A Benchmark Dataset for Large-Scale Traffic Forecasting.](https://arxiv.org/pdf/2306.08259.pdf) - **Period**: 2019 - **Number of Time Steps**: - CA: 35040 @@ -210,4 +207,41 @@ Visibility, DryBulbFarenheit, DryBulbCelsius, WetBulbFarenheit, DewPointFarenhei **Typical Settings**: -- Spatial temporal forecasting. \ No newline at end of file +- Spatial temporal forecasting. + +### 9. Illness + +**Source**: [Autoformer: Decomposition Transformers with Auto-Correlation for Long-Term Series Forecasting, NeurIPS 2021](https://github.com/thuml/Autoformer). [Data Link](https://github.com/thuml/Autoformer) + +**Description**: Illness includes the weekly recorded influenza-like illness (ILI) patients data from Centers for Disease Control and Prevention of the United States between 2002 and 2021, which describes the ratio of patients seen with ILI and the total number of the patients. + +**Period**: 2002-01-01 -> 2020-06-30 + +**Number of Time Steps**: 966 + +**Dataset Split**: 7:1:2 + +**Variates**: % WEIGHTED ILI, %UNWEIGHTED ILI, AGE 0-4, AGE 5-24, ILITOTAL, NUM. OF PROVIDERS, OT + +**Typical Settings**: + +- Long time series forecasting. + + +### 10. Traffic + +**Source**: [Autoformer: Decomposition Transformers with Auto-Correlation for Long-Term Series Forecasting, NeurIPS 2021](https://github.com/thuml/Autoformer). [Data Link](https://github.com/thuml/Autoformer) + +**Description**: Traffic is a collection of hourly data from California Department of Transportation, which describes the road occupancy rates measured by different sensors on San Francisco Bay area freeways. + +**Period**: 2016-07-01 02:00:00 -> 2018-07-02 01:00:00 + +**Number of Time Steps**: 17544 + +**Dataset Split**: 7:1:2 + +**Variates**: Data measured by 862 sensors. + +**Typical Settings**: + +- Long time series forecasting. diff --git a/examples/arch.py b/examples/arch.py new file mode 100644 index 00000000..e9af348a --- /dev/null +++ b/examples/arch.py @@ -0,0 +1,52 @@ +import torch +from torch import nn + +class MultiLayerPerceptron(nn.Module): + """ + A simple Multi-Layer Perceptron (MLP) model with two fully connected layers. + + This model is designed to take historical time series data as input and produce future predictions. + It consists of two linear layers with a ReLU activation in between. + + Attributes: + fc1 (nn.Linear): The first fully connected layer, which maps the input history sequence to a hidden dimension. + fc2 (nn.Linear): The second fully connected layer, which maps the hidden dimension to the prediction sequence. + act (nn.ReLU): The ReLU activation function applied between the two layers. + """ + + def __init__(self, history_seq_len: int, prediction_seq_len: int, hidden_dim: int): + """ + Initialize the MultiLayerPerceptron model. + + Args: + history_seq_len (int): The length of the input history sequence. + prediction_seq_len (int): The length of the output prediction sequence. + hidden_dim (int): The number of units in the hidden layer. + """ + super().__init__() + self.fc1 = nn.Linear(history_seq_len, hidden_dim) + self.fc2 = nn.Linear(hidden_dim, prediction_seq_len) + self.act = nn.ReLU() + + def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_seen: int, epoch: int, train: bool) -> torch.Tensor: + """ + Perform a forward pass through the network. + + Args: + history_data (torch.Tensor): A tensor containing historical data, typically of shape `[B, L, N, C]`. + future_data (torch.Tensor): A tensor containing future data, typically of shape `[B, L, N, C]`. + batch_seen (int): The number of batches seen so far during training. + epoch (int): The current epoch number. + train (bool): Flag indicating whether the model is in training mode. + + Returns: + torch.Tensor: The output prediction tensor, typically of shape `[B, L, N, C]`. + """ + + history_data = history_data[..., 0].transpose(1, 2) # [B, L, N, C] -> [B, N, L] + + # [B, N, L] --h=act(fc1(x))--> [B, N, D] --fc2(h)--> [B, N, L] -> [B, L, N] + prediction = self.fc2(self.act(self.fc1(history_data))).transpose(1, 2) + + # [B, L, N] -> [B, L, N, 1] + return prediction.unsqueeze(-1) diff --git a/examples/complete_config.py b/examples/complete_config.py new file mode 100644 index 00000000..71df2742 --- /dev/null +++ b/examples/complete_config.py @@ -0,0 +1,213 @@ +############################## Import Dependencies ############################## + +import os +import sys +from easydict import EasyDict + +# TODO: Remove this when basicts can be installed via pip +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +# Import metrics & loss functions +from basicts.metrics import masked_mae, masked_mape, masked_rmse +# Import dataset class +from basicts.data import TimeSeriesForecastingDataset +# Import runner class +from basicts.runners import SimpleTimeSeriesForecastingRunner +# Import scaler class +from basicts.scaler import ZScoreScaler +# Import model architecture +from .arch import MultiLayerPerceptron as MLP + +from basicts.utils import get_regular_settings + +############################## Hot Parameters ############################## + +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data + +# Model architecture and parameters +MODEL_ARCH = MLP +MODEL_PARAM = { + 'history_seq_len': INPUT_LEN, + 'prediction_seq_len': OUTPUT_LEN, + 'hidden_dim': 64 +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## + +CFG = EasyDict() + +# General settings +CFG.DESCRIPTION = 'An Example Config' # Description of this config, not used in the BasicTS, but useful for the user to remember the purpose of this config +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) + +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner # Runner class + +############################## Environment Configuration ############################## + +CFG.ENV = EasyDict() # Environment settings. Default: None + +# GPU and random seed settings +CFG.ENV.TF32 = False # Whether to use TensorFloat-32 in GPU. Default: False. See https://pytorch.org/docs/stable/notes/cuda.html#tf32-on-ampere. +CFG.ENV.SEED = 42 # Random seed. Default: 42 +CFG.ENV.DETERMINISTIC = False # Whether to set the random seed to get deterministic results. Default: True +CFG.ENV.CUDNN = EasyDict() +CFG.ENV.CUDNN.ENABLED = True # Whether to enable cuDNN. Default: False +CFG.ENV.CUDNN.BENCHMARK = True# Whether to enable cuDNN benchmark. Default: False +CFG.ENV.CUDNN.DETERMINISTIC = False # Whether to set cuDNN to deterministic mode. Default: True + +############################## Dataset Configuration ############################## + +CFG.DATASET = EasyDict() # Dataset settings. Default: None. If not specified, get the training, validation, and test datasets from CFG.[TRAIN, VAL, TEST].DATA.DATASET. + +# Dataset settings +CFG.DATASET.NAME = DATA_NAME # Name of the dataset, used for saving checkpoints and setting the process title. +CFG.DATASET.TYPE = TimeSeriesForecastingDataset # Dataset class use in both training, validation, and test. +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) # Parameters for the dataset class + +############################## Scaler Configuration ############################## + +CFG.SCALER = EasyDict() # Scaler settings. Default: None. If not specified, the data will not be normalized, i.e., the data will be used directly for training, validation, and test. + +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) # Parameters for the scaler class + +############################## Model Configuration ############################## + +CFG.MODEL = EasyDict() # Model settings, must be specified. + +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ # Model name, must be specified, used for saving checkpoints and set the process title. +CFG.MODEL.ARCH = MODEL_ARCH # Model architecture, must be specified. +CFG.MODEL.PARAM = MODEL_PARAM # Model parameters +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] # Features used as input. The size of input data `history_data` is usually [B, L, N, C], this parameter specifies the index of the last dimension, i.e., history_data[:, :, :, CFG.MODEL.FORWARD_FEATURES]. +CFG.MODEL.TARGET_FEATURES = [0] # Features used as output. The size of target data `future_data` is usually [B, L, N, C], this parameter specifies the index of the last dimension, i.e., future_data[:, :, :, CFG.MODEL.TARGET_FEATURES]. +CFG.MODEL.SETUP_GRAPH = False # Whether to set up the computation graph. Default: False. Implementation of many works (e.g., DCRNN, GTS) acts like TensorFlow, which creates parameters in the first feedforward process. +CFG.MODEL.DDP_FIND_UNUSED_PARAMETERS = False # Controls the `find_unused_parameters parameter` of `torch.nn.parallel.DistributedDataParallel`. In distributed computing, if there are unused parameters in the forward process, PyTorch usually raises a RuntimeError. In such cases, this parameter should be set to True. + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() # Metrics settings. Default: None. If not specified, the default metrics will be used. + +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) # Metrics functions, default: MAE, MSE, RMSE, MAPE, WAPE +CFG.METRICS.TARGET = 'MAE' # Target metric, used for saving best checkpoints. +CFG.METRICS.BEST = 'min' # Best metric, used for saving best checkpoints. 'min' or 'max'. Default: 'min'. If 'max', the larger the metric, the better. +CFG.METRICS.NULL_VAL = NULL_VAL # Null value for the metric. Default: np.nan + +############################## Training Configuration ############################## + +CFG.TRAIN = EasyDict() # Training settings, must be specified for training. + +# Training parameters +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) # Directory to save checkpoints. Default: 'checkpoints/{MODEL_NAME}/{DATASET_NAME}_{NUM_EPOCHS}_{INPUT_LEN}_{OUTPUT_LEN}' +CFG.TRAIN.CKPT_SAVE_STRATEGY = None # Checkpoint save strategy. `CFG.TRAIN.CKPT_SAVE_STRATEGY` should be None, an int value, a list or a tuple. None: remove last checkpoint file every epoch. Default: None. Int: save checkpoint every `CFG.TRAIN.CKPT_SAVE_STRATEGY` epoch. List or Tuple: save checkpoint when epoch in `CFG.TRAIN.CKPT_SAVE_STRATEGY, remove last checkpoint file when last_epoch not in ckpt_save_strategy +CFG.TRAIN.FINETUNE_FROM = None # Checkpoint path for fine-tuning. Default: None. If not specified, the model will be trained from scratch. +CFG.TRAIN.STRICT_LOAD = True # Whether to strictly load the checkpoint. Default: True. + +# Loss function +CFG.TRAIN.LOSS = masked_mae # Loss function, must be specified for training. + +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() # Optimizer settings, must be specified for training. +CFG.TRAIN.OPTIM.TYPE = 'Adam' # Optimizer type, must be specified for training. +CFG.TRAIN.OPTIM.PARAM = { + 'lr': 0.002, + 'weight_decay': 0.0001, + } # Optimizer parameters + +# Learning rate scheduler settings +CFG.TRAIN.LR_SCHEDULER = EasyDict() # Learning rate scheduler settings. Default: None. If not specified, the learning rate will not be adjusted during training. +CFG.TRAIN.LR_SCHEDULER.TYPE = 'MultiStepLR' # Learning rate scheduler type. +CFG.TRAIN.LR_SCHEDULER.PARAM = { + 'milestones': [1, 50, 80], + 'gamma': 0.5 + } # Learning rate scheduler parameters + +# Early stopping +CFG.TRAIN.EARLY_STOPPING_PATIENCE = None # Early stopping patience. Default: None. If not specified, the early stopping will not be used. + +# gradient clip settings +CFG.TRAIN.CLIP_GRAD_PARAM = None # Gradient clipping parameters. Default: None. If not specified, the gradient clipping will not be used. + +# Curriculum learning settings +CFG.TRAIN.CL = EasyDict() # Curriculum learning settings. Default: None. If not specified, the curriculum learning will not be used. +CFG.TRAIN.CL.CL_EPOCHS = 1 # Number of epochs for each curriculum learning stage, must be specified if CFG.TRAIN.CL is specified. +CFG.TRAIN.CL.WARM_EPOCHS = 0 # Number of warm-up epochs. Default: 0 +CFG.TRAIN.CL.PREDICTION_LENGTH = OUTPUT_LEN # Total prediction length, must be specified if CFG.TRAIN.CL is specified. +CFG.TRAIN.CL.STEP_SIZE = 1 # Step size for the curriculum learning. Default: 1. The current prediction length will be increased by CFG.TRAIN.CL.STEP_SIZE in each stage. + +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() # Training dataloader settings, must be specified for training. +CFG.TRAIN.DATA.PREFETCH = False # Whether to use dataloader with prefetch. See https://github.com/justheuristic/prefetch_generator. Default: False. +CFG.TRAIN.DATA.BATCH_SIZE = 64 # Batch size for training. Default: 1 +CFG.TRAIN.DATA.SHUFFLE = True # Whether to shuffle the training data. Default: False +CFG.TRAIN.DATA.COLLATE_FN = None # Collate function for the training dataloader. Default: None +CFG.TRAIN.DATA.NUM_WORKERS = 0 # Number of workers for the training dataloader. Default: 0 +CFG.TRAIN.DATA.PIN_MEMORY = False # Whether to pin memory for the training dataloader. Default: False + +############################## Validation Configuration ############################## + +CFG.VAL = EasyDict() + +# Validation parameters +CFG.VAL.INTERVAL = 1 # Conduct validation every `CFG.VAL.INTERVAL` epochs. Default: 1 +CFG.VAL.DATA = EasyDict() # See CFG.TRAIN.DATA +CFG.VAL.DATA.PREFETCH = False +CFG.VAL.DATA.BATCH_SIZE = 64 +CFG.VAL.DATA.SHUFFLE = False +CFG.VAL.DATA.COLLATE_FN = None +CFG.VAL.DATA.NUM_WORKERS = 0 +CFG.VAL.DATA.PIN_MEMORY = False + +############################## Test Configuration ############################## + +CFG.TEST = EasyDict() + +# Test parameters +CFG.TEST.INTERVAL = 1 # Conduct test every `CFG.TEST.INTERVAL` epochs. Default: 1 +CFG.TEST.DATA = EasyDict() # See CFG.TRAIN.DATA +CFG.VAL.DATA.PREFETCH = False +CFG.TEST.DATA.BATCH_SIZE = 64 +CFG.TEST.DATA.SHUFFLE = False +CFG.TEST.DATA.COLLATE_FN = None +CFG.TEST.DATA.NUM_WORKERS = 0 +CFG.TEST.DATA.PIN_MEMORY = False + +############################## Evaluation Configuration ############################## + +CFG.EVAL = EasyDict() + +# Evaluation parameters +CFG.EVAL.HORIZONS = [3, 6, 12] # Prediction horizons for evaluation. Default: [] +CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True diff --git a/examples/regular_config.py b/examples/regular_config.py new file mode 100644 index 00000000..6557544e --- /dev/null +++ b/examples/regular_config.py @@ -0,0 +1,116 @@ +import os +import sys +from easydict import EasyDict +sys.path.append(os.path.abspath(__file__ + '/../../..')) + +from basicts.metrics import masked_mae, masked_mape, masked_rmse +from basicts.data import TimeSeriesForecastingDataset +from basicts.runners import SimpleTimeSeriesForecastingRunner +from basicts.scaler import ZScoreScaler +from basicts.utils import get_regular_settings +from .arch import MultiLayerPerceptron as MLP + +############################## Hot Parameters ############################## +# Dataset & Metrics configuration +DATA_NAME = 'PEMS08' # Dataset name +regular_settings = get_regular_settings(DATA_NAME) +INPUT_LEN = regular_settings['INPUT_LEN'] # Length of input sequence +OUTPUT_LEN = regular_settings['OUTPUT_LEN'] # Length of output sequence +TRAIN_VAL_TEST_RATIO = regular_settings['TRAIN_VAL_TEST_RATIO'] # Train/Validation/Test split ratios +NORM_EACH_CHANNEL = regular_settings['NORM_EACH_CHANNEL'] # Whether to normalize each channel of the data +RESCALE = regular_settings['RESCALE'] # Whether to rescale the data +NULL_VAL = regular_settings['NULL_VAL'] # Null value in the data +# Model architecture and parameters +MODEL_ARCH = MLP +MODEL_PARAM = { + 'history_seq_len': INPUT_LEN, + 'prediction_seq_len': OUTPUT_LEN, + 'hidden_dim': 64 +} +NUM_EPOCHS = 100 + +############################## General Configuration ############################## +CFG = EasyDict() +# General settings +CFG.DESCRIPTION = 'An Example Config' +CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode) +# Runner +CFG.RUNNER = SimpleTimeSeriesForecastingRunner + +############################## Dataset Configuration ############################## +CFG.DATASET = EasyDict() +# Dataset settings +CFG.DATASET.NAME = DATA_NAME +CFG.DATASET.TYPE = TimeSeriesForecastingDataset +CFG.DATASET.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_val_test_ratio': TRAIN_VAL_TEST_RATIO, + 'input_len': INPUT_LEN, + 'output_len': OUTPUT_LEN, + # 'mode' is automatically set by the runner +}) + +############################## Scaler Configuration ############################## +CFG.SCALER = EasyDict() +# Scaler settings +CFG.SCALER.TYPE = ZScoreScaler # Scaler class +CFG.SCALER.PARAM = EasyDict({ + 'dataset_name': DATA_NAME, + 'train_ratio': TRAIN_VAL_TEST_RATIO[0], + 'norm_each_channel': NORM_EACH_CHANNEL, + 'rescale': RESCALE, +}) + +############################## Model Configuration ############################## +CFG.MODEL = EasyDict() +# Model settings +CFG.MODEL.NAME = MODEL_ARCH.__name__ +CFG.MODEL.ARCH = MODEL_ARCH +CFG.MODEL.PARAM = MODEL_PARAM +CFG.MODEL.FORWARD_FEATURES = [0, 1, 2] +CFG.MODEL.TARGET_FEATURES = [0] + +############################## Metrics Configuration ############################## + +CFG.METRICS = EasyDict() +# Metrics settings +CFG.METRICS.FUNCS = EasyDict({ + 'MAE': masked_mae, + 'MAPE': masked_mape, + 'RMSE': masked_rmse, + }) +CFG.METRICS.TARGET = 'MAE' +CFG.METRICS.NULL_VAL = NULL_VAL + +############################## Training Configuration ############################## +CFG.TRAIN = EasyDict() +CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS +CFG.TRAIN.CKPT_SAVE_DIR = os.path.join( + 'checkpoints', + MODEL_ARCH.__name__, + '_'.join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS), str(INPUT_LEN), str(OUTPUT_LEN)]) +) +CFG.TRAIN.LOSS = masked_mae +# Optimizer settings +CFG.TRAIN.OPTIM = EasyDict() +CFG.TRAIN.OPTIM.TYPE = 'Adam' +CFG.TRAIN.OPTIM.PARAM = { + 'lr': 0.002, + 'weight_decay': 0.0001, + } +# Train data loader settings +CFG.TRAIN.DATA = EasyDict() +CFG.TRAIN.DATA.BATCH_SIZE = 64 +CFG.TRAIN.DATA.SHUFFLE = True + +############################## Validation Configuration ############################## +CFG.VAL = EasyDict() +CFG.VAL.INTERVAL = 1 +CFG.VAL.DATA = EasyDict() +CFG.VAL.DATA.BATCH_SIZE = 64 + +############################## Test Configuration ############################## +CFG.TEST = EasyDict() +CFG.TEST.INTERVAL = 1 +CFG.TEST.DATA = EasyDict() +CFG.TEST.DATA.BATCH_SIZE = 64 diff --git a/experiments/evaluate.py b/experiments/evaluate.py new file mode 100644 index 00000000..612155c6 --- /dev/null +++ b/experiments/evaluate.py @@ -0,0 +1,23 @@ +import os +import sys +import time +sys.path.append(os.path.abspath(__file__ + '/../..')) +from argparse import ArgumentParser + +import basicts + +def parse_args(): + parser = ArgumentParser(description="Evaluate time series forecasting model in BasicTS framework!") + parser.add_argument("-cfg", "--config", default="examples/complete_config.py", help="training config") + parser.add_argument("-ckpt", "--checkpoint", default="") + parser.add_argument("-g", "--gpus", default="0") + parser.add_argument("-d", "--device_type", default="gpu") + parser.add_argument("-b", "--batch_size", default=None) + + return parser.parse_args() + +if __name__ == '__main__': + + args = parse_args() + + basicts.launch_evaluation(cfg=args.config, ckpt_path=args.checkpoint, device_type=args.device_type, gpus=args.gpus, batch_size=args.batch_size) diff --git a/experiments/inference.py b/experiments/inference.py deleted file mode 100644 index 2ee8a2ab..00000000 --- a/experiments/inference.py +++ /dev/null @@ -1,44 +0,0 @@ -import os -import sys -import time -sys.path.append(os.path.abspath(__file__ + '/../..')) -from argparse import ArgumentParser - -from basicts import launch_runner, BaseRunner - - -def inference(cfg: dict, runner: BaseRunner, ckpt: str = None, batch_size: int = 1): - # init logger - runner.init_logger(logger_name='easytorch-inference', log_file_name='validate_result') - # init model - cfg.TEST.DATA.BATCH_SIZE = batch_size - runner.model.eval() - runner.setup_graph(cfg=cfg, train=False) - # load model checkpoint - runner.load_model(ckpt_path=ckpt) - # inference & speed - t0 = time.perf_counter() - runner.test_pipline(cfg) - elapsed = time.perf_counter() - t0 - - print('##############################') - runner.logger.info('%s: %0.8fs' % ('Speed', elapsed)) - runner.logger.info('# Param: {0}'.format(sum(p.numel() for p in runner.model.parameters() if p.requires_grad))) - -if __name__ == '__main__': - MODEL_NAME = 'AGCRN' - DATASET_NAME = 'PEMS03' - BATCH_SIZE = 32 - GPUS = '2' - - parser = ArgumentParser(description='Welcome to EasyTorch!') - parser.add_argument('-m', '--model', default=MODEL_NAME, help='model name') - parser.add_argument('-d', '--dataset', default=DATASET_NAME, help='dataset name') - parser.add_argument('-g', '--gpus', default=GPUS, help='visible gpus') - parser.add_argument('-b', '--batch_size', default=BATCH_SIZE, type=int, help='batch size') - args = parser.parse_args() - - cfg_path = 'baselines/{0}/{1}.py'.format(args.model, args.dataset) - ckpt_path = 'ckpt/{0}/{1}/{0}_best_val_MAE.pt'.format(args.model, args.dataset) - - launch_runner(cfg_path, inference, (ckpt_path, args.batch_size), devices=args.gpus) diff --git a/experiments/run_m4.py b/experiments/run_m4.py deleted file mode 100644 index 5d238a79..00000000 --- a/experiments/run_m4.py +++ /dev/null @@ -1,54 +0,0 @@ -# 1. 给定方法,例如MLP; 给定是否保存结果,给定是否保留预测结果; -# 2. 检查是否存在配置文件生成器,例如baselines/MLP/M4_base.py -# 3. 循环获取CFG,进行训练并保存结果 -# 4. M4 Summary -# 5. 保存结果、删除预测结果 - -import os -import sys -import importlib -from argparse import ArgumentParser -# TODO: remove it when basicts can be installed by pip -project_dir = os.path.abspath(__file__ + "/../..") -sys.path.append(project_dir) -import torch -from basicts import launch_training -from basicts.utils import m4_summary -from easytorch.utils.logging import logger_initialized -from basicts.utils.logging import clear_loggers - -torch.set_num_threads(3) # aviod high cpu avg usage - - -def parse_args(): - parser = ArgumentParser(description="Run time series forecasting model in BasicTS framework!") - # parser.add_argument("-c", "--config", default="baselines/STID_M4/M4.py", help="training config template") - parser.add_argument("-c", "--config", default="baselines/MLP/M4.py", help="training config template") - parser.add_argument("-g", "--gpus", default="3", help="visible gpus") - parser.add_argument("--save_evaluation", default=True, help="if save evaluation results") - parser.add_argument("--save_prediction", default=False, help="if save prediction results") - return parser.parse_args() - -if __name__ == "__main__": - args = parse_args() - cfg_generator_file = args.config[:-3].replace("/", ".") - - # training - get_cfg = importlib.import_module(cfg_generator_file, package=project_dir).get_cfg - seasonal_patterns = ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] - for seasonal_pattern in seasonal_patterns: - cfg = get_cfg(seasonal_pattern) - launch_training(cfg, args.gpus) - clear_loggers() - - # evaluating - save_dir = os.path.abspath(args.config + "/..") - result = m4_summary(save_dir, project_dir) # pd.DataFrame - - # save results - if not args.save_prediction: os.system("rm -rf {0}/M4_*.npy".format(save_dir)) - if args.save_evaluation: result.to_csv("{0}/M4_summary.csv".format(save_dir), index=False) - else: os.system("rm {0}/M4_summary.csv".format(save_dir)) - - # print results - print(result) diff --git a/experiments/train.py b/experiments/train.py index 0ef9f399..1380f038 100644 --- a/experiments/train.py +++ b/experiments/train.py @@ -8,18 +8,22 @@ # TODO: remove it when basicts can be installed by pip sys.path.append(os.path.abspath(__file__ + "/../..")) import torch -from basicts import launch_training +import basicts -torch.set_num_threads(3) # aviod high cpu avg usage +torch.set_num_threads(4) # aviod high cpu avg usage def parse_args(): parser = ArgumentParser(description="Run time series forecasting model in BasicTS framework!") - parser.add_argument("-c", "--cfg", default="baselines/STID/METR-LA.py", help="training config") + # parser.add_argument("-c", "--cfg", default="baselines/STID/METR-LA.py", help="training config") + parser.add_argument("-c", "--cfg", default="baselines/STEP/STEP_METR-LA2.py", help="training config") + # parser.add_argument("-c", "--cfg", default="baselines/DGCRN/PEMS-BAY.py", help="training config") + # parser.add_argument("-c", "--cfg", default="baselines/DGCRN/example.py", help="training config") + # parser.add_argument("-c", "--cfg", default="examples/complete_config.py", help="training config") parser.add_argument("-g", "--gpus", default="0", help="visible gpus") return parser.parse_args() if __name__ == "__main__": args = parse_args() - launch_training(args.cfg, args.gpus) + basicts.launch_training(args.cfg, args.gpus, node_rank=0) diff --git a/requirements.txt b/requirements.txt index 4ad5ae8b..e5c85a27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -easy-torch==1.3.2 -easydict==1.10 -pandas==1.3.5 -packaging==23.1 +easy-torch +easydict +packaging +setproctitle +pandas +scikit-learn +tables +sympy setuptools==59.5.0 -scipy==1.7.3 -tables==3.7.0 -sympy==1.10.1 -setproctitle==1.3.2 -scikit-learn==1.0.2 +numpy==1.24.4 diff --git a/scripts/data_preparation/BeijingAirQuality/generate_training_data.py b/scripts/data_preparation/BeijingAirQuality/generate_training_data.py index a74119af..016cc964 100644 --- a/scripts/data_preparation/BeijingAirQuality/generate_training_data.py +++ b/scripts/data_preparation/BeijingAirQuality/generate_training_data.py @@ -1,158 +1,103 @@ import os -import sys -import pickle -import argparse +import json import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'BeijingAirQuality' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.xlsx' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +steps_per_day = 24 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'Beijing air quality' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_excel(data_file_path) data = df.values colums = df.columns data = np.expand_dims(df.values, axis=-1) data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - print("columns: {0}".format(colums)) + print(f'Raw time series shape: {data.shape}') + print('Columns: {0}'.format(colums)) + return data - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) +def add_temporal_features(data): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] if add_time_of_day: - # numerical time_of_day - tod = [i % steps_per_day / - steps_per_day for i in range(data_norm.shape[0])] - tod = np.array(tod) - tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(tod_tiled) + time_of_day = np.array([i % steps_per_day / steps_per_day for i in range(l)]) + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) if add_day_of_week: - # numerical day_of_week - dow = [(i // steps_per_day) % 7 / 7 for i in range(data_norm.shape[0])] - dow = np.array(dow) - dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dow_tiled) - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 96 - FUTURE_SEQ_LEN = 336 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 24 # every 1 hour - - DATASET_NAME = "BeijingAirQuality" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.xlsx".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + day_of_week = np.array([(i // steps_per_day) % 7 / 7 for i in range(l)]) + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/CA/generate_training_data.py b/scripts/data_preparation/CA/generate_training_data.py index bdd33a10..d621c9fb 100644 --- a/scripts/data_preparation/CA/generate_training_data.py +++ b/scripts/data_preparation/CA/generate_training_data.py @@ -1,179 +1,136 @@ import os -import sys -import shutil +import json import pickle -import argparse +import shutil import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - -# Dataset Description: -# LargeST: A Benchmark Dataset for Large-Scale Traffic Forecasting. - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - graph_file_path = args.graph_file_path - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'CA' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.h5' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.npy' +meta_file_path = f'datasets/raw_data/{dataset_name}/meta_{dataset_name}.csv' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = False # Add day of the month as a feature +add_day_of_year = False # Add day of the year as a feature +steps_per_day = 96 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic flow' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_hdf(data_file_path) data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + _, n, _ = data.shape + feature_list = [data] + if add_time_of_day: - # numerical time_of_day - tod = ( - df.index.values - df.index.values.astype("datetime64[D]")) / np.timedelta64(1, "D") - tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(tod_tiled) + time_of_day = (df.index.values - df.index.values.astype('datetime64[D]')) / np.timedelta64(1, 'D') + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) if add_day_of_week: - # numerical day_of_week - dow = df.index.dayofweek / 7 - dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dow_tiled) + day_of_week = df.index.dayofweek / 7 + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) if add_day_of_month: # numerical day_of_month - dom = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. - dom_tiled = np.tile(dom, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dom_tiled) + day_of_month = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + day_of_month_tiled = np.tile(day_of_month, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_month_tiled) if add_day_of_year: # numerical day_of_year - doy = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. - doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(doy_tiled) - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - # copy adj + day_of_year = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. + day_of_year_tiled = np.tile(day_of_year, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_year_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') adj_mx = np.load(graph_file_path) - with open(output_dir + "/adj_mx.pkl", "wb") as f: + with open(output_dir + '/adj_mx.pkl', 'wb') as f: pickle.dump(adj_mx, f) - # copy adj meta data - shutil.copyfile(graph_file_path, output_dir + "/adj_meta.csv") - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - - DATASET_NAME = "CA" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.h5".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.npy".format(DATASET_NAME) - GRAPH_METE_PATH = "datasets/raw_data/{0}/meta_{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_meta_data(): + '''Save the meta data to the output directory''' + output_meta_data_path = os.path.join(output_dir, 'meta.csv') + shutil.copyfile(meta_file_path, output_meta_data_path) + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Copy and save adjacency matrix + save_graph() + + # Copy and save meta data + save_meta_data() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/ETTh1/generate_training_data.py b/scripts/data_preparation/ETTh1/generate_training_data.py index 889b3aad..8991825a 100644 --- a/scripts/data_preparation/ETTh1/generate_training_data.py +++ b/scripts/data_preparation/ETTh1/generate_training_data.py @@ -1,81 +1,53 @@ import os -import sys -import pickle -import argparse +import json import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'ETTh1' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 24 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'electricity transformer temperature' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_csv(data_file_path) - # Following many previous works (e.g., Informer, Autoformer), we use the first 20 months of data, i.e., the first 14400 rows. df = df.iloc[:20*30*24] - df_index = pd.to_datetime(df["date"].values, format="%Y-%m-%d %H:%M").to_numpy() + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() df = df[df.columns[1:]] df.index = df_index - data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - # Following related works (e.g. informer and autoformer), we normalize each channel separately. - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: # numerical time_of_day - tod = [i % steps_per_day / steps_per_day for i in range(data_norm.shape[0])] + tod = [i % steps_per_day / steps_per_day for i in range(l)] tod = np.array(tod) tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(tod_tiled) @@ -98,78 +70,52 @@ def generate_data(args: argparse.Namespace): doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(doy_tiled) - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 96 - FUTURE_SEQ_LEN = 336 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 24 # every 1 hour - - DATASET_NAME = "ETTh1" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings, + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/ETTh2/generate_training_data.py b/scripts/data_preparation/ETTh2/generate_training_data.py index 320fac22..0034997a 100644 --- a/scripts/data_preparation/ETTh2/generate_training_data.py +++ b/scripts/data_preparation/ETTh2/generate_training_data.py @@ -1,68 +1,121 @@ import os -import sys -import argparse - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from scripts.data_preparation.ETTh1.generate_training_data import generate_data - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 336 - FUTURE_SEQ_LEN = 336 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 24 # every 1 hour - - DATASET_NAME = "ETTh2" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) +import json + +import numpy as np +import pandas as pd + +# Hyperparameters +dataset_name = 'ETTh2' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 24 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'electricity transformer temperature' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + df = pd.read_csv(data_file_path) + df = df.iloc[:20*30*24] + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() + df = df[df.columns[1:]] + df.index = df_index + data = np.expand_dims(df.values, axis=-1) + data = data[..., target_channel] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + + if add_time_of_day: + # numerical time_of_day + tod = [i % steps_per_day / steps_per_day for i in range(l)] + tod = np.array(tod) + tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(tod_tiled) + + if add_day_of_week: + # numerical day_of_week + dow = df.index.dayofweek / 7 + dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(dow_tiled) + + if add_day_of_month: + # numerical day_of_month + dom = (df.index.day - 1) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + dom_tiled = np.tile(dom, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(dom_tiled) + + if add_day_of_year: + # numerical day_of_year + doy = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. + doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(doy_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/ETTm1/generate_training_data.py b/scripts/data_preparation/ETTm1/generate_training_data.py index 7bc766e0..73c6d11b 100644 --- a/scripts/data_preparation/ETTm1/generate_training_data.py +++ b/scripts/data_preparation/ETTm1/generate_training_data.py @@ -1,81 +1,53 @@ import os -import sys -import pickle -import argparse +import json import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'ETTm1' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 96 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'electricity transformer temperature' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_csv(data_file_path) - # Following many previous works (e.g., Informer, Autoformer), we use the first 20 months of data, i.e., the first 14400 rows. df = df.iloc[:20*30*24*4] - df_index = pd.to_datetime(df["date"].values, format="%Y-%m-%d %H:%M").to_numpy() + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() df = df[df.columns[1:]] df.index = df_index - data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - # Following related works (e.g. informer and autoformer), we normalize each channel separately. - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: # numerical time_of_day - tod = [i % steps_per_day / steps_per_day for i in range(data_norm.shape[0])] + tod = [i % steps_per_day / steps_per_day for i in range(l)] tod = np.array(tod) tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(tod_tiled) @@ -98,77 +70,52 @@ def generate_data(args: argparse.Namespace): doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(doy_tiled) - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 96 - FUTURE_SEQ_LEN = 336 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 24 * 4 # every 15 minutes - - DATASET_NAME = "ETTm1" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/ETTm2/generate_training_data.py b/scripts/data_preparation/ETTm2/generate_training_data.py index 46c2e65a..e78905a2 100644 --- a/scripts/data_preparation/ETTm2/generate_training_data.py +++ b/scripts/data_preparation/ETTm2/generate_training_data.py @@ -1,68 +1,121 @@ import os -import sys -import argparse - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from scripts.data_preparation.ETTm1.generate_training_data import generate_data - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 1680 - FUTURE_SEQ_LEN = 336 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 24 * 4 # every 15 minutes - - DATASET_NAME = "ETTm2" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) \ No newline at end of file +import json + +import numpy as np +import pandas as pd + +# Hyperparameters +dataset_name = 'ETTm2' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 96 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'electricity transformer temperature' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + df = pd.read_csv(data_file_path) + df = df.iloc[:20*30*24*4] + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() + df = df[df.columns[1:]] + df.index = df_index + data = np.expand_dims(df.values, axis=-1) + data = data[..., target_channel] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + + if add_time_of_day: + # numerical time_of_day + tod = [i % steps_per_day / steps_per_day for i in range(l)] + tod = np.array(tod) + tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(tod_tiled) + + if add_day_of_week: + # numerical day_of_week + dow = df.index.dayofweek / 7 + dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(dow_tiled) + + if add_day_of_month: + # numerical day_of_month + dom = (df.index.day - 1) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + dom_tiled = np.tile(dom, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(dom_tiled) + + if add_day_of_year: + # numerical day_of_year + doy = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. + doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(doy_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/Electricity/generate_training_data.py b/scripts/data_preparation/Electricity/generate_training_data.py index c1920cb5..464498f5 100644 --- a/scripts/data_preparation/Electricity/generate_training_data.py +++ b/scripts/data_preparation/Electricity/generate_training_data.py @@ -1,79 +1,52 @@ import os -import sys -import pickle -import argparse +import json import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'Electricity' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 24 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'electricity consumption' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_csv(data_file_path) - df_index = pd.to_datetime(df["date"].values, format="%Y-%m-%d %H:%M").to_numpy() + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() df = df[df.columns[1:]] df.index = df_index - data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - # Following related works (e.g. informer and autoformer), we normalize each channel separately. - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: # numerical time_of_day - tod = [i % steps_per_day / steps_per_day for i in range(data_norm.shape[0])] + tod = [i % steps_per_day / steps_per_day for i in range(l)] tod = np.array(tod) tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(tod_tiled) @@ -86,7 +59,7 @@ def generate_data(args: argparse.Namespace): if add_day_of_month: # numerical day_of_month - dom = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + dom = (df.index.day - 1) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. dom_tiled = np.tile(dom, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(dom_tiled) @@ -96,78 +69,52 @@ def generate_data(args: argparse.Namespace): doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(doy_tiled) - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 168 - FUTURE_SEQ_LEN = 96 - - TRAIN_RATIO = 0.7 - VALID_RATIO = 0.1 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 24 # every 1 hour - - DATASET_NAME = "Electricity" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/ExchangeRate/generate_training_data.py b/scripts/data_preparation/ExchangeRate/generate_training_data.py index a24ca9ad..2cf9ccf5 100644 --- a/scripts/data_preparation/ExchangeRate/generate_training_data.py +++ b/scripts/data_preparation/ExchangeRate/generate_training_data.py @@ -1,79 +1,52 @@ import os -import sys -import pickle -import argparse +import json import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'ExchangeRate' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 1 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'exchange rate' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_csv(data_file_path) - df_index = pd.to_datetime(df["date"].values, format="%Y-%m-%d %H:%M").to_numpy() + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() df = df[df.columns[1:]] df.index = df_index - data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - # Following related works (e.g. informer and autoformer), we normalize each channel separately. - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: # numerical time_of_day - tod = [i % steps_per_day / steps_per_day for i in range(data_norm.shape[0])] + tod = [i % steps_per_day / steps_per_day for i in range(l)] tod = np.array(tod) tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(tod_tiled) @@ -96,78 +69,52 @@ def generate_data(args: argparse.Namespace): doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(doy_tiled) - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 96 - FUTURE_SEQ_LEN = 336 - - TRAIN_RATIO = 0.7 - VALID_RATIO = 0.1 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 1 # every 24 hour - - DATASET_NAME = "ExchangeRate" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) \ No newline at end of file + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/GBA/generate_training_data.py b/scripts/data_preparation/GBA/generate_training_data.py index d52c2ba6..0b8e83a7 100644 --- a/scripts/data_preparation/GBA/generate_training_data.py +++ b/scripts/data_preparation/GBA/generate_training_data.py @@ -1,73 +1,136 @@ import os -import sys -import argparse - - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from scripts.data_preparation.CA.generate_training_data import generate_data - -# Dataset Description: -# LargeST: A Benchmark Dataset for Large-Scale Traffic Forecasting. - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - - DATASET_NAME = "GBA" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.h5".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.npy".format(DATASET_NAME) - GRAPH_METE_PATH = "datasets/raw_data/{0}/meta_{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) +import json +import pickle +import shutil + +import numpy as np +import pandas as pd + +# Hyperparameters +dataset_name = 'GBA' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.h5' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.npy' +meta_file_path = f'datasets/raw_data/{dataset_name}/meta_{dataset_name}.csv' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = False # Add day of the month as a feature +add_day_of_year = False # Add day of the year as a feature +steps_per_day = 96 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic flow' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + df = pd.read_hdf(data_file_path) + data = np.expand_dims(df.values, axis=-1) + data = data[..., target_channel] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + _, n, _ = data.shape + feature_list = [data] + + if add_time_of_day: + time_of_day = (df.index.values - df.index.values.astype('datetime64[D]')) / np.timedelta64(1, 'D') + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) + + if add_day_of_week: + day_of_week = df.index.dayofweek / 7 + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) + + if add_day_of_month: + # numerical day_of_month + day_of_month = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + day_of_month_tiled = np.tile(day_of_month, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_month_tiled) + + if add_day_of_year: + # numerical day_of_year + day_of_year = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. + day_of_year_tiled = np.tile(day_of_year, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_year_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + adj_mx = np.load(graph_file_path) + with open(output_dir + '/adj_mx.pkl', 'wb') as f: + pickle.dump(adj_mx, f) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_meta_data(): + '''Save the meta data to the output directory''' + output_meta_data_path = os.path.join(output_dir, 'meta.csv') + shutil.copyfile(meta_file_path, output_meta_data_path) + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Copy and save adjacency matrix + save_graph() + + # Copy and save meta data + save_meta_data() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/GLA/generate_training_data.py b/scripts/data_preparation/GLA/generate_training_data.py index 9128316c..3cdb21d1 100644 --- a/scripts/data_preparation/GLA/generate_training_data.py +++ b/scripts/data_preparation/GLA/generate_training_data.py @@ -1,73 +1,136 @@ import os -import sys -import argparse - - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from scripts.data_preparation.CA.generate_training_data import generate_data - -# Dataset Description: -# LargeST: A Benchmark Dataset for Large-Scale Traffic Forecasting. - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - - DATASET_NAME = "GLA" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.h5".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.npy".format(DATASET_NAME) - GRAPH_METE_PATH = "datasets/raw_data/{0}/meta_{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) +import json +import pickle +import shutil + +import numpy as np +import pandas as pd + +# Hyperparameters +dataset_name = 'GLA' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.h5' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.npy' +meta_file_path = f'datasets/raw_data/{dataset_name}/meta_{dataset_name}.csv' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = False # Add day of the month as a feature +add_day_of_year = False # Add day of the year as a feature +steps_per_day = 96 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic flow' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + df = pd.read_hdf(data_file_path) + data = np.expand_dims(df.values, axis=-1) + data = data[..., target_channel] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + _, n, _ = data.shape + feature_list = [data] + + if add_time_of_day: + time_of_day = (df.index.values - df.index.values.astype('datetime64[D]')) / np.timedelta64(1, 'D') + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) + + if add_day_of_week: + day_of_week = df.index.dayofweek / 7 + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) + + if add_day_of_month: + # numerical day_of_month + day_of_month = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + day_of_month_tiled = np.tile(day_of_month, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_month_tiled) + + if add_day_of_year: + # numerical day_of_year + day_of_year = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. + day_of_year_tiled = np.tile(day_of_year, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_year_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + adj_mx = np.load(graph_file_path) + with open(output_dir + '/adj_mx.pkl', 'wb') as f: + pickle.dump(adj_mx, f) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_meta_data(): + '''Save the meta data to the output directory''' + output_meta_data_path = os.path.join(output_dir, 'meta.csv') + shutil.copyfile(meta_file_path, output_meta_data_path) + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Copy and save adjacency matrix + save_graph() + + # Copy and save meta data + save_meta_data() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/Gaussian/generate_training_data.py b/scripts/data_preparation/Gaussian/generate_training_data.py index c5174306..946cb6a5 100644 --- a/scripts/data_preparation/Gaussian/generate_training_data.py +++ b/scripts/data_preparation/Gaussian/generate_training_data.py @@ -1,122 +1,74 @@ import os -import sys -import shutil -import pickle -import argparse +import json import numpy as np -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'Gaussian' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.npy' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +frequency = None +domain = 'simulated Gaussian data' +feature_description = [domain] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' data = np.load(data_file_path) data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 96 - FUTURE_SEQ_LEN = 96 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - - DATASET_NAME = "Gaussian" - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.npy".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + print(f'Raw time series shape: {data.shape}') + return data + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data = load_and_preprocess_data() + + # Save processed data + save_data(data) + + # Save dataset description + save_description(data) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/Gaussian/simulate_data.py b/scripts/data_preparation/Gaussian/simulate_data.py index 23996644..a8a2cafc 100644 --- a/scripts/data_preparation/Gaussian/simulate_data.py +++ b/scripts/data_preparation/Gaussian/simulate_data.py @@ -1,24 +1,24 @@ import os -import sys + +import torch import numpy as np -import matplotlib.pyplot as plt -PROJECT_DIR = os.path.abspath(__file__ + "/../../../..") -os.chdir(PROJECT_DIR) +PROJECT_DIR = os.path.abspath(__file__ + '/../../../..') +os.chdir(PROJECT_DIR) -def generate_gaussian_noise_sequence(duration): - time_points = np.arange(0, duration, 1) - gaussion_noise_sequence = np.random.normal(0, 1, duration) - return time_points, gaussion_noise_sequence # hyper parameterts duration = 10000 # time series length +def generate_gaussian_noise_sequence(): + x = np.arange(0, duration, 1) + y = np.random.normal(0, 1, duration) + return x, y + # generate gaussian sequence -time_points, gaussian_noise_sequence = generate_gaussian_noise_sequence(duration) +time_points, gaussian_noise_sequence = generate_gaussian_noise_sequence() # save pulse sequence -import torch data = torch.Tensor(gaussian_noise_sequence).unsqueeze(-1).unsqueeze(-1).numpy() # mkdir datasets/raw_data/Gaussian if not os.path.exists('datasets/raw_data/Gaussian'): diff --git a/scripts/data_preparation/Illness/generate_training_data.py b/scripts/data_preparation/Illness/generate_training_data.py index 925003ac..47feb121 100644 --- a/scripts/data_preparation/Illness/generate_training_data.py +++ b/scripts/data_preparation/Illness/generate_training_data.py @@ -1,81 +1,53 @@ import os -import sys -import pickle -import argparse +import json import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'Illness' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 1/7 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'ilness data' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 96, + 'OUTPUT_LEN': 48, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_csv(data_file_path) - df_index = pd.to_datetime(df["date"].values, format="%Y-%m-%d %H:%M").to_numpy() + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() df = df[df.columns[1:]] df.index = df_index - data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - # Following related works (e.g. informer and autoformer), we normalize each channel separately. - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + _, n, _ = data.shape + feature_list = [data] + if add_time_of_day: # numerical time_of_day - tod = ( - df.index.values - df.index.values.astype("datetime64[D]")) / np.timedelta64(1, "D") + df.index.values - df.index.values.astype('datetime64[D]')) / np.timedelta64(1, 'D') tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(tod_tiled) @@ -87,7 +59,7 @@ def generate_data(args: argparse.Namespace): if add_day_of_month: # numerical day_of_month - dom = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + dom = (df.index.day - 1) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. dom_tiled = np.tile(dom, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(dom_tiled) @@ -97,78 +69,52 @@ def generate_data(args: argparse.Namespace): doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(doy_tiled) - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 168 - FUTURE_SEQ_LEN = 96 - - TRAIN_RATIO = 0.7 - VALID_RATIO = 0.1 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 1 # every 1 hour - - DATASET_NAME = "Illness" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/M4/generate_training_data.py b/scripts/data_preparation/M4/generate_training_data.py deleted file mode 100644 index 4a0b840f..00000000 --- a/scripts/data_preparation/M4/generate_training_data.py +++ /dev/null @@ -1,107 +0,0 @@ -import os -import pickle -import pandas as pd -import numpy as np -from tqdm import tqdm - -project_dir = os.path.abspath(__file__ + "/../../../..") -data_dir = project_dir + "/datasets/raw_data/M4/" - -def generate_subset(seasonal_pattern): - output_dir = project_dir + "/datasets/M4_{0}/".format(seasonal_pattern) - processed_data_train = [] # final data - processed_mask_train = [] - processed_data_test = [] - processed_mask_test = [] - train_index = [] - test_index = [] - scaler = [] - future_seq_len = {"Yearly": 6, "Quarterly": 8, "Monthly": 18, "Weekly": 13, "Daily": 14, "Hourly": 48}[seasonal_pattern] - lookback = 2 - history_seq_len = future_seq_len * lookback - index_lower_bound = int({"Yearly": 1.5, "Quarterly": 1.5, "Monthly": 1.5, "Weekly": 10, "Daily": 10, "Hourly": 10}[seasonal_pattern] * future_seq_len) # generate index from len(train_data) - index_lower_bound - - # read data - train_data = pd.read_csv(data_dir + seasonal_pattern + "-train.csv") - test_data = pd.read_csv(data_dir + seasonal_pattern + "-test.csv") - meta_info = pd.read_csv(data_dir + "M4-info.csv") - - def process_one_series(ts_id, ts_train, ts_info): - """generate data and index for one series. - - Args: - ts_id (int): time series id in each subset. - ts_train (pd.Series): time series data. - info (pd.Series): time series info. - """ - ts_train = ts_train.tolist() - mask_train = [1] * len(ts_train) - - # generate padded data - low = max(1, len(ts_train) - index_lower_bound) - if low - history_seq_len < 0: left_padding = [0] * (history_seq_len - low) - else: left_padding = [] - right_padding = [0] * future_seq_len - ts_train_padded = left_padding + ts_train + right_padding - # generate mask - mask_train_padded = [0] * len(left_padding) + mask_train + [0] * len(right_padding) - # df = generate_dataframe(start_time_padded, seasonal_pattern, ts_train_padded) - df = pd.DataFrame(data={'Values': ts_train_padded}) - df["IDs"] = ts_id - # generate data - new_data = df.values.tolist() # first dimension: values, second dimension: IDs - # generate index - index_list = [] - for t in range(low + len(left_padding), len(ts_train_padded) - future_seq_len): - index_list.append([t - history_seq_len, t, t + future_seq_len]) - # generate scaler - scaler = None - return new_data, mask_train_padded, index_list, scaler - - # generate training data - for ts_id in tqdm(range(train_data.shape[0])): - ts_name = train_data.iloc[ts_id, :]["V1"] - ts_info = meta_info[meta_info["M4id"] == ts_name].iloc[0, :] - ts_train = train_data.iloc[ts_id, :].drop("V1").dropna() - - ts_data, ts_mask, ts_index, ts_scaler = process_one_series(ts_id, ts_train, ts_info) - processed_data_train.append(ts_data) - processed_mask_train.append(ts_mask) - train_index.append(ts_index) - scaler.append(ts_scaler) - - for ts_id in tqdm(range(test_data.shape[0])): - ts_name = test_data.iloc[ts_id, :]["V1"] - ts_info = meta_info[meta_info["M4id"] == ts_name].iloc[0, :] - ts_test = test_data.iloc[ts_id, :].drop("V1").dropna() - ts_train_last_sample_index = train_index[ts_id][-1] - ts_train_last_sample_history = processed_data_train[ts_id][ts_train_last_sample_index[0]+1:ts_train_last_sample_index[1]+1] # last history sample - ts_train_last_sample_future = processed_data_train[ts_id][ts_train_last_sample_index[1]+1:ts_train_last_sample_index[2]+1] # last future sample, should be all zeros - ts_train_last_sample_mask = processed_mask_train[ts_id][ts_train_last_sample_index[0]+1:ts_train_last_sample_index[1]+1] # last history sample mask. there might be some mask in the history data when the history_seq_len is large. - assert sum([_[0] for _ in ts_train_last_sample_future]) == 0 - ts_train_last_sample_future = np.array(ts_train_last_sample_future) - ts_train_last_sample_future[:, 0] = ts_test.tolist() - ts_data = ts_train_last_sample_history + ts_train_last_sample_future.tolist() - processed_data_test.append(ts_data) - processed_mask_test.append(ts_train_last_sample_mask + [1] * len(ts_test)) - test_index.append([[0, len(ts_train_last_sample_history), len(ts_train_last_sample_history) + len(ts_test)]]) - assert ts_test.shape[0] == future_seq_len, "test data length should be equal to future_seq_len" - - # create output dir if not exists - if not os.path.exists(output_dir): - os.makedirs(output_dir) - ## save data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_None.pkl".format(history_seq_len, future_seq_len), "wb") as f: - pickle.dump({"train": processed_data_train, "test": processed_data_test}, f) - ## save mask - with open(output_dir + "/mask_in_{0}_out_{1}_rescale_None.pkl".format(history_seq_len, future_seq_len), "wb") as f: - pickle.dump({"train": processed_mask_train, "test": processed_mask_test}, f) - ## save index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_None.pkl".format(history_seq_len, future_seq_len), "wb") as f: - pickle.dump({"train": train_index, "test": test_index}, f) - -if __name__ == "__main__": - seasonal_patterns = ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily", "Hourly"] - for seasonal_pattern in seasonal_patterns: - print(seasonal_pattern) - generate_subset(seasonal_pattern) diff --git a/scripts/data_preparation/METR-LA/generate_training_data.py b/scripts/data_preparation/METR-LA/generate_training_data.py index a51ef0c3..b5d13e0e 100644 --- a/scripts/data_preparation/METR-LA/generate_training_data.py +++ b/scripts/data_preparation/METR-LA/generate_training_data.py @@ -1,171 +1,124 @@ import os -import sys +import json import shutil -import pickle -import argparse import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - graph_file_path = args.graph_file_path - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'METR-LA' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.h5' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.pkl' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = False # Add day of the month as a feature +add_day_of_year = False # Add day of the year as a feature +steps_per_day = 288 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic speed' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_hdf(data_file_path) data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + _, n, _ = data.shape + feature_list = [data] + if add_time_of_day: - # numerical time_of_day - tod = ( - df.index.values - df.index.values.astype("datetime64[D]")) / np.timedelta64(1, "D") - tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(tod_tiled) + time_of_day = (df.index.values - df.index.values.astype('datetime64[D]')) / np.timedelta64(1, 'D') + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) if add_day_of_week: - # numerical day_of_week - dow = df.index.dayofweek / 7 - dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dow_tiled) + day_of_week = df.index.dayofweek / 7 + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) if add_day_of_month: # numerical day_of_month - dom = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. - dom_tiled = np.tile(dom, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dom_tiled) + day_of_month = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + day_of_month_tiled = np.tile(day_of_month, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_month_tiled) if add_day_of_year: # numerical day_of_year - doy = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. - doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(doy_tiled) - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - # copy adj - shutil.copyfile(graph_file_path, output_dir + "/adj_mx.pkl") - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.7 - VALID_RATIO = 0.1 - TARGET_CHANNEL = [0] # target channel(s) - - DATASET_NAME = "METR-LA" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.h5".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.pkl".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + day_of_year = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. + day_of_year_tiled = np.tile(day_of_year, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_year_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + shutil.copyfile(graph_file_path, output_graph_path) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Copy and save adjacency matrix + save_graph() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/PEMS-BAY/generate_training_data.py b/scripts/data_preparation/PEMS-BAY/generate_training_data.py index f97ebf93..83670af4 100644 --- a/scripts/data_preparation/PEMS-BAY/generate_training_data.py +++ b/scripts/data_preparation/PEMS-BAY/generate_training_data.py @@ -1,171 +1,124 @@ import os -import sys +import json import shutil -import pickle -import argparse import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - graph_file_path = args.graph_file_path - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'PEMS-BAY' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.h5' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.pkl' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = False # Add day of the month as a feature +add_day_of_year = False # Add day of the year as a feature +steps_per_day = 288 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic speed' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_hdf(data_file_path) data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + _, n, _ = data.shape + feature_list = [data] + if add_time_of_day: - # numerical time_of_day - tod = ( - df.index.values - df.index.values.astype("datetime64[D]")) / np.timedelta64(1, "D") - tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(tod_tiled) + time_of_day = (df.index.values - df.index.values.astype('datetime64[D]')) / np.timedelta64(1, 'D') + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) if add_day_of_week: - # numerical day_of_week - dow = df.index.dayofweek / 7 - dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dow_tiled) + day_of_week = df.index.dayofweek / 7 + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) if add_day_of_month: # numerical day_of_month - dom = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. - dom_tiled = np.tile(dom, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dom_tiled) + day_of_month = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + day_of_month_tiled = np.tile(day_of_month, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_month_tiled) if add_day_of_year: # numerical day_of_year - doy = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. - doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(doy_tiled) - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - # copy adj - shutil.copyfile(graph_file_path, output_dir + "/adj_mx.pkl") - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.7 - VALID_RATIO = 0.1 - TARGET_CHANNEL = [0] # target channel(s) - - DATASET_NAME = "PEMS-BAY" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.h5".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.pkl".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + day_of_year = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. + day_of_year_tiled = np.tile(day_of_year, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_year_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + shutil.copyfile(graph_file_path, output_graph_path) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Copy and save adjacency matrix + save_graph() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/PEMS03/generate_training_data.py b/scripts/data_preparation/PEMS03/generate_training_data.py index 11d468a3..bf212a5f 100644 --- a/scripts/data_preparation/PEMS03/generate_training_data.py +++ b/scripts/data_preparation/PEMS03/generate_training_data.py @@ -1,161 +1,114 @@ import os -import sys +import json import shutil -import pickle -import argparse import numpy as np -from generate_adj_mx import generate_adj_pems03 -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - graph_file_path = args.graph_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data - data = np.load(data_file_path)["data"] +from generate_adj_mx import generate_adj_pems03 as generate_adj + +# Hyperparameters +dataset_name = 'PEMS03' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.npz' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.pkl' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +steps_per_day = 288 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic flow' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + data = np.load(data_file_path)['data'] data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data + +def add_temporal_features(data): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: - # numerical time_of_day - tod = [i % steps_per_day / - steps_per_day for i in range(data_norm.shape[0])] - tod = np.array(tod) - tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(tod_tiled) + time_of_day = np.array([i % steps_per_day / steps_per_day for i in range(l)]) + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) if add_day_of_week: - # numerical day_of_week - dow = [(i // steps_per_day) % 7 / 7 for i in range(data_norm.shape[0])] - dow = np.array(dow) - dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dow_tiled) - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - # copy adj - if os.path.exists(args.graph_file_path): - # copy - shutil.copyfile(args.graph_file_path, output_dir + "/adj_mx.pkl") + day_of_week = np.array([(i // steps_per_day) % 7 / 7 for i in range(l)]) + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory, generating it if necessary.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + if os.path.exists(graph_file_path): + shutil.copyfile(graph_file_path, output_graph_path) else: - # generate and copy - generate_adj_pems03() - shutil.copyfile(args.graph_file_path, output_dir + "/adj_mx.pkl") - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 288 - - DATASET_NAME = "PEMS03" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.npz".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.pkl".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + generate_adj() + shutil.copyfile(graph_file_path, output_graph_path) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data) + + # Save processed data + save_data(data_with_features) + + # Copy or generate and save adjacency matrix + save_graph() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/PEMS04/generate_training_data.py b/scripts/data_preparation/PEMS04/generate_training_data.py index bed3d946..ec3b9e3d 100644 --- a/scripts/data_preparation/PEMS04/generate_training_data.py +++ b/scripts/data_preparation/PEMS04/generate_training_data.py @@ -1,161 +1,114 @@ import os -import sys +import json import shutil -import pickle -import argparse import numpy as np -from generate_adj_mx import generate_adj_pems04 -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - graph_file_path = args.graph_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data - data = np.load(data_file_path)["data"] +from generate_adj_mx import generate_adj_pems04 as generate_adj + +# Hyperparameters +dataset_name = 'PEMS04' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.npz' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.pkl' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +steps_per_day = 288 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic flow' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + data = np.load(data_file_path)['data'] data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data + +def add_temporal_features(data): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: - # numerical time_of_day - tod = [i % steps_per_day / - steps_per_day for i in range(data_norm.shape[0])] - tod = np.array(tod) - tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(tod_tiled) + time_of_day = np.array([i % steps_per_day / steps_per_day for i in range(l)]) + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) if add_day_of_week: - # numerical day_of_week - dow = [(i // steps_per_day) % 7 / 7 for i in range(data_norm.shape[0])] - dow = np.array(dow) - dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dow_tiled) - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - # copy adj - if os.path.exists(args.graph_file_path): - # copy - shutil.copyfile(args.graph_file_path, output_dir + "/adj_mx.pkl") + day_of_week = np.array([(i // steps_per_day) % 7 / 7 for i in range(l)]) + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory, generating it if necessary.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + if os.path.exists(graph_file_path): + shutil.copyfile(graph_file_path, output_graph_path) else: - # generate and copy - generate_adj_pems04() - shutil.copyfile(graph_file_path, output_dir + "/adj_mx.pkl") - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 288 - - DATASET_NAME = "PEMS04" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.npz".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.pkl".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + generate_adj() + shutil.copyfile(graph_file_path, output_graph_path) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data) + + # Save processed data + save_data(data_with_features) + + # Copy or generate and save adjacency matrix + save_graph() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/PEMS07/generate_training_data.py b/scripts/data_preparation/PEMS07/generate_training_data.py index a5fa12c8..559c1797 100644 --- a/scripts/data_preparation/PEMS07/generate_training_data.py +++ b/scripts/data_preparation/PEMS07/generate_training_data.py @@ -1,161 +1,114 @@ import os -import sys +import json import shutil -import pickle -import argparse import numpy as np -from generate_adj_mx import generate_adj_pems07 -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - graph_file_path = args.graph_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data - data = np.load(data_file_path)["data"] +from generate_adj_mx import generate_adj_pems07 as generate_adj + +# Hyperparameters +dataset_name = 'PEMS07' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.npz' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.pkl' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +steps_per_day = 288 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic flow' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + data = np.load(data_file_path)['data'] data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data + +def add_temporal_features(data): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: - # numerical time_of_day - tod = [i % steps_per_day / - steps_per_day for i in range(data_norm.shape[0])] - tod = np.array(tod) - tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(tod_tiled) + time_of_day = np.array([i % steps_per_day / steps_per_day for i in range(l)]) + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) if add_day_of_week: - # numerical day_of_week - dow = [(i // steps_per_day) % 7 / 7 for i in range(data_norm.shape[0])] - dow = np.array(dow) - dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dow_tiled) - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - # copy adj - if os.path.exists(args.graph_file_path): - # copy - shutil.copyfile(args.graph_file_path, output_dir + "/adj_mx.pkl") + day_of_week = np.array([(i // steps_per_day) % 7 / 7 for i in range(l)]) + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory, generating it if necessary.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + if os.path.exists(graph_file_path): + shutil.copyfile(graph_file_path, output_graph_path) else: - # generate and copy - generate_adj_pems07() - shutil.copyfile(args.graph_file_path, output_dir + "/adj_mx.pkl") - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 288 - - DATASET_NAME = "PEMS07" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.npz".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.pkl".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + generate_adj() + shutil.copyfile(graph_file_path, output_graph_path) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data) + + # Save processed data + save_data(data_with_features) + + # Copy or generate and save adjacency matrix + save_graph() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/PEMS08/generate_training_data.py b/scripts/data_preparation/PEMS08/generate_training_data.py index ca452959..28a1e6e8 100644 --- a/scripts/data_preparation/PEMS08/generate_training_data.py +++ b/scripts/data_preparation/PEMS08/generate_training_data.py @@ -1,161 +1,114 @@ import os -import sys +import json import shutil -import pickle -import argparse import numpy as np -from generate_adj_mx import generate_adj_pems08 -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - graph_file_path = args.graph_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data - data = np.load(data_file_path)["data"] +from generate_adj_mx import generate_adj_pems08 as generate_adj + +# Hyperparameters +dataset_name = 'PEMS08' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.npz' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.pkl' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +steps_per_day = 288 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic flow' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + data = np.load(data_file_path)['data'] data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data + +def add_temporal_features(data): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: - # numerical time_of_day - tod = [i % steps_per_day / - steps_per_day for i in range(data_norm.shape[0])] - tod = np.array(tod) - tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(tod_tiled) + time_of_day = np.array([i % steps_per_day / steps_per_day for i in range(l)]) + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) if add_day_of_week: - # numerical day_of_week - dow = [(i // steps_per_day) % 7 / 7 for i in range(data_norm.shape[0])] - dow = np.array(dow) - dow_tiled = np.tile(dow, [1, n, 1]).transpose((2, 1, 0)) - feature_list.append(dow_tiled) - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - # copy adj - if os.path.exists(args.graph_file_path): - # copy - shutil.copyfile(args.graph_file_path, output_dir + "/adj_mx.pkl") + day_of_week = np.array([(i // steps_per_day) % 7 / 7 for i in range(l)]) + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory, generating it if necessary.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + if os.path.exists(graph_file_path): + shutil.copyfile(graph_file_path, output_graph_path) else: - # generate and copy - generate_adj_pems08() - shutil.copyfile(graph_file_path, output_dir + "/adj_mx.pkl") - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 288 - - DATASET_NAME = "PEMS08" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.npz".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.pkl".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + generate_adj() + shutil.copyfile(graph_file_path, output_graph_path) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data) + + # Save processed data + save_data(data_with_features) + + # Copy or generate and save adjacency matrix + save_graph() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/Pulse/generate_training_data.py b/scripts/data_preparation/Pulse/generate_training_data.py index f5ee733c..2e6ebe8f 100644 --- a/scripts/data_preparation/Pulse/generate_training_data.py +++ b/scripts/data_preparation/Pulse/generate_training_data.py @@ -1,122 +1,75 @@ import os -import sys -import shutil -import pickle -import argparse +import json import numpy as np -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'Pulse' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.npy' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +frequency = None +domain = 'simulated pulse data' +feature_description = [domain] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' data = np.load(data_file_path) data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] - - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 720 - FUTURE_SEQ_LEN = 96 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - - DATASET_NAME = "Pulse" - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.npy".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + print(f'Raw time series shape: {data.shape}') + return data + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data = load_and_preprocess_data() + + # Save processed data + save_data(data) + + # Save dataset description + save_description(data) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/Pulse/simulate_data.py b/scripts/data_preparation/Pulse/simulate_data.py index 29a6f918..272a252c 100644 --- a/scripts/data_preparation/Pulse/simulate_data.py +++ b/scripts/data_preparation/Pulse/simulate_data.py @@ -1,33 +1,33 @@ import os -import sys + +import torch import numpy as np -import matplotlib.pyplot as plt -PROJECT_DIR = os.path.abspath(__file__ + "/../../../..") + +PROJECT_DIR = os.path.abspath(__file__ + '/../../../..') os.chdir(PROJECT_DIR) -def generate_pulse_sequence(duration, min_interval, max_interval): - time_points = np.arange(0, duration, 1) - pulse_sequence = np.zeros_like(time_points) +# hyper parameterts +duration = 20000 # time series length +min_interval = 30 # minimum interval between two pulses +max_interval = 30 # maximum interval between two pulses + +def generate_pulse_sequence(): + x = np.arange(0, duration, 1) + y = np.zeros_like(x) current_time = 0 while current_time < duration: pulse_interval = np.random.uniform(min_interval, max_interval) pulse_width = 1 - pulse_sequence[int(current_time):int(current_time + pulse_width)] = 1 + y[int(current_time):int(current_time + pulse_width)] = 1 current_time += pulse_interval + pulse_width - return time_points, pulse_sequence - -# hyper parameterts -duration = 20000 # time series length -min_interval = 30 # minimum interval between two pulses -max_interval = 30 # maximum interval between two pulses + return x, y # generate pulse sequence -time_points, pulse_sequence = generate_pulse_sequence(duration, min_interval, max_interval) +time_points, pulse_sequence = generate_pulse_sequence() # save pulse sequence -import torch data = torch.Tensor(pulse_sequence).unsqueeze(-1).unsqueeze(-1).numpy() np.save('datasets/raw_data/Pulse/Pulse.npy', data) diff --git a/scripts/data_preparation/SD/generate_training_data.py b/scripts/data_preparation/SD/generate_training_data.py index 88348389..01d08eae 100644 --- a/scripts/data_preparation/SD/generate_training_data.py +++ b/scripts/data_preparation/SD/generate_training_data.py @@ -1,72 +1,136 @@ import os -import sys -import argparse - -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from scripts.data_preparation.CA.generate_training_data import generate_data - -# Dataset Description: -# LargeST: A Benchmark Dataset for Large-Scale Traffic Forecasting. - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 12 - FUTURE_SEQ_LEN = 12 - - TRAIN_RATIO = 0.6 - VALID_RATIO = 0.2 - TARGET_CHANNEL = [0] # target channel(s) - - DATASET_NAME = "SD" - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.h5".format(DATASET_NAME) - GRAPH_FILE_PATH = "datasets/raw_data/{0}/adj_{0}.npy".format(DATASET_NAME) - GRAPH_METE_PATH = "datasets/raw_data/{0}/meta_{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--graph_file_path", type=str, - default=GRAPH_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) +import json +import pickle +import shutil + +import numpy as np +import pandas as pd + +# Hyperparameters +dataset_name = 'SD' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.h5' +graph_file_path = f'datasets/raw_data/{dataset_name}/adj_{dataset_name}.npy' +meta_file_path = f'datasets/raw_data/{dataset_name}/meta_{dataset_name}.csv' +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = False # Add day of the month as a feature +add_day_of_year = False # Add day of the year as a feature +steps_per_day = 96 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'traffic flow' +feature_description = [domain, 'time of day', 'day of week'] +regular_settings = { + 'INPUT_LEN': 12, + 'OUTPUT_LEN': 12, + 'TRAIN_VAL_TEST_RATIO': [0.6, 0.2, 0.2], + 'NORM_EACH_CHANNEL': False, + 'RESCALE': True, + 'METRICS': ['MAE', 'RMSE', 'MAPE'], + 'NULL_VAL': 0.0 +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' + df = pd.read_hdf(data_file_path) + data = np.expand_dims(df.values, axis=-1) + data = data[..., target_channel] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + _, n, _ = data.shape + feature_list = [data] + + if add_time_of_day: + time_of_day = (df.index.values - df.index.values.astype('datetime64[D]')) / np.timedelta64(1, 'D') + time_of_day_tiled = np.tile(time_of_day, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(time_of_day_tiled) + + if add_day_of_week: + day_of_week = df.index.dayofweek / 7 + day_of_week_tiled = np.tile(day_of_week, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_week_tiled) + + if add_day_of_month: + # numerical day_of_month + day_of_month = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + day_of_month_tiled = np.tile(day_of_month, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_month_tiled) + + if add_day_of_year: + # numerical day_of_year + day_of_year = (df.index.dayofyear - 1) / 366 # df.index.month starts from 1. We need to minus 1 to make it start from 0. + day_of_year_tiled = np.tile(day_of_year, [1, n, 1]).transpose((2, 1, 0)) + feature_list.append(day_of_year_tiled) + + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_graph(): + '''Save the adjacency matrix to the output directory.''' + output_graph_path = os.path.join(output_dir, 'adj_mx.pkl') + adj_mx = np.load(graph_file_path) + with open(output_dir + '/adj_mx.pkl', 'wb') as f: + pickle.dump(adj_mx, f) + print(f'Adjacency matrix saved to {output_graph_path}') + +def save_meta_data(): + '''Save the meta data to the output directory''' + output_meta_data_path = os.path.join(output_dir, 'meta.csv') + shutil.copyfile(meta_file_path, output_meta_data_path) + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Copy and save adjacency matrix + save_graph() + + # Copy and save meta data + save_meta_data() + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/Traffic/generate_training_data.py b/scripts/data_preparation/Traffic/generate_training_data.py index d96d81ef..dd855ab2 100644 --- a/scripts/data_preparation/Traffic/generate_training_data.py +++ b/scripts/data_preparation/Traffic/generate_training_data.py @@ -1,90 +1,56 @@ import os -import sys -import pickle -import argparse +import json import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'Traffic' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 24 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'road occupancy rates' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_csv(data_file_path) - df_index = pd.to_datetime(df["date"].values, format="%Y-%m-%d %H:%M").to_numpy() + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() df = df[df.columns[1:]] df.index = df_index - data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - # Following related works (e.g. informer and autoformer), we normalize each channel separately. - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + _, n, _ = data.shape + feature_list = [data] + if add_time_of_day: # numerical time_of_day - tod = ( - df.index.values - df.index.values.astype("datetime64[D]")) / np.timedelta64(1, "D") + df.index.values - df.index.values.astype('datetime64[D]')) / np.timedelta64(1, 'D') tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(tod_tiled) - - # tod = [i % steps_per_day / steps_per_day for i in range(data_norm.shape[0])] - # tod = np.array(tod) - # tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) - # feature_list.append(tod_tiled) - if add_day_of_week: # numerical day_of_week dow = df.index.dayofweek / 7 @@ -93,7 +59,7 @@ def generate_data(args: argparse.Namespace): if add_day_of_month: # numerical day_of_month - dom = (df.index.day - 1 ) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. + dom = (df.index.day - 1) / 31 # df.index.day starts from 1. We need to minus 1 to make it start from 0. dom_tiled = np.tile(dom, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(dom_tiled) @@ -103,78 +69,52 @@ def generate_data(args: argparse.Namespace): doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(doy_tiled) - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 168 - FUTURE_SEQ_LEN = 96 - - TRAIN_RATIO = 0.7 - VALID_RATIO = 0.1 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 24 # every 1 hour - - DATASET_NAME = "Electricity" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/Weather/generate_training_data.py b/scripts/data_preparation/Weather/generate_training_data.py index c897afca..d531464e 100644 --- a/scripts/data_preparation/Weather/generate_training_data.py +++ b/scripts/data_preparation/Weather/generate_training_data.py @@ -1,79 +1,52 @@ import os -import sys -import pickle -import argparse +import json import numpy as np import pandas as pd -# TODO: remove it when basicts can be installed by pip -sys.path.append(os.path.abspath(__file__ + "/../../../..")) -from basicts.data.transform import standard_transform - - -def generate_data(args: argparse.Namespace): - """Preprocess and generate train/valid/test datasets. - - Args: - args (argparse): configurations of preprocessing - """ - - target_channel = args.target_channel - future_seq_len = args.future_seq_len - history_seq_len = args.history_seq_len - add_time_of_day = args.tod - add_day_of_week = args.dow - add_day_of_month = args.dom - add_day_of_year = args.doy - output_dir = args.output_dir - train_ratio = args.train_ratio - valid_ratio = args.valid_ratio - data_file_path = args.data_file_path - steps_per_day = args.steps_per_day - norm_each_channel = args.norm_each_channel - if_rescale = not norm_each_channel # if evaluate on rescaled data. see `basicts.runner.base_tsf_runner.BaseTimeSeriesForecastingRunner.build_train_dataset` for details. - - # read data +# Hyperparameters +dataset_name = 'Weather' +data_file_path = f'datasets/raw_data/{dataset_name}/{dataset_name}.csv' +graph_file_path = None +output_dir = f'datasets/{dataset_name}' +target_channel = [0] # Target traffic flow channel +add_time_of_day = True # Add time of day as a feature +add_day_of_week = True # Add day of the week as a feature +add_day_of_month = True # Add day of the month as a feature +add_day_of_year = True # Add day of the year as a feature +steps_per_day = 144 # Number of time steps per day +frequency = 1440 // steps_per_day +domain = 'weather' +feature_description = [domain, 'time of day', 'day of week', 'day of week', 'day of year'] +regular_settings = { + 'INPUT_LEN': 336, + 'OUTPUT_LEN': 336, + 'TRAIN_VAL_TEST_RATIO': [0.7, 0.1, 0.2], + 'NORM_EACH_CHANNEL': True, + 'RESCALE': False, + 'METRICS': ['MAE', 'MSE'], + 'NULL_VAL': np.nan +} + +def load_and_preprocess_data(): + '''Load and preprocess raw data, selecting the specified channel(s).''' df = pd.read_csv(data_file_path) - df_index = pd.to_datetime(df["date"].values, format="%Y/%m/%d %H:%M").to_numpy() + df_index = pd.to_datetime(df['date'].values, format='%Y-%m-%d %H:%M').to_numpy() df = df[df.columns[1:]] df.index = df_index - data = np.expand_dims(df.values, axis=-1) - data = data[..., target_channel] - print("raw time series shape: {0}".format(data.shape)) - - # split data - l, n, f = data.shape - num_samples = l - (history_seq_len + future_seq_len) + 1 - train_num = round(num_samples * train_ratio) - valid_num = round(num_samples * valid_ratio) - test_num = num_samples - train_num - valid_num - print("number of training samples:{0}".format(train_num)) - print("number of validation samples:{0}".format(valid_num)) - print("number of test samples:{0}".format(test_num)) - - index_list = [] - for t in range(history_seq_len, num_samples + history_seq_len): - index = (t-history_seq_len, t, t+future_seq_len) - index_list.append(index) - - train_index = index_list[:train_num] - valid_index = index_list[train_num: train_num + valid_num] - test_index = index_list[train_num + - valid_num: train_num + valid_num + test_num] - - # normalize data - scaler = standard_transform - # Following related works (e.g. informer and autoformer), we normalize each channel separately. - data_norm = scaler(data, output_dir, train_index, history_seq_len, future_seq_len, norm_each_channel=norm_each_channel) - - # add temporal feature - feature_list = [data_norm] + print(f'Raw time series shape: {data.shape}') + return data, df + +def add_temporal_features(data, df): + '''Add time of day and day of week as features to the data.''' + l, n, _ = data.shape + feature_list = [data] + if add_time_of_day: # numerical time_of_day - tod = [i % steps_per_day / steps_per_day for i in range(data_norm.shape[0])] + tod = [i % steps_per_day / steps_per_day for i in range(l)] tod = np.array(tod) tod_tiled = np.tile(tod, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(tod_tiled) @@ -96,78 +69,52 @@ def generate_data(args: argparse.Namespace): doy_tiled = np.tile(doy, [1, n, 1]).transpose((2, 1, 0)) feature_list.append(doy_tiled) - processed_data = np.concatenate(feature_list, axis=-1) - - # save data - index = {} - index["train"] = train_index - index["valid"] = valid_index - index["test"] = test_index - with open(output_dir + "/index_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(index, f) - - data = {} - data["processed_data"] = processed_data - with open(output_dir + "/data_in_{0}_out_{1}_rescale_{2}.pkl".format(history_seq_len, future_seq_len, if_rescale), "wb") as f: - pickle.dump(data, f) - - -if __name__ == "__main__": - # sliding window size for generating history sequence and target sequence - HISTORY_SEQ_LEN = 96 - FUTURE_SEQ_LEN = 336 - - TRAIN_RATIO = 0.7 - VALID_RATIO = 0.1 - TARGET_CHANNEL = [0] # target channel(s) - STEPS_PER_DAY = 144 # sampling rate: every 1 hour - - DATASET_NAME = "Weather" # sampling frequency: every 1 hour - TOD = True # if add time_of_day feature - DOW = True # if add day_of_week feature - DOM = True # if add day_of_month feature - DOY = True # if add day_of_year feature - - OUTPUT_DIR = "datasets/" + DATASET_NAME - DATA_FILE_PATH = "datasets/raw_data/{0}/{0}.csv".format(DATASET_NAME) - - parser = argparse.ArgumentParser() - parser.add_argument("--output_dir", type=str, - default=OUTPUT_DIR, help="Output directory.") - parser.add_argument("--data_file_path", type=str, - default=DATA_FILE_PATH, help="Raw traffic readings.") - parser.add_argument("--history_seq_len", type=int, - default=HISTORY_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--future_seq_len", type=int, - default=FUTURE_SEQ_LEN, help="Sequence Length.") - parser.add_argument("--steps_per_day", type=int, - default=STEPS_PER_DAY, help="Sequence Length.") - parser.add_argument("--tod", type=bool, default=TOD, - help="Add feature time_of_day.") - parser.add_argument("--dow", type=bool, default=DOW, - help="Add feature day_of_week.") - parser.add_argument("--dom", type=bool, default=DOM, - help="Add feature day_of_week.") - parser.add_argument("--doy", type=bool, default=DOY, - help="Add feature day_of_week.") - parser.add_argument("--target_channel", type=list, - default=TARGET_CHANNEL, help="Selected channels.") - parser.add_argument("--train_ratio", type=float, - default=TRAIN_RATIO, help="Train ratio") - parser.add_argument("--valid_ratio", type=float, - default=VALID_RATIO, help="Validate ratio.") - parser.add_argument("--norm_each_channel", type=float, help="Validate ratio.") - args = parser.parse_args() - - # print args - print("-"*(20+45+5)) - for key, value in sorted(vars(args).items()): - print("|{0:>20} = {1:<45}|".format(key, str(value))) - print("-"*(20+45+5)) - - if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - args.norm_each_channel = True - generate_data(args) - args.norm_each_channel = False - generate_data(args) + data_with_features = np.concatenate(feature_list, axis=-1) # L x N x C + return data_with_features + +def save_data(data): + '''Save the preprocessed data to a binary file.''' + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, 'data.dat') + fp = np.memmap(file_path, dtype='float32', mode='w+', shape=data.shape) + fp[:] = data[:] + fp.flush() + del fp + print(f'Data saved to {file_path}') + +def save_description(data): + '''Save a description of the dataset to a JSON file.''' + description = { + 'name': dataset_name, + 'domain': domain, + 'shape': data.shape, + 'num_time_steps': data.shape[0], + 'num_nodes': data.shape[1], + 'num_features': data.shape[2], + 'feature_description': feature_description, + 'has_graph': graph_file_path is not None, + 'frequency (minutes)': frequency, + 'regular_settings': regular_settings + } + description_path = os.path.join(output_dir, 'desc.json') + with open(description_path, 'w') as f: + json.dump(description, f, indent=4) + print(f'Description saved to {description_path}') + print(description) + +def main(): + # Load and preprocess data + data, df = load_and_preprocess_data() + + # Add temporal features + data_with_features = add_temporal_features(data, df) + + # Save processed data + save_data(data_with_features) + + # Save dataset description + save_description(data_with_features) + +if __name__ == '__main__': + main() diff --git a/scripts/data_preparation/run.sh b/scripts/data_preparation/run.sh index f602caf5..af1613e7 100755 --- a/scripts/data_preparation/run.sh +++ b/scripts/data_preparation/run.sh @@ -1,24 +1,29 @@ #!/bin/bash # spatial-temporal forecasting -python scripts/data_preparation/METR-LA/generate_training_data.py --history_seq_len 12 --future_seq_len 12 -python scripts/data_preparation/PEMS-BAY/generate_training_data.py --history_seq_len 12 --future_seq_len 12 -python scripts/data_preparation/PEMS03/generate_training_data.py --history_seq_len 12 --future_seq_len 12 -python scripts/data_preparation/PEMS04/generate_training_data.py --history_seq_len 12 --future_seq_len 12 -python scripts/data_preparation/PEMS07/generate_training_data.py --history_seq_len 12 --future_seq_len 12 -python scripts/data_preparation/PEMS08/generate_training_data.py --history_seq_len 12 --future_seq_len 12 +python scripts/data_preparation/METR-LA/generate_training_data.py +python scripts/data_preparation/PEMS-BAY/generate_training_data.py +python scripts/data_preparation/PEMS03/generate_training_data.py +python scripts/data_preparation/PEMS04/generate_training_data.py +python scripts/data_preparation/PEMS07/generate_training_data.py +python scripts/data_preparation/PEMS08/generate_training_data.py # long-term time series forecasting -python scripts/data_preparation/ETTh1/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/ETTh2/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/ETTm1/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/ETTm2/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/Electricity/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/Weather/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/ExchangeRate/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/Illness/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/Traffic/generate_training_data.py --history_seq_len 336 --future_seq_len 336 +python scripts/data_preparation/ETTh1/generate_training_data.py +python scripts/data_preparation/ETTh2/generate_training_data.py +python scripts/data_preparation/ETTm1/generate_training_data.py +python scripts/data_preparation/ETTm2/generate_training_data.py +python scripts/data_preparation/Electricity/generate_training_data.py +python scripts/data_preparation/Weather/generate_training_data.py +python scripts/data_preparation/ExchangeRate/generate_training_data.py +python scripts/data_preparation/Illness/generate_training_data.py +python scripts/data_preparation/Traffic/generate_training_data.py -python scripts/data_preparation/METR-LA/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/PEMS-BAY/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/PEMS04/generate_training_data.py --history_seq_len 336 --future_seq_len 336 -python scripts/data_preparation/PEMS08/generate_training_data.py --history_seq_len 336 --future_seq_len 336 +python scripts/data_preparation/CA/generate_training_data.py +python scripts/data_preparation/GBA/generate_training_data.py +python scripts/data_preparation/GLA/generate_training_data.py +python scripts/data_preparation/SD/generate_training_data.py + +python scripts/data_preparation/BeijingAirQuality/generate_training_data.py + +python scripts/data_preparation/Gaussian/generate_training_data.py +python scripts/data_preparation/Pulse/generate_training_data.py diff --git a/tutorial/config_design.md b/tutorial/config_design.md new file mode 100644 index 00000000..ff8fd1c9 --- /dev/null +++ b/tutorial/config_design.md @@ -0,0 +1,40 @@ +# 📜 Configuration Design + +The design philosophy of BasicTS is to be entirely configuration-based. Our goal is to allow users to focus on their models and data, without getting bogged down by the complexities of pipeline construction. + +The configuration file is a `.py` file where you can import your model and runner, and set all necessary options. BasicTS uses EasyDict as a parameter container, making it easy to extend and flexible to use. + +The configuration file typically includes the following sections: + +- **General Options**: Describes general settings such as configuration description, `GPU_NUM`, `RUNNER`, etc. +- **Environment Options**: Includes settings like `TF32`, `SEED`, `CUDNN`, `DETERMINISTIC`, etc. +- **Dataset Options**: Specifies `NAME`, `TYPE` (Dataset Class), `PARAMS` (Dataset Parameters), etc. +- **Scaler Options**: Specifies `NAME`, `TYPE` (Scaler Class), `PARAMS` (Scaler Parameters), etc. +- **Model Options**: Specifies `NAME`, `TYPE` (Model Class), `PARAMS` (Model Parameters), etc. +- **Metrics Options**: Includes `FUNCS` (Metric Functions), `TARGET` (Target Metrics), `NULL_VALUE` (Handling of Missing Values), etc. +- **Train Options**: + - **General**: Specifies settings like `EPOCHS`, `LOSS`, `EARLY_STOPPING`, etc. + - **Optimizer**: Specifies `TYPE` (Optimizer Class), `PARAMS` (Optimizer Parameters), etc. + - **Schduler**: Specifies `TYPE` (Scheduler Class), `PARAMS` (Scheduler Parameters), etc. + - **Curriculum Learning**: Includes settings like `CL_EPOHS`, `WARMUP_EPOCHS`, `STEP_SIZE`, etc. + - **Data**: Specifies settings like `BATCH_SIZE`, `NUM_WORKERS`, `PIN_MEMORY`, etc. +- **Valid Options**: + - **General**: Includes `INTERVAL` for validation frequency. + - **Data**: Specifies settings like `BATCH_SIZE`, `NUM_WORKERS`, `PIN_MEMORY`, etc. +- **Test Options**: + - **General**: Includes `INTERVAL` for testing frequency. + - **Data**: Specifies settings like `BATCH_SIZE`, `NUM_WORKERS`, `PIN_MEMORY`, etc. + +For a complete guide on all configuration options and examples, refer to [examples/complete_config.py](../examples/complete_config.py). + +## 🧑‍💻 Explore Further + +- **🎉 [Getting Stared](./getting_started.md)** +- **💡 [Understanding the Overall Design Convention of BasicTS](./overall_design.md)** +- **📦 [Exploring the Dataset Convention and Customizing Your Own Dataset](./dataset_design.md)** +- **🛠️ [Navigating The Scaler Convention and Designing Your Own Scaler](./scaler_design.md)** +- **🧠 [Diving into the Model Convention and Creating Your Own Model](./model_design.md)** +- **📉 [Examining the Metrics Convention and Developing Your Own Loss & Metrics](./metrics_design.md)** +- **🏃‍♂️ [Mastering The Runner Convention and Building Your Own Runner](./runner_design.md)** +- **📜 [Interpreting the Config File Convention and Customizing Your Configuration](./config_design.md)** +- **🔍 [Exploring a Variety of Baseline Models](../baselines/)** diff --git a/tutorial/dataset_design.md b/tutorial/dataset_design.md new file mode 100644 index 00000000..1e71a2ec --- /dev/null +++ b/tutorial/dataset_design.md @@ -0,0 +1,69 @@ +# 📦 Dataset Design + +## ⏬ Data Download + +To get started with the datasets, download the `all_data.zip` file from either [Google Drive](https://drive.google.com/drive/folders/14EJVODCU48fGK0FkyeVom_9lETh80Yjp?usp=sharing) or [Baidu Netdisk](https://pan.baidu.com/s/1shA2scuMdZHlx6pj35Dl7A?pwd=s2xe). After downloading, unzip the files into the `datasets/` directory: + +```bash +cd /path/to/BasicTS +unzip /path/to/all_data.zip -d datasets/ +mv datasets/all_data/* datasets/ +rmdir datasets/all_data +``` + +These datasets are preprocessed and ready for immediate use. + +## 💿 Data Format + +Each dataset contains at least two essential files: `data.dat` and `desc.json`: + +- **`data.dat`**: This file stores the raw time series data in `numpy.memmap` format with a shape of [L, N, C]. + - **L**: Number of time steps. Typically, the training, validation, and test sets are split along this dimension. + - **N**: Number of time series, also referred to as the number of nodes. + - **C**: Number of features. Usually, this includes [target feature, time of day, day of week, day of month, day of year], with the target feature being mandatory and the others optional. + +- **`desc.json`**: This file contains metadata about the dataset, including: + - Dataset name + - Domain of the dataset + - Shape of the data + - Number of time slices + - Number of nodes (i.e., the number of time series) + - Feature descriptions + - Presence of prior graph structures (if any) + - Regular settings: + - Input and output lengths + - Ratios for training, validation, and test sets + - Whether normalization is applied individually to each channel (i.e., time series) + - Whether to re-normalize during evaluation + - Evaluation metrics + - Handling of outliers + +## 🧑‍💻 Dataset Class Design + +
+ +
+ +In time series forecasting, datasets are typically generated from raw time series data using a sliding window approach. As illustrated above, the raw time series is split into training, validation, and test sets along the time dimension, and samples are generated using a sliding window of size `inputs + targets`. Most datasets adhere to this structure. + +BasicTS provides a built-in `Dataset` class called [`TimeSeriesForecastingDataset`](../basicts/data/simple_tsf_dataset.py), designed specifically for time series data. This class generates samples in the form of a dictionary containing two objects: `inputs` and `target`. `inputs` represents the input data, while `target` represents the target data. Detailed documentation can be found in the class's comments. + +## 🧑‍🍳 How to Add or Customize Datasets + +If your dataset follows the structure described above, you can preprocess your data into the `data.dat` and `desc.json` format and place it in the `datasets/` directory, e.g., `datasets/YOUR_DATA/{data.dat, desc.json}`. BasicTS will then automatically recognize and utilize your dataset. + +For reference, you can review the scripts in `scripts/data_preparation/`, which are used to process datasets from `raw_data.zip` ([Google Drive](https://drive.google.com/drive/folders/14EJVODCU48fGK0FkyeVom_9lETh80Yjp?usp=sharing), [Baidu Netdisk](https://pan.baidu.com/s/1shA2scuMdZHlx6pj35Dl7A?pwd=s2xe)). + +If your dataset does not conform to the standard format or has specific requirements, you can define your own dataset class by inheriting from `torch.utils.data.Dataset`. In this custom class, the `__getitem__` method should return a dictionary containing `inputs` and `target`. + +## 🧑‍💻 Explore Further + +- **🎉 [Getting Stared](./getting_started.md)** +- **💡 [Understanding the Overall Design Convention of BasicTS](./overall_design.md)** +- **📦 [Exploring the Dataset Convention and Customizing Your Own Dataset](./dataset_design.md)** +- **🛠️ [Navigating The Scaler Convention and Designing Your Own Scaler](./scaler_design.md)** +- **🧠 [Diving into the Model Convention and Creating Your Own Model](./model_design.md)** +- **📉 [Examining the Metrics Convention and Developing Your Own Loss & Metrics](./metrics_design.md)** +- **🏃‍♂️ [Mastering The Runner Convention and Building Your Own Runner](./runner_design.md)** +- **📜 [Interpreting the Config File Convention and Customizing Your Configuration](./config_design.md)** +- **🔍 [Exploring a Variety of Baseline Models](../baselines/)** diff --git a/tutorial/figures/DatasetDesign.jpeg b/tutorial/figures/DatasetDesign.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..aff7d470b3b7ba83118d43c32164c147fc0f7ae8 GIT binary patch literal 144113 zcmeFY2|SeF+dqEW5@He}Vv3|fB1<7t$(Ev2gfx}i#3Xx|k;=|wFJcs;MUy@2WN)|c z%UDXbVa77<%=W+a*+1Xyd7l6Cd%b?I*Z+Ul&2jE?pSjL;uJgX$*LBXY``BZ^2Lpds zCjdBo8rTN_z(!yL#}C7s=hj`=*7_@f{bfZFcZZ z{xZ7tx31cI-8gpU%&GPAZ@<^ee_XVlbxi<3r}BDVbIk0xL-&jt+)HZAZw*MO{L${8 zmiQeUz3ibU4Cp>!f8(Y%q*EoRe8$iF#(FsjDvREPgo4VDY5-B^U&=Y_&-~xCP`{?pim0{_$&UX+j9s2garWLUB!*7 zH?RKeZrwIHognL4%mV;HYXA^wgWj?AayqE?^Et<+;{d=j!e%e+hD<6E0GRjLZ2A*6 zo0$y&Ttfix)`L9=+=a|z{o;iFb8&KVa&dD(7dH?0`o*(>=jXNI*NgY(wQ>F8`~CX) zHYX=H^q+47&xYUY|H}pTFl5cA*lz*hjhq_XZ@D;R0Zw5KE@2LKBgYeH)_8sj$nkSf z&<{vXNHE@weEb5N0ZtAsE>3PP9!Os3N$~mt;uhxFvQzcg29dK@d1XCC)o#Z>-Y9px zuujaPowDoTwVR=Q{9DB(wn@tGR@k$5pSp&o)}h1N`X>wwjf_oBo;!cR(h5?$y@R8Z zvx}>nm$#3vpMSuuJ9qDeg-1k2B_t*#r=&i3nD!()C--Sy{q~>l+%I znp@tz|IpFd)!ozE*FP{eKJoS2V_@H&hlRM6p>ZCy;1ad{NutpKDmPy6tQbJ+xfTdQXiA2 zt`F(wi2iE>3jH6A==TBrJ|6Y}u!)NUnlLV500FRAd!9rA|NT3y#U0R&a=GGqa}S5x z;q8W1gB(T{{!IwkYG+!suX#TkXiXe2@jRH{r_nuCq9V_>d`tqMDD! zy`Tn;nujjiw%*a2el`{MVfY)Rwm^}Y#3h-?%IFMThG%j`E0}{SSAC zXWSma_@hEk5@lne0KTRZi8*(?9S&^Y?iG4S4tLTE{(&e|4a@Z(Y=8>~1ygxPSv3mb zD-}WZ1c3)R=RBVxBNlSci^sZ)awW?pzX*H29{e`r`5Dxr6fK1k-@^vntA_my zx(MFV=+l(T5lW>;w9WVV2YoiO8oGPHCHEff73dT@;hvTJ;h9XsC}L2t5eW-*!Inzr zoax^$rfc=okPTds%hJAA-YOq0qh-7)W!O4%=e;agdzzxwsqW<()ag6c?H;mKC6%#` zVJ>!;56fLmc5wVq6#s6=XjD6|I#6=f01*AZ?8dU&{%4l1m`I(>4R`{=t$VyD7w46g zJa$LE7LqyiFlm%pC%v)BC%K++h7BY*kE_r(*s+1Blq}dgM7RjUiHc~b;c6TpG{MBA z3XIH3D<8b1MOA%^w0r%e7jU5Po+KlRSc+_*-HhtY;eb{Mj ztK2qTDP}4a$%^ueMo?sX*+4uB-l!))Wdl*m0we+O&bU$3ypoSpfU<|ImgNVTZTGWZ zS@sDh@49hpnQ-z+$X?fh;HQas$8s;e>Ql}(&bIKndsNKmh`-nu|Ax1j64dUJ17H}Z zQwdhPV}{p%e}U6>KKEC$ib`sGrrIkrht(8cKM7Is7oo+2EtE@7lc{EkI2rJop7@30 zNNgEdgq7(o%b)s=mr+uF9?!)*@i_(l7!IqI2VvucupQLAhFF_H>yCM~E#v(S`Ho}p zA>VfCH^>1W3^#IHeK+G{14mx8I#Z5J#*atp$p!80-c7+r=J3l`7oVXF-=!Cl2DIiq z?hA2TKY1r22Uh@K-f`jUGmxUJ57<&e0`=qhUR}QN3~AYJ#=%056+8K~i4zsRIzexo z&}oWE&5k9(pSK!)8J(={;?+IFmH%&G?l=dbCBqg8)=Zd3UDi!v9PM4Ma@T6q(hkne zxSP^$+rU$O=H7*K9-oI2Ot+X2`ff4&+Yn!~gG|^!VI>=214qL`cdLwK^qx&eFDJM&Uc z@5Ukn+00Kd2aI~2`NNy>l0@gx6c)!DtT)SHr4)0%PhrE6np+%tPQTcnLh@B0kBD=@0z%{V|PrrN52v6=aT zCF_dZs*dmP9WQYm9~?ftt-vRC_l|N)s%*-VM)WS`knp4Hw)w`fUv~yQnBrP4N;BJg z>|MTxyzx%goUt=oJX_7L9u?tA<7*LPB(Z@yRpRJ$D>qa)K^%Q-Nh-8r>`TWBxL)4t zZV+^^Aj9}ls{+lZq&Ea<6P%_ho`L2nvwz5Vrn+q;=Ur_J8{ny819xJUN#HI7nQxR0 z^!Mt6S4Q-@H3}qEgxwCFeui~2;7RH|IM5)3^AM5hSh=A@K+4gCt$Q$d$a)Rp;>KDVgE7pbcc(c@nL+ei!8_n6hetK1zYTN{dmz7 zSL{DA-XP0aHXxwW^a$JedEHWb9SwjRmAs)I>O`^2;Kb!#4a8=*-ky{mL9b^g6Vz;N zbUO?rH%`Q!=Y4ZOQ|;yv9=}~&b(|?tz!GWG6y`9fl5g?~^Cd0mmfFcl+l&olo6}pT z`@Z!Ze}g(~Tiw$XFljv5tVkx^!I$=i>dB!F1RMd=OHm=&P7#=9c@-&8D+u5a%cos#B0CMR6KaP`U+xR?&tKr!wZ}{(^)UP?f$56d6k?6vtJc^Hhkgz7}<~M}r5s983yOa!*~=bTQ)nD1+M1 zonR8;!u1Ww9V7vMqxL$^XYI#G`=2xm>d!`!XWzBDjNVtjb?X4@a6@aHZiKBMGK<8= z5@4u0APtu>q$~+uM|Ywd_Ui)N0d6Z(v?dRN=x$| z@L6iM8zjqPre({p*KwdcaSj4;9xCpBcE?B|_B;77=vqa??I-EP2WmH%3t zwjo8D4QM6q^0ZrrdJmaBa~rj2(}oXn4%IFN+x%5Y}` zc>=hOV$>`;23}NtyH?G=meg`9^TkV-fk#`hSJbAhj>FW3?Q%}|c*kQbeRt#|tGzN+ zC$SK$K*aGcu;L2i(jfoCgXuh)5Cx*<4+ooN@QcFh5R}i=)cUb5hnzOy`pzT!7>90K!uDaeS_N!4R$#fJ z+G^Yz_-OBNyv0Q#h!E^eswj=Q9bf$;j(2_{F&FUD^7Bm#ewn(un6s7g%rCxg-;@0J zyD|YT0%#>anvRabmvH5SBF@rJLm668rN;OVYr=FDdVV zF_Qs*-R+iT6Mde@=Da$U0l#Jv#S9xtDr+V0q#yA|81(u$F`qA}E52|)Zd4jQck5B5 zPv!Lk`zKY0E;AyNFDyy_XzpR|rK&JQi)oXf2-R<*caBM2t;6uuQmq=;z}<6>Ocz8EOPHjvAg$dMW9G5$lzLV>&%nT?i$(=4*&1Ej9#wm#Bs)qyYy+&e ze+94xO%csJQiaWmlolGAnU8gEmBiK=vjG%3?DDQI?_lg((gt|L`y3F{YL=q-<4IS^ z;T2o|0uQ~8^LD1Y;&VSG=0tttO}Ve~VLyab`-r1?m~{}i>;J_1TQ5Vz&k;RF^(z2T zr^~z4TSdWhcGg*Orxh)VyA*t@%I%h9Ne!EX)$b=S_TK!;wYqV8&*fr*OQv**_!{%; z)&pAyCodTn?)H4-7$MEUcyU_$TCEi%qdn;kyo{U0T?npRNt+BGH5WD-k5CW1YobKZ z5C}AlwndgU&jj^lX({h(TD7?l^LnS}QJD!8Rcox9JY>1mhL*0>c_8jk$NZML&u=fB z>EE>sSfSsnSuH+h1Ki`x5QHN{;W4u`BE>I8S0&GNHB*9_QyNVaa$5_|t=3vo{*ow@ z?B~#95wRrdd9u3xjiI5p+$PjKLKISg5z!TK2T!p;-@_Lnn&m-M;20??E$l_^?)0$? z%lXyDO3{>*n4+ze0f{rlLtItlji(9!PEUJQ$7PyAW6MmIJmS-fab>Ba|;*po=)*_l;_?EmK zcS$RR4UE3RmOcdWV`TAhD~2MSuraUxAhi=pH-ta#a!NI)%t;({70w=hzpb`zSnw-h z`?-DE%!eBVkHR}fCZ-MNJ&_^s!c~SSo%WHLTkT9~LbJp#*?`UyTf_5bQr7OuY^561 z;P33R-5Hz{i?r(6QQo=YuVSxfomN+nbh`B1C!;M^A*}aU$~iVLXw`tFoS>_KdsWE% z16HxGR-`SjD)zZ*MXeb`gHLBq*Ai<&dz?|}r!~6*;_KW+EdtKm4d7Eg0I=|NttV|j z7>loUJ*c5of_t9_UF>397;W9|iXZf{nW|j9%>ZAbOTLwrCCf~HmH*VGb*+8TEosf%#qZlY z#Nl~z!(JB-ph@``vc+Z6ae_LA zx#_i+y{bIkK5uZVG|H=n`@(CuxM~&1&K^Ju_O>gLV!jYYqFjLTMwdA$5}?Mb%l*g3 zV-KFH>>`%GyslD;oJ;Cb{`w#|b@iN@N2%=b(NOu*p9({;7PpsT&Q?(f-y6oiGsF^U z(V!Pylhuz9S6>kFzd3P{`7p?enV5Tg)>pYykxMM&%WT%}k;ETb5E65bWRdG8I`kwk zUGTEhMm(rw6p3(-Ms9Z5N)jwF50}|aJ?U!UTC5Isdt~f4uCPtoJ$s<2MUIQ#sPu$R zc~uDJt3yW=V;eIBQXf$m;SL+HK;K>MWeK${s7J0;Aw+|8t3ug;88t8L+v|*p;-u0c zl5@BFq=CL}Sy_98W5`U7?m%=As?`?B>yZydm zA~FiQ>0y~lL`m^-RXC&|!Vcz3q9gnsvJBlIfDFZN-{G!bj~tZrD9E3F6rdf^@oLMW zfbIQb!ydiDCl>Rl&#&vM622FYS}{bv(~dHqVMlWil(WeG#Bsmd>S)klUVN-3IibIA zux@vfNA>qxF2V8sqImc3_(1v8BfJtw;Ej~L-`^eVP+oj8h#*pjJF;y zZBCi6rldB)wohepYa7%)^HGU4sC`XxD(~HOuV;ii`U}v?d2G+=sTT*ozV4&20fwj) zZJOFjUZA$noG4ZexJ{S9gQI$RS?^!YII+m3ImTkkHL3#s2E z+0L9wm2$4g+;E`5tEgwstFGoY;|=CdFdyNRi>(9Zw8y=*l0jA!WPR{C3(%2j)VMhH zdY}(PHsaB3C0_E4TQheMdvnSprJJgY^tZh!D|gf^`~b_?Yr+Y+uF#{mU~c||Nh0n1 zx&>oMJ*2_~vOfJH!zSDaO!uO^Xuho0?oEJARk}Cjenf=NUoDHhwpSO*z^a~*vBZ5y z7+pN|61A+u;!CHaXAtGEu%NROOvUCAUNC%2$ND;yx?92V>IdYRh53I)MIW&>>Ds^F zbw_Awb?~R5H zx%g_Ip2t6J`8qNrC6)fZd64|IPAas69Nc~NWeP4qR=q1_>f!VGZMaQ>0iSPYVKMrH8D7=e6;!1{=X zSBsB6O|=HEz6{c**iLD!&dI&$kT*_0*_~Gr8T7XK0>Z64^Z+m}_h#+~x}Hd}A`UJ= zoUBNtP6gbv+KlG}HwQ_9;n-}~0cKdN*E4B|G}u&ZJu`Dwchsi(?5DRiuX=*iZmWJ& z1F&YtYVrlsSYE9C*g7P_g$;xeUwN?@v&&e&I0Vx@Ps`&$J&%0kJ53NwQ8Pa^ytwp{z|z zTkDPzsoh zuTeLOUcD82aJ4(B=ss+w_tW>=O+2`($S`;bjv?wrOJycQ6!q@99m!$q%ZQZl#sw)` ze^qrm#dB3}sP~d?-L*&;wDHfqC~NDNph9Xc`&5WNZ}OGC@^B0R?qLJu&I$NH)dZ>G zh}0N9%tM!lu7`4$DGd~34$f*-o|_^(8!*ZnP;xqq&O`4xg@h1~(8 zp2lzmz-mdEV#MN~rrR?vQCr>y>rKML&rTt?1?@#N)||9EqM`T#w+TcWC-7+~>n*vz zw?^*@J-10b*L}>i#3il2EHqe=W&o+9lGQ~l6_3P-`_oT?=J~CZ)$z&4KJMFnF%r~$ z=3^N;*IQ0?`$+qa538f-f!UIL*`=z%_|H;~cwPYB243xWB!v2r$xOnIJ|UGqO@yfH z5ZFuiWVqr?z~sPDq6nB!|Iwtqr0Rw1gx$f5w!Degy*^jgf9ca@S+snAFro9*Nq%;D zmc>^D#Ha22!ZrpZ$C z`yB#ro`+zP{<;nM=Pk+qe%TU}_uREoIeo^arqME`eN%O@c5{>(@KF=^9@R$p2nUtt zaZgcriZO9uU#r#a{X5M$-Ikue*~DE&+4kfHY@3O&wfQv;QI54cj``Lzn(-vpj@(9( zJuY8k@5t?)Iv=-D1MREbyW~Z&rLQ2ZeuBCuzq;G-p07*1zJFdu6^DNOSYmm zPRF`H@}rrmiMylBW%*4&thMPwt{+jds_&!6aG+8P`3@B}sx>z}-pUiCO?h-#6|9(G z`i-?jkQYh?`j%$}&lv{hr=YZ=_R6CIFz*)2D4!)bcfkD~WWhUV6TREd6;C5SVb>Gv zJdw;YtOJ}6(@qqu)ddlCsR)r;0e`dswK>u&NAoc={1q5@p=(S1(Y98FfdY?$N6Cn4 zZ@=yP@`o_J%q%P#Js|RySf&6aE*xP%-;}WJ0E4?5v(IHM_pA6pQ=u1mGr|`IY$Fo< zJ5ZNQh+kFTKiYpo?I^-YYx{)gs2B3HN(f>!4^fJE3kT&^>+pkET2hyKMgSvt|7{_{ zc*L^pk5S5e#`9xJ<&w<=d;0@{C2{BDq*I6vr#p9W9BsmUz|_N~8Mbs|hy`Gq2;#Gu zTc|pu;Su?##iNlU?owxpqI^wJ5ti0YDSVO#xM_>VpEuP_>>|Gh=)#c_Z(kv;sJ(Y| z5#CBZ!!{p2Tzgz^?z!E)gd)_lW1^$-$w4F(#ZWGi`uu1?rA!-c zhGx;aHVltc2?Q`_zvCH;5+U`=?KyzmbhRRcsk zlyqK?j#hF&ztPOpiG>ZITwNuREKab6_|W+`6*XNY1#+jgC1=pP3nY&`Og&qX><~5Z zK_CfNKCr9Lx>^8obhhY$*xD@w9K&i1DM*^ZXo0B;^#rZ~e@v%!*Wbws-tgQU<+Ljc{$ehD!%^Xbq)VikKOI<>WKW;H!Vn?46DQAKY(hgF!B7-!t z@Zp)eDc>7g1wb?SmYGcP9`$q;VVAVw&((&jrk$n4eKTJS5)8w}4-2-WJP)eS(l&v2 zlFI!Uq7xGebJkR39kRH!(a!Jf2yeIUg|Qa}W*?g~_jUSQpk(H~YViAJ^!R1jsy$u# zu0)z`)x*gXo8Ck~TaP1IuBZfxOuDSRy(4kydm8@22s2)XGeuMjfi&1 zSA=z5cwO6hN~APr!Cmw7T;A7|-RI)FtX{H#1`-jivUY$Fg#@K?*#Om>gU&R-K-;+g z@BmL)T^RgWJ|{wjW{}I9$l4!*z)lgqGa&DG1%Vdh!PEG`900yV|JjcMV=FIhA)c#+ z&cm?SvD`GD$_AR+co(RLa$BVpDZDHhIz0f}elN&^4ZIzO!n0^7RIpkYY`lrRLX0|! z?fvBiewD{`hS}cOR>c8gf(kUmx^|WprHmLn7VJkmMU5?;&(JOFY=E}w3EN*+7+G?1 za#(CUq>!m7fvxM#+&PNtH*X?=T99A^-SYwwfpvim(vSD72-;ulCOQ*0Gc-yl87YsP0}S{3NI2XccSMn4%;- z@kZ=N02>gt1QS`Y=1s(31%ol9ZO0F?0nNN|5<_YLyLiNimJFLzWbGtu#PglWX_Kz| z#f|*DLl2Q(ek?NV{BbV;3&oy0zDHKOKMgVj(PJdsz~pVpLJM42eB2!$_PG6}{An%Q zK+s1ZX87Gma8s43@Z#f7XB&;3tofIyo1s197rKUr;YIjpECmg*w0Fxys+eCSVycT_ z3q1@pXBp)(bM=4R`9*TEUnHm7LK$xENh&@ZfohF~mk=8kqEZJoJfsd9canAWvu&o^ zgP$tvpX_kxP+EBDgE(aB{;l!7N=o7UlD&oe+2nkzGZx&(D!FTFhSx^%Gp^mczU?k}jN!0k%k zKFpEDv*$2BSoNgW8NGA1 z$((yT6b`q-)7b!wIC>Yqg(dAu5>#4756D?8v9^F8&r?0_K!&uj=Yhx5U^H2lS!$9K zAj$gC^Y!^)t`f*GH5u)M_q&_1W5ef#pyM0<^{L3c@5k5Gv!2uWAtn0IB z^f;`z-%bFw-!L$>u-IIC*SYP*4}wYtzVI`+=o>L#NF`WEr4b`=W;#m*OvdbDh*HS~ ztVpWVsV){jqQ3RD%jc(;-X=;L&86@K`a7x)20t=VdUJD5Ol_RNFt8mrU(@*0oI$pR zj99}q>;=oAj1LXZz?srdhA;6l^e^v#_Vcvypa3)=8kQykA)yb$iy7T%0D9(Q+A`&+ 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%0DRvc%+KBr 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�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 RnxIp>OI!cWO<~#}`xgci1u6gl literal 0 HcmV?d00001 diff --git a/tutorial/getting_started.md b/tutorial/getting_started.md new file mode 100644 index 00000000..5cd95a03 --- /dev/null +++ b/tutorial/getting_started.md @@ -0,0 +1,177 @@ +# 🎉 Getting Started + +Welcome to the BasicTS tutorial! This guide will walk you through the steps of training and evaluating a model using BasicTS. + +Before diving in, let’s take a moment to introduce BasicTS. + +***What is BasicTS?*** + +> [!IMPORTANT] +> BasicTS is a powerful and flexible tool designed specifically for time series forecasting. Whether you are new to this field or an experienced professional, BasicTS provides reliable support. With BasicTS, you can effortlessly build, train, and evaluate your time series forecasting models. You can also compare the performance of various models to find the best solution. We have integrated over 30 algorithms and 20 datasets, with more being added continuously. + +***Who Should Use BasicTS?*** + +> [!IMPORTANT] +> BasicTS is perfect for beginners looking to enter the world of time series forecasting. Its simplicity allows you to quickly grasp the basic pipeline and build your own forecasting model. For experts, BasicTS offers a robust platform for rigorous model comparison, ensuring precise research and development. + +***Core Features*** + +> [!IMPORTANT] +> Two key features define BasicTS: **fairness** and **scalability**. All models are trained and evaluated under the same conditions, eliminating biases introduced by external factors. This ensures trustworthy comparisons. Additionally, BasicTS is highly scalable, allowing customization of datasets, model structures, and metrics according to your needs. For example, to add a learning rate scheduler, simply specify `CFG.TRAIN.LR_SCHEDULER.TYPE = 'MultiStepLR'` in the configuration file. + +Now, let’s begin our journey and explore how BasicTS can enhance your time series forecasting projects! + +## ⏬ Cloning the Repository + +First, clone the BasicTS repository: + +```bash +cd /path/to/your/project +git clone https://github.com/zezhishao/BasicTS.git +``` + +## 💿 Installing Dependencies + +### Operating System + +We recommend using BasicTS on Linux systems (e.g., Ubuntu or CentOS). + +### Python + +Python 3.6 or higher is required (3.8 or higher is recommended). + +We recommend using [Miniconda](https://docs.conda.io/en/latest/miniconda.html) or [Anaconda](https://www.anaconda.com/) to create a virtual Python environment. + +### PyTorch + +BasicTS is flexible regarding the PyTorch version. You can [install PyTorch](https://pytorch.org/get-started/previous-versions/) according to your Python version. We recommend using `pip` for installation. + +### Other Dependencies + +After ensuring PyTorch is installed correctly, you can install the other dependencies: + +```bash +pip install -r requirements.txt +``` + +### Example Setups + +#### Example 1: Python 3.9 + PyTorch 1.10.0 + CUDA 11.1 + +```bash +# Install Python +conda create -n BasicTS python=3.9 +conda activate BasicTS +# Install PyTorch +pip install torch==1.10.0+cu111 torchvision==0.11.0+cu111 torchaudio==0.10.0 -f https://download.pytorch.org/whl/torch_stable.html +# Install other dependencies +pip install -r requirements.txt +``` + +#### Example 2: Python 3.11 + PyTorch 2.3.1 + CUDA 11.8 + +```bash +# Install Python +conda create -n BasicTS python=3.11 +conda activate BasicTS +# Install PyTorch +pip install torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 --index-url https://download.pytorch.org/whl/cu118 +# Install other dependencies +pip install -r requirements.txt +``` + +## 📦 Downloading Datasets + +You can download the `all_data.zip` file from [Google Drive](https://drive.google.com/drive/folders/14EJVODCU48fGK0FkyeVom_9lETh80Yjp?usp=sharing) or [Baidu Netdisk](https://pan.baidu.com/s/1shA2scuMdZHlx6pj35Dl7A?pwd=s2xe). Unzip the files to the `datasets/` directory: + +```bash +cd /path/to/BasicTS +unzip /path/to/all_data.zip -d datasets/ +mv datasets/all_data/* datasets/ +rmdir datasets/all_data +``` + +These datasets have been preprocessed and are ready for use. + +> [!NOTE] +> The `data.dat` file is an array in `numpy.memmap` format that stores the raw time series data with a shape of [L, N, C], where L is the number of time steps, N is the number of time series, and C is the number of features. +> +> The `desc.json` file is a dictionary that stores the dataset’s metadata, including the dataset name, domain, frequency, feature descriptions, regular settings, and null values. +> +> Other files are optional and may contain additional information, such as `adj_mx.pkl`, which represents a predefined prior graph between the time series. + +> [!NOTE] +> If you are interested in the preprocessing steps, you can refer to the [preprocessing script](../scripts/data_preparation) and `raw_data.zip`. + +## 🎯 Quick Tutorial: Train & Evaluate Your Model in Three Steps + +### Step 1: Define Your Model + +The `forward` function should follow the conventions of BasicTS. An example of the Multi-Layer Perceptron (`MLP`) model can be found in [examples/arch.py](../examples/arch.py). + +### Step 2: Define Your Runner + +BasicTS provides a unified and standard pipeline in `basicts.runner.BaseTimeSeriesForecastingRunner`. You still need to define the specific forward process in the `forward` function within the **runner**. Fortunately, BasicTS provides a ready-to-use implementation in `basicts.runner.SimpleTimeSeriesForecastingRunner`, which can handle most situations. The runner for the `MLP` model can use this built-in runner. + +### Step 3: Configure Your Configuration File + +All pipeline details and hyperparameters can be configured in a `.py` file. This configuration file allows you to import your model and runner and set all the options such as model, runner, dataset, scaler, optimizer, loss, and other hyperparameters. An example configuration file for the `MLP` model on the `PEMS08` dataset can be found in [examples/regular_config.py](../examples/regular_config.py). + +> [!NOTE] +> The configuration file is the core of training and evaluation in BasicTS. [`Examples/complete_config.py`](../examples/complete_config.py) outlines all the options available for configuration. + +## 🥳 Run It! + +`basicts.launch_training` is the entry point for training. You can run the following command to train your model: + +- **Train the MLP Model Mentioned Above** + + ```bash + python experiments/train.py -c examples/regular_config.py -g 0 + ``` + +or: + +- **Reproducing Other Built-in Models** + + BasicTS provides a variety of built-in models. You can reproduce these models with the following command: + + ```bash + python experiments/train.py -c baselines/${MODEL_NAME}/${DATASET_NAME}.py --gpus '0' + ``` + + Replace `${DATASET_NAME}` and `${MODEL_NAME}` with any supported models and datasets. For example, to run Graph WaveNet on the METR-LA dataset: + + ```bash + python experiments/train.py -c baselines/GWNet/METR-LA.py --gpus '0' + ``` + +## How to Evaluate Your Model + +`basicts.launch_evaluation` is the entry point for evaluation. You can run the following command to evaluate your model: + +```bash +python experiments/evaluate.py -cfg {CONFIG_FILE}.py -ckpt {CHECKPOINT_PATH}.pth -g 0 +``` + +## 🧑‍💻 Explore Further + +This tutorial has equipped you with the fundamentals to get started with BasicTS, but there’s much more to discover. Before delving into advanced topics, let’s take a closer look at the structure of BasicTS: + +

+ +The core components of BasicTS include `Dataset`, `Scaler`, `Model`, `Metrics`, `Runner`, and `Config`. To streamline the debugging process, BasicTS operates as a localized framework, meaning all the code runs directly on your machine. There’s no need to pip install basicts; simply clone the repository, and you’re ready to run the code locally. + +Below are some advanced topics and additional features to help you maximize the potential of BasicTS: + +- **🎉 [Getting Stared](./getting_started.md)** +- **💡 [Understanding the Overall Design Convention of BasicTS](./overall_design.md)** +- **📦 [Exploring the Dataset Convention and Customizing Your Own Dataset](./dataset_design.md)** +- **🛠️ [Navigating The Scaler Convention and Designing Your Own Scaler](./scaler_design.md)** +- **🧠 [Diving into the Model Convention and Creating Your Own Model](./model_design.md)** +- **📉 [Examining the Metrics Convention and Developing Your Own Loss & Metrics](./metrics_design.md)** +- **🏃‍♂️ [Mastering The Runner Convention and Building Your Own Runner](./runner_design.md)** +- **📜 [Interpreting the Config File Convention and Customizing Your Configuration](./config_design.md)** +- **🔍 [Exploring a Variety of Baseline Models](../baselines/)** diff --git a/tutorial/metrics_design.md b/tutorial/metrics_design.md new file mode 100644 index 00000000..0ff911cf --- /dev/null +++ b/tutorial/metrics_design.md @@ -0,0 +1,66 @@ +# 📉 Metrics Design + +## Interface Conventions + +Metrics are essential components for evaluating model performance. In BasicTS, metrics are functions that take the model's predictions, ground truth values, and other parameters as inputs and return a scalar value to assess the model's performance. + +A well-defined metric function should include the following parameters: +- **prediction**: The predicted values from the model +- **target**: The actual ground truth values +- **null_val**: An optional parameter to handle missing values + +The `prediction` and `target` parameters are mandatory, while the `null_val` parameter is optional but strongly recommended for handling missing values, which are common in time series data. At the very least, set it to `np.nan`. + +Metric functions can also accept additional parameters, which are extracted from the model's return values and passed to the metric function. + +> [!CAUTION] +> If these additional parameters (besides `prediction`, `target`, and `inputs`) require normalization or denormalization, please adjust the `preprocessing` and `postprocessing` functions in the `runner` accordingly. + +## Built-in Metrics in BasicTS + +BasicTS comes with several commonly used metrics, such as `MAE`, `MSE`, `RMSE`, `MAPE`, and `WAPE`. You can find these metrics implemented in the `basicts/metrics` directory. + +## How to Implement Custom Metrics + +Following the guidelines outlined in the Interface Conventions section, you can easily implement custom metrics. Here’s an example: + +```python +class MyModel: + def __init__(self): + # Initialize the model + ... + + def forward(...): + # Forward computation + ... + return { + 'prediction': prediction, + 'target': target, + 'other_key1': other_value1, + 'other_key2': other_value2, + 'other_key3': other_value3, + ... + } + +def my_metric_1(prediction, target, null_val=np.nan, other_key1=None, other_key2=None, ...): + # Calculate the metric + ... + +def my_metric_2(prediction, target, null_val=np.nan, other_key3=None, ...): + # Calculate the metric + ... +``` + +By adhering to these conventions, you can flexibly customize and extend the metrics in BasicTS to meet specific requirements. + +## 🧑‍💻 Explore Further + +- **🎉 [Getting Stared](./getting_started.md)** +- **💡 [Understanding the Overall Design Convention of BasicTS](./overall_design.md)** +- **📦 [Exploring the Dataset Convention and Customizing Your Own Dataset](./dataset_design.md)** +- **🛠️ [Navigating The Scaler Convention and Designing Your Own Scaler](./scaler_design.md)** +- **🧠 [Diving into the Model Convention and Creating Your Own Model](./model_design.md)** +- **📉 [Examining the Metrics Convention and Developing Your Own Loss & Metrics](./metrics_design.md)** +- **🏃‍♂️ [Mastering The Runner Convention and Building Your Own Runner](./runner_design.md)** +- **📜 [Interpreting the Config File Convention and Customizing Your Configuration](./config_design.md)** +- **🔍 [Exploring a Variety of Baseline Models](../baselines/)** diff --git a/tutorial/model_design.md b/tutorial/model_design.md new file mode 100644 index 00000000..1545be50 --- /dev/null +++ b/tutorial/model_design.md @@ -0,0 +1,41 @@ +# 🧠 Model Design + +The `forward` function of your model should adhere to the conventions established by BasicTS. + +## 🪴 Input Interface + +BasicTS will pass the following arguments to the `forward` function of your model: + +- **history_data** (`torch.Tensor`): Historical data with shape `[B, L, N, C]`, where `B` represents the batch size, `L` is the sequence length, `N` is the number of nodes, and `C` is the number of features. +- **future_data** (`torch.Tensor` or `None`): Future data with shape `[B, L, N, C]`. This can be `None` if future data is not available (e.g., during the testing phase). +- **batch_seen** (`int`): The number of batches processed so far. +- **epoch** (`int`): The current epoch number. +- **train** (`bool`): Indicates whether the model is in training mode. + +## 🌷 Output Interface + +The output of the `forward` function can be a `torch.Tensor` representing the predicted values with shape `[B, L, N, C]`, where typically `C=1`. + +Alternatively, the model can return a dictionary that must include the key `prediction`, which contains the predicted values as described above. This dictionary can also include additional custom keys that correspond to arguments for the loss and metrics functions. + +An example can be found in the [Multi-Layer Perceptron (MLP) model](../examples/arch.py). + +## 🥳 Supported Baslines + +BasicTS provides a variety of built-in models. You can find them in [baselines](../baselines/) folder. To run a baseline model, use the following command: + +```bash +python experiments/train.py -c baselines/${MODEL_NAME}/${DATASET_NAME}.py --gpus '{GPU_IDs}' +``` + +## 🧑‍💻 Explore Further + +- **🎉 [Getting Stared](./getting_started.md)** +- **💡 [Understanding the Overall Design Convention of BasicTS](./overall_design.md)** +- **📦 [Exploring the Dataset Convention and Customizing Your Own Dataset](./dataset_design.md)** +- **🛠️ [Navigating The Scaler Convention and Designing Your Own Scaler](./scaler_design.md)** +- **🧠 [Diving into the Model Convention and Creating Your Own Model](./model_design.md)** +- **📉 [Examining the Metrics Convention and Developing Your Own Loss & Metrics](./metrics_design.md)** +- **🏃‍♂️ [Mastering The Runner Convention and Building Your Own Runner](./runner_design.md)** +- **📜 [Interpreting the Config File Convention and Customizing Your Configuration](./config_design.md)** +- **🔍 [Exploring a Variety of Baseline Models](../baselines/)** diff --git a/tutorial/overall_design.md b/tutorial/overall_design.md new file mode 100644 index 00000000..6f89c043 --- /dev/null +++ b/tutorial/overall_design.md @@ -0,0 +1,33 @@ +# 💡 The Overall Design of BasicTS + +
+ +
+ +In a time series forecasting pipeline, the following key components are typically involved: + +- **Dataset**: Specifies the methods for reading datasets and generating samples. (Located in `basicts.data`) +- **Scaler**: Manages the normalization and denormalization of datasets, supporting techniques such as Z-score and Min-Max normalization. (Located in `basicts.scaler`) +- **Metrics**: Defines the evaluation metrics and loss functions, including MAE, MSE, MAPE, RMSE, and WAPE. (Located in `basicts.metrics`) +- **Runner**: The core module of BasicTS, responsible for orchestrating the entire training process. The Runner integrates components such as Dataset, Scaler, Model, and Metrics, and provides a wide range of features including multi-GPU training, distributed training, persistent logging, model auto-saving, curriculum learning, and gradient clipping. (Located in `basicts.runner`) +- **Model**: Defines the model architecture and the forward propagation process. + +BasicTS includes a comprehensive and extensible set of components, enabling users to complete most tasks simply by providing a model structure. + +To streamline the configuration of training strategies and centralize all options for easy and fair comparisons, BasicTS follows an **everything-based-on-config** design philosophy. + +Users can easily configure models, datasets, scaling methods, evaluation metrics, optimizers, learning rates, and other hyperparameters by modifying the configuration file—**as simple as filling out a form**. + +For example, setting `CFG.TRAIN.EARLY_STOPPING_PATIENCE = 10` enables early stopping with a patience level of 10. + +## 🧑‍💻 Explore Further + +- **🎉 [Getting Stared](./getting_started.md)** +- **💡 [Understanding the Overall Design Convention of BasicTS](./overall_design.md)** +- **📦 [Exploring the Dataset Convention and Customizing Your Own Dataset](./dataset_design.md)** +- **🛠️ [Navigating The Scaler Convention and Designing Your Own Scaler](./scaler_design.md)** +- **🧠 [Diving into the Model Convention and Creating Your Own Model](./model_design.md)** +- **📉 [Examining the Metrics Convention and Developing Your Own Loss & Metrics](./metrics_design.md)** +- **🏃‍♂️ [Mastering The Runner Convention and Building Your Own Runner](./runner_design.md)** +- **📜 [Interpreting the Config File Convention and Customizing Your Configuration](./config_design.md)** +- **🔍 [Exploring a Variety of Baseline Models](../baselines/)** diff --git a/tutorial/runner_design.md b/tutorial/runner_design.md new file mode 100644 index 00000000..caef43c1 --- /dev/null +++ b/tutorial/runner_design.md @@ -0,0 +1,111 @@ +# 🏃‍♂️ Runner + +## 💿 Overview + +The Runner is the core component of BasicTS, responsible for managing the entire training and evaluation process. It integrates various subcomponents, such as the Dataset, Scaler, Model, Metrics, and Config, to create a fair and standardized training workflow. The Runner provides several advanced features, including but not limited to: + +- Early stopping +- Curriculum learning +- Gradient clipping +- Automatic model saving +- Multi-GPU training +- Persistent logging + +The Runner can be used for both training and evaluating models. + +## ⚡️ Training Pipeline + +The typical training process using the Runner follows this structure: + +```python +# Initialization +runner = Runner(config) # Includes scaler, model, metrics, loss, optimizer, etc. + +# Training +runner.train(config) +``` + +The `runner.train` method operates as follows: + +```python +def train(config): + init_training(config) # Initialize training/validation/test dataloaders + for epoch in train_epochs: + on_epoch_start(epoch) + for data in train_dataloader: + loss = train_iters(data) + optimize(loss) # Includes backpropagation, learning rate scheduling, gradient clipping, etc. + on_epoch_end(epoch) + on_training_end(config) +``` + +### Hook Functions + +The Runner provides hook functions like `on_epoch_start`, `on_epoch_end`, and `on_training_end`, allowing users to implement custom logic. For example, `on_epoch_end` can be used to evaluate validation and test sets and save intermediate models, while `on_training_end` is typically used for final evaluations and saving the final model and results. + +### Training Iterations + +The flow within `runner.train_iters` is structured as follows: + +```python +def train_iters(data): + data = runner.preprocessing(data) # Normalize data + forward_return = runner.forward(data) # Forward pass + forward_return = runner.postprocessing(forward_return) # Denormalize results + loss = runner.loss(forward_return) # Calculate loss + metrics = runner.metrics(forward_return) # Calculate metrics + return loss +``` + +By default, `runner.preprocessing` normalizes only the `inputs` and `target`. If additional parameters from the Dataset require normalization, you need to customize the `runner.preprocessing` function. Similarly, `runner.postprocessing` denormalizes the `inputs`, `target`, and `prediction` by default. If more parameters need denormalization, customize the `runner.postprocessing` function. + +The `runner.forward` function handles data input to the model and packages the model's output into a dictionary containing `prediction`, `inputs`, `target`, and any other parameters needed for metrics calculation. + +## ✨ Evaluation Pipeline + +When evaluating a model using a checkpoint, the process generally follows this structure: + +```python +# Initialization +runner = Runner(config) # Includes scaler, model, metrics, loss, optimizer, etc. + +# Load checkpoint +runner.load_model(checkpoint) + +# Evaluation +runner.test_pipeline(config) +``` + +The `runner.test_pipeline` method operates as follows: + +```python +def test_pipeline(config): + init_testing(config) # Initialize test dataloader + all_data = [] + for data in test_dataloader: + data = runner.preprocessing(data) # Normalize data + forward_return = runner.forward(data) # Forward pass + forward_return = runner.postprocessing(forward_return) # Denormalize results + all_data.append(forward_return) + all_data = concatenate(all_data) + metrics = runner.metrics(all_data) # Calculate metrics + save(forward_return, metrics) # Optional +``` + +## 🛠️ Customizing the Runner + +BasicTS provides a [`SimpleTimeSeriesForecastingRunner`](../basicts/runners/runner_zoo/simple_tsf_runner.py) class that handles most use cases. + +For more specific needs, you can extend the [`SimpleTimeSeriesForecastingRunner`](../basicts/runners/runner_zoo/simple_tsf_runner.py) or [`BaseTimeSeriesForecastingRunner`](../basicts/runners/base_tsf_runner.py) classes to implement functions such as `test`, `forward`, `preprocessing`, `postprocessing`, and `train_iters`. + +## 🧑‍💻 Explore Further + +- **🎉 [Getting Stared](./getting_started.md)** +- **💡 [Understanding the Overall Design Convention of BasicTS](./overall_design.md)** +- **📦 [Exploring the Dataset Convention and Customizing Your Own Dataset](./dataset_design.md)** +- **🛠️ [Navigating The Scaler Convention and Designing Your Own Scaler](./scaler_design.md)** +- **🧠 [Diving into the Model Convention and Creating Your Own Model](./model_design.md)** +- **📉 [Examining the Metrics Convention and Developing Your Own Loss & Metrics](./metrics_design.md)** +- **🏃‍♂️ [Mastering The Runner Convention and Building Your Own Runner](./runner_design.md)** +- **📜 [Interpreting the Config File Convention and Customizing Your Configuration](./config_design.md)** +- **🔍 [Exploring a Variety of Baseline Models](../baselines/)** diff --git a/tutorial/scaler_design.md b/tutorial/scaler_design.md new file mode 100644 index 00000000..17c13c28 --- /dev/null +++ b/tutorial/scaler_design.md @@ -0,0 +1,44 @@ +# 🛠️ Scaler Design + +## 🧐 What is a Scaler and Why Do We Need It? + +A acaler is a class designed to handle the normalization and denormalization of data. In time series analysis, raw data often exhibits significant variations in scale. As a result, models—especially deep learning models—typically do not operate directly on the raw data. Instead, a acaler is used to normalize the data within a specific range, making it more suitable for modeling. When calculating loss functions or evaluation metrics, the data is often denormalized back to its original scale to ensure accurate comparison. + +This makes the acaler an essential component in the time series analysis workflow. + +## 👾 How is the Scaler Initialized and When Does It Function? + +The scaler is initialized alongside other components within the runner. + +For example, a Z-Score scaler reads the raw data and computes the mean and standard deviation based on the training data. + +The scaler functions after the data is extracted from the dataset. The data is first normalized by the scaler before being passed to the model for training. After the model processes the data, the scaler denormalizes the output before it is passed to the runner for loss calculation and metric evaluation. + +> [!IMPORTANT] +> In many time series analysis studies, normalization often occurs during data preprocessing, as was the case in earlier versions of BasicTS. However, this approach is not scalable. Adjustments like changing input/output lengths, applying different normalization ways (e.g., individual normalization for each time series), or altering the training/validation/test split ratios would require re-preprocessing the data. To overcome this limitation, BasicTS adopts an "instant normalization" approach, where data is normalized each time it is extracted. + +```python +# in runner +for data in dataloader: + data = scaler.transform(data) + forward_return = forward(data) + forward_return = scaler.inverse_transform(forward_return) +``` + +## 🧑‍🔧 How to Select or Customize a Scaler + +BasicTS provides several common scalers, such as the Z-Score scaler and Min-Max scaler. You can easily switch scalers by setting `CFG.SCALER.TYPE` in the configuration file. + +If you need to customize a scaler, you can extend the `basicts.scaler.BaseScaler` class and implement the `transform` and `inverse_transform` methods. Alternatively, you can choose not to extend the class but must still implement these two methods. + +## 🧑‍💻 Explore Further + +- **🎉 [Getting Stared](./getting_started.md)** +- **💡 [Understanding the Overall Design Convention of BasicTS](./overall_design.md)** +- **📦 [Exploring the Dataset Convention and Customizing Your Own Dataset](./dataset_design.md)** +- **🛠️ [Navigating The Scaler Convention and Designing Your Own Scaler](./scaler_design.md)** +- **🧠 [Diving into the Model Convention and Creating Your Own Model](./model_design.md)** +- **📉 [Examining the Metrics Convention and Developing Your Own Loss & Metrics](./metrics_design.md)** +- **🏃‍♂️ [Mastering The Runner Convention and Building Your Own Runner](./runner_design.md)** +- **📜 [Interpreting the Config File Convention and Customizing Your Configuration](./config_design.md)** +- **🔍 [Exploring a Variety of Baseline Models](../baselines/)**