bakhshaliyev commited on
Commit
556d303
·
verified ·
1 Parent(s): 5574ae4

added all the files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. README.md +139 -0
  3. fig/TPS_architecture.png +3 -0
  4. results/results.xlsx +3 -0
  5. time_series_classification/MultiRocket/LICENSE +692 -0
  6. time_series_classification/MultiRocket/THIRD_PARTY_LICENSES.txt +221 -0
  7. time_series_classification/MultiRocket/augmentation.py +543 -0
  8. time_series_classification/MultiRocket/dtw.py +226 -0
  9. time_series_classification/MultiRocket/hyperparamter_tune.py +478 -0
  10. time_series_classification/MultiRocket/main.py +467 -0
  11. time_series_classification/MultiRocket/multirocket/__init__.py +5 -0
  12. time_series_classification/MultiRocket/multirocket/logistic_regression.py +208 -0
  13. time_series_classification/MultiRocket/multirocket/multirocket.py +558 -0
  14. time_series_classification/MultiRocket/multirocket/multirocket_multivariate.py +622 -0
  15. time_series_classification/MultiRocket/requirements.txt +6 -0
  16. time_series_classification/MultiRocket/scripts/example.sh +76 -0
  17. time_series_classification/MultiRocket/utils/data_loader.py +319 -0
  18. time_series_classification/MultiRocket/utils/tools.py +154 -0
  19. time_series_classification/minirocket/LICENSE +692 -0
  20. time_series_classification/minirocket/THIRD_PARTY_LICENSES.txt +221 -0
  21. time_series_classification/minirocket/scripts/example.sh +65 -0
  22. time_series_classification/minirocket/src/augmentation.py +415 -0
  23. time_series_classification/minirocket/src/dtw.py +226 -0
  24. time_series_classification/minirocket/src/hyperparameter_tune.py +472 -0
  25. time_series_classification/minirocket/src/main.py +491 -0
  26. time_series_classification/minirocket/src/minirocket.py +234 -0
  27. time_series_classification/minirocket/src/minirocket_dv.py +126 -0
  28. time_series_classification/minirocket/src/minirocket_multivariate.py +283 -0
  29. time_series_classification/minirocket/src/minirocket_multivariate_variable.py +312 -0
  30. time_series_classification/minirocket/src/minirocket_variable.py +296 -0
  31. time_series_classification/minirocket/src/softmax.py +241 -0
  32. time_series_forecasting/data_provider/data_factory.py +120 -0
  33. time_series_forecasting/data_provider/data_loader.py +659 -0
  34. time_series_forecasting/exp/exp_basic.py +40 -0
  35. time_series_forecasting/exp/exp_main.py +493 -0
  36. time_series_forecasting/layers/AutoCorrelation.py +164 -0
  37. time_series_forecasting/layers/Autoformer_EncDec.py +191 -0
  38. time_series_forecasting/layers/Conv_Blocks.py +60 -0
  39. time_series_forecasting/layers/Embed.py +217 -0
  40. time_series_forecasting/layers/PatchTST_backbone.py +379 -0
  41. time_series_forecasting/layers/PatchTST_layers.py +121 -0
  42. time_series_forecasting/layers/RevIN.py +63 -0
  43. time_series_forecasting/layers/SelfAttention_Family.py +224 -0
  44. time_series_forecasting/layers/Transformer_EncDec.py +131 -0
  45. time_series_forecasting/models/Autoformer.py +123 -0
  46. time_series_forecasting/models/CycleNet.py +67 -0
  47. time_series_forecasting/models/DLinear.py +87 -0
  48. time_series_forecasting/models/Informer.py +101 -0
  49. time_series_forecasting/models/LightTS.py +114 -0
  50. time_series_forecasting/models/Linear.py +21 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ fig/TPS_architecture.png filter=lfs diff=lfs merge=lfs -text
37
+ results/results.xlsx filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Temporal Patch Shuffle (TPS): Leveraging Patch-Level Shuffling to Boost Generalization and Robustness in Time Series Forecasting [![arXiv](https://img.shields.io/badge/arXiv-Paper-B31B1B.svg)](https://arxiv.org/abs/2604.09067)
2
+
3
+
4
+ ## Abstract
5
+ Data augmentation is a crucial technique for improving model generalization and robustness, particularly in deep learning models where training data is limited. Although many augmentation methods have been developed for time series classification, most are not directly applicable to time series forecasting due to the need to preserve temporal coherence. In this work, we propose Temporal Patch Shuffle (TPS), a simple and model-agnostic data augmentation method for forecasting that extracts overlapping temporal patches, selectively shuffles a subset of patches using variance-based ordering as a conservative heuristic, and reconstructs the sequence by averaging overlapping regions. This design increases sample diversity while preserving forecast-consistent local temporal structure. We extensively evaluate TPS across nine long-term forecasting datasets using five recent model families (TSMixer, DLinear, PatchTST, TiDE, and LightTS), and across four short-term forecasting datasets using PatchTST, observing consistent performance improvements. Comprehensive ablation studies further demonstrate the effectiveness, robustness, and design rationale of the proposed method.
6
+
7
+ ## Key Contributions
8
+ - **TPS (Temporal Patch Shuffle)**:
9
+ - Time Series Forecasting implementation: `time_series_forecasting/utils/augmentations.py`
10
+ - Univariate classification implementation (MiniRocket): `time_series_classification/minirocket/src/augmentation.py`
11
+ - Multivariate classification implementation (MultiRocket): `time_series_classification/MultiRocket/augmentation.py`
12
+
13
+ ![TPS Architecture](fig/TPS_architecture.png)
14
+
15
+ ## Repository Structure
16
+ - `time_series_forecasting/`: forecasting models + augmentation pipeline (TPS and others)
17
+ - `time_series_classification/`:
18
+ - `minirocket/`: univariate classification (MiniRocket) + augmentations (TPS and others)
19
+ - `MultiRocket/`: multivariate classification (MultiRocket) + augmentations (TPS and others)
20
+
21
+ ## Quick Start
22
+
23
+ ### 1) Time Series Forecasting
24
+
25
+ #### Dataset
26
+ Download all forecasting datasets from:
27
+ - https://drive.google.com/drive/folders/1ZOYpTUa82_jCcxIdTmyr0LXQfvaM9vIy
28
+
29
+ Create the dataset folder and put the downloaded files inside:
30
+ ```bash
31
+ mkdir -p time_series_forecasting/dataset
32
+ ```
33
+
34
+ #### Install & Run
35
+ ```bash
36
+ git clone https://github.com/jafarbakhshaliyev/TPS.git
37
+ cd TPS
38
+
39
+ python3 -m venv .venv_forecasting
40
+ source .venv_forecasting/bin/activate
41
+
42
+ pip install -r time_series_forecasting/requirements.txt
43
+
44
+ # Install PyTorch (choose the right command for your CUDA/CPU setup):
45
+ # https://pytorch.org/get-started/locally/
46
+
47
+ bash time_series_forecasting/scripts/example.sh
48
+ ```
49
+
50
+ Notes:
51
+ - `time_series_forecasting/scripts/example.sh` now runs relative to the repo
52
+ - The training entrypoint is `time_series_forecasting/run_longExp.py`.
53
+
54
+ ### 2) Univariate Time Series Classification (MiniRocket)
55
+
56
+ #### Dataset (UCR Archive)
57
+ - https://www.cs.ucr.edu/~eamonn/time_series_data_2018/
58
+
59
+ The MiniRocket code expects TSV files under a folder you set in `time_series_classification/minirocket/src/main.py` via `UCR_PATH`.
60
+
61
+ #### Install & Run
62
+ ```bash
63
+ cd TPS
64
+
65
+ python3 -m venv .venv_minirocket
66
+ source .venv_minirocket/bin/activate
67
+
68
+ pip install numpy pandas scikit-learn tqdm
69
+
70
+ # Run the provided script (SLURM `srun`):
71
+ bash time_series_classification/minirocket/scripts/example.sh
72
+
73
+ # If you're not on SLURM, run directly:
74
+ cd time_series_classification/minirocket
75
+ python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup
76
+ ```
77
+
78
+ ### 3) Multivariate Time Series Classification (MultiRocket)
79
+
80
+ #### Dataset (UEA Multivariate Archive)
81
+ - https://www.timeseriesclassification.com/index.php
82
+
83
+ Place `.ts` files under (recommended):
84
+ ```bash
85
+ mkdir -p time_series_classification/MultiRocket/data/Multivariate_ts
86
+ ```
87
+
88
+ #### Install & Run
89
+ MultiRocket has its own pinned dependencies in `time_series_classification/MultiRocket/requirements.txt`.
90
+
91
+ ```bash
92
+ cd TPS
93
+
94
+ python3 -m venv .venv_multirocket
95
+ source .venv_multirocket/bin/activate
96
+
97
+ pip install -r time_series_classification/MultiRocket/requirements.txt
98
+
99
+ # The provided script uses SLURM `srun` and contains cluster-specific paths.
100
+ # For a portable local run, prefer calling `main.py` directly:
101
+ cd time_series_classification/MultiRocket
102
+ python3 main.py --datapath ./data/Multivariate_ts --problem FaceDetection --iter 5 --verbose 1
103
+ ```
104
+
105
+ ## Augmentation Code (Direct Links)
106
+ - Forecasting augmentations (TPS + others):
107
+ - [`time_series_forecasting/utils/augmentations.py`](time_series_forecasting/utils/augmentations.py)
108
+ - MiniRocket augmentations (TPS + others):
109
+ - [`time_series_classification/minirocket/src/augmentation.py`](time_series_classification/minirocket/src/augmentation.py)
110
+ - MultiRocket augmentations (TPS + others):
111
+ - [`time_series_classification/MultiRocket/augmentation.py`](time_series_classification/MultiRocket/augmentation.py)
112
+
113
+ ## Results
114
+
115
+ Headline numbers from the tables:
116
+ - Long-term forecasting (9 datasets × 4 horizons): TPS is rank-1 in most settings (e.g., **DLinear**: 35/36 wins for MSE; 34/36 wins for MAE).
117
+ - Short-term traffic forecasting (PeMS03/04/07/08 with PatchTST): TPS wins on most metrics (e.g., PeMS03: MSE/MAE **0.104/0.216**).
118
+ - Classification (mean ± std): TPS improves both univariate MiniRocket (**0.804 ± 0.0098**) and multivariate MultiRocket (**0.643 ± 0.0253**).
119
+
120
+ ## Citation
121
+ If you find this repository useful, please cite our paper:
122
+
123
+ ```bibtex
124
+ @misc{bakhshaliyev2026temporalpatchshuffletps,
125
+ title={Temporal Patch Shuffle (TPS): Leveraging Patch-Level Shuffling to Boost Generalization and Robustness in Time Series Forecasting},
126
+ author={Jafar Bakhshaliyev and Johannes Burchert and Niels Landwehr and Lars Schmidt-Thieme},
127
+ year={2026},
128
+ eprint={2604.09067},
129
+ archivePrefix={arXiv},
130
+ primaryClass={cs.LG},
131
+ url={https://arxiv.org/abs/2604.09067},
132
+ }
133
+ ```
134
+
135
+ ## Acknowledgements
136
+ - Forecasting code credits: see headers in `time_series_forecasting/` files and references in `time_series_forecasting/`.
137
+ - MiniRocket is modified from the official implementation (see `time_series_classification/minirocket/LICENSE`).
138
+ - MultiRocket is modified from the official implementation (see `time_series_classification/MultiRocket/LICENSE`).
139
+ - Third-party attributions: `THIRD_PARTY_LICENSES.txt` inside each classification submodule.
fig/TPS_architecture.png ADDED

Git LFS Details

  • SHA256: 8cc394dd6a7ce082edd08003991292c659cef844f61e6c1f6c1d7de0b06e6a29
  • Pointer size: 131 Bytes
  • Size of remote file: 118 kB
results/results.xlsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d5a6ceaa68aaeb6e98fe43aae4018cb53f7349d297290ae93c7424f08de9c199
3
+ size 102945
time_series_classification/MultiRocket/LICENSE ADDED
@@ -0,0 +1,692 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This project is licensed under the GNU General Public License v3.0 (GPL-3.0)
2
+
3
+ Original Authors:
4
+ - Chang Wei Tan
5
+ - Angus Dempster
6
+ - Christoph Bergmeir
7
+ - Geoffrey I. Webb
8
+ Original repository: https://github.com/ChangWeiTan/MultiRocket
9
+
10
+ Modifications by:
11
+ - Jafar Bakhshaliyev, 2025
12
+
13
+ This project includes modifications to the original MultiRocket codebase.
14
+ All changes are released under the same GPL-3.0 license.
15
+
16
+ -----------------------------------------------------------------------
17
+
18
+
19
+ GNU GENERAL PUBLIC LICENSE
20
+ Version 3, 29 June 2007
21
+
22
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
23
+ Everyone is permitted to copy and distribute verbatim copies
24
+ of this license document, but changing it is not allowed.
25
+
26
+ Preamble
27
+
28
+ The GNU General Public License is a free, copyleft license for
29
+ software and other kinds of works.
30
+
31
+ The licenses for most software and other practical works are designed
32
+ to take away your freedom to share and change the works. By contrast,
33
+ the GNU General Public License is intended to guarantee your freedom to
34
+ share and change all versions of a program--to make sure it remains free
35
+ software for all its users. We, the Free Software Foundation, use the
36
+ GNU General Public License for most of our software; it applies also to
37
+ any other work released this way by its authors. You can apply it to
38
+ your programs, too.
39
+
40
+ When we speak of free software, we are referring to freedom, not
41
+ price. Our General Public Licenses are designed to make sure that you
42
+ have the freedom to distribute copies of free software (and charge for
43
+ them if you wish), that you receive source code or can get it if you
44
+ want it, that you can change the software or use pieces of it in new
45
+ free programs, and that you know you can do these things.
46
+
47
+ To protect your rights, we need to prevent others from denying you
48
+ these rights or asking you to surrender the rights. Therefore, you have
49
+ certain responsibilities if you distribute copies of the software, or if
50
+ you modify it: responsibilities to respect the freedom of others.
51
+
52
+ For example, if you distribute copies of such a program, whether
53
+ gratis or for a fee, you must pass on to the recipients the same
54
+ freedoms that you received. You must make sure that they, too, receive
55
+ or can get the source code. And you must show them these terms so they
56
+ know their rights.
57
+
58
+ Developers that use the GNU GPL protect your rights with two steps:
59
+ (1) assert copyright on the software, and (2) offer you this License
60
+ giving you legal permission to copy, distribute and/or modify it.
61
+
62
+ For the developers' and authors' protection, the GPL clearly explains
63
+ that there is no warranty for this free software. For both users' and
64
+ authors' sake, the GPL requires that modified versions be marked as
65
+ changed, so that their problems will not be attributed erroneously to
66
+ authors of previous versions.
67
+
68
+ Some devices are designed to deny users access to install or run
69
+ modified versions of the software inside them, although the manufacturer
70
+ can do so. This is fundamentally incompatible with the aim of
71
+ protecting users' freedom to change the software. The systematic
72
+ pattern of such abuse occurs in the area of products for individuals to
73
+ use, which is precisely where it is most unacceptable. Therefore, we
74
+ have designed this version of the GPL to prohibit the practice for those
75
+ products. If such problems arise substantially in other domains, we
76
+ stand ready to extend this provision to those domains in future versions
77
+ of the GPL, as needed to protect the freedom of users.
78
+
79
+ Finally, every program is threatened constantly by software patents.
80
+ States should not allow patents to restrict development and use of
81
+ software on general-purpose computers, but in those that do, we wish to
82
+ avoid the special danger that patents applied to a free program could
83
+ make it effectively proprietary. To prevent this, the GPL assures that
84
+ patents cannot be used to render the program non-free.
85
+
86
+ The precise terms and conditions for copying, distribution and
87
+ modification follow.
88
+
89
+ TERMS AND CONDITIONS
90
+
91
+ 0. Definitions.
92
+
93
+ "This License" refers to version 3 of the GNU General Public License.
94
+
95
+ "Copyright" also means copyright-like laws that apply to other kinds of
96
+ works, such as semiconductor masks.
97
+
98
+ "The Program" refers to any copyrightable work licensed under this
99
+ License. Each licensee is addressed as "you". "Licensees" and
100
+ "recipients" may be individuals or organizations.
101
+
102
+ To "modify" a work means to copy from or adapt all or part of the work
103
+ in a fashion requiring copyright permission, other than the making of an
104
+ exact copy. The resulting work is called a "modified version" of the
105
+ earlier work or a work "based on" the earlier work.
106
+
107
+ A "covered work" means either the unmodified Program or a work based
108
+ on the Program.
109
+
110
+ To "propagate" a work means to do anything with it that, without
111
+ permission, would make you directly or secondarily liable for
112
+ infringement under applicable copyright law, except executing it on a
113
+ computer or modifying a private copy. Propagation includes copying,
114
+ distribution (with or without modification), making available to the
115
+ public, and in some countries other activities as well.
116
+
117
+ To "convey" a work means any kind of propagation that enables other
118
+ parties to make or receive copies. Mere interaction with a user through
119
+ a computer network, with no transfer of a copy, is not conveying.
120
+
121
+ An interactive user interface displays "Appropriate Legal Notices"
122
+ to the extent that it includes a convenient and prominently visible
123
+ feature that (1) displays an appropriate copyright notice, and (2)
124
+ tells the user that there is no warranty for the work (except to the
125
+ extent that warranties are provided), that licensees may convey the
126
+ work under this License, and how to view a copy of this License. If
127
+ the interface presents a list of user commands or options, such as a
128
+ menu, a prominent item in the list meets this criterion.
129
+
130
+ 1. Source Code.
131
+
132
+ The "source code" for a work means the preferred form of the work
133
+ for making modifications to it. "Object code" means any non-source
134
+ form of a work.
135
+
136
+ A "Standard Interface" means an interface that either is an official
137
+ standard defined by a recognized standards body, or, in the case of
138
+ interfaces specified for a particular programming language, one that
139
+ is widely used among developers working in that language.
140
+
141
+ The "System Libraries" of an executable work include anything, other
142
+ than the work as a whole, that (a) is included in the normal form of
143
+ packaging a Major Component, but which is not part of that Major
144
+ Component, and (b) serves only to enable use of the work with that
145
+ Major Component, or to implement a Standard Interface for which an
146
+ implementation is available to the public in source code form. A
147
+ "Major Component", in this context, means a major essential component
148
+ (kernel, window system, and so on) of the specific operating system
149
+ (if any) on which the executable work runs, or a compiler used to
150
+ produce the work, or an object code interpreter used to run it.
151
+
152
+ The "Corresponding Source" for a work in object code form means all
153
+ the source code needed to generate, install, and (for an executable
154
+ work) run the object code and to modify the work, including scripts to
155
+ control those activities. However, it does not include the work's
156
+ System Libraries, or general-purpose tools or generally available free
157
+ programs which are used unmodified in performing those activities but
158
+ which are not part of the work. For example, Corresponding Source
159
+ includes interface definition files associated with source files for
160
+ the work, and the source code for shared libraries and dynamically
161
+ linked subprograms that the work is specifically designed to require,
162
+ such as by intimate data communication or control flow between those
163
+ subprograms and other parts of the work.
164
+
165
+ The Corresponding Source need not include anything that users
166
+ can regenerate automatically from other parts of the Corresponding
167
+ Source.
168
+
169
+ The Corresponding Source for a work in source code form is that
170
+ same work.
171
+
172
+ 2. Basic Permissions.
173
+
174
+ All rights granted under this License are granted for the term of
175
+ copyright on the Program, and are irrevocable provided the stated
176
+ conditions are met. This License explicitly affirms your unlimited
177
+ permission to run the unmodified Program. The output from running a
178
+ covered work is covered by this License only if the output, given its
179
+ content, constitutes a covered work. This License acknowledges your
180
+ rights of fair use or other equivalent, as provided by copyright law.
181
+
182
+ You may make, run and propagate covered works that you do not
183
+ convey, without conditions so long as your license otherwise remains
184
+ in force. You may convey covered works to others for the sole purpose
185
+ of having them make modifications exclusively for you, or provide you
186
+ with facilities for running those works, provided that you comply with
187
+ the terms of this License in conveying all material for which you do
188
+ not control copyright. Those thus making or running the covered works
189
+ for you must do so exclusively on your behalf, under your direction
190
+ and control, on terms that prohibit them from making any copies of
191
+ your copyrighted material outside their relationship with you.
192
+
193
+ Conveying under any other circumstances is permitted solely under
194
+ the conditions stated below. Sublicensing is not allowed; section 10
195
+ makes it unnecessary.
196
+
197
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
198
+
199
+ No covered work shall be deemed part of an effective technological
200
+ measure under any applicable law fulfilling obligations under article
201
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
202
+ similar laws prohibiting or restricting circumvention of such
203
+ measures.
204
+
205
+ When you convey a covered work, you waive any legal power to forbid
206
+ circumvention of technological measures to the extent such circumvention
207
+ is effected by exercising rights under this License with respect to
208
+ the covered work, and you disclaim any intention to limit operation or
209
+ modification of the work as a means of enforcing, against the work's
210
+ users, your or third parties' legal rights to forbid circumvention of
211
+ technological measures.
212
+
213
+ 4. Conveying Verbatim Copies.
214
+
215
+ You may convey verbatim copies of the Program's source code as you
216
+ receive it, in any medium, provided that you conspicuously and
217
+ appropriately publish on each copy an appropriate copyright notice;
218
+ keep intact all notices stating that this License and any
219
+ non-permissive terms added in accord with section 7 apply to the code;
220
+ keep intact all notices of the absence of any warranty; and give all
221
+ recipients a copy of this License along with the Program.
222
+
223
+ You may charge any price or no price for each copy that you convey,
224
+ and you may offer support or warranty protection for a fee.
225
+
226
+ 5. Conveying Modified Source Versions.
227
+
228
+ You may convey a work based on the Program, or the modifications to
229
+ produce it from the Program, in the form of source code under the
230
+ terms of section 4, provided that you also meet all of these conditions:
231
+
232
+ a) The work must carry prominent notices stating that you modified
233
+ it, and giving a relevant date.
234
+
235
+ b) The work must carry prominent notices stating that it is
236
+ released under this License and any conditions added under section
237
+ 7. This requirement modifies the requirement in section 4 to
238
+ "keep intact all notices".
239
+
240
+ c) You must license the entire work, as a whole, under this
241
+ License to anyone who comes into possession of a copy. This
242
+ License will therefore apply, along with any applicable section 7
243
+ additional terms, to the whole of the work, and all its parts,
244
+ regardless of how they are packaged. This License gives no
245
+ permission to license the work in any other way, but it does not
246
+ invalidate such permission if you have separately received it.
247
+
248
+ d) If the work has interactive user interfaces, each must display
249
+ Appropriate Legal Notices; however, if the Program has interactive
250
+ interfaces that do not display Appropriate Legal Notices, your
251
+ work need not make them do so.
252
+
253
+ A compilation of a covered work with other separate and independent
254
+ works, which are not by their nature extensions of the covered work,
255
+ and which are not combined with it such as to form a larger program,
256
+ in or on a volume of a storage or distribution medium, is called an
257
+ "aggregate" if the compilation and its resulting copyright are not
258
+ used to limit the access or legal rights of the compilation's users
259
+ beyond what the individual works permit. Inclusion of a covered work
260
+ in an aggregate does not cause this License to apply to the other
261
+ parts of the aggregate.
262
+
263
+ 6. Conveying Non-Source Forms.
264
+
265
+ You may convey a covered work in object code form under the terms
266
+ of sections 4 and 5, provided that you also convey the
267
+ machine-readable Corresponding Source under the terms of this License,
268
+ in one of these ways:
269
+
270
+ a) Convey the object code in, or embodied in, a physical product
271
+ (including a physical distribution medium), accompanied by the
272
+ Corresponding Source fixed on a durable physical medium
273
+ customarily used for software interchange.
274
+
275
+ b) Convey the object code in, or embodied in, a physical product
276
+ (including a physical distribution medium), accompanied by a
277
+ written offer, valid for at least three years and valid for as
278
+ long as you offer spare parts or customer support for that product
279
+ model, to give anyone who possesses the object code either (1) a
280
+ copy of the Corresponding Source for all the software in the
281
+ product that is covered by this License, on a durable physical
282
+ medium customarily used for software interchange, for a price no
283
+ more than your reasonable cost of physically performing this
284
+ conveying of source, or (2) access to copy the
285
+ Corresponding Source from a network server at no charge.
286
+
287
+ c) Convey individual copies of the object code with a copy of the
288
+ written offer to provide the Corresponding Source. This
289
+ alternative is allowed only occasionally and noncommercially, and
290
+ only if you received the object code with such an offer, in accord
291
+ with subsection 6b.
292
+
293
+ d) Convey the object code by offering access from a designated
294
+ place (gratis or for a charge), and offer equivalent access to the
295
+ Corresponding Source in the same way through the same place at no
296
+ further charge. You need not require recipients to copy the
297
+ Corresponding Source along with the object code. If the place to
298
+ copy the object code is a network server, the Corresponding Source
299
+ may be on a different server (operated by you or a third party)
300
+ that supports equivalent copying facilities, provided you maintain
301
+ clear directions next to the object code saying where to find the
302
+ Corresponding Source. Regardless of what server hosts the
303
+ Corresponding Source, you remain obligated to ensure that it is
304
+ available for as long as needed to satisfy these requirements.
305
+
306
+ e) Convey the object code using peer-to-peer transmission, provided
307
+ you inform other peers where the object code and Corresponding
308
+ Source of the work are being offered to the general public at no
309
+ charge under subsection 6d.
310
+
311
+ A separable portion of the object code, whose source code is excluded
312
+ from the Corresponding Source as a System Library, need not be
313
+ included in conveying the object code work.
314
+
315
+ A "User Product" is either (1) a "consumer product", which means any
316
+ tangible personal property which is normally used for personal, family,
317
+ or household purposes, or (2) anything designed or sold for incorporation
318
+ into a dwelling. In determining whether a product is a consumer product,
319
+ doubtful cases shall be resolved in favor of coverage. For a particular
320
+ product received by a particular user, "normally used" refers to a
321
+ typical or common use of that class of product, regardless of the status
322
+ of the particular user or of the way in which the particular user
323
+ actually uses, or expects or is expected to use, the product. A product
324
+ is a consumer product regardless of whether the product has substantial
325
+ commercial, industrial or non-consumer uses, unless such uses represent
326
+ the only significant mode of use of the product.
327
+
328
+ "Installation Information" for a User Product means any methods,
329
+ procedures, authorization keys, or other information required to install
330
+ and execute modified versions of a covered work in that User Product from
331
+ a modified version of its Corresponding Source. The information must
332
+ suffice to ensure that the continued functioning of the modified object
333
+ code is in no case prevented or interfered with solely because
334
+ modification has been made.
335
+
336
+ If you convey an object code work under this section in, or with, or
337
+ specifically for use in, a User Product, and the conveying occurs as
338
+ part of a transaction in which the right of possession and use of the
339
+ User Product is transferred to the recipient in perpetuity or for a
340
+ fixed term (regardless of how the transaction is characterized), the
341
+ Corresponding Source conveyed under this section must be accompanied
342
+ by the Installation Information. But this requirement does not apply
343
+ if neither you nor any third party retains the ability to install
344
+ modified object code on the User Product (for example, the work has
345
+ been installed in ROM).
346
+
347
+ The requirement to provide Installation Information does not include a
348
+ requirement to continue to provide support service, warranty, or updates
349
+ for a work that has been modified or installed by the recipient, or for
350
+ the User Product in which it has been modified or installed. Access to a
351
+ network may be denied when the modification itself materially and
352
+ adversely affects the operation of the network or violates the rules and
353
+ protocols for communication across the network.
354
+
355
+ Corresponding Source conveyed, and Installation Information provided,
356
+ in accord with this section must be in a format that is publicly
357
+ documented (and with an implementation available to the public in
358
+ source code form), and must require no special password or key for
359
+ unpacking, reading or copying.
360
+
361
+ 7. Additional Terms.
362
+
363
+ "Additional permissions" are terms that supplement the terms of this
364
+ License by making exceptions from one or more of its conditions.
365
+ Additional permissions that are applicable to the entire Program shall
366
+ be treated as though they were included in this License, to the extent
367
+ that they are valid under applicable law. If additional permissions
368
+ apply only to part of the Program, that part may be used separately
369
+ under those permissions, but the entire Program remains governed by
370
+ this License without regard to the additional permissions.
371
+
372
+ When you convey a copy of a covered work, you may at your option
373
+ remove any additional permissions from that copy, or from any part of
374
+ it. (Additional permissions may be written to require their own
375
+ removal in certain cases when you modify the work.) You may place
376
+ additional permissions on material, added by you to a covered work,
377
+ for which you have or can give appropriate copyright permission.
378
+
379
+ Notwithstanding any other provision of this License, for material you
380
+ add to a covered work, you may (if authorized by the copyright holders of
381
+ that material) supplement the terms of this License with terms:
382
+
383
+ a) Disclaiming warranty or limiting liability differently from the
384
+ terms of sections 15 and 16 of this License; or
385
+
386
+ b) Requiring preservation of specified reasonable legal notices or
387
+ author attributions in that material or in the Appropriate Legal
388
+ Notices displayed by works containing it; or
389
+
390
+ c) Prohibiting misrepresentation of the origin of that material, or
391
+ requiring that modified versions of such material be marked in
392
+ reasonable ways as different from the original version; or
393
+
394
+ d) Limiting the use for publicity purposes of names of licensors or
395
+ authors of the material; or
396
+
397
+ e) Declining to grant rights under trademark law for use of some
398
+ trade names, trademarks, or service marks; or
399
+
400
+ f) Requiring indemnification of licensors and authors of that
401
+ material by anyone who conveys the material (or modified versions of
402
+ it) with contractual assumptions of liability to the recipient, for
403
+ any liability that these contractual assumptions directly impose on
404
+ those licensors and authors.
405
+
406
+ All other non-permissive additional terms are considered "further
407
+ restrictions" within the meaning of section 10. If the Program as you
408
+ received it, or any part of it, contains a notice stating that it is
409
+ governed by this License along with a term that is a further
410
+ restriction, you may remove that term. If a license document contains
411
+ a further restriction but permits relicensing or conveying under this
412
+ License, you may add to a covered work material governed by the terms
413
+ of that license document, provided that the further restriction does
414
+ not survive such relicensing or conveying.
415
+
416
+ If you add terms to a covered work in accord with this section, you
417
+ must place, in the relevant source files, a statement of the
418
+ additional terms that apply to those files, or a notice indicating
419
+ where to find the applicable terms.
420
+
421
+ Additional terms, permissive or non-permissive, may be stated in the
422
+ form of a separately written license, or stated as exceptions;
423
+ the above requirements apply either way.
424
+
425
+ 8. Termination.
426
+
427
+ You may not propagate or modify a covered work except as expressly
428
+ provided under this License. Any attempt otherwise to propagate or
429
+ modify it is void, and will automatically terminate your rights under
430
+ this License (including any patent licenses granted under the third
431
+ paragraph of section 11).
432
+
433
+ However, if you cease all violation of this License, then your
434
+ license from a particular copyright holder is reinstated (a)
435
+ provisionally, unless and until the copyright holder explicitly and
436
+ finally terminates your license, and (b) permanently, if the copyright
437
+ holder fails to notify you of the violation by some reasonable means
438
+ prior to 60 days after the cessation.
439
+
440
+ Moreover, your license from a particular copyright holder is
441
+ reinstated permanently if the copyright holder notifies you of the
442
+ violation by some reasonable means, this is the first time you have
443
+ received notice of violation of this License (for any work) from that
444
+ copyright holder, and you cure the violation prior to 30 days after
445
+ your receipt of the notice.
446
+
447
+ Termination of your rights under this section does not terminate the
448
+ licenses of parties who have received copies or rights from you under
449
+ this License. If your rights have been terminated and not permanently
450
+ reinstated, you do not qualify to receive new licenses for the same
451
+ material under section 10.
452
+
453
+ 9. Acceptance Not Required for Having Copies.
454
+
455
+ You are not required to accept this License in order to receive or
456
+ run a copy of the Program. Ancillary propagation of a covered work
457
+ occurring solely as a consequence of using peer-to-peer transmission
458
+ to receive a copy likewise does not require acceptance. However,
459
+ nothing other than this License grants you permission to propagate or
460
+ modify any covered work. These actions infringe copyright if you do
461
+ not accept this License. Therefore, by modifying or propagating a
462
+ covered work, you indicate your acceptance of this License to do so.
463
+
464
+ 10. Automatic Licensing of Downstream Recipients.
465
+
466
+ Each time you convey a covered work, the recipient automatically
467
+ receives a license from the original licensors, to run, modify and
468
+ propagate that work, subject to this License. You are not responsible
469
+ for enforcing compliance by third parties with this License.
470
+
471
+ An "entity transaction" is a transaction transferring control of an
472
+ organization, or substantially all assets of one, or subdividing an
473
+ organization, or merging organizations. If propagation of a covered
474
+ work results from an entity transaction, each party to that
475
+ transaction who receives a copy of the work also receives whatever
476
+ licenses to the work the party's predecessor in interest had or could
477
+ give under the previous paragraph, plus a right to possession of the
478
+ Corresponding Source of the work from the predecessor in interest, if
479
+ the predecessor has it or can get it with reasonable efforts.
480
+
481
+ You may not impose any further restrictions on the exercise of the
482
+ rights granted or affirmed under this License. For example, you may
483
+ not impose a license fee, royalty, or other charge for exercise of
484
+ rights granted under this License, and you may not initiate litigation
485
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
486
+ any patent claim is infringed by making, using, selling, offering for
487
+ sale, or importing the Program or any portion of it.
488
+
489
+ 11. Patents.
490
+
491
+ A "contributor" is a copyright holder who authorizes use under this
492
+ License of the Program or a work on which the Program is based. The
493
+ work thus licensed is called the contributor's "contributor version".
494
+
495
+ A contributor's "essential patent claims" are all patent claims
496
+ owned or controlled by the contributor, whether already acquired or
497
+ hereafter acquired, that would be infringed by some manner, permitted
498
+ by this License, of making, using, or selling its contributor version,
499
+ but do not include claims that would be infringed only as a
500
+ consequence of further modification of the contributor version. For
501
+ purposes of this definition, "control" includes the right to grant
502
+ patent sublicenses in a manner consistent with the requirements of
503
+ this License.
504
+
505
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
506
+ patent license under the contributor's essential patent claims, to
507
+ make, use, sell, offer for sale, import and otherwise run, modify and
508
+ propagate the contents of its contributor version.
509
+
510
+ In the following three paragraphs, a "patent license" is any express
511
+ agreement or commitment, however denominated, not to enforce a patent
512
+ (such as an express permission to practice a patent or covenant not to
513
+ sue for patent infringement). To "grant" such a patent license to a
514
+ party means to make such an agreement or commitment not to enforce a
515
+ patent against the party.
516
+
517
+ If you convey a covered work, knowingly relying on a patent license,
518
+ and the Corresponding Source of the work is not available for anyone
519
+ to copy, free of charge and under the terms of this License, through a
520
+ publicly available network server or other readily accessible means,
521
+ then you must either (1) cause the Corresponding Source to be so
522
+ available, or (2) arrange to deprive yourself of the benefit of the
523
+ patent license for this particular work, or (3) arrange, in a manner
524
+ consistent with the requirements of this License, to extend the patent
525
+ license to downstream recipients. "Knowingly relying" means you have
526
+ actual knowledge that, but for the patent license, your conveying the
527
+ covered work in a country, or your recipient's use of the covered work
528
+ in a country, would infringe one or more identifiable patents in that
529
+ country that you have reason to believe are valid.
530
+
531
+ If, pursuant to or in connection with a single transaction or
532
+ arrangement, you convey, or propagate by procuring conveyance of, a
533
+ covered work, and grant a patent license to some of the parties
534
+ receiving the covered work authorizing them to use, propagate, modify
535
+ or convey a specific copy of the covered work, then the patent license
536
+ you grant is automatically extended to all recipients of the covered
537
+ work and works based on it.
538
+
539
+ A patent license is "discriminatory" if it does not include within
540
+ the scope of its coverage, prohibits the exercise of, or is
541
+ conditioned on the non-exercise of one or more of the rights that are
542
+ specifically granted under this License. You may not convey a covered
543
+ work if you are a party to an arrangement with a third party that is
544
+ in the business of distributing software, under which you make payment
545
+ to the third party based on the extent of your activity of conveying
546
+ the work, and under which the third party grants, to any of the
547
+ parties who would receive the covered work from you, a discriminatory
548
+ patent license (a) in connection with copies of the covered work
549
+ conveyed by you (or copies made from those copies), or (b) primarily
550
+ for and in connection with specific products or compilations that
551
+ contain the covered work, unless you entered into that arrangement,
552
+ or that patent license was granted, prior to 28 March 2007.
553
+
554
+ Nothing in this License shall be construed as excluding or limiting
555
+ any implied license or other defenses to infringement that may
556
+ otherwise be available to you under applicable patent law.
557
+
558
+ 12. No Surrender of Others' Freedom.
559
+
560
+ If conditions are imposed on you (whether by court order, agreement or
561
+ otherwise) that contradict the conditions of this License, they do not
562
+ excuse you from the conditions of this License. If you cannot convey a
563
+ covered work so as to satisfy simultaneously your obligations under this
564
+ License and any other pertinent obligations, then as a consequence you may
565
+ not convey it at all. For example, if you agree to terms that obligate you
566
+ to collect a royalty for further conveying from those to whom you convey
567
+ the Program, the only way you could satisfy both those terms and this
568
+ License would be to refrain entirely from conveying the Program.
569
+
570
+ 13. Use with the GNU Affero General Public License.
571
+
572
+ Notwithstanding any other provision of this License, you have
573
+ permission to link or combine any covered work with a work licensed
574
+ under version 3 of the GNU Affero General Public License into a single
575
+ combined work, and to convey the resulting work. The terms of this
576
+ License will continue to apply to the part which is the covered work,
577
+ but the special requirements of the GNU Affero General Public License,
578
+ section 13, concerning interaction through a network will apply to the
579
+ combination as such.
580
+
581
+ 14. Revised Versions of this License.
582
+
583
+ The Free Software Foundation may publish revised and/or new versions of
584
+ the GNU General Public License from time to time. Such new versions will
585
+ be similar in spirit to the present version, but may differ in detail to
586
+ address new problems or concerns.
587
+
588
+ Each version is given a distinguishing version number. If the
589
+ Program specifies that a certain numbered version of the GNU General
590
+ Public License "or any later version" applies to it, you have the
591
+ option of following the terms and conditions either of that numbered
592
+ version or of any later version published by the Free Software
593
+ Foundation. If the Program does not specify a version number of the
594
+ GNU General Public License, you may choose any version ever published
595
+ by the Free Software Foundation.
596
+
597
+ If the Program specifies that a proxy can decide which future
598
+ versions of the GNU General Public License can be used, that proxy's
599
+ public statement of acceptance of a version permanently authorizes you
600
+ to choose that version for the Program.
601
+
602
+ Later license versions may give you additional or different
603
+ permissions. However, no additional obligations are imposed on any
604
+ author or copyright holder as a result of your choosing to follow a
605
+ later version.
606
+
607
+ 15. Disclaimer of Warranty.
608
+
609
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
610
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
611
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
612
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
613
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
614
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
615
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
616
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
617
+
618
+ 16. Limitation of Liability.
619
+
620
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
621
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
622
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
623
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
624
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
625
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
626
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
627
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
628
+ SUCH DAMAGES.
629
+
630
+ 17. Interpretation of Sections 15 and 16.
631
+
632
+ If the disclaimer of warranty and limitation of liability provided
633
+ above cannot be given local legal effect according to their terms,
634
+ reviewing courts shall apply local law that most closely approximates
635
+ an absolute waiver of all civil liability in connection with the
636
+ Program, unless a warranty or assumption of liability accompanies a
637
+ copy of the Program in return for a fee.
638
+
639
+ END OF TERMS AND CONDITIONS
640
+
641
+ How to Apply These Terms to Your New Programs
642
+
643
+ If you develop a new program, and you want it to be of the greatest
644
+ possible use to the public, the best way to achieve this is to make it
645
+ free software which everyone can redistribute and change under these terms.
646
+
647
+ To do so, attach the following notices to the program. It is safest
648
+ to attach them to the start of each source file to most effectively
649
+ state the exclusion of warranty; and each file should have at least
650
+ the "copyright" line and a pointer to where the full notice is found.
651
+
652
+ <one line to give the program's name and a brief idea of what it does.>
653
+ Copyright (C) <year> <name of author>
654
+
655
+ This program is free software: you can redistribute it and/or modify
656
+ it under the terms of the GNU General Public License as published by
657
+ the Free Software Foundation, either version 3 of the License, or
658
+ (at your option) any later version.
659
+
660
+ This program is distributed in the hope that it will be useful,
661
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
662
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
663
+ GNU General Public License for more details.
664
+
665
+ You should have received a copy of the GNU General Public License
666
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
667
+
668
+ Also add information on how to contact you by electronic and paper mail.
669
+
670
+ If the program does terminal interaction, make it output a short
671
+ notice like this when it starts in an interactive mode:
672
+
673
+ <program> Copyright (C) <year> <name of author>
674
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
675
+ This is free software, and you are welcome to redistribute it
676
+ under certain conditions; type `show c' for details.
677
+
678
+ The hypothetical commands `show w' and `show c' should show the appropriate
679
+ parts of the General Public License. Of course, your program's commands
680
+ might be different; for a GUI interface, you would use an "about box".
681
+
682
+ You should also get your employer (if you work as a programmer) or school,
683
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
684
+ For more information on this, and how to apply and follow the GNU GPL, see
685
+ <https://www.gnu.org/licenses/>.
686
+
687
+ The GNU General Public License does not permit incorporating your program
688
+ into proprietary programs. If your program is a subroutine library, you
689
+ may consider it more useful to permit linking proprietary applications with
690
+ the library. If this is what you want to do, use the GNU Lesser General
691
+ Public License instead of this License. But first, please read
692
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
time_series_classification/MultiRocket/THIRD_PARTY_LICENSES.txt ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ THIRD PARTY LICENSES
2
+
3
+ This file contains the licenses for third-party components used in this project.
4
+
5
+ ================================================================================
6
+
7
+ Time Series Augmentation Components
8
+
9
+ Some augmentation methods in this repository are derived from or inspired by the
10
+ time_series_augmentation repository:
11
+
12
+ Source: https://github.com/uchidalab/time_series_augmentation
13
+ Authors: Brian Kenji Iwana and Seiichi Uchida
14
+ License: Apache License 2.0
15
+ Files affected: ./time_series_classification/MultiRocket/augmentation.py
16
+
17
+ ================================================================================
18
+
19
+
20
+
21
+ Apache License
22
+ Version 2.0, January 2004
23
+ http://www.apache.org/licenses/
24
+
25
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
26
+
27
+ 1. Definitions.
28
+
29
+ "License" shall mean the terms and conditions for use, reproduction,
30
+ and distribution as defined by Sections 1 through 9 of this document.
31
+
32
+ "Licensor" shall mean the copyright owner or entity authorized by
33
+ the copyright owner that is granting the License.
34
+
35
+ "Legal Entity" shall mean the union of the acting entity and all
36
+ other entities that control, are controlled by, or are under common
37
+ control with that entity. For the purposes of this definition,
38
+ "control" means (i) the power, direct or indirect, to cause the
39
+ direction or management of such entity, whether by contract or
40
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
41
+ outstanding shares, or (iii) beneficial ownership of such entity.
42
+
43
+ "You" (or "Your") shall mean an individual or Legal Entity
44
+ exercising permissions granted by this License.
45
+
46
+ "Source" form shall mean the preferred form for making modifications,
47
+ including but not limited to software source code, documentation
48
+ source, and configuration files.
49
+
50
+ "Object" form shall mean any form resulting from mechanical
51
+ transformation or translation of a Source form, including but
52
+ not limited to compiled object code, generated documentation,
53
+ and conversions to other media types.
54
+
55
+ "Work" shall mean the work of authorship, whether in Source or
56
+ Object form, made available under the License, as indicated by a
57
+ copyright notice that is included in or attached to the work
58
+ (an example is provided in the Appendix below).
59
+
60
+ "Derivative Works" shall mean any work, whether in Source or Object
61
+ form, that is based on (or derived from) the Work and for which the
62
+ editorial revisions, annotations, elaborations, or other modifications
63
+ represent, as a whole, an original work of authorship. For the purposes
64
+ of this License, Derivative Works shall not include works that remain
65
+ separable from, or merely link (or bind by name) to the interfaces of,
66
+ the Work and Derivative Works thereof.
67
+
68
+ "Contribution" shall mean any work of authorship, including
69
+ the original version of the Work and any modifications or additions
70
+ to that Work or Derivative Works thereof, that is intentionally
71
+ submitted to Licensor for inclusion in the Work by the copyright owner
72
+ or by an individual or Legal Entity authorized to submit on behalf of
73
+ the copyright owner. For the purposes of this definition, "submitted"
74
+ means any form of electronic, verbal, or written communication sent
75
+ to the Licensor or its representatives, including but not limited to
76
+ communication on electronic mailing lists, source code control systems,
77
+ and issue tracking systems that are managed by, or on behalf of, the
78
+ Licensor for the purpose of discussing and improving the Work, but
79
+ excluding communication that is conspicuously marked or otherwise
80
+ designated in writing by the copyright owner as "Not a Contribution."
81
+
82
+ "Contributor" shall mean Licensor and any individual or Legal Entity
83
+ on behalf of whom a Contribution has been received by Licensor and
84
+ subsequently incorporated within the Work.
85
+
86
+ 2. Grant of Copyright License. Subject to the terms and conditions of
87
+ this License, each Contributor hereby grants to You a perpetual,
88
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
89
+ copyright license to reproduce, prepare Derivative Works of,
90
+ publicly display, publicly perform, sublicense, and distribute the
91
+ Work and such Derivative Works in Source or Object form.
92
+
93
+ 3. Grant of Patent License. Subject to the terms and conditions of
94
+ this License, each Contributor hereby grants to You a perpetual,
95
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
96
+ (except as stated in this section) patent license to make, have made,
97
+ use, offer to sell, sell, import, and otherwise transfer the Work,
98
+ where such license applies only to those patent claims licensable
99
+ by such Contributor that are necessarily infringed by their
100
+ Contribution(s) alone or by combination of their Contribution(s)
101
+ with the Work to which such Contribution(s) was submitted. If You
102
+ institute patent litigation against any entity (including a
103
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
104
+ or a Contribution incorporated within the Work constitutes direct
105
+ or contributory patent infringement, then any patent licenses
106
+ granted to You under this License for that Work shall terminate
107
+ as of the date such litigation is filed.
108
+
109
+ 4. Redistribution. You may reproduce and distribute copies of the
110
+ Work or Derivative Works thereof in any medium, with or without
111
+ modifications, and in Source or Object form, provided that You
112
+ meet the following conditions:
113
+
114
+ (a) You must give any other recipients of the Work or
115
+ Derivative Works a copy of this License; and
116
+
117
+ (b) You must cause any modified files to carry prominent notices
118
+ stating that You changed the files; and
119
+
120
+ (c) You must retain, in the Source form of any Derivative Works
121
+ that You distribute, all copyright, patent, trademark, and
122
+ attribution notices from the Source form of the Work,
123
+ excluding those notices that do not pertain to any part of
124
+ the Derivative Works; and
125
+
126
+ (d) If the Work includes a "NOTICE" text file as part of its
127
+ distribution, then any Derivative Works that You distribute must
128
+ include a readable copy of the attribution notices contained
129
+ within such NOTICE file, excluding those notices that do not
130
+ pertain to any part of the Derivative Works, in at least one
131
+ of the following places: within a NOTICE text file distributed
132
+ as part of the Derivative Works; within the Source form or
133
+ documentation, if provided along with the Derivative Works; or,
134
+ within a display generated by the Derivative Works, if and
135
+ wherever such third-party notices normally appear. The contents
136
+ of the NOTICE file are for informational purposes only and
137
+ do not modify the License. You may add Your own attribution
138
+ notices within Derivative Works that You distribute, alongside
139
+ or as an addendum to the NOTICE text from the Work, provided
140
+ that such additional attribution notices cannot be construed
141
+ as modifying the License.
142
+
143
+ You may add Your own copyright statement to Your modifications and
144
+ may provide additional or different license terms and conditions
145
+ for use, reproduction, or distribution of Your modifications, or
146
+ for any such Derivative Works as a whole, provided Your use,
147
+ reproduction, and distribution of the Work otherwise complies with
148
+ the conditions stated in this License.
149
+
150
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
151
+ any Contribution intentionally submitted for inclusion in the Work
152
+ by You to the Licensor shall be under the terms and conditions of
153
+ this License, without any additional terms or conditions.
154
+ Notwithstanding the above, nothing herein shall supersede or modify
155
+ the terms of any separate license agreement you may have executed
156
+ with Licensor regarding such Contributions.
157
+
158
+ 6. Trademarks. This License does not grant permission to use the trade
159
+ names, trademarks, service marks, or product names of the Licensor,
160
+ except as required for reasonable and customary use in describing the
161
+ origin of the Work and reproducing the content of the NOTICE file.
162
+
163
+ 7. Disclaimer of Warranty. Unless required by applicable law or
164
+ agreed to in writing, Licensor provides the Work (and each
165
+ Contributor provides its Contributions) on an "AS IS" BASIS,
166
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
167
+ implied, including, without limitation, any warranties or conditions
168
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
169
+ PARTICULAR PURPOSE. You are solely responsible for determining the
170
+ appropriateness of using or redistributing the Work and assume any
171
+ risks associated with Your exercise of permissions under this License.
172
+
173
+ 8. Limitation of Liability. In no event and under no legal theory,
174
+ whether in tort (including negligence), contract, or otherwise,
175
+ unless required by applicable law (such as deliberate and grossly
176
+ negligent acts) or agreed to in writing, shall any Contributor be
177
+ liable to You for damages, including any direct, indirect, special,
178
+ incidental, or consequential damages of any character arising as a
179
+ result of this License or out of the use or inability to use the
180
+ Work (including but not limited to damages for loss of goodwill,
181
+ work stoppage, computer failure or malfunction, or any and all
182
+ other commercial damages or losses), even if such Contributor
183
+ has been advised of the possibility of such damages.
184
+
185
+ 9. Accepting Warranty or Additional Liability. While redistributing
186
+ the Work or Derivative Works thereof, You may choose to offer,
187
+ and charge a fee for, acceptance of support, warranty, indemnity,
188
+ or other liability obligations and/or rights consistent with this
189
+ License. However, in accepting such obligations, You may act only
190
+ on Your own behalf and on Your sole responsibility, not on behalf
191
+ of any other Contributor, and only if You agree to indemnify,
192
+ defend, and hold each Contributor harmless for any liability
193
+ incurred by, or claims asserted against, such Contributor by reason
194
+ of your accepting any such warranty or additional liability.
195
+
196
+ END OF TERMS AND CONDITIONS
197
+
198
+ APPENDIX: How to apply the Apache License to your work.
199
+
200
+ To apply the Apache License to your work, attach the following
201
+ boilerplate notice, with the fields enclosed by brackets "[]"
202
+ replaced with your own identifying information. (Don't include
203
+ the brackets!) The text should be enclosed in the appropriate
204
+ comment syntax for the file format. We also recommend that a
205
+ file or class name and description of purpose be included on the
206
+ same "printed page" as the copyright notice for easier
207
+ identification within third-party archives.
208
+
209
+ Copyright [yyyy] [name of copyright owner]
210
+
211
+ Licensed under the Apache License, Version 2.0 (the "License");
212
+ you may not use this file except in compliance with the License.
213
+ You may obtain a copy of the License at
214
+
215
+ http://www.apache.org/licenses/LICENSE-2.0
216
+
217
+ Unless required by applicable law or agreed to in writing, software
218
+ distributed under the License is distributed on an "AS IS" BASIS,
219
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
220
+ See the License for the specific language governing permissions and
221
+ limitations under the License.
time_series_classification/MultiRocket/augmentation.py ADDED
@@ -0,0 +1,543 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from time_series_augmentation (https://github.com/uchidalab/time_series_augmentation)
2
+ # Original: Apache License 2.0 by Brian Kenji Iwana and Seiichi Uchida
3
+ # Modified by Jafar Bakhshaliyev (2025) - Licensed under GPL v3.0
4
+
5
+ import numpy as np
6
+ from tqdm import tqdm
7
+
8
+
9
+
10
+
11
+ def tps(x, y, patch_len=0, stride=0, shuffle_rate=0.0):
12
+ """
13
+ Temporal Patch Shuffle (TPS) augmentation for time series classification.
14
+
15
+ Parameters:
16
+ -----------
17
+ x : numpy.ndarray
18
+ Input time series data of shape (n_samples, timesteps, n_features)
19
+ patch_len : int
20
+ Length of each patch
21
+ stride : int
22
+ Stride between patches
23
+ shuffle_rate : float
24
+ Proportion of patches to shuffle (between 0 and 1)
25
+
26
+ Returns:
27
+ --------
28
+ numpy.ndarray
29
+ Augmented time series data with same shape as input
30
+ """
31
+ n_samples, T, n_features = x.shape
32
+
33
+ ret = np.zeros_like(x)
34
+
35
+ # Calculate required padding to avoid zeros at the end
36
+ total_patches = (T - patch_len + stride - 1) // stride + 1
37
+ total_len = (total_patches - 1) * stride + patch_len
38
+ padding_needed = total_len - T
39
+
40
+ # Process each sample
41
+ for i in range(n_samples):
42
+ # current sample
43
+ sample = x[i] # shape: (timesteps, n_features)
44
+
45
+ # Apply padding if needed
46
+ if padding_needed > 0:
47
+ padded_sample = np.pad(sample, ((0, padding_needed), (0, 0)), mode='edge')
48
+ T_padded = T + padding_needed
49
+ else:
50
+ padded_sample = sample
51
+ T_padded = T
52
+
53
+ num_patches = ((T_padded - patch_len) // stride) + 1
54
+
55
+ # Create patches for current sample
56
+ patches = np.zeros((num_patches, patch_len, n_features))
57
+ for j in range(num_patches):
58
+ start = j * stride
59
+ patches[j] = padded_sample[start:start + patch_len]
60
+
61
+ # importance of each patch
62
+ importance_scores = np.var(patches, axis=(1, 2))
63
+
64
+ # number of patches to shuffle
65
+ num_to_shuffle = int(num_patches * shuffle_rate)
66
+
67
+ if num_to_shuffle > 0:
68
+ # indices of least important patches
69
+ shuffle_indices = np.argsort(importance_scores)[:num_to_shuffle]
70
+
71
+ # Shuffle these patches among themselves
72
+ patches_to_shuffle = patches[shuffle_indices].copy()
73
+ shuffled_order = np.random.permutation(num_to_shuffle)
74
+
75
+ for idx, new_idx in enumerate(shuffled_order):
76
+ patch_idx = shuffle_indices[idx]
77
+ new_patch = patches_to_shuffle[new_idx]
78
+ patches[patch_idx] = new_patch
79
+
80
+ # Reconstruct the time series
81
+ reconstructed = np.zeros((T_padded, n_features))
82
+ counts = np.zeros((T_padded, n_features))
83
+
84
+ for j in range(num_patches):
85
+ start = j * stride
86
+ end = start + patch_len
87
+ reconstructed[start:end] += patches[j]
88
+ counts[start:end] += 1
89
+
90
+ # Average overlapping patches and handle potential zeros
91
+ # Use a mask to identify zero counts
92
+ mask = counts == 0
93
+ if np.any(mask):
94
+ # Fill in zeros with nearest non-zero values
95
+ for feat in range(n_features):
96
+ feat_mask = mask[:, feat]
97
+ if np.any(feat_mask):
98
+ # Get indices of zero and non-zero values
99
+ zero_indices = np.where(feat_mask)[0]
100
+ nonzero_indices = np.where(~feat_mask)[0]
101
+
102
+ if len(nonzero_indices) > 0:
103
+ # Find nearest non-zero index for each zero index
104
+ for zero_idx in zero_indices:
105
+ nearest_idx = nonzero_indices[np.argmin(np.abs(nonzero_indices - zero_idx))]
106
+ reconstructed[zero_idx, feat] = reconstructed[nearest_idx, feat]
107
+ counts[zero_idx, feat] = 1
108
+
109
+ # Avoid division by zero
110
+ counts[counts == 0] = 1
111
+ reconstructed = reconstructed / counts
112
+
113
+ # Remove padding
114
+ ret[i] = reconstructed[:T]
115
+
116
+ return ret
117
+
118
+ def jitter(x, sigma=0.03):
119
+ # https://arxiv.org/pdf/1706.00527.pdf
120
+ return x + np.random.normal(loc=0., scale=sigma, size=x.shape)
121
+
122
+ def scaling(x, sigma=0.1):
123
+ # https://arxiv.org/pdf/1706.00527.pdf
124
+ factor = np.random.normal(loc=1., scale=sigma, size=(x.shape[0],x.shape[2]))
125
+ return np.multiply(x, factor[:,np.newaxis,:])
126
+
127
+ def rotation(x):
128
+ flip = np.random.choice([-1, 1], size=(x.shape[0],x.shape[2]))
129
+ rotate_axis = np.arange(x.shape[2])
130
+ np.random.shuffle(rotate_axis)
131
+ return flip[:,np.newaxis,:] * x[:,:,rotate_axis]
132
+
133
+
134
+
135
+ def magnitude_warp(x, sigma=0.2, knot=4):
136
+ from scipy.interpolate import CubicSpline
137
+ orig_steps = np.arange(x.shape[1])
138
+
139
+ random_warps = np.random.normal(loc=1.0, scale=sigma, size=(x.shape[0], knot+2, x.shape[2]))
140
+ warp_steps = (np.ones((x.shape[2],1))*(np.linspace(0, x.shape[1]-1., num=knot+2))).T
141
+ ret = np.zeros_like(x)
142
+ for i, pat in enumerate(x):
143
+ warper = np.array([CubicSpline(warp_steps[:,dim], random_warps[i,:,dim])(orig_steps) for dim in range(x.shape[2])]).T
144
+ ret[i] = pat * warper
145
+
146
+ return ret
147
+
148
+ def time_warp(x, sigma=0.2, knot=4):
149
+ from scipy.interpolate import CubicSpline
150
+ orig_steps = np.arange(x.shape[1])
151
+
152
+ random_warps = np.random.normal(loc=1.0, scale=sigma, size=(x.shape[0], knot+2, x.shape[2]))
153
+ warp_steps = (np.ones((x.shape[2],1))*(np.linspace(0, x.shape[1]-1., num=knot+2))).T
154
+
155
+ ret = np.zeros_like(x)
156
+ for i, pat in enumerate(x):
157
+ for dim in range(x.shape[2]):
158
+ time_warp = CubicSpline(warp_steps[:,dim], warp_steps[:,dim] * random_warps[i,:,dim])(orig_steps)
159
+ scale = (x.shape[1]-1)/time_warp[-1]
160
+ ret[i,:,dim] = np.interp(orig_steps, np.clip(scale*time_warp, 0, x.shape[1]-1), pat[:,dim]).T
161
+ return ret
162
+
163
+ def window_slice(x, reduce_ratio=0.9):
164
+ # https://halshs.archives-ouvertes.fr/halshs-01357973/document
165
+ target_len = np.ceil(reduce_ratio*x.shape[1]).astype(int)
166
+ if target_len >= x.shape[1]:
167
+ return x
168
+ starts = np.random.randint(low=0, high=x.shape[1]-target_len, size=(x.shape[0])).astype(int)
169
+ ends = (target_len + starts).astype(int)
170
+
171
+ ret = np.zeros_like(x)
172
+ for i, pat in enumerate(x):
173
+ for dim in range(x.shape[2]):
174
+ ret[i,:,dim] = np.interp(np.linspace(0, target_len, num=x.shape[1]), np.arange(target_len), pat[starts[i]:ends[i],dim]).T
175
+ return ret
176
+
177
+ def permutation(x, max_segments=5, seg_mode="equal"):
178
+ orig_steps = np.arange(x.shape[1])
179
+ num_segs = np.random.randint(1, max_segments, size=(x.shape[0]))
180
+
181
+ ret = np.zeros_like(x)
182
+ for i, pat in enumerate(x):
183
+ if num_segs[i] > 1:
184
+ if seg_mode == "random":
185
+ # Fix: Check if we have enough points to sample from
186
+ available_points = x.shape[1] - 2
187
+ needed_points = num_segs[i] - 1
188
+
189
+ # Ensure we have enough points to sample and adjust if necessary
190
+ if available_points <= 0:
191
+ # Not enough points for random splitting, fallback to equal segments
192
+ splits = np.array_split(orig_steps, num_segs[i])
193
+ elif needed_points > available_points:
194
+ # Too many segments requested, adjust number of segments
195
+ actual_segs = min(available_points + 1, num_segs[i])
196
+ splits = np.array_split(orig_steps, actual_segs)
197
+ else:
198
+ # Original logic can work
199
+ split_points = np.random.choice(available_points, needed_points, replace=False)
200
+ split_points.sort()
201
+ splits = np.split(orig_steps, split_points)
202
+ else:
203
+ splits = np.array_split(orig_steps, num_segs[i])
204
+
205
+ # Only permute if we have more than one segment
206
+ if len(splits) > 1:
207
+ perm = np.random.permutation(len(splits))
208
+ warp = np.concatenate([splits[j] for j in perm]).ravel()
209
+ ret[i] = pat[warp]
210
+ else:
211
+ ret[i] = pat
212
+ else:
213
+ ret[i] = pat
214
+ return ret
215
+
216
+ # Fixed window_warp function
217
+ def window_warp(x, window_ratio=0.1, scales=[0.5, 2.]):
218
+ # https://halshs.archives-ouvertes.fr/halshs-01357973/document
219
+ warp_scales = np.random.choice(scales, x.shape[0])
220
+ warp_size = np.ceil(window_ratio*x.shape[1]).astype(int)
221
+
222
+ # Handle edge cases: ensure warp_size is at least 1
223
+ warp_size = max(1, warp_size)
224
+ window_steps = np.arange(warp_size)
225
+
226
+ ret = np.zeros_like(x)
227
+
228
+ for i, pat in enumerate(x):
229
+ # Check if we have enough room for warping
230
+ if x.shape[1] <= warp_size + 2:
231
+ # Not enough space for warping, return original pattern
232
+ ret[i] = pat
233
+ continue
234
+
235
+ # Safely generate window start position
236
+ try:
237
+ window_start = np.random.randint(low=1, high=x.shape[1]-warp_size-1)
238
+ except ValueError:
239
+ # Fallback if random range is invalid
240
+ window_start = 1
241
+
242
+ window_end = window_start + warp_size
243
+
244
+ for dim in range(x.shape[2]):
245
+ start_seg = pat[:window_start, dim]
246
+ window_seg = np.interp(
247
+ np.linspace(0, warp_size-1, num=int(warp_size*warp_scales[i])),
248
+ window_steps,
249
+ pat[window_start:window_end, dim]
250
+ )
251
+ end_seg = pat[window_end:, dim]
252
+ warped = np.concatenate((start_seg, window_seg, end_seg))
253
+ ret[i, :, dim] = np.interp(
254
+ np.arange(x.shape[1]),
255
+ np.linspace(0, x.shape[1]-1., num=warped.size),
256
+ warped
257
+ ).T
258
+
259
+ return ret
260
+
261
+ def spawner(x, labels, sigma=0.05, verbose=0):
262
+ # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6983028/
263
+ # use verbose=-1 to turn off warnings
264
+ # use verbose=1 to print out figures
265
+
266
+ import dtw as dtw
267
+
268
+ # Fix for the random_points generation
269
+ if x.shape[1] <= 2: # Check if there are enough time points
270
+ if verbose > -1:
271
+ print("Warning: Time series too short for spawner augmentation")
272
+ return x # Return the original data if too short
273
+
274
+ # Generate random points safely for each time series
275
+ random_points = np.zeros(x.shape[0], dtype=int)
276
+ for i in range(x.shape[0]):
277
+ try:
278
+ random_points[i] = np.random.randint(low=1, high=x.shape[1]-1)
279
+ except ValueError:
280
+ # Fallback if random range is invalid
281
+ random_points[i] = 1
282
+
283
+ window = np.ceil(x.shape[1] / 10.).astype(int)
284
+ window = max(1, window) # Ensure window is at least 1
285
+
286
+ orig_steps = np.arange(x.shape[1])
287
+ l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
288
+
289
+ ret = np.zeros_like(x)
290
+ for i, pat in enumerate(tqdm(x) if 'tqdm' in globals() else x):
291
+ # guarantees that same one isn't selected
292
+ choices = np.delete(np.arange(x.shape[0]), i)
293
+ # remove ones of different classes
294
+ choices = np.where(l[choices] == l[i])[0]
295
+ if choices.size > 0:
296
+ random_sample = x[np.random.choice(choices)]
297
+
298
+ # SPAWNER splits the path into two randomly
299
+ try:
300
+ # Handle potential edge cases with very small sequences
301
+ random_point = random_points[i]
302
+ if random_point <= 0:
303
+ random_point = 1
304
+ if random_point >= x.shape[1]:
305
+ random_point = x.shape[1] - 1
306
+
307
+ # Check if window size is appropriate
308
+ if window >= min(random_point, pat.shape[0] - random_point):
309
+ # Adjust window if it's too large
310
+ adjusted_window = max(1, min(random_point, pat.shape[0] - random_point) - 1)
311
+ if verbose > -1:
312
+ print(f"Warning: Adjusting window from {window} to {adjusted_window}")
313
+ window = adjusted_window
314
+
315
+ path1 = dtw.dtw(pat[:random_point], random_sample[:random_point],
316
+ dtw.RETURN_PATH, slope_constraint="symmetric", window=window)
317
+
318
+ path2 = dtw.dtw(pat[random_point:], random_sample[random_point:],
319
+ dtw.RETURN_PATH, slope_constraint="symmetric", window=window)
320
+
321
+ combined = np.concatenate((np.vstack(path1), np.vstack(path2+random_point)), axis=1)
322
+
323
+ if verbose:
324
+ print(random_point)
325
+ dtw_value, cost, DTW_map, path = dtw.dtw(pat, random_sample,
326
+ return_flag=dtw.RETURN_ALL,
327
+ slope_constraint="symmetric",
328
+ window=window)
329
+ dtw.draw_graph1d(cost, DTW_map, path, pat, random_sample)
330
+ dtw.draw_graph1d(cost, DTW_map, combined, pat, random_sample)
331
+
332
+ mean = np.mean([pat[combined[0]], random_sample[combined[1]]], axis=0)
333
+
334
+ # Handle potential size mismatch
335
+ if mean.shape[0] > 0:
336
+ for dim in range(x.shape[2]):
337
+ ret[i,:,dim] = np.interp(orig_steps,
338
+ np.linspace(0, x.shape[1]-1., num=mean.shape[0]),
339
+ mean[:,dim]).T
340
+ else:
341
+ if verbose > -1:
342
+ print("Warning: DTW produced empty path, skipping augmentation")
343
+ ret[i,:] = pat
344
+
345
+ except Exception as e:
346
+ if verbose > -1:
347
+ print(f"Error in DTW computation: {e}")
348
+ ret[i,:] = pat
349
+ else:
350
+ if verbose > -1:
351
+ print(f"There is only one pattern of class {l[i]}, skipping pattern average")
352
+ ret[i,:] = pat
353
+
354
+ # Assuming jitter is defined elsewhere
355
+ try:
356
+ return jitter(ret, sigma=sigma)
357
+ except:
358
+ if verbose > -1:
359
+ print("Warning: jitter function failed or not found, returning unjittered data")
360
+ return ret
361
+
362
+ def wdba(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True, verbose=0):
363
+ # https://ieeexplore.ieee.org/document/8215569
364
+ # use verbose = -1 to turn off warnings
365
+ # slope_constraint is for DTW. "symmetric" or "asymmetric"
366
+
367
+ import dtw as dtw
368
+
369
+ if use_window:
370
+ window = np.ceil(x.shape[1] / 10.).astype(int)
371
+ else:
372
+ window = None
373
+ orig_steps = np.arange(x.shape[1])
374
+ l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
375
+
376
+ ret = np.zeros_like(x)
377
+ for i in tqdm(range(ret.shape[0])):
378
+ # get the same class as i
379
+ choices = np.where(l == l[i])[0]
380
+ if choices.size > 0:
381
+ # pick random intra-class pattern
382
+ k = min(choices.size, batch_size)
383
+ random_prototypes = x[np.random.choice(choices, k, replace=False)]
384
+
385
+ # calculate dtw between all
386
+ dtw_matrix = np.zeros((k, k))
387
+ for p, prototype in enumerate(random_prototypes):
388
+ for s, sample in enumerate(random_prototypes):
389
+ if p == s:
390
+ dtw_matrix[p, s] = 0.
391
+ else:
392
+ dtw_matrix[p, s] = dtw.dtw(prototype, sample, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
393
+
394
+ # get medoid
395
+ medoid_id = np.argsort(np.sum(dtw_matrix, axis=1))[0]
396
+ nearest_order = np.argsort(dtw_matrix[medoid_id])
397
+ medoid_pattern = random_prototypes[medoid_id]
398
+
399
+ # start weighted DBA
400
+ average_pattern = np.zeros_like(medoid_pattern)
401
+ weighted_sums = np.zeros((medoid_pattern.shape[0]))
402
+ for nid in nearest_order:
403
+ if nid == medoid_id or dtw_matrix[medoid_id, nearest_order[1]] == 0.:
404
+ average_pattern += medoid_pattern
405
+ weighted_sums += np.ones_like(weighted_sums)
406
+ else:
407
+ path = dtw.dtw(medoid_pattern, random_prototypes[nid], dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
408
+ dtw_value = dtw_matrix[medoid_id, nid]
409
+ warped = random_prototypes[nid, path[1]]
410
+ weight = np.exp(np.log(0.5)*dtw_value/dtw_matrix[medoid_id, nearest_order[1]])
411
+ average_pattern[path[0]] += weight * warped
412
+ weighted_sums[path[0]] += weight
413
+
414
+ ret[i,:] = average_pattern / weighted_sums[:,np.newaxis]
415
+ else:
416
+ if verbose > -1:
417
+ print("There is only one pattern of class %d, skipping pattern average"%l[i])
418
+ ret[i,:] = x[i]
419
+ return ret
420
+
421
+
422
+
423
+ def random_guided_warp(x, labels, slope_constraint="symmetric", use_window=True, dtw_type="normal", verbose=0):
424
+ # use verbose = -1 to turn off warnings
425
+ # slope_constraint is for DTW. "symmetric" or "asymmetric"
426
+ # dtw_type is for shapeDTW or DTW. "normal" or "shape"
427
+
428
+ import dtw as dtw
429
+
430
+ if use_window:
431
+ window = np.ceil(x.shape[1] / 10.).astype(int)
432
+ else:
433
+ window = None
434
+ orig_steps = np.arange(x.shape[1])
435
+ l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
436
+
437
+ ret = np.zeros_like(x)
438
+ for i, pat in enumerate(tqdm(x)):
439
+ # guarentees that same one isnt selected
440
+ choices = np.delete(np.arange(x.shape[0]), i)
441
+ # remove ones of different classes
442
+ choices = np.where(l[choices] == l[i])[0]
443
+ if choices.size > 0:
444
+ # pick random intra-class pattern
445
+ random_prototype = x[np.random.choice(choices)]
446
+
447
+ if dtw_type == "shape":
448
+ path = dtw.shape_dtw(random_prototype, pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
449
+ else:
450
+ path = dtw.dtw(random_prototype, pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
451
+
452
+ # Time warp
453
+ warped = pat[path[1]]
454
+ for dim in range(x.shape[2]):
455
+ ret[i,:,dim] = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), warped[:,dim]).T
456
+ else:
457
+ if verbose > -1:
458
+ print("There is only one pattern of class %d, skipping timewarping"%l[i])
459
+ ret[i,:] = pat
460
+ return ret
461
+
462
+ def random_guided_warp_shape(x, labels, slope_constraint="symmetric", use_window=True):
463
+ return random_guided_warp(x, labels, slope_constraint, use_window, dtw_type="shape")
464
+
465
+ def discriminative_guided_warp(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True, dtw_type="normal", use_variable_slice=True, verbose=0):
466
+ # use verbose = -1 to turn off warnings
467
+ # slope_constraint is for DTW. "symmetric" or "asymmetric"
468
+ # dtw_type is for shapeDTW or DTW. "normal" or "shape"
469
+
470
+ import dtw as dtw
471
+
472
+ if use_window:
473
+ window = np.ceil(x.shape[1] / 10.).astype(int)
474
+ else:
475
+ window = None
476
+ orig_steps = np.arange(x.shape[1])
477
+ l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
478
+
479
+ positive_batch = np.ceil(batch_size / 2).astype(int)
480
+ negative_batch = np.floor(batch_size / 2).astype(int)
481
+
482
+ ret = np.zeros_like(x)
483
+ warp_amount = np.zeros(x.shape[0])
484
+ for i, pat in enumerate(tqdm(x)):
485
+ # guarentees that same one isnt selected
486
+ choices = np.delete(np.arange(x.shape[0]), i)
487
+
488
+ # remove ones of different classes
489
+ positive = np.where(l[choices] == l[i])[0]
490
+ negative = np.where(l[choices] != l[i])[0]
491
+
492
+ if positive.size > 0 and negative.size > 0:
493
+ pos_k = min(positive.size, positive_batch)
494
+ neg_k = min(negative.size, negative_batch)
495
+ positive_prototypes = x[np.random.choice(positive, pos_k, replace=False)]
496
+ negative_prototypes = x[np.random.choice(negative, neg_k, replace=False)]
497
+
498
+ # vector embedding and nearest prototype in one
499
+ pos_aves = np.zeros((pos_k))
500
+ neg_aves = np.zeros((pos_k))
501
+ if dtw_type == "shape":
502
+ for p, pos_prot in enumerate(positive_prototypes):
503
+ for ps, pos_samp in enumerate(positive_prototypes):
504
+ if p != ps:
505
+ pos_aves[p] += (1./(pos_k-1.))*dtw.shape_dtw(pos_prot, pos_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
506
+ for ns, neg_samp in enumerate(negative_prototypes):
507
+ neg_aves[p] += (1./neg_k)*dtw.shape_dtw(pos_prot, neg_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
508
+ selected_id = np.argmax(neg_aves - pos_aves)
509
+ path = dtw.shape_dtw(positive_prototypes[selected_id], pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
510
+ else:
511
+ for p, pos_prot in enumerate(positive_prototypes):
512
+ for ps, pos_samp in enumerate(positive_prototypes):
513
+ if p != ps:
514
+ pos_aves[p] += (1./(pos_k-1.))*dtw.dtw(pos_prot, pos_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
515
+ for ns, neg_samp in enumerate(negative_prototypes):
516
+ neg_aves[p] += (1./neg_k)*dtw.dtw(pos_prot, neg_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
517
+ selected_id = np.argmax(neg_aves - pos_aves)
518
+ path = dtw.dtw(positive_prototypes[selected_id], pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
519
+
520
+ # Time warp
521
+ warped = pat[path[1]]
522
+ warp_path_interp = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), path[1])
523
+ warp_amount[i] = np.sum(np.abs(orig_steps-warp_path_interp))
524
+ for dim in range(x.shape[2]):
525
+ ret[i,:,dim] = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), warped[:,dim]).T
526
+ else:
527
+ if verbose > -1:
528
+ print("There is only one pattern of class %d"%l[i])
529
+ ret[i,:] = pat
530
+ warp_amount[i] = 0.
531
+ if use_variable_slice:
532
+ max_warp = np.max(warp_amount)
533
+ if max_warp == 0:
534
+ # unchanged
535
+ ret = window_slice(ret, reduce_ratio=0.9)
536
+ else:
537
+ for i, pat in enumerate(ret):
538
+ # Variable Sllicing
539
+ ret[i] = window_slice(pat[np.newaxis,:,:], reduce_ratio=0.9+0.1*warp_amount[i]/max_warp)[0]
540
+ return ret
541
+
542
+ def discriminative_guided_warp_shape(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True):
543
+ return discriminative_guided_warp(x, labels, batch_size, slope_constraint, use_window, dtw_type="shape")
time_series_classification/MultiRocket/dtw.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from time_series_augmentation (https://github.com/uchidalab/time_series_augmentation)
2
+ # Original: Apache License 2.0 by Brian Kenji Iwana and Seiichi Uchida
3
+
4
+ __author__ = 'Brian Iwana'
5
+
6
+ import numpy as np
7
+ import math
8
+ import sys
9
+
10
+ RETURN_VALUE = 0
11
+ RETURN_PATH = 1
12
+ RETURN_ALL = -1
13
+
14
+ # Core DTW
15
+ def _traceback(DTW, slope_constraint):
16
+ i, j = np.array(DTW.shape) - 1
17
+ p, q = [i-1], [j-1]
18
+
19
+ if slope_constraint == "asymmetric":
20
+ while (i > 1):
21
+ tb = np.argmin((DTW[i-1, j], DTW[i-1, j-1], DTW[i-1, j-2]))
22
+
23
+ if (tb == 0):
24
+ i = i - 1
25
+ elif (tb == 1):
26
+ i = i - 1
27
+ j = j - 1
28
+ elif (tb == 2):
29
+ i = i - 1
30
+ j = j - 2
31
+
32
+ p.insert(0, i-1)
33
+ q.insert(0, j-1)
34
+ elif slope_constraint == "symmetric":
35
+ while (i > 1 or j > 1):
36
+ tb = np.argmin((DTW[i-1, j-1], DTW[i-1, j], DTW[i, j-1]))
37
+
38
+ if (tb == 0):
39
+ i = i - 1
40
+ j = j - 1
41
+ elif (tb == 1):
42
+ i = i - 1
43
+ elif (tb == 2):
44
+ j = j - 1
45
+
46
+ p.insert(0, i-1)
47
+ q.insert(0, j-1)
48
+ else:
49
+ sys.exit("Unknown slope constraint %s"%slope_constraint)
50
+
51
+ return (np.array(p), np.array(q))
52
+
53
+ def dtw(prototype, sample, return_flag = RETURN_VALUE, slope_constraint="asymmetric", window=None):
54
+ """ Computes the DTW of two sequences.
55
+ :param prototype: np array [0..b]
56
+ :param sample: np array [0..t]
57
+ :param extended: bool
58
+ """
59
+ p = prototype.shape[0]
60
+ assert p != 0, "Prototype empty!"
61
+ s = sample.shape[0]
62
+ assert s != 0, "Sample empty!"
63
+
64
+ if window is None:
65
+ window = s
66
+
67
+ cost = np.full((p, s), np.inf)
68
+ for i in range(p):
69
+ start = max(0, i-window)
70
+ end = min(s, i+window)+1
71
+ cost[i,start:end]=np.linalg.norm(sample[start:end] - prototype[i], axis=1)
72
+
73
+ DTW = _cummulative_matrix(cost, slope_constraint, window)
74
+
75
+ if return_flag == RETURN_ALL:
76
+ return DTW[-1,-1], cost, DTW[1:,1:], _traceback(DTW, slope_constraint)
77
+ elif return_flag == RETURN_PATH:
78
+ return _traceback(DTW, slope_constraint)
79
+ else:
80
+ return DTW[-1,-1]
81
+
82
+ def _cummulative_matrix(cost, slope_constraint, window):
83
+ p = cost.shape[0]
84
+ s = cost.shape[1]
85
+
86
+ # Note: DTW is one larger than cost and the original patterns
87
+ DTW = np.full((p+1, s+1), np.inf)
88
+
89
+ DTW[0, 0] = 0.0
90
+
91
+ if slope_constraint == "asymmetric":
92
+ for i in range(1, p+1):
93
+ if i <= window+1:
94
+ DTW[i,1] = cost[i-1,0] + min(DTW[i-1,0], DTW[i-1,1])
95
+ for j in range(max(2, i-window), min(s, i+window)+1):
96
+ DTW[i,j] = cost[i-1,j-1] + min(DTW[i-1,j-2], DTW[i-1,j-1], DTW[i-1,j])
97
+ elif slope_constraint == "symmetric":
98
+ for i in range(1, p+1):
99
+ for j in range(max(1, i-window), min(s, i+window)+1):
100
+ DTW[i,j] = cost[i-1,j-1] + min(DTW[i-1,j-1], DTW[i,j-1], DTW[i-1,j])
101
+ else:
102
+ sys.exit("Unknown slope constraint %s"%slope_constraint)
103
+
104
+ return DTW
105
+
106
+ def shape_dtw(prototype, sample, return_flag = RETURN_VALUE, slope_constraint="asymmetric", window=None, descr_ratio=0.05):
107
+ """ Computes the shapeDTW of two sequences.
108
+ :param prototype: np array [0..b]
109
+ :param sample: np array [0..t]
110
+ :param extended: bool
111
+ """
112
+ # shapeDTW
113
+ # https://www.sciencedirect.com/science/article/pii/S0031320317303710
114
+
115
+ p = prototype.shape[0]
116
+ assert p != 0, "Prototype empty!"
117
+ s = sample.shape[0]
118
+ assert s != 0, "Sample empty!"
119
+
120
+ if window is None:
121
+ window = s
122
+
123
+ p_feature_len = np.clip(np.round(p * descr_ratio), 5, 100).astype(int)
124
+ s_feature_len = np.clip(np.round(s * descr_ratio), 5, 100).astype(int)
125
+
126
+ # padding
127
+ p_pad_front = (np.ceil(p_feature_len / 2.)).astype(int)
128
+ p_pad_back = (np.floor(p_feature_len / 2.)).astype(int)
129
+ s_pad_front = (np.ceil(s_feature_len / 2.)).astype(int)
130
+ s_pad_back = (np.floor(s_feature_len / 2.)).astype(int)
131
+
132
+ prototype_pad = np.pad(prototype, ((p_pad_front, p_pad_back), (0, 0)), mode="edge")
133
+ sample_pad = np.pad(sample, ((s_pad_front, s_pad_back), (0, 0)), mode="edge")
134
+ p_p = prototype_pad.shape[0]
135
+ s_p = sample_pad.shape[0]
136
+
137
+ cost = np.full((p, s), np.inf)
138
+ for i in range(p):
139
+ for j in range(max(0, i-window), min(s, i+window)):
140
+ cost[i, j] = np.linalg.norm(sample_pad[j:j+s_feature_len] - prototype_pad[i:i+p_feature_len])
141
+
142
+ DTW = _cummulative_matrix(cost, slope_constraint=slope_constraint, window=window)
143
+
144
+ if return_flag == RETURN_ALL:
145
+ return DTW[-1,-1], cost, DTW[1:,1:], _traceback(DTW, slope_constraint)
146
+ elif return_flag == RETURN_PATH:
147
+ return _traceback(DTW, slope_constraint)
148
+ else:
149
+ return DTW[-1,-1]
150
+
151
+ # Draw helpers
152
+ def draw_graph2d(cost, DTW, path, prototype, sample):
153
+ import matplotlib.pyplot as plt
154
+ plt.figure(figsize=(12, 8))
155
+ # plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
156
+
157
+ #cost
158
+ plt.subplot(2, 3, 1)
159
+ plt.imshow(cost.T, cmap=plt.cm.gray, interpolation='none', origin='lower')
160
+ plt.plot(path[0], path[1], 'y')
161
+ plt.xlim((-0.5, cost.shape[0]-0.5))
162
+ plt.ylim((-0.5, cost.shape[0]-0.5))
163
+
164
+ #dtw
165
+ plt.subplot(2, 3, 2)
166
+ plt.imshow(DTW.T, cmap=plt.cm.gray, interpolation='none', origin='lower')
167
+ plt.plot(path[0]+1, path[1]+1, 'y')
168
+ plt.xlim((-0.5, DTW.shape[0]-0.5))
169
+ plt.ylim((-0.5, DTW.shape[0]-0.5))
170
+
171
+ #prototype
172
+ plt.subplot(2, 3, 4)
173
+ plt.plot(prototype[:,0], prototype[:,1], 'b-o')
174
+
175
+ #connection
176
+ plt.subplot(2, 3, 5)
177
+ for i in range(0,path[0].shape[0]):
178
+ plt.plot([prototype[path[0][i],0], sample[path[1][i],0]],[prototype[path[0][i],1], sample[path[1][i],1]], 'y-')
179
+ plt.plot(sample[:,0], sample[:,1], 'g-o')
180
+ plt.plot(prototype[:,0], prototype[:,1], 'b-o')
181
+
182
+ #sample
183
+ plt.subplot(2, 3, 6)
184
+ plt.plot(sample[:,0], sample[:,1], 'g-o')
185
+
186
+ plt.tight_layout()
187
+ plt.show()
188
+
189
+ def draw_graph1d(cost, DTW, path, prototype, sample):
190
+ import matplotlib.pyplot as plt
191
+ plt.figure(figsize=(12, 8))
192
+ # plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
193
+ p_steps = np.arange(prototype.shape[0])
194
+ s_steps = np.arange(sample.shape[0])
195
+
196
+ #cost
197
+ plt.subplot(2, 3, 1)
198
+ plt.imshow(cost.T, cmap=plt.cm.gray, interpolation='none', origin='lower')
199
+ plt.plot(path[0], path[1], 'y')
200
+ plt.xlim((-0.5, cost.shape[0]-0.5))
201
+ plt.ylim((-0.5, cost.shape[0]-0.5))
202
+
203
+ #dtw
204
+ plt.subplot(2, 3, 2)
205
+ plt.imshow(DTW.T, cmap=plt.cm.gray, interpolation='none', origin='lower')
206
+ plt.plot(path[0]+1, path[1]+1, 'y')
207
+ plt.xlim((-0.5, DTW.shape[0]-0.5))
208
+ plt.ylim((-0.5, DTW.shape[0]-0.5))
209
+
210
+ #prototype
211
+ plt.subplot(2, 3, 4)
212
+ plt.plot(p_steps, prototype[:,0], 'b-o')
213
+
214
+ #connection
215
+ plt.subplot(2, 3, 5)
216
+ for i in range(0,path[0].shape[0]):
217
+ plt.plot([path[0][i], path[1][i]],[prototype[path[0][i],0], sample[path[1][i],0]], 'y-')
218
+ plt.plot(p_steps, sample[:,0], 'g-o')
219
+ plt.plot(s_steps, prototype[:,0], 'b-o')
220
+
221
+ #sample
222
+ plt.subplot(2, 3, 6)
223
+ plt.plot(s_steps, sample[:,0], 'g-o')
224
+
225
+ plt.tight_layout()
226
+ plt.show()
time_series_classification/MultiRocket/hyperparamter_tune.py ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from MultiRocket (https://github.com/ChangWeiTan/MultiRocket)
2
+ # Copyright (C) 2025 Jafar Bakhshaliyev
3
+ # Licensed under GNU General Public License v3.0
4
+
5
+
6
+ import argparse
7
+ import os
8
+ import time
9
+ import sys
10
+ import numpy as np
11
+ import pandas as pd
12
+ from sklearn.metrics import accuracy_score, log_loss
13
+ from sklearn.preprocessing import LabelEncoder
14
+ from sklearn.model_selection import train_test_split
15
+ from sktime.utils.data_io import load_from_tsfile_to_dataframe
16
+ from scipy.special import softmax
17
+
18
+ from multirocket.multirocket_multivariate import MultiRocket
19
+ from utils.data_loader import process_ts_data
20
+ from utils.tools import create_directory
21
+
22
+ import augmentation as aug
23
+
24
+ pd.set_option('display.max_columns', 500)
25
+
26
+ def run_augmentation(x, y, args):
27
+ """
28
+ Apply data augmentation to the input data based on args.
29
+
30
+ Parameters:
31
+ -----------
32
+ x : numpy.ndarray
33
+ Original time series data
34
+ y : numpy.ndarray
35
+ Original labels
36
+ args : argparse.Namespace
37
+ Command line arguments containing augmentation options
38
+
39
+ Returns:
40
+ --------
41
+ x_aug : numpy.ndarray
42
+ Augmented time series data
43
+ y_aug : numpy.ndarray
44
+ Augmented labels
45
+ augmentation_tags : str
46
+ String describing the applied augmentations
47
+ """
48
+ print("Augmenting data for dataset %s" % args.problem)
49
+ np.random.seed(args.seed)
50
+ x_aug = x.copy()
51
+ y_aug = y.copy()
52
+
53
+ augmentation_tags = ""
54
+
55
+ if args.augmentation_ratio > 0:
56
+ augmentation_tags = "%d" % args.augmentation_ratio
57
+ print(f"Original training size: {x.shape[0]} samples")
58
+
59
+ for n in range(args.augmentation_ratio):
60
+ x_temp, current_tags = augment(x, y, args)
61
+
62
+ if x_temp.shape != x.shape:
63
+ print(f"Warning: Augmented data shape {x_temp.shape} doesn't match original shape {x.shape}")
64
+ continue
65
+
66
+ x_aug = np.concatenate((x_aug, x_temp), axis=0)
67
+ y_aug = np.append(y_aug, y)
68
+
69
+ print(f"Round {n+1}: {current_tags} done - Added {x_temp.shape[0]} samples")
70
+
71
+ if n == 0:
72
+ augmentation_tags += current_tags
73
+
74
+ print(f"Augmented training size: {x_aug.shape[0]} samples")
75
+
76
+ if args.extra_tag:
77
+ augmentation_tags += "_" + args.extra_tag
78
+ else:
79
+ augmentation_tags = "none"
80
+ if args.extra_tag:
81
+ augmentation_tags = args.extra_tag
82
+
83
+ return x_aug, y_aug, augmentation_tags
84
+
85
+
86
+ def augment(x, y, args):
87
+ """
88
+ Apply specified augmentations to the multivariate time series data.
89
+
90
+ Parameters:
91
+ -----------
92
+ x : numpy.ndarray
93
+ Original time series data with shape (n_samples, n_dimensions, n_timesteps)
94
+ y : numpy.ndarray
95
+ Original labels
96
+ args : argparse.Namespace
97
+ Command line arguments containing augmentation options
98
+
99
+ Returns:
100
+ --------
101
+ x : numpy.ndarray
102
+ Augmented time series data
103
+ augmentation_tags : str
104
+ String describing the applied augmentations
105
+ """
106
+ augmentation_tags = ""
107
+
108
+ x_aug = x.copy()
109
+
110
+
111
+ if len(x_aug.shape) != 3:
112
+ if len(x_aug.shape) == 2:
113
+ x_aug = x_aug.reshape(x_aug.shape[0], 1, x_aug.shape[1])
114
+ print(f"Reshaped to {x_aug.shape} for processing")
115
+
116
+ if args.jitter:
117
+ x_aug = aug.jitter(x_aug)
118
+ augmentation_tags += "_jitter"
119
+
120
+ if args.tps and args.patch_len > 0:
121
+ x_aug = aug.tps(x_aug, y, args.patch_len, args.stride, args.shuffle_rate)
122
+ augmentation_tags += "_tps"
123
+
124
+ if args.scaling:
125
+ x_aug = aug.scaling(x_aug)
126
+ augmentation_tags += "_scaling"
127
+
128
+ if args.rotation:
129
+ x_aug = aug.rotation(x_aug)
130
+ augmentation_tags += "_rotation"
131
+
132
+ if args.permutation:
133
+ x_aug = aug.permutation(x_aug)
134
+ augmentation_tags += "_permutation"
135
+
136
+ if args.randompermutation:
137
+ x_aug = aug.permutation(x_aug, seg_mode="random")
138
+ augmentation_tags += "_randomperm"
139
+
140
+ if args.magwarp:
141
+ x_aug = aug.magnitude_warp(x_aug)
142
+ augmentation_tags += "_magwarp"
143
+
144
+ if args.timewarp:
145
+ x_aug = aug.time_warp(x_aug)
146
+ augmentation_tags += "_timewarp"
147
+
148
+ if args.windowslice:
149
+ x_aug = aug.window_slice(x_aug)
150
+ augmentation_tags += "_windowslice"
151
+
152
+ if args.windowwarp:
153
+ x_aug = aug.window_warp(x_aug)
154
+ augmentation_tags += "_windowwarp"
155
+
156
+ if args.spawner:
157
+ x_aug = aug.spawner(x_aug, y)
158
+ augmentation_tags += "_spawner"
159
+
160
+ if args.dtwwarp:
161
+ x_aug = aug.random_guided_warp(x_aug, y)
162
+ augmentation_tags += "_rgw"
163
+
164
+ if args.shapedtwwarp:
165
+ x_aug = aug.random_guided_warp_shape(x_aug, y)
166
+ augmentation_tags += "_rgws"
167
+
168
+ if args.wdba:
169
+ x_aug = aug.wdba(x_aug, y)
170
+ augmentation_tags += "_wdba"
171
+
172
+ if args.discdtw:
173
+ x_aug = aug.discriminative_guided_warp(x_aug, y)
174
+ augmentation_tags += "_dgw"
175
+
176
+ if args.discsdtw:
177
+ x_aug = aug.discriminative_guided_warp_shape(x_aug, y)
178
+ augmentation_tags += "_dgws"
179
+
180
+ if not augmentation_tags:
181
+ augmentation_tags = "_none"
182
+
183
+ return x_aug, augmentation_tags
184
+
185
+ def run_multirocket_hyperparameter_tuning(args):
186
+ """
187
+ Run MultiRocket hyperparameter tuning on a dataset with train/validation split.
188
+
189
+ Parameters:
190
+ -----------
191
+ args : argparse.Namespace
192
+ Command line arguments containing options
193
+
194
+ Returns:
195
+ --------
196
+ results_df : pandas.DataFrame
197
+ DataFrame containing results of the hyperparameter tuning
198
+ """
199
+ problem = args.problem
200
+ data_path = args.datapath
201
+ data_folder = data_path + problem + "/"
202
+
203
+ # Set output directory
204
+ output_path = os.getcwd() + "/output/"
205
+ classifier_name = f"MultiRocket_{args.num_features}"
206
+
207
+ output_dir = "{}/multirocket/hyperparameter_tuning/{}/{}/".format(
208
+ output_path,
209
+ classifier_name,
210
+ problem
211
+ )
212
+
213
+ if args.save:
214
+ create_directory(output_dir)
215
+
216
+ train_file = data_folder + problem + "_TRAIN.ts"
217
+ test_file = data_folder + problem + "_TEST.ts"
218
+
219
+ print("Loading data")
220
+ X_train_full, y_train_full = load_from_tsfile_to_dataframe(train_file)
221
+
222
+ encoder = LabelEncoder()
223
+ y_train_full = encoder.fit_transform(y_train_full)
224
+
225
+ X_train_full_processed = process_ts_data(X_train_full, normalise=False)
226
+
227
+ # Split the training set into training and validation sets (80/20)
228
+ try:
229
+ if len(np.unique(y_train_full)) > 1:
230
+ class_counts = np.bincount(y_train_full.astype(int))
231
+ if np.min(class_counts[class_counts > 0]) >= 2:
232
+ train_indices, val_indices = train_test_split(
233
+ np.arange(len(y_train_full)),
234
+ test_size=0.2,
235
+ random_state=args.seed,
236
+ stratify=y_train_full
237
+ )
238
+ else:
239
+ print("Warning: Some classes have only 1 sample. Using regular split instead of stratified split.")
240
+ train_indices, val_indices = train_test_split(
241
+ np.arange(len(y_train_full)),
242
+ test_size=0.2,
243
+ random_state=args.seed,
244
+ stratify=None
245
+ )
246
+ else:
247
+ train_indices, val_indices = train_test_split(
248
+ np.arange(len(y_train_full)),
249
+ test_size=0.2,
250
+ random_state=args.seed,
251
+ stratify=None
252
+ )
253
+ except Exception as e:
254
+ print(f"Warning: Failed to perform stratified split: {e}")
255
+ print("Falling back to regular random split.")
256
+ train_indices, val_indices = train_test_split(
257
+ np.arange(len(y_train_full)),
258
+ test_size=0.2,
259
+ random_state=args.seed,
260
+ stratify=None
261
+ )
262
+
263
+ y_train = y_train_full[train_indices].copy()
264
+ y_val = y_train_full[val_indices].copy()
265
+
266
+ X_train = X_train_full_processed[train_indices]
267
+ X_val = X_train_full_processed[val_indices]
268
+
269
+ print(f"Split training data: Train shape: {X_train.shape}, Validation shape: {X_val.shape}")
270
+
271
+ # Apply augmentation
272
+ augmentation_tags = "none"
273
+ if args.use_augmentation:
274
+ X_train_aug, y_train_aug, augmentation_tags = run_augmentation(X_train, y_train, args)
275
+ else:
276
+ X_train_aug, y_train_aug = X_train.copy(), y_train.copy()
277
+
278
+ train_accuracies = []
279
+ val_accuracies = []
280
+ val_cross_entropies = []
281
+ train_times = []
282
+
283
+ for iteration in range(args.iterations):
284
+ print(f"Running iteration {iteration+1}/{args.iterations}")
285
+
286
+ start_time = time.perf_counter()
287
+
288
+ np.random.seed(args.seed + iteration)
289
+
290
+ classifier = MultiRocket(
291
+ num_features=args.num_features,
292
+ classifier="logistic",
293
+ verbose=args.verbose
294
+ )
295
+
296
+ yhat_train = classifier.fit(
297
+ X_train_aug, y_train_aug,
298
+ predict_on_train=True
299
+ )
300
+
301
+ yhat_val = classifier.predict(X_val)
302
+
303
+ train_acc = accuracy_score(y_train_aug, yhat_train)
304
+ train_accuracies.append(train_acc)
305
+
306
+ val_acc = accuracy_score(y_val, yhat_val)
307
+ val_accuracies.append(val_acc)
308
+
309
+ try:
310
+ val_proba = classifier.predict_proba(X_val)
311
+
312
+ try:
313
+ all_classes = np.unique(np.concatenate((y_train_aug, y_val)))
314
+ val_cross_entropy = log_loss(y_val, val_proba, labels=all_classes)
315
+ val_cross_entropies.append(val_cross_entropy)
316
+ except Exception as e:
317
+ print(f"Warning: Could not calculate cross-entropy: {e}")
318
+ val_cross_entropy = np.nan
319
+ val_cross_entropies.append(val_cross_entropy)
320
+
321
+ except (AttributeError, NotImplementedError) as e:
322
+ print(f"Warning: Could not get probability estimates: {e}")
323
+ val_cross_entropy = np.nan
324
+ val_cross_entropies.append(val_cross_entropy)
325
+
326
+ train_time = classifier.train_duration
327
+ train_times.append(train_time)
328
+
329
+ print(f"Iteration {iteration+1} - Train Accuracy: {train_acc:.4f}")
330
+ print(f"Iteration {iteration+1} - Validation Accuracy: {val_acc:.4f}")
331
+ if not np.isnan(val_cross_entropy):
332
+ print(f"Iteration {iteration+1} - Validation Cross-Entropy: {val_cross_entropy:.4f}")
333
+ print(f"Iteration {iteration+1} - Train Time: {train_time:.2f} seconds")
334
+
335
+ # Calculate mean and standard deviation
336
+ mean_train_accuracy = np.mean(train_accuracies)
337
+ std_train_accuracy = np.std(train_accuracies)
338
+ mean_val_accuracy = np.mean(val_accuracies)
339
+ std_val_accuracy = np.std(val_accuracies)
340
+ mean_val_cross_entropy = np.nanmean(val_cross_entropies) if not all(np.isnan(val_cross_entropies)) else np.nan
341
+ std_val_cross_entropy = np.nanstd(val_cross_entropies) if not all(np.isnan(val_cross_entropies)) else np.nan
342
+ mean_train_time = np.mean(train_times)
343
+
344
+ print(f"\nHyperparameter Tuning Results for {problem} with augmentation: {augmentation_tags}")
345
+ print(f"Original train size: {X_train.shape[0]} samples")
346
+ print(f"Augmented train size: {X_train_aug.shape[0]} samples")
347
+ print(f"Validation size: {X_val.shape[0]} samples")
348
+ print(f"Mean Train Accuracy: {mean_train_accuracy:.4f} ± {std_train_accuracy:.4f}")
349
+ print(f"Mean Validation Accuracy: {mean_val_accuracy:.4f} ± {std_val_accuracy:.4f}")
350
+ if not np.isnan(mean_val_cross_entropy):
351
+ print(f"Mean Validation Cross-Entropy: {mean_val_cross_entropy:.4f} ± {std_val_cross_entropy:.4f}")
352
+ print(f"Mean Train Time: {mean_train_time:.2f} seconds")
353
+
354
+ # Create results DataFrame
355
+ results_df = pd.DataFrame({
356
+ 'dataset': [problem],
357
+ 'augmentation': [augmentation_tags],
358
+ 'train_size': [X_train.shape[0]],
359
+ 'train_size_after_aug': [X_train_aug.shape[0]],
360
+ 'val_size': [X_val.shape[0]],
361
+ 'mean_train_accuracy': [mean_train_accuracy],
362
+ 'train_accuracy_std': [std_train_accuracy],
363
+ 'mean_val_accuracy': [mean_val_accuracy],
364
+ 'val_accuracy_std': [std_val_accuracy],
365
+ 'mean_val_cross_entropy': [mean_val_cross_entropy],
366
+ 'val_cross_entropy_std': [std_val_cross_entropy],
367
+ 'mean_train_time': [mean_train_time],
368
+ 'iterations': [args.iterations],
369
+ 'features': [args.num_features],
370
+ 'individual_train_accuracies': [','.join(map(str, train_accuracies))],
371
+ 'individual_val_accuracies': [','.join(map(str, val_accuracies))],
372
+ 'individual_val_cross_entropies': [','.join(map(str, val_cross_entropies))],
373
+ 'patch_len': [args.patch_len],
374
+ 'stride': [args.stride],
375
+ 'shuffle_rate': [args.shuffle_rate]
376
+ })
377
+
378
+ if args.save:
379
+ results_filename = f"{output_dir}/multirocket_hyperparameter_tuning_{problem}_{augmentation_tags}.csv"
380
+ if os.path.exists(results_filename):
381
+ try:
382
+ existing_df = pd.read_csv(results_filename)
383
+ combined_df = pd.concat([existing_df, results_df], ignore_index=True)
384
+ combined_df.to_csv(results_filename, index=False)
385
+ print(f"Results appended to {results_filename}")
386
+ except Exception as e:
387
+ print(f"Error appending to existing file: {e}")
388
+ results_df.to_csv(results_filename, index=False)
389
+ print(f"Created new file instead: {results_filename}")
390
+ else:
391
+ results_df.to_csv(results_filename, index=False)
392
+ print(f"Results saved to new file {results_filename}")
393
+
394
+ return results_df
395
+
396
+ def list_available_datasets(args):
397
+ """
398
+ List all available datasets in the data path.
399
+
400
+ Parameters:
401
+ -----------
402
+ args : argparse.Namespace
403
+ Command line arguments containing options
404
+ """
405
+ data_path = args.datapath
406
+ try:
407
+ datasets = [d for d in os.listdir(data_path) if os.path.isdir(os.path.join(data_path, d))]
408
+ print("Available datasets:")
409
+ for dataset in sorted(datasets):
410
+ print(f" - {dataset}")
411
+ return sorted(datasets)
412
+ except Exception as e:
413
+ print(f"Error listing datasets: {e}")
414
+ return []
415
+
416
+ if __name__ == '__main__':
417
+ parser = argparse.ArgumentParser(description='Hyperparameter Tuning for MultiRocket on Multivariate Time Series')
418
+
419
+ # Dataset selection
420
+ parser.add_argument("-d", "--datapath", type=str, required=False, default="/home/bakhshaliyev/classification-aug/MultiRocket/data/Multivariate_ts/")
421
+ parser.add_argument("-p", "--problem", type=str, required=False, default="UWaveGestureLibrary")
422
+ parser.add_argument("-n", "--num_features", type=int, required=False, default=50000)
423
+ parser.add_argument("-t", "--num_threads", type=int, required=False, default=-1)
424
+ parser.add_argument("-s", "--save", type=bool, required=False, default=True)
425
+ parser.add_argument("-v", "--verbose", type=int, required=False, default=2)
426
+
427
+ # Added arguments for tuning
428
+ parser.add_argument('--iterations', type=int, default=5, help='Number of iterations for each experiment (default: 5)')
429
+ parser.add_argument('--seed', type=int, default=42, help='Random seed (default: 42)')
430
+ parser.add_argument('--list', action='store_true', help='List available datasets')
431
+
432
+ # Augmentation control
433
+ parser.add_argument('--use-augmentation', action='store_true', help='Use data augmentation')
434
+ parser.add_argument('--augmentation-ratio', type=int, default=0,
435
+ help='Number of augmented copies to add (default: 0)')
436
+ parser.add_argument('--extra-tag', type=str, default='',
437
+ help='Extra tag to add to augmentation tags')
438
+
439
+ # Augmentation methods
440
+ parser.add_argument('--jitter', action='store_true', help='Apply jitter augmentation')
441
+ parser.add_argument('--scaling', action='store_true', help='Apply scaling augmentation')
442
+ parser.add_argument('--rotation', action='store_true', help='Apply rotation augmentation')
443
+ parser.add_argument('--permutation', action='store_true', help='Apply permutation augmentation')
444
+ parser.add_argument('--randompermutation', action='store_true', help='Apply random permutation augmentation')
445
+ parser.add_argument('--magwarp', action='store_true', help='Apply magnitude warp augmentation')
446
+ parser.add_argument('--timewarp', action='store_true', help='Apply time warp augmentation')
447
+ parser.add_argument('--windowslice', action='store_true', help='Apply window slice augmentation')
448
+ parser.add_argument('--windowwarp', action='store_true', help='Apply window warp augmentation')
449
+ parser.add_argument('--spawner', action='store_true', help='Apply spawner augmentation')
450
+ parser.add_argument('--dtwwarp', action='store_true', help='Apply DTW-based warp augmentation')
451
+ parser.add_argument('--shapedtwwarp', action='store_true', help='Apply shape DTW warp augmentation')
452
+ parser.add_argument('--wdba', action='store_true', help='Apply WDBA augmentation')
453
+ parser.add_argument('--discdtw', action='store_true', help='Apply discriminative DTW augmentation')
454
+ parser.add_argument('--discsdtw', action='store_true', help='Apply discriminative shape DTW augmentation')
455
+ parser.add_argument('--tps', action='store_true', help='Apply TPS augmentation')
456
+
457
+ # TPS specific parameters
458
+ parser.add_argument('--stride', type=int, default=0, help='# of patches stride')
459
+ parser.add_argument('--patch_len', type=int, default=0, help='# of patches')
460
+ parser.add_argument('--shuffle_rate', type=float, default=0.0, help='shuffle rate')
461
+
462
+ args = parser.parse_args()
463
+
464
+ if args.num_threads > 0:
465
+ import numba
466
+ numba.set_num_threads(args.num_threads)
467
+
468
+ if args.list:
469
+ list_available_datasets(args)
470
+ sys.exit(0)
471
+
472
+ # Run hyperparameter tuning on specified dataset
473
+ print(f"Running MultiRocket hyperparameter tuning on {args.problem} dataset")
474
+ print(f"Using {args.num_features} features and {args.iterations} iterations")
475
+ if args.use_augmentation:
476
+ print(f"Using data augmentation with ratio {args.augmentation_ratio}")
477
+
478
+ run_multirocket_hyperparameter_tuning(args)
time_series_classification/MultiRocket/main.py ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from MultiRocket (https://github.com/ChangWeiTan/MultiRocket)
2
+ # Copyright (C) 2025 Jafar Bakhshaliyev
3
+ # Licensed under GNU General Public License v3.0
4
+
5
+
6
+ import argparse
7
+ import os
8
+ import time
9
+ import sys
10
+ import socket
11
+ import platform
12
+ from datetime import datetime
13
+
14
+ import numba
15
+ import numpy as np
16
+ import pandas as pd
17
+ import psutil
18
+ import pytz
19
+ from sklearn.metrics import accuracy_score
20
+ from sklearn.preprocessing import LabelEncoder
21
+ from sktime.utils.data_io import load_from_tsfile_to_dataframe
22
+
23
+ from multirocket.multirocket_multivariate import MultiRocket
24
+ from utils.data_loader import process_ts_data
25
+ from utils.tools import create_directory
26
+
27
+ import augmentation as aug
28
+
29
+ pd.set_option('display.max_columns', 500)
30
+
31
+ def run_augmentation(x, y, args):
32
+ """
33
+ Apply data augmentation to the input data based on args.
34
+
35
+ Parameters:
36
+ -----------
37
+ x : numpy.ndarray
38
+ Original time series data
39
+ y : numpy.ndarray
40
+ Original labels
41
+ args : argparse.Namespace
42
+ Command line arguments containing augmentation options
43
+
44
+ Returns:
45
+ --------
46
+ x_aug : numpy.ndarray
47
+ Augmented time series data
48
+ y_aug : numpy.ndarray
49
+ Augmented labels
50
+ augmentation_tags : str
51
+ String describing the applied augmentations
52
+ """
53
+ print("Augmenting data for dataset %s" % args.problem)
54
+ np.random.seed(args.seed)
55
+ x_aug = x.copy()
56
+ y_aug = y.copy()
57
+
58
+ augmentation_tags = ""
59
+
60
+ if args.augmentation_ratio > 0:
61
+ augmentation_tags = "%d" % args.augmentation_ratio
62
+ print(f"Original training size: {x.shape[0]} samples")
63
+
64
+ for n in range(args.augmentation_ratio):
65
+ x_temp, current_tags = augment(x, y, args)
66
+
67
+ if x_temp.shape != x.shape:
68
+ print(f"Warning: Augmented data shape {x_temp.shape} doesn't match original shape {x.shape}")
69
+ continue
70
+
71
+ x_aug = np.concatenate((x_aug, x_temp), axis=0)
72
+ y_aug = np.append(y_aug, y)
73
+
74
+ print(f"Round {n+1}: {current_tags} done - Added {x_temp.shape[0]} samples")
75
+
76
+ if n == 0:
77
+ augmentation_tags += current_tags
78
+
79
+ print(f"Augmented training size: {x_aug.shape[0]} samples")
80
+
81
+ if args.extra_tag:
82
+ augmentation_tags += "_" + args.extra_tag
83
+ else:
84
+ augmentation_tags = "none"
85
+ if args.extra_tag:
86
+ augmentation_tags = args.extra_tag
87
+
88
+ return x_aug, y_aug, augmentation_tags
89
+
90
+
91
+ def augment(x, y, args):
92
+ """
93
+ Apply specified augmentations to the multivariate time series data.
94
+
95
+ Parameters:
96
+ -----------
97
+ x : numpy.ndarray
98
+ Original time series data with shape (n_samples, n_dimensions, n_timesteps)
99
+ y : numpy.ndarray
100
+ Original labels
101
+ args : argparse.Namespace
102
+ Command line arguments containing augmentation options
103
+
104
+ Returns:
105
+ --------
106
+ x : numpy.ndarray
107
+ Augmented time series data
108
+ augmentation_tags : str
109
+ String describing the applied augmentations
110
+ """
111
+ augmentation_tags = ""
112
+
113
+ x_aug = x.copy()
114
+
115
+ if len(x_aug.shape) != 3:
116
+ if len(x_aug.shape) == 2:
117
+ x_aug = x_aug.reshape(x_aug.shape[0], 1, x_aug.shape[1])
118
+ print(f"Reshaped to {x_aug.shape} for processing")
119
+
120
+ if args.jitter:
121
+ x_aug = aug.jitter(x_aug)
122
+ augmentation_tags += "_jitter"
123
+
124
+ if args.tps and args.patch_len > 0:
125
+ x_aug = aug.tps(x_aug, y, args.patch_len, args.stride, args.shuffle_rate)
126
+ augmentation_tags += "_tps"
127
+
128
+ if args.scaling:
129
+ x_aug = aug.scaling(x_aug)
130
+ augmentation_tags += "_scaling"
131
+
132
+ if args.rotation:
133
+ x_aug = aug.rotation(x_aug)
134
+ augmentation_tags += "_rotation"
135
+
136
+ if args.permutation:
137
+ x_aug = aug.permutation(x_aug)
138
+ augmentation_tags += "_permutation"
139
+
140
+ if args.randompermutation:
141
+ x_aug = aug.permutation(x_aug, seg_mode="random")
142
+ augmentation_tags += "_randomperm"
143
+
144
+ if args.magwarp:
145
+ x_aug = aug.magnitude_warp(x_aug)
146
+ augmentation_tags += "_magwarp"
147
+
148
+ if args.timewarp:
149
+ x_aug = aug.time_warp(x_aug)
150
+ augmentation_tags += "_timewarp"
151
+
152
+ if args.windowslice:
153
+ x_aug = aug.window_slice(x_aug)
154
+ augmentation_tags += "_windowslice"
155
+
156
+ if args.windowwarp:
157
+ x_aug = aug.window_warp(x_aug)
158
+ augmentation_tags += "_windowwarp"
159
+
160
+ if args.spawner:
161
+ x_aug = aug.spawner(x_aug, y)
162
+ augmentation_tags += "_spawner"
163
+
164
+ if args.dtwwarp:
165
+ x_aug = aug.random_guided_warp(x_aug, y)
166
+ augmentation_tags += "_rgw"
167
+
168
+ if args.shapedtwwarp:
169
+ x_aug = aug.random_guided_warp_shape(x_aug, y)
170
+ augmentation_tags += "_rgws"
171
+
172
+ if args.wdba:
173
+ x_aug = aug.wdba(x_aug, y)
174
+ augmentation_tags += "_wdba"
175
+
176
+ if args.discdtw:
177
+ x_aug = aug.discriminative_guided_warp(x_aug, y)
178
+ augmentation_tags += "_dgw"
179
+
180
+ if args.discsdtw:
181
+ x_aug = aug.discriminative_guided_warp_shape(x_aug, y)
182
+ augmentation_tags += "_dgws"
183
+
184
+ if not augmentation_tags:
185
+ augmentation_tags = "_none"
186
+
187
+ return x_aug, augmentation_tags
188
+
189
+ def run_multirocket_experiment(args):
190
+ """
191
+ Run MultiRocket on a dataset with multiple iterations and optional augmentation.
192
+
193
+ Parameters:
194
+ -----------
195
+ args : argparse.Namespace
196
+ Command line arguments containing options
197
+
198
+ Returns:
199
+ --------
200
+ results_df : pandas.DataFrame
201
+ DataFrame containing results of the experiment
202
+ """
203
+ problem = args.problem
204
+ data_path = args.datapath
205
+ data_folder = data_path + problem + "/"
206
+
207
+ # Set output directory
208
+ output_path = os.getcwd() + "/output/"
209
+ classifier_name = f"MultiRocket_{args.num_features}"
210
+
211
+ output_dir = "{}/multirocket/resample_{}/{}/{}/".format(
212
+ output_path,
213
+ args.iter,
214
+ classifier_name,
215
+ problem
216
+ )
217
+
218
+ if args.save:
219
+ create_directory(output_dir)
220
+
221
+ train_file = data_folder + problem + "_TRAIN.ts"
222
+ test_file = data_folder + problem + "_TEST.ts"
223
+
224
+ # Loading data
225
+ X_train, y_train = load_from_tsfile_to_dataframe(train_file)
226
+ X_test, y_test = load_from_tsfile_to_dataframe(test_file)
227
+
228
+ encoder = LabelEncoder()
229
+ y_train = encoder.fit_transform(y_train)
230
+ y_test = encoder.transform(y_test)
231
+
232
+ X_train_processed = process_ts_data(X_train, normalise=False)
233
+ X_test_processed = process_ts_data(X_test, normalise=False)
234
+
235
+ # Apply augmentation
236
+ augmentation_tags = "none"
237
+ if args.use_augmentation:
238
+ X_train_processed, y_train, augmentation_tags = run_augmentation(X_train_processed, y_train, args)
239
+
240
+ accuracies = []
241
+ train_times = []
242
+ test_times = []
243
+
244
+ for iteration in range(args.iterations):
245
+ print(f"Running iteration {iteration+1}/{args.iterations}")
246
+
247
+ start_time = time.perf_counter()
248
+
249
+ np.random.seed(args.seed + iteration)
250
+
251
+ classifier = MultiRocket(
252
+ num_features=args.num_features,
253
+ classifier="logistic",
254
+ verbose=args.verbose
255
+ )
256
+
257
+ yhat_train = classifier.fit(
258
+ X_train_processed, y_train,
259
+ predict_on_train=False
260
+ )
261
+
262
+ yhat_test = classifier.predict(X_test_processed)
263
+
264
+ test_acc = accuracy_score(y_test, yhat_test)
265
+
266
+ if yhat_train is not None:
267
+ train_acc = accuracy_score(y_train, yhat_train)
268
+ else:
269
+ train_acc = -1
270
+
271
+ accuracies.append(test_acc)
272
+ train_times.append(classifier.train_duration)
273
+ test_times.append(classifier.test_duration)
274
+
275
+ print(f"Iteration {iteration+1} - Test Accuracy: {test_acc:.4f}, Train Time: {classifier.train_duration:.2f} seconds")
276
+
277
+ mean_accuracy = np.mean(accuracies)
278
+ std_accuracy = np.std(accuracies)
279
+ mean_train_time = np.mean(train_times)
280
+ mean_test_time = np.mean(test_times)
281
+
282
+ print(f"\nResults for {problem} with augmentation: {augmentation_tags}")
283
+ print(f"Train size: {X_train_processed.shape[0]} samples")
284
+ print(f"Mean Accuracy: {mean_accuracy:.4f} ± {std_accuracy:.4f}")
285
+ print(f"Mean Train Time: {mean_train_time:.2f} seconds")
286
+ print(f"Mean Test Time: {mean_test_time:.2f} seconds")
287
+ print(f"Individual Accuracies: {accuracies}")
288
+
289
+ results_df = pd.DataFrame({
290
+ 'dataset': [problem],
291
+ 'augmentation': [augmentation_tags],
292
+ 'train_size': [X_train_processed.shape[0]],
293
+ 'test_size': [X_test_processed.shape[0]],
294
+ 'mean_accuracy': [mean_accuracy],
295
+ 'std_accuracy': [std_accuracy],
296
+ 'mean_train_time': [mean_train_time],
297
+ 'mean_test_time': [mean_test_time],
298
+ 'iterations': [args.iterations],
299
+ 'features': [args.num_features],
300
+ 'individual_accuracies': [','.join(map(str, accuracies))],
301
+ 'patch_len': [args.patch_len] if hasattr(args, 'patch_len') else [0],
302
+ 'stride': [args.stride] if hasattr(args, 'stride') else [0],
303
+ 'shuffle_rate': [args.shuffle_rate] if hasattr(args, 'shuffle_rate') else [0.0],
304
+ })
305
+
306
+ if args.save:
307
+ results_filename = f"{output_dir}/multirocket_results_{problem}_{augmentation_tags}.csv"
308
+ if os.path.exists(results_filename):
309
+ existing_df = pd.read_csv(results_filename)
310
+ combined_df = pd.concat([existing_df, results_df], ignore_index=True)
311
+ combined_df.to_csv(results_filename, index=False)
312
+ print(f"Results appended to {results_filename}")
313
+ else:
314
+ results_df.to_csv(results_filename, index=False)
315
+ print(f"Results saved to new file {results_filename}")
316
+
317
+ return results_df
318
+
319
+ def run_all_datasets(args):
320
+ """
321
+ Run MultiRocket on all available datasets in the data path.
322
+
323
+ Parameters:
324
+ -----------
325
+ args : argparse.Namespace
326
+ Command line arguments containing options
327
+ """
328
+ # list of available datasets
329
+ data_path = args.datapath
330
+ datasets = [d for d in os.listdir(data_path) if os.path.isdir(os.path.join(data_path, d))]
331
+
332
+ if not datasets:
333
+ print(f"No datasets found in {data_path}")
334
+ return
335
+
336
+ print(f"Found {len(datasets)} datasets: {', '.join(datasets)}")
337
+
338
+ results = []
339
+
340
+ for dataset_name in datasets:
341
+ print(f"\n{'='*50}")
342
+ print(f"Processing dataset: {dataset_name}")
343
+ print(f"{'='*50}")
344
+
345
+ try:
346
+ args.problem = dataset_name
347
+
348
+ result_df = run_multirocket_experiment(args)
349
+ results.append(result_df)
350
+
351
+ except Exception as e:
352
+ print(f"Error processing dataset {dataset_name}: {e}")
353
+
354
+ if results:
355
+ all_results_df = pd.concat(results, ignore_index=True)
356
+ overall_mean = all_results_df['mean_accuracy'].mean()
357
+
358
+ print("\n" + "="*80)
359
+ print("SUMMARY OF RESULTS")
360
+ print("="*80)
361
+ print(f"{'Dataset':<25} {'Augmentation':<25} {'Mean Accuracy':<15} {'Std Dev':<10}")
362
+ print("-"*80)
363
+
364
+ for _, row in all_results_df.iterrows():
365
+ print(f"{row['dataset']:<25} {row['augmentation']:<25} {row['mean_accuracy']:.4f}{' '*8} {row['std_accuracy']:.4f}")
366
+
367
+ print("-"*80)
368
+ print(f"{'OVERALL':<25} {'':<25} {overall_mean:.4f}")
369
+ print("="*80)
370
+
371
+ aug_tag = "none" if not args.use_augmentation else "aug"
372
+ output_path = os.getcwd() + "/output/"
373
+ all_results_df.to_csv(f"{output_path}/multirocket_summary_results_{aug_tag}.csv", index=False)
374
+ print(f"\nSummary results saved to {output_path}/multirocket_summary_results_{aug_tag}.csv")
375
+
376
+ def list_available_datasets(args):
377
+ """
378
+ List all available datasets in the data path.
379
+
380
+ Parameters:
381
+ -----------
382
+ args : argparse.Namespace
383
+ Command line arguments containing options
384
+ """
385
+ data_path = args.datapath
386
+ try:
387
+ datasets = [d for d in os.listdir(data_path) if os.path.isdir(os.path.join(data_path, d))]
388
+ print("Available datasets:")
389
+ for dataset in sorted(datasets):
390
+ print(f" - {dataset}")
391
+ except Exception as e:
392
+ print(f"Error listing datasets: {e}")
393
+ return []
394
+
395
+ if __name__ == '__main__':
396
+ parser = argparse.ArgumentParser(description='Run MultiRocket on multivariate time series datasets with optional augmentation')
397
+
398
+ # Dataset selection
399
+ parser.add_argument("-d", "--datapath", type=str, required=False, default="/home/bakhshaliyev/classification-aug/MultiRocket/data/Multivariate_ts/") # change to your data path
400
+ parser.add_argument("-p", "--problem", type=str, required=False, default="UWaveGestureLibrary")
401
+ parser.add_argument("-i", "--iter", type=int, required=False, default=0)
402
+ parser.add_argument("-n", "--num_features", type=int, required=False, default=50000)
403
+ parser.add_argument("-t", "--num_threads", type=int, required=False, default=-1)
404
+ parser.add_argument("-s", "--save", type=bool, required=False, default=True)
405
+ parser.add_argument("-v", "--verbose", type=int, required=False, default=2)
406
+
407
+ # Added arguments for augmentation
408
+ parser.add_argument('--iterations', type=int, default=5, help='Number of iterations for each experiment (default: 5)')
409
+ parser.add_argument('--seed', type=int, default=42, help='Random seed (default: 42)')
410
+ parser.add_argument('--list', action='store_true', help='List available datasets')
411
+ parser.add_argument('--all', action='store_true', help='Run on all available datasets')
412
+
413
+ # Augmentation control
414
+ parser.add_argument('--use-augmentation', action='store_true', help='Use data augmentation')
415
+ parser.add_argument('--augmentation-ratio', type=int, default=0,
416
+ help='Number of augmented copies to add (default: 0)')
417
+ parser.add_argument('--extra-tag', type=str, default='',
418
+ help='Extra tag to add to augmentation tags')
419
+
420
+ # Augmentation methods
421
+ parser.add_argument('--jitter', action='store_true', help='Apply jitter augmentation')
422
+ parser.add_argument('--scaling', action='store_true', help='Apply scaling augmentation')
423
+ parser.add_argument('--rotation', action='store_true', help='Apply rotation augmentation')
424
+ parser.add_argument('--permutation', action='store_true', help='Apply permutation augmentation')
425
+ parser.add_argument('--randompermutation', action='store_true', help='Apply random permutation augmentation')
426
+ parser.add_argument('--magwarp', action='store_true', help='Apply magnitude warp augmentation')
427
+ parser.add_argument('--timewarp', action='store_true', help='Apply time warp augmentation')
428
+ parser.add_argument('--windowslice', action='store_true', help='Apply window slice augmentation')
429
+ parser.add_argument('--windowwarp', action='store_true', help='Apply window warp augmentation')
430
+ parser.add_argument('--spawner', action='store_true', help='Apply spawner augmentation')
431
+ parser.add_argument('--dtwwarp', action='store_true', help='Apply DTW-based warp augmentation')
432
+ parser.add_argument('--shapedtwwarp', action='store_true', help='Apply shape DTW warp augmentation')
433
+ parser.add_argument('--wdba', action='store_true', help='Apply WDBA augmentation')
434
+ parser.add_argument('--discdtw', action='store_true', help='Apply discriminative DTW augmentation')
435
+ parser.add_argument('--discsdtw', action='store_true', help='Apply discriminative shape DTW augmentation')
436
+ parser.add_argument('--tps', action='store_true', help='Apply TPS augmentation')
437
+
438
+ # TPS specific parameters
439
+ parser.add_argument('--stride', type=int, default=0, help='# of patches stride')
440
+ parser.add_argument('--patch_len', type=int, default=0, help='# of patches')
441
+ parser.add_argument('--shuffle_rate', type=float, default=0.0, help='shuffle rate')
442
+
443
+ args = parser.parse_args()
444
+
445
+ if args.num_threads > 0:
446
+ numba.set_num_threads(args.num_threads)
447
+
448
+ if args.list:
449
+ list_available_datasets(args)
450
+ sys.exit(0)
451
+
452
+ # Run on all datasets
453
+ if args.all:
454
+ print(f"Running MultiRocket on all available datasets")
455
+ print(f"Using {args.num_features} features and {args.iterations} iterations")
456
+ if args.use_augmentation:
457
+ print(f"Using data augmentation with ratio {args.augmentation_ratio}")
458
+ run_all_datasets(args)
459
+ sys.exit(0)
460
+
461
+ # Run on specific dataset
462
+ print(f"Running MultiRocket on {args.problem} dataset")
463
+ print(f"Using {args.num_features} features and {args.iterations} iterations")
464
+ if args.use_augmentation:
465
+ print(f"Using data augmentation with ratio {args.augmentation_ratio}")
466
+
467
+ run_multirocket_experiment(args)
time_series_classification/MultiRocket/multirocket/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __version__ = "0.0.2"
2
+
3
+
4
+ def get_module_version():
5
+ return __version__
time_series_classification/MultiRocket/multirocket/logistic_regression.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from MultiRocket (https://github.com/ChangWeiTan/MultiRocket)
2
+ # Copyright (C) 2025 Jafar Bakhshaliyev
3
+ # Licensed under GNU General Public License v3.0
4
+
5
+ import copy
6
+
7
+ import numpy as np
8
+ import torch
9
+ import torch.nn.functional
10
+ from sklearn.model_selection import train_test_split
11
+ from sklearn.preprocessing import StandardScaler
12
+ from torch.utils.data import TensorDataset, DataLoader
13
+ from tqdm import tqdm
14
+
15
+
16
+ class LogisticRegression:
17
+
18
+ def __init__(
19
+ self,
20
+ num_features,
21
+ max_epochs=500,
22
+ minibatch_size=256,
23
+ validation_size=2 ** 11,
24
+ learning_rate=1e-4,
25
+ patience_lr=5, # 50 minibatches
26
+ patience=10, # 100 minibatches
27
+ device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
28
+ ):
29
+ self.name = "LogisticRegression"
30
+ self.args = {
31
+ "num_features": num_features,
32
+ "validation_size": validation_size,
33
+ "minibatch_size": minibatch_size,
34
+ "lr": learning_rate,
35
+ "max_epochs": max_epochs,
36
+ "patience_lr": patience_lr,
37
+ "patience": patience,
38
+ }
39
+
40
+ self.model = None
41
+ self.device = device
42
+ self.classes = None
43
+ self.scaler = None
44
+ self.num_classes = None
45
+
46
+ def fit(self, x_train, y_train):
47
+ self.classes = np.unique(y_train)
48
+ self.num_classes = len(self.classes)
49
+
50
+ num_outputs = self.num_classes if self.num_classes > 2 else 1
51
+ train_steps = int(x_train.shape[0] / self.args["minibatch_size"])
52
+
53
+ self.scaler = StandardScaler()
54
+ x_train = self.scaler.fit_transform(x_train)
55
+
56
+ model = torch.nn.Sequential(torch.nn.Linear(self.args["num_features"], num_outputs)).to(self.device)
57
+
58
+ if num_outputs == 1:
59
+ loss_function = torch.nn.BCEWithLogitsLoss()
60
+ else:
61
+ loss_function = torch.nn.CrossEntropyLoss()
62
+ optimizer = torch.optim.Adam(model.parameters(), lr=self.args["lr"])
63
+ scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
64
+ optimizer,
65
+ factor=0.5,
66
+ min_lr=1e-8,
67
+ patience=self.args["patience_lr"]
68
+ )
69
+
70
+ training_size = x_train.shape[0]
71
+ if self.args["validation_size"] < training_size:
72
+ x_training, x_validation, y_training, y_validation = train_test_split(
73
+ x_train, y_train,
74
+ test_size=self.args["validation_size"],
75
+ stratify=y_train
76
+ )
77
+
78
+
79
+ if num_outputs == 1:
80
+ train_data = TensorDataset(
81
+ torch.tensor(x_training, dtype=torch.float32).to(self.device),
82
+ torch.tensor(y_training, dtype=torch.float32).to(self.device)
83
+ )
84
+ val_data = TensorDataset(
85
+ torch.tensor(x_validation, dtype=torch.float32).to(self.device),
86
+ torch.tensor(y_validation, dtype=torch.float32).to(self.device)
87
+ )
88
+ else:
89
+ train_data = TensorDataset(
90
+ torch.tensor(x_training, dtype=torch.float32).to(self.device),
91
+ torch.tensor(y_training, dtype=torch.long).to(self.device)
92
+ )
93
+ val_data = TensorDataset(
94
+ torch.tensor(x_validation, dtype=torch.float32).to(self.device),
95
+ torch.tensor(y_validation, dtype=torch.long).to(self.device)
96
+ )
97
+
98
+ train_dataloader = DataLoader(train_data, shuffle=True, batch_size=self.args["minibatch_size"])
99
+ val_dataloader = DataLoader(val_data, batch_size=self.args["minibatch_size"])
100
+ else:
101
+ if num_outputs == 1:
102
+ train_data = TensorDataset(
103
+ torch.tensor(x_train, dtype=torch.float32).to(self.device),
104
+ torch.tensor(y_train, dtype=torch.float32).to(self.device)
105
+ )
106
+ else:
107
+ train_data = TensorDataset(
108
+ torch.tensor(x_train, dtype=torch.float32).to(self.device),
109
+ torch.tensor(y_train, dtype=torch.long).to(self.device)
110
+ )
111
+
112
+ train_dataloader = DataLoader(train_data, shuffle=True, batch_size=self.args["minibatch_size"])
113
+ val_dataloader = None
114
+
115
+ best_loss = np.inf
116
+ best_model = None
117
+ stall_count = 0
118
+ stop = False
119
+
120
+ for epoch in range(self.args["max_epochs"]):
121
+ if epoch > 0 and stop:
122
+ break
123
+ model.train()
124
+
125
+ # loop over the training set
126
+ total_train_loss = 0
127
+ steps = 0
128
+ for i, data in tqdm(enumerate(train_dataloader), desc=f"epoch: {epoch}", total=train_steps):
129
+ x, y = data
130
+
131
+ y_hat = model(x)
132
+ if num_outputs == 1:
133
+ y_hat = y_hat.squeeze()
134
+ loss = loss_function(y_hat, y)
135
+ else:
136
+ loss = loss_function(y_hat, y)
137
+
138
+ optimizer.zero_grad()
139
+ loss.backward()
140
+ optimizer.step()
141
+ total_train_loss += loss
142
+ steps += 1
143
+
144
+ total_train_loss = total_train_loss.cpu().detach().numpy() / steps
145
+
146
+ if val_dataloader is not None:
147
+ total_val_loss = 0
148
+ # switch off autograd for evaluation
149
+ with torch.no_grad():
150
+ # set the model in evaluation mode
151
+ model.eval()
152
+ for i, data in enumerate(val_dataloader):
153
+ x, y = data
154
+
155
+ y_hat = model(x)
156
+ if num_outputs == 1:
157
+ y_hat = y_hat.squeeze()
158
+ total_val_loss += loss_function(y_hat, y)
159
+ else:
160
+ total_val_loss += loss_function(y_hat, y)
161
+ total_val_loss = total_val_loss.cpu().detach().numpy() / steps
162
+ scheduler.step(total_val_loss)
163
+
164
+ if total_val_loss >= best_loss:
165
+ stall_count += 1
166
+ if stall_count >= self.args["patience"]:
167
+ stop = True
168
+ print(f"\n<Stopped at Epoch {epoch + 1}>")
169
+ else:
170
+ best_loss = total_val_loss
171
+ best_model = copy.deepcopy(model)
172
+ if not stop:
173
+ stall_count = 0
174
+ else:
175
+ scheduler.step(total_train_loss)
176
+ if total_train_loss >= best_loss:
177
+ stall_count += 1
178
+ if stall_count >= self.args["patience"]:
179
+ stop = True
180
+ print(f"\n<Stopped at Epoch {epoch + 1}>")
181
+ else:
182
+ best_loss = total_train_loss
183
+ best_model = copy.deepcopy(model)
184
+ if not stop:
185
+ stall_count = 0
186
+
187
+ self.model = best_model
188
+ return self.model
189
+
190
+ def predict(self, x):
191
+ x = self.scaler.transform(x)
192
+
193
+ with torch.no_grad():
194
+ # set the model in evaluation mode
195
+ self.model.eval()
196
+
197
+ y_hat = self.model(torch.tensor(x, dtype=torch.float32).to(self.device))
198
+
199
+ if self.num_classes > 2:
200
+ pred_indices = np.argmax(y_hat.cpu().detach().numpy(), axis=1)
201
+ predictions = self.classes[pred_indices]
202
+ else:
203
+ y_hat = y_hat.squeeze()
204
+ sigmoid_output = torch.sigmoid(y_hat).cpu().detach().numpy()
205
+ binary_predictions = np.round(sigmoid_output).astype(int)
206
+ predictions = np.array([self.classes[int(p)] for p in binary_predictions])
207
+
208
+ return predictions
time_series_classification/MultiRocket/multirocket/multirocket.py ADDED
@@ -0,0 +1,558 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chang Wei Tan, Angus Dempster, Christoph Bergmeir, Geoffrey I Webb
2
+ #
3
+ # MultiRocket: Multiple pooling operators and transformations for fast and effective time series classification
4
+ # https://arxiv.org/abs/2102.00457
5
+
6
+ import time
7
+
8
+ import numpy as np
9
+ from numba import njit, prange
10
+ from sklearn.linear_model import RidgeClassifierCV
11
+ from sklearn.pipeline import make_pipeline
12
+ from sklearn.preprocessing import StandardScaler
13
+
14
+ from multirocket.logistic_regression import LogisticRegression
15
+
16
+
17
+ @njit("float32[:](float64[:,:],int32[:],int32[:],float32[:])",
18
+ fastmath=True, parallel=False, cache=True)
19
+ def _fit_biases(X, dilations, num_features_per_dilation, quantiles):
20
+ num_examples, input_length = X.shape
21
+
22
+ # equivalent to:
23
+ # >>> from itertools import combinations
24
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
25
+ indices = np.array((
26
+ 0, 1, 2, 0, 1, 3, 0, 1, 4, 0, 1, 5, 0, 1, 6, 0, 1, 7, 0, 1, 8,
27
+ 0, 2, 3, 0, 2, 4, 0, 2, 5, 0, 2, 6, 0, 2, 7, 0, 2, 8, 0, 3, 4,
28
+ 0, 3, 5, 0, 3, 6, 0, 3, 7, 0, 3, 8, 0, 4, 5, 0, 4, 6, 0, 4, 7,
29
+ 0, 4, 8, 0, 5, 6, 0, 5, 7, 0, 5, 8, 0, 6, 7, 0, 6, 8, 0, 7, 8,
30
+ 1, 2, 3, 1, 2, 4, 1, 2, 5, 1, 2, 6, 1, 2, 7, 1, 2, 8, 1, 3, 4,
31
+ 1, 3, 5, 1, 3, 6, 1, 3, 7, 1, 3, 8, 1, 4, 5, 1, 4, 6, 1, 4, 7,
32
+ 1, 4, 8, 1, 5, 6, 1, 5, 7, 1, 5, 8, 1, 6, 7, 1, 6, 8, 1, 7, 8,
33
+ 2, 3, 4, 2, 3, 5, 2, 3, 6, 2, 3, 7, 2, 3, 8, 2, 4, 5, 2, 4, 6,
34
+ 2, 4, 7, 2, 4, 8, 2, 5, 6, 2, 5, 7, 2, 5, 8, 2, 6, 7, 2, 6, 8,
35
+ 2, 7, 8, 3, 4, 5, 3, 4, 6, 3, 4, 7, 3, 4, 8, 3, 5, 6, 3, 5, 7,
36
+ 3, 5, 8, 3, 6, 7, 3, 6, 8, 3, 7, 8, 4, 5, 6, 4, 5, 7, 4, 5, 8,
37
+ 4, 6, 7, 4, 6, 8, 4, 7, 8, 5, 6, 7, 5, 6, 8, 5, 7, 8, 6, 7, 8
38
+ ), dtype=np.int32).reshape(84, 3)
39
+
40
+ num_kernels = len(indices)
41
+ num_dilations = len(dilations)
42
+
43
+ num_features = num_kernels * np.sum(num_features_per_dilation)
44
+
45
+ biases = np.zeros(num_features, dtype=np.float32)
46
+
47
+ feature_index_start = 0
48
+
49
+ for dilation_index in range(num_dilations):
50
+
51
+ dilation = dilations[dilation_index]
52
+ padding = ((9 - 1) * dilation) // 2
53
+
54
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
55
+
56
+ for kernel_index in range(num_kernels):
57
+
58
+ feature_index_end = feature_index_start + num_features_this_dilation
59
+
60
+ _X = X[np.random.randint(num_examples)]
61
+
62
+ A = -_X # A = alpha * X = -X
63
+ G = _X + _X + _X # G = gamma * X = 3X
64
+
65
+ C_alpha = np.zeros(input_length, dtype=np.float32)
66
+ C_alpha[:] = A
67
+
68
+ C_gamma = np.zeros((9, input_length), dtype=np.float32)
69
+ C_gamma[9 // 2] = G
70
+
71
+ start = dilation
72
+ end = input_length - padding
73
+
74
+ for gamma_index in range(9 // 2):
75
+ C_alpha[-end:] = C_alpha[-end:] + A[:end]
76
+ C_gamma[gamma_index, -end:] = G[:end]
77
+
78
+ end += dilation
79
+
80
+ for gamma_index in range(9 // 2 + 1, 9):
81
+ C_alpha[:-start] = C_alpha[:-start] + A[start:]
82
+ C_gamma[gamma_index, :-start] = G[start:]
83
+
84
+ start += dilation
85
+
86
+ index_0, index_1, index_2 = indices[kernel_index]
87
+
88
+ C = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
89
+
90
+ biases[feature_index_start:feature_index_end] = np.quantile(C, quantiles[
91
+ feature_index_start:feature_index_end])
92
+
93
+ feature_index_start = feature_index_end
94
+
95
+ return biases
96
+
97
+
98
+ def _fit_dilations(input_length, num_features, max_dilations_per_kernel):
99
+ num_kernels = 84
100
+
101
+ num_features_per_kernel = num_features // num_kernels
102
+ true_max_dilations_per_kernel = min(num_features_per_kernel, max_dilations_per_kernel)
103
+ multiplier = num_features_per_kernel / true_max_dilations_per_kernel
104
+
105
+ max_exponent = np.log2((input_length - 1) / (9 - 1))
106
+ dilations, num_features_per_dilation = \
107
+ np.unique(np.logspace(0, max_exponent, true_max_dilations_per_kernel, base=2).astype(np.int32),
108
+ return_counts=True)
109
+ num_features_per_dilation = (num_features_per_dilation * multiplier).astype(np.int32) # this is a vector
110
+
111
+ remainder = num_features_per_kernel - np.sum(num_features_per_dilation)
112
+ i = 0
113
+ while remainder > 0:
114
+ num_features_per_dilation[i] += 1
115
+ remainder -= 1
116
+ i = (i + 1) % len(num_features_per_dilation)
117
+
118
+ return dilations, num_features_per_dilation
119
+
120
+
121
+ # low-discrepancy sequence to assign quantiles to kernel/dilation combinations
122
+ def _quantiles(n):
123
+ return np.array([(_ * ((np.sqrt(5) + 1) / 2)) % 1 for _ in range(1, n + 1)], dtype=np.float32)
124
+
125
+
126
+ def fit(X, num_features=10_000, max_dilations_per_kernel=32):
127
+ _, input_length = X.shape
128
+
129
+ num_kernels = 84
130
+
131
+ dilations, num_features_per_dilation = _fit_dilations(input_length, num_features, max_dilations_per_kernel)
132
+
133
+ num_features_per_kernel = np.sum(num_features_per_dilation)
134
+
135
+ quantiles = _quantiles(num_kernels * num_features_per_kernel)
136
+
137
+ biases = _fit_biases(X, dilations, num_features_per_dilation, quantiles)
138
+
139
+ return dilations, num_features_per_dilation, biases
140
+
141
+
142
+ @njit(
143
+ "float32[:,:](float64[:,:],float64[:,:],Tuple((int32[:],int32[:],float32[:])),Tuple((int32[:],int32[:],float32[:])),int32)",
144
+ fastmath=True, parallel=True, cache=True)
145
+ def transform(X, X1, parameters, parameters1, n_features_per_kernel=4):
146
+ num_examples, input_length = X.shape
147
+
148
+ dilations, num_features_per_dilation, biases = parameters
149
+ dilations1, num_features_per_dilation1, biases1 = parameters1
150
+
151
+ # equivalent to:
152
+ # >>> from itertools import combinations
153
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
154
+ indices = np.array((
155
+ 0, 1, 2, 0, 1, 3, 0, 1, 4, 0, 1, 5, 0, 1, 6, 0, 1, 7, 0, 1, 8,
156
+ 0, 2, 3, 0, 2, 4, 0, 2, 5, 0, 2, 6, 0, 2, 7, 0, 2, 8, 0, 3, 4,
157
+ 0, 3, 5, 0, 3, 6, 0, 3, 7, 0, 3, 8, 0, 4, 5, 0, 4, 6, 0, 4, 7,
158
+ 0, 4, 8, 0, 5, 6, 0, 5, 7, 0, 5, 8, 0, 6, 7, 0, 6, 8, 0, 7, 8,
159
+ 1, 2, 3, 1, 2, 4, 1, 2, 5, 1, 2, 6, 1, 2, 7, 1, 2, 8, 1, 3, 4,
160
+ 1, 3, 5, 1, 3, 6, 1, 3, 7, 1, 3, 8, 1, 4, 5, 1, 4, 6, 1, 4, 7,
161
+ 1, 4, 8, 1, 5, 6, 1, 5, 7, 1, 5, 8, 1, 6, 7, 1, 6, 8, 1, 7, 8,
162
+ 2, 3, 4, 2, 3, 5, 2, 3, 6, 2, 3, 7, 2, 3, 8, 2, 4, 5, 2, 4, 6,
163
+ 2, 4, 7, 2, 4, 8, 2, 5, 6, 2, 5, 7, 2, 5, 8, 2, 6, 7, 2, 6, 8,
164
+ 2, 7, 8, 3, 4, 5, 3, 4, 6, 3, 4, 7, 3, 4, 8, 3, 5, 6, 3, 5, 7,
165
+ 3, 5, 8, 3, 6, 7, 3, 6, 8, 3, 7, 8, 4, 5, 6, 4, 5, 7, 4, 5, 8,
166
+ 4, 6, 7, 4, 6, 8, 4, 7, 8, 5, 6, 7, 5, 6, 8, 5, 7, 8, 6, 7, 8
167
+ ), dtype=np.int32).reshape(84, 3)
168
+
169
+ num_kernels = len(indices)
170
+ num_dilations = len(dilations)
171
+ num_dilations1 = len(dilations1)
172
+
173
+ num_features = num_kernels * np.sum(num_features_per_dilation)
174
+ num_features1 = num_kernels * np.sum(num_features_per_dilation1)
175
+
176
+ features = np.zeros((num_examples, (num_features + num_features1) * n_features_per_kernel), dtype=np.float32)
177
+ n_features_per_transform = np.int64(features.shape[1] / 2)
178
+
179
+ for example_index in prange(num_examples):
180
+
181
+ _X = X[example_index]
182
+
183
+ A = -_X # A = alpha * X = -X
184
+ G = _X + _X + _X # G = gamma * X = 3X
185
+
186
+ # Base series
187
+ feature_index_start = 0
188
+
189
+ for dilation_index in range(num_dilations):
190
+
191
+ _padding0 = dilation_index % 2
192
+
193
+ dilation = dilations[dilation_index]
194
+ padding = ((9 - 1) * dilation) // 2
195
+
196
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
197
+
198
+ C_alpha = np.zeros(input_length, dtype=np.float32)
199
+ C_alpha[:] = A
200
+
201
+ C_gamma = np.zeros((9, input_length), dtype=np.float32)
202
+ C_gamma[9 // 2] = G
203
+
204
+ start = dilation
205
+ end = input_length - padding
206
+
207
+ for gamma_index in range(9 // 2):
208
+ C_alpha[-end:] = C_alpha[-end:] + A[:end]
209
+ C_gamma[gamma_index, -end:] = G[:end]
210
+
211
+ end += dilation
212
+
213
+ for gamma_index in range(9 // 2 + 1, 9):
214
+ C_alpha[:-start] = C_alpha[:-start] + A[start:]
215
+ C_gamma[gamma_index, :-start] = G[start:]
216
+
217
+ start += dilation
218
+
219
+ for kernel_index in range(num_kernels):
220
+
221
+ feature_index_end = feature_index_start + num_features_this_dilation
222
+
223
+ _padding1 = (_padding0 + kernel_index) % 2
224
+
225
+ index_0, index_1, index_2 = indices[kernel_index]
226
+
227
+ C = C_alpha + \
228
+ C_gamma[index_0] + \
229
+ C_gamma[index_1] + \
230
+ C_gamma[index_2]
231
+
232
+ if _padding1 == 0:
233
+ for feature_count in range(num_features_this_dilation):
234
+ feature_index = feature_index_start + feature_count
235
+ _bias = biases[feature_index]
236
+
237
+ ppv = 0
238
+ last_val = 0
239
+ max_stretch = 0.0
240
+ mean_index = 0
241
+ mean = 0
242
+
243
+ for j in range(C.shape[0]):
244
+ if C[j] > _bias:
245
+ ppv += 1
246
+ mean_index += j
247
+ mean += C[j] + _bias
248
+ elif C[j] < _bias:
249
+ stretch = j - last_val
250
+ if stretch > max_stretch:
251
+ max_stretch = stretch
252
+ last_val = j
253
+ stretch = C.shape[0] - 1 - last_val
254
+ if stretch > max_stretch:
255
+ max_stretch = stretch
256
+
257
+ end = feature_index
258
+ features[example_index, end] = ppv / C.shape[0]
259
+ end = end + num_features
260
+ features[example_index, end] = max_stretch
261
+ end = end + num_features
262
+ features[example_index, end] = mean / ppv if ppv > 0 else 0
263
+ end = end + num_features
264
+ features[example_index, end] = mean_index / ppv if ppv > 0 else -1
265
+ else:
266
+ _c = C[padding:-padding]
267
+
268
+ for feature_count in range(num_features_this_dilation):
269
+ feature_index = feature_index_start + feature_count
270
+ _bias = biases[feature_index]
271
+
272
+ ppv = 0
273
+ last_val = 0
274
+ max_stretch = 0.0
275
+ mean_index = 0
276
+ mean = 0
277
+
278
+ for j in range(_c.shape[0]):
279
+ if _c[j] > _bias:
280
+ ppv += 1
281
+ mean_index += j
282
+ mean += _c[j] + _bias
283
+ elif _c[j] < _bias:
284
+ stretch = j - last_val
285
+ if stretch > max_stretch:
286
+ max_stretch = stretch
287
+ last_val = j
288
+ stretch = _c.shape[0] - 1 - last_val
289
+ if stretch > max_stretch:
290
+ max_stretch = stretch
291
+
292
+ end = feature_index
293
+ features[example_index, end] = ppv / _c.shape[0]
294
+ end = end + num_features
295
+ features[example_index, end] = max_stretch
296
+ end = end + num_features
297
+ features[example_index, end] = mean / ppv if ppv > 0 else 0
298
+ end = end + num_features
299
+ features[example_index, end] = mean_index / ppv if ppv > 0 else -1
300
+
301
+ feature_index_start = feature_index_end
302
+
303
+ # First order difference
304
+ _X1 = X1[example_index]
305
+ A1 = -_X1 # A = alpha * X = -X
306
+ G1 = _X1 + _X1 + _X1 # G = gamma * X = 3X
307
+
308
+ feature_index_start = 0
309
+
310
+ for dilation_index in range(num_dilations1):
311
+
312
+ _padding0 = dilation_index % 2
313
+
314
+ dilation = dilations1[dilation_index]
315
+ padding = ((9 - 1) * dilation) // 2
316
+
317
+ num_features_this_dilation = num_features_per_dilation1[dilation_index]
318
+
319
+ C_alpha = np.zeros(input_length - 1, dtype=np.float32)
320
+ C_alpha[:] = A1
321
+
322
+ C_gamma = np.zeros((9, input_length - 1), dtype=np.float32)
323
+ C_gamma[9 // 2] = G1
324
+
325
+ start = dilation
326
+ end = input_length - padding
327
+
328
+ for gamma_index in range(9 // 2):
329
+ C_alpha[-end:] = C_alpha[-end:] + A1[:end]
330
+ C_gamma[gamma_index, -end:] = G1[:end]
331
+
332
+ end += dilation
333
+
334
+ for gamma_index in range(9 // 2 + 1, 9):
335
+ C_alpha[:-start] = C_alpha[:-start] + A1[start:]
336
+ C_gamma[gamma_index, :-start] = G1[start:]
337
+
338
+ start += dilation
339
+
340
+ for kernel_index in range(num_kernels):
341
+
342
+ feature_index_end = feature_index_start + num_features_this_dilation
343
+
344
+ _padding1 = (_padding0 + kernel_index) % 2
345
+
346
+ index_0, index_1, index_2 = indices[kernel_index]
347
+
348
+ C = C_alpha + \
349
+ C_gamma[index_0] + \
350
+ C_gamma[index_1] + \
351
+ C_gamma[index_2]
352
+
353
+ if _padding1 == 0:
354
+ for feature_count in range(num_features_this_dilation):
355
+ feature_index = feature_index_start + feature_count
356
+ _bias = biases1[feature_index]
357
+
358
+ ppv = 0
359
+ last_val = 0
360
+ max_stretch = 0.0
361
+ mean_index = 0
362
+ mean = 0
363
+
364
+ for j in range(C.shape[0]):
365
+ if C[j] > _bias:
366
+ ppv += 1
367
+ mean_index += j
368
+ mean += C[j] + _bias
369
+ elif C[j] < _bias:
370
+ stretch = j - last_val
371
+ if stretch > max_stretch:
372
+ max_stretch = stretch
373
+ last_val = j
374
+ stretch = C.shape[0] - 1 - last_val
375
+ if stretch > max_stretch:
376
+ max_stretch = stretch
377
+
378
+ end = feature_index + n_features_per_transform
379
+ features[example_index, end] = ppv / C.shape[0]
380
+ end = end + num_features
381
+ features[example_index, end] = max_stretch
382
+ end = end + num_features
383
+ features[example_index, end] = mean / ppv if ppv > 0 else 0
384
+ end = end + num_features
385
+ features[example_index, end] = mean_index / ppv if ppv > 0 else -1
386
+ else:
387
+ _c = C[padding:-padding]
388
+
389
+ for feature_count in range(num_features_this_dilation):
390
+ feature_index = feature_index_start + feature_count
391
+ _bias = biases1[feature_index]
392
+
393
+ ppv = 0
394
+ last_val = 0
395
+ max_stretch = 0.0
396
+ mean_index = 0
397
+ mean = 0
398
+
399
+ for j in range(_c.shape[0]):
400
+ if _c[j] > _bias:
401
+ ppv += 1
402
+ mean_index += j
403
+ mean += _c[j] + _bias
404
+ elif _c[j] < _bias:
405
+ stretch = j - last_val
406
+ if stretch > max_stretch:
407
+ max_stretch = stretch
408
+ last_val = j
409
+ stretch = _c.shape[0] - 1 - last_val
410
+ if stretch > max_stretch:
411
+ max_stretch = stretch
412
+
413
+ end = feature_index + n_features_per_transform
414
+ features[example_index, end] = ppv / _c.shape[0]
415
+ end = end + num_features
416
+ features[example_index, end] = max_stretch
417
+ end = end + num_features
418
+ features[example_index, end] = mean / ppv if ppv > 0 else 0
419
+ end = end + num_features
420
+ features[example_index, end] = mean_index / ppv if ppv > 0 else -1
421
+
422
+ feature_index_start = feature_index_end
423
+
424
+ return features
425
+
426
+
427
+ class MultiRocket:
428
+
429
+ def __init__(
430
+ self,
431
+ num_features=50000,
432
+ classifier="ridge",
433
+ verbose=0
434
+ ):
435
+ self.name = "MultiRocket"
436
+
437
+ self.base_parameters = None
438
+ self.diff1_parameters = None
439
+
440
+ self.n_features_per_kernel = 4
441
+ self.num_features = num_features / 2 # 1 per transformation
442
+ self.num_kernels = int(self.num_features / self.n_features_per_kernel)
443
+
444
+ if verbose > 1:
445
+ print('[{}] Creating {} with {} kernels'.format(self.name, self.name, self.num_kernels))
446
+
447
+ self.clf = classifier
448
+ self.classifier = None
449
+ self.train_duration = 0
450
+ self.test_duration = 0
451
+ self.generate_kernel_duration = 0
452
+ self.train_transforms_duration = 0
453
+ self.test_transforms_duration = 0
454
+ self.apply_kernel_on_train_duration = 0
455
+ self.apply_kernel_on_test_duration = 0
456
+
457
+ self.verbose = verbose
458
+
459
+ def fit(self, x_train, y_train, predict_on_train=True):
460
+ if self.verbose > 1:
461
+ print('[{}] Training with training set of {}'.format(self.name, x_train.shape))
462
+
463
+ self.generate_kernel_duration = 0
464
+ self.apply_kernel_on_train_duration = 0
465
+ self.train_transforms_duration = 0
466
+
467
+ start_time = time.perf_counter()
468
+
469
+ _start_time = time.perf_counter()
470
+ xx = np.diff(x_train, 1)
471
+ self.train_transforms_duration += time.perf_counter() - _start_time
472
+
473
+ _start_time = time.perf_counter()
474
+ self.base_parameters = fit(
475
+ x_train,
476
+ num_features=self.num_kernels
477
+ )
478
+ self.diff1_parameters = fit(
479
+ xx,
480
+ num_features=self.num_kernels
481
+ )
482
+ self.generate_kernel_duration += time.perf_counter() - _start_time
483
+
484
+ _start_time = time.perf_counter()
485
+ x_train_transform = transform(
486
+ x_train, xx,
487
+ self.base_parameters, self.diff1_parameters,
488
+ self.n_features_per_kernel
489
+ )
490
+ self.apply_kernel_on_train_duration += time.perf_counter() - _start_time
491
+
492
+ x_train_transform = np.nan_to_num(x_train_transform)
493
+
494
+ elapsed_time = time.perf_counter() - start_time
495
+ if self.verbose > 1:
496
+ print('[{}] Kernels applied!, took {}s'.format(self.name, elapsed_time))
497
+ print('[{}] Transformed Shape {}'.format(self.name, x_train_transform.shape))
498
+
499
+ if self.verbose > 1:
500
+ print('[{}] Training'.format(self.name))
501
+
502
+ if self.clf.lower() == "ridge":
503
+ self.classifier = make_pipeline(
504
+ StandardScaler(),
505
+ RidgeClassifierCV(
506
+ alphas=np.logspace(-3, 3, 10),
507
+ normalize=False
508
+ )
509
+ )
510
+ else:
511
+ self.classifier = LogisticRegression(
512
+ num_features=x_train_transform.shape[1],
513
+ max_epochs=200,
514
+ )
515
+ _start_time = time.perf_counter()
516
+ self.classifier.fit(x_train_transform, y_train)
517
+ self.train_duration = time.perf_counter() - _start_time
518
+
519
+ if self.verbose > 1:
520
+ print('[{}] Training done!, took {:.3f}s'.format(self.name, self.train_duration))
521
+ if predict_on_train:
522
+ yhat = self.classifier.predict(x_train_transform)
523
+ else:
524
+ yhat = None
525
+
526
+ return yhat
527
+
528
+ def predict(self, x):
529
+ if self.verbose > 1:
530
+ print('[{}] Predicting'.format(self.name))
531
+
532
+ self.apply_kernel_on_test_duration = 0
533
+ self.test_transforms_duration = 0
534
+
535
+ _start_time = time.perf_counter()
536
+ xx = np.diff(x, 1)
537
+ self.test_transforms_duration += time.perf_counter() - _start_time
538
+
539
+ _start_time = time.perf_counter()
540
+ x_transform = transform(
541
+ x, xx,
542
+ self.base_parameters, self.diff1_parameters,
543
+ self.n_features_per_kernel
544
+ )
545
+ self.apply_kernel_on_test_duration += time.perf_counter() - _start_time
546
+
547
+ x_transform = np.nan_to_num(x_transform)
548
+ if self.verbose > 1:
549
+ print('Kernels applied!, took {:.3f}s. Transformed shape: {}.'.format(self.apply_kernel_on_test_duration,
550
+ x_transform.shape))
551
+
552
+ start_time = time.perf_counter()
553
+ yhat = self.classifier.predict(x_transform)
554
+ self.test_duration = time.perf_counter() - start_time
555
+ if self.verbose > 1:
556
+ print("[{}] Predicting completed, took {:.3f}s".format(self.name, self.test_duration))
557
+
558
+ return yhat
time_series_classification/MultiRocket/multirocket/multirocket_multivariate.py ADDED
@@ -0,0 +1,622 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chang Wei Tan, Angus Dempster, Christoph Bergmeir, Geoffrey I Webb
2
+ #
3
+ # MultiRocket: Multiple pooling operators and transformations for fast and effective time series classification
4
+ # https://arxiv.org/abs/2102.00457
5
+
6
+ import time
7
+
8
+ import numpy as np
9
+ from numba import njit, prange
10
+ from sklearn.linear_model import RidgeClassifierCV
11
+ from sklearn.pipeline import make_pipeline
12
+ from sklearn.preprocessing import StandardScaler
13
+
14
+ from multirocket.logistic_regression import LogisticRegression
15
+
16
+
17
+ @njit("float32[:](float64[:,:,:],int32[:],int32[:],int32[:],int32[:],float32[:])",
18
+ fastmath=True, parallel=False, cache=True)
19
+ def _fit_biases(X, num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, quantiles):
20
+ num_examples, num_channels, input_length = X.shape
21
+
22
+ # equivalent to:
23
+ # >>> from itertools import combinations
24
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
25
+ indices = np.array((
26
+ 0, 1, 2, 0, 1, 3, 0, 1, 4, 0, 1, 5, 0, 1, 6, 0, 1, 7, 0, 1, 8,
27
+ 0, 2, 3, 0, 2, 4, 0, 2, 5, 0, 2, 6, 0, 2, 7, 0, 2, 8, 0, 3, 4,
28
+ 0, 3, 5, 0, 3, 6, 0, 3, 7, 0, 3, 8, 0, 4, 5, 0, 4, 6, 0, 4, 7,
29
+ 0, 4, 8, 0, 5, 6, 0, 5, 7, 0, 5, 8, 0, 6, 7, 0, 6, 8, 0, 7, 8,
30
+ 1, 2, 3, 1, 2, 4, 1, 2, 5, 1, 2, 6, 1, 2, 7, 1, 2, 8, 1, 3, 4,
31
+ 1, 3, 5, 1, 3, 6, 1, 3, 7, 1, 3, 8, 1, 4, 5, 1, 4, 6, 1, 4, 7,
32
+ 1, 4, 8, 1, 5, 6, 1, 5, 7, 1, 5, 8, 1, 6, 7, 1, 6, 8, 1, 7, 8,
33
+ 2, 3, 4, 2, 3, 5, 2, 3, 6, 2, 3, 7, 2, 3, 8, 2, 4, 5, 2, 4, 6,
34
+ 2, 4, 7, 2, 4, 8, 2, 5, 6, 2, 5, 7, 2, 5, 8, 2, 6, 7, 2, 6, 8,
35
+ 2, 7, 8, 3, 4, 5, 3, 4, 6, 3, 4, 7, 3, 4, 8, 3, 5, 6, 3, 5, 7,
36
+ 3, 5, 8, 3, 6, 7, 3, 6, 8, 3, 7, 8, 4, 5, 6, 4, 5, 7, 4, 5, 8,
37
+ 4, 6, 7, 4, 6, 8, 4, 7, 8, 5, 6, 7, 5, 6, 8, 5, 7, 8, 6, 7, 8
38
+ ), dtype=np.int32).reshape(84, 3)
39
+
40
+ num_kernels = len(indices)
41
+ num_dilations = len(dilations)
42
+
43
+ num_features = num_kernels * np.sum(num_features_per_dilation)
44
+
45
+ biases = np.zeros(num_features, dtype=np.float32)
46
+
47
+ feature_index_start = 0
48
+
49
+ combination_index = 0
50
+ num_channels_start = 0
51
+
52
+ for dilation_index in range(num_dilations):
53
+
54
+ dilation = dilations[dilation_index]
55
+ padding = ((9 - 1) * dilation) // 2
56
+
57
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
58
+
59
+ for kernel_index in range(num_kernels):
60
+
61
+ feature_index_end = feature_index_start + num_features_this_dilation
62
+
63
+ num_channels_this_combination = num_channels_per_combination[combination_index]
64
+
65
+ num_channels_end = num_channels_start + num_channels_this_combination
66
+
67
+ channels_this_combination = channel_indices[num_channels_start:num_channels_end]
68
+
69
+ _X = X[np.random.randint(num_examples)][channels_this_combination]
70
+
71
+ A = -_X # A = alpha * X = -X
72
+ G = _X + _X + _X # G = gamma * X = 3X
73
+
74
+ C_alpha = np.zeros((num_channels_this_combination, input_length), dtype=np.float32)
75
+ C_alpha[:] = A
76
+
77
+ C_gamma = np.zeros((9, num_channels_this_combination, input_length), dtype=np.float32)
78
+ C_gamma[9 // 2] = G
79
+
80
+ start = dilation
81
+ end = input_length - padding
82
+
83
+ for gamma_index in range(9 // 2):
84
+ C_alpha[:, -end:] = C_alpha[:, -end:] + A[:, :end]
85
+ C_gamma[gamma_index, :, -end:] = G[:, :end]
86
+
87
+ end += dilation
88
+
89
+ for gamma_index in range(9 // 2 + 1, 9):
90
+ C_alpha[:, :-start] = C_alpha[:, :-start] + A[:, start:]
91
+ C_gamma[gamma_index, :, :-start] = G[:, start:]
92
+
93
+ start += dilation
94
+
95
+ index_0, index_1, index_2 = indices[kernel_index]
96
+
97
+ C = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
98
+ C = np.sum(C, axis=0)
99
+
100
+ biases[feature_index_start:feature_index_end] = np.quantile(C, quantiles[
101
+ feature_index_start:feature_index_end])
102
+
103
+ feature_index_start = feature_index_end
104
+
105
+ combination_index += 1
106
+ num_channels_start = num_channels_end
107
+
108
+ return biases
109
+
110
+
111
+ def _fit_dilations(input_length, num_features, max_dilations_per_kernel):
112
+ num_kernels = 84
113
+
114
+ num_features_per_kernel = num_features // num_kernels
115
+ true_max_dilations_per_kernel = min(num_features_per_kernel, max_dilations_per_kernel)
116
+ multiplier = num_features_per_kernel / true_max_dilations_per_kernel
117
+
118
+ max_exponent = np.log2((input_length - 1) / (9 - 1))
119
+ dilations, num_features_per_dilation = \
120
+ np.unique(np.logspace(0, max_exponent, true_max_dilations_per_kernel, base=2).astype(np.int32),
121
+ return_counts=True)
122
+ num_features_per_dilation = (num_features_per_dilation * multiplier).astype(np.int32) # this is a vector
123
+
124
+ remainder = num_features_per_kernel - np.sum(num_features_per_dilation)
125
+ i = 0
126
+ while remainder > 0:
127
+ num_features_per_dilation[i] += 1
128
+ remainder -= 1
129
+ i = (i + 1) % len(num_features_per_dilation)
130
+
131
+ return dilations, num_features_per_dilation
132
+
133
+
134
+ # low-discrepancy sequence to assign quantiles to kernel/dilation combinations
135
+ def _quantiles(n):
136
+ return np.array([(_ * ((np.sqrt(5) + 1) / 2)) % 1 for _ in range(1, n + 1)], dtype=np.float32)
137
+
138
+
139
+ def fit(X, num_features=10_000, max_dilations_per_kernel=32):
140
+ _, num_channels, input_length = X.shape
141
+
142
+ num_kernels = 84
143
+
144
+ dilations, num_features_per_dilation = _fit_dilations(input_length, num_features, max_dilations_per_kernel)
145
+
146
+ num_features_per_kernel = np.sum(num_features_per_dilation)
147
+
148
+ quantiles = _quantiles(num_kernels * num_features_per_kernel)
149
+
150
+ num_dilations = len(dilations)
151
+ num_combinations = num_kernels * num_dilations
152
+
153
+ max_num_channels = min(num_channels, 9)
154
+ max_exponent = np.log2(max_num_channels + 1)
155
+
156
+ num_channels_per_combination = (2 ** np.random.uniform(0, max_exponent, num_combinations)).astype(np.int32)
157
+
158
+ channel_indices = np.zeros(num_channels_per_combination.sum(), dtype=np.int32)
159
+
160
+ num_channels_start = 0
161
+ for combination_index in range(num_combinations):
162
+ num_channels_this_combination = num_channels_per_combination[combination_index]
163
+ num_channels_end = num_channels_start + num_channels_this_combination
164
+ channel_indices[num_channels_start:num_channels_end] = np.random.choice(num_channels,
165
+ num_channels_this_combination,
166
+ replace=False)
167
+
168
+ num_channels_start = num_channels_end
169
+
170
+ biases = _fit_biases(X, num_channels_per_combination, channel_indices,
171
+ dilations, num_features_per_dilation, quantiles)
172
+
173
+ return num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, biases
174
+
175
+
176
+ @njit(
177
+ "float32[:,:](float64[:,:,:],float64[:,:,:],Tuple((int32[:],int32[:],int32[:],int32[:],float32[:])),Tuple((int32[:],int32[:],int32[:],int32[:],float32[:])),int32)",
178
+ fastmath=True, parallel=True, cache=True)
179
+ def transform(X, X1, parameters, parameters1, n_features_per_kernel=4):
180
+ num_examples, num_channels, input_length = X.shape
181
+
182
+ num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, biases = parameters
183
+ _, _, dilations1, num_features_per_dilation1, biases1 = parameters1
184
+
185
+ # equivalent to:
186
+ # >>> from itertools import combinations
187
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
188
+ indices = np.array((
189
+ 0, 1, 2, 0, 1, 3, 0, 1, 4, 0, 1, 5, 0, 1, 6, 0, 1, 7, 0, 1, 8,
190
+ 0, 2, 3, 0, 2, 4, 0, 2, 5, 0, 2, 6, 0, 2, 7, 0, 2, 8, 0, 3, 4,
191
+ 0, 3, 5, 0, 3, 6, 0, 3, 7, 0, 3, 8, 0, 4, 5, 0, 4, 6, 0, 4, 7,
192
+ 0, 4, 8, 0, 5, 6, 0, 5, 7, 0, 5, 8, 0, 6, 7, 0, 6, 8, 0, 7, 8,
193
+ 1, 2, 3, 1, 2, 4, 1, 2, 5, 1, 2, 6, 1, 2, 7, 1, 2, 8, 1, 3, 4,
194
+ 1, 3, 5, 1, 3, 6, 1, 3, 7, 1, 3, 8, 1, 4, 5, 1, 4, 6, 1, 4, 7,
195
+ 1, 4, 8, 1, 5, 6, 1, 5, 7, 1, 5, 8, 1, 6, 7, 1, 6, 8, 1, 7, 8,
196
+ 2, 3, 4, 2, 3, 5, 2, 3, 6, 2, 3, 7, 2, 3, 8, 2, 4, 5, 2, 4, 6,
197
+ 2, 4, 7, 2, 4, 8, 2, 5, 6, 2, 5, 7, 2, 5, 8, 2, 6, 7, 2, 6, 8,
198
+ 2, 7, 8, 3, 4, 5, 3, 4, 6, 3, 4, 7, 3, 4, 8, 3, 5, 6, 3, 5, 7,
199
+ 3, 5, 8, 3, 6, 7, 3, 6, 8, 3, 7, 8, 4, 5, 6, 4, 5, 7, 4, 5, 8,
200
+ 4, 6, 7, 4, 6, 8, 4, 7, 8, 5, 6, 7, 5, 6, 8, 5, 7, 8, 6, 7, 8
201
+ ), dtype=np.int32).reshape(84, 3)
202
+
203
+ num_kernels = len(indices)
204
+ num_dilations = len(dilations)
205
+ num_dilations1 = len(dilations1)
206
+
207
+ num_features = num_kernels * np.sum(num_features_per_dilation)
208
+ num_features1 = num_kernels * np.sum(num_features_per_dilation1)
209
+
210
+ features = np.zeros((num_examples, (num_features + num_features1) * n_features_per_kernel), dtype=np.float32)
211
+ n_features_per_transform = np.int64(features.shape[1] / 2)
212
+
213
+ for example_index in prange(num_examples):
214
+
215
+ _X = X[example_index]
216
+
217
+ A = -_X # A = alpha * X = -X
218
+ G = _X + _X + _X # G = gamma * X = 3X
219
+
220
+ # Base series
221
+ feature_index_start = 0
222
+
223
+ combination_index = 0
224
+ num_channels_start = 0
225
+
226
+ for dilation_index in range(num_dilations):
227
+
228
+ _padding0 = dilation_index % 2
229
+
230
+ dilation = dilations[dilation_index]
231
+ padding = ((9 - 1) * dilation) // 2
232
+
233
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
234
+
235
+ C_alpha = np.zeros((num_channels, input_length), dtype=np.float32)
236
+ C_alpha[:] = A
237
+
238
+ C_gamma = np.zeros((9, num_channels, input_length), dtype=np.float32)
239
+ C_gamma[9 // 2] = G
240
+
241
+ start = dilation
242
+ end = input_length - padding
243
+
244
+ for gamma_index in range(9 // 2):
245
+ C_alpha[:, -end:] = C_alpha[:, -end:] + A[:, :end]
246
+ C_gamma[gamma_index, :, -end:] = G[:, :end]
247
+
248
+ end += dilation
249
+
250
+ for gamma_index in range(9 // 2 + 1, 9):
251
+ C_alpha[:, :-start] = C_alpha[:, :-start] + A[:, start:]
252
+ C_gamma[gamma_index, :, :-start] = G[:, start:]
253
+
254
+ start += dilation
255
+
256
+ for kernel_index in range(num_kernels):
257
+
258
+ feature_index_end = feature_index_start + num_features_this_dilation
259
+
260
+ num_channels_this_combination = num_channels_per_combination[combination_index]
261
+
262
+ num_channels_end = num_channels_start + num_channels_this_combination
263
+
264
+ channels_this_combination = channel_indices[num_channels_start:num_channels_end]
265
+
266
+ _padding1 = (_padding0 + kernel_index) % 2
267
+
268
+ index_0, index_1, index_2 = indices[kernel_index]
269
+
270
+ C = C_alpha[channels_this_combination] + \
271
+ C_gamma[index_0][channels_this_combination] + \
272
+ C_gamma[index_1][channels_this_combination] + \
273
+ C_gamma[index_2][channels_this_combination]
274
+ C = np.sum(C, axis=0)
275
+
276
+ if _padding1 == 0:
277
+ for feature_count in range(num_features_this_dilation):
278
+ feature_index = feature_index_start + feature_count
279
+ _bias = biases[feature_index]
280
+
281
+ ppv = 0
282
+ last_val = 0
283
+ max_stretch = 0.0
284
+ mean_index = 0
285
+ mean = 0
286
+
287
+ for j in range(C.shape[0]):
288
+ if C[j] > _bias:
289
+ ppv += 1
290
+ mean_index += j
291
+ mean += C[j] + _bias
292
+ elif C[j] < _bias:
293
+ stretch = j - last_val
294
+ if stretch > max_stretch:
295
+ max_stretch = stretch
296
+ last_val = j
297
+ stretch = C.shape[0] - 1 - last_val
298
+ if stretch > max_stretch:
299
+ max_stretch = stretch
300
+
301
+ end = feature_index
302
+ features[example_index, end] = ppv / C.shape[0]
303
+ end = end + num_features
304
+ features[example_index, end] = max_stretch
305
+ end = end + num_features
306
+ features[example_index, end] = mean / ppv if ppv > 0 else 0
307
+ end = end + num_features
308
+ features[example_index, end] = mean_index / ppv if ppv > 0 else -1
309
+ else:
310
+ _c = C[padding:-padding]
311
+
312
+ for feature_count in range(num_features_this_dilation):
313
+ feature_index = feature_index_start + feature_count
314
+ _bias = biases[feature_index]
315
+
316
+ ppv = 0
317
+ last_val = 0
318
+ max_stretch = 0.0
319
+ mean_index = 0
320
+ mean = 0
321
+
322
+ for j in range(_c.shape[0]):
323
+ if _c[j] > _bias:
324
+ ppv += 1
325
+ mean_index += j
326
+ mean += _c[j] + _bias
327
+ elif _c[j] < _bias:
328
+ stretch = j - last_val
329
+ if stretch > max_stretch:
330
+ max_stretch = stretch
331
+ last_val = j
332
+ stretch = _c.shape[0] - 1 - last_val
333
+ if stretch > max_stretch:
334
+ max_stretch = stretch
335
+
336
+ end = feature_index
337
+ features[example_index, end] = ppv / _c.shape[0]
338
+ end = end + num_features
339
+ features[example_index, end] = max_stretch
340
+ end = end + num_features
341
+ features[example_index, end] = mean / ppv if ppv > 0 else 0
342
+ end = end + num_features
343
+ features[example_index, end] = mean_index / ppv if ppv > 0 else -1
344
+
345
+ feature_index_start = feature_index_end
346
+
347
+ combination_index += 1
348
+ num_channels_start = num_channels_end
349
+
350
+ # First order difference
351
+ _X1 = X1[example_index]
352
+ A1 = -_X1 # A = alpha * X = -X
353
+ G1 = _X1 + _X1 + _X1 # G = gamma * X = 3X
354
+
355
+ feature_index_start = 0
356
+
357
+ combination_index = 0
358
+ num_channels_start = 0
359
+
360
+ for dilation_index in range(num_dilations1):
361
+
362
+ _padding0 = dilation_index % 2
363
+
364
+ dilation = dilations1[dilation_index]
365
+ padding = ((9 - 1) * dilation) // 2
366
+
367
+ num_features_this_dilation = num_features_per_dilation1[dilation_index]
368
+
369
+ C_alpha = np.zeros((num_channels, input_length - 1), dtype=np.float32)
370
+ C_alpha[:] = A1
371
+
372
+ C_gamma = np.zeros((9, num_channels, input_length - 1), dtype=np.float32)
373
+ C_gamma[9 // 2] = G1
374
+
375
+ start = dilation
376
+ end = input_length - padding
377
+
378
+ for gamma_index in range(9 // 2):
379
+ C_alpha[:, -end:] = C_alpha[:, -end:] + A1[:, :end]
380
+ C_gamma[gamma_index, :, -end:] = G1[:, :end]
381
+
382
+ end += dilation
383
+
384
+ for gamma_index in range(9 // 2 + 1, 9):
385
+ C_alpha[:, :-start] = C_alpha[:, :-start] + A1[:, start:]
386
+ C_gamma[gamma_index, :, :-start] = G1[:, start:]
387
+
388
+ start += dilation
389
+
390
+ for kernel_index in range(num_kernels):
391
+
392
+ feature_index_end = feature_index_start + num_features_this_dilation
393
+
394
+ num_channels_this_combination = num_channels_per_combination[combination_index]
395
+
396
+ num_channels_end = num_channels_start + num_channels_this_combination
397
+
398
+ channels_this_combination = channel_indices[num_channels_start:num_channels_end]
399
+
400
+ _padding1 = (_padding0 + kernel_index) % 2
401
+
402
+ index_0, index_1, index_2 = indices[kernel_index]
403
+
404
+ C = C_alpha[channels_this_combination] + \
405
+ C_gamma[index_0][channels_this_combination] + \
406
+ C_gamma[index_1][channels_this_combination] + \
407
+ C_gamma[index_2][channels_this_combination]
408
+ C = np.sum(C, axis=0)
409
+
410
+ if _padding1 == 0:
411
+ for feature_count in range(num_features_this_dilation):
412
+ feature_index = feature_index_start + feature_count
413
+ _bias = biases1[feature_index]
414
+
415
+ ppv = 0
416
+ last_val = 0
417
+ max_stretch = 0.0
418
+ mean_index = 0
419
+ mean = 0
420
+
421
+ for j in range(C.shape[0]):
422
+ if C[j] > _bias:
423
+ ppv += 1
424
+ mean_index += j
425
+ mean += C[j] + _bias
426
+ elif C[j] < _bias:
427
+ stretch = j - last_val
428
+ if stretch > max_stretch:
429
+ max_stretch = stretch
430
+ last_val = j
431
+ stretch = C.shape[0] - 1 - last_val
432
+ if stretch > max_stretch:
433
+ max_stretch = stretch
434
+
435
+ end = feature_index + n_features_per_transform
436
+ features[example_index, end] = ppv / C.shape[0]
437
+ end = end + num_features
438
+ features[example_index, end] = max_stretch
439
+ end = end + num_features
440
+ features[example_index, end] = mean / ppv if ppv > 0 else 0
441
+ end = end + num_features
442
+ features[example_index, end] = mean_index / ppv if ppv > 0 else -1
443
+ else:
444
+ _c = C[padding:-padding]
445
+
446
+ for feature_count in range(num_features_this_dilation):
447
+ feature_index = feature_index_start + feature_count
448
+ _bias = biases1[feature_index]
449
+
450
+ ppv = 0
451
+ last_val = 0
452
+ max_stretch = 0.0
453
+ mean_index = 0
454
+ mean = 0
455
+
456
+ for j in range(_c.shape[0]):
457
+ if _c[j] > _bias:
458
+ ppv += 1
459
+ mean_index += j
460
+ mean += _c[j] + _bias
461
+ elif _c[j] < _bias:
462
+ stretch = j - last_val
463
+ if stretch > max_stretch:
464
+ max_stretch = stretch
465
+ last_val = j
466
+ stretch = _c.shape[0] - 1 - last_val
467
+ if stretch > max_stretch:
468
+ max_stretch = stretch
469
+
470
+ end = feature_index + n_features_per_transform
471
+ features[example_index, end] = ppv / _c.shape[0]
472
+ end = end + num_features
473
+ features[example_index, end] = max_stretch
474
+ end = end + num_features
475
+ features[example_index, end] = mean / ppv if ppv > 0 else 0
476
+ end = end + num_features
477
+ features[example_index, end] = mean_index / ppv if ppv > 0 else -1
478
+
479
+ feature_index_start = feature_index_end
480
+
481
+ return features
482
+
483
+
484
+ class MultiRocket:
485
+
486
+ def __init__(
487
+ self,
488
+ num_features=50000,
489
+ classifier="ridge",
490
+ verbose=0
491
+ ):
492
+ self.name = "MultiRocket"
493
+
494
+ self.base_parameters = None
495
+ self.diff1_parameters = None
496
+
497
+ self.n_features_per_kernel = 4
498
+ self.num_features = num_features / 2 # 1 per transformation
499
+ self.num_kernels = int(self.num_features / self.n_features_per_kernel)
500
+
501
+ if verbose > 1:
502
+ print('[{}] Creating {} with {} kernels'.format(self.name, self.name, self.num_kernels))
503
+
504
+ self.clf = classifier
505
+ self.classifier = None
506
+ self.train_duration = 0
507
+ self.test_duration = 0
508
+ self.generate_kernel_duration = 0
509
+ self.train_transforms_duration = 0
510
+ self.test_transforms_duration = 0
511
+ self.apply_kernel_on_train_duration = 0
512
+ self.apply_kernel_on_test_duration = 0
513
+
514
+ self.verbose = verbose
515
+
516
+ def fit(self, x_train, y_train, predict_on_train=True):
517
+ if self.verbose > 1:
518
+ print('[{}] Training with training set of {}'.format(self.name, x_train.shape))
519
+ if x_train.shape[2] < 10:
520
+ # handling very short series (like PensDigit from the MTSC archive)
521
+ # series have to be at least a length of 10 (including differencing)
522
+ _x_train = np.zeros((x_train.shape[0], x_train.shape[1], 10), dtype=x_train.dtype)
523
+ _x_train[:, :, :x_train.shape[2]] = x_train
524
+ x_train = _x_train
525
+ del _x_train
526
+
527
+ self.generate_kernel_duration = 0
528
+ self.apply_kernel_on_train_duration = 0
529
+ self.train_transforms_duration = 0
530
+
531
+ start_time = time.perf_counter()
532
+
533
+ _start_time = time.perf_counter()
534
+ xx = np.diff(x_train, 1)
535
+ self.train_transforms_duration += time.perf_counter() - _start_time
536
+
537
+ _start_time = time.perf_counter()
538
+ self.base_parameters = fit(
539
+ x_train,
540
+ num_features=self.num_kernels
541
+ )
542
+ self.diff1_parameters = fit(
543
+ xx,
544
+ num_features=self.num_kernels
545
+ )
546
+ self.generate_kernel_duration += time.perf_counter() - _start_time
547
+
548
+ _start_time = time.perf_counter()
549
+ x_train_transform = transform(
550
+ x_train, xx,
551
+ self.base_parameters, self.diff1_parameters,
552
+ self.n_features_per_kernel
553
+ )
554
+ self.apply_kernel_on_train_duration += time.perf_counter() - _start_time
555
+
556
+ x_train_transform = np.nan_to_num(x_train_transform)
557
+
558
+ elapsed_time = time.perf_counter() - start_time
559
+ if self.verbose > 1:
560
+ print('[{}] Kernels applied!, took {}s'.format(self.name, elapsed_time))
561
+ print('[{}] Transformed Shape {}'.format(self.name, x_train_transform.shape))
562
+
563
+ if self.verbose > 1:
564
+ print('[{}] Training'.format(self.name))
565
+
566
+ if self.clf.lower() == "ridge":
567
+ self.classifier = make_pipeline(
568
+ StandardScaler(),
569
+ RidgeClassifierCV(
570
+ alphas=np.logspace(-3, 3, 10),
571
+ normalize=False
572
+ )
573
+ )
574
+ else:
575
+ self.classifier = LogisticRegression(
576
+ num_features=x_train_transform.shape[1],
577
+ max_epochs=200,
578
+ )
579
+ _start_time = time.perf_counter()
580
+ self.classifier.fit(x_train_transform, y_train)
581
+ self.train_duration = time.perf_counter() - _start_time
582
+
583
+ if self.verbose > 1:
584
+ print('[{}] Training done!, took {:.3f}s'.format(self.name, self.train_duration))
585
+ if predict_on_train:
586
+ yhat = self.classifier.predict(x_train_transform)
587
+ else:
588
+ yhat = None
589
+
590
+ return yhat
591
+
592
+ def predict(self, x):
593
+ if self.verbose > 1:
594
+ print('[{}] Predicting'.format(self.name))
595
+
596
+ self.apply_kernel_on_test_duration = 0
597
+ self.test_transforms_duration = 0
598
+
599
+ _start_time = time.perf_counter()
600
+ xx = np.diff(x, 1)
601
+ self.test_transforms_duration += time.perf_counter() - _start_time
602
+
603
+ _start_time = time.perf_counter()
604
+ x_transform = transform(
605
+ x, xx,
606
+ self.base_parameters, self.diff1_parameters,
607
+ self.n_features_per_kernel
608
+ )
609
+ self.apply_kernel_on_test_duration += time.perf_counter() - _start_time
610
+
611
+ x_transform = np.nan_to_num(x_transform)
612
+ if self.verbose > 1:
613
+ print('Kernels applied!, took {:.3f}s. Transformed shape: {}.'.format(self.apply_kernel_on_test_duration,
614
+ x_transform.shape))
615
+
616
+ start_time = time.perf_counter()
617
+ yhat = self.classifier.predict(x_transform)
618
+ self.test_duration = time.perf_counter() - start_time
619
+ if self.verbose > 1:
620
+ print("[{}] Predicting completed, took {:.3f}s".format(self.name, self.test_duration))
621
+
622
+ return yhat
time_series_classification/MultiRocket/requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ numba==0.50.1
2
+ numpy==1.18.5
3
+ pandas == 1.0.5
4
+ scikit_learn>=0.23.1
5
+ sktime==0.4.3
6
+ torch==1.11.0+cu113
time_series_classification/MultiRocket/scripts/example.sh ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ #SBATCH --job-name=face_detection
3
+ #SBATCH --output=fd.out
4
+ #SBATCH --error=fd.err
5
+ #SBATCH --partition=CPU
6
+ #SBATCH --nodes=1
7
+ #SBATCH --ntasks=1
8
+ #SBATCH --cpus-per-task=40
9
+ #SBATCH --chdir=/home/bakhshaliyev/classification-aug/MultiRocket # change to the directory
10
+
11
+
12
+ echo "Activating environment..."
13
+ source ~/venvs/sktime-env/bin/activate # change to your virtual environment path
14
+
15
+ echo "Running script..."
16
+
17
+
18
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1
19
+
20
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --jitter
21
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --scaling
22
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --rotation
23
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --permutation
24
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --randompermutation
25
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --magwarp
26
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --timewarp
27
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --windowslice
28
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --windowwarp
29
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --spawner
30
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --dtwwarp
31
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --shapedtwwarp
32
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --wdba
33
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --discdtw
34
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --discsdtw
35
+
36
+
37
+
38
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 420 --stride 1 --shuffle_rate 0.8
39
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 120 --stride 1 --shuffle_rate 0.7
40
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 4 --stride 4 --shuffle_rate 0.6
41
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 64 --stride 2 --shuffle_rate 0.6
42
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 32 --stride 1 --shuffle_rate 0.6
43
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 8 --stride 4 --shuffle_rate 1.0
44
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 6 --stride 4 --shuffle_rate 1.0
45
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 120 --stride 1 --shuffle_rate 1.0
46
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 96 --stride 8 --shuffle_rate 0.8
47
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 48 --stride 36 --shuffle_rate 0.9
48
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 24 --stride 36 --shuffle_rate 0.8
49
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 240 --stride 1 --shuffle_rate 0.8
50
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 240 --stride 1 --shuffle_rate 0.4
51
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 240 --stride 1 --shuffle_rate 0.2
52
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 96 --stride 96 --shuffle_rate 1.0
53
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 96 --stride 96 --shuffle_rate 0.6
54
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 120 --stride 48 --shuffle_rate 0.8
55
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 240 --stride 96 --shuffle_rate 1.0
56
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 12 --stride 96 --shuffle_rate 0.8
57
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 2 --stride 1 --shuffle_rate 0.4
58
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 480 --stride 1 --shuffle_rate 0.7
59
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 900 --stride 1 --shuffle_rate 0.8
60
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 64 --stride 24 --shuffle_rate 0.8
61
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 64 --stride 24 --shuffle_rate 1.0
62
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 148 --stride 24 --shuffle_rate 1.0
63
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 148 --stride 96 --shuffle_rate 1.0
64
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 148 --stride 48 --shuffle_rate 1.0
65
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 180 --stride 12 --shuffle_rate 0.8
66
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 180 --stride 24 --shuffle_rate 0.8
67
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 180 --stride 12 --shuffle_rate 0.5
68
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 152 --stride 96 --shuffle_rate 1.0
69
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 360 --stride 1 --shuffle_rate 0.1
70
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 72 --stride 2 --shuffle_rate 0.4
71
+ srun python3 main.py --problem FaceDetection --iter 5 --verbose 1 --use-augmentation --augmentation-ratio 1 --tps --patch_len 120 --stride 1 --shuffle_rate 0.2
72
+
73
+
74
+
75
+
76
+ echo "Job finished"
time_series_classification/MultiRocket/utils/data_loader.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chang Wei Tan, Angus Dempster, Christoph Bergmeir, Geoffrey I Webb
2
+ #
3
+ # MultiRocket: Multiple pooling operators and transformations for fast and effective time series classification
4
+ # https://arxiv.org/abs/2102.00457
5
+
6
+ import os
7
+ import random
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ from sklearn.preprocessing import StandardScaler
12
+
13
+ non_109_datasets = ["HandOutlines",
14
+ "NonInvasiveFetalECGThorax1",
15
+ "NonInvasiveFetalECGThorax2",
16
+ "AllGestureWiimoteX",
17
+ "AllGestureWiimoteY",
18
+ "AllGestureWiimoteZ",
19
+ "DodgerLoopDay",
20
+ "DodgerLoopGame",
21
+ "DodgerLoopWeekend",
22
+ "Fungi",
23
+ "GestureMidAirD1",
24
+ "GestureMidAirD2",
25
+ "GestureMidAirD3",
26
+ "GesturePebbleZ1",
27
+ "GesturePebbleZ2",
28
+ "MelbournePedestrian",
29
+ "PickupGestureWiimoteZ",
30
+ "PLAID",
31
+ "ShakeGestureWiimoteZ"]
32
+
33
+ classification_datasets = ["Adiac", # 390,391,37,176,0.3887,0.3913 (3),0.3964
34
+ "ArrowHead", # 36,175,3,251,0.2,0.2000 (0),0.2971
35
+ "Beef", # 30,30,5,470,0.3333,0.3333 (0),0.3667
36
+ "BeetleFly", # 20,20,2,512,0.25,0.3000 (7),0.3
37
+ "BirdChicken", # 20,20,2,512,0.45,0.3000 (6),0.25
38
+ "Car", # 60,60,4,577,0.2667,0.2333 (1),0.2667
39
+ "CBF", # 30,900,3,128,0.1478,0.0044 (11),0.0033
40
+ "ChlorineConcentration", # 467,3840,3,166,0.35,0.3500 (0),0.3516
41
+ "CinCECGTorso", # 40,1380,4,1639,0.1029,0.0696 (1),0.3493
42
+ "Coffee", # 28,28,2,286,0,0.0000 (0),0
43
+ "Computers", # 250,250,2,720,0.424,0.3800 (12),0.3
44
+ "CricketX", # 390,390,12,300,0.4231,0.2282 (10),0.2462
45
+ "CricketY", # 390,390,12,300,0.4333,0.2410 (17),0.2564
46
+ "CricketZ", # 390,390,12,300,0.4128,0.2538 (5),0.2462
47
+ "DiatomSizeReduction", # 16,306,4,345,0.0654,0.0654 (0),0.0327
48
+ "DistalPhalanxOutlineAgeGroup", # 400,139,3,80,0.3741,0.3741 (0),0.2302
49
+ "DistalPhalanxOutlineCorrect", # 600,276,2,80,0.2826,0.2754 (1),0.2826
50
+ "DistalPhalanxTW", # 400,139,6,80,0.3669,0.3669 (0),0.4101
51
+ "Earthquakes", # 322,139,2,512,0.2878,0.2734 (6),0.2806
52
+ "ECG200", # 100,100,2,96,0.12,0.1200 (0),0.23
53
+ "ECG5000", # 500,4500,5,140,0.0751,0.0749 (1),0.0756
54
+ "ECGFiveDays", # 23,861,2,136,0.2033,0.2033 (0),0.2323
55
+ "ElectricDevices", # 8926,7711,7,96,0.4492,0.3806 (14),0.3988
56
+ "FaceAll", # 560,1690,14,131,0.2864,0.1917 (3),0.1923
57
+ "FaceFour", # 24,88,4,350,0.2159,0.1136 (2),0.1705
58
+ "FacesUCR", # 200,2050,14,131,0.2307,0.0878 (12),0.0951
59
+ "FiftyWords", # 450,455,50,270,0.3692,0.2418 (6),0.3099
60
+ "Fish", # 175,175,7,463,0.2171,0.1543 (4),0.1771
61
+ "FordA", # 3601,1320,2,500,0.3348,0.3091 (1),0.4455
62
+ "FordB", # 3636,810,2,500,0.3938,0.3926 (1),0.3802
63
+ "GunPoint", # 50,150,2,150,0.0867,0.0867 (0) ,0.0933
64
+ "Ham", # 109,105,2,431,0.4,0.4000 (0),0.5333
65
+ "HandOutlines", # 1000,370,2,2709,0.1378,0.1378 (0),0.1189
66
+ "Haptics", # 155,308,5,1092,0.6299,0.5877 (2),0.6234
67
+ "Herring", # 64,64,2,512,0.4844,0.4688 (5),0.4688
68
+ "InlineSkate", # 100,550,7,1882,0.6582,0.6127 (14),0.6164
69
+ "InsectWingbeatSound", # 220,1980,11,256,0.4384,0.4152 (1),0.6449
70
+ "ItalyPowerDemand", # 67,1029,2,24,0.0447,0.0447 (0),0.0496
71
+ "LargeKitchenAppliances", # 375,375,3,720,0.5067,0.2053 (94),0.2053
72
+ "Lightning2", # 60,61,2,637,0.2459,0.1311 (6),0.1311
73
+ "Lightning7", # 70,73,7,319,0.4247,0.2877 (5),0.274
74
+ "Mallat", # 55,2345,8,1024,0.0857,0.0857 (0),0.0661
75
+ "Meat", # 60,60,3,448,0.0667,0.0667 (0),0.0667
76
+ "MedicalImages", # 381,760,10,99,0.3158,0.2526 (20),0.2632
77
+ "MiddlePhalanxOutlineAgeGroup", # 400,154,3,80,0.4805,0.4805 (0),0.5
78
+ "MiddlePhalanxOutlineCorrect", # 600,291,2,80,0.2337,0.2337 (0),0.3024
79
+ "MiddlePhalanxTW", # 399,154,6,80,0.487,0.4935 (3),0.4935
80
+ "MoteStrain", # 20,1252,2,84,0.1214,0.1342 (1),0.1653
81
+ "NonInvasiveFetalECGThorax1", # 1800,1965,42,750,0.171,0.1893 (1),0.2097
82
+ "NonInvasiveFetalECGThorax2", # 1800,1965,42,750,0.1201,0.1290 (1),0.1354
83
+ "OliveOil", # 30,30,4,570,0.1333,0.1333 (0),0.1667
84
+ "OSULeaf", # 200,242,6,427,0.4793,0.3884 (7),0.4091
85
+ "PhalangesOutlinesCorrect", # 1800,858,2,80,0.2389,0.2389 (0),0.2716
86
+ "Phoneme", # 214,1896,39,1024,0.8908,0.7727 (14),0.7716
87
+ "Plane", # 105,105,7,144,0.0381,0.0000 (5),0
88
+ "ProximalPhalanxOutlineAgeGroup", # 400,205,3,80,0.2146,0.2146 (0),0.1951
89
+ "ProximalPhalanxOutlineCorrect", # 600,291,2,80,0.1924,0.2096 (1),0.2165
90
+ "ProximalPhalanxTW", # 400,205,6,80,0.2927,0.2439 (2),0.2439
91
+ "RefrigerationDevices", # 375,375,3,720,0.6053,0.5600 (8),0.536
92
+ "ScreenType", # 375,375,3,720,0.64,0.5893 (17),0.6027
93
+ "ShapeletSim", # 20,180,2,500,0.4611,0.3000 (3),0.35
94
+ "ShapesAll", # 600,600,60,512,0.2483,0.1980 (4),0.2317
95
+ "SmallKitchenAppliances", # 375,375,3,720,0.6587,0.3280 (15),0.3573
96
+ "SonyAIBORobotSurface1", # 20,601,2,70,0.3045,0.3045 (0),0.2745
97
+ "SonyAIBORobotSurface2", # 27,953,2,65,0.1406,0.1406 (0),0.1689
98
+ "StarLightCurves", # 1000,8236,3,1024,0.1512,0.0947 (16),0.0934
99
+ "Strawberry", # 613,370,2,235,0.0541,0.0541 (0),0.0595
100
+ "SwedishLeaf", # 500,625,15,128,0.2112,0.1536 (2),0.208
101
+ "Symbols", # 25,995,6,398,0.1005,0.0623 (8),0.0503
102
+ "SyntheticControl", # 300,300,6,60,0.12,0.0167 (6),0.0067
103
+ "ToeSegmentation1", # 40,228,2,277,0.3202,0.2500 (8),0.2281
104
+ "ToeSegmentation2", # 36,130,2,343,0.1923,0.0923 (5),0.1615
105
+ "Trace", # 100,100,4,275,0.24,0.0100 (3),0
106
+ "TwoLeadECG", # 23,1139,2,82,0.2529,0.1317 (4),0.0957
107
+ "TwoPatterns", # 1000,4000,4,128,0.0932,0.0015 (4),0
108
+ "UWaveGestureLibraryAll", # 896,3582,8,945,0.0519,0.0343 (4),0.1083
109
+ "UWaveGestureLibraryX", # 896,3582,8,315,0.2607,0.2267 (4),0.2725
110
+ "UWaveGestureLibraryY", # 896,3582,8,315,0.338,0.3009 (4),0.366
111
+ "UWaveGestureLibraryZ", # 896,3582,8,315,0.3504,0.3222 (6),0.3417
112
+ "Wafer", # 1000,6164,2,152,0.0045,0.0045 (1),0.0201
113
+ "Wine", # 57,54,2,234,0.3889,0.3889 (0),0.4259
114
+ "WordSynonyms", # 267,638,25,270,0.3824,0.2618 (9),0.3511
115
+ "Worms", # 181,77,5,900,0.5455,0.4675 (9),0.4156
116
+ "WormsTwoClass", # 181,77,2,900,0.3896,0.4156 (7),0.3766
117
+ "Yoga", # 300,3000,2,426,0.1697,0.1560 (7),0.1637
118
+ "ACSF1", # 100,100,10,1460,0.46,0.3800 (4),0.36
119
+ "AllGestureWiimoteX", # 300,700,10,Vary,0.4843, 0.2829 (14),0.2843
120
+ "AllGestureWiimoteY", # 300,700,10,Vary,0.4314, 0.2700 (9),0.2714
121
+ "AllGestureWiimoteZ", # 300,700,10,Vary,0.5457,0.3486 (11),0.3571
122
+ "BME", # 30,150,3,128,0.1667,0.0200 (4),0.1
123
+ "Chinatown", # 20,345,2,24,0.0464,0.0464 (0),0.0435
124
+ "Crop", # 7200,16800,24,46,0.2883,0.2883 (0),0.3348
125
+ "DodgerLoopDay", # 78,80,7,288,0.45, 0.4125 (1),0.5
126
+ "DodgerLoopGame", # 20,138,2,288,0.1159, 0.0725 (1),0.1232
127
+ "DodgerLoopWeekend", # 20,138,2,288,0.0145, 0.0217 (1),0.0507
128
+ "EOGHorizontalSignal", # 362,362,12,1250,0.5829, 0.5249 (1),0.4972
129
+ "EOGVerticalSignal", # 362,362,12,1250,0.558, 0.5249 (2),0.5525
130
+ "EthanolLevel", # 504,500,4,1751,0.726,0.7180 (1),0.724
131
+ "FreezerRegularTrain", # 150,2850,2,301,0.1951,0.0930 (1),0.1011
132
+ "FreezerSmallTrain", # 28,2850,2,301,0.3302,0.3302 (0),0.2467
133
+ "Fungi", # 18,186,18,201,0.1774,0.1774 (0),0.1613
134
+ "GestureMidAirD1", # 208,130,26,Vary,0.4231, 0.3615 (5),0.4308
135
+ "GestureMidAirD2", # 208,130,26,Vary,0.5077, 0.4000 (6),0.3923
136
+ "GestureMidAirD3", # 208,130,26,Vary,0.6538, 0.6231 (1),0.6769
137
+ "GesturePebbleZ1", # 132,172,6,Vary,0.2674,0.1744 (2),0.2093
138
+ "GesturePebbleZ2", # 146,158,6,Vary,0.3291,0.2215 (6),0.3291
139
+ "GunPointAgeSpan", # 135,316,2,150,0.1013,0.0348 (3),0.0823
140
+ "GunPointMaleVersusFemale", # 135,316,2,150,0.0253,0.0253 (0),0.0032
141
+ "GunPointOldVersusYoung", # 135,316,2,150,0.0476,0.0349 (4),0.1619
142
+ "HouseTwenty", # 40,119,2,2000,0.3361, 0.0588 (33),0.0756
143
+ "InsectEPGRegularTrain", # 62,249,3,601,0.3213,0.1727 (11),0.1285
144
+ "InsectEPGSmallTrain", # 17,249,3,601,0.3373,0.3052 (1),0.2651
145
+ "MelbournePedestrian", # 1200,2450,10,24,0.1518,0.1518 (0),0.2094
146
+ "MixedShapesRegularTrain", # 500,2425,5,1024,0.1027, 0.0911 (4),0.1584
147
+ "MixedShapesSmallTrain", # 100,2425,5,1024,0.1645, 0.1674 (7),0.2202
148
+ "PickupGestureWiimoteZ", # 50,50,10,Vary,0.44,0.3400 (17),0.34
149
+ "PigAirwayPressure", # 104,208,52,2000,0.9423,0.9038 (1),0.8942
150
+ "PigArtPressure", # 104,208,52,2000,0.875,0.8029 (1),0.7548
151
+ "PigCVP", # 104,208,52,2000,0.9183,0.8413 (11),0.8462
152
+ "PLAID", # 537,537,11,Vary,0.4786,0.1862 (3),0.1601
153
+ "PowerCons", # 180,180,2,144,0.0667,0.0778 (3),0.1222
154
+ "Rock", # 20,50,4,2844,0.16, 0.1600 (0),0.4
155
+ "SemgHandGenderCh2", # 300,600,2,1500,0.2383,0.1550 (1),0.1983
156
+ "SemgHandMovementCh2", # 450,450,6,1500,0.6311,0.3622 (1),0.4156
157
+ "SemgHandSubjectCh2", # 450,450,5,1500,0.5956,0.2000 (3),0.2733
158
+ "ShakeGestureWiimoteZ", # 50,50,10,Vary,0.4,0.1600 (6),0.14
159
+ "SmoothSubspace", # 150,150,3,15,0.0933,0.0533 (1),0.1733
160
+ "UMD", # 36,144,3,150,0.2361,0.0278 (6),0.0069
161
+ ]
162
+
163
+
164
+ def get_classification_datasets_summary(dataset=None, subset="full"):
165
+ if subset == "109":
166
+ if os.path.exists("../data/classification_datasets_109.csv"):
167
+ df = pd.read_csv("../data/classification_datasets_109.csv")
168
+ else:
169
+ df = pd.read_csv(os.getcwd() + "/data/classification_datasets_109.csv")
170
+ df.columns = [x.strip() for x in df.columns]
171
+ if dataset is None:
172
+ return df
173
+ elif subset == "bakeoff":
174
+ if os.path.exists("../data/classification_datasets_bakeoff.csv"):
175
+ df = pd.read_csv("../data/classification_datasets_bakeoff.csv")
176
+ else:
177
+ df = pd.read_csv(os.getcwd() + "/data/classification_datasets_bakeoff.csv")
178
+ df.columns = [x.strip() for x in df.columns]
179
+ if dataset is None:
180
+ return df
181
+ elif subset == "development":
182
+ if os.path.exists("../data/classification_datasets_development.csv"):
183
+ df = pd.read_csv("../data/classification_datasets_development.csv")
184
+ else:
185
+ df = pd.read_csv(os.getcwd() + "/data/classification_datasets_development.csv")
186
+ df.columns = [x.strip() for x in df.columns]
187
+ if dataset is None:
188
+ return df
189
+ elif subset == "holdout":
190
+ if os.path.exists("../data/classification_datasets_development.csv"):
191
+ df_dev = pd.read_csv("../data/classification_datasets_development.csv")
192
+ else:
193
+ df_dev = pd.read_csv(os.getcwd() + "/data/classification_datasets_development.csv")
194
+ if os.path.exists("../data/classification_datasets_bakeoff.csv"):
195
+ df = pd.read_csv("../data/classification_datasets_bakeoff.csv")
196
+ else:
197
+ df = pd.read_csv(os.getcwd() + "/data/classification_datasets_bakeoff.csv")
198
+ df = df.loc[~df["Name"].isin(df_dev["Name"])].reset_index(drop=True)
199
+ df.columns = [x.strip() for x in df.columns]
200
+ if dataset is None:
201
+ return df
202
+ else:
203
+ if os.path.exists("../data/classification_datasets.csv"):
204
+ df = pd.read_csv("../data/classification_datasets.csv")
205
+ else:
206
+ df = pd.read_csv(os.getcwd() + "/data/classification_datasets.csv")
207
+ df.columns = [x.strip() for x in df.columns]
208
+ if dataset is None:
209
+ return df
210
+
211
+ return df.loc[df.Name == dataset].reset_index(drop=True)
212
+
213
+
214
+ def read_univariate_ucr(filename, normalise=True):
215
+ if "csv" in filename:
216
+ data = np.loadtxt(filename, delimiter=',')
217
+ else:
218
+ data = np.loadtxt(filename, delimiter='\t')
219
+ Y = data[:, 0]
220
+ X = data[:, 1:]
221
+
222
+ scaler = StandardScaler()
223
+ for i in range(len(X)):
224
+ for j in range(len(X[i])):
225
+ if np.isnan(X[i, j]):
226
+ X[i, j] = random.random() / 1000
227
+ # scale it later
228
+ if normalise:
229
+ tmp = scaler.fit_transform(X[i].reshape(-1, 1))
230
+ X[i] = tmp[:, 0]
231
+ X = X.reshape((X.shape[0], X.shape[1], 1))
232
+ return X, Y
233
+
234
+
235
+ def fill_missing(x: np.array,
236
+ max_len: int,
237
+ vary_len: str = "suffix-noise",
238
+ normalise: bool = True):
239
+ if vary_len == "zero":
240
+ if normalise:
241
+ x = StandardScaler().fit_transform(x)
242
+ x = np.nan_to_num(x)
243
+ elif vary_len == 'prefix-suffix-noise':
244
+ for i in range(len(x)):
245
+ series = list()
246
+ for a in x[i, :]:
247
+ if np.isnan(a):
248
+ break
249
+ series.append(a)
250
+ series = np.array(series)
251
+ seq_len = len(series)
252
+ diff_len = int(0.5 * (max_len - seq_len))
253
+
254
+ for j in range(diff_len):
255
+ x[i, j] = random.random() / 1000
256
+
257
+ for j in range(diff_len, seq_len):
258
+ x[i, j] = series[j - seq_len]
259
+
260
+ for j in range(seq_len, max_len):
261
+ x[i, j] = random.random() / 1000
262
+
263
+ if normalise:
264
+ tmp = StandardScaler().fit_transform(x[i].reshape(-1, 1))
265
+ x[i] = tmp[:, 0]
266
+ elif vary_len == 'uniform-scaling':
267
+ for i in range(len(x)):
268
+ series = list()
269
+ for a in x[i, :]:
270
+ if np.isnan(a):
271
+ break
272
+ series.append(a)
273
+ series = np.array(series)
274
+ seq_len = len(series)
275
+
276
+ for j in range(max_len):
277
+ scaling_factor = int(j * seq_len / max_len)
278
+ x[i, j] = series[scaling_factor]
279
+ if normalise:
280
+ tmp = StandardScaler().fit_transform(x[i].reshape(-1, 1))
281
+ x[i] = tmp[:, 0]
282
+ else:
283
+ for i in range(len(x)):
284
+ for j in range(len(x[i])):
285
+ if np.isnan(x[i, j]):
286
+ x[i, j] = random.random() / 1000
287
+
288
+ if normalise:
289
+ tmp = StandardScaler().fit_transform(x[i].reshape(-1, 1))
290
+ x[i] = tmp[:, 0]
291
+
292
+ return x
293
+
294
+
295
+ def process_ts_data(X,
296
+ vary_len: str = "suffix-noise",
297
+ normalise: bool = False):
298
+ """
299
+ This is a function to process the data, i.e. convert dataframe to numpy array
300
+ :param X:
301
+ :param normalise:
302
+ :return:
303
+ """
304
+ num_instances, num_dim = X.shape
305
+ columns = X.columns
306
+ max_len = np.max([len(X[columns[0]][i]) for i in range(num_instances)])
307
+ output = np.zeros((num_instances, num_dim, max_len), dtype=np.float64)
308
+
309
+ for i in range(num_dim):
310
+ for j in range(num_instances):
311
+ output[j, i, :] = X[columns[i]][j].values
312
+ output[:, i, :] = fill_missing(
313
+ output[:, i, :],
314
+ max_len,
315
+ vary_len,
316
+ normalise
317
+ )
318
+
319
+ return output
time_series_classification/MultiRocket/utils/tools.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chang Wei Tan, Angus Dempster, Christoph Bergmeir, Geoffrey I Webb
2
+ #
3
+ # MultiRocket: Multiple pooling operators and transformations for fast and effective time series classification
4
+ # https://arxiv.org/abs/2102.00457
5
+
6
+ import cmath
7
+ import os
8
+
9
+ import numpy as np
10
+ from numba import njit
11
+
12
+
13
+ # =======================================================================================================
14
+ # Simple functions that worked in Numba
15
+ # =======================================================================================================
16
+ @njit(fastmath=True, cache=True)
17
+ def downsample(x, n):
18
+ len_y = int(np.ceil(len(x) / n))
19
+ y = np.zeros(len_y, dtype=np.float64)
20
+ for i in range(len_y):
21
+ y[i] = x[i * n]
22
+
23
+ return y
24
+
25
+
26
+ @njit(fastmath=True, cache=True)
27
+ def histc(X, bins):
28
+ # https://stackoverflow.com/questions/32765333/how-do-i-replicate-this-matlab-function-in-numpy
29
+ map_to_bins = np.digitize(X, bins)
30
+ r = np.zeros(bins.shape, dtype=np.int32)
31
+ for i in map_to_bins:
32
+ r[i - 1] += 1
33
+ return r, map_to_bins
34
+
35
+
36
+ @njit(fastmath=True, cache=True)
37
+ def numba_dft(x=None, sign=-1):
38
+ N = x.shape[0]
39
+ if np.log2(N) % 1 > 0:
40
+ nFFT = int(2 ** (np.ceil(np.log2(np.abs(N))) + 1))
41
+ z = np.zeros(nFFT, dtype=x.dtype)
42
+ z[:N] = x
43
+ x = z
44
+ N = nFFT
45
+
46
+ dft = np.zeros(N, dtype=np.complex128)
47
+ for i in range(N):
48
+ series_element = 0
49
+ for n in range(N):
50
+ series_element += x[n] * cmath.exp(sign * 2j * cmath.pi * i * n * (1 / N))
51
+ dft[i] = series_element
52
+ if sign == 1:
53
+ dft = dft / N
54
+ return dft
55
+
56
+
57
+ @njit(fastmath=True, cache=True)
58
+ def numba_fft_v(x, sign=-1):
59
+ N = x.shape[0]
60
+ if np.log2(N) % 1 > 0:
61
+ nFFT = int(2 ** (np.ceil(np.log2(np.abs(N))) + 1))
62
+ z = np.zeros(nFFT, dtype=x.dtype)
63
+ z[:N] = x
64
+ x = z
65
+ N = nFFT
66
+
67
+ x = np.asarray(x, dtype=np.complex128)
68
+
69
+ N_min = min(N, 2)
70
+
71
+ n = np.arange(N_min, dtype=np.float64)
72
+ k = n.T.reshape(-1, 1)
73
+ M = np.exp(sign * 2j * np.pi * n * k / N_min)
74
+ X = np.dot(M, x.reshape((N_min, -1)))
75
+ while X.shape[0] < N:
76
+ X_even = X[:, :int(X.shape[1] / 2)]
77
+ X_odd = X[:, int(X.shape[1] / 2):]
78
+ terms = np.exp(sign * 1j * np.pi * np.arange(X.shape[0]) / X.shape[0]).T.reshape(-1, 1)
79
+ X = np.vstack((X_even + terms * X_odd,
80
+ X_even - terms * X_odd))
81
+ if sign == 1:
82
+ return X.ravel() / N
83
+ return X.ravel()
84
+
85
+
86
+ @njit(fastmath=True, cache=True)
87
+ def autocorr(y, fft):
88
+ l = len(y)
89
+
90
+ fft = fft * np.conjugate(fft)
91
+ acf = numba_fft_v(fft, sign=1)
92
+
93
+ acf = acf.real
94
+ acf = acf / acf[0]
95
+ acf = acf[:l]
96
+
97
+ return acf
98
+
99
+
100
+ @njit(fastmath=True, cache=True)
101
+ def numba_std(values, mean):
102
+ if len(values) == 1:
103
+ return 0
104
+ sum_squares_diff = 0
105
+ for v in values:
106
+ diff = v - mean
107
+ sum_squares_diff += diff * diff
108
+
109
+ return np.sqrt(sum_squares_diff / (len(values) - 1))
110
+
111
+
112
+ @njit(fastmath=True, cache=True)
113
+ def numba_min(a, b):
114
+ if a < b:
115
+ return a
116
+ return b
117
+
118
+
119
+ @njit(fastmath=True, cache=True)
120
+ def numba_max(a, b):
121
+ if a < b:
122
+ return b
123
+ return a
124
+
125
+
126
+ @njit(fastmath=True, cache=True)
127
+ def numba_linear_regression(x, y, n, lag):
128
+ co = np.zeros(2, dtype=np.float64)
129
+ sumx, sumx2, sumxy, sumy = 0, 0, 0, 0
130
+
131
+ for i in range(lag, n + lag):
132
+ sumx += x[i]
133
+ sumx2 += x[i] * x[i]
134
+ sumxy += x[i] * y[i]
135
+ sumy += y[i]
136
+
137
+ denom = n * sumx2 - sumx * sumx
138
+ if denom != 0:
139
+ co[0] = (n * sumxy - sumx * sumy) / denom
140
+ co[1] = (sumy * sumx2 - sumx * sumxy) / denom
141
+
142
+ return co
143
+
144
+
145
+ def create_directory(directory_path):
146
+ if os.path.exists(directory_path):
147
+ return None
148
+ else:
149
+ try:
150
+ os.makedirs(directory_path)
151
+ except:
152
+ # in case another machine created the path meanwhile !:(
153
+ return None
154
+ return directory_path
time_series_classification/minirocket/LICENSE ADDED
@@ -0,0 +1,692 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This project is licensed under the GNU General Public License v3.0 (GPL-3.0)
2
+
3
+ Original Authors:
4
+ - Angus Dempster
5
+ - Daniel F. Schmidt
6
+ - Geoffrey I. Webb
7
+ Original repository: https://github.com/angus924/minirocket
8
+ Paper: Dempster, A., Schmidt, D. F., & Webb, G. I. (2021).
9
+ "MiniRocket: A Very Fast (Almost) Deterministic Transform for Time Series Classification",
10
+ *Proceedings of the 27th ACM SIGKDD Conference on Knowledge Discovery and Data Mining*, pp. 248–257.
11
+
12
+ Modifications by:
13
+ - Jafar Bakhshaliyev, 2025
14
+
15
+ This project includes modifications to the original MiniRocket codebase.
16
+ All changes are released under the same GPL-3.0 license.
17
+ -----------------------------------------------------------------------
18
+
19
+ GNU GENERAL PUBLIC LICENSE
20
+ Version 3, 29 June 2007
21
+
22
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
23
+ Everyone is permitted to copy and distribute verbatim copies
24
+ of this license document, but changing it is not allowed.
25
+
26
+ Preamble
27
+
28
+ The GNU General Public License is a free, copyleft license for
29
+ software and other kinds of works.
30
+
31
+ The licenses for most software and other practical works are designed
32
+ to take away your freedom to share and change the works. By contrast,
33
+ the GNU General Public License is intended to guarantee your freedom to
34
+ share and change all versions of a program--to make sure it remains free
35
+ software for all its users. We, the Free Software Foundation, use the
36
+ GNU General Public License for most of our software; it applies also to
37
+ any other work released this way by its authors. You can apply it to
38
+ your programs, too.
39
+
40
+ When we speak of free software, we are referring to freedom, not
41
+ price. Our General Public Licenses are designed to make sure that you
42
+ have the freedom to distribute copies of free software (and charge for
43
+ them if you wish), that you receive source code or can get it if you
44
+ want it, that you can change the software or use pieces of it in new
45
+ free programs, and that you know you can do these things.
46
+
47
+ To protect your rights, we need to prevent others from denying you
48
+ these rights or asking you to surrender the rights. Therefore, you have
49
+ certain responsibilities if you distribute copies of the software, or if
50
+ you modify it: responsibilities to respect the freedom of others.
51
+
52
+ For example, if you distribute copies of such a program, whether
53
+ gratis or for a fee, you must pass on to the recipients the same
54
+ freedoms that you received. You must make sure that they, too, receive
55
+ or can get the source code. And you must show them these terms so they
56
+ know their rights.
57
+
58
+ Developers that use the GNU GPL protect your rights with two steps:
59
+ (1) assert copyright on the software, and (2) offer you this License
60
+ giving you legal permission to copy, distribute and/or modify it.
61
+
62
+ For the developers' and authors' protection, the GPL clearly explains
63
+ that there is no warranty for this free software. For both users' and
64
+ authors' sake, the GPL requires that modified versions be marked as
65
+ changed, so that their problems will not be attributed erroneously to
66
+ authors of previous versions.
67
+
68
+ Some devices are designed to deny users access to install or run
69
+ modified versions of the software inside them, although the manufacturer
70
+ can do so. This is fundamentally incompatible with the aim of
71
+ protecting users' freedom to change the software. The systematic
72
+ pattern of such abuse occurs in the area of products for individuals to
73
+ use, which is precisely where it is most unacceptable. Therefore, we
74
+ have designed this version of the GPL to prohibit the practice for those
75
+ products. If such problems arise substantially in other domains, we
76
+ stand ready to extend this provision to those domains in future versions
77
+ of the GPL, as needed to protect the freedom of users.
78
+
79
+ Finally, every program is threatened constantly by software patents.
80
+ States should not allow patents to restrict development and use of
81
+ software on general-purpose computers, but in those that do, we wish to
82
+ avoid the special danger that patents applied to a free program could
83
+ make it effectively proprietary. To prevent this, the GPL assures that
84
+ patents cannot be used to render the program non-free.
85
+
86
+ The precise terms and conditions for copying, distribution and
87
+ modification follow.
88
+
89
+ TERMS AND CONDITIONS
90
+
91
+ 0. Definitions.
92
+
93
+ "This License" refers to version 3 of the GNU General Public License.
94
+
95
+ "Copyright" also means copyright-like laws that apply to other kinds of
96
+ works, such as semiconductor masks.
97
+
98
+ "The Program" refers to any copyrightable work licensed under this
99
+ License. Each licensee is addressed as "you". "Licensees" and
100
+ "recipients" may be individuals or organizations.
101
+
102
+ To "modify" a work means to copy from or adapt all or part of the work
103
+ in a fashion requiring copyright permission, other than the making of an
104
+ exact copy. The resulting work is called a "modified version" of the
105
+ earlier work or a work "based on" the earlier work.
106
+
107
+ A "covered work" means either the unmodified Program or a work based
108
+ on the Program.
109
+
110
+ To "propagate" a work means to do anything with it that, without
111
+ permission, would make you directly or secondarily liable for
112
+ infringement under applicable copyright law, except executing it on a
113
+ computer or modifying a private copy. Propagation includes copying,
114
+ distribution (with or without modification), making available to the
115
+ public, and in some countries other activities as well.
116
+
117
+ To "convey" a work means any kind of propagation that enables other
118
+ parties to make or receive copies. Mere interaction with a user through
119
+ a computer network, with no transfer of a copy, is not conveying.
120
+
121
+ An interactive user interface displays "Appropriate Legal Notices"
122
+ to the extent that it includes a convenient and prominently visible
123
+ feature that (1) displays an appropriate copyright notice, and (2)
124
+ tells the user that there is no warranty for the work (except to the
125
+ extent that warranties are provided), that licensees may convey the
126
+ work under this License, and how to view a copy of this License. If
127
+ the interface presents a list of user commands or options, such as a
128
+ menu, a prominent item in the list meets this criterion.
129
+
130
+ 1. Source Code.
131
+
132
+ The "source code" for a work means the preferred form of the work
133
+ for making modifications to it. "Object code" means any non-source
134
+ form of a work.
135
+
136
+ A "Standard Interface" means an interface that either is an official
137
+ standard defined by a recognized standards body, or, in the case of
138
+ interfaces specified for a particular programming language, one that
139
+ is widely used among developers working in that language.
140
+
141
+ The "System Libraries" of an executable work include anything, other
142
+ than the work as a whole, that (a) is included in the normal form of
143
+ packaging a Major Component, but which is not part of that Major
144
+ Component, and (b) serves only to enable use of the work with that
145
+ Major Component, or to implement a Standard Interface for which an
146
+ implementation is available to the public in source code form. A
147
+ "Major Component", in this context, means a major essential component
148
+ (kernel, window system, and so on) of the specific operating system
149
+ (if any) on which the executable work runs, or a compiler used to
150
+ produce the work, or an object code interpreter used to run it.
151
+
152
+ The "Corresponding Source" for a work in object code form means all
153
+ the source code needed to generate, install, and (for an executable
154
+ work) run the object code and to modify the work, including scripts to
155
+ control those activities. However, it does not include the work's
156
+ System Libraries, or general-purpose tools or generally available free
157
+ programs which are used unmodified in performing those activities but
158
+ which are not part of the work. For example, Corresponding Source
159
+ includes interface definition files associated with source files for
160
+ the work, and the source code for shared libraries and dynamically
161
+ linked subprograms that the work is specifically designed to require,
162
+ such as by intimate data communication or control flow between those
163
+ subprograms and other parts of the work.
164
+
165
+ The Corresponding Source need not include anything that users
166
+ can regenerate automatically from other parts of the Corresponding
167
+ Source.
168
+
169
+ The Corresponding Source for a work in source code form is that
170
+ same work.
171
+
172
+ 2. Basic Permissions.
173
+
174
+ All rights granted under this License are granted for the term of
175
+ copyright on the Program, and are irrevocable provided the stated
176
+ conditions are met. This License explicitly affirms your unlimited
177
+ permission to run the unmodified Program. The output from running a
178
+ covered work is covered by this License only if the output, given its
179
+ content, constitutes a covered work. This License acknowledges your
180
+ rights of fair use or other equivalent, as provided by copyright law.
181
+
182
+ You may make, run and propagate covered works that you do not
183
+ convey, without conditions so long as your license otherwise remains
184
+ in force. You may convey covered works to others for the sole purpose
185
+ of having them make modifications exclusively for you, or provide you
186
+ with facilities for running those works, provided that you comply with
187
+ the terms of this License in conveying all material for which you do
188
+ not control copyright. Those thus making or running the covered works
189
+ for you must do so exclusively on your behalf, under your direction
190
+ and control, on terms that prohibit them from making any copies of
191
+ your copyrighted material outside their relationship with you.
192
+
193
+ Conveying under any other circumstances is permitted solely under
194
+ the conditions stated below. Sublicensing is not allowed; section 10
195
+ makes it unnecessary.
196
+
197
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
198
+
199
+ No covered work shall be deemed part of an effective technological
200
+ measure under any applicable law fulfilling obligations under article
201
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
202
+ similar laws prohibiting or restricting circumvention of such
203
+ measures.
204
+
205
+ When you convey a covered work, you waive any legal power to forbid
206
+ circumvention of technological measures to the extent such circumvention
207
+ is effected by exercising rights under this License with respect to
208
+ the covered work, and you disclaim any intention to limit operation or
209
+ modification of the work as a means of enforcing, against the work's
210
+ users, your or third parties' legal rights to forbid circumvention of
211
+ technological measures.
212
+
213
+ 4. Conveying Verbatim Copies.
214
+
215
+ You may convey verbatim copies of the Program's source code as you
216
+ receive it, in any medium, provided that you conspicuously and
217
+ appropriately publish on each copy an appropriate copyright notice;
218
+ keep intact all notices stating that this License and any
219
+ non-permissive terms added in accord with section 7 apply to the code;
220
+ keep intact all notices of the absence of any warranty; and give all
221
+ recipients a copy of this License along with the Program.
222
+
223
+ You may charge any price or no price for each copy that you convey,
224
+ and you may offer support or warranty protection for a fee.
225
+
226
+ 5. Conveying Modified Source Versions.
227
+
228
+ You may convey a work based on the Program, or the modifications to
229
+ produce it from the Program, in the form of source code under the
230
+ terms of section 4, provided that you also meet all of these conditions:
231
+
232
+ a) The work must carry prominent notices stating that you modified
233
+ it, and giving a relevant date.
234
+
235
+ b) The work must carry prominent notices stating that it is
236
+ released under this License and any conditions added under section
237
+ 7. This requirement modifies the requirement in section 4 to
238
+ "keep intact all notices".
239
+
240
+ c) You must license the entire work, as a whole, under this
241
+ License to anyone who comes into possession of a copy. This
242
+ License will therefore apply, along with any applicable section 7
243
+ additional terms, to the whole of the work, and all its parts,
244
+ regardless of how they are packaged. This License gives no
245
+ permission to license the work in any other way, but it does not
246
+ invalidate such permission if you have separately received it.
247
+
248
+ d) If the work has interactive user interfaces, each must display
249
+ Appropriate Legal Notices; however, if the Program has interactive
250
+ interfaces that do not display Appropriate Legal Notices, your
251
+ work need not make them do so.
252
+
253
+ A compilation of a covered work with other separate and independent
254
+ works, which are not by their nature extensions of the covered work,
255
+ and which are not combined with it such as to form a larger program,
256
+ in or on a volume of a storage or distribution medium, is called an
257
+ "aggregate" if the compilation and its resulting copyright are not
258
+ used to limit the access or legal rights of the compilation's users
259
+ beyond what the individual works permit. Inclusion of a covered work
260
+ in an aggregate does not cause this License to apply to the other
261
+ parts of the aggregate.
262
+
263
+ 6. Conveying Non-Source Forms.
264
+
265
+ You may convey a covered work in object code form under the terms
266
+ of sections 4 and 5, provided that you also convey the
267
+ machine-readable Corresponding Source under the terms of this License,
268
+ in one of these ways:
269
+
270
+ a) Convey the object code in, or embodied in, a physical product
271
+ (including a physical distribution medium), accompanied by the
272
+ Corresponding Source fixed on a durable physical medium
273
+ customarily used for software interchange.
274
+
275
+ b) Convey the object code in, or embodied in, a physical product
276
+ (including a physical distribution medium), accompanied by a
277
+ written offer, valid for at least three years and valid for as
278
+ long as you offer spare parts or customer support for that product
279
+ model, to give anyone who possesses the object code either (1) a
280
+ copy of the Corresponding Source for all the software in the
281
+ product that is covered by this License, on a durable physical
282
+ medium customarily used for software interchange, for a price no
283
+ more than your reasonable cost of physically performing this
284
+ conveying of source, or (2) access to copy the
285
+ Corresponding Source from a network server at no charge.
286
+
287
+ c) Convey individual copies of the object code with a copy of the
288
+ written offer to provide the Corresponding Source. This
289
+ alternative is allowed only occasionally and noncommercially, and
290
+ only if you received the object code with such an offer, in accord
291
+ with subsection 6b.
292
+
293
+ d) Convey the object code by offering access from a designated
294
+ place (gratis or for a charge), and offer equivalent access to the
295
+ Corresponding Source in the same way through the same place at no
296
+ further charge. You need not require recipients to copy the
297
+ Corresponding Source along with the object code. If the place to
298
+ copy the object code is a network server, the Corresponding Source
299
+ may be on a different server (operated by you or a third party)
300
+ that supports equivalent copying facilities, provided you maintain
301
+ clear directions next to the object code saying where to find the
302
+ Corresponding Source. Regardless of what server hosts the
303
+ Corresponding Source, you remain obligated to ensure that it is
304
+ available for as long as needed to satisfy these requirements.
305
+
306
+ e) Convey the object code using peer-to-peer transmission, provided
307
+ you inform other peers where the object code and Corresponding
308
+ Source of the work are being offered to the general public at no
309
+ charge under subsection 6d.
310
+
311
+ A separable portion of the object code, whose source code is excluded
312
+ from the Corresponding Source as a System Library, need not be
313
+ included in conveying the object code work.
314
+
315
+ A "User Product" is either (1) a "consumer product", which means any
316
+ tangible personal property which is normally used for personal, family,
317
+ or household purposes, or (2) anything designed or sold for incorporation
318
+ into a dwelling. In determining whether a product is a consumer product,
319
+ doubtful cases shall be resolved in favor of coverage. For a particular
320
+ product received by a particular user, "normally used" refers to a
321
+ typical or common use of that class of product, regardless of the status
322
+ of the particular user or of the way in which the particular user
323
+ actually uses, or expects or is expected to use, the product. A product
324
+ is a consumer product regardless of whether the product has substantial
325
+ commercial, industrial or non-consumer uses, unless such uses represent
326
+ the only significant mode of use of the product.
327
+
328
+ "Installation Information" for a User Product means any methods,
329
+ procedures, authorization keys, or other information required to install
330
+ and execute modified versions of a covered work in that User Product from
331
+ a modified version of its Corresponding Source. The information must
332
+ suffice to ensure that the continued functioning of the modified object
333
+ code is in no case prevented or interfered with solely because
334
+ modification has been made.
335
+
336
+ If you convey an object code work under this section in, or with, or
337
+ specifically for use in, a User Product, and the conveying occurs as
338
+ part of a transaction in which the right of possession and use of the
339
+ User Product is transferred to the recipient in perpetuity or for a
340
+ fixed term (regardless of how the transaction is characterized), the
341
+ Corresponding Source conveyed under this section must be accompanied
342
+ by the Installation Information. But this requirement does not apply
343
+ if neither you nor any third party retains the ability to install
344
+ modified object code on the User Product (for example, the work has
345
+ been installed in ROM).
346
+
347
+ The requirement to provide Installation Information does not include a
348
+ requirement to continue to provide support service, warranty, or updates
349
+ for a work that has been modified or installed by the recipient, or for
350
+ the User Product in which it has been modified or installed. Access to a
351
+ network may be denied when the modification itself materially and
352
+ adversely affects the operation of the network or violates the rules and
353
+ protocols for communication across the network.
354
+
355
+ Corresponding Source conveyed, and Installation Information provided,
356
+ in accord with this section must be in a format that is publicly
357
+ documented (and with an implementation available to the public in
358
+ source code form), and must require no special password or key for
359
+ unpacking, reading or copying.
360
+
361
+ 7. Additional Terms.
362
+
363
+ "Additional permissions" are terms that supplement the terms of this
364
+ License by making exceptions from one or more of its conditions.
365
+ Additional permissions that are applicable to the entire Program shall
366
+ be treated as though they were included in this License, to the extent
367
+ that they are valid under applicable law. If additional permissions
368
+ apply only to part of the Program, that part may be used separately
369
+ under those permissions, but the entire Program remains governed by
370
+ this License without regard to the additional permissions.
371
+
372
+ When you convey a copy of a covered work, you may at your option
373
+ remove any additional permissions from that copy, or from any part of
374
+ it. (Additional permissions may be written to require their own
375
+ removal in certain cases when you modify the work.) You may place
376
+ additional permissions on material, added by you to a covered work,
377
+ for which you have or can give appropriate copyright permission.
378
+
379
+ Notwithstanding any other provision of this License, for material you
380
+ add to a covered work, you may (if authorized by the copyright holders of
381
+ that material) supplement the terms of this License with terms:
382
+
383
+ a) Disclaiming warranty or limiting liability differently from the
384
+ terms of sections 15 and 16 of this License; or
385
+
386
+ b) Requiring preservation of specified reasonable legal notices or
387
+ author attributions in that material or in the Appropriate Legal
388
+ Notices displayed by works containing it; or
389
+
390
+ c) Prohibiting misrepresentation of the origin of that material, or
391
+ requiring that modified versions of such material be marked in
392
+ reasonable ways as different from the original version; or
393
+
394
+ d) Limiting the use for publicity purposes of names of licensors or
395
+ authors of the material; or
396
+
397
+ e) Declining to grant rights under trademark law for use of some
398
+ trade names, trademarks, or service marks; or
399
+
400
+ f) Requiring indemnification of licensors and authors of that
401
+ material by anyone who conveys the material (or modified versions of
402
+ it) with contractual assumptions of liability to the recipient, for
403
+ any liability that these contractual assumptions directly impose on
404
+ those licensors and authors.
405
+
406
+ All other non-permissive additional terms are considered "further
407
+ restrictions" within the meaning of section 10. If the Program as you
408
+ received it, or any part of it, contains a notice stating that it is
409
+ governed by this License along with a term that is a further
410
+ restriction, you may remove that term. If a license document contains
411
+ a further restriction but permits relicensing or conveying under this
412
+ License, you may add to a covered work material governed by the terms
413
+ of that license document, provided that the further restriction does
414
+ not survive such relicensing or conveying.
415
+
416
+ If you add terms to a covered work in accord with this section, you
417
+ must place, in the relevant source files, a statement of the
418
+ additional terms that apply to those files, or a notice indicating
419
+ where to find the applicable terms.
420
+
421
+ Additional terms, permissive or non-permissive, may be stated in the
422
+ form of a separately written license, or stated as exceptions;
423
+ the above requirements apply either way.
424
+
425
+ 8. Termination.
426
+
427
+ You may not propagate or modify a covered work except as expressly
428
+ provided under this License. Any attempt otherwise to propagate or
429
+ modify it is void, and will automatically terminate your rights under
430
+ this License (including any patent licenses granted under the third
431
+ paragraph of section 11).
432
+
433
+ However, if you cease all violation of this License, then your
434
+ license from a particular copyright holder is reinstated (a)
435
+ provisionally, unless and until the copyright holder explicitly and
436
+ finally terminates your license, and (b) permanently, if the copyright
437
+ holder fails to notify you of the violation by some reasonable means
438
+ prior to 60 days after the cessation.
439
+
440
+ Moreover, your license from a particular copyright holder is
441
+ reinstated permanently if the copyright holder notifies you of the
442
+ violation by some reasonable means, this is the first time you have
443
+ received notice of violation of this License (for any work) from that
444
+ copyright holder, and you cure the violation prior to 30 days after
445
+ your receipt of the notice.
446
+
447
+ Termination of your rights under this section does not terminate the
448
+ licenses of parties who have received copies or rights from you under
449
+ this License. If your rights have been terminated and not permanently
450
+ reinstated, you do not qualify to receive new licenses for the same
451
+ material under section 10.
452
+
453
+ 9. Acceptance Not Required for Having Copies.
454
+
455
+ You are not required to accept this License in order to receive or
456
+ run a copy of the Program. Ancillary propagation of a covered work
457
+ occurring solely as a consequence of using peer-to-peer transmission
458
+ to receive a copy likewise does not require acceptance. However,
459
+ nothing other than this License grants you permission to propagate or
460
+ modify any covered work. These actions infringe copyright if you do
461
+ not accept this License. Therefore, by modifying or propagating a
462
+ covered work, you indicate your acceptance of this License to do so.
463
+
464
+ 10. Automatic Licensing of Downstream Recipients.
465
+
466
+ Each time you convey a covered work, the recipient automatically
467
+ receives a license from the original licensors, to run, modify and
468
+ propagate that work, subject to this License. You are not responsible
469
+ for enforcing compliance by third parties with this License.
470
+
471
+ An "entity transaction" is a transaction transferring control of an
472
+ organization, or substantially all assets of one, or subdividing an
473
+ organization, or merging organizations. If propagation of a covered
474
+ work results from an entity transaction, each party to that
475
+ transaction who receives a copy of the work also receives whatever
476
+ licenses to the work the party's predecessor in interest had or could
477
+ give under the previous paragraph, plus a right to possession of the
478
+ Corresponding Source of the work from the predecessor in interest, if
479
+ the predecessor has it or can get it with reasonable efforts.
480
+
481
+ You may not impose any further restrictions on the exercise of the
482
+ rights granted or affirmed under this License. For example, you may
483
+ not impose a license fee, royalty, or other charge for exercise of
484
+ rights granted under this License, and you may not initiate litigation
485
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
486
+ any patent claim is infringed by making, using, selling, offering for
487
+ sale, or importing the Program or any portion of it.
488
+
489
+ 11. Patents.
490
+
491
+ A "contributor" is a copyright holder who authorizes use under this
492
+ License of the Program or a work on which the Program is based. The
493
+ work thus licensed is called the contributor's "contributor version".
494
+
495
+ A contributor's "essential patent claims" are all patent claims
496
+ owned or controlled by the contributor, whether already acquired or
497
+ hereafter acquired, that would be infringed by some manner, permitted
498
+ by this License, of making, using, or selling its contributor version,
499
+ but do not include claims that would be infringed only as a
500
+ consequence of further modification of the contributor version. For
501
+ purposes of this definition, "control" includes the right to grant
502
+ patent sublicenses in a manner consistent with the requirements of
503
+ this License.
504
+
505
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
506
+ patent license under the contributor's essential patent claims, to
507
+ make, use, sell, offer for sale, import and otherwise run, modify and
508
+ propagate the contents of its contributor version.
509
+
510
+ In the following three paragraphs, a "patent license" is any express
511
+ agreement or commitment, however denominated, not to enforce a patent
512
+ (such as an express permission to practice a patent or covenant not to
513
+ sue for patent infringement). To "grant" such a patent license to a
514
+ party means to make such an agreement or commitment not to enforce a
515
+ patent against the party.
516
+
517
+ If you convey a covered work, knowingly relying on a patent license,
518
+ and the Corresponding Source of the work is not available for anyone
519
+ to copy, free of charge and under the terms of this License, through a
520
+ publicly available network server or other readily accessible means,
521
+ then you must either (1) cause the Corresponding Source to be so
522
+ available, or (2) arrange to deprive yourself of the benefit of the
523
+ patent license for this particular work, or (3) arrange, in a manner
524
+ consistent with the requirements of this License, to extend the patent
525
+ license to downstream recipients. "Knowingly relying" means you have
526
+ actual knowledge that, but for the patent license, your conveying the
527
+ covered work in a country, or your recipient's use of the covered work
528
+ in a country, would infringe one or more identifiable patents in that
529
+ country that you have reason to believe are valid.
530
+
531
+ If, pursuant to or in connection with a single transaction or
532
+ arrangement, you convey, or propagate by procuring conveyance of, a
533
+ covered work, and grant a patent license to some of the parties
534
+ receiving the covered work authorizing them to use, propagate, modify
535
+ or convey a specific copy of the covered work, then the patent license
536
+ you grant is automatically extended to all recipients of the covered
537
+ work and works based on it.
538
+
539
+ A patent license is "discriminatory" if it does not include within
540
+ the scope of its coverage, prohibits the exercise of, or is
541
+ conditioned on the non-exercise of one or more of the rights that are
542
+ specifically granted under this License. You may not convey a covered
543
+ work if you are a party to an arrangement with a third party that is
544
+ in the business of distributing software, under which you make payment
545
+ to the third party based on the extent of your activity of conveying
546
+ the work, and under which the third party grants, to any of the
547
+ parties who would receive the covered work from you, a discriminatory
548
+ patent license (a) in connection with copies of the covered work
549
+ conveyed by you (or copies made from those copies), or (b) primarily
550
+ for and in connection with specific products or compilations that
551
+ contain the covered work, unless you entered into that arrangement,
552
+ or that patent license was granted, prior to 28 March 2007.
553
+
554
+ Nothing in this License shall be construed as excluding or limiting
555
+ any implied license or other defenses to infringement that may
556
+ otherwise be available to you under applicable patent law.
557
+
558
+ 12. No Surrender of Others' Freedom.
559
+
560
+ If conditions are imposed on you (whether by court order, agreement or
561
+ otherwise) that contradict the conditions of this License, they do not
562
+ excuse you from the conditions of this License. If you cannot convey a
563
+ covered work so as to satisfy simultaneously your obligations under this
564
+ License and any other pertinent obligations, then as a consequence you may
565
+ not convey it at all. For example, if you agree to terms that obligate you
566
+ to collect a royalty for further conveying from those to whom you convey
567
+ the Program, the only way you could satisfy both those terms and this
568
+ License would be to refrain entirely from conveying the Program.
569
+
570
+ 13. Use with the GNU Affero General Public License.
571
+
572
+ Notwithstanding any other provision of this License, you have
573
+ permission to link or combine any covered work with a work licensed
574
+ under version 3 of the GNU Affero General Public License into a single
575
+ combined work, and to convey the resulting work. The terms of this
576
+ License will continue to apply to the part which is the covered work,
577
+ but the special requirements of the GNU Affero General Public License,
578
+ section 13, concerning interaction through a network will apply to the
579
+ combination as such.
580
+
581
+ 14. Revised Versions of this License.
582
+
583
+ The Free Software Foundation may publish revised and/or new versions of
584
+ the GNU General Public License from time to time. Such new versions will
585
+ be similar in spirit to the present version, but may differ in detail to
586
+ address new problems or concerns.
587
+
588
+ Each version is given a distinguishing version number. If the
589
+ Program specifies that a certain numbered version of the GNU General
590
+ Public License "or any later version" applies to it, you have the
591
+ option of following the terms and conditions either of that numbered
592
+ version or of any later version published by the Free Software
593
+ Foundation. If the Program does not specify a version number of the
594
+ GNU General Public License, you may choose any version ever published
595
+ by the Free Software Foundation.
596
+
597
+ If the Program specifies that a proxy can decide which future
598
+ versions of the GNU General Public License can be used, that proxy's
599
+ public statement of acceptance of a version permanently authorizes you
600
+ to choose that version for the Program.
601
+
602
+ Later license versions may give you additional or different
603
+ permissions. However, no additional obligations are imposed on any
604
+ author or copyright holder as a result of your choosing to follow a
605
+ later version.
606
+
607
+ 15. Disclaimer of Warranty.
608
+
609
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
610
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
611
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
612
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
613
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
614
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
615
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
616
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
617
+
618
+ 16. Limitation of Liability.
619
+
620
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
621
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
622
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
623
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
624
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
625
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
626
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
627
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
628
+ SUCH DAMAGES.
629
+
630
+ 17. Interpretation of Sections 15 and 16.
631
+
632
+ If the disclaimer of warranty and limitation of liability provided
633
+ above cannot be given local legal effect according to their terms,
634
+ reviewing courts shall apply local law that most closely approximates
635
+ an absolute waiver of all civil liability in connection with the
636
+ Program, unless a warranty or assumption of liability accompanies a
637
+ copy of the Program in return for a fee.
638
+
639
+ END OF TERMS AND CONDITIONS
640
+
641
+ How to Apply These Terms to Your New Programs
642
+
643
+ If you develop a new program, and you want it to be of the greatest
644
+ possible use to the public, the best way to achieve this is to make it
645
+ free software which everyone can redistribute and change under these terms.
646
+
647
+ To do so, attach the following notices to the program. It is safest
648
+ to attach them to the start of each source file to most effectively
649
+ state the exclusion of warranty; and each file should have at least
650
+ the "copyright" line and a pointer to where the full notice is found.
651
+
652
+ <one line to give the program's name and a brief idea of what it does.>
653
+ Copyright (C) <year> <name of author>
654
+
655
+ This program is free software: you can redistribute it and/or modify
656
+ it under the terms of the GNU General Public License as published by
657
+ the Free Software Foundation, either version 3 of the License, or
658
+ (at your option) any later version.
659
+
660
+ This program is distributed in the hope that it will be useful,
661
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
662
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
663
+ GNU General Public License for more details.
664
+
665
+ You should have received a copy of the GNU General Public License
666
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
667
+
668
+ Also add information on how to contact you by electronic and paper mail.
669
+
670
+ If the program does terminal interaction, make it output a short
671
+ notice like this when it starts in an interactive mode:
672
+
673
+ <program> Copyright (C) <year> <name of author>
674
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
675
+ This is free software, and you are welcome to redistribute it
676
+ under certain conditions; type `show c' for details.
677
+
678
+ The hypothetical commands `show w' and `show c' should show the appropriate
679
+ parts of the General Public License. Of course, your program's commands
680
+ might be different; for a GUI interface, you would use an "about box".
681
+
682
+ You should also get your employer (if you work as a programmer) or school,
683
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
684
+ For more information on this, and how to apply and follow the GNU GPL, see
685
+ <https://www.gnu.org/licenses/>.
686
+
687
+ The GNU General Public License does not permit incorporating your program
688
+ into proprietary programs. If your program is a subroutine library, you
689
+ may consider it more useful to permit linking proprietary applications with
690
+ the library. If this is what you want to do, use the GNU Lesser General
691
+ Public License instead of this License. But first, please read
692
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
time_series_classification/minirocket/THIRD_PARTY_LICENSES.txt ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ THIRD PARTY LICENSES
2
+
3
+ This file contains the licenses for third-party components used in this project.
4
+
5
+ ================================================================================
6
+
7
+ Time Series Augmentation Components
8
+
9
+ Some augmentation methods in this repository are derived from or inspired by the
10
+ time_series_augmentation repository:
11
+
12
+ Source: https://github.com/uchidalab/time_series_augmentation
13
+ Authors: Brian Kenji Iwana and Seiichi Uchida
14
+ License: Apache License 2.0
15
+ Files affected: ./time_series_classification/minirocket/src/augmentation.py
16
+
17
+ ================================================================================
18
+
19
+
20
+
21
+ Apache License
22
+ Version 2.0, January 2004
23
+ http://www.apache.org/licenses/
24
+
25
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
26
+
27
+ 1. Definitions.
28
+
29
+ "License" shall mean the terms and conditions for use, reproduction,
30
+ and distribution as defined by Sections 1 through 9 of this document.
31
+
32
+ "Licensor" shall mean the copyright owner or entity authorized by
33
+ the copyright owner that is granting the License.
34
+
35
+ "Legal Entity" shall mean the union of the acting entity and all
36
+ other entities that control, are controlled by, or are under common
37
+ control with that entity. For the purposes of this definition,
38
+ "control" means (i) the power, direct or indirect, to cause the
39
+ direction or management of such entity, whether by contract or
40
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
41
+ outstanding shares, or (iii) beneficial ownership of such entity.
42
+
43
+ "You" (or "Your") shall mean an individual or Legal Entity
44
+ exercising permissions granted by this License.
45
+
46
+ "Source" form shall mean the preferred form for making modifications,
47
+ including but not limited to software source code, documentation
48
+ source, and configuration files.
49
+
50
+ "Object" form shall mean any form resulting from mechanical
51
+ transformation or translation of a Source form, including but
52
+ not limited to compiled object code, generated documentation,
53
+ and conversions to other media types.
54
+
55
+ "Work" shall mean the work of authorship, whether in Source or
56
+ Object form, made available under the License, as indicated by a
57
+ copyright notice that is included in or attached to the work
58
+ (an example is provided in the Appendix below).
59
+
60
+ "Derivative Works" shall mean any work, whether in Source or Object
61
+ form, that is based on (or derived from) the Work and for which the
62
+ editorial revisions, annotations, elaborations, or other modifications
63
+ represent, as a whole, an original work of authorship. For the purposes
64
+ of this License, Derivative Works shall not include works that remain
65
+ separable from, or merely link (or bind by name) to the interfaces of,
66
+ the Work and Derivative Works thereof.
67
+
68
+ "Contribution" shall mean any work of authorship, including
69
+ the original version of the Work and any modifications or additions
70
+ to that Work or Derivative Works thereof, that is intentionally
71
+ submitted to Licensor for inclusion in the Work by the copyright owner
72
+ or by an individual or Legal Entity authorized to submit on behalf of
73
+ the copyright owner. For the purposes of this definition, "submitted"
74
+ means any form of electronic, verbal, or written communication sent
75
+ to the Licensor or its representatives, including but not limited to
76
+ communication on electronic mailing lists, source code control systems,
77
+ and issue tracking systems that are managed by, or on behalf of, the
78
+ Licensor for the purpose of discussing and improving the Work, but
79
+ excluding communication that is conspicuously marked or otherwise
80
+ designated in writing by the copyright owner as "Not a Contribution."
81
+
82
+ "Contributor" shall mean Licensor and any individual or Legal Entity
83
+ on behalf of whom a Contribution has been received by Licensor and
84
+ subsequently incorporated within the Work.
85
+
86
+ 2. Grant of Copyright License. Subject to the terms and conditions of
87
+ this License, each Contributor hereby grants to You a perpetual,
88
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
89
+ copyright license to reproduce, prepare Derivative Works of,
90
+ publicly display, publicly perform, sublicense, and distribute the
91
+ Work and such Derivative Works in Source or Object form.
92
+
93
+ 3. Grant of Patent License. Subject to the terms and conditions of
94
+ this License, each Contributor hereby grants to You a perpetual,
95
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
96
+ (except as stated in this section) patent license to make, have made,
97
+ use, offer to sell, sell, import, and otherwise transfer the Work,
98
+ where such license applies only to those patent claims licensable
99
+ by such Contributor that are necessarily infringed by their
100
+ Contribution(s) alone or by combination of their Contribution(s)
101
+ with the Work to which such Contribution(s) was submitted. If You
102
+ institute patent litigation against any entity (including a
103
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
104
+ or a Contribution incorporated within the Work constitutes direct
105
+ or contributory patent infringement, then any patent licenses
106
+ granted to You under this License for that Work shall terminate
107
+ as of the date such litigation is filed.
108
+
109
+ 4. Redistribution. You may reproduce and distribute copies of the
110
+ Work or Derivative Works thereof in any medium, with or without
111
+ modifications, and in Source or Object form, provided that You
112
+ meet the following conditions:
113
+
114
+ (a) You must give any other recipients of the Work or
115
+ Derivative Works a copy of this License; and
116
+
117
+ (b) You must cause any modified files to carry prominent notices
118
+ stating that You changed the files; and
119
+
120
+ (c) You must retain, in the Source form of any Derivative Works
121
+ that You distribute, all copyright, patent, trademark, and
122
+ attribution notices from the Source form of the Work,
123
+ excluding those notices that do not pertain to any part of
124
+ the Derivative Works; and
125
+
126
+ (d) If the Work includes a "NOTICE" text file as part of its
127
+ distribution, then any Derivative Works that You distribute must
128
+ include a readable copy of the attribution notices contained
129
+ within such NOTICE file, excluding those notices that do not
130
+ pertain to any part of the Derivative Works, in at least one
131
+ of the following places: within a NOTICE text file distributed
132
+ as part of the Derivative Works; within the Source form or
133
+ documentation, if provided along with the Derivative Works; or,
134
+ within a display generated by the Derivative Works, if and
135
+ wherever such third-party notices normally appear. The contents
136
+ of the NOTICE file are for informational purposes only and
137
+ do not modify the License. You may add Your own attribution
138
+ notices within Derivative Works that You distribute, alongside
139
+ or as an addendum to the NOTICE text from the Work, provided
140
+ that such additional attribution notices cannot be construed
141
+ as modifying the License.
142
+
143
+ You may add Your own copyright statement to Your modifications and
144
+ may provide additional or different license terms and conditions
145
+ for use, reproduction, or distribution of Your modifications, or
146
+ for any such Derivative Works as a whole, provided Your use,
147
+ reproduction, and distribution of the Work otherwise complies with
148
+ the conditions stated in this License.
149
+
150
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
151
+ any Contribution intentionally submitted for inclusion in the Work
152
+ by You to the Licensor shall be under the terms and conditions of
153
+ this License, without any additional terms or conditions.
154
+ Notwithstanding the above, nothing herein shall supersede or modify
155
+ the terms of any separate license agreement you may have executed
156
+ with Licensor regarding such Contributions.
157
+
158
+ 6. Trademarks. This License does not grant permission to use the trade
159
+ names, trademarks, service marks, or product names of the Licensor,
160
+ except as required for reasonable and customary use in describing the
161
+ origin of the Work and reproducing the content of the NOTICE file.
162
+
163
+ 7. Disclaimer of Warranty. Unless required by applicable law or
164
+ agreed to in writing, Licensor provides the Work (and each
165
+ Contributor provides its Contributions) on an "AS IS" BASIS,
166
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
167
+ implied, including, without limitation, any warranties or conditions
168
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
169
+ PARTICULAR PURPOSE. You are solely responsible for determining the
170
+ appropriateness of using or redistributing the Work and assume any
171
+ risks associated with Your exercise of permissions under this License.
172
+
173
+ 8. Limitation of Liability. In no event and under no legal theory,
174
+ whether in tort (including negligence), contract, or otherwise,
175
+ unless required by applicable law (such as deliberate and grossly
176
+ negligent acts) or agreed to in writing, shall any Contributor be
177
+ liable to You for damages, including any direct, indirect, special,
178
+ incidental, or consequential damages of any character arising as a
179
+ result of this License or out of the use or inability to use the
180
+ Work (including but not limited to damages for loss of goodwill,
181
+ work stoppage, computer failure or malfunction, or any and all
182
+ other commercial damages or losses), even if such Contributor
183
+ has been advised of the possibility of such damages.
184
+
185
+ 9. Accepting Warranty or Additional Liability. While redistributing
186
+ the Work or Derivative Works thereof, You may choose to offer,
187
+ and charge a fee for, acceptance of support, warranty, indemnity,
188
+ or other liability obligations and/or rights consistent with this
189
+ License. However, in accepting such obligations, You may act only
190
+ on Your own behalf and on Your sole responsibility, not on behalf
191
+ of any other Contributor, and only if You agree to indemnify,
192
+ defend, and hold each Contributor harmless for any liability
193
+ incurred by, or claims asserted against, such Contributor by reason
194
+ of your accepting any such warranty or additional liability.
195
+
196
+ END OF TERMS AND CONDITIONS
197
+
198
+ APPENDIX: How to apply the Apache License to your work.
199
+
200
+ To apply the Apache License to your work, attach the following
201
+ boilerplate notice, with the fields enclosed by brackets "[]"
202
+ replaced with your own identifying information. (Don't include
203
+ the brackets!) The text should be enclosed in the appropriate
204
+ comment syntax for the file format. We also recommend that a
205
+ file or class name and description of purpose be included on the
206
+ same "printed page" as the copyright notice for easier
207
+ identification within third-party archives.
208
+
209
+ Copyright [yyyy] [name of copyright owner]
210
+
211
+ Licensed under the Apache License, Version 2.0 (the "License");
212
+ you may not use this file except in compliance with the License.
213
+ You may obtain a copy of the License at
214
+
215
+ http://www.apache.org/licenses/LICENSE-2.0
216
+
217
+ Unless required by applicable law or agreed to in writing, software
218
+ distributed under the License is distributed on an "AS IS" BASIS,
219
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
220
+ See the License for the specific language governing permissions and
221
+ limitations under the License.
time_series_classification/minirocket/scripts/example.sh ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ #SBATCH --job-name=univariate_tsc
3
+ #SBATCH --output=tsc_univariate.out
4
+ #SBATCH --error=tsc_univariate.err
5
+ #SBATCH --partition=CPU
6
+ #SBATCH --nodes=1
7
+ #SBATCH --ntasks=1
8
+ #SBATCH --cpus-per-task=40
9
+ #SBATCH --chdir= # Change to your working directory
10
+
11
+
12
+
13
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup
14
+
15
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --jitter
16
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --scaling
17
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --rotation
18
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --permutation
19
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --randompermutation
20
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --magwarp
21
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --timewarp
22
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --windowslice
23
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --windowwarp
24
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --spawner
25
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --dtwwarp
26
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --shapedtwwarp
27
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --wdba
28
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --discdtw
29
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --discsdtw
30
+
31
+
32
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 420 --stride 1 --shuffle_rate 0.8
33
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 120 --stride 1 --shuffle_rate 0.7
34
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 4 --stride 4 --shuffle_rate 0.6
35
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 64 --stride 2 --shuffle_rate 0.6
36
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 32 --stride 1 --shuffle_rate 0.6
37
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 8 --stride 4 --shuffle_rate 1.0
38
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 6 --stride 4 --shuffle_rate 1.0
39
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 120 --stride 1 --shuffle_rate 1.0
40
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 96 --stride 8 --shuffle_rate 0.8
41
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 48 --stride 36 --shuffle_rate 0.9
42
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 24 --stride 36 --shuffle_rate 0.8
43
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 240 --stride 1 --shuffle_rate 0.8
44
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 240 --stride 1 --shuffle_rate 0.4
45
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 240 --stride 1 --shuffle_rate 0.2
46
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 96 --stride 96 --shuffle_rate 1.0
47
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 96 --stride 96 --shuffle_rate 0.6
48
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 120 --stride 48 --shuffle_rate 0.8
49
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 240 --stride 96 --shuffle_rate 1.0
50
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 12 --stride 96 --shuffle_rate 0.8
51
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 2 --stride 1 --shuffle_rate 0.4
52
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 480 --stride 1 --shuffle_rate 0.7
53
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 900 --stride 1 --shuffle_rate 0.8
54
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 64 --stride 24 --shuffle_rate 0.8
55
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 64 --stride 24 --shuffle_rate 1.0
56
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 148 --stride 24 --shuffle_rate 1.0
57
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 148 --stride 96 --shuffle_rate 1.0
58
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 148 --stride 48 --shuffle_rate 1.0
59
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 180 --stride 12 --shuffle_rate 0.8
60
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 180 --stride 24 --shuffle_rate 0.8
61
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 180 --stride 12 --shuffle_rate 0.5
62
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 152 --stride 96 --shuffle_rate 1.0
63
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 360 --stride 1 --shuffle_rate 0.1
64
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 72 --stride 2 --shuffle_rate 0.4
65
+ srun python3 ./src/main.py --dataset MiddlePhalanxOutlineAgeGroup --use-augmentation --augmentation-ratio 1 --tps --patch_len 120 --stride 1 --shuffle_rate 0.2
time_series_classification/minirocket/src/augmentation.py ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from time_series_augmentation (https://github.com/uchidalab/time_series_augmentation)
2
+ # Original: Apache License 2.0 by Brian Kenji Iwana and Seiichi Uchida
3
+ # Modified by Jafar Bakhshaliyev (2025) - Licensed under GPL v3.0
4
+
5
+
6
+ import numpy as np
7
+ from tqdm import tqdm
8
+
9
+
10
+
11
+
12
+ def tps(x, y, patch_len=0, stride=0, shuffle_rate=0.0):
13
+ """
14
+ Temporal Patch Shuffle (TPS) augmentation for time series classification.
15
+
16
+ Parameters:
17
+ -----------
18
+ x : numpy.ndarray
19
+ Input time series data of shape (n_samples, timesteps, n_features)
20
+ patch_len : int
21
+ Length of each patch
22
+ stride : int
23
+ Stride between patches
24
+ shuffle_rate : float
25
+ Proportion of patches to shuffle (between 0 and 1)
26
+
27
+ Returns:
28
+ --------
29
+ numpy.ndarray
30
+ Augmented time series data with same shape as input
31
+ """
32
+ n_samples, T, n_features = x.shape
33
+
34
+
35
+ ret = np.zeros_like(x)
36
+
37
+
38
+ total_patches = (T - patch_len + stride - 1) // stride + 1
39
+ total_len = (total_patches - 1) * stride + patch_len
40
+ padding_needed = total_len - T
41
+
42
+ for i in range(n_samples):
43
+
44
+ sample = x[i] # shape: (timesteps, n_features)
45
+
46
+ if padding_needed > 0:
47
+ padded_sample = np.pad(sample, ((0, padding_needed), (0, 0)), mode='edge')
48
+ T_padded = T + padding_needed
49
+ else:
50
+ padded_sample = sample
51
+ T_padded = T
52
+
53
+ num_patches = ((T_padded - patch_len) // stride) + 1
54
+
55
+ patches = np.zeros((num_patches, patch_len, n_features))
56
+ for j in range(num_patches):
57
+ start = j * stride
58
+ patches[j] = padded_sample[start:start + patch_len]
59
+
60
+
61
+ variance_scores = np.var(patches, axis=(1, 2))
62
+
63
+
64
+ num_to_shuffle = int(num_patches * shuffle_rate)
65
+
66
+ if num_to_shuffle > 0:
67
+
68
+ shuffle_indices = np.argsort(variance_scores)[:num_to_shuffle]
69
+
70
+
71
+ patches_to_shuffle = patches[shuffle_indices].copy()
72
+ shuffled_order = np.random.permutation(num_to_shuffle)
73
+
74
+ for idx, new_idx in enumerate(shuffled_order):
75
+ patch_idx = shuffle_indices[idx]
76
+ new_patch = patches_to_shuffle[new_idx]
77
+ patches[patch_idx] = new_patch
78
+
79
+ # Reconstruct the time series
80
+ reconstructed = np.zeros((T_padded, n_features))
81
+ counts = np.zeros((T_padded, n_features))
82
+
83
+ for j in range(num_patches):
84
+ start = j * stride
85
+ end = start + patch_len
86
+ reconstructed[start:end] += patches[j]
87
+ counts[start:end] += 1
88
+
89
+ counts[counts == 0] = 1
90
+ reconstructed = reconstructed / counts
91
+
92
+
93
+ ret[i] = reconstructed[:T]
94
+
95
+ return ret
96
+
97
+
98
+ def jitter(x, sigma=0.03):
99
+ # https://arxiv.org/pdf/1706.00527.pdf
100
+ return x + np.random.normal(loc=0., scale=sigma, size=x.shape)
101
+
102
+ def scaling(x, sigma=0.1):
103
+ # https://arxiv.org/pdf/1706.00527.pdf
104
+ factor = np.random.normal(loc=1., scale=sigma, size=(x.shape[0],x.shape[2]))
105
+ return np.multiply(x, factor[:,np.newaxis,:])
106
+
107
+ def rotation(x):
108
+ flip = np.random.choice([-1, 1], size=(x.shape[0],x.shape[2]))
109
+ rotate_axis = np.arange(x.shape[2])
110
+ np.random.shuffle(rotate_axis)
111
+ return flip[:,np.newaxis,:] * x[:,:,rotate_axis]
112
+
113
+ def permutation(x, max_segments=5, seg_mode="equal"):
114
+ orig_steps = np.arange(x.shape[1])
115
+ num_segs = np.random.randint(1, max_segments, size=(x.shape[0]))
116
+
117
+ ret = np.zeros_like(x)
118
+ for i, pat in enumerate(x):
119
+ if num_segs[i] > 1:
120
+ if seg_mode == "random":
121
+ split_points = np.random.choice(x.shape[1] - 2, num_segs[i] - 1, replace=False)
122
+ split_points.sort()
123
+ splits = np.split(orig_steps, split_points)
124
+ else:
125
+ splits = np.array_split(orig_steps, num_segs[i])
126
+
127
+
128
+ perm = np.random.permutation(len(splits))
129
+ warp = np.concatenate([splits[j] for j in perm]).ravel()
130
+
131
+ ret[i] = pat[warp]
132
+ else:
133
+ ret[i] = pat
134
+ return ret
135
+
136
+
137
+ def magnitude_warp(x, sigma=0.2, knot=4):
138
+ from scipy.interpolate import CubicSpline
139
+ orig_steps = np.arange(x.shape[1])
140
+
141
+ random_warps = np.random.normal(loc=1.0, scale=sigma, size=(x.shape[0], knot+2, x.shape[2]))
142
+ warp_steps = (np.ones((x.shape[2],1))*(np.linspace(0, x.shape[1]-1., num=knot+2))).T
143
+ ret = np.zeros_like(x)
144
+ for i, pat in enumerate(x):
145
+ warper = np.array([CubicSpline(warp_steps[:,dim], random_warps[i,:,dim])(orig_steps) for dim in range(x.shape[2])]).T
146
+ ret[i] = pat * warper
147
+
148
+ return ret
149
+
150
+ def time_warp(x, sigma=0.2, knot=4):
151
+ from scipy.interpolate import CubicSpline
152
+ orig_steps = np.arange(x.shape[1])
153
+
154
+ random_warps = np.random.normal(loc=1.0, scale=sigma, size=(x.shape[0], knot+2, x.shape[2]))
155
+ warp_steps = (np.ones((x.shape[2],1))*(np.linspace(0, x.shape[1]-1., num=knot+2))).T
156
+
157
+ ret = np.zeros_like(x)
158
+ for i, pat in enumerate(x):
159
+ for dim in range(x.shape[2]):
160
+ time_warp = CubicSpline(warp_steps[:,dim], warp_steps[:,dim] * random_warps[i,:,dim])(orig_steps)
161
+ scale = (x.shape[1]-1)/time_warp[-1]
162
+ ret[i,:,dim] = np.interp(orig_steps, np.clip(scale*time_warp, 0, x.shape[1]-1), pat[:,dim]).T
163
+ return ret
164
+
165
+ def window_slice(x, reduce_ratio=0.9):
166
+ # https://halshs.archives-ouvertes.fr/halshs-01357973/document
167
+ target_len = np.ceil(reduce_ratio*x.shape[1]).astype(int)
168
+ if target_len >= x.shape[1]:
169
+ return x
170
+ starts = np.random.randint(low=0, high=x.shape[1]-target_len, size=(x.shape[0])).astype(int)
171
+ ends = (target_len + starts).astype(int)
172
+
173
+ ret = np.zeros_like(x)
174
+ for i, pat in enumerate(x):
175
+ for dim in range(x.shape[2]):
176
+ ret[i,:,dim] = np.interp(np.linspace(0, target_len, num=x.shape[1]), np.arange(target_len), pat[starts[i]:ends[i],dim]).T
177
+ return ret
178
+
179
+ def window_warp(x, window_ratio=0.1, scales=[0.5, 2.]):
180
+ # https://halshs.archives-ouvertes.fr/halshs-01357973/document
181
+ warp_scales = np.random.choice(scales, x.shape[0])
182
+ warp_size = np.ceil(window_ratio*x.shape[1]).astype(int)
183
+ window_steps = np.arange(warp_size)
184
+
185
+ window_starts = np.random.randint(low=1, high=x.shape[1]-warp_size-1, size=(x.shape[0])).astype(int)
186
+ window_ends = (window_starts + warp_size).astype(int)
187
+
188
+ ret = np.zeros_like(x)
189
+ for i, pat in enumerate(x):
190
+ for dim in range(x.shape[2]):
191
+ start_seg = pat[:window_starts[i],dim]
192
+ window_seg = np.interp(np.linspace(0, warp_size-1, num=int(warp_size*warp_scales[i])), window_steps, pat[window_starts[i]:window_ends[i],dim])
193
+ end_seg = pat[window_ends[i]:,dim]
194
+ warped = np.concatenate((start_seg, window_seg, end_seg))
195
+ ret[i,:,dim] = np.interp(np.arange(x.shape[1]), np.linspace(0, x.shape[1]-1., num=warped.size), warped).T
196
+ return ret
197
+
198
+ def spawner(x, labels, sigma=0.05, verbose=0):
199
+ # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6983028/
200
+ # use verbose=-1 to turn off warnings
201
+ # use verbose=1 to print out figures
202
+
203
+ import dtw as dtw
204
+ random_points = np.random.randint(low=1, high=x.shape[1]-1, size=x.shape[0])
205
+ window = np.ceil(x.shape[1] / 10.).astype(int)
206
+ orig_steps = np.arange(x.shape[1])
207
+ l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
208
+
209
+ ret = np.zeros_like(x)
210
+ for i, pat in enumerate(tqdm(x)):
211
+ # guarentees that same one isnt selected
212
+ choices = np.delete(np.arange(x.shape[0]), i)
213
+ # remove ones of different classes
214
+ choices = np.where(l[choices] == l[i])[0]
215
+ if choices.size > 0:
216
+ random_sample = x[np.random.choice(choices)]
217
+ # SPAWNER splits the path into two randomly
218
+ path1 = dtw.dtw(pat[:random_points[i]], random_sample[:random_points[i]], dtw.RETURN_PATH, slope_constraint="symmetric", window=window)
219
+ path2 = dtw.dtw(pat[random_points[i]:], random_sample[random_points[i]:], dtw.RETURN_PATH, slope_constraint="symmetric", window=window)
220
+ combined = np.concatenate((np.vstack(path1), np.vstack(path2+random_points[i])), axis=1)
221
+ if verbose:
222
+ print(random_points[i])
223
+ dtw_value, cost, DTW_map, path = dtw.dtw(pat, random_sample, return_flag = dtw.RETURN_ALL, slope_constraint=slope_constraint, window=window)
224
+ dtw.draw_graph1d(cost, DTW_map, path, pat, random_sample)
225
+ dtw.draw_graph1d(cost, DTW_map, combined, pat, random_sample)
226
+ mean = np.mean([pat[combined[0]], random_sample[combined[1]]], axis=0)
227
+ for dim in range(x.shape[2]):
228
+ ret[i,:,dim] = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=mean.shape[0]), mean[:,dim]).T
229
+ else:
230
+ if verbose > -1:
231
+ print("There is only one pattern of class %d, skipping pattern average"%l[i])
232
+ ret[i,:] = pat
233
+ return jitter(ret, sigma=sigma)
234
+
235
+ def wdba(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True, verbose=0):
236
+ # https://ieeexplore.ieee.org/document/8215569
237
+ # use verbose = -1 to turn off warnings
238
+ # slope_constraint is for DTW. "symmetric" or "asymmetric"
239
+
240
+ import dtw as dtw
241
+
242
+ if use_window:
243
+ window = np.ceil(x.shape[1] / 10.).astype(int)
244
+ else:
245
+ window = None
246
+ orig_steps = np.arange(x.shape[1])
247
+ l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
248
+
249
+ ret = np.zeros_like(x)
250
+ for i in tqdm(range(ret.shape[0])):
251
+ # get the same class as i
252
+ choices = np.where(l == l[i])[0]
253
+ if choices.size > 0:
254
+ # pick random intra-class pattern
255
+ k = min(choices.size, batch_size)
256
+ random_prototypes = x[np.random.choice(choices, k, replace=False)]
257
+
258
+ # calculate dtw between all
259
+ dtw_matrix = np.zeros((k, k))
260
+ for p, prototype in enumerate(random_prototypes):
261
+ for s, sample in enumerate(random_prototypes):
262
+ if p == s:
263
+ dtw_matrix[p, s] = 0.
264
+ else:
265
+ dtw_matrix[p, s] = dtw.dtw(prototype, sample, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
266
+
267
+ # get medoid
268
+ medoid_id = np.argsort(np.sum(dtw_matrix, axis=1))[0]
269
+ nearest_order = np.argsort(dtw_matrix[medoid_id])
270
+ medoid_pattern = random_prototypes[medoid_id]
271
+
272
+ # start weighted DBA
273
+ average_pattern = np.zeros_like(medoid_pattern)
274
+ weighted_sums = np.zeros((medoid_pattern.shape[0]))
275
+ for nid in nearest_order:
276
+ if nid == medoid_id or dtw_matrix[medoid_id, nearest_order[1]] == 0.:
277
+ average_pattern += medoid_pattern
278
+ weighted_sums += np.ones_like(weighted_sums)
279
+ else:
280
+ path = dtw.dtw(medoid_pattern, random_prototypes[nid], dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
281
+ dtw_value = dtw_matrix[medoid_id, nid]
282
+ warped = random_prototypes[nid, path[1]]
283
+ weight = np.exp(np.log(0.5)*dtw_value/dtw_matrix[medoid_id, nearest_order[1]])
284
+ average_pattern[path[0]] += weight * warped
285
+ weighted_sums[path[0]] += weight
286
+
287
+ ret[i,:] = average_pattern / weighted_sums[:,np.newaxis]
288
+ else:
289
+ if verbose > -1:
290
+ print("There is only one pattern of class %d, skipping pattern average"%l[i])
291
+ ret[i,:] = x[i]
292
+ return ret
293
+
294
+
295
+ def random_guided_warp(x, labels, slope_constraint="symmetric", use_window=True, dtw_type="normal", verbose=0):
296
+ # use verbose = -1 to turn off warnings
297
+ # slope_constraint is for DTW. "symmetric" or "asymmetric"
298
+ # dtw_type is for shapeDTW or DTW. "normal" or "shape"
299
+
300
+ import dtw as dtw
301
+
302
+ if use_window:
303
+ window = np.ceil(x.shape[1] / 10.).astype(int)
304
+ else:
305
+ window = None
306
+ orig_steps = np.arange(x.shape[1])
307
+ l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
308
+
309
+ ret = np.zeros_like(x)
310
+ for i, pat in enumerate(tqdm(x)):
311
+ # guarentees that same one isnt selected
312
+ choices = np.delete(np.arange(x.shape[0]), i)
313
+ # remove ones of different classes
314
+ choices = np.where(l[choices] == l[i])[0]
315
+ if choices.size > 0:
316
+ # pick random intra-class pattern
317
+ random_prototype = x[np.random.choice(choices)]
318
+
319
+ if dtw_type == "shape":
320
+ path = dtw.shape_dtw(random_prototype, pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
321
+ else:
322
+ path = dtw.dtw(random_prototype, pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
323
+
324
+ # Time warp
325
+ warped = pat[path[1]]
326
+ for dim in range(x.shape[2]):
327
+ ret[i,:,dim] = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), warped[:,dim]).T
328
+ else:
329
+ if verbose > -1:
330
+ print("There is only one pattern of class %d, skipping timewarping"%l[i])
331
+ ret[i,:] = pat
332
+ return ret
333
+
334
+ def random_guided_warp_shape(x, labels, slope_constraint="symmetric", use_window=True):
335
+ return random_guided_warp(x, labels, slope_constraint, use_window, dtw_type="shape")
336
+
337
+ def discriminative_guided_warp(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True, dtw_type="normal", use_variable_slice=True, verbose=0):
338
+ # use verbose = -1 to turn off warnings
339
+ # slope_constraint is for DTW. "symmetric" or "asymmetric"
340
+ # dtw_type is for shapeDTW or DTW. "normal" or "shape"
341
+
342
+ import dtw as dtw
343
+
344
+ if use_window:
345
+ window = np.ceil(x.shape[1] / 10.).astype(int)
346
+ else:
347
+ window = None
348
+ orig_steps = np.arange(x.shape[1])
349
+ l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
350
+
351
+ positive_batch = np.ceil(batch_size / 2).astype(int)
352
+ negative_batch = np.floor(batch_size / 2).astype(int)
353
+
354
+ ret = np.zeros_like(x)
355
+ warp_amount = np.zeros(x.shape[0])
356
+ for i, pat in enumerate(tqdm(x)):
357
+ # guarentees that same one isnt selected
358
+ choices = np.delete(np.arange(x.shape[0]), i)
359
+
360
+ # remove ones of different classes
361
+ positive = np.where(l[choices] == l[i])[0]
362
+ negative = np.where(l[choices] != l[i])[0]
363
+
364
+ if positive.size > 0 and negative.size > 0:
365
+ pos_k = min(positive.size, positive_batch)
366
+ neg_k = min(negative.size, negative_batch)
367
+ positive_prototypes = x[np.random.choice(positive, pos_k, replace=False)]
368
+ negative_prototypes = x[np.random.choice(negative, neg_k, replace=False)]
369
+
370
+ # vector embedding and nearest prototype in one
371
+ pos_aves = np.zeros((pos_k))
372
+ neg_aves = np.zeros((pos_k))
373
+ if dtw_type == "shape":
374
+ for p, pos_prot in enumerate(positive_prototypes):
375
+ for ps, pos_samp in enumerate(positive_prototypes):
376
+ if p != ps:
377
+ pos_aves[p] += (1./(pos_k-1.))*dtw.shape_dtw(pos_prot, pos_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
378
+ for ns, neg_samp in enumerate(negative_prototypes):
379
+ neg_aves[p] += (1./neg_k)*dtw.shape_dtw(pos_prot, neg_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
380
+ selected_id = np.argmax(neg_aves - pos_aves)
381
+ path = dtw.shape_dtw(positive_prototypes[selected_id], pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
382
+ else:
383
+ for p, pos_prot in enumerate(positive_prototypes):
384
+ for ps, pos_samp in enumerate(positive_prototypes):
385
+ if p != ps:
386
+ pos_aves[p] += (1./(pos_k-1.))*dtw.dtw(pos_prot, pos_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
387
+ for ns, neg_samp in enumerate(negative_prototypes):
388
+ neg_aves[p] += (1./neg_k)*dtw.dtw(pos_prot, neg_samp, dtw.RETURN_VALUE, slope_constraint=slope_constraint, window=window)
389
+ selected_id = np.argmax(neg_aves - pos_aves)
390
+ path = dtw.dtw(positive_prototypes[selected_id], pat, dtw.RETURN_PATH, slope_constraint=slope_constraint, window=window)
391
+
392
+ # Time warp
393
+ warped = pat[path[1]]
394
+ warp_path_interp = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), path[1])
395
+ warp_amount[i] = np.sum(np.abs(orig_steps-warp_path_interp))
396
+ for dim in range(x.shape[2]):
397
+ ret[i,:,dim] = np.interp(orig_steps, np.linspace(0, x.shape[1]-1., num=warped.shape[0]), warped[:,dim]).T
398
+ else:
399
+ if verbose > -1:
400
+ print("There is only one pattern of class %d"%l[i])
401
+ ret[i,:] = pat
402
+ warp_amount[i] = 0.
403
+ if use_variable_slice:
404
+ max_warp = np.max(warp_amount)
405
+ if max_warp == 0:
406
+ # unchanged
407
+ ret = window_slice(ret, reduce_ratio=0.9)
408
+ else:
409
+ for i, pat in enumerate(ret):
410
+ # Variable Sllicing
411
+ ret[i] = window_slice(pat[np.newaxis,:,:], reduce_ratio=0.9+0.1*warp_amount[i]/max_warp)[0]
412
+ return ret
413
+
414
+ def discriminative_guided_warp_shape(x, labels, batch_size=6, slope_constraint="symmetric", use_window=True):
415
+ return discriminative_guided_warp(x, labels, batch_size, slope_constraint, use_window, dtw_type="shape")
time_series_classification/minirocket/src/dtw.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from time_series_augmentation (https://github.com/uchidalab/time_series_augmentation)
2
+ # Original: Apache License 2.0 by Brian Kenji Iwana and Seiichi Uchida
3
+
4
+ __author__ = 'Brian Iwana'
5
+
6
+ import numpy as np
7
+ import math
8
+ import sys
9
+
10
+ RETURN_VALUE = 0
11
+ RETURN_PATH = 1
12
+ RETURN_ALL = -1
13
+
14
+ # Core DTW
15
+ def _traceback(DTW, slope_constraint):
16
+ i, j = np.array(DTW.shape) - 1
17
+ p, q = [i-1], [j-1]
18
+
19
+ if slope_constraint == "asymmetric":
20
+ while (i > 1):
21
+ tb = np.argmin((DTW[i-1, j], DTW[i-1, j-1], DTW[i-1, j-2]))
22
+
23
+ if (tb == 0):
24
+ i = i - 1
25
+ elif (tb == 1):
26
+ i = i - 1
27
+ j = j - 1
28
+ elif (tb == 2):
29
+ i = i - 1
30
+ j = j - 2
31
+
32
+ p.insert(0, i-1)
33
+ q.insert(0, j-1)
34
+ elif slope_constraint == "symmetric":
35
+ while (i > 1 or j > 1):
36
+ tb = np.argmin((DTW[i-1, j-1], DTW[i-1, j], DTW[i, j-1]))
37
+
38
+ if (tb == 0):
39
+ i = i - 1
40
+ j = j - 1
41
+ elif (tb == 1):
42
+ i = i - 1
43
+ elif (tb == 2):
44
+ j = j - 1
45
+
46
+ p.insert(0, i-1)
47
+ q.insert(0, j-1)
48
+ else:
49
+ sys.exit("Unknown slope constraint %s"%slope_constraint)
50
+
51
+ return (np.array(p), np.array(q))
52
+
53
+ def dtw(prototype, sample, return_flag = RETURN_VALUE, slope_constraint="asymmetric", window=None):
54
+ """ Computes the DTW of two sequences.
55
+ :param prototype: np array [0..b]
56
+ :param sample: np array [0..t]
57
+ :param extended: bool
58
+ """
59
+ p = prototype.shape[0]
60
+ assert p != 0, "Prototype empty!"
61
+ s = sample.shape[0]
62
+ assert s != 0, "Sample empty!"
63
+
64
+ if window is None:
65
+ window = s
66
+
67
+ cost = np.full((p, s), np.inf)
68
+ for i in range(p):
69
+ start = max(0, i-window)
70
+ end = min(s, i+window)+1
71
+ cost[i,start:end]=np.linalg.norm(sample[start:end] - prototype[i], axis=1)
72
+
73
+ DTW = _cummulative_matrix(cost, slope_constraint, window)
74
+
75
+ if return_flag == RETURN_ALL:
76
+ return DTW[-1,-1], cost, DTW[1:,1:], _traceback(DTW, slope_constraint)
77
+ elif return_flag == RETURN_PATH:
78
+ return _traceback(DTW, slope_constraint)
79
+ else:
80
+ return DTW[-1,-1]
81
+
82
+ def _cummulative_matrix(cost, slope_constraint, window):
83
+ p = cost.shape[0]
84
+ s = cost.shape[1]
85
+
86
+ # Note: DTW is one larger than cost and the original patterns
87
+ DTW = np.full((p+1, s+1), np.inf)
88
+
89
+ DTW[0, 0] = 0.0
90
+
91
+ if slope_constraint == "asymmetric":
92
+ for i in range(1, p+1):
93
+ if i <= window+1:
94
+ DTW[i,1] = cost[i-1,0] + min(DTW[i-1,0], DTW[i-1,1])
95
+ for j in range(max(2, i-window), min(s, i+window)+1):
96
+ DTW[i,j] = cost[i-1,j-1] + min(DTW[i-1,j-2], DTW[i-1,j-1], DTW[i-1,j])
97
+ elif slope_constraint == "symmetric":
98
+ for i in range(1, p+1):
99
+ for j in range(max(1, i-window), min(s, i+window)+1):
100
+ DTW[i,j] = cost[i-1,j-1] + min(DTW[i-1,j-1], DTW[i,j-1], DTW[i-1,j])
101
+ else:
102
+ sys.exit("Unknown slope constraint %s"%slope_constraint)
103
+
104
+ return DTW
105
+
106
+ def shape_dtw(prototype, sample, return_flag = RETURN_VALUE, slope_constraint="asymmetric", window=None, descr_ratio=0.05):
107
+ """ Computes the shapeDTW of two sequences.
108
+ :param prototype: np array [0..b]
109
+ :param sample: np array [0..t]
110
+ :param extended: bool
111
+ """
112
+ # shapeDTW
113
+ # https://www.sciencedirect.com/science/article/pii/S0031320317303710
114
+
115
+ p = prototype.shape[0]
116
+ assert p != 0, "Prototype empty!"
117
+ s = sample.shape[0]
118
+ assert s != 0, "Sample empty!"
119
+
120
+ if window is None:
121
+ window = s
122
+
123
+ p_feature_len = np.clip(np.round(p * descr_ratio), 5, 100).astype(int)
124
+ s_feature_len = np.clip(np.round(s * descr_ratio), 5, 100).astype(int)
125
+
126
+ # padding
127
+ p_pad_front = (np.ceil(p_feature_len / 2.)).astype(int)
128
+ p_pad_back = (np.floor(p_feature_len / 2.)).astype(int)
129
+ s_pad_front = (np.ceil(s_feature_len / 2.)).astype(int)
130
+ s_pad_back = (np.floor(s_feature_len / 2.)).astype(int)
131
+
132
+ prototype_pad = np.pad(prototype, ((p_pad_front, p_pad_back), (0, 0)), mode="edge")
133
+ sample_pad = np.pad(sample, ((s_pad_front, s_pad_back), (0, 0)), mode="edge")
134
+ p_p = prototype_pad.shape[0]
135
+ s_p = sample_pad.shape[0]
136
+
137
+ cost = np.full((p, s), np.inf)
138
+ for i in range(p):
139
+ for j in range(max(0, i-window), min(s, i+window)):
140
+ cost[i, j] = np.linalg.norm(sample_pad[j:j+s_feature_len] - prototype_pad[i:i+p_feature_len])
141
+
142
+ DTW = _cummulative_matrix(cost, slope_constraint=slope_constraint, window=window)
143
+
144
+ if return_flag == RETURN_ALL:
145
+ return DTW[-1,-1], cost, DTW[1:,1:], _traceback(DTW, slope_constraint)
146
+ elif return_flag == RETURN_PATH:
147
+ return _traceback(DTW, slope_constraint)
148
+ else:
149
+ return DTW[-1,-1]
150
+
151
+ # Draw helpers
152
+ def draw_graph2d(cost, DTW, path, prototype, sample):
153
+ import matplotlib.pyplot as plt
154
+ plt.figure(figsize=(12, 8))
155
+ # plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
156
+
157
+ #cost
158
+ plt.subplot(2, 3, 1)
159
+ plt.imshow(cost.T, cmap=plt.cm.gray, interpolation='none', origin='lower')
160
+ plt.plot(path[0], path[1], 'y')
161
+ plt.xlim((-0.5, cost.shape[0]-0.5))
162
+ plt.ylim((-0.5, cost.shape[0]-0.5))
163
+
164
+ #dtw
165
+ plt.subplot(2, 3, 2)
166
+ plt.imshow(DTW.T, cmap=plt.cm.gray, interpolation='none', origin='lower')
167
+ plt.plot(path[0]+1, path[1]+1, 'y')
168
+ plt.xlim((-0.5, DTW.shape[0]-0.5))
169
+ plt.ylim((-0.5, DTW.shape[0]-0.5))
170
+
171
+ #prototype
172
+ plt.subplot(2, 3, 4)
173
+ plt.plot(prototype[:,0], prototype[:,1], 'b-o')
174
+
175
+ #connection
176
+ plt.subplot(2, 3, 5)
177
+ for i in range(0,path[0].shape[0]):
178
+ plt.plot([prototype[path[0][i],0], sample[path[1][i],0]],[prototype[path[0][i],1], sample[path[1][i],1]], 'y-')
179
+ plt.plot(sample[:,0], sample[:,1], 'g-o')
180
+ plt.plot(prototype[:,0], prototype[:,1], 'b-o')
181
+
182
+ #sample
183
+ plt.subplot(2, 3, 6)
184
+ plt.plot(sample[:,0], sample[:,1], 'g-o')
185
+
186
+ plt.tight_layout()
187
+ plt.show()
188
+
189
+ def draw_graph1d(cost, DTW, path, prototype, sample):
190
+ import matplotlib.pyplot as plt
191
+ plt.figure(figsize=(12, 8))
192
+ # plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
193
+ p_steps = np.arange(prototype.shape[0])
194
+ s_steps = np.arange(sample.shape[0])
195
+
196
+ #cost
197
+ plt.subplot(2, 3, 1)
198
+ plt.imshow(cost.T, cmap=plt.cm.gray, interpolation='none', origin='lower')
199
+ plt.plot(path[0], path[1], 'y')
200
+ plt.xlim((-0.5, cost.shape[0]-0.5))
201
+ plt.ylim((-0.5, cost.shape[0]-0.5))
202
+
203
+ #dtw
204
+ plt.subplot(2, 3, 2)
205
+ plt.imshow(DTW.T, cmap=plt.cm.gray, interpolation='none', origin='lower')
206
+ plt.plot(path[0]+1, path[1]+1, 'y')
207
+ plt.xlim((-0.5, DTW.shape[0]-0.5))
208
+ plt.ylim((-0.5, DTW.shape[0]-0.5))
209
+
210
+ #prototype
211
+ plt.subplot(2, 3, 4)
212
+ plt.plot(p_steps, prototype[:,0], 'b-o')
213
+
214
+ #connection
215
+ plt.subplot(2, 3, 5)
216
+ for i in range(0,path[0].shape[0]):
217
+ plt.plot([path[0][i], path[1][i]],[prototype[path[0][i],0], sample[path[1][i],0]], 'y-')
218
+ plt.plot(p_steps, sample[:,0], 'g-o')
219
+ plt.plot(s_steps, prototype[:,0], 'b-o')
220
+
221
+ #sample
222
+ plt.subplot(2, 3, 6)
223
+ plt.plot(s_steps, sample[:,0], 'g-o')
224
+
225
+ plt.tight_layout()
226
+ plt.show()
time_series_classification/minirocket/src/hyperparameter_tune.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from MiniRocket (https://github.com/angus924/minirocket)
2
+ # Original authors: Angus Dempster, Daniel F. Schmidt, Geoffrey I. Webb
3
+ # Copyright (C) 2025 Jafar Bakhshaliyev
4
+ # Licensed under GNU General Public License v3.0
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ import os
9
+ import time
10
+ import sys
11
+ import argparse
12
+ from sklearn.linear_model import RidgeClassifierCV
13
+ from sklearn.metrics import accuracy_score
14
+ import augmentation as aug
15
+ from scipy.special import softmax
16
+ from sklearn.metrics import log_loss
17
+ from sklearn.model_selection import train_test_split
18
+ from minirocket import fit, transform
19
+
20
+ UCR_PATH = "" # Update this path to your environment
21
+
22
+
23
+ def run_augmentation(x, y, args):
24
+ """
25
+ Apply data augmentation to the input data based on args.
26
+
27
+ Parameters:
28
+ -----------
29
+ x : numpy.ndarray
30
+ Original time series data
31
+ y : numpy.ndarray
32
+ Original labels
33
+ args : argparse.Namespace
34
+ Command line arguments containing augmentation options
35
+
36
+ Returns:
37
+ --------
38
+ x_aug : numpy.ndarray
39
+ Augmented time series data
40
+ y_aug : numpy.ndarray
41
+ Augmented labels
42
+ augmentation_tags : str
43
+ String describing the applied augmentations
44
+ """
45
+ print("Augmenting data for dataset %s" % args.dataset)
46
+
47
+ np.random.seed(args.seed)
48
+ x_aug = x.copy()
49
+ y_aug = y.copy()
50
+
51
+ augmentation_tags = ""
52
+
53
+ if args.augmentation_ratio > 0:
54
+ augmentation_tags = "%d" % args.augmentation_ratio
55
+ print(f"Original training size: {x.shape[0]} samples")
56
+
57
+ for n in range(args.augmentation_ratio):
58
+ x_temp, current_tags = augment(x, y, args)
59
+
60
+ x_temp = x_temp.astype(np.float32)
61
+ x_aug = np.vstack((x_aug, x_temp))
62
+ y_aug = np.append(y_aug, y)
63
+
64
+ print(f"Round {n+1}: {current_tags} done - Added {x_temp.shape[0]} samples")
65
+
66
+ if n == 0:
67
+ augmentation_tags += current_tags
68
+
69
+ print(f"Augmented training size: {x_aug.shape[0]} samples")
70
+ print(f"Augmented data type: {x_aug.dtype}")
71
+
72
+ if args.extra_tag:
73
+ augmentation_tags += "_" + args.extra_tag
74
+ else:
75
+ augmentation_tags = "none"
76
+ if args.extra_tag:
77
+ augmentation_tags = args.extra_tag
78
+
79
+ x_aug = x_aug.astype(np.float32)
80
+
81
+ return x_aug, y_aug, augmentation_tags
82
+
83
+
84
+
85
+ def augment(x, y, args):
86
+ """
87
+ Apply specified augmentations to the data including SFCC.
88
+
89
+ Parameters:
90
+ -----------
91
+ x : numpy.ndarray
92
+ Original time series data
93
+ y : numpy.ndarray
94
+ Original labels
95
+ args : argparse.Namespace
96
+ Command line arguments containing augmentation options
97
+
98
+ Returns:
99
+ --------
100
+ x : numpy.ndarray
101
+ Augmented time series data
102
+ augmentation_tags : str
103
+ String describing the applied augmentations
104
+ """
105
+ augmentation_tags = ""
106
+ x_aug = x.copy()
107
+
108
+ needs_reshape = False
109
+ original_shape = x_aug.shape
110
+
111
+ if len(x_aug.shape) == 2:
112
+ # Reshape from (n_samples, timesteps) to (n_samples, timesteps, 1)
113
+ x_aug = x_aug.reshape(x_aug.shape[0], x_aug.shape[1], 1)
114
+ needs_reshape = True
115
+
116
+ print('after needs reshape', x_aug.shape)
117
+
118
+
119
+ if args.jitter:
120
+ x_aug = aug.jitter(x_aug)
121
+ augmentation_tags += "_jitter"
122
+
123
+ if args.tps:
124
+ x_aug = aug.tps(x_aug, y, args.patch_len, args.stride, args.shuffle_rate)
125
+ augmentation_tags += "_tps"
126
+
127
+ if args.scaling:
128
+ x_aug = aug.scaling(x_aug)
129
+ augmentation_tags += "_scaling"
130
+
131
+ if args.rotation:
132
+ x_aug = aug.rotation(x_aug)
133
+ augmentation_tags += "_rotation"
134
+
135
+ if args.permutation:
136
+ x_aug = aug.permutation(x_aug)
137
+ augmentation_tags += "_permutation"
138
+
139
+ if args.randompermutation:
140
+ x_aug = aug.permutation(x_aug, seg_mode="random")
141
+ augmentation_tags += "_randomperm"
142
+
143
+ if args.magwarp:
144
+ x_aug = aug.magnitude_warp(x_aug)
145
+ augmentation_tags += "_magwarp"
146
+
147
+ if args.timewarp:
148
+ x_aug = aug.time_warp(x_aug)
149
+ augmentation_tags += "_timewarp"
150
+
151
+ if args.windowslice:
152
+ x_aug = aug.window_slice(x_aug)
153
+ augmentation_tags += "_windowslice"
154
+
155
+ if args.windowwarp:
156
+ x_aug = aug.window_warp(x_aug)
157
+ augmentation_tags += "_windowwarp"
158
+
159
+ if args.spawner:
160
+ x_aug = aug.spawner(x_aug, y)
161
+ augmentation_tags += "_spawner"
162
+
163
+ if args.dtwwarp:
164
+ x_aug = aug.random_guided_warp(x_aug, y)
165
+ augmentation_tags += "_rgw"
166
+
167
+ if args.shapedtwwarp:
168
+ x_aug = aug.random_guided_warp_shape(x_aug, y)
169
+ augmentation_tags += "_rgws"
170
+
171
+ if args.wdba:
172
+ x_aug = aug.wdba(x_aug, y)
173
+ augmentation_tags += "_wdba"
174
+
175
+ if args.discdtw:
176
+ x_aug = aug.discriminative_guided_warp(x_aug, y)
177
+ augmentation_tags += "_dgw"
178
+
179
+ if args.discsdtw:
180
+ x_aug = aug.discriminative_guided_warp_shape(x_aug, y)
181
+ augmentation_tags += "_dgws"
182
+
183
+ if needs_reshape:
184
+ x_aug = x_aug.reshape(original_shape)
185
+
186
+
187
+ if not augmentation_tags:
188
+ augmentation_tags = "_none"
189
+
190
+ return x_aug.astype(np.float32), augmentation_tags
191
+
192
+ def load_ucr_dataset(dataset_name):
193
+ """
194
+ Load a UCR dataset from TSV files.
195
+
196
+ Parameters:
197
+ -----------
198
+ dataset_name : str
199
+ Name of the dataset (e.g., 'FordB')
200
+
201
+ Returns:
202
+ --------
203
+ X_train : numpy.ndarray
204
+ Training data (time series)
205
+ y_train : numpy.ndarray
206
+ Training labels
207
+ X_test : numpy.ndarray
208
+ Test data (time series)
209
+ y_test : numpy.ndarray
210
+ Test labels
211
+ """
212
+ # training data
213
+ train_file = os.path.join(UCR_PATH, dataset_name, f"{dataset_name}_TRAIN.tsv")
214
+ if not os.path.exists(train_file):
215
+ train_file = os.path.join(UCR_PATH, dataset_name, f"{dataset_name}_Train.tsv")
216
+
217
+ # testing data
218
+ test_file = os.path.join(UCR_PATH, dataset_name, f"{dataset_name}_TEST.tsv")
219
+ if not os.path.exists(test_file):
220
+ test_file = os.path.join(UCR_PATH, dataset_name, f"{dataset_name}_Test.tsv")
221
+
222
+ if not os.path.exists(train_file) or not os.path.exists(test_file):
223
+ raise FileNotFoundError(f"Dataset files for {dataset_name} not found: {train_file}, {test_file}")
224
+
225
+ print(f"Loading files: {train_file}, {test_file}")
226
+
227
+ # Load data
228
+ train_df = pd.read_csv(train_file, sep='\t', header=None)
229
+ test_df = pd.read_csv(test_file, sep='\t', header=None)
230
+
231
+ y_train = train_df.iloc[:, 0].values
232
+ X_train = train_df.iloc[:, 1:].values
233
+
234
+ y_test = test_df.iloc[:, 0].values
235
+ X_test = test_df.iloc[:, 1:].values
236
+
237
+ unique_train, counts_train = np.unique(y_train, return_counts=True)
238
+ unique_test, counts_test = np.unique(y_test, return_counts=True)
239
+
240
+ print(f"Train class distribution: {dict(zip(unique_train, counts_train))}")
241
+ print(f"Test class distribution: {dict(zip(unique_test, counts_test))}")
242
+
243
+ X_train = X_train.astype(np.float32)
244
+ X_test = X_test.astype(np.float32)
245
+
246
+ print(f"Data loaded successfully. Train shape: {X_train.shape}, Test shape: {X_test.shape}")
247
+ print(f"Train data type: {X_train.dtype}, Test data type: {X_test.dtype}")
248
+
249
+ return X_train, y_train, X_test, y_test
250
+
251
+ def run_minirocket_experiment(dataset_name, args):
252
+ """
253
+ Run MiniRocket on a UCR dataset with validation split for hyperparameter tuning.
254
+
255
+ Parameters:
256
+ -----------
257
+ dataset_name : str
258
+ Name of the dataset (e.g., 'FordB')
259
+ args : argparse.Namespace
260
+ Command line arguments containing options
261
+
262
+ Returns:
263
+ --------
264
+ mean_train_accuracy : float
265
+ Mean training accuracy across iterations
266
+ std_train_accuracy : float
267
+ Standard deviation of training accuracy across iterations
268
+ mean_val_accuracy : float
269
+ Mean validation accuracy across iterations
270
+ std_val_accuracy : float
271
+ Standard deviation of validation accuracy across iterations
272
+ """
273
+ # Load dataset
274
+ print(f"Loading dataset: {dataset_name}")
275
+ X_train_full, y_train_full, X_test, y_test = load_ucr_dataset(dataset_name)
276
+
277
+ # Split original training set into train (80%) and validation (20%)
278
+ X_train, X_val, y_train, y_val = train_test_split(
279
+ X_train_full, y_train_full,
280
+ test_size=0.2,
281
+ random_state=args.seed,
282
+ stratify=y_train_full if len(np.unique(y_train_full)) > 1 else None
283
+ )
284
+
285
+ print(f"Split training data: Train shape: {X_train.shape}, Validation shape: {X_val.shape}")
286
+
287
+
288
+ if args.use_augmentation:
289
+ X_train_aug, y_train_aug, augmentation_tags = run_augmentation(X_train, y_train, args)
290
+ else:
291
+ X_train_aug, y_train_aug = X_train.copy(), y_train.copy()
292
+ augmentation_tags = "none"
293
+
294
+ train_accuracies = []
295
+ val_accuracies = []
296
+ val_cross_entropies = []
297
+ runtimes = []
298
+
299
+ for iteration in range(args.iterations):
300
+ print(f"Running iteration {iteration+1}/{args.iterations}")
301
+
302
+ start_time = time.time()
303
+
304
+ np.random.seed(args.seed + iteration)
305
+
306
+ parameters = fit(X_train_aug, num_features=args.features)
307
+
308
+ X_train_transform = transform(X_train_aug, parameters)
309
+ classifier = RidgeClassifierCV(alphas=np.logspace(-3, 3, 10))
310
+ classifier.fit(X_train_transform, y_train_aug)
311
+
312
+ train_predictions = classifier.predict(X_train_transform)
313
+ train_accuracy = accuracy_score(y_train_aug, train_predictions)
314
+ train_accuracies.append(train_accuracy)
315
+
316
+ X_val_transform = transform(X_val, parameters)
317
+
318
+ val_predictions = classifier.predict(X_val_transform)
319
+ val_accuracy = accuracy_score(y_val, val_predictions)
320
+ val_accuracies.append(val_accuracy)
321
+
322
+ # Get decision function values for validation set
323
+ val_decision_scores = classifier.decision_function(X_val_transform)
324
+
325
+ if len(np.unique(y_val)) > 2: # Multi-class
326
+ val_probabilities = softmax(val_decision_scores, axis=1)
327
+ else: # Binary case
328
+
329
+ val_scores = np.vstack([-val_decision_scores, val_decision_scores]).T
330
+ val_probabilities = softmax(val_scores, axis=1)
331
+
332
+ try:
333
+ all_classes = np.unique(np.concatenate((y_train_aug, y_val)))
334
+ val_cross_entropy = log_loss(y_val, val_probabilities, labels=all_classes)
335
+ val_cross_entropies.append(val_cross_entropy)
336
+ except Exception as e:
337
+ print(f"Warning: Could not calculate cross-entropy: {e}")
338
+ val_cross_entropy = np.nan
339
+ val_cross_entropies.append(val_cross_entropy)
340
+
341
+ runtime = time.time() - start_time
342
+ runtimes.append(runtime)
343
+
344
+ print(f"Iteration {iteration+1} - Train Accuracy: {train_accuracy:.4f}")
345
+ print(f"Iteration {iteration+1} - Validation Accuracy: {val_accuracy:.4f}, Cross-Entropy: {val_cross_entropy:.4f}")
346
+ print(f"Iteration {iteration+1} - Runtime: {runtime:.2f} seconds")
347
+
348
+ mean_train_accuracy = np.mean(train_accuracies)
349
+ std_train_accuracy = np.std(train_accuracies)
350
+ mean_val_accuracy = np.mean(val_accuracies)
351
+ std_val_accuracy = np.std(val_accuracies)
352
+ mean_val_cross_entropy = np.mean(val_cross_entropies)
353
+ std_val_cross_entropy = np.std(val_cross_entropies)
354
+ mean_runtime = np.mean(runtimes)
355
+
356
+ print(f"\nResults for {dataset_name} with augmentation: {augmentation_tags}")
357
+ print(f"Train size (after augmentation): {X_train_aug.shape[0]} samples")
358
+ print(f"Validation size: {X_val.shape[0]} samples")
359
+ print(f"Mean Train Accuracy: {mean_train_accuracy:.4f} ± {std_train_accuracy:.4f}")
360
+ print(f"Mean Validation Accuracy: {mean_val_accuracy:.4f} ± {std_val_accuracy:.4f}")
361
+ print(f"Mean Validation Cross-Entropy: {mean_val_cross_entropy:.4f} ± {std_val_cross_entropy:.4f}")
362
+ print(f"Mean Runtime: {mean_runtime:.2f} seconds")
363
+
364
+ results_df = pd.DataFrame({
365
+ 'Dataset': [dataset_name],
366
+ 'Augmentation': [augmentation_tags],
367
+ 'Train_Size': [X_train.shape[0]],
368
+ 'Train_Size_After_Aug': [X_train_aug.shape[0]],
369
+ 'Val_Size': [X_val.shape[0]],
370
+ 'Mean_Train_Accuracy': [mean_train_accuracy],
371
+ 'Train_Accuracy_STD': [std_train_accuracy],
372
+ 'Mean_Val_Accuracy': [mean_val_accuracy],
373
+ 'Val_Accuracy_STD': [std_val_accuracy],
374
+ 'Mean_Val_Cross_Entropy': [mean_val_cross_entropy],
375
+ 'Val_Cross_Entropy_STD': [std_val_cross_entropy],
376
+ 'Mean_Runtime': [mean_runtime],
377
+ 'Iterations': [args.iterations],
378
+ 'Features': [args.features],
379
+ 'Individual_Train_Accuracies': [','.join(map(str, train_accuracies))],
380
+ 'Individual_Val_Accuracies': [','.join(map(str, val_accuracies))],
381
+ 'Individual_Val_Cross_Entropies': [','.join(map(str, val_cross_entropies))],
382
+ 'patch_len': [args.patch_len],
383
+ 'stride': [args.stride],
384
+ 'shuffle_rate': [args.shuffle_rate],
385
+ 'sfcc_groups': [args.sfcc_groups]
386
+ })
387
+
388
+ results_filename = f"hyperparameter_tuning_{dataset_name}_{augmentation_tags}.csv"
389
+
390
+ if os.path.exists(results_filename):
391
+ existing_df = pd.read_csv(results_filename)
392
+ combined_df = pd.concat([existing_df, results_df], ignore_index=True)
393
+ combined_df.to_csv(results_filename, index=False)
394
+ print(f"Results appended to {results_filename}")
395
+ else:
396
+ results_df.to_csv(results_filename, index=False)
397
+ print(f"Results saved to new file {results_filename}")
398
+
399
+ return mean_train_accuracy, std_train_accuracy, mean_val_accuracy, std_val_accuracy, mean_val_cross_entropy, std_val_cross_entropy, runtimes, augmentation_tags
400
+
401
+ def list_ucr_datasets():
402
+ """List all available UCR datasets in the UCR_PATH directory"""
403
+ try:
404
+ datasets = [d for d in os.listdir(UCR_PATH) if os.path.isdir(os.path.join(UCR_PATH, d))]
405
+ return sorted(datasets)
406
+ except Exception as e:
407
+ print(f"Error listing datasets: {e}")
408
+ return []
409
+
410
+ if __name__ == "__main__":
411
+ parser = argparse.ArgumentParser(description='Hyperparameter Tuning for Time Series Augmentation')
412
+
413
+ # Dataset selection
414
+ parser.add_argument('--dataset', type=str, help='Dataset name (default: FordB)')
415
+ parser.add_argument('--list', action='store_true', help='List available datasets')
416
+
417
+ # MiniRocket parameters
418
+ parser.add_argument('--features', type=int, default=10000, help='Number of features (default: 10000)')
419
+ parser.add_argument('--iterations', type=int, default=5, help='Number of iterations (default: 5)')
420
+ parser.add_argument('--seed', type=int, default=42, help='Random seed (default: 42)')
421
+
422
+ # Augmentation control
423
+ parser.add_argument('--use-augmentation', action='store_true', help='Use data augmentation')
424
+ parser.add_argument('--augmentation-ratio', type=int, default=0,
425
+ help='Number of augmented copies to add (default: 0)')
426
+ parser.add_argument('--extra-tag', type=str, default='',
427
+ help='Extra tag to add to augmentation tags')
428
+
429
+ # Augmentation methods
430
+ parser.add_argument('--sfcc', action='store_true', help='Apply SFCC augmentation')
431
+ parser.add_argument('--sfcc_groups', type=int, default=4, help='Number of groups for SFCC augmentation (default: 4)')
432
+ parser.add_argument('--jitter', action='store_true', help='Apply jitter augmentation')
433
+ parser.add_argument('--scaling', action='store_true', help='Apply scaling augmentation')
434
+ parser.add_argument('--rotation', action='store_true', help='Apply rotation augmentation')
435
+ parser.add_argument('--permutation', action='store_true', help='Apply permutation augmentation')
436
+ parser.add_argument('--randompermutation', action='store_true', help='Apply random permutation augmentation')
437
+ parser.add_argument('--timewarp', action='store_true', help='Apply time warp augmentation')
438
+ parser.add_argument('--windowslice', action='store_true', help='Apply window slice augmentation')
439
+ parser.add_argument('--windowwarp', action='store_true', help='Apply window warp augmentation')
440
+ parser.add_argument('--spawner', action='store_true', help='Apply spawner augmentation')
441
+ parser.add_argument('--dtwwarp', action='store_true', help='Apply DTW-based warp augmentation')
442
+ parser.add_argument('--shapedtwwarp', action='store_true', help='Apply shape DTW warp augmentation')
443
+ parser.add_argument('--wdba', action='store_true', help='Apply WDBA augmentation')
444
+ parser.add_argument('--discdtw', action='store_true', help='Apply discriminative DTW augmentation')
445
+ parser.add_argument('--discsdtw', action='store_true', help='Apply discriminative shape DTW augmentation')
446
+ parser.add_argument('--tps', action='store_true', help='Apply TPS augmentation')
447
+
448
+ parser.add_argument('--stride', type=int, default=2, help='# of patches stride (default: 2)')
449
+ parser.add_argument('--patch_len', type=int, default=10, help='Patch length (default: 10)')
450
+ parser.add_argument('--shuffle_rate', type=float, default=0.3, help='Shuffle rate (default: 0.3)')
451
+
452
+ args = parser.parse_args()
453
+
454
+ if args.list:
455
+ datasets = list_ucr_datasets()
456
+ if datasets:
457
+ print("Available datasets:")
458
+ for dataset in datasets:
459
+ print(f" - {dataset}")
460
+ else:
461
+ print("No datasets found or UCR_PATH is incorrect.")
462
+ sys.exit(0)
463
+
464
+ # Run on specific dataset
465
+ dataset_name = args.dataset if args.dataset else "FordB"
466
+
467
+ print(f"Running hyperparameter tuning on {dataset_name} dataset")
468
+ print(f"Using {args.features} features and {args.iterations} iterations")
469
+ if args.use_augmentation:
470
+ print(f"Using data augmentation with ratio {args.augmentation_ratio}")
471
+
472
+ run_minirocket_experiment(dataset_name, args)
time_series_classification/minirocket/src/main.py ADDED
@@ -0,0 +1,491 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from MiniRocket (https://github.com/angus924/minirocket)
2
+ # Original authors: Angus Dempster, Daniel F. Schmidt, Geoffrey I. Webb
3
+ # Copyright (C) 2025 Jafar Bakhshaliyev
4
+ # Licensed under GNU General Public License v3.0
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ import os
9
+ import time
10
+ import sys
11
+ import argparse
12
+ from sklearn.linear_model import RidgeClassifierCV
13
+ from sklearn.metrics import accuracy_score
14
+ import augmentation as aug
15
+ from minirocket import fit, transform
16
+
17
+
18
+ UCR_PATH = "" # Update this path for your environment
19
+
20
+
21
+ def run_augmentation(x, y, args):
22
+ """
23
+ Apply data augmentation to the input data based on args.
24
+
25
+ Parameters:
26
+ -----------
27
+ x : numpy.ndarray
28
+ Original time series data
29
+ y : numpy.ndarray
30
+ Original labels
31
+ args : argparse.Namespace
32
+ Command line arguments containing augmentation options
33
+
34
+ Returns:
35
+ --------
36
+ x_aug : numpy.ndarray
37
+ Augmented time series data
38
+ y_aug : numpy.ndarray
39
+ Augmented labels
40
+ augmentation_tags : str
41
+ String describing the applied augmentations
42
+ """
43
+ print("Augmenting data for dataset %s" % args.dataset)
44
+
45
+ np.random.seed(args.seed)
46
+ x_aug = x.copy()
47
+ y_aug = y.copy()
48
+ start_time = time.time()
49
+
50
+ augmentation_tags = ""
51
+
52
+ if args.augmentation_ratio > 0:
53
+ augmentation_tags = "%d" % args.augmentation_ratio
54
+ print(f"Original training size: {x.shape[0]} samples")
55
+
56
+ for n in range(args.augmentation_ratio):
57
+ x_temp, current_tags = augment(x, y, args)
58
+
59
+ x_temp = x_temp.astype(np.float32)
60
+ x_aug = np.vstack((x_aug, x_temp))
61
+ y_aug = np.append(y_aug, y)
62
+
63
+ print(f"Round {n+1}: {current_tags} done - Added {x_temp.shape[0]} samples")
64
+
65
+ if n == 0:
66
+ augmentation_tags += current_tags
67
+
68
+ print(f"Augmented training size: {x_aug.shape[0]} samples")
69
+ print(f"Augmented data type: {x_aug.dtype}")
70
+
71
+ if args.extra_tag:
72
+ augmentation_tags += "_" + args.extra_tag
73
+ else:
74
+ augmentation_tags = "none"
75
+ if args.extra_tag:
76
+ augmentation_tags = args.extra_tag
77
+
78
+ x_aug = x_aug.astype(np.float32)
79
+
80
+ time_dif = time.time() - start_time
81
+ with open("augmentation_time.txt", "a") as f:
82
+ f.write(f"Dataset: {args.dataset}, Augmentation tags: {augmentation_tags}, Time taken: {time_dif:.2f} seconds\n")
83
+ print(f"Data augmentation completed in {time_dif:.2f} seconds")
84
+
85
+ return x_aug, y_aug, augmentation_tags
86
+
87
+
88
+ def augment(x, y, args):
89
+ """
90
+ Apply specified augmentations to the data.
91
+
92
+ Parameters:
93
+ -----------
94
+ x : numpy.ndarray
95
+ Original time series data
96
+ y : numpy.ndarray
97
+ Original labels
98
+ args : argparse.Namespace
99
+ Command line arguments containing augmentation options
100
+
101
+ Returns:
102
+ --------
103
+ x : numpy.ndarray
104
+ Augmented time series data
105
+ augmentation_tags : str
106
+ String describing the applied augmentations
107
+ """
108
+ augmentation_tags = ""
109
+
110
+ x_aug = x.copy()
111
+
112
+ needs_reshape = False
113
+ original_shape = x_aug.shape
114
+
115
+ if len(x_aug.shape) == 2:
116
+ # Reshape from (n_samples, timesteps) to (n_samples, timesteps, 1)
117
+ x_aug = x_aug.reshape(x_aug.shape[0], x_aug.shape[1], 1)
118
+ needs_reshape = True
119
+
120
+ if args.jitter:
121
+ x_aug = aug.jitter(x_aug)
122
+ augmentation_tags += "_jitter"
123
+
124
+ if args.tps:
125
+ x_aug = aug.tps(x_aug, y, args.patch_len, args.stride, args.shuffle_rate)
126
+ augmentation_tags += "_tps"
127
+
128
+ if args.scaling:
129
+ x_aug = aug.scaling(x_aug)
130
+ augmentation_tags += "_scaling"
131
+
132
+ if args.rotation:
133
+ x_aug = aug.rotation(x_aug)
134
+ augmentation_tags += "_rotation"
135
+
136
+ if args.permutation:
137
+ x_aug = aug.permutation(x_aug)
138
+ augmentation_tags += "_permutation"
139
+
140
+ if args.randompermutation:
141
+ x_aug = aug.permutation(x_aug, seg_mode="random")
142
+ augmentation_tags += "_randomperm"
143
+
144
+ if args.magwarp:
145
+ x_aug = aug.magnitude_warp(x_aug)
146
+ augmentation_tags += "_magwarp"
147
+
148
+ if args.timewarp:
149
+ x_aug = aug.time_warp(x_aug)
150
+ augmentation_tags += "_timewarp"
151
+
152
+ if args.windowslice:
153
+ x_aug = aug.window_slice(x_aug)
154
+ augmentation_tags += "_windowslice"
155
+
156
+ if args.windowwarp:
157
+ x_aug = aug.window_warp(x_aug)
158
+ augmentation_tags += "_windowwarp"
159
+
160
+ if args.spawner:
161
+ x_aug = aug.spawner(x_aug, y)
162
+ augmentation_tags += "_spawner"
163
+
164
+ if args.dtwwarp:
165
+ x_aug = aug.random_guided_warp(x_aug, y)
166
+ augmentation_tags += "_rgw"
167
+
168
+ if args.shapedtwwarp:
169
+ x_aug = aug.random_guided_warp_shape(x_aug, y)
170
+ augmentation_tags += "_rgws"
171
+
172
+ if args.wdba:
173
+ x_aug = aug.wdba(x_aug, y)
174
+ augmentation_tags += "_wdba"
175
+
176
+ if args.discdtw:
177
+ x_aug = aug.discriminative_guided_warp(x_aug, y)
178
+ augmentation_tags += "_dgw"
179
+
180
+ if args.discsdtw:
181
+ x_aug = aug.discriminative_guided_warp_shape(x_aug, y)
182
+ augmentation_tags += "_dgws"
183
+
184
+ if needs_reshape:
185
+ x_aug = x_aug.reshape(original_shape)
186
+
187
+ if not augmentation_tags:
188
+ augmentation_tags = "_none"
189
+
190
+ return x_aug.astype(np.float32), augmentation_tags
191
+
192
+
193
+ def load_ucr_dataset(dataset_name):
194
+ """
195
+ Load a UCR dataset from TSV files.
196
+
197
+ Parameters:
198
+ -----------
199
+ dataset_name : str
200
+ Name of the dataset (e.g., 'FordB')
201
+
202
+ Returns:
203
+ --------
204
+ X_train : numpy.ndarray
205
+ Training data (time series)
206
+ y_train : numpy.ndarray
207
+ Training labels
208
+ X_test : numpy.ndarray
209
+ Test data (time series)
210
+ y_test : numpy.ndarray
211
+ Test labels
212
+ """
213
+ # training data
214
+ train_file = os.path.join(UCR_PATH, dataset_name, f"{dataset_name}_TRAIN.tsv")
215
+ if not os.path.exists(train_file):
216
+ train_file = os.path.join(UCR_PATH, dataset_name, f"{dataset_name}_Train.tsv")
217
+
218
+ # testing data
219
+ test_file = os.path.join(UCR_PATH, dataset_name, f"{dataset_name}_TEST.tsv")
220
+ if not os.path.exists(test_file):
221
+ test_file = os.path.join(UCR_PATH, dataset_name, f"{dataset_name}_Test.tsv")
222
+
223
+ if not os.path.exists(train_file) or not os.path.exists(test_file):
224
+ raise FileNotFoundError(f"Dataset files for {dataset_name} not found: {train_file}, {test_file}")
225
+
226
+ print(f"Loading files: {train_file}, {test_file}")
227
+
228
+ # Load data
229
+ train_df = pd.read_csv(train_file, sep='\t', header=None)
230
+ test_df = pd.read_csv(test_file, sep='\t', header=None)
231
+
232
+ y_train = train_df.iloc[:, 0].values
233
+ X_train = train_df.iloc[:, 1:].values
234
+
235
+ y_test = test_df.iloc[:, 0].values
236
+ X_test = test_df.iloc[:, 1:].values
237
+
238
+ unique_train, counts_train = np.unique(y_train, return_counts=True)
239
+ unique_test, counts_test = np.unique(y_test, return_counts=True)
240
+
241
+ print(f"Train class distribution: {dict(zip(unique_train, counts_train))}")
242
+ print(f"Test class distribution: {dict(zip(unique_test, counts_test))}")
243
+
244
+ X_train = X_train.astype(np.float32)
245
+ X_test = X_test.astype(np.float32)
246
+
247
+ print(f"Data loaded successfully. Train shape: {X_train.shape}, Test shape: {X_test.shape}")
248
+ print(f"Train data type: {X_train.dtype}, Test data type: {X_test.dtype}")
249
+
250
+ return X_train, y_train, X_test, y_test
251
+
252
+ def run_minirocket_experiment(dataset_name, args):
253
+ """
254
+ Run MiniRocket on a UCR dataset with multiple iterations and optional augmentation.
255
+
256
+ Parameters:
257
+ -----------
258
+ dataset_name : str
259
+ Name of the dataset (e.g., 'FordB')
260
+ args : argparse.Namespace
261
+ Command line arguments containing options
262
+
263
+ Returns:
264
+ --------
265
+ mean_accuracy : float
266
+ Mean accuracy across iterations
267
+ std_accuracy : float
268
+ Standard deviation of accuracy across iterations
269
+ """
270
+
271
+ print(f"Loading dataset: {dataset_name}")
272
+ X_train, y_train, X_test, y_test = load_ucr_dataset(dataset_name)
273
+
274
+ # Apply augmentation
275
+ if args.use_augmentation:
276
+ X_train, y_train, augmentation_tags = run_augmentation(X_train, y_train, args)
277
+ else:
278
+ augmentation_tags = "none"
279
+
280
+ accuracies = []
281
+ runtimes = []
282
+
283
+ for iteration in range(args.iterations):
284
+ print(f"Running iteration {iteration+1}/{args.iterations}")
285
+
286
+ start_time = time.time()
287
+
288
+ np.random.seed(args.seed + iteration)
289
+
290
+ parameters = fit(X_train, num_features=args.features)
291
+ X_train_transform = transform(X_train, parameters)
292
+ classifier = RidgeClassifierCV(alphas=np.logspace(-3, 3, 10))
293
+ classifier.fit(X_train_transform, y_train)
294
+ X_test_transform = transform(X_test, parameters)
295
+ predictions = classifier.predict(X_test_transform)
296
+
297
+ accuracy = accuracy_score(y_test, predictions)
298
+ runtime = time.time() - start_time
299
+
300
+ accuracies.append(accuracy)
301
+ runtimes.append(runtime)
302
+
303
+ print(f"Iteration {iteration+1} - Accuracy: {accuracy:.4f}, Runtime: {runtime:.2f} seconds")
304
+
305
+
306
+ mean_accuracy = np.mean(accuracies)
307
+ std_accuracy = np.std(accuracies)
308
+ mean_runtime = np.mean(runtimes)
309
+
310
+ print(f"\nResults for {dataset_name} with augmentation: {augmentation_tags}")
311
+ print(f"Train size: {X_train.shape[0]} samples")
312
+ print(f"Mean Accuracy: {mean_accuracy:.4f} ± {std_accuracy:.4f}")
313
+ print(f"Mean Runtime: {mean_runtime:.2f} seconds")
314
+ print(f"Individual Accuracies: {accuracies}")
315
+
316
+ results_df = pd.DataFrame({
317
+ 'Dataset': [dataset_name],
318
+ 'Augmentation': [augmentation_tags],
319
+ 'Train_Size': [X_train.shape[0]],
320
+ 'Test_Size': [X_test.shape[0]],
321
+ 'Mean_Accuracy': [mean_accuracy],
322
+ 'Std_Dev': [std_accuracy],
323
+ 'Mean_Runtime': [mean_runtime],
324
+ 'Iterations': [args.iterations],
325
+ 'Features': [args.features],
326
+ 'Individual_Accuracies': [','.join(map(str, accuracies))],
327
+ 'patch_len': [args.patch_len],
328
+ 'stride': [args.stride],
329
+ 'shuffle_rate': [args.shuffle_rate],
330
+ })
331
+
332
+ results_filename = f"minirocket_results_{dataset_name}_{augmentation_tags}.csv"
333
+
334
+ if os.path.exists(results_filename):
335
+ existing_df = pd.read_csv(results_filename)
336
+ combined_df = pd.concat([existing_df, results_df], ignore_index=True)
337
+ combined_df.to_csv(results_filename, index=False)
338
+ print(f"Results appended to {results_filename}")
339
+ else:
340
+ results_df.to_csv(results_filename, index=False)
341
+ print(f"Results saved to new file {results_filename}")
342
+
343
+ return mean_accuracy, std_accuracy, runtimes, augmentation_tags
344
+
345
+ def run_all_datasets(args):
346
+ """
347
+ Run MiniRocket on all available datasets.
348
+
349
+ Parameters:
350
+ -----------
351
+ args : argparse.Namespace
352
+ Command line arguments containing options
353
+ """
354
+ # list of available datasets
355
+ datasets = [d for d in os.listdir(UCR_PATH) if os.path.isdir(os.path.join(UCR_PATH, d))]
356
+
357
+ if not datasets:
358
+ print(f"No datasets found in {UCR_PATH}")
359
+ return
360
+
361
+ print(f"Found {len(datasets)} datasets: {', '.join(datasets)}")
362
+
363
+ results = []
364
+
365
+ for dataset_name in datasets:
366
+ print(f"\n{'='*50}")
367
+ print(f"Processing dataset: {dataset_name}")
368
+ print(f"{'='*50}")
369
+
370
+ try:
371
+ mean_acc, std_acc, _, aug_tags = run_minirocket_experiment(dataset_name, args)
372
+
373
+ results.append({
374
+ 'Dataset': dataset_name,
375
+ 'Augmentation': aug_tags,
376
+ 'Mean_Accuracy': mean_acc,
377
+ 'Std_Dev': std_acc,
378
+ 'patch_len': args.patch_len,
379
+ 'stride': args.stride,
380
+ 'shuffle_rate': args.shuffle_rate
381
+ })
382
+
383
+ except Exception as e:
384
+ print(f"Error processing dataset {dataset_name}: {e}")
385
+
386
+
387
+ if results:
388
+ results_df = pd.DataFrame(results)
389
+ overall_mean = results_df['Mean_Accuracy'].mean()
390
+
391
+ print("\n" + "="*70)
392
+ print("SUMMARY OF RESULTS")
393
+ print("="*70)
394
+ print(f"{'Dataset':<25} {'Augmentation':<25} {'Mean Accuracy':<15} {'Std Dev':<10}")
395
+ print("-"*70)
396
+
397
+ for _, row in results_df.iterrows():
398
+ print(f"{row['Dataset']:<25} {row['Augmentation']:<25} {row['Mean_Accuracy']:.4f}{' '*8} {row['Std_Dev']:.4f}")
399
+
400
+ print("-"*70)
401
+ print(f"{'OVERALL':<25} {'':<25} {overall_mean:.4f}")
402
+ print("="*70)
403
+
404
+
405
+ aug_tag = "none" if not args.use_augmentation else "aug"
406
+ results_df.to_csv(f"minirocket_summary_results_{aug_tag}.csv", index=False)
407
+ print(f"\nSummary results saved to minirocket_summary_results_{aug_tag}.csv")
408
+
409
+ def list_ucr_datasets():
410
+ """List all available UCR datasets in the UCR_PATH directory"""
411
+ try:
412
+ datasets = [d for d in os.listdir(UCR_PATH) if os.path.isdir(os.path.join(UCR_PATH, d))]
413
+ return sorted(datasets)
414
+ except Exception as e:
415
+ print(f"Error listing datasets: {e}")
416
+ return []
417
+
418
+ if __name__ == "__main__":
419
+ parser = argparse.ArgumentParser(description='Run MiniRocket on UCR datasets with optional augmentation')
420
+
421
+ # Dataset selection
422
+ parser.add_argument('--dataset', type=str, help='Dataset name (default: FordB)')
423
+ parser.add_argument('--list', action='store_true', help='List available datasets')
424
+ parser.add_argument('--all', action='store_true', help='Run on all available datasets')
425
+
426
+ # MiniRocket parameters
427
+ parser.add_argument('--features', type=int, default=10000, help='Number of features (default: 10000)')
428
+ parser.add_argument('--iterations', type=int, default=5, help='Number of iterations (default: 5)')
429
+ parser.add_argument('--seed', type=int, default=42, help='Random seed (default: 42)')
430
+
431
+ # Augmentation control
432
+ parser.add_argument('--use-augmentation', action='store_true', help='Use data augmentation')
433
+ parser.add_argument('--augmentation-ratio', type=int, default=0,
434
+ help='Number of augmented copies to add (default: 0)')
435
+ parser.add_argument('--extra-tag', type=str, default='',
436
+ help='Extra tag to add to augmentation tags')
437
+
438
+ # Augmentation methods
439
+ parser.add_argument('--jitter', action='store_true', help='Apply jitter augmentation')
440
+ parser.add_argument('--scaling', action='store_true', help='Apply scaling augmentation')
441
+ parser.add_argument('--rotation', action='store_true', help='Apply rotation augmentation')
442
+ parser.add_argument('--permutation', action='store_true', help='Apply permutation augmentation')
443
+ parser.add_argument('--randompermutation', action='store_true', help='Apply random permutation augmentation')
444
+ parser.add_argument('--magwarp', action='store_true', help='Apply magnitude warp augmentation')
445
+ parser.add_argument('--timewarp', action='store_true', help='Apply time warp augmentation')
446
+ parser.add_argument('--windowslice', action='store_true', help='Apply window slice augmentation')
447
+ parser.add_argument('--windowwarp', action='store_true', help='Apply window warp augmentation')
448
+ parser.add_argument('--spawner', action='store_true', help='Apply spawner augmentation')
449
+ parser.add_argument('--dtwwarp', action='store_true', help='Apply DTW-based warp augmentation')
450
+ parser.add_argument('--shapedtwwarp', action='store_true', help='Apply shape DTW warp augmentation')
451
+ parser.add_argument('--wdba', action='store_true', help='Apply WDBA augmentation')
452
+ parser.add_argument('--discdtw', action='store_true', help='Apply discriminative DTW augmentation')
453
+ parser.add_argument('--discsdtw', action='store_true', help='Apply discriminative shape DTW augmentation')
454
+ parser.add_argument('--tps', action='store_true', help='Apply TPS augmentation')
455
+
456
+ parser.add_argument('--stride', type=int, default=0, help='# of patches stride')
457
+ parser.add_argument('--patch_len', type=int, default=0, help='# of patches')
458
+ parser.add_argument('--shuffle_rate', type=float, default=0.0, help='shuffle rate')
459
+
460
+
461
+
462
+ args = parser.parse_args()
463
+
464
+
465
+ if args.list:
466
+ datasets = list_ucr_datasets()
467
+ if datasets:
468
+ print("Available datasets:")
469
+ for dataset in datasets:
470
+ print(f" - {dataset}")
471
+ else:
472
+ print("No datasets found or UCR_PATH is incorrect.")
473
+ sys.exit(0)
474
+
475
+ # Run on all datasets
476
+ if args.all:
477
+ print(f"Running MiniRocket on all available datasets")
478
+ print(f"Using {args.features} features and {args.iterations} iterations")
479
+ if args.use_augmentation:
480
+ print(f"Using data augmentation with ratio {args.augmentation_ratio}")
481
+ run_all_datasets(args)
482
+ sys.exit(0)
483
+
484
+ # Run on specific dataset
485
+ dataset_name = args.dataset if args.dataset else "FordB"
486
+ print(f"Running MiniRocket on {dataset_name} dataset")
487
+ print(f"Using {args.features} features and {args.iterations} iterations")
488
+ if args.use_augmentation:
489
+ print(f"Using data augmentation with ratio {args.augmentation_ratio}")
490
+
491
+ run_minirocket_experiment(dataset_name, args)
time_series_classification/minirocket/src/minirocket.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Angus Dempster, Daniel F Schmidt, Geoffrey I Webb
2
+
3
+ # MiniRocket: A Very Fast (Almost) Deterministic Transform for Time Series
4
+ # Classification
5
+
6
+ # https://arxiv.org/abs/2012.08791
7
+
8
+ from numba import njit, prange, vectorize
9
+ import numpy as np
10
+
11
+ @njit("float32[:](float32[:,:],int32[:],int32[:],float32[:])", fastmath = True, parallel = False, cache = True)
12
+ def _fit_biases(X, dilations, num_features_per_dilation, quantiles):
13
+
14
+ num_examples, input_length = X.shape
15
+
16
+ # equivalent to:
17
+ # >>> from itertools import combinations
18
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
19
+ indices = np.array((
20
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
21
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
22
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
23
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
24
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
25
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
26
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
27
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
28
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
29
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
30
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
31
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
32
+ ), dtype = np.int32).reshape(84, 3)
33
+
34
+ num_kernels = len(indices)
35
+ num_dilations = len(dilations)
36
+
37
+ num_features = num_kernels * np.sum(num_features_per_dilation)
38
+
39
+ biases = np.zeros(num_features, dtype = np.float32)
40
+
41
+ feature_index_start = 0
42
+
43
+ for dilation_index in range(num_dilations):
44
+
45
+ dilation = dilations[dilation_index]
46
+ padding = ((9 - 1) * dilation) // 2
47
+
48
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
49
+
50
+ for kernel_index in range(num_kernels):
51
+
52
+ feature_index_end = feature_index_start + num_features_this_dilation
53
+
54
+ _X = X[np.random.randint(num_examples)]
55
+
56
+ A = -_X # A = alpha * X = -X
57
+ G = _X + _X + _X # G = gamma * X = 3X
58
+
59
+ C_alpha = np.zeros(input_length, dtype = np.float32)
60
+ C_alpha[:] = A
61
+
62
+ C_gamma = np.zeros((9, input_length), dtype = np.float32)
63
+ C_gamma[9 // 2] = G
64
+
65
+ start = dilation
66
+ end = input_length - padding
67
+
68
+ for gamma_index in range(9 // 2):
69
+
70
+ C_alpha[-end:] = C_alpha[-end:] + A[:end]
71
+ C_gamma[gamma_index, -end:] = G[:end]
72
+
73
+ end += dilation
74
+
75
+ for gamma_index in range(9 // 2 + 1, 9):
76
+
77
+ C_alpha[:-start] = C_alpha[:-start] + A[start:]
78
+ C_gamma[gamma_index, :-start] = G[start:]
79
+
80
+ start += dilation
81
+
82
+ index_0, index_1, index_2 = indices[kernel_index]
83
+
84
+ C = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
85
+
86
+ biases[feature_index_start:feature_index_end] = np.quantile(C, quantiles[feature_index_start:feature_index_end])
87
+
88
+ feature_index_start = feature_index_end
89
+
90
+ return biases
91
+
92
+ def _fit_dilations(input_length, num_features, max_dilations_per_kernel):
93
+
94
+ num_kernels = 84
95
+
96
+ num_features_per_kernel = num_features // num_kernels
97
+ true_max_dilations_per_kernel = min(num_features_per_kernel, max_dilations_per_kernel)
98
+ multiplier = num_features_per_kernel / true_max_dilations_per_kernel
99
+
100
+ max_exponent = np.log2((input_length - 1) / (9 - 1))
101
+ dilations, num_features_per_dilation = \
102
+ np.unique(np.logspace(0, max_exponent, true_max_dilations_per_kernel, base = 2).astype(np.int32), return_counts = True)
103
+ num_features_per_dilation = (num_features_per_dilation * multiplier).astype(np.int32) # this is a vector
104
+
105
+ remainder = num_features_per_kernel - np.sum(num_features_per_dilation)
106
+ i = 0
107
+ while remainder > 0:
108
+ num_features_per_dilation[i] += 1
109
+ remainder -= 1
110
+ i = (i + 1) % len(num_features_per_dilation)
111
+
112
+ return dilations, num_features_per_dilation
113
+
114
+ # low-discrepancy sequence to assign quantiles to kernel/dilation combinations
115
+ def _quantiles(n):
116
+ return np.array([(_ * ((np.sqrt(5) + 1) / 2)) % 1 for _ in range(1, n + 1)], dtype = np.float32)
117
+
118
+ def fit(X, num_features = 10_000, max_dilations_per_kernel = 32):
119
+
120
+ _, input_length = X.shape
121
+
122
+ num_kernels = 84
123
+
124
+ dilations, num_features_per_dilation = _fit_dilations(input_length, num_features, max_dilations_per_kernel)
125
+
126
+ num_features_per_kernel = np.sum(num_features_per_dilation)
127
+
128
+ quantiles = _quantiles(num_kernels * num_features_per_kernel)
129
+
130
+ biases = _fit_biases(X, dilations, num_features_per_dilation, quantiles)
131
+
132
+ return dilations, num_features_per_dilation, biases
133
+
134
+ # _PPV(C, b).mean() returns PPV for vector C (convolution output) and scalar b (bias)
135
+ @vectorize("float32(float32,float32)", nopython = True, cache = True)
136
+ def _PPV(a, b):
137
+ if a > b:
138
+ return 1
139
+ else:
140
+ return 0
141
+
142
+ @njit("float32[:,:](float32[:,:],Tuple((int32[:],int32[:],float32[:])))", fastmath = True, parallel = True, cache = True)
143
+ def transform(X, parameters):
144
+
145
+ num_examples, input_length = X.shape
146
+
147
+ dilations, num_features_per_dilation, biases = parameters
148
+
149
+ # equivalent to:
150
+ # >>> from itertools import combinations
151
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
152
+ indices = np.array((
153
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
154
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
155
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
156
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
157
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
158
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
159
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
160
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
161
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
162
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
163
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
164
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
165
+ ), dtype = np.int32).reshape(84, 3)
166
+
167
+ num_kernels = len(indices)
168
+ num_dilations = len(dilations)
169
+
170
+ num_features = num_kernels * np.sum(num_features_per_dilation)
171
+
172
+ features = np.zeros((num_examples, num_features), dtype = np.float32)
173
+
174
+ for example_index in prange(num_examples):
175
+
176
+ _X = X[example_index]
177
+
178
+ A = -_X # A = alpha * X = -X
179
+ G = _X + _X + _X # G = gamma * X = 3X
180
+
181
+ feature_index_start = 0
182
+
183
+ for dilation_index in range(num_dilations):
184
+
185
+ _padding0 = dilation_index % 2
186
+
187
+ dilation = dilations[dilation_index]
188
+ padding = ((9 - 1) * dilation) // 2
189
+
190
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
191
+
192
+ C_alpha = np.zeros(input_length, dtype = np.float32)
193
+ C_alpha[:] = A
194
+
195
+ C_gamma = np.zeros((9, input_length), dtype = np.float32)
196
+ C_gamma[9 // 2] = G
197
+
198
+ start = dilation
199
+ end = input_length - padding
200
+
201
+ for gamma_index in range(9 // 2):
202
+
203
+ C_alpha[-end:] = C_alpha[-end:] + A[:end]
204
+ C_gamma[gamma_index, -end:] = G[:end]
205
+
206
+ end += dilation
207
+
208
+ for gamma_index in range(9 // 2 + 1, 9):
209
+
210
+ C_alpha[:-start] = C_alpha[:-start] + A[start:]
211
+ C_gamma[gamma_index, :-start] = G[start:]
212
+
213
+ start += dilation
214
+
215
+ for kernel_index in range(num_kernels):
216
+
217
+ feature_index_end = feature_index_start + num_features_this_dilation
218
+
219
+ _padding1 = (_padding0 + kernel_index) % 2
220
+
221
+ index_0, index_1, index_2 = indices[kernel_index]
222
+
223
+ C = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
224
+
225
+ if _padding1 == 0:
226
+ for feature_count in range(num_features_this_dilation):
227
+ features[example_index, feature_index_start + feature_count] = _PPV(C, biases[feature_index_start + feature_count]).mean()
228
+ else:
229
+ for feature_count in range(num_features_this_dilation):
230
+ features[example_index, feature_index_start + feature_count] = _PPV(C[padding:-padding], biases[feature_index_start + feature_count]).mean()
231
+
232
+ feature_index_start = feature_index_end
233
+
234
+ return features
time_series_classification/minirocket/src/minirocket_dv.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Angus Dempster, Daniel F Schmidt, Geoffrey I Webb
2
+
3
+ # MiniRocket: A Very Fast (Almost) Deterministic Transform for Time Series
4
+ # Classification
5
+
6
+ # https://arxiv.org/abs/2012.08791
7
+
8
+ from numba import njit
9
+ import numpy as np
10
+
11
+ from minirocket import _PPV, _fit_dilations, _quantiles
12
+
13
+ @njit("Tuple((float32[:],float32[:,:]))(float32[:,:],int32[:],int32[:],float32[:])", fastmath = True, parallel = False, cache = True)
14
+ def _fit_biases_transform(X, dilations, num_features_per_dilation, quantiles):
15
+
16
+ num_examples, input_length = X.shape
17
+
18
+ # equivalent to:
19
+ # >>> from itertools import combinations
20
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
21
+ indices = np.array((
22
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
23
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
24
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
25
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
26
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
27
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
28
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
29
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
30
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
31
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
32
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
33
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
34
+ ), dtype = np.int32).reshape(84, 3)
35
+
36
+ num_kernels = len(indices)
37
+ num_dilations = len(dilations)
38
+
39
+ num_features = num_kernels * np.sum(num_features_per_dilation)
40
+
41
+ biases = np.zeros(num_features, dtype = np.float32)
42
+
43
+ features = np.zeros((num_examples, num_features), dtype = np.float32)
44
+
45
+ feature_index_start = 0
46
+
47
+ for dilation_index in range(num_dilations):
48
+
49
+ _padding0 = dilation_index % 2
50
+
51
+ dilation = dilations[dilation_index]
52
+ padding = ((9 - 1) * dilation) // 2
53
+
54
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
55
+
56
+ for kernel_index in range(num_kernels):
57
+
58
+ feature_index_end = feature_index_start + num_features_this_dilation
59
+
60
+ _padding1 = (_padding0 + kernel_index) % 2
61
+
62
+ index_0, index_1, index_2 = indices[kernel_index]
63
+
64
+ C = np.zeros((num_examples, input_length), dtype = np.float32)
65
+
66
+ for example_index in range(num_examples):
67
+
68
+ _X = X[example_index]
69
+
70
+ A = -_X # A = alpha * X = -X
71
+ G = _X + _X + _X # G = gamma * X = 3X
72
+
73
+ C_alpha = np.zeros(input_length, dtype = np.float32)
74
+ C_alpha[:] = A
75
+
76
+ C_gamma = np.zeros((9, input_length), dtype = np.float32)
77
+ C_gamma[9 // 2] = G
78
+
79
+ start = dilation
80
+ end = input_length - padding
81
+
82
+ for gamma_index in range(9 // 2):
83
+
84
+ C_alpha[-end:] = C_alpha[-end:] + A[:end]
85
+ C_gamma[gamma_index, -end:] = G[:end]
86
+
87
+ end += dilation
88
+
89
+ for gamma_index in range(9 // 2 + 1, 9):
90
+
91
+ C_alpha[:-start] = C_alpha[:-start] + A[start:]
92
+ C_gamma[gamma_index, :-start] = G[start:]
93
+
94
+ start += dilation
95
+
96
+ C[example_index] = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
97
+
98
+ biases[feature_index_start:feature_index_end] = np.quantile(C, quantiles[feature_index_start:feature_index_end])
99
+
100
+ for example_index in range(num_examples):
101
+ if _padding1 == 0:
102
+ for feature_count in range(num_features_this_dilation):
103
+ features[example_index, feature_index_start + feature_count] = _PPV(C[example_index], biases[feature_index_start + feature_count]).mean()
104
+ else:
105
+ for feature_count in range(num_features_this_dilation):
106
+ features[example_index, feature_index_start + feature_count] = _PPV(C[example_index][padding:-padding], biases[feature_index_start + feature_count]).mean()
107
+
108
+ feature_index_start = feature_index_end
109
+
110
+ return biases, features
111
+
112
+ def fit_transform(X, num_features = 10_000, max_dilations_per_kernel = 32):
113
+
114
+ _, input_length = X.shape
115
+
116
+ num_kernels = 84
117
+
118
+ dilations, num_features_per_dilation = _fit_dilations(input_length, num_features, max_dilations_per_kernel)
119
+
120
+ num_features_per_kernel = np.sum(num_features_per_dilation)
121
+
122
+ quantiles = _quantiles(num_kernels * num_features_per_kernel)
123
+
124
+ biases, features = _fit_biases_transform(X, dilations, num_features_per_dilation, quantiles)
125
+
126
+ return (dilations, num_features_per_dilation, biases), features
time_series_classification/minirocket/src/minirocket_multivariate.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Angus Dempster, Daniel F Schmidt, Geoffrey I Webb
2
+
3
+ # MiniRocket: A Very Fast (Almost) Deterministic Transform for Time Series
4
+ # Classification
5
+
6
+ # https://arxiv.org/abs/2012.08791
7
+
8
+ # ** This is a naive extension of MiniRocket to multivariate time series. **
9
+
10
+ from numba import njit, prange, vectorize
11
+ import numpy as np
12
+
13
+ @njit("float32[:](float32[:,:,:],int32[:],int32[:],int32[:],int32[:],float32[:])", fastmath = True, parallel = False, cache = True)
14
+ def _fit_biases(X, num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, quantiles):
15
+
16
+ num_examples, num_channels, input_length = X.shape
17
+
18
+ # equivalent to:
19
+ # >>> from itertools import combinations
20
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
21
+ indices = np.array((
22
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
23
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
24
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
25
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
26
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
27
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
28
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
29
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
30
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
31
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
32
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
33
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
34
+ ), dtype = np.int32).reshape(84, 3)
35
+
36
+ num_kernels = len(indices)
37
+ num_dilations = len(dilations)
38
+
39
+ num_features = num_kernels * np.sum(num_features_per_dilation)
40
+
41
+ biases = np.zeros(num_features, dtype = np.float32)
42
+
43
+ feature_index_start = 0
44
+
45
+ combination_index = 0
46
+ num_channels_start = 0
47
+
48
+ for dilation_index in range(num_dilations):
49
+
50
+ dilation = dilations[dilation_index]
51
+ padding = ((9 - 1) * dilation) // 2
52
+
53
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
54
+
55
+ for kernel_index in range(num_kernels):
56
+
57
+ feature_index_end = feature_index_start + num_features_this_dilation
58
+
59
+ num_channels_this_combination = num_channels_per_combination[combination_index]
60
+
61
+ num_channels_end = num_channels_start + num_channels_this_combination
62
+
63
+ channels_this_combination = channel_indices[num_channels_start:num_channels_end]
64
+
65
+ _X = X[np.random.randint(num_examples)][channels_this_combination]
66
+
67
+ A = -_X # A = alpha * X = -X
68
+ G = _X + _X + _X # G = gamma * X = 3X
69
+
70
+ C_alpha = np.zeros((num_channels_this_combination, input_length), dtype = np.float32)
71
+ C_alpha[:] = A
72
+
73
+ C_gamma = np.zeros((9, num_channels_this_combination, input_length), dtype = np.float32)
74
+ C_gamma[9 // 2] = G
75
+
76
+ start = dilation
77
+ end = input_length - padding
78
+
79
+ for gamma_index in range(9 // 2):
80
+
81
+ C_alpha[:, -end:] = C_alpha[:, -end:] + A[:, :end]
82
+ C_gamma[gamma_index, :, -end:] = G[:, :end]
83
+
84
+ end += dilation
85
+
86
+ for gamma_index in range(9 // 2 + 1, 9):
87
+
88
+ C_alpha[:, :-start] = C_alpha[:, :-start] + A[:, start:]
89
+ C_gamma[gamma_index, :, :-start] = G[:, start:]
90
+
91
+ start += dilation
92
+
93
+ index_0, index_1, index_2 = indices[kernel_index]
94
+
95
+ C = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
96
+ C = np.sum(C, axis = 0)
97
+
98
+ biases[feature_index_start:feature_index_end] = np.quantile(C, quantiles[feature_index_start:feature_index_end])
99
+
100
+ feature_index_start = feature_index_end
101
+
102
+ combination_index += 1
103
+ num_channels_start = num_channels_end
104
+
105
+ return biases
106
+
107
+ def _fit_dilations(input_length, num_features, max_dilations_per_kernel):
108
+
109
+ num_kernels = 84
110
+
111
+ num_features_per_kernel = num_features // num_kernels
112
+ true_max_dilations_per_kernel = min(num_features_per_kernel, max_dilations_per_kernel)
113
+ multiplier = num_features_per_kernel / true_max_dilations_per_kernel
114
+
115
+ max_exponent = np.log2((input_length - 1) / (9 - 1))
116
+ dilations, num_features_per_dilation = \
117
+ np.unique(np.logspace(0, max_exponent, true_max_dilations_per_kernel, base = 2).astype(np.int32), return_counts = True)
118
+ num_features_per_dilation = (num_features_per_dilation * multiplier).astype(np.int32) # this is a vector
119
+
120
+ remainder = num_features_per_kernel - np.sum(num_features_per_dilation)
121
+ i = 0
122
+ while remainder > 0:
123
+ num_features_per_dilation[i] += 1
124
+ remainder -= 1
125
+ i = (i + 1) % len(num_features_per_dilation)
126
+
127
+ return dilations, num_features_per_dilation
128
+
129
+ # low-discrepancy sequence to assign quantiles to kernel/dilation combinations
130
+ def _quantiles(n):
131
+ return np.array([(_ * ((np.sqrt(5) + 1) / 2)) % 1 for _ in range(1, n + 1)], dtype = np.float32)
132
+
133
+ def fit(X, num_features = 10_000, max_dilations_per_kernel = 32):
134
+
135
+ _, num_channels, input_length = X.shape
136
+
137
+ num_kernels = 84
138
+
139
+ dilations, num_features_per_dilation = _fit_dilations(input_length, num_features, max_dilations_per_kernel)
140
+
141
+ num_features_per_kernel = np.sum(num_features_per_dilation)
142
+
143
+ quantiles = _quantiles(num_kernels * num_features_per_kernel)
144
+
145
+ num_dilations = len(dilations)
146
+ num_combinations = num_kernels * num_dilations
147
+
148
+ max_num_channels = min(num_channels, 9)
149
+ max_exponent = np.log2(max_num_channels + 1)
150
+
151
+ num_channels_per_combination = (2 ** np.random.uniform(0, max_exponent, num_combinations)).astype(np.int32)
152
+
153
+ channel_indices = np.zeros(num_channels_per_combination.sum(), dtype = np.int32)
154
+
155
+ num_channels_start = 0
156
+ for combination_index in range(num_combinations):
157
+ num_channels_this_combination = num_channels_per_combination[combination_index]
158
+ num_channels_end = num_channels_start + num_channels_this_combination
159
+ channel_indices[num_channels_start:num_channels_end] = np.random.choice(num_channels, num_channels_this_combination, replace = False)
160
+
161
+ num_channels_start = num_channels_end
162
+
163
+ biases = _fit_biases(X, num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, quantiles)
164
+
165
+ return num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, biases
166
+
167
+ # _PPV(C, b).mean() returns PPV for vector C (convolution output) and scalar b (bias)
168
+ @vectorize("float32(float32,float32)", nopython = True, cache = True)
169
+ def _PPV(a, b):
170
+ if a > b:
171
+ return 1
172
+ else:
173
+ return 0
174
+
175
+ @njit("float32[:,:](float32[:,:,:],Tuple((int32[:],int32[:],int32[:],int32[:],float32[:])))", fastmath = True, parallel = True, cache = True)
176
+ def transform(X, parameters):
177
+
178
+ num_examples, num_channels, input_length = X.shape
179
+
180
+ num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, biases = parameters
181
+
182
+ # equivalent to:
183
+ # >>> from itertools import combinations
184
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
185
+ indices = np.array((
186
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
187
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
188
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
189
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
190
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
191
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
192
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
193
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
194
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
195
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
196
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
197
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
198
+ ), dtype = np.int32).reshape(84, 3)
199
+
200
+ num_kernels = len(indices)
201
+ num_dilations = len(dilations)
202
+
203
+ num_features = num_kernels * np.sum(num_features_per_dilation)
204
+
205
+ features = np.zeros((num_examples, num_features), dtype = np.float32)
206
+
207
+ for example_index in prange(num_examples):
208
+
209
+ _X = X[example_index]
210
+
211
+ A = -_X # A = alpha * X = -X
212
+ G = _X + _X + _X # G = gamma * X = 3X
213
+
214
+ feature_index_start = 0
215
+
216
+ combination_index = 0
217
+ num_channels_start = 0
218
+
219
+ for dilation_index in range(num_dilations):
220
+
221
+ _padding0 = dilation_index % 2
222
+
223
+ dilation = dilations[dilation_index]
224
+ padding = ((9 - 1) * dilation) // 2
225
+
226
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
227
+
228
+ C_alpha = np.zeros((num_channels, input_length), dtype = np.float32)
229
+ C_alpha[:] = A
230
+
231
+ C_gamma = np.zeros((9, num_channels, input_length), dtype = np.float32)
232
+ C_gamma[9 // 2] = G
233
+
234
+ start = dilation
235
+ end = input_length - padding
236
+
237
+ for gamma_index in range(9 // 2):
238
+
239
+ C_alpha[:, -end:] = C_alpha[:, -end:] + A[:, :end]
240
+ C_gamma[gamma_index, :, -end:] = G[:, :end]
241
+
242
+ end += dilation
243
+
244
+ for gamma_index in range(9 // 2 + 1, 9):
245
+
246
+ C_alpha[:, :-start] = C_alpha[:, :-start] + A[:, start:]
247
+ C_gamma[gamma_index, :, :-start] = G[:, start:]
248
+
249
+ start += dilation
250
+
251
+ for kernel_index in range(num_kernels):
252
+
253
+ feature_index_end = feature_index_start + num_features_this_dilation
254
+
255
+ num_channels_this_combination = num_channels_per_combination[combination_index]
256
+
257
+ num_channels_end = num_channels_start + num_channels_this_combination
258
+
259
+ channels_this_combination = channel_indices[num_channels_start:num_channels_end]
260
+
261
+ _padding1 = (_padding0 + kernel_index) % 2
262
+
263
+ index_0, index_1, index_2 = indices[kernel_index]
264
+
265
+ C = C_alpha[channels_this_combination] + \
266
+ C_gamma[index_0][channels_this_combination] + \
267
+ C_gamma[index_1][channels_this_combination] + \
268
+ C_gamma[index_2][channels_this_combination]
269
+ C = np.sum(C, axis = 0)
270
+
271
+ if _padding1 == 0:
272
+ for feature_count in range(num_features_this_dilation):
273
+ features[example_index, feature_index_start + feature_count] = _PPV(C, biases[feature_index_start + feature_count]).mean()
274
+ else:
275
+ for feature_count in range(num_features_this_dilation):
276
+ features[example_index, feature_index_start + feature_count] = _PPV(C[padding:-padding], biases[feature_index_start + feature_count]).mean()
277
+
278
+ feature_index_start = feature_index_end
279
+
280
+ combination_index += 1
281
+ num_channels_start = num_channels_end
282
+
283
+ return features
time_series_classification/minirocket/src/minirocket_multivariate_variable.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Angus Dempster, Daniel F Schmidt, Geoffrey I Webb
2
+
3
+ # MiniRocket: A Very Fast (Almost) Deterministic Transform for Time Series
4
+ # Classification
5
+
6
+ # https://arxiv.org/abs/2012.08791
7
+
8
+ # ** This is an experimental extension of MiniRocket to variable-length,
9
+ # multivariate input. It is untested, may contain errors, and may be
10
+ # inefficient in terms of both storage and computation. **
11
+
12
+ from numba import njit, prange, vectorize
13
+ import numpy as np
14
+
15
+ @njit("float32[:](float32[:,:],int32[:],int32[:],int32[:],int32[:],int32[:],float32[:])", fastmath = True, parallel = False, cache = True)
16
+ def _fit_biases(X, L, num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, quantiles):
17
+
18
+ num_examples = len(L)
19
+
20
+ num_channels, _ = X.shape
21
+
22
+ # equivalent to:
23
+ # >>> from itertools import combinations
24
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
25
+ indices = np.array((
26
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
27
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
28
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
29
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
30
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
31
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
32
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
33
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
34
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
35
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
36
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
37
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
38
+ ), dtype = np.int32).reshape(84, 3)
39
+
40
+ num_kernels = len(indices)
41
+ num_dilations = len(dilations)
42
+
43
+ num_features = num_kernels * np.sum(num_features_per_dilation)
44
+
45
+ biases = np.zeros(num_features, dtype = np.float32)
46
+
47
+ feature_index_start = 0
48
+
49
+ combination_index = 0
50
+ num_channels_start = 0
51
+
52
+ for dilation_index in range(num_dilations):
53
+
54
+ dilation = dilations[dilation_index]
55
+ padding = ((9 - 1) * dilation) // 2
56
+
57
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
58
+
59
+ for kernel_index in range(num_kernels):
60
+
61
+ feature_index_end = feature_index_start + num_features_this_dilation
62
+
63
+ num_channels_this_combination = num_channels_per_combination[combination_index]
64
+
65
+ num_channels_end = num_channels_start + num_channels_this_combination
66
+
67
+ channels_this_combination = channel_indices[num_channels_start:num_channels_end]
68
+
69
+ example_index = np.random.randint(num_examples)
70
+
71
+ input_length = np.int64(L[example_index])
72
+
73
+ b = np.sum(L[0:example_index + 1])
74
+ a = b - input_length
75
+
76
+ _X = X[channels_this_combination, a:b]
77
+
78
+ A = -_X # A = alpha * X = -X
79
+ G = _X + _X + _X # G = gamma * X = 3X
80
+
81
+ C_alpha = np.zeros((num_channels_this_combination, input_length), dtype = np.float32)
82
+ C_alpha[:] = A
83
+
84
+ C_gamma = np.zeros((9, num_channels_this_combination, input_length), dtype = np.float32)
85
+ C_gamma[9 // 2] = G
86
+
87
+ start = dilation
88
+ end = input_length - padding
89
+
90
+ for gamma_index in range(9 // 2):
91
+
92
+ # thanks to Murtaza Jafferji @murtazajafferji for suggesting this fix
93
+ if end > 0:
94
+
95
+ C_alpha[:, -end:] = C_alpha[:, -end:] + A[:, :end]
96
+ C_gamma[gamma_index, :, -end:] = G[:, :end]
97
+
98
+ end += dilation
99
+
100
+ for gamma_index in range(9 // 2 + 1, 9):
101
+
102
+ if start < input_length:
103
+
104
+ C_alpha[:, :-start] = C_alpha[:, :-start] + A[:, start:]
105
+ C_gamma[gamma_index, :, :-start] = G[:, start:]
106
+
107
+ start += dilation
108
+
109
+ index_0, index_1, index_2 = indices[kernel_index]
110
+
111
+ C = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
112
+ C = np.sum(C, axis = 0)
113
+
114
+ biases[feature_index_start:feature_index_end] = np.quantile(C, quantiles[feature_index_start:feature_index_end])
115
+
116
+ feature_index_start = feature_index_end
117
+
118
+ combination_index += 1
119
+ num_channels_start = num_channels_end
120
+
121
+ return biases
122
+
123
+ def _fit_dilations(reference_length, num_features, max_dilations_per_kernel):
124
+
125
+ num_kernels = 84
126
+
127
+ num_features_per_kernel = num_features // num_kernels
128
+ true_max_dilations_per_kernel = min(num_features_per_kernel, max_dilations_per_kernel)
129
+ multiplier = num_features_per_kernel / true_max_dilations_per_kernel
130
+
131
+ max_exponent = np.log2((reference_length - 1) / (9 - 1))
132
+ dilations, num_features_per_dilation = \
133
+ np.unique(np.logspace(0, max_exponent, true_max_dilations_per_kernel, base = 2).astype(np.int32), return_counts = True)
134
+ num_features_per_dilation = (num_features_per_dilation * multiplier).astype(np.int32) # this is a vector
135
+
136
+ remainder = num_features_per_kernel - np.sum(num_features_per_dilation)
137
+ i = 0
138
+ while remainder > 0:
139
+ num_features_per_dilation[i] += 1
140
+ remainder -= 1
141
+ i = (i + 1) % len(num_features_per_dilation)
142
+
143
+ return dilations, num_features_per_dilation
144
+
145
+ # low-discrepancy sequence to assign quantiles to kernel/dilation combinations
146
+ def _quantiles(n):
147
+ return np.array([(_ * ((np.sqrt(5) + 1) / 2)) % 1 for _ in range(1, n + 1)], dtype = np.float32)
148
+
149
+ def fit(X, L, reference_length = None, num_features = 10_000, max_dilations_per_kernel = 32):
150
+
151
+ # note in relation to dilation:
152
+ # * change *reference_length* according to what is appropriate for your
153
+ # application, e.g., L.max(), L.mean(), np.median(L)
154
+ # * use fit(...) with an appropriate subset of time series, e.g., for
155
+ # reference_length = L.mean(), call fit(...) using only time series of at
156
+ # least length L.mean() [see filter_by_length(...)]
157
+ if reference_length == None:
158
+ reference_length = L.max()
159
+
160
+ num_channels, _ = X.shape
161
+
162
+ num_kernels = 84
163
+
164
+ dilations, num_features_per_dilation = _fit_dilations(reference_length, num_features, max_dilations_per_kernel)
165
+
166
+ num_features_per_kernel = np.sum(num_features_per_dilation)
167
+
168
+ quantiles = _quantiles(num_kernels * num_features_per_kernel)
169
+
170
+ num_dilations = len(dilations)
171
+ num_combinations = num_kernels * num_dilations
172
+
173
+ max_num_channels = min(num_channels, 9)
174
+ max_exponent = np.log2(max_num_channels + 1)
175
+
176
+ num_channels_per_combination = (2 ** np.random.uniform(0, max_exponent, num_combinations)).astype(np.int32)
177
+
178
+ channel_indices = np.zeros(num_channels_per_combination.sum(), dtype = np.int32)
179
+
180
+ num_channels_start = 0
181
+ for combination_index in range(num_combinations):
182
+ num_channels_this_combination = num_channels_per_combination[combination_index]
183
+ num_channels_end = num_channels_start + num_channels_this_combination
184
+ channel_indices[num_channels_start:num_channels_end] = np.random.choice(num_channels, num_channels_this_combination, replace = False)
185
+
186
+ num_channels_start = num_channels_end
187
+
188
+ biases = _fit_biases(X, L, num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, quantiles)
189
+
190
+ return num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, biases
191
+
192
+ # _PPV(C, b).mean() returns PPV for vector C (convolution output) and scalar b (bias)
193
+ @vectorize("float32(float32,float32)", nopython = True, cache = True)
194
+ def _PPV(a, b):
195
+ if a > b:
196
+ return 1
197
+ else:
198
+ return 0
199
+
200
+ @njit("float32[:,:](float32[:,:],int32[:],Tuple((int32[:],int32[:],int32[:],int32[:],float32[:])))", fastmath = True, parallel = True, cache = True)
201
+ def transform(X, L, parameters):
202
+
203
+ num_examples = len(L)
204
+
205
+ num_channels, _ = X.shape
206
+
207
+ num_channels_per_combination, channel_indices, dilations, num_features_per_dilation, biases = parameters
208
+
209
+ # equivalent to:
210
+ # >>> from itertools import combinations
211
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
212
+ indices = np.array((
213
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
214
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
215
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
216
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
217
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
218
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
219
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
220
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
221
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
222
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
223
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
224
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
225
+ ), dtype = np.int32).reshape(84, 3)
226
+
227
+ num_kernels = len(indices)
228
+ num_dilations = len(dilations)
229
+
230
+ num_features = num_kernels * np.sum(num_features_per_dilation)
231
+
232
+ features = np.zeros((num_examples, num_features), dtype = np.float32)
233
+
234
+ for example_index in prange(num_examples):
235
+
236
+ input_length = np.int64(L[example_index])
237
+
238
+ b = np.sum(L[0:example_index + 1])
239
+ a = b - input_length
240
+
241
+ _X = X[:, a:b]
242
+
243
+ A = -_X # A = alpha * X = -X
244
+ G = _X + _X + _X # G = gamma * X = 3X
245
+
246
+ feature_index_start = 0
247
+
248
+ combination_index = 0
249
+ num_channels_start = 0
250
+
251
+ for dilation_index in range(num_dilations):
252
+
253
+ dilation = dilations[dilation_index]
254
+ padding = ((9 - 1) * dilation) // 2
255
+
256
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
257
+
258
+ C_alpha = np.zeros((num_channels, input_length), dtype = np.float32)
259
+ C_alpha[:] = A
260
+
261
+ C_gamma = np.zeros((9, num_channels, input_length), dtype = np.float32)
262
+ C_gamma[9 // 2] = G
263
+
264
+ start = dilation
265
+ end = input_length - padding
266
+
267
+ for gamma_index in range(9 // 2):
268
+
269
+ # thanks to Murtaza Jafferji @murtazajafferji for suggesting this fix
270
+ if end > 0:
271
+
272
+ C_alpha[:, -end:] = C_alpha[:, -end:] + A[:, :end]
273
+ C_gamma[gamma_index, :, -end:] = G[:, :end]
274
+
275
+ end += dilation
276
+
277
+ for gamma_index in range(9 // 2 + 1, 9):
278
+
279
+ if start < input_length:
280
+
281
+ C_alpha[:, :-start] = C_alpha[:, :-start] + A[:, start:]
282
+ C_gamma[gamma_index, :, :-start] = G[:, start:]
283
+
284
+ start += dilation
285
+
286
+ for kernel_index in range(num_kernels):
287
+
288
+ feature_index_end = feature_index_start + num_features_this_dilation
289
+
290
+ num_channels_this_combination = num_channels_per_combination[combination_index]
291
+
292
+ num_channels_end = num_channels_start + num_channels_this_combination
293
+
294
+ channels_this_combination = channel_indices[num_channels_start:num_channels_end]
295
+
296
+ index_0, index_1, index_2 = indices[kernel_index]
297
+
298
+ C = C_alpha[channels_this_combination] + \
299
+ C_gamma[index_0][channels_this_combination] + \
300
+ C_gamma[index_1][channels_this_combination] + \
301
+ C_gamma[index_2][channels_this_combination]
302
+ C = np.sum(C, axis = 0)
303
+
304
+ for feature_count in range(num_features_this_dilation):
305
+ features[example_index, feature_index_start + feature_count] = _PPV(C, biases[feature_index_start + feature_count]).mean()
306
+
307
+ feature_index_start = feature_index_end
308
+
309
+ combination_index += 1
310
+ num_channels_start = num_channels_end
311
+
312
+ return features
time_series_classification/minirocket/src/minirocket_variable.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Angus Dempster, Daniel F Schmidt, Geoffrey I Webb
2
+
3
+ # MiniRocket: A Very Fast (Almost) Deterministic Transform for Time Series
4
+ # Classification
5
+
6
+ # https://arxiv.org/abs/2012.08791
7
+
8
+ # ** This is an experimental extension of MiniRocket to variable-length input.
9
+ # It is untested, may contain errors, and may be inefficient in terms of both
10
+ # storage and computation. **
11
+
12
+ from numba import njit, prange, vectorize
13
+ import numpy as np
14
+
15
+ @njit("float32[:](float32[:],int32[:],int32[:],int32[:],float32[:])", fastmath = True, parallel = False, cache = True)
16
+ def _fit_biases(X, L, dilations, num_features_per_dilation, quantiles):
17
+
18
+ num_examples = len(L)
19
+
20
+ # equivalent to:
21
+ # >>> from itertools import combinations
22
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
23
+ indices = np.array((
24
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
25
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
26
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
27
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
28
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
29
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
30
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
31
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
32
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
33
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
34
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
35
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
36
+ ), dtype = np.int32).reshape(84, 3)
37
+
38
+ num_kernels = len(indices)
39
+ num_dilations = len(dilations)
40
+
41
+ num_features = num_kernels * np.sum(num_features_per_dilation)
42
+
43
+ biases = np.zeros(num_features, dtype = np.float32)
44
+
45
+ feature_index_start = 0
46
+
47
+ for dilation_index in range(num_dilations):
48
+
49
+ dilation = dilations[dilation_index]
50
+ padding = ((9 - 1) * dilation) // 2
51
+
52
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
53
+
54
+ for kernel_index in range(num_kernels):
55
+
56
+ feature_index_end = feature_index_start + num_features_this_dilation
57
+
58
+ example_index = np.random.randint(num_examples)
59
+
60
+ input_length = np.int64(L[example_index])
61
+
62
+ b = np.sum(L[0:example_index + 1])
63
+ a = b - input_length
64
+
65
+ _X = X[a:b]
66
+
67
+ A = -_X # A = alpha * X = -X
68
+ G = _X + _X + _X # G = gamma * X = 3X
69
+
70
+ C_alpha = np.zeros(input_length, dtype = np.float32)
71
+ C_alpha[:] = A
72
+
73
+ C_gamma = np.zeros((9, input_length), dtype = np.float32)
74
+ C_gamma[9 // 2] = G
75
+
76
+ start = dilation
77
+ end = input_length - padding
78
+
79
+ for gamma_index in range(9 // 2):
80
+
81
+ # thanks to Murtaza Jafferji @murtazajafferji for suggesting this fix
82
+ if end > 0:
83
+
84
+ C_alpha[-end:] = C_alpha[-end:] + A[:end]
85
+ C_gamma[gamma_index, -end:] = G[:end]
86
+
87
+ end += dilation
88
+
89
+ for gamma_index in range(9 // 2 + 1, 9):
90
+
91
+ if start < input_length:
92
+
93
+ C_alpha[:-start] = C_alpha[:-start] + A[start:]
94
+ C_gamma[gamma_index, :-start] = G[start:]
95
+
96
+ start += dilation
97
+
98
+ index_0, index_1, index_2 = indices[kernel_index]
99
+
100
+ C = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
101
+
102
+ biases[feature_index_start:feature_index_end] = np.quantile(C, quantiles[feature_index_start:feature_index_end])
103
+
104
+ feature_index_start = feature_index_end
105
+
106
+ return biases
107
+
108
+ def _fit_dilations(reference_length, num_features, max_dilations_per_kernel):
109
+
110
+ num_kernels = 84
111
+
112
+ num_features_per_kernel = num_features // num_kernels
113
+ true_max_dilations_per_kernel = min(num_features_per_kernel, max_dilations_per_kernel)
114
+ multiplier = num_features_per_kernel / true_max_dilations_per_kernel
115
+
116
+ max_exponent = np.log2((reference_length - 1) / (9 - 1))
117
+ dilations, num_features_per_dilation = \
118
+ np.unique(np.logspace(0, max_exponent, true_max_dilations_per_kernel, base = 2).astype(np.int32), return_counts = True)
119
+ num_features_per_dilation = (num_features_per_dilation * multiplier).astype(np.int32) # this is a vector
120
+
121
+ remainder = num_features_per_kernel - np.sum(num_features_per_dilation)
122
+ i = 0
123
+ while remainder > 0:
124
+ num_features_per_dilation[i] += 1
125
+ remainder -= 1
126
+ i = (i + 1) % len(num_features_per_dilation)
127
+
128
+ return dilations, num_features_per_dilation
129
+
130
+ # low-discrepancy sequence to assign quantiles to kernel/dilation combinations
131
+ def _quantiles(n):
132
+ return np.array([(_ * ((np.sqrt(5) + 1) / 2)) % 1 for _ in range(1, n + 1)], dtype = np.float32)
133
+
134
+ def fit(X, L, reference_length = None, num_features = 10_000, max_dilations_per_kernel = 32):
135
+
136
+ # note in relation to dilation:
137
+ # * change *reference_length* according to what is appropriate for your
138
+ # application, e.g., L.max(), L.mean(), np.median(L)
139
+ # * use fit(...) with an appropriate subset of time series, e.g., for
140
+ # reference_length = L.mean(), call fit(...) using only time series of at
141
+ # least length L.mean() [see filter_by_length(...)]
142
+ if reference_length == None:
143
+ reference_length = L.max()
144
+
145
+ num_kernels = 84
146
+
147
+ dilations, num_features_per_dilation = _fit_dilations(reference_length, num_features, max_dilations_per_kernel)
148
+
149
+ num_features_per_kernel = np.sum(num_features_per_dilation)
150
+
151
+ quantiles = _quantiles(num_kernels * num_features_per_kernel)
152
+
153
+ biases = _fit_biases(X, L, dilations, num_features_per_dilation, quantiles)
154
+
155
+ return dilations, num_features_per_dilation, biases
156
+
157
+ # _PPV(C, b).mean() returns PPV for vector C (convolution output) and scalar b (bias)
158
+ @vectorize("float32(float32,float32)", nopython = True, cache = True)
159
+ def _PPV(a, b):
160
+ if a > b:
161
+ return 1
162
+ else:
163
+ return 0
164
+
165
+ @njit("float32[:,:](float32[:],int32[:],Tuple((int32[:],int32[:],float32[:])))", fastmath = True, parallel = True, cache = True)
166
+ def transform(X, L, parameters):
167
+
168
+ num_examples = len(L)
169
+
170
+ dilations, num_features_per_dilation, biases = parameters
171
+
172
+ # equivalent to:
173
+ # >>> from itertools import combinations
174
+ # >>> indices = np.array([_ for _ in combinations(np.arange(9), 3)], dtype = np.int32)
175
+ indices = np.array((
176
+ 0,1,2,0,1,3,0,1,4,0,1,5,0,1,6,0,1,7,0,1,8,
177
+ 0,2,3,0,2,4,0,2,5,0,2,6,0,2,7,0,2,8,0,3,4,
178
+ 0,3,5,0,3,6,0,3,7,0,3,8,0,4,5,0,4,6,0,4,7,
179
+ 0,4,8,0,5,6,0,5,7,0,5,8,0,6,7,0,6,8,0,7,8,
180
+ 1,2,3,1,2,4,1,2,5,1,2,6,1,2,7,1,2,8,1,3,4,
181
+ 1,3,5,1,3,6,1,3,7,1,3,8,1,4,5,1,4,6,1,4,7,
182
+ 1,4,8,1,5,6,1,5,7,1,5,8,1,6,7,1,6,8,1,7,8,
183
+ 2,3,4,2,3,5,2,3,6,2,3,7,2,3,8,2,4,5,2,4,6,
184
+ 2,4,7,2,4,8,2,5,6,2,5,7,2,5,8,2,6,7,2,6,8,
185
+ 2,7,8,3,4,5,3,4,6,3,4,7,3,4,8,3,5,6,3,5,7,
186
+ 3,5,8,3,6,7,3,6,8,3,7,8,4,5,6,4,5,7,4,5,8,
187
+ 4,6,7,4,6,8,4,7,8,5,6,7,5,6,8,5,7,8,6,7,8
188
+ ), dtype = np.int32).reshape(84, 3)
189
+
190
+ num_kernels = len(indices)
191
+ num_dilations = len(dilations)
192
+
193
+ num_features = num_kernels * np.sum(num_features_per_dilation)
194
+
195
+ features = np.zeros((num_examples, num_features), dtype = np.float32)
196
+
197
+ for example_index in prange(num_examples):
198
+
199
+ input_length = np.int64(L[example_index])
200
+
201
+ b = np.sum(L[0:example_index + 1])
202
+ a = b - input_length
203
+
204
+ _X = X[a:b]
205
+
206
+ A = -_X # A = alpha * X = -X
207
+ G = _X + _X + _X # G = gamma * X = 3X
208
+
209
+ feature_index_start = 0
210
+
211
+ for dilation_index in range(num_dilations):
212
+
213
+ _padding0 = dilation_index % 2
214
+
215
+ dilation = dilations[dilation_index]
216
+ padding = ((9 - 1) * dilation) // 2
217
+
218
+ num_features_this_dilation = num_features_per_dilation[dilation_index]
219
+
220
+ C_alpha = np.zeros(input_length, dtype = np.float32)
221
+ C_alpha[:] = A
222
+
223
+ C_gamma = np.zeros((9, input_length), dtype = np.float32)
224
+ C_gamma[9 // 2] = G
225
+
226
+ start = dilation
227
+ end = input_length - padding
228
+
229
+ for gamma_index in range(9 // 2):
230
+
231
+ # thanks to Murtaza Jafferji @murtazajafferji for suggesting this fix
232
+ if end > 0:
233
+
234
+ C_alpha[-end:] = C_alpha[-end:] + A[:end]
235
+ C_gamma[gamma_index, -end:] = G[:end]
236
+
237
+ end += dilation
238
+
239
+ for gamma_index in range(9 // 2 + 1, 9):
240
+
241
+ if start < input_length:
242
+
243
+ C_alpha[:-start] = C_alpha[:-start] + A[start:]
244
+ C_gamma[gamma_index, :-start] = G[start:]
245
+
246
+ start += dilation
247
+
248
+ for kernel_index in range(num_kernels):
249
+
250
+ feature_index_end = feature_index_start + num_features_this_dilation
251
+
252
+ # force padding
253
+ # alternatively, pass output through np.nan_to_num(...)
254
+ # _padding1 = (_padding0 + kernel_index) % 2
255
+ _padding1 = 0
256
+
257
+ index_0, index_1, index_2 = indices[kernel_index]
258
+
259
+ C = C_alpha + C_gamma[index_0] + C_gamma[index_1] + C_gamma[index_2]
260
+
261
+ if _padding1 == 0:
262
+ for feature_count in range(num_features_this_dilation):
263
+ features[example_index, feature_index_start + feature_count] = _PPV(C, biases[feature_index_start + feature_count]).mean()
264
+ else:
265
+ for feature_count in range(num_features_this_dilation):
266
+ features[example_index, feature_index_start + feature_count] = _PPV(C[padding:-padding], biases[feature_index_start + feature_count]).mean()
267
+
268
+ feature_index_start = feature_index_end
269
+
270
+ return features
271
+
272
+ # return only time series of at least *min_length*
273
+ def filter_by_length(X, L, min_length = 0):
274
+
275
+ _L = L[L >= min_length]
276
+ _X = np.zeros(_L.sum(), dtype = np.float32)
277
+
278
+ count = 0
279
+
280
+ for example_index in range(len(L)):
281
+
282
+ if L[example_index] >= min_length:
283
+
284
+ # indices for L
285
+ b = L[0:example_index + 1].sum()
286
+ a = b - L[example_index]
287
+
288
+ # indices for _L
289
+ _b = _L[0:count + 1].sum()
290
+ _a = _b - _L[count]
291
+
292
+ _X[_a:_b] = X[a:b]
293
+
294
+ count += 1
295
+
296
+ return _X, _L
time_series_classification/minirocket/src/softmax.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Angus Dempster, Daniel F Schmidt, Geoffrey I Webb
2
+
3
+ # MiniRocket: A Very Fast (Almost) Deterministic Transform for Time Series
4
+ # Classification
5
+
6
+ # https://arxiv.org/abs/2012.08791
7
+
8
+ import copy
9
+ import numpy as np
10
+ import pandas as pd
11
+ import torch, torch.nn as nn, torch.optim as optim
12
+
13
+ from minirocket import fit, transform
14
+
15
+ def train(path, num_classes, training_size, **kwargs):
16
+
17
+ # -- init ------------------------------------------------------------------
18
+
19
+ # default hyperparameters are reusable for any dataset
20
+ args = \
21
+ {
22
+ "num_features" : 10_000,
23
+ "validation_size" : 2 ** 11,
24
+ "chunk_size" : 2 ** 12,
25
+ "minibatch_size" : 256,
26
+ "lr" : 1e-4,
27
+ "max_epochs" : 50,
28
+ "patience_lr" : 5, # 50 minibatches
29
+ "patience" : 10, # 100 minibatches
30
+ "cache_size" : training_size # set to 0 to prevent caching
31
+ }
32
+ args = {**args, **kwargs}
33
+
34
+ _num_features = 84 * (args["num_features"] // 84)
35
+ num_chunks = np.int32(np.ceil(training_size / args["chunk_size"]))
36
+
37
+ def init(layer):
38
+ if isinstance(layer, nn.Linear):
39
+ nn.init.constant_(layer.weight.data, 0)
40
+ nn.init.constant_(layer.bias.data, 0)
41
+
42
+ # -- cache -----------------------------------------------------------------
43
+
44
+ # cache as much as possible to avoid unecessarily repeating the transform
45
+ # consider caching to disk if appropriate, along the lines of numpy.memmap
46
+
47
+ cache_X = torch.zeros((args["cache_size"], _num_features))
48
+ cache_Y = torch.zeros(args["cache_size"], dtype = torch.long)
49
+ cache_count = 0
50
+ fully_cached = False
51
+
52
+ # -- model -----------------------------------------------------------------
53
+
54
+ model = nn.Sequential(nn.Linear(_num_features, num_classes))
55
+ loss_function = nn.CrossEntropyLoss()
56
+ optimizer = optim.Adam(model.parameters(), lr = args["lr"])
57
+ scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor = 0.5, min_lr = 1e-8, patience = args["patience_lr"])
58
+ model.apply(init)
59
+
60
+ # -- validation data -------------------------------------------------------
61
+
62
+ # gotcha: copy() is essential to avoid competition for memory access with read_csv(...)
63
+ validation_data = pd.read_csv(path,
64
+ header = None,
65
+ sep = ",",
66
+ nrows = args["validation_size"],
67
+ engine = "c").values.copy()
68
+ Y_validation, X_validation = torch.LongTensor(validation_data[:, 0]), validation_data[:, 1:].astype(np.float32)
69
+
70
+ # -- run -------------------------------------------------------------------
71
+
72
+ minibatch_count = 0
73
+ best_validation_loss = np.inf
74
+ stall_count = 0
75
+ stop = False
76
+
77
+ print("Training... (faster once caching is finished)")
78
+
79
+ for epoch in range(args["max_epochs"]):
80
+
81
+ if epoch > 0 and stop:
82
+ break
83
+
84
+ if not fully_cached:
85
+ file = pd.read_csv(path,
86
+ header = None,
87
+ sep = ",",
88
+ skiprows = args["validation_size"],
89
+ chunksize = args["chunk_size"],
90
+ engine = "c")
91
+
92
+ for chunk_index in range(num_chunks):
93
+
94
+ a = chunk_index * args["chunk_size"]
95
+ b = min(a + args["chunk_size"], training_size)
96
+ _b = b - a
97
+
98
+ if epoch > 0 and stop:
99
+ break
100
+
101
+ print(f"Epoch {epoch + 1}; Chunk = {chunk_index + 1}...".ljust(80, " "), end = "\r", flush = True)
102
+
103
+ # if not fully cached, read next file chunk
104
+ if not fully_cached:
105
+
106
+ # gotcha: copy() is essential to avoid competition for memory access with read_csv(...)
107
+ training_data = file.get_chunk().values[:_b].copy()
108
+ Y_training, X_training = torch.LongTensor(training_data[:, 0]), training_data[:, 1:].astype(np.float32)
109
+
110
+ if epoch == 0 and chunk_index == 0:
111
+
112
+ parameters = fit(X_training, args["num_features"])
113
+
114
+ # transform validation data
115
+ X_validation_transform = transform(X_validation, parameters)
116
+
117
+ # if cached, retrieve from cache
118
+ if b <= cache_count:
119
+
120
+ X_training_transform = cache_X[a:b]
121
+ Y_training = cache_Y[a:b]
122
+
123
+ # else, transform and cache
124
+ else:
125
+
126
+ # transform training data
127
+ X_training_transform = transform(X_training, parameters)
128
+
129
+ if epoch == 0 and chunk_index == 0:
130
+
131
+ # per-feature mean and standard deviation
132
+ f_mean = X_training_transform.mean(0)
133
+ f_std = X_training_transform.std(0) + 1e-8
134
+
135
+ # normalise validation features
136
+ X_validation_transform = (X_validation_transform - f_mean) / f_std
137
+ X_validation_transform = torch.FloatTensor(X_validation_transform)
138
+
139
+ # normalise training features
140
+ X_training_transform = (X_training_transform - f_mean) / f_std
141
+ X_training_transform = torch.FloatTensor(X_training_transform)
142
+
143
+ # cache as much of the transform as possible
144
+ if b <= args["cache_size"]:
145
+ cache_X[a:b] = X_training_transform
146
+ cache_Y[a:b] = Y_training
147
+ cache_count = b
148
+
149
+ if cache_count >= training_size:
150
+ fully_cached = True
151
+
152
+ minibatches = torch.randperm(len(X_training_transform)).split(args["minibatch_size"])
153
+
154
+ # train on transformed features
155
+ for minibatch_index, minibatch in enumerate(minibatches):
156
+
157
+ if epoch > 0 and stop:
158
+ break
159
+
160
+ if minibatch_index > 0 and len(minibatch) < args["minibatch_size"]:
161
+ break
162
+
163
+ # -- training --------------------------------------------------
164
+
165
+ optimizer.zero_grad()
166
+ _Y_training = model(X_training_transform[minibatch])
167
+ training_loss = loss_function(_Y_training, Y_training[minibatch])
168
+ training_loss.backward()
169
+ optimizer.step()
170
+
171
+ minibatch_count += 1
172
+
173
+ if minibatch_count % 10 == 0:
174
+
175
+ _Y_validation = model(X_validation_transform)
176
+ validation_loss = loss_function(_Y_validation, Y_validation)
177
+
178
+ scheduler.step(validation_loss)
179
+
180
+ if validation_loss.item() >= best_validation_loss:
181
+ stall_count += 1
182
+ if stall_count >= args["patience"]:
183
+ stop = True
184
+ print(f"\n<Stopped at Epoch {epoch + 1}>")
185
+ else:
186
+ best_validation_loss = validation_loss.item()
187
+ best_model = copy.deepcopy(model)
188
+ if not stop:
189
+ stall_count = 0
190
+
191
+ return parameters, best_model, f_mean, f_std
192
+
193
+ def predict(path,
194
+ parameters,
195
+ model,
196
+ f_mean,
197
+ f_std,
198
+ **kwargs):
199
+
200
+ args = \
201
+ {
202
+ "score" : True,
203
+ "chunk_size" : 2 ** 12,
204
+ "test_size" : None
205
+ }
206
+ args = {**args, **kwargs}
207
+
208
+ file = pd.read_csv(path,
209
+ header = None,
210
+ sep = ",",
211
+ chunksize = args["chunk_size"],
212
+ nrows = args["test_size"],
213
+ engine = "c")
214
+
215
+ predictions = []
216
+
217
+ correct = 0
218
+ total = 0
219
+
220
+ for chunk_index, chunk in enumerate(file):
221
+
222
+ print(f"Chunk = {chunk_index + 1}...".ljust(80, " "), end = "\r")
223
+
224
+ # gotcha: copy() is essential to avoid competition for memory access with read_csv(...)
225
+ test_data = chunk.values.copy()
226
+ Y_test, X_test = test_data[:, 0], test_data[:, 1:].astype(np.float32)
227
+
228
+ X_test_transform = transform(X_test, parameters)
229
+ X_test_transform = (X_test_transform - f_mean) / f_std
230
+ X_test_transform = torch.FloatTensor(X_test_transform)
231
+
232
+ _predictions = model(X_test_transform).argmax(1).numpy()
233
+ predictions.append(_predictions)
234
+
235
+ total += len(test_data)
236
+ correct += (_predictions == Y_test).sum()
237
+
238
+ if args["score"]:
239
+ return np.concatenate(predictions), correct / total
240
+ else:
241
+ return np.concatenate(predictions)
time_series_forecasting/data_provider/data_factory.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from dominant-shuffle (https://github.com/zuojie2024/dominant-shuffle) and FrAug implementations
2
+ # Original works: Dominant Shuffle by Kai Zhao et al., FrAug by Muxi Chen et al.
3
+
4
+ from data_provider.data_loader import Dataset_ETT_hour, Dataset_ETT_minute, Dataset_Custom, Dataset_Pred, Dataset_PEMS
5
+ from torch.utils.data import DataLoader
6
+ import torch
7
+ #from data_provider.uea import collate_fn
8
+ data_dict = {
9
+ 'ETTh1': Dataset_ETT_hour,
10
+ 'ETTh2': Dataset_ETT_hour,
11
+ 'ETTm1': Dataset_ETT_minute,
12
+ 'ETTm2': Dataset_ETT_minute,
13
+ 'custom': Dataset_Custom,
14
+ 'PEMS': Dataset_PEMS,
15
+ }
16
+
17
+
18
+ def data_provider(args, flag):
19
+ Data = data_dict[args.data]
20
+ timeenc = 0 if args.embed != 'timeF' else 1
21
+
22
+ if flag == 'test':
23
+ shuffle_flag = False
24
+ drop_last = True
25
+ batch_size = args.batch_size
26
+ #if args.task_name == 'anomaly_detection' or args.task_name == 'classification':
27
+ # batch_size = args.batch_size
28
+ #else:
29
+ # batch_size = 1 # bsz=1 for evaluation
30
+ freq = args.freq
31
+ elif flag == 'pred':
32
+ shuffle_flag = False
33
+ drop_last = False
34
+ batch_size = 1
35
+ freq = args.freq
36
+ Data = Dataset_Pred
37
+ else:
38
+ shuffle_flag = True
39
+ drop_last = True
40
+ batch_size = args.batch_size
41
+ freq = args.freq
42
+
43
+
44
+ if args.task_name == 'classification':
45
+ drop_last = False
46
+ data_set = Data(
47
+ root_path=args.root_path,
48
+ flag=flag,
49
+ )
50
+
51
+ data_loader = DataLoader(
52
+ data_set,
53
+ batch_size=batch_size,
54
+ shuffle=shuffle_flag,
55
+ num_workers=args.num_workers, #
56
+ drop_last=drop_last,
57
+ collate_fn=lambda x: collate_fn(x, max_len=args.seq_len)
58
+ )
59
+ return data_set, data_loader
60
+ elif args.task_name == 'short_term_forecast':
61
+ drop_last = False
62
+ data_set = Data(
63
+ root_path=args.root_path,
64
+ data_path=args.data_path,
65
+ flag=flag,
66
+ size=[args.seq_len, args.label_len, args.pred_len],
67
+ features=args.features,
68
+ target=args.target,
69
+ timeenc=timeenc,
70
+ freq=freq,
71
+ seasonal_patterns=args.seasonal_patterns
72
+ )
73
+ print(flag, len(data_set))
74
+ data_loader = DataLoader(
75
+ data_set,
76
+ batch_size=batch_size,
77
+ shuffle=shuffle_flag,
78
+ num_workers=args.num_workers,
79
+ drop_last=drop_last)
80
+ return data_set, data_loader
81
+ else:
82
+ data_set = Data(
83
+ config=args,
84
+ root_path=args.root_path,
85
+ data_path=args.data_path,
86
+ flag=flag,
87
+ size=[args.seq_len, args.label_len, args.pred_len],
88
+ features=args.features,
89
+ target=args.target,
90
+ timeenc=timeenc,
91
+ freq=freq,
92
+ cycle=args.cycle
93
+ )
94
+ print(flag, len(data_set))
95
+ data_loader = DataLoader(
96
+ data_set,
97
+ batch_size=batch_size,
98
+ shuffle=shuffle_flag,
99
+ num_workers=args.num_workers,
100
+ drop_last=drop_last)
101
+ return data_set, data_loader
102
+
103
+ def collate_fn(batch, max_len):
104
+ # Assuming batch is a list of tuples (input, target) and input is a sequence
105
+ # Pad all sequences to the same length (max_len) with zeros
106
+ inputs, targets = zip(*batch)
107
+
108
+ # Pad sequences to max_len
109
+ padded_inputs = torch.nn.utils.rnn.pad_sequence(
110
+ [torch.tensor(seq) for seq in inputs],
111
+ batch_first=True, padding_value=0
112
+ )[:, :max_len] # Ensure padding to exactly max_len
113
+
114
+ # Similarly, process targets (adjust as necessary)
115
+ padded_targets = torch.nn.utils.rnn.pad_sequence(
116
+ [torch.tensor(target) for target in targets],
117
+ batch_first=True, padding_value=0
118
+ )[:, :max_len]
119
+
120
+ return padded_inputs, padded_targets
time_series_forecasting/data_provider/data_loader.py ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from dominant-shuffle (https://github.com/zuojie2024/dominant-shuffle) and FrAug implementations
2
+ # Original works: Dominant Shuffle by Kai Zhao et al., FrAug by Muxi Chen et al.
3
+ # Modified and extended by Jafar Bakhshaliyev (2025)
4
+
5
+
6
+
7
+ import os
8
+ import numpy as np
9
+ import pandas as pd
10
+ import os
11
+ import torch
12
+ from torch.utils.data import Dataset
13
+ from sklearn.preprocessing import StandardScaler
14
+ from utils.timefeatures import time_features
15
+ import warnings
16
+ #from data_provider.m4 import M4Dataset, M4Meta
17
+ from utils.augmentations import augmentation
18
+ #from data_provider.uea import subsample, interpolate_missing, Normalizer
19
+ #from sktime.datasets import load_from_tsfile_to_dataframe
20
+ #import glob
21
+ import re
22
+ warnings.filterwarnings('ignore')
23
+
24
+ class Dataset_ETT_hour(Dataset):
25
+ def __init__(self, config, root_path, flag='train', size=None,
26
+ features='S', data_path='ETTh1.csv',
27
+ target='OT', scale=True, timeenc=0, freq='h', cycle = None):
28
+ # size [seq_len, label_len, pred_len]
29
+ # info
30
+ self.args = config
31
+ if size == None:
32
+ self.seq_len = 24 * 4 * 4
33
+ self.label_len = 24 * 4
34
+ self.pred_len = 24 * 4
35
+ else:
36
+ self.seq_len = size[0]
37
+ self.label_len = size[1]
38
+ self.pred_len = size[2]
39
+ # init
40
+ assert flag in ['train', 'test', 'val']
41
+ type_map = {'train': 0, 'val': 1, 'test': 2}
42
+ self.set_type = type_map[flag]
43
+
44
+ self.features = features
45
+ self.target = target
46
+ self.scale = scale
47
+ self.timeenc = timeenc
48
+ self.freq = freq
49
+ self.cycle = cycle
50
+
51
+ self.root_path = root_path
52
+ self.data_path = data_path
53
+ self.__read_data__()
54
+ self.collect_all_data()
55
+ if self.args.in_dataset_augmentation and self.set_type==0:
56
+ self.data_augmentation()
57
+
58
+ def __read_data__(self):
59
+ self.scaler = StandardScaler()
60
+ df_raw = pd.read_csv(os.path.join(self.root_path,
61
+ self.data_path))
62
+
63
+ border1s = [0, 12 * 30 * 24 - self.seq_len, 12 * 30 * 24 + 4 * 30 * 24 - self.seq_len]
64
+ border2s = [12 * 30 * 24, 12 * 30 * 24 + 4 * 30 * 24, 12 * 30 * 24 + 8 * 30 * 24]
65
+
66
+ if self.args.test_time_train:
67
+ border1s = [0, 18 * 30 * 24 - self.seq_len, 20 * 30 * 24]
68
+ border2s = [18 * 30 * 24, 20 * 30 * 24, 20 * 30 * 24]
69
+
70
+ border1 = border1s[self.set_type]
71
+ border2 = border2s[self.set_type]
72
+
73
+ if self.features == 'M' or self.features == 'MS':
74
+ cols_data = df_raw.columns[1:]
75
+ df_data = df_raw[cols_data]
76
+ elif self.features == 'S':
77
+ df_data = df_raw[[self.target]]
78
+
79
+ if self.scale:
80
+ train_data = df_data[border1s[0]:border2s[0]]
81
+ self.scaler.fit(train_data.values)
82
+ data = self.scaler.transform(df_data.values)
83
+ else:
84
+ data = df_data.values
85
+
86
+ df_stamp = df_raw[['date']][border1:border2]
87
+ df_stamp['date'] = pd.to_datetime(df_stamp.date)
88
+ if self.timeenc == 0:
89
+ df_stamp['month'] = df_stamp.date.apply(lambda row: row.month, 1)
90
+ df_stamp['day'] = df_stamp.date.apply(lambda row: row.day, 1)
91
+ df_stamp['weekday'] = df_stamp.date.apply(lambda row: row.weekday(), 1)
92
+ df_stamp['hour'] = df_stamp.date.apply(lambda row: row.hour, 1)
93
+ data_stamp = df_stamp.drop(['date'], 1).values
94
+ elif self.timeenc == 1:
95
+ data_stamp = time_features(pd.to_datetime(df_stamp['date'].values), freq=self.freq)
96
+ data_stamp = data_stamp.transpose(1, 0)
97
+
98
+ self.data_x = data[border1:border2]
99
+ self.data_y = data[border1:border2]
100
+ self.data_stamp = data_stamp
101
+ self.cycle_index = (np.arange(len(data)) % self.cycle)[border1:border2]
102
+
103
+
104
+ def regenerate_augmentation_data(self):
105
+ self.collect_all_data()
106
+ self.data_augmentation()
107
+
108
+ def reload_data(self, x_data, y_data, x_time, y_time):
109
+ self.x_data = x_data
110
+ self.y_data = y_data
111
+ self.x_time = x_time
112
+ self.y_time = y_time
113
+
114
+ def collect_all_data(self):
115
+ self.x_data = []
116
+ self.y_data = []
117
+ self.x_time = []
118
+ self.y_time = []
119
+ self.s_end_list = []
120
+ data_len = len(self.data_x) - self.seq_len - self.pred_len + 1
121
+ mask_data_len = int((1-self.args.data_size) * data_len) if self.args.data_size < 1 else 0
122
+ for i in range(len(self.data_x) - self.seq_len - self.pred_len + 1):
123
+ if (self.set_type == 0 and i >= mask_data_len) or self.set_type != 0:
124
+ s_begin = i
125
+ s_end = s_begin + self.seq_len
126
+ r_begin = s_end - self.label_len
127
+ r_end = r_begin + self.label_len + self.pred_len
128
+ self.x_data.append(self.data_x[s_begin:s_end])
129
+ self.y_data.append(self.data_y[r_begin:r_end])
130
+ self.x_time.append(self.data_stamp[s_begin:s_end])
131
+ self.y_time.append(self.data_stamp[r_begin:r_end])
132
+ self.s_end_list.append(s_end) # Save s_end
133
+
134
+ def data_augmentation(self):
135
+ origin_len = len(self.x_data)
136
+ if not self.args.closer_data_aug_more:
137
+ aug_size = [self.args.aug_data_size for i in range(origin_len)]
138
+ else:
139
+ aug_size = [int(self.args.aug_data_size * i/origin_len) + 1 for i in range(origin_len)]
140
+
141
+ for i in range(origin_len):
142
+ for _ in range(aug_size[i]):
143
+ aug = augmentation('dataset')
144
+ if self.args.aug_method == 'f_mask':
145
+ x,y = aug.freq_dropout(self.x_data[i],self.y_data[i],dropout_rate=self.args.aug_rate)
146
+ elif self.args.aug_method == 'f_mix':
147
+ rand = float(np.random.random(1))
148
+ i2 = int(rand*len(self.x_data))
149
+ x,y = aug.freq_mix(self.x_data[i],self.y_data[i],self.x_data[i2],self.y_data[i2],dropout_rate=self.args.aug_rate)
150
+ elif self.args.aug_method == 'sp':
151
+ x,y = aug.seasonal_shuffle(self.x_data[i],self.y_data[i], patch_len=self.args.patch_len, stride=self.args.skip, rate=self.args.aug_rate, season=self.args.season, frequency=self.args.aug_freq)
152
+ else:
153
+ raise ValueError
154
+ self.x_data.append(x)
155
+ self.y_data.append(y)
156
+ self.x_time.append(self.x_time[i])
157
+ self.y_time.append(self.y_time[i])
158
+ self.s_end_list.append(self.s_end_list[i])
159
+
160
+ def __getitem__(self, index):
161
+ seq_x = self.x_data[index]
162
+ seq_y = self.y_data[index]
163
+ cycle_index = torch.tensor(self.cycle_index[self.s_end_list[index]])
164
+ return seq_x, seq_y, self.x_time[index], self.y_time[index], cycle_index
165
+
166
+ def __len__(self):
167
+ return len(self.x_data)
168
+
169
+ def inverse_transform(self, data):
170
+ return self.scaler.inverse_transform(data)
171
+
172
+
173
+ class Dataset_ETT_minute(Dataset):
174
+ def __init__(self, config, root_path, flag='train', size=None,
175
+ features='S', data_path='ETTm1.csv',
176
+ target='OT', scale=True, timeenc=0, freq='t', cycle = None):
177
+ self.args = config
178
+ if size == None:
179
+ self.seq_len = 24 * 4 * 4
180
+ self.label_len = 24 * 4
181
+ self.pred_len = 24 * 4
182
+ else:
183
+ self.seq_len = size[0]
184
+ self.label_len = size[1]
185
+ self.pred_len = size[2]
186
+ # init
187
+ assert flag in ['train', 'test', 'val']
188
+ type_map = {'train': 0, 'val': 1, 'test': 2}
189
+ self.set_type = type_map[flag]
190
+
191
+ self.features = features
192
+ self.target = target
193
+ self.scale = scale
194
+ self.timeenc = timeenc
195
+ self.freq = freq
196
+ self.cycle = cycle
197
+
198
+ self.root_path = root_path
199
+ self.data_path = data_path
200
+ self.__read_data__()
201
+ self.collect_all_data()
202
+ if self.args.in_dataset_augmentation and self.set_type==0:
203
+ self.data_augmentation()
204
+
205
+ def __read_data__(self):
206
+ self.scaler = StandardScaler()
207
+ df_raw = pd.read_csv(os.path.join(self.root_path,
208
+ self.data_path))
209
+
210
+ border1s = [0, 12 * 30 * 24 * 4 - self.seq_len, 12 * 30 * 24 * 4 + 4 * 30 * 24 * 4 - self.seq_len]
211
+ border2s = [12 * 30 * 24 * 4, 12 * 30 * 24 * 4 + 4 * 30 * 24 * 4, 12 * 30 * 24 * 4 + 8 * 30 * 24 * 4]
212
+
213
+ if self.args.test_time_train:
214
+ border1s = [0, 18 * 30 * 24 * 4 - self.seq_len, 20 * 30 * 24 * 4]
215
+ border2s = [18 * 30 * 24 * 4, 20 * 30 * 24 * 4, 20 * 30 * 24 * 4]
216
+
217
+ border1 = border1s[self.set_type]
218
+ border2 = border2s[self.set_type]
219
+
220
+ if self.features == 'M' or self.features == 'MS':
221
+ cols_data = df_raw.columns[1:]
222
+ df_data = df_raw[cols_data]
223
+ elif self.features == 'S':
224
+ df_data = df_raw[[self.target]]
225
+
226
+ if self.scale:
227
+ train_data = df_data[border1s[0]:border2s[0]]
228
+ self.scaler.fit(train_data.values)
229
+ data = self.scaler.transform(df_data.values)
230
+ else:
231
+ data = df_data.values
232
+
233
+ df_stamp = df_raw[['date']][border1:border2]
234
+ df_stamp['date'] = pd.to_datetime(df_stamp.date)
235
+ if self.timeenc == 0:
236
+ df_stamp['month'] = df_stamp.date.apply(lambda row: row.month, 1)
237
+ df_stamp['day'] = df_stamp.date.apply(lambda row: row.day, 1)
238
+ df_stamp['weekday'] = df_stamp.date.apply(lambda row: row.weekday(), 1)
239
+ df_stamp['hour'] = df_stamp.date.apply(lambda row: row.hour, 1)
240
+ df_stamp['minute'] = df_stamp.date.apply(lambda row: row.minute, 1)
241
+ df_stamp['minute'] = df_stamp.minute.map(lambda x: x // 15)
242
+ data_stamp = df_stamp.drop(['date'], 1).values
243
+ elif self.timeenc == 1:
244
+ data_stamp = time_features(pd.to_datetime(df_stamp['date'].values), freq=self.freq)
245
+ data_stamp = data_stamp.transpose(1, 0)
246
+
247
+ self.data_x = data[border1:border2]
248
+ self.data_y = data[border1:border2]
249
+ self.data_stamp = data_stamp
250
+ self.cycle_index = (np.arange(len(data)) % self.cycle)[border1:border2]
251
+
252
+ def regenerate_augmentation_data(self):
253
+ self.collect_all_data()
254
+ self.data_augmentation()
255
+
256
+ def reload_data(self, x_data, y_data, x_time, y_time):
257
+ self.x_data = x_data
258
+ self.y_data = y_data
259
+ self.x_time = x_time
260
+ self.y_time = y_time
261
+
262
+ def collect_all_data(self):
263
+ self.x_data = []
264
+ self.y_data = []
265
+ self.x_time = []
266
+ self.y_time = []
267
+ self.s_end_list = []
268
+ data_len = len(self.data_x) - self.seq_len - self.pred_len + 1
269
+ mask_data_len = int((1-self.args.data_size) * data_len) if self.args.data_size < 1 else 0
270
+ for i in range(len(self.data_x) - self.seq_len - self.pred_len + 1):
271
+ if (self.set_type == 0 and i >= mask_data_len) or self.set_type != 0:
272
+ s_begin = i
273
+ s_end = s_begin + self.seq_len
274
+ r_begin = s_end - self.label_len
275
+ r_end = r_begin + self.label_len + self.pred_len
276
+ self.x_data.append(self.data_x[s_begin:s_end])
277
+ self.y_data.append(self.data_y[r_begin:r_end])
278
+ self.x_time.append(self.data_stamp[s_begin:s_end])
279
+ self.y_time.append(self.data_stamp[r_begin:r_end])
280
+ self.s_end_list.append(s_end) # 💡 Save s_end
281
+
282
+ def data_augmentation(self):
283
+ origin_len = len(self.x_data)
284
+ if not self.args.closer_data_aug_more:
285
+ aug_size = [self.args.aug_data_size for i in range(origin_len)]
286
+ else:
287
+ aug_size = [int(self.args.aug_data_size * i/origin_len) + 1 for i in range(origin_len)]
288
+
289
+ for i in range(origin_len):
290
+ for _ in range(aug_size[i]):
291
+ aug = augmentation('dataset')
292
+ if self.args.aug_method == 'f_mask':
293
+ x,y = aug.freq_dropout(self.x_data[i],self.y_data[i],dropout_rate=self.args.aug_rate)
294
+ elif self.args.aug_method == 'f_mix':
295
+ rand = float(np.random.random(1))
296
+ i2 = int(rand*len(self.x_data))
297
+ x,y = aug.freq_mix(self.x_data[i],self.y_data[i],self.x_data[i2],self.y_data[i2],dropout_rate=self.args.aug_rate)
298
+ elif self.args.aug_method == 'sp':
299
+ x,y = aug.seasonal_shuffle(self.x_data[i],self.y_data[i], patch_len=self.args.patch_len, stride=self.args.skip, rate=self.args.aug_rate, season=self.args.season, frequency=self.args.aug_freq)
300
+ else:
301
+ raise ValueError
302
+ self.x_data.append(x)
303
+ self.y_data.append(y)
304
+ self.x_time.append(self.x_time[i])
305
+ self.y_time.append(self.y_time[i])
306
+ self.s_end_list.append(self.s_end_list[i])
307
+
308
+ def __getitem__(self, index):
309
+ seq_x = self.x_data[index]
310
+ seq_y = self.y_data[index]
311
+ cycle_index = torch.tensor(self.cycle_index[self.s_end_list[index]])
312
+ return seq_x, seq_y, self.x_time[index], self.y_time[index], cycle_index
313
+
314
+ def __len__(self):
315
+ return len(self.x_data)
316
+
317
+ def inverse_transform(self, data):
318
+ return self.scaler.inverse_transform(data)
319
+
320
+
321
+ class Dataset_Custom(Dataset):
322
+ def __init__(self, config, root_path, flag='train', size=None,
323
+ features='S', data_path='ETTh1.csv',
324
+ target='OT', scale=True, timeenc=0, freq='h', cycle = None):
325
+ self.args = config
326
+ # info
327
+ if size == None:
328
+ self.seq_len = 24 * 4 * 4
329
+ self.label_len = 24 * 4
330
+ self.pred_len = 24 * 4
331
+ else:
332
+ self.seq_len = size[0]
333
+ self.label_len = size[1]
334
+ self.pred_len = size[2]
335
+ # init
336
+ assert flag in ['train', 'test', 'val']
337
+ type_map = {'train': 0, 'val': 1, 'test': 2}
338
+ self.set_type = type_map[flag]
339
+
340
+ self.features = features
341
+ self.target = target
342
+ self.scale = scale
343
+ self.timeenc = timeenc
344
+ self.freq = freq
345
+ self.cycle = cycle
346
+
347
+ self.root_path = root_path
348
+ self.data_path = data_path
349
+ self.__read_data__()
350
+ self.collect_all_data()
351
+ if self.args.in_dataset_augmentation and self.set_type==0:
352
+ self.data_augmentation()
353
+
354
+ def __read_data__(self):
355
+ self.scaler = StandardScaler()
356
+ df_raw = pd.read_csv(os.path.join(self.root_path,
357
+ self.data_path))
358
+ '''
359
+ df_raw.columns: ['date', ...(other features), target feature]
360
+ '''
361
+ cols = list(df_raw.columns)
362
+ cols.remove(self.target)
363
+ cols.remove('date')
364
+ df_raw = df_raw[['date'] + cols + [self.target]]
365
+ # print(cols)
366
+ num_train = int(len(df_raw) * 0.7)
367
+ num_test = int(len(df_raw) * 0.2)
368
+ num_vali = len(df_raw) - num_train - num_test
369
+ border1s = [0, num_train - self.seq_len, len(df_raw) - num_test - self.seq_len]
370
+ border2s = [num_train, num_train + num_vali, len(df_raw)]
371
+
372
+ if self.args.test_time_train:
373
+ num_train = int(len(df_raw) * 0.9)
374
+ border1s = [0, num_train - self.seq_len, len(df_raw)]
375
+ border2s = [num_train, len(df_raw), len(df_raw)]
376
+
377
+ border1 = border1s[self.set_type]
378
+ border2 = border2s[self.set_type]
379
+
380
+ if self.features == 'M' or self.features == 'MS':
381
+ cols_data = df_raw.columns[1:]
382
+ df_data = df_raw[cols_data]
383
+ elif self.features == 'S':
384
+ df_data = df_raw[[self.target]]
385
+
386
+ if self.scale:
387
+ train_data = df_data[border1s[0]:border2s[0]]
388
+ self.scaler.fit(train_data.values)
389
+ # print(self.scaler.mean_)
390
+ # exit()
391
+ data = self.scaler.transform(df_data.values)
392
+ else:
393
+ data = df_data.values
394
+
395
+ df_stamp = df_raw[['date']][border1:border2]
396
+ df_stamp['date'] = pd.to_datetime(df_stamp.date)
397
+ if self.timeenc == 0:
398
+ df_stamp['month'] = df_stamp.date.apply(lambda row: row.month, 1)
399
+ df_stamp['day'] = df_stamp.date.apply(lambda row: row.day, 1)
400
+ df_stamp['weekday'] = df_stamp.date.apply(lambda row: row.weekday(), 1)
401
+ df_stamp['hour'] = df_stamp.date.apply(lambda row: row.hour, 1)
402
+ data_stamp = df_stamp.drop(['date'], 1).values
403
+ elif self.timeenc == 1:
404
+ data_stamp = time_features(pd.to_datetime(df_stamp['date'].values), freq=self.freq)
405
+ data_stamp = data_stamp.transpose(1, 0)
406
+
407
+ self.data_x = data[border1:border2]
408
+ self.data_y = data[border1:border2]
409
+ self.data_stamp = data_stamp
410
+ self.cycle_index = (np.arange(len(data)) % self.cycle)[border1:border2]
411
+
412
+ def regenerate_augmentation_data(self):
413
+ self.collect_all_data()
414
+ self.data_augmentation()
415
+
416
+ def reload_data(self, x_data, y_data, x_time, y_time):
417
+ self.x_data = x_data
418
+ self.y_data = y_data
419
+ self.x_time = x_time
420
+ self.y_time = y_time
421
+
422
+ def collect_all_data(self):
423
+ self.x_data = []
424
+ self.y_data = []
425
+ self.x_time = []
426
+ self.y_time = []
427
+ self.s_end_list = []
428
+ data_len = len(self.data_x) - self.seq_len - self.pred_len + 1
429
+ mask_data_len = int((1-self.args.data_size) * data_len) if self.args.data_size < 1 else 0
430
+ for i in range(len(self.data_x) - self.seq_len - self.pred_len + 1):
431
+ if (self.set_type == 0 and i >= mask_data_len) or self.set_type != 0:
432
+ s_begin = i
433
+ s_end = s_begin + self.seq_len
434
+ r_begin = s_end - self.label_len
435
+ r_end = r_begin + self.label_len + self.pred_len
436
+ self.x_data.append(self.data_x[s_begin:s_end])
437
+ self.y_data.append(self.data_y[r_begin:r_end])
438
+ self.x_time.append(self.data_stamp[s_begin:s_end])
439
+ self.y_time.append(self.data_stamp[r_begin:r_end])
440
+ self.s_end_list.append(s_end) # 💡 Save s_end
441
+
442
+ def data_augmentation(self):
443
+ origin_len = len(self.x_data)
444
+ if not self.args.closer_data_aug_more:
445
+ aug_size = [self.args.aug_data_size for i in range(origin_len)]
446
+ else:
447
+ aug_size = [int(self.args.aug_data_size * i/origin_len) + 1 for i in range(origin_len)]
448
+
449
+ for i in range(origin_len):
450
+ for _ in range(aug_size[i]):
451
+ aug = augmentation('dataset')
452
+ if self.args.aug_method == 'f_mask':
453
+ x,y = aug.freq_dropout(self.x_data[i],self.y_data[i],dropout_rate=self.args.aug_rate)
454
+ elif self.args.aug_method == 'f_mix':
455
+ rand = float(np.random.random(1))
456
+ i2 = int(rand*len(self.x_data))
457
+ x,y = aug.freq_mix(self.x_data[i],self.y_data[i],self.x_data[i2],self.y_data[i2],dropout_rate=self.args.aug_rate)
458
+ elif self.args.aug_method == 'sp':
459
+ x,y = aug.seasonal_shuffle(self.x_data[i],self.y_data[i], patch_len=self.args.patch_len, stride=self.args.skip, rate=self.args.aug_rate, season=self.args.season, frequency=self.args.aug_freq)
460
+ else:
461
+ raise ValueError
462
+ self.x_data.append(x)
463
+ self.y_data.append(y)
464
+ self.x_time.append(self.x_time[i])
465
+ self.y_time.append(self.y_time[i])
466
+ self.s_end_list.append(self.s_end_list[i])
467
+
468
+ def __getitem__(self, index):
469
+ seq_x = self.x_data[index]
470
+ seq_y = self.y_data[index]
471
+ cycle_index = torch.tensor(self.cycle_index[self.s_end_list[index]])
472
+ return seq_x, seq_y, self.x_time[index], self.y_time[index], cycle_index
473
+
474
+ def __len__(self):
475
+ return len(self.x_data)
476
+
477
+ def inverse_transform(self, data):
478
+ return self.scaler.inverse_transform(data)
479
+
480
+
481
+ class Dataset_Pred(Dataset):
482
+ def __init__(self, args, root_path, flag='pred', size=None,
483
+ features='S', data_path='ETTh1.csv',
484
+ target='OT', scale=True, inverse=False, timeenc=0, freq='15min', cols=None):
485
+ # size [seq_len, label_len, pred_len]
486
+ # info
487
+ if size == None:
488
+ self.seq_len = 24 * 4 * 4
489
+ self.label_len = 24 * 4
490
+ self.pred_len = 24 * 4
491
+ else:
492
+ self.seq_len = size[0]
493
+ self.label_len = size[1]
494
+ self.pred_len = size[2]
495
+ # init
496
+ assert flag in ['pred']
497
+
498
+ self.features = features
499
+ self.target = target
500
+ self.scale = scale
501
+ self.inverse = inverse
502
+ self.timeenc = timeenc
503
+ self.freq = freq
504
+ self.cols = cols
505
+ self.root_path = root_path
506
+ self.data_path = data_path
507
+ self.__read_data__()
508
+
509
+ def __read_data__(self):
510
+ self.scaler = StandardScaler()
511
+ df_raw = pd.read_csv(os.path.join(self.root_path,
512
+ self.data_path))
513
+ '''
514
+ df_raw.columns: ['date', ...(other features), target feature]
515
+ '''
516
+ if self.cols:
517
+ cols = self.cols.copy()
518
+ cols.remove(self.target)
519
+ else:
520
+ cols = list(df_raw.columns)
521
+ cols.remove(self.target)
522
+ cols.remove('date')
523
+ df_raw = df_raw[['date'] + cols + [self.target]]
524
+ border1 = len(df_raw) - self.seq_len
525
+ border2 = len(df_raw)
526
+
527
+ if self.features == 'M' or self.features == 'MS':
528
+ cols_data = df_raw.columns[1:]
529
+ df_data = df_raw[cols_data]
530
+ elif self.features == 'S':
531
+ df_data = df_raw[[self.target]]
532
+
533
+ if self.scale:
534
+ self.scaler.fit(df_data.values)
535
+ data = self.scaler.transform(df_data.values)
536
+ else:
537
+ data = df_data.values
538
+
539
+ tmp_stamp = df_raw[['date']][border1:border2]
540
+ tmp_stamp['date'] = pd.to_datetime(tmp_stamp.date)
541
+ pred_dates = pd.date_range(tmp_stamp.date.values[-1], periods=self.pred_len + 1, freq=self.freq)
542
+
543
+ df_stamp = pd.DataFrame(columns=['date'])
544
+ df_stamp.date = list(tmp_stamp.date.values) + list(pred_dates[1:])
545
+ if self.timeenc == 0:
546
+ df_stamp['month'] = df_stamp.date.apply(lambda row: row.month, 1)
547
+ df_stamp['day'] = df_stamp.date.apply(lambda row: row.day, 1)
548
+ df_stamp['weekday'] = df_stamp.date.apply(lambda row: row.weekday(), 1)
549
+ df_stamp['hour'] = df_stamp.date.apply(lambda row: row.hour, 1)
550
+ df_stamp['minute'] = df_stamp.date.apply(lambda row: row.minute, 1)
551
+ df_stamp['minute'] = df_stamp.minute.map(lambda x: x // 15)
552
+ data_stamp = df_stamp.drop(['date'], 1).values
553
+ elif self.timeenc == 1:
554
+ data_stamp = time_features(pd.to_datetime(df_stamp['date'].values), freq=self.freq)
555
+ data_stamp = data_stamp.transpose(1, 0)
556
+
557
+ self.data_x = data[border1:border2]
558
+ if self.inverse:
559
+ self.data_y = df_data.values[border1:border2]
560
+ else:
561
+ self.data_y = data[border1:border2]
562
+ self.data_stamp = data_stamp
563
+
564
+ def __getitem__(self, index):
565
+ s_begin = index
566
+ s_end = s_begin + self.seq_len
567
+ r_begin = s_end - self.label_len
568
+ r_end = r_begin + self.label_len + self.pred_len
569
+
570
+ seq_x = self.data_x[s_begin:s_end]
571
+ if self.inverse:
572
+ seq_y = self.data_x[r_begin:r_begin + self.label_len]
573
+ else:
574
+ seq_y = self.data_y[r_begin:r_begin + self.label_len]
575
+ seq_x_mark = self.data_stamp[s_begin:s_end]
576
+ seq_y_mark = self.data_stamp[r_begin:r_end]
577
+
578
+ return seq_x, seq_y, seq_x_mark, seq_y_mark
579
+
580
+ def __len__(self):
581
+ return len(self.data_x) - self.seq_len + 1
582
+
583
+ def inverse_transform(self, data):
584
+ return self.scaler.inverse_transform(data)
585
+
586
+
587
+
588
+ class Dataset_PEMS(Dataset):
589
+ def __init__(self, config, root_path, flag='train', size=None,
590
+ features='S', data_path='ETTh1.csv',
591
+ target='OT', scale=True, timeenc=0, freq='h', cycle = None):
592
+ # size [seq_len, label_len, pred_len]
593
+ # info
594
+ self.seq_len = size[0]
595
+ self.label_len = size[1]
596
+ self.pred_len = size[2]
597
+ # init
598
+ assert flag in ['train', 'test', 'val']
599
+ type_map = {'train': 0, 'val': 1, 'test': 2}
600
+ self.set_type = type_map[flag]
601
+
602
+ self.features = features
603
+ self.target = target
604
+ self.scale = scale
605
+ self.timeenc = timeenc
606
+ self.freq = freq
607
+ self.cycle = cycle
608
+
609
+
610
+ self.root_path = root_path
611
+ self.data_path = data_path
612
+
613
+ self.__read_data__()
614
+
615
+ def __read_data__(self):
616
+ self.scaler = StandardScaler()
617
+ data_file = os.path.join(self.root_path, self.data_path)
618
+ data = np.load(data_file, allow_pickle=True)
619
+ data = data['data'][:, :, 0]
620
+
621
+ train_ratio = 0.6
622
+ valid_ratio = 0.2
623
+ train_data = data[:int(train_ratio * len(data))]
624
+ valid_data = data[int(train_ratio * len(data)): int((train_ratio + valid_ratio) * len(data))]
625
+ test_data = data[int((train_ratio + valid_ratio) * len(data)):]
626
+ total_data = [train_data, valid_data, test_data]
627
+ data = total_data[self.set_type]
628
+
629
+ if self.scale:
630
+ self.scaler.fit(train_data)
631
+ data = self.scaler.transform(data)
632
+
633
+ df = pd.DataFrame(data)
634
+ df = df.fillna(method='ffill', limit=len(df)).fillna(method='bfill', limit=len(df)).values
635
+
636
+ self.data_x = df
637
+ self.data_y = df
638
+ self.cycle_index = (np.arange(len(data)) % self.cycle)
639
+
640
+ def __getitem__(self, index):
641
+ s_begin = index
642
+ s_end = s_begin + self.seq_len
643
+ r_begin = s_end - self.label_len
644
+ r_end = r_begin + self.label_len + self.pred_len
645
+
646
+ seq_x = self.data_x[s_begin:s_end]
647
+ seq_y = self.data_y[r_begin:r_end]
648
+ seq_x_mark = torch.zeros((seq_x.shape[0], 1))
649
+ seq_y_mark = torch.zeros((seq_x.shape[0], 1))
650
+ cycle_index = 24
651
+
652
+ return seq_x, seq_y, seq_x_mark, seq_y_mark, cycle_index
653
+
654
+ def __len__(self):
655
+ return len(self.data_x) - self.seq_len - self.pred_len + 1
656
+
657
+ def inverse_transform(self, data):
658
+ return self.scaler.inverse_transform(data)
659
+
time_series_forecasting/exp/exp_basic.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from dominant-shuffle (https://github.com/zuojie2024/dominant-shuffle) and FrAug implementations
2
+ # Original works: Dominant Shuffle by Kai Zhao et al., FrAug by Muxi Chen et al.
3
+
4
+ import os
5
+ import torch
6
+ import numpy as np
7
+
8
+
9
+ class Exp_Basic(object):
10
+ def __init__(self, args):
11
+ self.args = args
12
+ self.device = self._acquire_device()
13
+ self.model = self._build_model().to(self.device)
14
+
15
+ def _build_model(self):
16
+ raise NotImplementedError
17
+ return None
18
+
19
+ def _acquire_device(self):
20
+ if self.args.use_gpu:
21
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(
22
+ self.args.gpu) if not self.args.use_multi_gpu else self.args.devices
23
+ device = torch.device('cuda:{}'.format(self.args.gpu))
24
+ print('Use GPU: cuda:{}'.format(self.args.gpu))
25
+ else:
26
+ device = torch.device('cpu')
27
+ print('Use CPU')
28
+ return device
29
+
30
+ def _get_data(self):
31
+ pass
32
+
33
+ def vali(self):
34
+ pass
35
+
36
+ def train(self):
37
+ pass
38
+
39
+ def test(self):
40
+ pass
time_series_forecasting/exp/exp_main.py ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from dominant-shuffle (https://github.com/zuojie2024/dominant-shuffle) and FrAug implementations
2
+ # Original works: Dominant Shuffle by Kai Zhao et al., FrAug by Muxi Chen et al.
3
+ # Modified and extended by Jafar Bakhshaliyev (2025)
4
+
5
+ from data_provider.data_factory import data_provider
6
+ from exp.exp_basic import Exp_Basic
7
+ from models import Autoformer, LightTS, MICN , TiDE, iTransformer, SCINet, Informer, Transformer, DLinear, Linear, NLinear, PatchTST, TSMixer, CycleNet
8
+ from utils.tools import EarlyStopping, adjust_learning_rate, visual, test_params_flop
9
+ from utils.metrics import metric
10
+ import numpy as np
11
+ import torch
12
+ import torch.nn as nn
13
+ from torch import optim
14
+ from utils.augmentations import augmentation
15
+ import os
16
+ import time
17
+ from torch.optim import lr_scheduler
18
+ import warnings
19
+ import matplotlib.pyplot as plt
20
+ from torch.optim.lr_scheduler import CosineAnnealingLR
21
+
22
+ warnings.filterwarnings('ignore')
23
+
24
+ class Exp_Main(Exp_Basic):
25
+ def __init__(self, args):
26
+ super(Exp_Main, self).__init__(args)
27
+
28
+ def _build_model(self):
29
+ model_dict = {
30
+ 'Autoformer': Autoformer,
31
+ 'LightTS': LightTS,
32
+ 'MICN':MICN,
33
+ 'TiDE': TiDE,
34
+ 'iTransformer': iTransformer,
35
+ 'SCINet': SCINet,
36
+ 'Transformer': Transformer,
37
+ 'Informer': Informer,
38
+ 'DLinear': DLinear,
39
+ 'NLinear': NLinear,
40
+ 'Linear': Linear,
41
+ 'TSMixer': TSMixer,
42
+ 'PatchTST': PatchTST,
43
+ 'CycleNet': CycleNet
44
+ }
45
+ model = model_dict[self.args.model].Model(self.args).float()
46
+
47
+ if self.args.use_multi_gpu and self.args.use_gpu:
48
+ model = nn.DataParallel(model, device_ids=self.args.device_ids)
49
+ return model
50
+
51
+ def _get_data(self, flag):
52
+ data_set, data_loader = data_provider(self.args, flag)
53
+ return data_set, data_loader
54
+
55
+ def _select_optimizer(self):
56
+ if self.args.model == 'needed':
57
+ model_optim = optim.Adam(self.model.parameters(), lr=self.args.learning_rate, betas=(0.9, 0.999), eps=1e-08)
58
+ scheduler = CosineAnnealingLR(optimizer=model_optim, T_max=30 * 8640, eta_min=0)
59
+ else:
60
+ model_optim = optim.Adam(self.model.parameters(), lr=self.args.learning_rate)
61
+ scheduler = None
62
+ return model_optim, scheduler
63
+
64
+
65
+ def _select_criterion(self):
66
+ criterion = nn.MSELoss()
67
+ return criterion
68
+
69
+ def vali(self, vali_data, vali_loader, criterion):
70
+ total_loss = []
71
+ self.model.eval()
72
+ with torch.no_grad():
73
+ for i, (batch_x, batch_y, batch_x_mark, batch_y_mark, batch_cycle) in enumerate(vali_loader):
74
+ batch_x = batch_x.float().to(self.device)
75
+ batch_y = batch_y.float()
76
+ batch_cycle = batch_cycle.int().to(self.device)
77
+
78
+ if 'PEMS' in self.args.data or 'Solar' in self.args.data:
79
+ batch_x_mark = None
80
+ batch_y_mark = None
81
+ else:
82
+ batch_x_mark = batch_x_mark.float().to(self.device)
83
+ batch_y_mark = batch_y_mark.float().to(self.device)
84
+
85
+ # decoder input
86
+ dec_inp = torch.zeros_like(batch_y[:, -self.args.pred_len:, :]).float()
87
+ dec_inp = torch.cat([batch_y[:, :self.args.label_len, :], dec_inp], dim=1).float().to(self.device)
88
+
89
+ # encoder - decoder
90
+ if self.args.use_amp:
91
+ with torch.cuda.amp.autocast():
92
+ if not self.args.use_former:
93
+ if any(substr in self.args.model for substr in {'Cycle'}):
94
+ outputs = self.model(batch_x, batch_cycle)
95
+ else:
96
+ outputs = self.model(batch_x)
97
+ else:
98
+ if self.args.output_attention:
99
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
100
+ else:
101
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
102
+ else:
103
+ if not self.args.use_former:
104
+ if any(substr in self.args.model for substr in {'Cycle'}):
105
+ outputs = self.model(batch_x, batch_cycle)
106
+ else:
107
+ outputs = self.model(batch_x)
108
+ else:
109
+ if self.args.output_attention:
110
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
111
+ else:
112
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
113
+
114
+ f_dim = -1 if self.args.features == 'MS' else 0
115
+ outputs = outputs[:, -self.args.pred_len:, f_dim:]
116
+ batch_y = batch_y[:, -self.args.pred_len:, f_dim:].to(self.device)
117
+
118
+ pred = outputs.detach().cpu()
119
+ true = batch_y.detach().cpu()
120
+
121
+ loss = criterion(pred, true)
122
+
123
+ total_loss.append(loss)
124
+ total_loss = np.average(total_loss)
125
+ self.model.train()
126
+ return total_loss
127
+
128
+ def train(self, setting):
129
+ train_data, train_loader = self._get_data(flag='train')
130
+ vali_data, vali_loader = self._get_data(flag='val')
131
+ test_data, test_loader = self._get_data(flag='test')
132
+
133
+ path = os.path.join(self.args.checkpoints, setting)
134
+ if not os.path.exists(path):
135
+ os.makedirs(path)
136
+
137
+ time_now = time.time()
138
+
139
+ train_steps = len(train_loader)
140
+ early_stopping = EarlyStopping(patience=self.args.patience, verbose=True)
141
+
142
+ model_optim, scheduler = self._select_optimizer()
143
+ criterion = self._select_criterion()
144
+
145
+ if self.args.model == 'PatchTST':
146
+ scheduler = lr_scheduler.OneCycleLR(optimizer = model_optim,
147
+ steps_per_epoch = train_steps,
148
+ pct_start = self.args.pct_start,
149
+ epochs = self.args.train_epochs,
150
+ max_lr = self.args.learning_rate)
151
+
152
+ if self.args.use_amp:
153
+ scaler = torch.cuda.amp.GradScaler()
154
+
155
+ for epoch in range(self.args.train_epochs):
156
+ iter_count = 0
157
+ train_loss = []
158
+
159
+ self.model.train()
160
+ epoch_time = time.time()
161
+
162
+ if self.args.in_dataset_augmentation:
163
+ train_loader.dataset.regenerate_augmentation_data()
164
+
165
+ for i, (batch_x, batch_y, batch_x_mark, batch_y_mark, batch_cycle) in enumerate(train_loader):
166
+ iter_count += 1
167
+ model_optim.zero_grad()
168
+
169
+ batch_x = batch_x.float().to(self.device)
170
+ batch_y = batch_y.float().to(self.device)
171
+ batch_cycle = batch_cycle.int().to(self.device)
172
+ if self.args.in_batch_augmentation and not self.args.wo_original_set:
173
+ batch_cycle = torch.cat([batch_cycle, batch_cycle], dim=0)
174
+
175
+ if 'PEMS' in self.args.data or 'Solar' in self.args.data:
176
+ batch_x_mark = None
177
+ batch_y_mark = None
178
+ else:
179
+ batch_x_mark = batch_x_mark.float().to(self.device)
180
+ batch_y_mark = batch_y_mark.float().to(self.device)
181
+
182
+ if self.args.in_batch_augmentation:
183
+ aug = augmentation('batch')
184
+ methods = {
185
+ 'f_mask':aug.freq_mask,
186
+ 'f_mix': aug.freq_mix,
187
+ 'noise': aug.noise,
188
+ 'warp': aug.warping,
189
+ 'flip': aug.flipping,
190
+ 'mask': aug.masking,
191
+ 'mask_seg': aug.masking_seg,
192
+ 'noise_input':aug.noise_input,
193
+ 'dom_shuffle':aug.dom_shuffle,
194
+ 'w_mask': aug.wave_mask,
195
+ 'w_mix': aug.wave_mix,
196
+ 'f_add': aug.freq_add,
197
+ 'f_pool': aug.freq_pool,
198
+ 'tps': aug.temporal_patch_shuffle,
199
+ 'robust_m': aug.robusttad_m,
200
+ 'robust_p': aug.robusttad_p,
201
+ 'upsample': aug.upsample,
202
+ 'asd': aug.asd,
203
+ 'mbb': aug.mbb,
204
+ }
205
+
206
+ if self.args.wo_original_set:
207
+ xy = methods[self.args.aug_method](batch_x, batch_y[:, -self.args.pred_len:, :], rate=self.args.aug_rate)
208
+ batch_x, batch_y = xy[:, :self.args.seq_len, :], xy[:, -self.args.label_len-self.args.pred_len:, :]
209
+ else:
210
+ for step in range(self.args.aug_data_size):
211
+
212
+ if self.args.aug_method == 'w_mask' or self.args.aug_method == 'w_mix':
213
+ xy2, indices = methods[self.args.aug_method](batch_x, batch_y[:, -self.args.pred_len:, :], self.args.rates, self.args.wavelet, self.args.level, self.args.uniform, self.args.sampling_rate)
214
+ if not ('PEMS' in self.args.data or 'Solar' in self.args.data):
215
+ batch_x2_mark, batch_y2_mark = batch_x_mark[indices], batch_y_mark[indices]
216
+ elif self.args.aug_method.startswith('tps'):
217
+ xy2 = methods[self.args.aug_method](batch_x, batch_y[:, -self.args.pred_len:, :], self.args.aug_patch_len, self.args.aug_stride, self.args.aug_rate)
218
+ batch_x2_mark, batch_y2_mark = batch_x_mark, batch_y_mark
219
+ elif self.args.aug_method.startswith('robust'):
220
+ xy2 = methods[self.args.aug_method](batch_x, batch_y[:, -self.args.pred_len:, :], self.args.aug_rate, self.args.K_num, self.args.seg_ratio)
221
+ batch_x2_mark, batch_y2_mark = batch_x_mark, batch_y_mark
222
+ elif self.args.aug_method.startswith('mbb'):
223
+ xy2 = methods[self.args.aug_method](batch_x, batch_y[:, -self.args.pred_len:, :], self.args.block_size)
224
+ batch_x2_mark, batch_y2_mark = batch_x_mark, batch_y_mark
225
+ else:
226
+ xy2 = methods[self.args.aug_method](batch_x, batch_y[:, -self.args.pred_len:, :], rate=self.args.aug_rate)
227
+ batch_x2_mark, batch_y2_mark = batch_x_mark, batch_y_mark
228
+
229
+ batch_x2, batch_y2 = xy2[:, :self.args.seq_len, :], xy2[:, -self.args.label_len-self.args.pred_len:, :]
230
+
231
+ batch_x = torch.cat([batch_x,batch_x2],dim=0)
232
+ batch_y = torch.cat([batch_y,batch_y2],dim=0)
233
+
234
+ if not ('PEMS' in self.args.data or 'Solar' in self.args.data):
235
+ batch_x_mark = torch.cat([batch_x_mark,batch_x2_mark],dim=0)
236
+ batch_y_mark = torch.cat([batch_y_mark,batch_y2_mark],dim=0)
237
+
238
+
239
+ # decoder input
240
+ dec_inp = torch.zeros_like(batch_y[:, -self.args.pred_len:, :]).float()
241
+ dec_inp = torch.cat([batch_y[:, :self.args.label_len, :], dec_inp], dim=1).float().to(self.device)
242
+ # encoder - decoder
243
+ if self.args.use_amp:
244
+ with torch.cuda.amp.autocast():
245
+ if not self.args.use_former:
246
+ if any(substr in self.args.model for substr in {'Cycle'}):
247
+ outputs = self.model(batch_x, batch_cycle)
248
+ else:
249
+ outputs = self.model(batch_x)
250
+ else:
251
+ if self.args.output_attention:
252
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
253
+ else:
254
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
255
+
256
+ else:
257
+ if not self.args.use_former:
258
+ if any(substr in self.args.model for substr in {'Cycle'}):
259
+ outputs = self.model(batch_x, batch_cycle)
260
+ else:
261
+ outputs = self.model(batch_x)
262
+ else:
263
+ if self.args.output_attention:
264
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
265
+
266
+ else:
267
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
268
+
269
+ f_dim = -1 if self.args.features == 'MS' else 0
270
+ outputs = outputs[:, -self.args.pred_len:, f_dim:]
271
+ batch_y = batch_y[:, -self.args.pred_len:, f_dim:].to(self.device)
272
+ loss = criterion(outputs, batch_y)
273
+ train_loss.append(loss.item())
274
+
275
+ if (i + 1) % 100 == 0:
276
+ print("\titers: {0}, epoch: {1} | loss: {2:.7f}".format(i + 1, epoch + 1, loss.item()))
277
+ speed = (time.time() - time_now) / iter_count
278
+ left_time = speed * ((self.args.train_epochs - epoch) * train_steps - i)
279
+ print('\tspeed: {:.4f}s/iter; left time: {:.4f}s'.format(speed, left_time))
280
+ iter_count = 0
281
+ time_now = time.time()
282
+
283
+ if self.args.use_amp:
284
+ scaler.scale(loss).backward()
285
+ scaler.step(model_optim)
286
+ scaler.update()
287
+ else:
288
+ loss.backward()
289
+ model_optim.step()
290
+
291
+ if self.args.lradj == 'TST':
292
+ adjust_learning_rate(model_optim, scheduler, epoch + 1, self.args, printout=False)
293
+ scheduler.step()
294
+
295
+ print("Epoch: {} cost time: {}".format(epoch + 1, time.time() - epoch_time))
296
+ train_loss = np.average(train_loss)
297
+ vali_loss = self.vali(vali_data, vali_loader, criterion)
298
+ test_loss = self.vali(test_data, test_loader, criterion)
299
+
300
+ print("Epoch: {0}, Steps: {1} | Train Loss: {2:.7f} Vali Loss: {3:.7f} Test Loss: {4:.7f}".format(
301
+ epoch + 1, train_steps, train_loss, vali_loss, test_loss))
302
+ early_stopping(vali_loss, self.model, path)
303
+
304
+ if early_stopping.early_stop:
305
+ print("Early stopping")
306
+ break
307
+
308
+ if self.args.lradj != 'TST':
309
+ adjust_learning_rate(model_optim, scheduler, epoch + 1, self.args)
310
+ else:
311
+ print('Updating learning rate to {}'.format(scheduler.get_last_lr()[0]))
312
+
313
+
314
+ #if self.args.model == 'needed':
315
+ #scheduler.step()
316
+ # else:
317
+ # adjust_learning_rate(model_optim, epoch + 1, self.args)
318
+
319
+
320
+
321
+ best_model_path = path + '/' + 'checkpoint.pth'
322
+ self.model.load_state_dict(torch.load(best_model_path))
323
+ min_val_loss = early_stopping.get_val_loss_min() # delete after search everywhere in train
324
+
325
+ return self.model, min_val_loss
326
+
327
+ def test(self, setting, test=0):
328
+ test_data, test_loader = self._get_data(flag='test')
329
+
330
+ if test:
331
+ print('loading model')
332
+ self.model.load_state_dict(torch.load(os.path.join('./checkpoints/' + setting, 'checkpoint.pth')))
333
+
334
+ preds = []
335
+ trues = []
336
+ inputx = []
337
+ folder_path = './test_results/' + setting + '/'
338
+ if not os.path.exists(folder_path):
339
+ os.makedirs(folder_path)
340
+
341
+ self.model.eval()
342
+ with torch.no_grad():
343
+ for i, (batch_x, batch_y, batch_x_mark, batch_y_mark,batch_cycle ) in enumerate(test_loader):
344
+ batch_x = batch_x.float().to(self.device)
345
+ batch_y = batch_y.float().to(self.device)
346
+ batch_cycle = batch_cycle.int().to(self.device)
347
+
348
+ if 'PEMS' in self.args.data or 'Solar' in self.args.data:
349
+ batch_x_mark = None
350
+ batch_y_mark = None
351
+ else:
352
+ batch_x_mark = batch_x_mark.float().to(self.device)
353
+ batch_y_mark = batch_y_mark.float().to(self.device)
354
+
355
+ # decoder input
356
+ dec_inp = torch.zeros_like(batch_y[:, -self.args.pred_len:, :]).float()
357
+ dec_inp = torch.cat([batch_y[:, :self.args.label_len, :], dec_inp], dim=1).float().to(self.device)
358
+
359
+
360
+ # encoder - decoder
361
+ if self.args.use_amp:
362
+ with torch.cuda.amp.autocast():
363
+ if not self.args.use_former:
364
+ if any(substr in self.args.model for substr in {'Cycle'}):
365
+ outputs = self.model(batch_x, batch_cycle)
366
+ else:
367
+ outputs = self.model(batch_x)
368
+ else:
369
+ if self.args.output_attention:
370
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
371
+ else:
372
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
373
+ else:
374
+ if not self.args.use_former:
375
+ if any(substr in self.args.model for substr in {'Cycle'}):
376
+ outputs = self.model(batch_x, batch_cycle)
377
+ else:
378
+ outputs = self.model(batch_x)
379
+ else:
380
+ if self.args.output_attention:
381
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
382
+
383
+ else:
384
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
385
+
386
+ f_dim = -1 if self.args.features == 'MS' else 0
387
+ outputs = outputs[:, -self.args.pred_len:, f_dim:]
388
+ batch_y = batch_y[:, -self.args.pred_len:, f_dim:].to(self.device)
389
+ outputs = outputs.detach().cpu().numpy()
390
+ batch_y = batch_y.detach().cpu().numpy()
391
+
392
+ pred = outputs # outputs.detach().cpu().numpy() # .squeeze()
393
+ true = batch_y # batch_y.detach().cpu().numpy() # .squeeze()
394
+
395
+ preds.append(pred)
396
+ trues.append(true)
397
+ inputx.append(batch_x.detach().cpu().numpy())
398
+ # if i % 20 == 0:
399
+ # input = batch_x.detach().cpu().numpy()
400
+ # gt = np.concatenate((input[0, :, -1], true[0, :, -1]), axis=0)
401
+ #pd = np.concatenate((input[0, :, -1], pred[0, :, -1]), axis=0)
402
+ # visual(gt, pd, os.path.join(folder_path, str(i) + '.pdf'), os.path.join(folder_path, str(i) + '.npy'))
403
+
404
+ if self.args.test_flop:
405
+ test_params_flop((batch_x.shape[1],batch_x.shape[2]))
406
+ exit()
407
+ preds = np.array(preds)
408
+ trues = np.array(trues)
409
+ inputx = np.array(inputx)
410
+
411
+ preds = preds.reshape(-1, preds.shape[-2], preds.shape[-1])
412
+ trues = trues.reshape(-1, trues.shape[-2], trues.shape[-1])
413
+ inputx = inputx.reshape(-1, inputx.shape[-2], inputx.shape[-1])
414
+
415
+ # result save
416
+ folder_path = './results/' + setting + '/'
417
+ if not os.path.exists(folder_path):
418
+ os.makedirs(folder_path)
419
+
420
+ mae, mse, rmse, mape, mspe, rse, corr = metric(preds, trues)
421
+ if self.args.use_PEMSmetric:
422
+ print('mae:{}, mape:{}, rmse:{}'.format(mae, mape, rmse))
423
+ else:
424
+ print('mse:{}, mae:{}, rse:{}'.format(mse, mae, rse))
425
+
426
+ f = open("result.txt", 'a')
427
+ f.write(setting + " \n")
428
+ f.write('{} --- Pred {} -> mse:{}, mae:{}, rse:{}'.format(self.args.aug_method, self.args.pred_len, mse, mae, rse))
429
+ f.write('\n')
430
+ f.close()
431
+
432
+ # np.save(folder_path + 'metrics.npy', np.array([mae, mse, rmse, mape, mspe,rse, corr]))
433
+ # np.save(folder_path + 'pred.npy', preds)
434
+ # np.save(folder_path + 'true.npy', trues)
435
+ # np.save(folder_path + 'x.npy', inputx)
436
+ return mse, mae, rse
437
+
438
+ def predict(self, setting, load=False):
439
+ pred_data, pred_loader = self._get_data(flag='pred')
440
+
441
+ if load:
442
+ path = os.path.join(self.args.checkpoints, setting)
443
+ best_model_path = path + '/' + 'checkpoint.pth'
444
+ self.model.load_state_dict(torch.load(best_model_path))
445
+
446
+ preds = []
447
+
448
+ self.model.eval()
449
+ with torch.no_grad():
450
+ for i, (batch_x, batch_y, batch_x_mark, batch_y_mark) in enumerate(pred_loader):
451
+ batch_x = batch_x.float().to(self.device)
452
+ batch_y = batch_y.float()
453
+ batch_x_mark = batch_x_mark.float().to(self.device)
454
+ batch_y_mark = batch_y_mark.float().to(self.device)
455
+
456
+ # decoder input
457
+ dec_inp = torch.zeros([batch_y.shape[0], self.args.pred_len, batch_y.shape[2]]).float().to(batch_y.device)
458
+ dec_inp = torch.cat([batch_y[:, :self.args.label_len, :], dec_inp], dim=1).float().to(self.device)
459
+
460
+ # encoder - decoder
461
+ if self.args.use_amp:
462
+ with torch.cuda.amp.autocast():
463
+ if not self.args.use_former:
464
+ outputs = self.model(batch_x)
465
+ else:
466
+ if self.args.output_attention:
467
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
468
+ else:
469
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
470
+ else:
471
+ if not self.args.use_former:
472
+ outputs = self.model(batch_x)
473
+ else:
474
+ if self.args.output_attention:
475
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)[0]
476
+ else:
477
+ outputs = self.model(batch_x, batch_x_mark, dec_inp, batch_y_mark)
478
+
479
+ pred = outputs.detach().cpu().numpy() # .squeeze()
480
+ preds.append(pred)
481
+
482
+ preds = np.array(preds)
483
+ preds = preds.reshape(-1, preds.shape[-2], preds.shape[-1])
484
+
485
+ # result save
486
+ folder_path = './results/' + setting + '/'
487
+ if not os.path.exists(folder_path):
488
+ os.makedirs(folder_path)
489
+
490
+ #np.save(folder_path + 'real_prediction.npy', preds)
491
+
492
+ return
493
+
time_series_forecasting/layers/AutoCorrelation.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import matplotlib.pyplot as plt
5
+ import numpy as np
6
+ import math
7
+ from math import sqrt
8
+ import os
9
+
10
+
11
+ class AutoCorrelation(nn.Module):
12
+ """
13
+ AutoCorrelation Mechanism with the following two phases:
14
+ (1) period-based dependencies discovery
15
+ (2) time delay aggregation
16
+ This block can replace the self-attention family mechanism seamlessly.
17
+ """
18
+ def __init__(self, mask_flag=True, factor=1, scale=None, attention_dropout=0.1, output_attention=False):
19
+ super(AutoCorrelation, self).__init__()
20
+ self.factor = factor
21
+ self.scale = scale
22
+ self.mask_flag = mask_flag
23
+ self.output_attention = output_attention
24
+ self.dropout = nn.Dropout(attention_dropout)
25
+
26
+ def time_delay_agg_training(self, values, corr):
27
+ """
28
+ SpeedUp version of Autocorrelation (a batch-normalization style design)
29
+ This is for the training phase.
30
+ """
31
+ head = values.shape[1]
32
+ channel = values.shape[2]
33
+ length = values.shape[3]
34
+ # find top k
35
+ top_k = int(self.factor * math.log(length))
36
+ mean_value = torch.mean(torch.mean(corr, dim=1), dim=1)
37
+ index = torch.topk(torch.mean(mean_value, dim=0), top_k, dim=-1)[1]
38
+ weights = torch.stack([mean_value[:, index[i]] for i in range(top_k)], dim=-1)
39
+ # update corr
40
+ tmp_corr = torch.softmax(weights, dim=-1)
41
+ # aggregation
42
+ tmp_values = values
43
+ delays_agg = torch.zeros_like(values).float()
44
+ for i in range(top_k):
45
+ pattern = torch.roll(tmp_values, -int(index[i]), -1)
46
+ delays_agg = delays_agg + pattern * \
47
+ (tmp_corr[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length))
48
+ return delays_agg
49
+
50
+ def time_delay_agg_inference(self, values, corr):
51
+ """
52
+ SpeedUp version of Autocorrelation (a batch-normalization style design)
53
+ This is for the inference phase.
54
+ """
55
+ batch = values.shape[0]
56
+ head = values.shape[1]
57
+ channel = values.shape[2]
58
+ length = values.shape[3]
59
+ # index init
60
+ init_index = torch.arange(length).unsqueeze(0).unsqueeze(0).unsqueeze(0).repeat(batch, head, channel, 1).cuda()
61
+ # find top k
62
+ top_k = int(self.factor * math.log(length))
63
+ mean_value = torch.mean(torch.mean(corr, dim=1), dim=1)
64
+ weights = torch.topk(mean_value, top_k, dim=-1)[0]
65
+ delay = torch.topk(mean_value, top_k, dim=-1)[1]
66
+ # update corr
67
+ tmp_corr = torch.softmax(weights, dim=-1)
68
+ # aggregation
69
+ tmp_values = values.repeat(1, 1, 1, 2)
70
+ delays_agg = torch.zeros_like(values).float()
71
+ for i in range(top_k):
72
+ tmp_delay = init_index + delay[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length)
73
+ pattern = torch.gather(tmp_values, dim=-1, index=tmp_delay)
74
+ delays_agg = delays_agg + pattern * \
75
+ (tmp_corr[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length))
76
+ return delays_agg
77
+
78
+ def time_delay_agg_full(self, values, corr):
79
+ """
80
+ Standard version of Autocorrelation
81
+ """
82
+ batch = values.shape[0]
83
+ head = values.shape[1]
84
+ channel = values.shape[2]
85
+ length = values.shape[3]
86
+ # index init
87
+ init_index = torch.arange(length).unsqueeze(0).unsqueeze(0).unsqueeze(0).repeat(batch, head, channel, 1).cuda()
88
+ # find top k
89
+ top_k = int(self.factor * math.log(length))
90
+ weights = torch.topk(corr, top_k, dim=-1)[0]
91
+ delay = torch.topk(corr, top_k, dim=-1)[1]
92
+ # update corr
93
+ tmp_corr = torch.softmax(weights, dim=-1)
94
+ # aggregation
95
+ tmp_values = values.repeat(1, 1, 1, 2)
96
+ delays_agg = torch.zeros_like(values).float()
97
+ for i in range(top_k):
98
+ tmp_delay = init_index + delay[..., i].unsqueeze(-1)
99
+ pattern = torch.gather(tmp_values, dim=-1, index=tmp_delay)
100
+ delays_agg = delays_agg + pattern * (tmp_corr[..., i].unsqueeze(-1))
101
+ return delays_agg
102
+
103
+ def forward(self, queries, keys, values, attn_mask):
104
+ B, L, H, E = queries.shape
105
+ _, S, _, D = values.shape
106
+ if L > S:
107
+ zeros = torch.zeros_like(queries[:, :(L - S), :]).float()
108
+ values = torch.cat([values, zeros], dim=1)
109
+ keys = torch.cat([keys, zeros], dim=1)
110
+ else:
111
+ values = values[:, :L, :, :]
112
+ keys = keys[:, :L, :, :]
113
+
114
+ # period-based dependencies
115
+ q_fft = torch.fft.rfft(queries.permute(0, 2, 3, 1).contiguous(), dim=-1)
116
+ k_fft = torch.fft.rfft(keys.permute(0, 2, 3, 1).contiguous(), dim=-1)
117
+ res = q_fft * torch.conj(k_fft)
118
+ corr = torch.fft.irfft(res, dim=-1)
119
+
120
+ # time delay agg
121
+ if self.training:
122
+ V = self.time_delay_agg_training(values.permute(0, 2, 3, 1).contiguous(), corr).permute(0, 3, 1, 2)
123
+ else:
124
+ V = self.time_delay_agg_inference(values.permute(0, 2, 3, 1).contiguous(), corr).permute(0, 3, 1, 2)
125
+
126
+ if self.output_attention:
127
+ return (V.contiguous(), corr.permute(0, 3, 1, 2))
128
+ else:
129
+ return (V.contiguous(), None)
130
+
131
+
132
+ class AutoCorrelationLayer(nn.Module):
133
+ def __init__(self, correlation, d_model, n_heads, d_keys=None,
134
+ d_values=None):
135
+ super(AutoCorrelationLayer, self).__init__()
136
+
137
+ d_keys = d_keys or (d_model // n_heads)
138
+ d_values = d_values or (d_model // n_heads)
139
+
140
+ self.inner_correlation = correlation
141
+ self.query_projection = nn.Linear(d_model, d_keys * n_heads)
142
+ self.key_projection = nn.Linear(d_model, d_keys * n_heads)
143
+ self.value_projection = nn.Linear(d_model, d_values * n_heads)
144
+ self.out_projection = nn.Linear(d_values * n_heads, d_model)
145
+ self.n_heads = n_heads
146
+
147
+ def forward(self, queries, keys, values, attn_mask):
148
+ B, L, _ = queries.shape
149
+ _, S, _ = keys.shape
150
+ H = self.n_heads
151
+
152
+ queries = self.query_projection(queries).view(B, L, H, -1)
153
+ keys = self.key_projection(keys).view(B, S, H, -1)
154
+ values = self.value_projection(values).view(B, S, H, -1)
155
+
156
+ out, attn = self.inner_correlation(
157
+ queries,
158
+ keys,
159
+ values,
160
+ attn_mask
161
+ )
162
+ out = out.view(B, L, -1)
163
+
164
+ return self.out_projection(out), attn
time_series_forecasting/layers/Autoformer_EncDec.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ class series_decomp_multi(nn.Module):
6
+ """
7
+ Series decomposition block
8
+ """
9
+ def __init__(self,kernel_size):
10
+ super(series_decomp_multi, self).__init__()
11
+ self.moving_avg = [moving_avg(kernel, stride=1) for kernel in kernel_size]
12
+ self.layer = torch.nn.Linear(1,len(kernel_size))
13
+
14
+ def forward(self, x):
15
+ moving_mean=[]
16
+ for func in self.moving_avg:
17
+ moving_avg = func(x)
18
+ moving_mean.append(moving_avg.unsqueeze(-1))
19
+ moving_mean=torch.cat(moving_mean,dim=-1)
20
+ moving_mean = torch.sum(moving_mean*nn.Softmax(-1)(self.layer(x.unsqueeze(-1))),dim=-1)
21
+ res = x - moving_mean
22
+ return res, moving_mean
23
+
24
+ class my_Layernorm(nn.Module):
25
+ """
26
+ Special designed layernorm for the seasonal part
27
+ """
28
+ def __init__(self, channels):
29
+ super(my_Layernorm, self).__init__()
30
+ self.layernorm = nn.LayerNorm(channels)
31
+
32
+ def forward(self, x):
33
+ x_hat = self.layernorm(x)
34
+ bias = torch.mean(x_hat, dim=1).unsqueeze(1).repeat(1, x.shape[1], 1)
35
+ return x_hat - bias
36
+
37
+
38
+ class moving_avg(nn.Module):
39
+ """
40
+ Moving average block to highlight the trend of time series
41
+ """
42
+ def __init__(self, kernel_size, stride):
43
+ super(moving_avg, self).__init__()
44
+ self.kernel_size = kernel_size
45
+ self.avg = nn.AvgPool1d(kernel_size=kernel_size, stride=stride, padding=0)
46
+
47
+ def forward(self, x):
48
+ # padding on the both ends of time series
49
+ front = x[:, 0:1, :].repeat(1, (self.kernel_size - 1) // 2, 1)
50
+ end = x[:, -1:, :].repeat(1, (self.kernel_size - 1) // 2, 1)
51
+ x = torch.cat([front, x, end], dim=1)
52
+ x = self.avg(x.permute(0, 2, 1))
53
+ x = x.permute(0, 2, 1)
54
+ return x
55
+
56
+
57
+ class series_decomp(nn.Module):
58
+ """
59
+ Series decomposition block
60
+ """
61
+ def __init__(self, kernel_size):
62
+ super(series_decomp, self).__init__()
63
+ self.moving_avg = moving_avg(kernel_size, stride=1)
64
+
65
+ def forward(self, x):
66
+ moving_mean = self.moving_avg(x)
67
+ res = x - moving_mean
68
+ return res, moving_mean
69
+
70
+
71
+ class EncoderLayer(nn.Module):
72
+ """
73
+ Autoformer encoder layer with the progressive decomposition architecture
74
+ """
75
+ def __init__(self, attention, d_model, d_ff=None, moving_avg=25, dropout=0.1, activation="relu"):
76
+ super(EncoderLayer, self).__init__()
77
+ d_ff = d_ff or 4 * d_model
78
+ self.attention = attention
79
+ self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1, bias=False)
80
+ self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1, bias=False)
81
+ self.decomp1 = series_decomp(moving_avg)
82
+ self.decomp2 = series_decomp(moving_avg)
83
+ self.dropout = nn.Dropout(dropout)
84
+ self.activation = F.relu if activation == "relu" else F.gelu
85
+
86
+ def forward(self, x, attn_mask=None):
87
+ new_x, attn = self.attention(
88
+ x, x, x,
89
+ attn_mask=attn_mask
90
+ )
91
+ x = x + self.dropout(new_x)
92
+ x, _ = self.decomp1(x)
93
+ y = x
94
+ y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1))))
95
+ y = self.dropout(self.conv2(y).transpose(-1, 1))
96
+ res, _ = self.decomp2(x + y)
97
+ return res, attn
98
+
99
+
100
+ class Encoder(nn.Module):
101
+ """
102
+ Autoformer encoder
103
+ """
104
+ def __init__(self, attn_layers, conv_layers=None, norm_layer=None):
105
+ super(Encoder, self).__init__()
106
+ self.attn_layers = nn.ModuleList(attn_layers)
107
+ self.conv_layers = nn.ModuleList(conv_layers) if conv_layers is not None else None
108
+ self.norm = norm_layer
109
+
110
+ def forward(self, x, attn_mask=None):
111
+ attns = []
112
+ if self.conv_layers is not None:
113
+ for attn_layer, conv_layer in zip(self.attn_layers, self.conv_layers):
114
+ x, attn = attn_layer(x, attn_mask=attn_mask)
115
+ x = conv_layer(x)
116
+ attns.append(attn)
117
+ x, attn = self.attn_layers[-1](x)
118
+ attns.append(attn)
119
+ else:
120
+ for attn_layer in self.attn_layers:
121
+ x, attn = attn_layer(x, attn_mask=attn_mask)
122
+ attns.append(attn)
123
+
124
+ if self.norm is not None:
125
+ x = self.norm(x)
126
+
127
+ return x, attns
128
+
129
+
130
+ class DecoderLayer(nn.Module):
131
+ """
132
+ Autoformer decoder layer with the progressive decomposition architecture
133
+ """
134
+ def __init__(self, self_attention, cross_attention, d_model, c_out, d_ff=None,
135
+ moving_avg=25, dropout=0.1, activation="relu"):
136
+ super(DecoderLayer, self).__init__()
137
+ d_ff = d_ff or 4 * d_model
138
+ self.self_attention = self_attention
139
+ self.cross_attention = cross_attention
140
+ self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1, bias=False)
141
+ self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1, bias=False)
142
+ self.decomp1 = series_decomp(moving_avg)
143
+ self.decomp2 = series_decomp(moving_avg)
144
+ self.decomp3 = series_decomp(moving_avg)
145
+ self.dropout = nn.Dropout(dropout)
146
+ self.projection = nn.Conv1d(in_channels=d_model, out_channels=c_out, kernel_size=3, stride=1, padding=1,
147
+ padding_mode='circular', bias=False)
148
+ self.activation = F.relu if activation == "relu" else F.gelu
149
+
150
+ def forward(self, x, cross, x_mask=None, cross_mask=None):
151
+ x = x + self.dropout(self.self_attention(
152
+ x, x, x,
153
+ attn_mask=x_mask
154
+ )[0])
155
+ x, trend1 = self.decomp1(x)
156
+ x = x + self.dropout(self.cross_attention(
157
+ x, cross, cross,
158
+ attn_mask=cross_mask
159
+ )[0])
160
+ x, trend2 = self.decomp2(x)
161
+ y = x
162
+ y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1))))
163
+ y = self.dropout(self.conv2(y).transpose(-1, 1))
164
+ x, trend3 = self.decomp3(x + y)
165
+
166
+ residual_trend = trend1 + trend2 + trend3
167
+ residual_trend = self.projection(residual_trend.permute(0, 2, 1)).transpose(1, 2)
168
+ return x, residual_trend
169
+
170
+
171
+ class Decoder(nn.Module):
172
+ """
173
+ Autoformer encoder
174
+ """
175
+ def __init__(self, layers, norm_layer=None, projection=None):
176
+ super(Decoder, self).__init__()
177
+ self.layers = nn.ModuleList(layers)
178
+ self.norm = norm_layer
179
+ self.projection = projection
180
+
181
+ def forward(self, x, cross, x_mask=None, cross_mask=None, trend=None):
182
+ for layer in self.layers:
183
+ x, residual_trend = layer(x, cross, x_mask=x_mask, cross_mask=cross_mask)
184
+ trend = trend + residual_trend
185
+
186
+ if self.norm is not None:
187
+ x = self.norm(x)
188
+
189
+ if self.projection is not None:
190
+ x = self.projection(x)
191
+ return x, trend
time_series_forecasting/layers/Conv_Blocks.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+
5
+ class Inception_Block_V1(nn.Module):
6
+ def __init__(self, in_channels, out_channels, num_kernels=6, init_weight=True):
7
+ super(Inception_Block_V1, self).__init__()
8
+ self.in_channels = in_channels
9
+ self.out_channels = out_channels
10
+ self.num_kernels = num_kernels
11
+ kernels = []
12
+ for i in range(self.num_kernels):
13
+ kernels.append(nn.Conv2d(in_channels, out_channels, kernel_size=2 * i + 1, padding=i))
14
+ self.kernels = nn.ModuleList(kernels)
15
+ if init_weight:
16
+ self._initialize_weights()
17
+
18
+ def _initialize_weights(self):
19
+ for m in self.modules():
20
+ if isinstance(m, nn.Conv2d):
21
+ nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
22
+ if m.bias is not None:
23
+ nn.init.constant_(m.bias, 0)
24
+
25
+ def forward(self, x):
26
+ res_list = []
27
+ for i in range(self.num_kernels):
28
+ res_list.append(self.kernels[i](x))
29
+ res = torch.stack(res_list, dim=-1).mean(-1)
30
+ return res
31
+
32
+
33
+ class Inception_Block_V2(nn.Module):
34
+ def __init__(self, in_channels, out_channels, num_kernels=6, init_weight=True):
35
+ super(Inception_Block_V2, self).__init__()
36
+ self.in_channels = in_channels
37
+ self.out_channels = out_channels
38
+ self.num_kernels = num_kernels
39
+ kernels = []
40
+ for i in range(self.num_kernels // 2):
41
+ kernels.append(nn.Conv2d(in_channels, out_channels, kernel_size=[1, 2 * i + 3], padding=[0, i + 1]))
42
+ kernels.append(nn.Conv2d(in_channels, out_channels, kernel_size=[2 * i + 3, 1], padding=[i + 1, 0]))
43
+ kernels.append(nn.Conv2d(in_channels, out_channels, kernel_size=1))
44
+ self.kernels = nn.ModuleList(kernels)
45
+ if init_weight:
46
+ self._initialize_weights()
47
+
48
+ def _initialize_weights(self):
49
+ for m in self.modules():
50
+ if isinstance(m, nn.Conv2d):
51
+ nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
52
+ if m.bias is not None:
53
+ nn.init.constant_(m.bias, 0)
54
+
55
+ def forward(self, x):
56
+ res_list = []
57
+ for i in range(self.num_kernels + 1):
58
+ res_list.append(self.kernels[i](x))
59
+ res = torch.stack(res_list, dim=-1).mean(-1)
60
+ return res
time_series_forecasting/layers/Embed.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from torch.nn.utils import weight_norm
5
+ import math
6
+
7
+
8
+ class PositionalEmbedding(nn.Module):
9
+ def __init__(self, d_model, max_len=5000):
10
+ super(PositionalEmbedding, self).__init__()
11
+ # Compute the positional encodings once in log space.
12
+ pe = torch.zeros(max_len, d_model).float()
13
+ pe.require_grad = False
14
+
15
+ position = torch.arange(0, max_len).float().unsqueeze(1)
16
+ div_term = (torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)).exp()
17
+
18
+ pe[:, 0::2] = torch.sin(position * div_term)
19
+ pe[:, 1::2] = torch.cos(position * div_term)
20
+
21
+ pe = pe.unsqueeze(0)
22
+ self.register_buffer('pe', pe)
23
+
24
+ def forward(self, x):
25
+ return self.pe[:, :x.size(1)]
26
+
27
+
28
+ class TokenEmbedding(nn.Module):
29
+ def __init__(self, c_in, d_model):
30
+ super(TokenEmbedding, self).__init__()
31
+ padding = 1 if torch.__version__ >= '1.5.0' else 2
32
+ self.tokenConv = nn.Conv1d(in_channels=c_in, out_channels=d_model,
33
+ kernel_size=3, padding=padding, padding_mode='circular', bias=False)
34
+ for m in self.modules():
35
+ if isinstance(m, nn.Conv1d):
36
+ nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='leaky_relu')
37
+
38
+ def forward(self, x):
39
+ x = self.tokenConv(x.permute(0, 2, 1)).transpose(1, 2)
40
+ return x
41
+
42
+
43
+ class FixedEmbedding(nn.Module):
44
+ def __init__(self, c_in, d_model):
45
+ super(FixedEmbedding, self).__init__()
46
+
47
+ w = torch.zeros(c_in, d_model).float()
48
+ w.require_grad = False
49
+
50
+ position = torch.arange(0, c_in).float().unsqueeze(1)
51
+ div_term = (torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)).exp()
52
+
53
+ w[:, 0::2] = torch.sin(position * div_term)
54
+ w[:, 1::2] = torch.cos(position * div_term)
55
+
56
+ self.emb = nn.Embedding(c_in, d_model)
57
+ self.emb.weight = nn.Parameter(w, requires_grad=False)
58
+
59
+ def forward(self, x):
60
+ return self.emb(x).detach()
61
+
62
+
63
+ class TemporalEmbedding(nn.Module):
64
+ def __init__(self, d_model, embed_type='fixed', freq='h'):
65
+ super(TemporalEmbedding, self).__init__()
66
+
67
+ minute_size = 4
68
+ hour_size = 24
69
+ weekday_size = 7
70
+ day_size = 32
71
+ month_size = 13
72
+
73
+ Embed = FixedEmbedding if embed_type == 'fixed' else nn.Embedding
74
+ if freq == 't':
75
+ self.minute_embed = Embed(minute_size, d_model)
76
+ self.hour_embed = Embed(hour_size, d_model)
77
+ self.weekday_embed = Embed(weekday_size, d_model)
78
+ self.day_embed = Embed(day_size, d_model)
79
+ self.month_embed = Embed(month_size, d_model)
80
+
81
+ def forward(self, x):
82
+ x = x.long()
83
+
84
+ minute_x = self.minute_embed(x[:, :, 4]) if hasattr(self, 'minute_embed') else 0.
85
+ hour_x = self.hour_embed(x[:, :, 3])
86
+ weekday_x = self.weekday_embed(x[:, :, 2])
87
+ day_x = self.day_embed(x[:, :, 1])
88
+ month_x = self.month_embed(x[:, :, 0])
89
+
90
+ return hour_x + weekday_x + day_x + month_x + minute_x
91
+
92
+
93
+ class TimeFeatureEmbedding(nn.Module):
94
+ def __init__(self, d_model, embed_type='timeF', freq='h'):
95
+ super(TimeFeatureEmbedding, self).__init__()
96
+
97
+ freq_map = {'h': 4, 't': 5, 's': 6, 'm': 1, 'a': 1, 'w': 2, 'd': 3, 'b': 3}
98
+ d_inp = freq_map[freq]
99
+ self.embed = nn.Linear(d_inp, d_model, bias=False)
100
+
101
+ def forward(self, x):
102
+ return self.embed(x)
103
+
104
+
105
+ class DataEmbedding(nn.Module):
106
+ def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
107
+ super(DataEmbedding, self).__init__()
108
+
109
+ self.value_embedding = TokenEmbedding(c_in=c_in, d_model=d_model)
110
+ self.position_embedding = PositionalEmbedding(d_model=d_model)
111
+ self.temporal_embedding = TemporalEmbedding(d_model=d_model, embed_type=embed_type,
112
+ freq=freq) if embed_type != 'timeF' else TimeFeatureEmbedding(
113
+ d_model=d_model, embed_type=embed_type, freq=freq)
114
+ self.dropout = nn.Dropout(p=dropout)
115
+
116
+ def forward(self, x, x_mark):
117
+ if x_mark is None:
118
+ x = self.value_embedding(x) + self.position_embedding(x)
119
+ else:
120
+ x = self.value_embedding(
121
+ x) + self.temporal_embedding(x_mark) + self.position_embedding(x)
122
+ return self.dropout(x)
123
+
124
+
125
+ class DataEmbedding_wo_pos(nn.Module):
126
+ def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
127
+ super(DataEmbedding_wo_pos, self).__init__()
128
+
129
+ self.value_embedding = TokenEmbedding(c_in=c_in, d_model=d_model)
130
+ self.position_embedding = PositionalEmbedding(d_model=d_model)
131
+ self.temporal_embedding = TemporalEmbedding(d_model=d_model, embed_type=embed_type,
132
+ freq=freq) if embed_type != 'timeF' else TimeFeatureEmbedding(
133
+ d_model=d_model, embed_type=embed_type, freq=freq)
134
+ self.dropout = nn.Dropout(p=dropout)
135
+
136
+ def forward(self, x, x_mark):
137
+ #x = self.value_embedding(x) + self.temporal_embedding(x_mark)
138
+ if x_mark is None:
139
+ x = self.value_embedding(x)
140
+ else:
141
+ x = self.value_embedding(x) + self.temporal_embedding(x_mark)
142
+ return self.dropout(x)
143
+
144
+ class DataEmbedding_wo_pos_temp(nn.Module):
145
+ def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
146
+ super(DataEmbedding_wo_pos_temp, self).__init__()
147
+
148
+ self.value_embedding = TokenEmbedding(c_in=c_in, d_model=d_model)
149
+ self.position_embedding = PositionalEmbedding(d_model=d_model)
150
+ self.temporal_embedding = TemporalEmbedding(d_model=d_model, embed_type=embed_type,
151
+ freq=freq) if embed_type != 'timeF' else TimeFeatureEmbedding(
152
+ d_model=d_model, embed_type=embed_type, freq=freq)
153
+ self.dropout = nn.Dropout(p=dropout)
154
+
155
+ def forward(self, x, x_mark):
156
+ x = self.value_embedding(x)
157
+ return self.dropout(x)
158
+
159
+ class DataEmbedding_wo_temp(nn.Module):
160
+ def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
161
+ super(DataEmbedding_wo_temp, self).__init__()
162
+
163
+ self.value_embedding = TokenEmbedding(c_in=c_in, d_model=d_model)
164
+ self.position_embedding = PositionalEmbedding(d_model=d_model)
165
+ self.temporal_embedding = TemporalEmbedding(d_model=d_model, embed_type=embed_type,
166
+ freq=freq) if embed_type != 'timeF' else TimeFeatureEmbedding(
167
+ d_model=d_model, embed_type=embed_type, freq=freq)
168
+ self.dropout = nn.Dropout(p=dropout)
169
+
170
+ def forward(self, x, x_mark):
171
+ x = self.value_embedding(x) + self.position_embedding(x)
172
+ return self.dropout(x)
173
+
174
+
175
+ class PatchEmbedding(nn.Module):
176
+ def __init__(self, d_model, patch_len, stride, padding, dropout):
177
+ super(PatchEmbedding, self).__init__()
178
+ # Patching
179
+ self.patch_len = patch_len
180
+ self.stride = stride
181
+ self.padding_patch_layer = nn.ReplicationPad1d((0, padding))
182
+
183
+ # Backbone, Input encoding: projection of feature vectors onto a d-dim vector space
184
+ self.value_embedding = nn.Linear(patch_len, d_model, bias=False)
185
+
186
+ # Positional embedding
187
+ self.position_embedding = PositionalEmbedding(d_model)
188
+
189
+ # Residual dropout
190
+ self.dropout = nn.Dropout(dropout)
191
+
192
+ def forward(self, x):
193
+ # do patching
194
+ n_vars = x.shape[1]
195
+ x = self.padding_patch_layer(x)
196
+ x = x.unfold(dimension=-1, size=self.patch_len, step=self.stride)
197
+ x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3]))
198
+ # Input encoding
199
+ x = self.value_embedding(x) + self.position_embedding(x)
200
+ return self.dropout(x), n_vars
201
+
202
+
203
+ class DataEmbedding_inverted(nn.Module):
204
+ def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
205
+ super(DataEmbedding_inverted, self).__init__()
206
+ self.value_embedding = nn.Linear(c_in, d_model)
207
+ self.dropout = nn.Dropout(p=dropout)
208
+
209
+ def forward(self, x, x_mark):
210
+ x = x.permute(0, 2, 1)
211
+ # x: [Batch Variate Time]
212
+ if x_mark is None:
213
+ x = self.value_embedding(x)
214
+ else:
215
+ x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1))
216
+ # x: [Batch Variate d_model]
217
+ return self.dropout(x)
time_series_forecasting/layers/PatchTST_backbone.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __all__ = ['PatchTST_backbone']
2
+
3
+ # Cell
4
+ from typing import Callable, Optional
5
+ import torch
6
+ from torch import nn
7
+ from torch import Tensor
8
+ import torch.nn.functional as F
9
+ import numpy as np
10
+
11
+ #from collections import OrderedDict
12
+ from layers.PatchTST_layers import *
13
+ from layers.RevIN import RevIN
14
+
15
+ # Cell
16
+ class PatchTST_backbone(nn.Module):
17
+ def __init__(self, c_in:int, context_window:int, target_window:int, patch_len:int, stride:int, max_seq_len:Optional[int]=1024,
18
+ n_layers:int=3, d_model=128, n_heads=16, d_k:Optional[int]=None, d_v:Optional[int]=None,
19
+ d_ff:int=256, norm:str='BatchNorm', attn_dropout:float=0., dropout:float=0., act:str="gelu", key_padding_mask:bool='auto',
20
+ padding_var:Optional[int]=None, attn_mask:Optional[Tensor]=None, res_attention:bool=True, pre_norm:bool=False, store_attn:bool=False,
21
+ pe:str='zeros', learn_pe:bool=True, fc_dropout:float=0., head_dropout = 0, padding_patch = None,
22
+ pretrain_head:bool=False, head_type = 'flatten', individual = False, revin = True, affine = True, subtract_last = False,
23
+ verbose:bool=False, **kwargs):
24
+
25
+ super().__init__()
26
+
27
+ # RevIn
28
+ self.revin = revin
29
+ if self.revin: self.revin_layer = RevIN(c_in, affine=affine, subtract_last=subtract_last)
30
+
31
+ # Patching
32
+ self.patch_len = patch_len
33
+ self.stride = stride
34
+ self.padding_patch = padding_patch
35
+ patch_num = int((context_window - patch_len)/stride + 1)
36
+ if padding_patch == 'end': # can be modified to general case
37
+ self.padding_patch_layer = nn.ReplicationPad1d((0, stride))
38
+ patch_num += 1
39
+
40
+ # Backbone
41
+ self.backbone = TSTiEncoder(c_in, patch_num=patch_num, patch_len=patch_len, max_seq_len=max_seq_len,
42
+ n_layers=n_layers, d_model=d_model, n_heads=n_heads, d_k=d_k, d_v=d_v, d_ff=d_ff,
43
+ attn_dropout=attn_dropout, dropout=dropout, act=act, key_padding_mask=key_padding_mask, padding_var=padding_var,
44
+ attn_mask=attn_mask, res_attention=res_attention, pre_norm=pre_norm, store_attn=store_attn,
45
+ pe=pe, learn_pe=learn_pe, verbose=verbose, **kwargs)
46
+
47
+ # Head
48
+ self.head_nf = d_model * patch_num
49
+ self.n_vars = c_in
50
+ self.pretrain_head = pretrain_head
51
+ self.head_type = head_type
52
+ self.individual = individual
53
+
54
+ if self.pretrain_head:
55
+ self.head = self.create_pretrain_head(self.head_nf, c_in, fc_dropout) # custom head passed as a partial func with all its kwargs
56
+ elif head_type == 'flatten':
57
+ self.head = Flatten_Head(self.individual, self.n_vars, self.head_nf, target_window, head_dropout=head_dropout)
58
+
59
+
60
+ def forward(self, z): # z: [bs x nvars x seq_len]
61
+ # norm
62
+ if self.revin:
63
+ z = z.permute(0,2,1)
64
+ z = self.revin_layer(z, 'norm')
65
+ z = z.permute(0,2,1)
66
+
67
+ # do patching
68
+ if self.padding_patch == 'end':
69
+ z = self.padding_patch_layer(z)
70
+ z = z.unfold(dimension=-1, size=self.patch_len, step=self.stride) # z: [bs x nvars x patch_num x patch_len]
71
+ z = z.permute(0,1,3,2) # z: [bs x nvars x patch_len x patch_num]
72
+
73
+ # model
74
+ z = self.backbone(z) # z: [bs x nvars x d_model x patch_num]
75
+ z = self.head(z) # z: [bs x nvars x target_window]
76
+
77
+ # denorm
78
+ if self.revin:
79
+ z = z.permute(0,2,1)
80
+ z = self.revin_layer(z, 'denorm')
81
+ z = z.permute(0,2,1)
82
+ return z
83
+
84
+ def create_pretrain_head(self, head_nf, vars, dropout):
85
+ return nn.Sequential(nn.Dropout(dropout),
86
+ nn.Conv1d(head_nf, vars, 1)
87
+ )
88
+
89
+
90
+ class Flatten_Head(nn.Module):
91
+ def __init__(self, individual, n_vars, nf, target_window, head_dropout=0):
92
+ super().__init__()
93
+
94
+ self.individual = individual
95
+ self.n_vars = n_vars
96
+
97
+ if self.individual:
98
+ self.linears = nn.ModuleList()
99
+ self.dropouts = nn.ModuleList()
100
+ self.flattens = nn.ModuleList()
101
+ for i in range(self.n_vars):
102
+ self.flattens.append(nn.Flatten(start_dim=-2))
103
+ self.linears.append(nn.Linear(nf, target_window))
104
+ self.dropouts.append(nn.Dropout(head_dropout))
105
+ else:
106
+ self.flatten = nn.Flatten(start_dim=-2)
107
+ self.linear = nn.Linear(nf, target_window)
108
+ self.dropout = nn.Dropout(head_dropout)
109
+
110
+ def forward(self, x): # x: [bs x nvars x d_model x patch_num]
111
+ if self.individual:
112
+ x_out = []
113
+ for i in range(self.n_vars):
114
+ z = self.flattens[i](x[:,i,:,:]) # z: [bs x d_model * patch_num]
115
+ z = self.linears[i](z) # z: [bs x target_window]
116
+ z = self.dropouts[i](z)
117
+ x_out.append(z)
118
+ x = torch.stack(x_out, dim=1) # x: [bs x nvars x target_window]
119
+ else:
120
+ x = self.flatten(x)
121
+ x = self.linear(x)
122
+ x = self.dropout(x)
123
+ return x
124
+
125
+
126
+
127
+
128
+ class TSTiEncoder(nn.Module): #i means channel-independent
129
+ def __init__(self, c_in, patch_num, patch_len, max_seq_len=1024,
130
+ n_layers=3, d_model=128, n_heads=16, d_k=None, d_v=None,
131
+ d_ff=256, norm='BatchNorm', attn_dropout=0., dropout=0., act="gelu", store_attn=False,
132
+ key_padding_mask='auto', padding_var=None, attn_mask=None, res_attention=True, pre_norm=False,
133
+ pe='zeros', learn_pe=True, verbose=False, **kwargs):
134
+
135
+
136
+ super().__init__()
137
+
138
+ self.patch_num = patch_num
139
+ self.patch_len = patch_len
140
+
141
+ # Input encoding
142
+ q_len = patch_num
143
+ self.W_P = nn.Linear(patch_len, d_model) # Eq 1: projection of feature vectors onto a d-dim vector space
144
+ self.seq_len = q_len
145
+
146
+ # Positional encoding
147
+ self.W_pos = positional_encoding(pe, learn_pe, q_len, d_model)
148
+
149
+ # Residual dropout
150
+ self.dropout = nn.Dropout(dropout)
151
+
152
+ # Encoder
153
+ 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,
154
+ pre_norm=pre_norm, activation=act, res_attention=res_attention, n_layers=n_layers, store_attn=store_attn)
155
+
156
+
157
+ def forward(self, x) -> Tensor: # x: [bs x nvars x patch_len x patch_num]
158
+
159
+ n_vars = x.shape[1]
160
+ # Input encoding
161
+ x = x.permute(0,1,3,2) # x: [bs x nvars x patch_num x patch_len]
162
+ x = self.W_P(x) # x: [bs x nvars x patch_num x d_model]
163
+
164
+ u = torch.reshape(x, (x.shape[0]*x.shape[1],x.shape[2],x.shape[3])) # u: [bs * nvars x patch_num x d_model]
165
+ u = self.dropout(u + self.W_pos) # u: [bs * nvars x patch_num x d_model]
166
+
167
+ # Encoder
168
+ z = self.encoder(u) # z: [bs * nvars x patch_num x d_model]
169
+ z = torch.reshape(z, (-1,n_vars,z.shape[-2],z.shape[-1])) # z: [bs x nvars x patch_num x d_model]
170
+ z = z.permute(0,1,3,2) # z: [bs x nvars x d_model x patch_num]
171
+
172
+ return z
173
+
174
+
175
+
176
+ # Cell
177
+ class TSTEncoder(nn.Module):
178
+ def __init__(self, q_len, d_model, n_heads, d_k=None, d_v=None, d_ff=None,
179
+ norm='BatchNorm', attn_dropout=0., dropout=0., activation='gelu',
180
+ res_attention=False, n_layers=1, pre_norm=False, store_attn=False):
181
+ super().__init__()
182
+
183
+ self.layers = nn.ModuleList([TSTEncoderLayer(q_len, d_model, n_heads=n_heads, d_k=d_k, d_v=d_v, d_ff=d_ff, norm=norm,
184
+ attn_dropout=attn_dropout, dropout=dropout,
185
+ activation=activation, res_attention=res_attention,
186
+ pre_norm=pre_norm, store_attn=store_attn) for i in range(n_layers)])
187
+ self.res_attention = res_attention
188
+
189
+ def forward(self, src:Tensor, key_padding_mask:Optional[Tensor]=None, attn_mask:Optional[Tensor]=None):
190
+ output = src
191
+ scores = None
192
+ if self.res_attention:
193
+ for mod in self.layers: output, scores = mod(output, prev=scores, key_padding_mask=key_padding_mask, attn_mask=attn_mask)
194
+ return output
195
+ else:
196
+ for mod in self.layers: output = mod(output, key_padding_mask=key_padding_mask, attn_mask=attn_mask)
197
+ return output
198
+
199
+
200
+
201
+ class TSTEncoderLayer(nn.Module):
202
+ def __init__(self, q_len, d_model, n_heads, d_k=None, d_v=None, d_ff=256, store_attn=False,
203
+ norm='BatchNorm', attn_dropout=0, dropout=0., bias=True, activation="gelu", res_attention=False, pre_norm=False):
204
+ super().__init__()
205
+ assert not d_model%n_heads, f"d_model ({d_model}) must be divisible by n_heads ({n_heads})"
206
+ d_k = d_model // n_heads if d_k is None else d_k
207
+ d_v = d_model // n_heads if d_v is None else d_v
208
+
209
+ # Multi-Head attention
210
+ self.res_attention = res_attention
211
+ self.self_attn = _MultiheadAttention(d_model, n_heads, d_k, d_v, attn_dropout=attn_dropout, proj_dropout=dropout, res_attention=res_attention)
212
+
213
+ # Add & Norm
214
+ self.dropout_attn = nn.Dropout(dropout)
215
+ if "batch" in norm.lower():
216
+ self.norm_attn = nn.Sequential(Transpose(1,2), nn.BatchNorm1d(d_model), Transpose(1,2))
217
+ else:
218
+ self.norm_attn = nn.LayerNorm(d_model)
219
+
220
+ # Position-wise Feed-Forward
221
+ self.ff = nn.Sequential(nn.Linear(d_model, d_ff, bias=bias),
222
+ get_activation_fn(activation),
223
+ nn.Dropout(dropout),
224
+ nn.Linear(d_ff, d_model, bias=bias))
225
+
226
+ # Add & Norm
227
+ self.dropout_ffn = nn.Dropout(dropout)
228
+ if "batch" in norm.lower():
229
+ self.norm_ffn = nn.Sequential(Transpose(1,2), nn.BatchNorm1d(d_model), Transpose(1,2))
230
+ else:
231
+ self.norm_ffn = nn.LayerNorm(d_model)
232
+
233
+ self.pre_norm = pre_norm
234
+ self.store_attn = store_attn
235
+
236
+
237
+ def forward(self, src:Tensor, prev:Optional[Tensor]=None, key_padding_mask:Optional[Tensor]=None, attn_mask:Optional[Tensor]=None) -> Tensor:
238
+
239
+ # Multi-Head attention sublayer
240
+ if self.pre_norm:
241
+ src = self.norm_attn(src)
242
+ ## Multi-Head attention
243
+ if self.res_attention:
244
+ src2, attn, scores = self.self_attn(src, src, src, prev, key_padding_mask=key_padding_mask, attn_mask=attn_mask)
245
+ else:
246
+ src2, attn = self.self_attn(src, src, src, key_padding_mask=key_padding_mask, attn_mask=attn_mask)
247
+ if self.store_attn:
248
+ self.attn = attn
249
+ ## Add & Norm
250
+ src = src + self.dropout_attn(src2) # Add: residual connection with residual dropout
251
+ if not self.pre_norm:
252
+ src = self.norm_attn(src)
253
+
254
+ # Feed-forward sublayer
255
+ if self.pre_norm:
256
+ src = self.norm_ffn(src)
257
+ ## Position-wise Feed-Forward
258
+ src2 = self.ff(src)
259
+ ## Add & Norm
260
+ src = src + self.dropout_ffn(src2) # Add: residual connection with residual dropout
261
+ if not self.pre_norm:
262
+ src = self.norm_ffn(src)
263
+
264
+ if self.res_attention:
265
+ return src, scores
266
+ else:
267
+ return src
268
+
269
+
270
+
271
+
272
+ class _MultiheadAttention(nn.Module):
273
+ def __init__(self, d_model, n_heads, d_k=None, d_v=None, res_attention=False, attn_dropout=0., proj_dropout=0., qkv_bias=True, lsa=False):
274
+ """Multi Head Attention Layer
275
+ Input shape:
276
+ Q: [batch_size (bs) x max_q_len x d_model]
277
+ K, V: [batch_size (bs) x q_len x d_model]
278
+ mask: [q_len x q_len]
279
+ """
280
+ super().__init__()
281
+ d_k = d_model // n_heads if d_k is None else d_k
282
+ d_v = d_model // n_heads if d_v is None else d_v
283
+
284
+ self.n_heads, self.d_k, self.d_v = n_heads, d_k, d_v
285
+
286
+ self.W_Q = nn.Linear(d_model, d_k * n_heads, bias=qkv_bias)
287
+ self.W_K = nn.Linear(d_model, d_k * n_heads, bias=qkv_bias)
288
+ self.W_V = nn.Linear(d_model, d_v * n_heads, bias=qkv_bias)
289
+
290
+ # Scaled Dot-Product Attention (multiple heads)
291
+ self.res_attention = res_attention
292
+ self.sdp_attn = _ScaledDotProductAttention(d_model, n_heads, attn_dropout=attn_dropout, res_attention=self.res_attention, lsa=lsa)
293
+
294
+ # Poject output
295
+ self.to_out = nn.Sequential(nn.Linear(n_heads * d_v, d_model), nn.Dropout(proj_dropout))
296
+
297
+
298
+ def forward(self, Q:Tensor, K:Optional[Tensor]=None, V:Optional[Tensor]=None, prev:Optional[Tensor]=None,
299
+ key_padding_mask:Optional[Tensor]=None, attn_mask:Optional[Tensor]=None):
300
+
301
+ bs = Q.size(0)
302
+ if K is None: K = Q
303
+ if V is None: V = Q
304
+
305
+ # Linear (+ split in multiple heads)
306
+ q_s = self.W_Q(Q).view(bs, -1, self.n_heads, self.d_k).transpose(1,2) # q_s : [bs x n_heads x max_q_len x d_k]
307
+ k_s = self.W_K(K).view(bs, -1, self.n_heads, self.d_k).permute(0,2,3,1) # k_s : [bs x n_heads x d_k x q_len] - transpose(1,2) + transpose(2,3)
308
+ v_s = self.W_V(V).view(bs, -1, self.n_heads, self.d_v).transpose(1,2) # v_s : [bs x n_heads x q_len x d_v]
309
+
310
+ # Apply Scaled Dot-Product Attention (multiple heads)
311
+ if self.res_attention:
312
+ output, attn_weights, attn_scores = self.sdp_attn(q_s, k_s, v_s, prev=prev, key_padding_mask=key_padding_mask, attn_mask=attn_mask)
313
+ else:
314
+ output, attn_weights = self.sdp_attn(q_s, k_s, v_s, key_padding_mask=key_padding_mask, attn_mask=attn_mask)
315
+ # output: [bs x n_heads x q_len x d_v], attn: [bs x n_heads x q_len x q_len], scores: [bs x n_heads x max_q_len x q_len]
316
+
317
+ # back to the original inputs dimensions
318
+ output = output.transpose(1, 2).contiguous().view(bs, -1, self.n_heads * self.d_v) # output: [bs x q_len x n_heads * d_v]
319
+ output = self.to_out(output)
320
+
321
+ if self.res_attention: return output, attn_weights, attn_scores
322
+ else: return output, attn_weights
323
+
324
+
325
+ class _ScaledDotProductAttention(nn.Module):
326
+ r"""Scaled Dot-Product Attention module (Attention is all you need by Vaswani et al., 2017) with optional residual attention from previous layer
327
+ (Realformer: Transformer likes residual attention by He et al, 2020) and locality self sttention (Vision Transformer for Small-Size Datasets
328
+ by Lee et al, 2021)"""
329
+
330
+ def __init__(self, d_model, n_heads, attn_dropout=0., res_attention=False, lsa=False):
331
+ super().__init__()
332
+ self.attn_dropout = nn.Dropout(attn_dropout)
333
+ self.res_attention = res_attention
334
+ head_dim = d_model // n_heads
335
+ self.scale = nn.Parameter(torch.tensor(head_dim ** -0.5), requires_grad=lsa)
336
+ self.lsa = lsa
337
+
338
+ def forward(self, q:Tensor, k:Tensor, v:Tensor, prev:Optional[Tensor]=None, key_padding_mask:Optional[Tensor]=None, attn_mask:Optional[Tensor]=None):
339
+ '''
340
+ Input shape:
341
+ q : [bs x n_heads x max_q_len x d_k]
342
+ k : [bs x n_heads x d_k x seq_len]
343
+ v : [bs x n_heads x seq_len x d_v]
344
+ prev : [bs x n_heads x q_len x seq_len]
345
+ key_padding_mask: [bs x seq_len]
346
+ attn_mask : [1 x seq_len x seq_len]
347
+ Output shape:
348
+ output: [bs x n_heads x q_len x d_v]
349
+ attn : [bs x n_heads x q_len x seq_len]
350
+ scores : [bs x n_heads x q_len x seq_len]
351
+ '''
352
+
353
+ # Scaled MatMul (q, k) - similarity scores for all pairs of positions in an input sequence
354
+ attn_scores = torch.matmul(q, k) * self.scale # attn_scores : [bs x n_heads x max_q_len x q_len]
355
+
356
+ # Add pre-softmax attention scores from the previous layer (optional)
357
+ if prev is not None: attn_scores = attn_scores + prev
358
+
359
+ # Attention mask (optional)
360
+ if attn_mask is not None: # attn_mask with shape [q_len x seq_len] - only used when q_len == seq_len
361
+ if attn_mask.dtype == torch.bool:
362
+ attn_scores.masked_fill_(attn_mask, -np.inf)
363
+ else:
364
+ attn_scores += attn_mask
365
+
366
+ # Key padding mask (optional)
367
+ if key_padding_mask is not None: # mask with shape [bs x q_len] (only when max_w_len == q_len)
368
+ attn_scores.masked_fill_(key_padding_mask.unsqueeze(1).unsqueeze(2), -np.inf)
369
+
370
+ # normalize the attention weights
371
+ attn_weights = F.softmax(attn_scores, dim=-1) # attn_weights : [bs x n_heads x max_q_len x q_len]
372
+ attn_weights = self.attn_dropout(attn_weights)
373
+
374
+ # compute the new values given the attention weights
375
+ output = torch.matmul(attn_weights, v) # output: [bs x n_heads x max_q_len x d_v]
376
+
377
+ if self.res_attention: return output, attn_weights, attn_scores
378
+ else: return output, attn_weights
379
+
time_series_forecasting/layers/PatchTST_layers.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __all__ = ['Transpose', 'get_activation_fn', 'moving_avg', 'series_decomp', 'PositionalEncoding', 'SinCosPosEncoding', 'Coord2dPosEncoding', 'Coord1dPosEncoding', 'positional_encoding']
2
+
3
+ import torch
4
+ from torch import nn
5
+ import math
6
+
7
+ class Transpose(nn.Module):
8
+ def __init__(self, *dims, contiguous=False):
9
+ super().__init__()
10
+ self.dims, self.contiguous = dims, contiguous
11
+ def forward(self, x):
12
+ if self.contiguous: return x.transpose(*self.dims).contiguous()
13
+ else: return x.transpose(*self.dims)
14
+
15
+
16
+ def get_activation_fn(activation):
17
+ if callable(activation): return activation()
18
+ elif activation.lower() == "relu": return nn.ReLU()
19
+ elif activation.lower() == "gelu": return nn.GELU()
20
+ raise ValueError(f'{activation} is not available. You can use "relu", "gelu", or a callable')
21
+
22
+
23
+ # decomposition
24
+
25
+ class moving_avg(nn.Module):
26
+ """
27
+ Moving average block to highlight the trend of time series
28
+ """
29
+ def __init__(self, kernel_size, stride):
30
+ super(moving_avg, self).__init__()
31
+ self.kernel_size = kernel_size
32
+ self.avg = nn.AvgPool1d(kernel_size=kernel_size, stride=stride, padding=0)
33
+
34
+ def forward(self, x):
35
+ # padding on the both ends of time series
36
+ front = x[:, 0:1, :].repeat(1, (self.kernel_size - 1) // 2, 1)
37
+ end = x[:, -1:, :].repeat(1, (self.kernel_size - 1) // 2, 1)
38
+ x = torch.cat([front, x, end], dim=1)
39
+ x = self.avg(x.permute(0, 2, 1))
40
+ x = x.permute(0, 2, 1)
41
+ return x
42
+
43
+
44
+ class series_decomp(nn.Module):
45
+ """
46
+ Series decomposition block
47
+ """
48
+ def __init__(self, kernel_size):
49
+ super(series_decomp, self).__init__()
50
+ self.moving_avg = moving_avg(kernel_size, stride=1)
51
+
52
+ def forward(self, x):
53
+ moving_mean = self.moving_avg(x)
54
+ res = x - moving_mean
55
+ return res, moving_mean
56
+
57
+
58
+
59
+ # pos_encoding
60
+
61
+ def PositionalEncoding(q_len, d_model, normalize=True):
62
+ pe = torch.zeros(q_len, d_model)
63
+ position = torch.arange(0, q_len).unsqueeze(1)
64
+ div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
65
+ pe[:, 0::2] = torch.sin(position * div_term)
66
+ pe[:, 1::2] = torch.cos(position * div_term)
67
+ if normalize:
68
+ pe = pe - pe.mean()
69
+ pe = pe / (pe.std() * 10)
70
+ return pe
71
+
72
+ SinCosPosEncoding = PositionalEncoding
73
+
74
+ def Coord2dPosEncoding(q_len, d_model, exponential=False, normalize=True, eps=1e-3, verbose=False):
75
+ x = .5 if exponential else 1
76
+ i = 0
77
+ for i in range(100):
78
+ cpe = 2 * (torch.linspace(0, 1, q_len).reshape(-1, 1) ** x) * (torch.linspace(0, 1, d_model).reshape(1, -1) ** x) - 1
79
+ pv(f'{i:4.0f} {x:5.3f} {cpe.mean():+6.3f}', verbose)
80
+ if abs(cpe.mean()) <= eps: break
81
+ elif cpe.mean() > eps: x += .001
82
+ else: x -= .001
83
+ i += 1
84
+ if normalize:
85
+ cpe = cpe - cpe.mean()
86
+ cpe = cpe / (cpe.std() * 10)
87
+ return cpe
88
+
89
+ def Coord1dPosEncoding(q_len, exponential=False, normalize=True):
90
+ cpe = (2 * (torch.linspace(0, 1, q_len).reshape(-1, 1)**(.5 if exponential else 1)) - 1)
91
+ if normalize:
92
+ cpe = cpe - cpe.mean()
93
+ cpe = cpe / (cpe.std() * 10)
94
+ return cpe
95
+
96
+ def positional_encoding(pe, learn_pe, q_len, d_model):
97
+ # Positional encoding
98
+ if pe == None:
99
+ W_pos = torch.empty((q_len, d_model)) # pe = None and learn_pe = False can be used to measure impact of pe
100
+ nn.init.uniform_(W_pos, -0.02, 0.02)
101
+ learn_pe = False
102
+ elif pe == 'zero':
103
+ W_pos = torch.empty((q_len, 1))
104
+ nn.init.uniform_(W_pos, -0.02, 0.02)
105
+ elif pe == 'zeros':
106
+ W_pos = torch.empty((q_len, d_model))
107
+ nn.init.uniform_(W_pos, -0.02, 0.02)
108
+ elif pe == 'normal' or pe == 'gauss':
109
+ W_pos = torch.zeros((q_len, 1))
110
+ torch.nn.init.normal_(W_pos, mean=0.0, std=0.1)
111
+ elif pe == 'uniform':
112
+ W_pos = torch.zeros((q_len, 1))
113
+ nn.init.uniform_(W_pos, a=0.0, b=0.1)
114
+ elif pe == 'lin1d': W_pos = Coord1dPosEncoding(q_len, exponential=False, normalize=True)
115
+ elif pe == 'exp1d': W_pos = Coord1dPosEncoding(q_len, exponential=True, normalize=True)
116
+ elif pe == 'lin2d': W_pos = Coord2dPosEncoding(q_len, d_model, exponential=False, normalize=True)
117
+ elif pe == 'exp2d': W_pos = Coord2dPosEncoding(q_len, d_model, exponential=True, normalize=True)
118
+ elif pe == 'sincos': W_pos = PositionalEncoding(q_len, d_model, normalize=True)
119
+ else: raise ValueError(f"{pe} is not a valid pe (positional encoder. Available types: 'gauss'=='normal', \
120
+ 'zeros', 'zero', uniform', 'lin1d', 'exp1d', 'lin2d', 'exp2d', 'sincos', None.)")
121
+ return nn.Parameter(W_pos, requires_grad=learn_pe)
time_series_forecasting/layers/RevIN.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # code from https://github.com/ts-kim/RevIN, with minor modifications
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+
6
+ class RevIN(nn.Module):
7
+ def __init__(self, num_features: int, eps=1e-5, affine=True, subtract_last=False):
8
+ """
9
+ :param num_features: the number of features or channels
10
+ :param eps: a value added for numerical stability
11
+ :param affine: if True, RevIN has learnable affine parameters
12
+ """
13
+ super(RevIN, self).__init__()
14
+ self.num_features = num_features
15
+ self.eps = eps
16
+ self.affine = affine
17
+ self.subtract_last = subtract_last
18
+ if self.affine:
19
+ self._init_params()
20
+
21
+ def forward(self, x, mode:str):
22
+ if mode == 'norm':
23
+ self._get_statistics(x)
24
+ x = self._normalize(x)
25
+ elif mode == 'denorm':
26
+ x = self._denormalize(x)
27
+ else: raise NotImplementedError
28
+ return x
29
+
30
+ def _init_params(self):
31
+ # initialize RevIN params: (C,)
32
+ self.affine_weight = nn.Parameter(torch.ones(self.num_features))
33
+ self.affine_bias = nn.Parameter(torch.zeros(self.num_features))
34
+
35
+ def _get_statistics(self, x):
36
+ dim2reduce = tuple(range(1, x.ndim-1))
37
+ if self.subtract_last:
38
+ self.last = x[:,-1,:].unsqueeze(1)
39
+ else:
40
+ self.mean = torch.mean(x, dim=dim2reduce, keepdim=True).detach()
41
+ self.stdev = torch.sqrt(torch.var(x, dim=dim2reduce, keepdim=True, unbiased=False) + self.eps).detach()
42
+
43
+ def _normalize(self, x):
44
+ if self.subtract_last:
45
+ x = x - self.last
46
+ else:
47
+ x = x - self.mean
48
+ x = x / self.stdev
49
+ if self.affine:
50
+ x = x * self.affine_weight
51
+ x = x + self.affine_bias
52
+ return x
53
+
54
+ def _denormalize(self, x):
55
+ if self.affine:
56
+ x = x - self.affine_bias
57
+ x = x / (self.affine_weight + self.eps*self.eps)
58
+ x = x * self.stdev
59
+ if self.subtract_last:
60
+ x = x + self.last
61
+ else:
62
+ x = x + self.mean
63
+ return x
time_series_forecasting/layers/SelfAttention_Family.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ import matplotlib.pyplot as plt
6
+
7
+ import numpy as np
8
+ import math
9
+ from math import sqrt
10
+ from utils.masking import TriangularCausalMask, ProbMask
11
+ import os
12
+ from einops import rearrange, repeat
13
+
14
+ class FullAttention(nn.Module):
15
+ def __init__(self, mask_flag=True, factor=5, scale=None, attention_dropout=0.1, output_attention=False):
16
+ super(FullAttention, self).__init__()
17
+ self.scale = scale
18
+ self.mask_flag = mask_flag
19
+ self.output_attention = output_attention
20
+ self.dropout = nn.Dropout(attention_dropout)
21
+
22
+ def forward(self, queries, keys, values, attn_mask, tau=None, delta=None):
23
+ B, L, H, E = queries.shape
24
+ _, S, _, D = values.shape
25
+ scale = self.scale or 1. / sqrt(E)
26
+
27
+ scores = torch.einsum("blhe,bshe->bhls", queries, keys)
28
+
29
+ if self.mask_flag:
30
+ if attn_mask is None:
31
+ attn_mask = TriangularCausalMask(B, L, device=queries.device)
32
+
33
+ scores.masked_fill_(attn_mask.mask, -np.inf)
34
+
35
+ A = self.dropout(torch.softmax(scale * scores, dim=-1))
36
+ V = torch.einsum("bhls,bshd->blhd", A, values)
37
+
38
+ if self.output_attention:
39
+ return (V.contiguous(), A)
40
+ else:
41
+ return (V.contiguous(), None)
42
+
43
+
44
+ class ProbAttention(nn.Module):
45
+ def __init__(self, mask_flag=True, factor=5, scale=None, attention_dropout=0.1, output_attention=False):
46
+ super(ProbAttention, self).__init__()
47
+ self.factor = factor
48
+ self.scale = scale
49
+ self.mask_flag = mask_flag
50
+ self.output_attention = output_attention
51
+ self.dropout = nn.Dropout(attention_dropout)
52
+
53
+ def _prob_QK(self, Q, K, sample_k, n_top): # n_top: c*ln(L_q)
54
+ # Q [B, H, L, D]
55
+ B, H, L_K, E = K.shape
56
+ _, _, L_Q, _ = Q.shape
57
+
58
+ # calculate the sampled Q_K
59
+ K_expand = K.unsqueeze(-3).expand(B, H, L_Q, L_K, E)
60
+ index_sample = torch.randint(L_K, (L_Q, sample_k)) # real U = U_part(factor*ln(L_k))*L_q
61
+ K_sample = K_expand[:, :, torch.arange(L_Q).unsqueeze(1), index_sample, :]
62
+ Q_K_sample = torch.matmul(Q.unsqueeze(-2), K_sample.transpose(-2, -1)).squeeze()
63
+
64
+ # find the Top_k query with sparisty measurement
65
+ M = Q_K_sample.max(-1)[0] - torch.div(Q_K_sample.sum(-1), L_K)
66
+ M_top = M.topk(n_top, sorted=False)[1]
67
+
68
+ # use the reduced Q to calculate Q_K
69
+ Q_reduce = Q[torch.arange(B)[:, None, None],
70
+ torch.arange(H)[None, :, None],
71
+ M_top, :] # factor*ln(L_q)
72
+ Q_K = torch.matmul(Q_reduce, K.transpose(-2, -1)) # factor*ln(L_q)*L_k
73
+
74
+ return Q_K, M_top
75
+
76
+ def _get_initial_context(self, V, L_Q):
77
+ B, H, L_V, D = V.shape
78
+ if not self.mask_flag:
79
+ # V_sum = V.sum(dim=-2)
80
+ V_sum = V.mean(dim=-2)
81
+ contex = V_sum.unsqueeze(-2).expand(B, H, L_Q, V_sum.shape[-1]).clone()
82
+ else: # use mask
83
+ assert (L_Q == L_V) # requires that L_Q == L_V, i.e. for self-attention only
84
+ contex = V.cumsum(dim=-2)
85
+ return contex
86
+
87
+ def _update_context(self, context_in, V, scores, index, L_Q, attn_mask):
88
+ B, H, L_V, D = V.shape
89
+
90
+ if self.mask_flag:
91
+ attn_mask = ProbMask(B, H, L_Q, index, scores, device=V.device)
92
+ scores.masked_fill_(attn_mask.mask, -np.inf)
93
+
94
+ attn = torch.softmax(scores, dim=-1) # nn.Softmax(dim=-1)(scores)
95
+
96
+ context_in[torch.arange(B)[:, None, None],
97
+ torch.arange(H)[None, :, None],
98
+ index, :] = torch.matmul(attn, V).type_as(context_in)
99
+ if self.output_attention:
100
+ attns = (torch.ones([B, H, L_V, L_V]) / L_V).type_as(attn).to(attn.device)
101
+ attns[torch.arange(B)[:, None, None], torch.arange(H)[None, :, None], index, :] = attn
102
+ return (context_in, attns)
103
+ else:
104
+ return (context_in, None)
105
+
106
+ def forward(self, queries, keys, values, attn_mask):
107
+ B, L_Q, H, D = queries.shape
108
+ _, L_K, _, _ = keys.shape
109
+
110
+ queries = queries.transpose(2, 1)
111
+ keys = keys.transpose(2, 1)
112
+ values = values.transpose(2, 1)
113
+
114
+ U_part = self.factor * np.ceil(np.log(L_K)).astype('int').item() # c*ln(L_k)
115
+ u = self.factor * np.ceil(np.log(L_Q)).astype('int').item() # c*ln(L_q)
116
+
117
+ U_part = U_part if U_part < L_K else L_K
118
+ u = u if u < L_Q else L_Q
119
+
120
+ scores_top, index = self._prob_QK(queries, keys, sample_k=U_part, n_top=u)
121
+
122
+ # add scale factor
123
+ scale = self.scale or 1. / sqrt(D)
124
+ if scale is not None:
125
+ scores_top = scores_top * scale
126
+ # get the context
127
+ context = self._get_initial_context(values, L_Q)
128
+ # update the context with selected top_k queries
129
+ context, attn = self._update_context(context, values, scores_top, index, L_Q, attn_mask)
130
+
131
+ return context.contiguous(), attn
132
+
133
+
134
+ class AttentionLayer(nn.Module):
135
+ def __init__(self, attention, d_model, n_heads, d_keys=None,
136
+ d_values=None):
137
+ super(AttentionLayer, self).__init__()
138
+
139
+ d_keys = d_keys or (d_model // n_heads)
140
+ d_values = d_values or (d_model // n_heads)
141
+
142
+ self.inner_attention = attention
143
+ self.query_projection = nn.Linear(d_model, d_keys * n_heads)
144
+ self.key_projection = nn.Linear(d_model, d_keys * n_heads)
145
+ self.value_projection = nn.Linear(d_model, d_values * n_heads)
146
+ self.out_projection = nn.Linear(d_values * n_heads, d_model)
147
+ self.n_heads = n_heads
148
+
149
+ def forward(self, queries, keys, values, attn_mask, tau=None, delta=None):
150
+ B, L, _ = queries.shape
151
+ _, S, _ = keys.shape
152
+ H = self.n_heads
153
+
154
+ queries = self.query_projection(queries).view(B, L, H, -1)
155
+ keys = self.key_projection(keys).view(B, S, H, -1)
156
+ values = self.value_projection(values).view(B, S, H, -1)
157
+
158
+ out, attn = self.inner_attention(
159
+ queries,
160
+ keys,
161
+ values,
162
+ attn_mask
163
+ )
164
+ out = out.view(B, L, -1)
165
+
166
+ return self.out_projection(out), attn
167
+
168
+ class TwoStageAttentionLayer(nn.Module):
169
+ '''
170
+ The Two Stage Attention (TSA) Layer
171
+ input/output shape: [batch_size, Data_dim(D), Seg_num(L), d_model]
172
+ '''
173
+
174
+ def __init__(self, configs,
175
+ seg_num, factor, d_model, n_heads, d_ff=None, dropout=0.1):
176
+ super(TwoStageAttentionLayer, self).__init__()
177
+ d_ff = d_ff or 4 * d_model
178
+ self.time_attention = AttentionLayer(FullAttention(False, configs.factor, attention_dropout=configs.dropout,
179
+ output_attention=configs.output_attention), d_model, n_heads)
180
+ self.dim_sender = AttentionLayer(FullAttention(False, configs.factor, attention_dropout=configs.dropout,
181
+ output_attention=configs.output_attention), d_model, n_heads)
182
+ self.dim_receiver = AttentionLayer(FullAttention(False, configs.factor, attention_dropout=configs.dropout,
183
+ output_attention=configs.output_attention), d_model, n_heads)
184
+ self.router = nn.Parameter(torch.randn(seg_num, factor, d_model))
185
+
186
+ self.dropout = nn.Dropout(dropout)
187
+
188
+ self.norm1 = nn.LayerNorm(d_model)
189
+ self.norm2 = nn.LayerNorm(d_model)
190
+ self.norm3 = nn.LayerNorm(d_model)
191
+ self.norm4 = nn.LayerNorm(d_model)
192
+
193
+ self.MLP1 = nn.Sequential(nn.Linear(d_model, d_ff),
194
+ nn.GELU(),
195
+ nn.Linear(d_ff, d_model))
196
+ self.MLP2 = nn.Sequential(nn.Linear(d_model, d_ff),
197
+ nn.GELU(),
198
+ nn.Linear(d_ff, d_model))
199
+
200
+ def forward(self, x, attn_mask=None, tau=None, delta=None):
201
+ # Cross Time Stage: Directly apply MSA to each dimension
202
+ batch = x.shape[0]
203
+ time_in = rearrange(x, 'b ts_d seg_num d_model -> (b ts_d) seg_num d_model')
204
+ time_enc, attn = self.time_attention(
205
+ time_in, time_in, time_in, attn_mask=None, tau=None, delta=None
206
+ )
207
+ dim_in = time_in + self.dropout(time_enc)
208
+ dim_in = self.norm1(dim_in)
209
+ dim_in = dim_in + self.dropout(self.MLP1(dim_in))
210
+ dim_in = self.norm2(dim_in)
211
+
212
+ # Cross Dimension Stage: use a small set of learnable vectors to aggregate and distribute messages to build the D-to-D connection
213
+ dim_send = rearrange(dim_in, '(b ts_d) seg_num d_model -> (b seg_num) ts_d d_model', b=batch)
214
+ batch_router = repeat(self.router, 'seg_num factor d_model -> (repeat seg_num) factor d_model', repeat=batch)
215
+ dim_buffer, attn = self.dim_sender(batch_router, dim_send, dim_send, attn_mask=None, tau=None, delta=None)
216
+ dim_receive, attn = self.dim_receiver(dim_send, dim_buffer, dim_buffer, attn_mask=None, tau=None, delta=None)
217
+ dim_enc = dim_send + self.dropout(dim_receive)
218
+ dim_enc = self.norm3(dim_enc)
219
+ dim_enc = dim_enc + self.dropout(self.MLP2(dim_enc))
220
+ dim_enc = self.norm4(dim_enc)
221
+
222
+ final_out = rearrange(dim_enc, '(b seg_num) ts_d d_model -> b ts_d seg_num d_model', b=batch)
223
+
224
+ return final_out
time_series_forecasting/layers/Transformer_EncDec.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+
6
+ class ConvLayer(nn.Module):
7
+ def __init__(self, c_in):
8
+ super(ConvLayer, self).__init__()
9
+ self.downConv = nn.Conv1d(in_channels=c_in,
10
+ out_channels=c_in,
11
+ kernel_size=3,
12
+ padding=2,
13
+ padding_mode='circular')
14
+ self.norm = nn.BatchNorm1d(c_in)
15
+ self.activation = nn.ELU()
16
+ self.maxPool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1)
17
+
18
+ def forward(self, x):
19
+ x = self.downConv(x.permute(0, 2, 1))
20
+ x = self.norm(x)
21
+ x = self.activation(x)
22
+ x = self.maxPool(x)
23
+ x = x.transpose(1, 2)
24
+ return x
25
+
26
+
27
+ class EncoderLayer(nn.Module):
28
+ def __init__(self, attention, d_model, d_ff=None, dropout=0.1, activation="relu"):
29
+ super(EncoderLayer, self).__init__()
30
+ d_ff = d_ff or 4 * d_model
31
+ self.attention = attention
32
+ self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
33
+ self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
34
+ self.norm1 = nn.LayerNorm(d_model)
35
+ self.norm2 = nn.LayerNorm(d_model)
36
+ self.dropout = nn.Dropout(dropout)
37
+ self.activation = F.relu if activation == "relu" else F.gelu
38
+
39
+ def forward(self, x, attn_mask=None):
40
+ new_x, attn = self.attention(
41
+ x, x, x,
42
+ attn_mask=attn_mask
43
+ )
44
+ x = x + self.dropout(new_x)
45
+
46
+ y = x = self.norm1(x)
47
+ y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1))))
48
+ y = self.dropout(self.conv2(y).transpose(-1, 1))
49
+
50
+ return self.norm2(x + y), attn
51
+
52
+
53
+ class Encoder(nn.Module):
54
+ def __init__(self, attn_layers, conv_layers=None, norm_layer=None):
55
+ super(Encoder, self).__init__()
56
+ self.attn_layers = nn.ModuleList(attn_layers)
57
+ self.conv_layers = nn.ModuleList(conv_layers) if conv_layers is not None else None
58
+ self.norm = norm_layer
59
+
60
+ def forward(self, x, attn_mask=None):
61
+ # x [B, L, D]
62
+ attns = []
63
+ if self.conv_layers is not None:
64
+ for attn_layer, conv_layer in zip(self.attn_layers, self.conv_layers):
65
+ x, attn = attn_layer(x, attn_mask=attn_mask)
66
+ x = conv_layer(x)
67
+ attns.append(attn)
68
+ x, attn = self.attn_layers[-1](x)
69
+ attns.append(attn)
70
+ else:
71
+ for attn_layer in self.attn_layers:
72
+ x, attn = attn_layer(x, attn_mask=attn_mask)
73
+ attns.append(attn)
74
+
75
+ if self.norm is not None:
76
+ x = self.norm(x)
77
+
78
+ return x, attns
79
+
80
+
81
+ class DecoderLayer(nn.Module):
82
+ def __init__(self, self_attention, cross_attention, d_model, d_ff=None,
83
+ dropout=0.1, activation="relu"):
84
+ super(DecoderLayer, self).__init__()
85
+ d_ff = d_ff or 4 * d_model
86
+ self.self_attention = self_attention
87
+ self.cross_attention = cross_attention
88
+ self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
89
+ self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
90
+ self.norm1 = nn.LayerNorm(d_model)
91
+ self.norm2 = nn.LayerNorm(d_model)
92
+ self.norm3 = nn.LayerNorm(d_model)
93
+ self.dropout = nn.Dropout(dropout)
94
+ self.activation = F.relu if activation == "relu" else F.gelu
95
+
96
+ def forward(self, x, cross, x_mask=None, cross_mask=None):
97
+ x = x + self.dropout(self.self_attention(
98
+ x, x, x,
99
+ attn_mask=x_mask
100
+ )[0])
101
+ x = self.norm1(x)
102
+
103
+ x = x + self.dropout(self.cross_attention(
104
+ x, cross, cross,
105
+ attn_mask=cross_mask
106
+ )[0])
107
+
108
+ y = x = self.norm2(x)
109
+ y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1))))
110
+ y = self.dropout(self.conv2(y).transpose(-1, 1))
111
+
112
+ return self.norm3(x + y)
113
+
114
+
115
+ class Decoder(nn.Module):
116
+ def __init__(self, layers, norm_layer=None, projection=None):
117
+ super(Decoder, self).__init__()
118
+ self.layers = nn.ModuleList(layers)
119
+ self.norm = norm_layer
120
+ self.projection = projection
121
+
122
+ def forward(self, x, cross, x_mask=None, cross_mask=None):
123
+ for layer in self.layers:
124
+ x = layer(x, cross, x_mask=x_mask, cross_mask=cross_mask)
125
+
126
+ if self.norm is not None:
127
+ x = self.norm(x)
128
+
129
+ if self.projection is not None:
130
+ x = self.projection(x)
131
+ return x
time_series_forecasting/models/Autoformer.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from layers.Embed import DataEmbedding, DataEmbedding_wo_pos,DataEmbedding_wo_pos_temp,DataEmbedding_wo_temp
5
+ from layers.AutoCorrelation import AutoCorrelation, AutoCorrelationLayer
6
+ from layers.Autoformer_EncDec import Encoder, Decoder, EncoderLayer, DecoderLayer, my_Layernorm, series_decomp
7
+ import math
8
+ import numpy as np
9
+
10
+
11
+ class Model(nn.Module):
12
+ """
13
+ Autoformer is the first method to achieve the series-wise connection,
14
+ with inherent O(LlogL) complexity
15
+ """
16
+ def __init__(self, configs):
17
+ super(Model, self).__init__()
18
+ self.seq_len = configs.seq_len
19
+ self.label_len = configs.label_len
20
+ self.pred_len = configs.pred_len
21
+ self.output_attention = configs.output_attention
22
+
23
+ # Decomp
24
+ kernel_size = configs.moving_avg
25
+ self.decomp = series_decomp(kernel_size)
26
+
27
+ # Embedding
28
+ # The series-wise connection inherently contains the sequential information.
29
+ # Thus, we can discard the position embedding of transformers.
30
+ if configs.embed_type == 0:
31
+ self.enc_embedding = DataEmbedding_wo_pos(configs.enc_in, configs.d_model, configs.embed, configs.freq,
32
+ configs.dropout)
33
+ self.dec_embedding = DataEmbedding_wo_pos(configs.dec_in, configs.d_model, configs.embed, configs.freq,
34
+ configs.dropout)
35
+ elif configs.embed_type == 1:
36
+ self.enc_embedding = DataEmbedding(configs.enc_in, configs.d_model, configs.embed, configs.freq,
37
+ configs.dropout)
38
+ self.dec_embedding = DataEmbedding(configs.dec_in, configs.d_model, configs.embed, configs.freq,
39
+ configs.dropout)
40
+ elif configs.embed_type == 2:
41
+ self.enc_embedding = DataEmbedding_wo_pos(configs.enc_in, configs.d_model, configs.embed, configs.freq,
42
+ configs.dropout)
43
+ self.dec_embedding = DataEmbedding_wo_pos(configs.dec_in, configs.d_model, configs.embed, configs.freq,
44
+ configs.dropout)
45
+
46
+ elif configs.embed_type == 3:
47
+ self.enc_embedding = DataEmbedding_wo_temp(configs.enc_in, configs.d_model, configs.embed, configs.freq,
48
+ configs.dropout)
49
+ self.dec_embedding = DataEmbedding_wo_temp(configs.dec_in, configs.d_model, configs.embed, configs.freq,
50
+ configs.dropout)
51
+ elif configs.embed_type == 4:
52
+ self.enc_embedding = DataEmbedding_wo_pos_temp(configs.enc_in, configs.d_model, configs.embed, configs.freq,
53
+ configs.dropout)
54
+ self.dec_embedding = DataEmbedding_wo_pos_temp(configs.dec_in, configs.d_model, configs.embed, configs.freq,
55
+ configs.dropout)
56
+
57
+ # Encoder
58
+ self.encoder = Encoder(
59
+ [
60
+ EncoderLayer(
61
+ AutoCorrelationLayer(
62
+ AutoCorrelation(False, configs.factor, attention_dropout=configs.dropout,
63
+ output_attention=configs.output_attention),
64
+ configs.d_model, configs.n_heads),
65
+ configs.d_model,
66
+ configs.d_ff,
67
+ moving_avg=configs.moving_avg,
68
+ dropout=configs.dropout,
69
+ activation=configs.activation
70
+ ) for l in range(configs.e_layers)
71
+ ],
72
+ norm_layer=my_Layernorm(configs.d_model)
73
+ )
74
+ # Decoder
75
+ self.decoder = Decoder(
76
+ [
77
+ DecoderLayer(
78
+ AutoCorrelationLayer(
79
+ AutoCorrelation(True, configs.factor, attention_dropout=configs.dropout,
80
+ output_attention=False),
81
+ configs.d_model, configs.n_heads),
82
+ AutoCorrelationLayer(
83
+ AutoCorrelation(False, configs.factor, attention_dropout=configs.dropout,
84
+ output_attention=False),
85
+ configs.d_model, configs.n_heads),
86
+ configs.d_model,
87
+ configs.c_out,
88
+ configs.d_ff,
89
+ moving_avg=configs.moving_avg,
90
+ dropout=configs.dropout,
91
+ activation=configs.activation,
92
+ )
93
+ for l in range(configs.d_layers)
94
+ ],
95
+ norm_layer=my_Layernorm(configs.d_model),
96
+ projection=nn.Linear(configs.d_model, configs.c_out, bias=True)
97
+ )
98
+
99
+
100
+
101
+ def forward(self, x_enc, x_mark_enc, x_dec, x_mark_dec,
102
+ enc_self_mask=None, dec_self_mask=None, dec_enc_mask=None):
103
+ # decomp init
104
+ mean = torch.mean(x_enc, dim=1).unsqueeze(1).repeat(1, self.pred_len, 1)
105
+ zeros = torch.zeros([x_dec.shape[0], self.pred_len, x_dec.shape[2]], device=x_enc.device)
106
+ seasonal_init, trend_init = self.decomp(x_enc)
107
+ # decoder input
108
+ trend_init = torch.cat([trend_init[:, -self.label_len:, :], mean], dim=1)
109
+ seasonal_init = torch.cat([seasonal_init[:, -self.label_len:, :], zeros], dim=1)
110
+ # enc
111
+ enc_out = self.enc_embedding(x_enc, x_mark_enc)
112
+ enc_out, attns = self.encoder(enc_out, attn_mask=enc_self_mask)
113
+ # dec
114
+ dec_out = self.dec_embedding(seasonal_init, x_mark_dec)
115
+ seasonal_part, trend_part = self.decoder(dec_out, enc_out, x_mask=dec_self_mask, cross_mask=dec_enc_mask,
116
+ trend=trend_init)
117
+ # final
118
+ dec_out = trend_part + seasonal_part
119
+
120
+ if self.output_attention:
121
+ return dec_out[:, -self.pred_len:, :], attns
122
+ else:
123
+ return dec_out[:, -self.pred_len:, :] # [B, L, D]
time_series_forecasting/models/CycleNet.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ class RecurrentCycle(torch.nn.Module):
5
+ # Thanks for the contribution of wayhoww.
6
+ # The new implementation uses index arithmetic with modulo to directly gather cyclic data in a single operation,
7
+ # while the original implementation manually rolls and repeats the data through looping.
8
+ # It achieves a significant speed improvement (2x ~ 3x acceleration).
9
+ # See https://github.com/ACAT-SCUT/CycleNet/pull/4 for more details.
10
+ def __init__(self, cycle_len, channel_size):
11
+ super(RecurrentCycle, self).__init__()
12
+ self.cycle_len = cycle_len
13
+ self.channel_size = channel_size
14
+ self.data = torch.nn.Parameter(torch.zeros(cycle_len, channel_size), requires_grad=True)
15
+
16
+ def forward(self, index, length):
17
+ gather_index = (index.view(-1, 1) + torch.arange(length, device=index.device).view(1, -1)) % self.cycle_len
18
+ return self.data[gather_index]
19
+
20
+
21
+ class Model(nn.Module):
22
+ def __init__(self, configs):
23
+ super(Model, self).__init__()
24
+
25
+ self.seq_len = configs.seq_len
26
+ self.pred_len = configs.pred_len
27
+ self.enc_in = configs.enc_in
28
+ self.cycle_len = configs.cycle
29
+ self.model_type = configs.model_type
30
+ self.d_model = configs.d_model
31
+ self.use_revin = configs.use_revin
32
+
33
+ self.cycleQueue = RecurrentCycle(cycle_len=self.cycle_len, channel_size=self.enc_in)
34
+
35
+ assert self.model_type in ['linear', 'mlp']
36
+ if self.model_type == 'linear':
37
+ self.model = nn.Linear(self.seq_len, self.pred_len)
38
+ elif self.model_type == 'mlp':
39
+ self.model = nn.Sequential(
40
+ nn.Linear(self.seq_len, self.d_model),
41
+ nn.ReLU(),
42
+ nn.Linear(self.d_model, self.pred_len)
43
+ )
44
+
45
+ def forward(self, x, cycle_index):
46
+ # x: (batch_size, seq_len, enc_in), cycle_index: (batch_size,)
47
+
48
+ # instance norm
49
+ if self.use_revin:
50
+ seq_mean = torch.mean(x, dim=1, keepdim=True)
51
+ seq_var = torch.var(x, dim=1, keepdim=True) + 1e-5
52
+ x = (x - seq_mean) / torch.sqrt(seq_var)
53
+
54
+ # remove the cycle of the input data
55
+ x = x - self.cycleQueue(cycle_index, self.seq_len)
56
+
57
+ # forecasting with channel independence (parameters-sharing)
58
+ y = self.model(x.permute(0, 2, 1)).permute(0, 2, 1)
59
+
60
+ # add back the cycle of the output data
61
+ y = y + self.cycleQueue((cycle_index + self.seq_len) % self.cycle_len, self.pred_len)
62
+
63
+ # instance denorm
64
+ if self.use_revin:
65
+ y = y * torch.sqrt(seq_var) + seq_mean
66
+
67
+ return y
time_series_forecasting/models/DLinear.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import numpy as np
5
+
6
+ class moving_avg(nn.Module):
7
+ """
8
+ Moving average block to highlight the trend of time series
9
+ """
10
+ def __init__(self, kernel_size, stride):
11
+ super(moving_avg, self).__init__()
12
+ self.kernel_size = kernel_size
13
+ self.avg = nn.AvgPool1d(kernel_size=kernel_size, stride=stride, padding=0)
14
+
15
+ def forward(self, x):
16
+ # padding on the both ends of time series
17
+ front = x[:, 0:1, :].repeat(1, (self.kernel_size - 1) // 2, 1)
18
+ end = x[:, -1:, :].repeat(1, (self.kernel_size - 1) // 2, 1)
19
+ x = torch.cat([front, x, end], dim=1)
20
+ x = self.avg(x.permute(0, 2, 1))
21
+ x = x.permute(0, 2, 1)
22
+ return x
23
+
24
+
25
+ class series_decomp(nn.Module):
26
+ """
27
+ Series decomposition block
28
+ """
29
+ def __init__(self, kernel_size):
30
+ super(series_decomp, self).__init__()
31
+ self.moving_avg = moving_avg(kernel_size, stride=1)
32
+
33
+ def forward(self, x):
34
+ moving_mean = self.moving_avg(x)
35
+ res = x - moving_mean
36
+ return res, moving_mean
37
+
38
+ class Model(nn.Module):
39
+ """
40
+ Decomposition-Linear
41
+ """
42
+ def __init__(self, configs):
43
+ super(Model, self).__init__()
44
+ self.seq_len = configs.seq_len
45
+ self.pred_len = configs.pred_len
46
+
47
+ # Decompsition Kernel Size
48
+ kernel_size = 25
49
+ self.decompsition = series_decomp(kernel_size)
50
+ self.individual = configs.individual
51
+ self.channels = configs.enc_in
52
+
53
+ if self.individual:
54
+ self.Linear_Seasonal = nn.ModuleList()
55
+ self.Linear_Trend = nn.ModuleList()
56
+
57
+ for i in range(self.channels):
58
+ self.Linear_Seasonal.append(nn.Linear(self.seq_len,self.pred_len))
59
+ self.Linear_Trend.append(nn.Linear(self.seq_len,self.pred_len))
60
+
61
+ # Use this two lines if you want to visualize the weights
62
+ # self.Linear_Seasonal[i].weight = nn.Parameter((1/self.seq_len)*torch.ones([self.pred_len,self.seq_len]))
63
+ # self.Linear_Trend[i].weight = nn.Parameter((1/self.seq_len)*torch.ones([self.pred_len,self.seq_len]))
64
+ else:
65
+ self.Linear_Seasonal = nn.Linear(self.seq_len,self.pred_len)
66
+ self.Linear_Trend = nn.Linear(self.seq_len,self.pred_len)
67
+
68
+ # Use this two lines if you want to visualize the weights
69
+ # self.Linear_Seasonal.weight = nn.Parameter((1/self.seq_len)*torch.ones([self.pred_len,self.seq_len]))
70
+ # self.Linear_Trend.weight = nn.Parameter((1/self.seq_len)*torch.ones([self.pred_len,self.seq_len]))
71
+
72
+ def forward(self, x):
73
+ # x: [Batch, Input length, Channel]
74
+ seasonal_init, trend_init = self.decompsition(x)
75
+ seasonal_init, trend_init = seasonal_init.permute(0,2,1), trend_init.permute(0,2,1)
76
+ if self.individual:
77
+ seasonal_output = torch.zeros([seasonal_init.size(0),seasonal_init.size(1),self.pred_len],dtype=seasonal_init.dtype).to(seasonal_init.device)
78
+ trend_output = torch.zeros([trend_init.size(0),trend_init.size(1),self.pred_len],dtype=trend_init.dtype).to(trend_init.device)
79
+ for i in range(self.channels):
80
+ seasonal_output[:,i,:] = self.Linear_Seasonal[i](seasonal_init[:,i,:])
81
+ trend_output[:,i,:] = self.Linear_Trend[i](trend_init[:,i,:])
82
+ else:
83
+ seasonal_output = self.Linear_Seasonal(seasonal_init)
84
+ trend_output = self.Linear_Trend(trend_init)
85
+
86
+ x = seasonal_output + trend_output
87
+ return x.permute(0,2,1) # to [Batch, Output length, Channel]
time_series_forecasting/models/Informer.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from utils.masking import TriangularCausalMask, ProbMask
5
+ from layers.Transformer_EncDec import Decoder, DecoderLayer, Encoder, EncoderLayer, ConvLayer
6
+ from layers.SelfAttention_Family import FullAttention, ProbAttention, AttentionLayer
7
+ from layers.Embed import DataEmbedding,DataEmbedding_wo_pos,DataEmbedding_wo_temp,DataEmbedding_wo_pos_temp
8
+ import numpy as np
9
+
10
+
11
+ class Model(nn.Module):
12
+ """
13
+ Informer with Propspare attention in O(LlogL) complexity
14
+ """
15
+ def __init__(self, configs):
16
+ super(Model, self).__init__()
17
+ self.pred_len = configs.pred_len
18
+ self.output_attention = configs.output_attention
19
+
20
+ # Embedding
21
+ if configs.embed_type == 0:
22
+ self.enc_embedding = DataEmbedding(configs.enc_in, configs.d_model, configs.embed, configs.freq,
23
+ configs.dropout)
24
+ self.dec_embedding = DataEmbedding(configs.dec_in, configs.d_model, configs.embed, configs.freq,
25
+ configs.dropout)
26
+ elif configs.embed_type == 1:
27
+ self.enc_embedding = DataEmbedding(configs.enc_in, configs.d_model, configs.embed, configs.freq,
28
+ configs.dropout)
29
+ self.dec_embedding = DataEmbedding(configs.dec_in, configs.d_model, configs.embed, configs.freq,
30
+ configs.dropout)
31
+ elif configs.embed_type == 2:
32
+ self.enc_embedding = DataEmbedding_wo_pos(configs.enc_in, configs.d_model, configs.embed, configs.freq,
33
+ configs.dropout)
34
+ self.dec_embedding = DataEmbedding_wo_pos(configs.dec_in, configs.d_model, configs.embed, configs.freq,
35
+ configs.dropout)
36
+
37
+ elif configs.embed_type == 3:
38
+ self.enc_embedding = DataEmbedding_wo_temp(configs.enc_in, configs.d_model, configs.embed, configs.freq,
39
+ configs.dropout)
40
+ self.dec_embedding = DataEmbedding_wo_temp(configs.dec_in, configs.d_model, configs.embed, configs.freq,
41
+ configs.dropout)
42
+ elif configs.embed_type == 4:
43
+ self.enc_embedding = DataEmbedding_wo_pos_temp(configs.enc_in, configs.d_model, configs.embed, configs.freq,
44
+ configs.dropout)
45
+ self.dec_embedding = DataEmbedding_wo_pos_temp(configs.dec_in, configs.d_model, configs.embed, configs.freq,
46
+ configs.dropout)
47
+ # Encoder
48
+ self.encoder = Encoder(
49
+ [
50
+ EncoderLayer(
51
+ AttentionLayer(
52
+ ProbAttention(False, configs.factor, attention_dropout=configs.dropout,
53
+ output_attention=configs.output_attention),
54
+ configs.d_model, configs.n_heads),
55
+ configs.d_model,
56
+ configs.d_ff,
57
+ dropout=configs.dropout,
58
+ activation=configs.activation
59
+ ) for l in range(configs.e_layers)
60
+ ],
61
+ [
62
+ ConvLayer(
63
+ configs.d_model
64
+ ) for l in range(configs.e_layers - 1)
65
+ ] if configs.distil else None,
66
+ norm_layer=torch.nn.LayerNorm(configs.d_model)
67
+ )
68
+ # Decoder
69
+ self.decoder = Decoder(
70
+ [
71
+ DecoderLayer(
72
+ AttentionLayer(
73
+ ProbAttention(True, configs.factor, attention_dropout=configs.dropout, output_attention=False),
74
+ configs.d_model, configs.n_heads),
75
+ AttentionLayer(
76
+ ProbAttention(False, configs.factor, attention_dropout=configs.dropout, output_attention=False),
77
+ configs.d_model, configs.n_heads),
78
+ configs.d_model,
79
+ configs.d_ff,
80
+ dropout=configs.dropout,
81
+ activation=configs.activation,
82
+ )
83
+ for l in range(configs.d_layers)
84
+ ],
85
+ norm_layer=torch.nn.LayerNorm(configs.d_model),
86
+ projection=nn.Linear(configs.d_model, configs.c_out, bias=True)
87
+ )
88
+
89
+ def forward(self, x_enc, x_mark_enc, x_dec, x_mark_dec,
90
+ enc_self_mask=None, dec_self_mask=None, dec_enc_mask=None):
91
+
92
+ enc_out = self.enc_embedding(x_enc, x_mark_enc)
93
+ enc_out, attns = self.encoder(enc_out, attn_mask=enc_self_mask)
94
+
95
+ dec_out = self.dec_embedding(x_dec, x_mark_dec)
96
+ dec_out = self.decoder(dec_out, enc_out, x_mask=dec_self_mask, cross_mask=dec_enc_mask)
97
+
98
+ if self.output_attention:
99
+ return dec_out[:, -self.pred_len:, :], attns
100
+ else:
101
+ return dec_out[:, -self.pred_len:, :] # [B, L, D]
time_series_forecasting/models/LightTS.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+
6
+ class IEBlock(nn.Module):
7
+ def __init__(self, input_dim, hid_dim, output_dim, num_node):
8
+ super(IEBlock, self).__init__()
9
+
10
+ self.input_dim = input_dim
11
+ self.hid_dim = hid_dim
12
+ self.output_dim = output_dim
13
+ self.num_node = num_node
14
+
15
+ self._build()
16
+
17
+ def _build(self):
18
+ self.spatial_proj = nn.Sequential(
19
+ nn.Linear(self.input_dim, self.hid_dim),
20
+ nn.LeakyReLU(),
21
+ nn.Linear(self.hid_dim, self.hid_dim // 4)
22
+ )
23
+
24
+ self.channel_proj = nn.Linear(self.num_node, self.num_node)
25
+ torch.nn.init.eye_(self.channel_proj.weight)
26
+
27
+ self.output_proj = nn.Linear(self.hid_dim // 4, self.output_dim)
28
+
29
+
30
+ def forward(self, x):
31
+ x = self.spatial_proj(x.permute(0, 2, 1))
32
+ x = x.permute(0, 2, 1) + self.channel_proj(x.permute(0, 2, 1))
33
+ x = self.output_proj(x.permute(0, 2, 1))
34
+
35
+ x = x.permute(0, 2, 1)
36
+
37
+ return x
38
+
39
+
40
+ class Model(nn.Module):
41
+ def __init__(self, config):
42
+ super(Model, self).__init__()
43
+ self.lookback = config.seq_len
44
+ self.lookahead = config.pred_len
45
+ self.chunk_size = config.chunk_size
46
+ assert(self.lookback % self.chunk_size == 0)
47
+ self.num_chunks = self.lookback // self.chunk_size
48
+ self.hid_dim = config.d_model
49
+ self.num_node = config.enc_in
50
+ self.dropout = config.dropout
51
+ self._build()
52
+
53
+ def _build(self):
54
+ self.layer_1 = IEBlock(
55
+ input_dim=self.chunk_size,
56
+ hid_dim=self.hid_dim // 4,
57
+ output_dim=self.hid_dim // 4,
58
+ num_node=self.num_chunks
59
+ )
60
+
61
+ self.chunk_proj_1 = nn.Linear(self.num_chunks, 1)
62
+
63
+ self.layer_2 = IEBlock(
64
+ input_dim=self.chunk_size,
65
+ hid_dim=self.hid_dim // 4,
66
+ output_dim=self.hid_dim // 4,
67
+ num_node=self.num_chunks
68
+ )
69
+
70
+ self.chunk_proj_2 = nn.Linear(self.num_chunks, 1)
71
+
72
+ self.layer_3 = IEBlock(
73
+ input_dim=self.hid_dim // 2,
74
+ hid_dim=self.hid_dim // 2,
75
+ output_dim=self.lookahead,
76
+ num_node=self.num_node
77
+ )
78
+
79
+ # self.ar = nn.Sequential(
80
+ # nn.Linear(self.lookback, self.hid_dim //4),
81
+ # nn.LeakyReLU(),
82
+ # nn.Linear(self.hid_dim // 4, self.lookahead)
83
+ # )
84
+ self.ar = nn.Linear(self.lookback, self.lookahead)
85
+
86
+ def forward(self, x):
87
+ B, T, N = x.size()
88
+
89
+ highway = self.ar(x.permute(0, 2, 1))
90
+ highway = highway.permute(0, 2, 1)
91
+
92
+ # continuous sampling
93
+ x1 = x.reshape(B, self.num_chunks, self.chunk_size, N)
94
+ x1 = x1.permute(0, 3, 2, 1)
95
+ x1 = x1.reshape(-1, self.chunk_size, self.num_chunks)
96
+ x1 = self.layer_1(x1)
97
+ x1 = self.chunk_proj_1(x1).squeeze(dim=-1)
98
+
99
+ # interval sampling
100
+ x2 = x.reshape(B, self.chunk_size, self.num_chunks, N)
101
+ x2 = x2.permute(0, 3, 1, 2)
102
+ x2 = x2.reshape(-1, self.chunk_size, self.num_chunks)
103
+ x2 = self.layer_2(x2)
104
+ x2 = self.chunk_proj_2(x2).squeeze(dim=-1)
105
+
106
+ x3 = torch.cat([x1, x2], dim=-1)
107
+
108
+ x3 = x3.reshape(B, N, -1)
109
+ x3 = x3.permute(0, 2, 1)
110
+
111
+ out = self.layer_3(x3)
112
+
113
+ out = out + highway
114
+ return out
time_series_forecasting/models/Linear.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import numpy as np
5
+
6
+ class Model(nn.Module):
7
+ """
8
+ Just one Linear layer
9
+ """
10
+ def __init__(self, configs):
11
+ super(Model, self).__init__()
12
+ self.seq_len = configs.seq_len
13
+ self.pred_len = configs.pred_len
14
+ self.Linear = nn.Linear(self.seq_len, self.pred_len)
15
+ # Use this line if you want to visualize the weights
16
+ # self.Linear.weight = nn.Parameter((1/self.seq_len)*torch.ones([self.pred_len,self.seq_len]))
17
+
18
+ def forward(self, x):
19
+ # x: [Batch, Input length, Channel]
20
+ x = self.Linear(x.permute(0,2,1)).permute(0,2,1)
21
+ return x # [Batch, Output length, Channel]