deep-river 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deep_river-0.3.2 → deep_river-0.3.4}/PKG-INFO +14 -57
- {deep_river-0.3.2 → deep_river-0.3.4}/README.md +5 -5
- deep_river-0.3.4/deep_river/__init__.py +7 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/ae.py +1 -1
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/probability_weighted_ae.py +2 -1
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/rolling_ae.py +11 -7
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/scaler.py +12 -12
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/base.py +2 -2
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/classification/classifier.py +3 -1
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/classification/rolling_classifier.py +3 -12
- deep_river-0.3.4/deep_river/forecasting/__init__.py +23 -0
- deep_river-0.3.4/deep_river/forecasting/forecaster.py +189 -0
- deep_river-0.3.4/deep_river/forecasting/zoo.py +601 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/multioutput.py +1 -1
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/regressor.py +4 -4
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/rolling_regressor.py +5 -18
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/estimator_checks.py +22 -7
- deep_river-0.3.4/deep_river/utils/inspect.py +13 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/tensor_conversion.py +1 -1
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/test_tensor_conversion.py +2 -2
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river.egg-info/PKG-INFO +14 -57
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river.egg-info/SOURCES.txt +3 -1
- deep_river-0.3.4/deep_river.egg-info/requires.txt +13 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/pyproject.toml +23 -63
- deep_river-0.3.2/deep_river/__init__.py +0 -4
- deep_river-0.3.2/deep_river/__version__.py +0 -3
- deep_river-0.3.2/deep_river/utils/inspect.py +0 -7
- deep_river-0.3.2/deep_river.egg-info/requires.txt +0 -46
- {deep_river-0.3.2 → deep_river-0.3.4}/LICENSE +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/__init__.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/classification/__init__.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/classification/zoo.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/__init__.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/zoo.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/__init__.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/hooks.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/params.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/test_estimators.py +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river.egg-info/dependency_links.txt +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/deep_river.egg-info/top_level.txt +0 -0
- {deep_river-0.3.2 → deep_river-0.3.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deep_river
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Online Deep Learning for river
|
|
5
5
|
Author-email: Cedric Kulbach <cedric.kulbach@googlemail.com>, Lucas Cazzonelli <Cazzonelli@fzi.de>, Hoang-Anh Ngo <hoang-anh.ngo@alumni.polytechnique.org>, Max Halford <maxhalford25@gmail.com>, Saulo Martiello Mastelini <saulomastelini@gmail.com>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -8,71 +8,28 @@ Project-URL: Homepage, https://github.com/online-ml/deep-river
|
|
|
8
8
|
Project-URL: Repository, https://github.com/online-ml/deep-river
|
|
9
9
|
Project-URL: Documentation, https://github.com/online-ml/deep-river
|
|
10
10
|
Keywords: online-learning,deep-learning,data-streams
|
|
11
|
-
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
17
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
18
|
-
Requires-Python: <3.13,>=3.10
|
|
11
|
+
Requires-Python: <3.13,>=3.11
|
|
19
12
|
Description-Content-Type: text/markdown
|
|
20
13
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: numpy<3,>=2.3.4
|
|
15
|
+
Requires-Dist: pandas<3,>=2.2
|
|
16
|
+
Requires-Dist: river<0.26,>=0.25
|
|
17
|
+
Requires-Dist: scikit-learn<2,>=1.5.1
|
|
18
|
+
Requires-Dist: sortedcontainers>=2.4.0
|
|
19
|
+
Requires-Dist: torch==2.2.2; sys_platform == "darwin" and platform_machine == "x86_64"
|
|
20
|
+
Requires-Dist: torch<2.6,>=2.5; sys_platform != "darwin" or platform_machine != "x86_64"
|
|
26
21
|
Requires-Dist: tqdm~=4.66.5
|
|
27
22
|
Requires-Dist: torchviz~=0.0.2
|
|
28
|
-
Requires-Dist: mkdocstrings[python]>=0.19.0
|
|
29
|
-
Requires-Dist: pytkdocs[numpy-style]>=0.5.0
|
|
30
|
-
Requires-Dist: sortedcontainers>=2.4.0
|
|
31
|
-
Provides-Extra: dev
|
|
32
|
-
Requires-Dist: graphviz>=0.20.3; extra == "dev"
|
|
33
|
-
Requires-Dist: matplotlib>=3.9.2; extra == "dev"
|
|
34
|
-
Requires-Dist: mypy>=1.11.1; extra == "dev"
|
|
35
|
-
Requires-Dist: codecov>=2.1.13; extra == "dev"
|
|
36
|
-
Requires-Dist: pre-commit>=3.8.0; extra == "dev"
|
|
37
|
-
Requires-Dist: pytest>=8.3.2; extra == "dev"
|
|
38
|
-
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
39
|
-
Requires-Dist: black>=24.8.0; extra == "dev"
|
|
40
|
-
Requires-Dist: flake8>=7.1.1; extra == "dev"
|
|
41
|
-
Requires-Dist: isort>=5.13.2; extra == "dev"
|
|
42
|
-
Requires-Dist: jupyter>=1.0.0; extra == "dev"
|
|
43
|
-
Requires-Dist: pyupgrade==3.17.0; extra == "dev"
|
|
44
|
-
Requires-Dist: flask>=3.0.2; extra == "dev"
|
|
45
|
-
Requires-Dist: ipykernel>=6.9.0; extra == "dev"
|
|
46
|
-
Requires-Dist: mike>=0.5.3; extra == "dev"
|
|
47
|
-
Requires-Dist: mkdocs>=1.2.3; extra == "dev"
|
|
48
|
-
Requires-Dist: mkdocs-awesome-pages-plugin>=2.7.0; extra == "dev"
|
|
49
|
-
Requires-Dist: mkdocs-gen-files>=0.3.5; extra == "dev"
|
|
50
|
-
Requires-Dist: mkdocs-charts-plugin>=0.0.8; extra == "dev"
|
|
51
|
-
Requires-Dist: mkdocs-literate-nav>=0.4.1; extra == "dev"
|
|
52
|
-
Requires-Dist: mkdocs-material>=8.1.11; extra == "dev"
|
|
53
|
-
Requires-Dist: ipython_genutils>=0.1.0; extra == "dev"
|
|
54
|
-
Requires-Dist: mkdocs-jupyter>=0.20.0; extra == "dev"
|
|
55
|
-
Requires-Dist: nbconvert>=6.4.2; extra == "dev"
|
|
56
|
-
Requires-Dist: jupyter_contrib_nbextensions==0.7.0; extra == "dev"
|
|
57
|
-
Requires-Dist: numpydoc>=1.2; extra == "dev"
|
|
58
|
-
Requires-Dist: notebook==6.4.3; extra == "dev"
|
|
59
|
-
Requires-Dist: spacy>=3.2.2; extra == "dev"
|
|
60
|
-
Requires-Dist: jinja2>=3.0.3; extra == "dev"
|
|
61
|
-
Requires-Dist: dominate; extra == "dev"
|
|
62
|
-
Requires-Dist: jupyter-client; extra == "dev"
|
|
63
|
-
Requires-Dist: python-slugify; extra == "dev"
|
|
64
|
-
Requires-Dist: watermark==2.3.1; extra == "dev"
|
|
65
|
-
Requires-Dist: plotly>=5.0.0; extra == "dev"
|
|
66
23
|
Dynamic: license-file
|
|
67
24
|
|
|
68
25
|
<p align="center">
|
|
69
|
-
<img height="150px" src="https://raw.githubusercontent.com/online-ml/deep-river/
|
|
26
|
+
<img height="150px" src="https://raw.githubusercontent.com/online-ml/deep-river/main/docs/img/logo.png" alt="incremental dl logo">
|
|
70
27
|
</p>
|
|
71
28
|
<p align="center">
|
|
72
29
|
<img alt="PyPI" src="https://img.shields.io/pypi/v/deep-river">
|
|
73
30
|
<!-- Single Coverage Badge (Shields.io) -->
|
|
74
31
|
<a href="https://app.codecov.io/gh/online-ml/deep-river" >
|
|
75
|
-
<img alt="Coverage" src="https://img.shields.io/codecov/c/github/online-ml/deep-river?branch=
|
|
32
|
+
<img alt="Coverage" src="https://img.shields.io/codecov/c/github/online-ml/deep-river?branch=main&logo=codecov&label=coverage" />
|
|
76
33
|
</a>
|
|
77
34
|
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/deep-river">
|
|
78
35
|
<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/deep-river">
|
|
@@ -88,7 +45,7 @@ Dynamic: license-file
|
|
|
88
45
|
|
|
89
46
|
## 📚 [Documentation](https://online-ml.github.io/deep-river/)
|
|
90
47
|
The [documentation](https://online-ml.github.io/deep-river/) contains an overview of all features of this repository as well as the repository's full features list.
|
|
91
|
-
In each of these, the git repo reference is listed in a section that shows [examples](https://github.com/online-ml/deep-river/blob/
|
|
48
|
+
In each of these, the git repo reference is listed in a section that shows [examples](https://github.com/online-ml/deep-river/blob/main/docs/examples) of the features and functionality.
|
|
92
49
|
As we are always looking for further use cases and examples, feel free to contribute to the documentation or the repository itself via a pull request
|
|
93
50
|
|
|
94
51
|
## 💈 Installation
|
|
@@ -103,7 +60,7 @@ pip install "river[deep]"
|
|
|
103
60
|
You can install the latest development version from GitHub as so:
|
|
104
61
|
|
|
105
62
|
```shell
|
|
106
|
-
pip install https://github.com/online-ml/deep-river/archive/refs/heads/
|
|
63
|
+
pip install https://github.com/online-ml/deep-river/archive/refs/heads/main.zip
|
|
107
64
|
```
|
|
108
65
|
|
|
109
66
|
### Development Environment
|
|
@@ -316,7 +273,7 @@ To acknowledge the use of the `DeepRiver` library in your research, please refer
|
|
|
316
273
|
## 🏫 Affiliations
|
|
317
274
|
|
|
318
275
|
<p align="center">
|
|
319
|
-
<img src="https://upload.wikimedia.org/wikipedia/de/
|
|
276
|
+
<img src="https://upload.wikimedia.org/wikipedia/de/4/44/Fzi_logo.svg" alt="FZI Logo" height="200"/>
|
|
320
277
|
</p>
|
|
321
278
|
|
|
322
279
|
<p align="center">
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img height="150px" src="https://raw.githubusercontent.com/online-ml/deep-river/
|
|
2
|
+
<img height="150px" src="https://raw.githubusercontent.com/online-ml/deep-river/main/docs/img/logo.png" alt="incremental dl logo">
|
|
3
3
|
</p>
|
|
4
4
|
<p align="center">
|
|
5
5
|
<img alt="PyPI" src="https://img.shields.io/pypi/v/deep-river">
|
|
6
6
|
<!-- Single Coverage Badge (Shields.io) -->
|
|
7
7
|
<a href="https://app.codecov.io/gh/online-ml/deep-river" >
|
|
8
|
-
<img alt="Coverage" src="https://img.shields.io/codecov/c/github/online-ml/deep-river?branch=
|
|
8
|
+
<img alt="Coverage" src="https://img.shields.io/codecov/c/github/online-ml/deep-river?branch=main&logo=codecov&label=coverage" />
|
|
9
9
|
</a>
|
|
10
10
|
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/deep-river">
|
|
11
11
|
<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/deep-river">
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
## 📚 [Documentation](https://online-ml.github.io/deep-river/)
|
|
23
23
|
The [documentation](https://online-ml.github.io/deep-river/) contains an overview of all features of this repository as well as the repository's full features list.
|
|
24
|
-
In each of these, the git repo reference is listed in a section that shows [examples](https://github.com/online-ml/deep-river/blob/
|
|
24
|
+
In each of these, the git repo reference is listed in a section that shows [examples](https://github.com/online-ml/deep-river/blob/main/docs/examples) of the features and functionality.
|
|
25
25
|
As we are always looking for further use cases and examples, feel free to contribute to the documentation or the repository itself via a pull request
|
|
26
26
|
|
|
27
27
|
## 💈 Installation
|
|
@@ -36,7 +36,7 @@ pip install "river[deep]"
|
|
|
36
36
|
You can install the latest development version from GitHub as so:
|
|
37
37
|
|
|
38
38
|
```shell
|
|
39
|
-
pip install https://github.com/online-ml/deep-river/archive/refs/heads/
|
|
39
|
+
pip install https://github.com/online-ml/deep-river/archive/refs/heads/main.zip
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
### Development Environment
|
|
@@ -249,7 +249,7 @@ To acknowledge the use of the `DeepRiver` library in your research, please refer
|
|
|
249
249
|
## 🏫 Affiliations
|
|
250
250
|
|
|
251
251
|
<p align="center">
|
|
252
|
-
<img src="https://upload.wikimedia.org/wikipedia/de/
|
|
252
|
+
<img src="https://upload.wikimedia.org/wikipedia/de/4/44/Fzi_logo.svg" alt="FZI Logo" height="200"/>
|
|
253
253
|
</p>
|
|
254
254
|
|
|
255
255
|
<p align="center">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import math
|
|
2
2
|
from typing import Any, Callable, Union
|
|
3
3
|
|
|
4
|
+
import numpy as np
|
|
4
5
|
import pandas as pd
|
|
5
6
|
import torch
|
|
6
7
|
from river import stats, utils
|
|
@@ -65,7 +66,7 @@ class ProbabilityWeightedAutoencoder(ae.Autoencoder):
|
|
|
65
66
|
self._apply_loss(loss)
|
|
66
67
|
|
|
67
68
|
def _apply_loss(self, loss):
|
|
68
|
-
losses_numpy = loss.detach().
|
|
69
|
+
losses_numpy = np.asarray(loss.detach().cpu().tolist())
|
|
69
70
|
mean = self.rolling_mean.get()
|
|
70
71
|
var = self.rolling_var.get() if self.rolling_var.get() > 0 else 1
|
|
71
72
|
if losses_numpy.ndim == 0:
|
|
@@ -154,7 +154,7 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
154
154
|
Additional keyword arguments.
|
|
155
155
|
"""
|
|
156
156
|
self._update_observed_features(x)
|
|
157
|
-
self._x_window.append(
|
|
157
|
+
self._x_window.append([x.get(feature, 0) for feature in self.observed_features])
|
|
158
158
|
|
|
159
159
|
x_t = deque2rolling_tensor(self._x_window, device=self.device)
|
|
160
160
|
self._learn(x=x_t)
|
|
@@ -171,7 +171,8 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
171
171
|
"""
|
|
172
172
|
self._update_observed_features(X)
|
|
173
173
|
|
|
174
|
-
self.
|
|
174
|
+
X = X[list(self.observed_features)]
|
|
175
|
+
self._x_window.extend(X.values.tolist())
|
|
175
176
|
if len(self._x_window) == self.window_size:
|
|
176
177
|
X_t = deque2rolling_tensor(self._x_window, device=self.device)
|
|
177
178
|
self._learn(x=X_t)
|
|
@@ -193,7 +194,7 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
193
194
|
self._update_observed_features(x)
|
|
194
195
|
if len(self._x_window) == self.window_size:
|
|
195
196
|
x_win = self._x_window.copy()
|
|
196
|
-
x_win.append(
|
|
197
|
+
x_win.append([x.get(feature, 0) for feature in self.observed_features])
|
|
197
198
|
x_t = deque2rolling_tensor(x_win, device=self.device)
|
|
198
199
|
self.module.eval()
|
|
199
200
|
with torch.inference_mode():
|
|
@@ -202,7 +203,9 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
202
203
|
res = loss.item()
|
|
203
204
|
|
|
204
205
|
if self.append_predict:
|
|
205
|
-
self._x_window.append(
|
|
206
|
+
self._x_window.append(
|
|
207
|
+
[x.get(feature, 0) for feature in self.observed_features]
|
|
208
|
+
)
|
|
206
209
|
return res
|
|
207
210
|
|
|
208
211
|
def score_many(self, X: pd.DataFrame) -> List[Any]:
|
|
@@ -221,10 +224,11 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
221
224
|
List of computed anomaly scores (reconstruction errors) for each sample in X.
|
|
222
225
|
"""
|
|
223
226
|
self._update_observed_features(X)
|
|
227
|
+
X = X[list(self.observed_features)]
|
|
224
228
|
x_win = self._x_window.copy()
|
|
225
|
-
x_win.
|
|
229
|
+
x_win.extend(X.values.tolist())
|
|
226
230
|
if self.append_predict:
|
|
227
|
-
self._x_window.
|
|
231
|
+
self._x_window.extend(X.values.tolist())
|
|
228
232
|
|
|
229
233
|
if len(self._x_window) == self.window_size:
|
|
230
234
|
X_t = deque2rolling_tensor(x_win, device=self.device)
|
|
@@ -235,7 +239,7 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
235
239
|
self.loss_func(x_pred, x_pred, reduction="none"),
|
|
236
240
|
dim=list(range(1, x_pred.dim())),
|
|
237
241
|
)
|
|
238
|
-
losses = loss.detach().
|
|
242
|
+
losses = np.asarray(loss.detach().cpu().tolist())
|
|
239
243
|
if len(losses) < len(X):
|
|
240
244
|
losses = np.pad(losses, (len(X) - len(losses), 0))
|
|
241
245
|
return losses.tolist()
|
|
@@ -21,7 +21,7 @@ class AnomalyScaler(base.Wrapper, AnomalyDetector):
|
|
|
21
21
|
self.anomaly_detector = anomaly_detector
|
|
22
22
|
|
|
23
23
|
@classmethod
|
|
24
|
-
def _unit_test_params(
|
|
24
|
+
def _unit_test_params(cls):
|
|
25
25
|
"""
|
|
26
26
|
Returns a dictionary of parameters to be used for unit testing
|
|
27
27
|
the respective class.
|
|
@@ -32,7 +32,7 @@ class AnomalyScaler(base.Wrapper, AnomalyDetector):
|
|
|
32
32
|
Dictionary of parameters to be used for unit testing the
|
|
33
33
|
respective class.
|
|
34
34
|
"""
|
|
35
|
-
|
|
35
|
+
yield {"anomaly_detector": HalfSpaceTrees()}
|
|
36
36
|
|
|
37
37
|
@classmethod
|
|
38
38
|
def _unit_test_skips(self) -> set:
|
|
@@ -60,7 +60,7 @@ class AnomalyScaler(base.Wrapper, AnomalyDetector):
|
|
|
60
60
|
return self.anomaly_detector
|
|
61
61
|
|
|
62
62
|
@abc.abstractmethod
|
|
63
|
-
def score_one(self, *args) -> float:
|
|
63
|
+
def score_one(self, *args, **kwargs) -> float:
|
|
64
64
|
"""Return a scaled anomaly score based on raw score provided by
|
|
65
65
|
the wrapped anomaly detector.
|
|
66
66
|
|
|
@@ -79,7 +79,7 @@ class AnomalyScaler(base.Wrapper, AnomalyDetector):
|
|
|
79
79
|
more anomalous examples.
|
|
80
80
|
"""
|
|
81
81
|
|
|
82
|
-
def learn_one(self, *args) -> None:
|
|
82
|
+
def learn_one(self, *args, **kwargs) -> None:
|
|
83
83
|
"""
|
|
84
84
|
Update the scaler and the underlying anomaly scaler.
|
|
85
85
|
|
|
@@ -95,10 +95,10 @@ class AnomalyScaler(base.Wrapper, AnomalyDetector):
|
|
|
95
95
|
The model itself.
|
|
96
96
|
"""
|
|
97
97
|
|
|
98
|
-
self.anomaly_detector.learn_one(*args)
|
|
98
|
+
self.anomaly_detector.learn_one(*args, **kwargs)
|
|
99
99
|
|
|
100
100
|
@abc.abstractmethod
|
|
101
|
-
def score_many(self, *args) -> np.ndarray:
|
|
101
|
+
def score_many(self, *args, **kwargs) -> np.ndarray:
|
|
102
102
|
"""Return scaled anomaly scores based on raw score provided by
|
|
103
103
|
the wrapped anomaly detector.
|
|
104
104
|
|
|
@@ -150,7 +150,7 @@ class AnomalyStandardScaler(AnomalyScaler):
|
|
|
150
150
|
)
|
|
151
151
|
self.with_std = with_std
|
|
152
152
|
|
|
153
|
-
def score_one(self, *args):
|
|
153
|
+
def score_one(self, *args, **kwargs):
|
|
154
154
|
"""
|
|
155
155
|
Return a scaled anomaly score based on raw score provided by the
|
|
156
156
|
wrapped anomaly detector. Larger values indicate more
|
|
@@ -167,7 +167,7 @@ class AnomalyStandardScaler(AnomalyScaler):
|
|
|
167
167
|
An scaled anomaly score. Larger values indicate more
|
|
168
168
|
anomalous examples.
|
|
169
169
|
"""
|
|
170
|
-
raw_score = self.anomaly_detector.score_one(*args)
|
|
170
|
+
raw_score = self.anomaly_detector.score_one(*args, **kwargs)
|
|
171
171
|
mean = self.mean.update(raw_score).get()
|
|
172
172
|
if self.with_std:
|
|
173
173
|
var = (
|
|
@@ -207,7 +207,7 @@ class AnomalyMeanScaler(AnomalyScaler):
|
|
|
207
207
|
self.window_size = window_size
|
|
208
208
|
self.mean = utils.Rolling(Mean(), self.window_size) if self.rolling else Mean()
|
|
209
209
|
|
|
210
|
-
def score_one(self, *args):
|
|
210
|
+
def score_one(self, *args, **kwargs):
|
|
211
211
|
"""
|
|
212
212
|
Return a scaled anomaly score based on raw score provided by the
|
|
213
213
|
wrapped anomaly detector. Larger values indicate more
|
|
@@ -224,7 +224,7 @@ class AnomalyMeanScaler(AnomalyScaler):
|
|
|
224
224
|
An scaled anomaly score. Larger values indicate more
|
|
225
225
|
anomalous examples.
|
|
226
226
|
"""
|
|
227
|
-
raw_score = self.anomaly_detector.score_one(*args)
|
|
227
|
+
raw_score = self.anomaly_detector.score_one(*args, **kwargs)
|
|
228
228
|
mean = self.mean.update(raw_score).get()
|
|
229
229
|
score = raw_score / mean
|
|
230
230
|
|
|
@@ -257,7 +257,7 @@ class AnomalyMinMaxScaler(AnomalyScaler):
|
|
|
257
257
|
self.min = RollingMin(self.window_size) if self.rolling else Min()
|
|
258
258
|
self.max = RollingMin(self.window_size) if self.rolling else Min()
|
|
259
259
|
|
|
260
|
-
def score_one(self, *args):
|
|
260
|
+
def score_one(self, *args, **kwargs):
|
|
261
261
|
"""
|
|
262
262
|
Return a scaled anomaly score based on raw score provided by the
|
|
263
263
|
wrapped anomaly detector. Larger values indicate more
|
|
@@ -274,7 +274,7 @@ class AnomalyMinMaxScaler(AnomalyScaler):
|
|
|
274
274
|
An scaled anomaly score. Larger values indicate more
|
|
275
275
|
anomalous examples.
|
|
276
276
|
"""
|
|
277
|
-
raw_score = self.anomaly_detector.score_one(*args)
|
|
277
|
+
raw_score = self.anomaly_detector.score_one(*args, **kwargs)
|
|
278
278
|
min = self.min.update(raw_score).get()
|
|
279
279
|
max = self.max.update(raw_score).get()
|
|
280
280
|
score = (raw_score - min) / (max - min)
|
|
@@ -388,8 +388,8 @@ class DeepEstimator(base.Estimator):
|
|
|
388
388
|
}
|
|
389
389
|
if hasattr(layer, "bias") and layer.bias is not None:
|
|
390
390
|
instructions["bias"] = {"output": [{"axis": 0, "n_subparams": 1}]}
|
|
391
|
-
#
|
|
392
|
-
if isinstance(layer, torch.nn.LSTM):
|
|
391
|
+
# Recurrent layer special case
|
|
392
|
+
if isinstance(layer, (torch.nn.LSTM, torch.nn.GRU, torch.nn.RNN)):
|
|
393
393
|
instructions["input_size"] = "input_attribute"
|
|
394
394
|
if hasattr(layer, "weight_ih_l0"):
|
|
395
395
|
instructions["weight_ih_l0"] = {
|
|
@@ -252,7 +252,9 @@ class Classifier(DeepEstimator, base.MiniBatchClassifier):
|
|
|
252
252
|
# ------------------------------------------------------------------
|
|
253
253
|
# Prediction
|
|
254
254
|
# ------------------------------------------------------------------
|
|
255
|
-
def predict_proba_one(
|
|
255
|
+
def predict_proba_one(
|
|
256
|
+
self, x: dict, **kwargs
|
|
257
|
+
) -> dict[base.typing.ClfTarget, float]:
|
|
256
258
|
"""Predict class membership probabilities for one instance.
|
|
257
259
|
|
|
258
260
|
Parameters
|
|
@@ -167,7 +167,7 @@ class RollingClassifier(Classifier, RollingDeepEstimator):
|
|
|
167
167
|
x_t = self._deque2rolling_tensor(self._x_window)
|
|
168
168
|
self._learn(x=x_t, y=y)
|
|
169
169
|
|
|
170
|
-
def predict_proba_one(self, x: dict) -> Dict[ClfTarget, float]:
|
|
170
|
+
def predict_proba_one(self, x: dict, **kwargs) -> Dict[ClfTarget, float]:
|
|
171
171
|
"""Return class probability mapping for one sample using rolling context."""
|
|
172
172
|
self._update_observed_features(x)
|
|
173
173
|
x_win = self._x_window.copy()
|
|
@@ -192,14 +192,5 @@ class RollingClassifier(Classifier, RollingDeepEstimator):
|
|
|
192
192
|
|
|
193
193
|
def predict_proba_many(self, X: pd.DataFrame) -> pd.DataFrame:
|
|
194
194
|
"""Return probability DataFrame for multiple samples with rolling context."""
|
|
195
|
-
self.
|
|
196
|
-
|
|
197
|
-
x_win = self._x_window.copy()
|
|
198
|
-
x_win.extend(X.values.tolist())
|
|
199
|
-
if self.append_predict:
|
|
200
|
-
self._x_window = x_win
|
|
201
|
-
self.module.eval()
|
|
202
|
-
with torch.inference_mode():
|
|
203
|
-
x_t = self._deque2rolling_tensor(x_win)
|
|
204
|
-
probas = self.module(x_t).detach().tolist()
|
|
205
|
-
return pd.DataFrame(probas)
|
|
195
|
+
probas = [self.predict_proba_one(row) for row in X.to_dict(orient="records")]
|
|
196
|
+
return pd.DataFrame(probas, index=X.index)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from deep_river.forecasting.forecaster import DeepForecaster
|
|
2
|
+
from deep_river.forecasting.zoo import (
|
|
3
|
+
GRUForecaster,
|
|
4
|
+
LinearForecaster,
|
|
5
|
+
LiquidForecaster,
|
|
6
|
+
LSTMForecaster,
|
|
7
|
+
MLPForecaster,
|
|
8
|
+
NBEATSForecaster,
|
|
9
|
+
RNNForecaster,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
"""This module contains forecasters for the deep_river package."""
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"DeepForecaster",
|
|
16
|
+
"LinearForecaster",
|
|
17
|
+
"MLPForecaster",
|
|
18
|
+
"RNNForecaster",
|
|
19
|
+
"GRUForecaster",
|
|
20
|
+
"LiquidForecaster",
|
|
21
|
+
"LSTMForecaster",
|
|
22
|
+
"NBEATSForecaster",
|
|
23
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
from typing import Callable, Deque, Type, Union
|
|
3
|
+
|
|
4
|
+
import torch
|
|
5
|
+
from river.time_series import base as time_series_base
|
|
6
|
+
from torch import nn, optim
|
|
7
|
+
|
|
8
|
+
from deep_river.base import DeepEstimator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _TestModule(nn.Module):
|
|
12
|
+
def __init__(self, n_features: int = 4):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.n_features = n_features
|
|
15
|
+
self.dense0 = nn.Linear(n_features, 8)
|
|
16
|
+
self.activation = nn.ReLU()
|
|
17
|
+
self.dense1 = nn.Linear(8, 1)
|
|
18
|
+
|
|
19
|
+
def forward(self, x, **kwargs):
|
|
20
|
+
return self.dense1(self.activation(self.dense0(x)))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DeepForecaster(DeepEstimator, time_series_base.Forecaster):
|
|
24
|
+
"""Incremental PyTorch forecaster compatible with River's time-series API.
|
|
25
|
+
|
|
26
|
+
``DeepForecaster`` learns a one-step-ahead model from the recent endogenous
|
|
27
|
+
target history and optional exogenous features. Multi-step forecasts are
|
|
28
|
+
produced autoregressively by feeding each predicted value back into a copied
|
|
29
|
+
history buffer.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
module
|
|
34
|
+
PyTorch module. In flat mode it receives a tensor of shape
|
|
35
|
+
``(1, window_size + n_exogenous_features)``. In sequence mode it receives
|
|
36
|
+
``(window_size, 1, 1 + n_exogenous_features)``.
|
|
37
|
+
window_size
|
|
38
|
+
Number of past target values used as autoregressive context.
|
|
39
|
+
is_sequence_model
|
|
40
|
+
Whether ``module`` expects sequence-shaped input.
|
|
41
|
+
loss_fn, optimizer_fn, lr, is_feature_incremental, device, seed,
|
|
42
|
+
gradient_clip_value, **kwargs
|
|
43
|
+
Standard deep-river estimator configuration.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
module: nn.Module,
|
|
49
|
+
loss_fn: Union[str, Callable] = "mse",
|
|
50
|
+
optimizer_fn: Union[str, Type[optim.Optimizer]] = "sgd",
|
|
51
|
+
lr: float = 1e-3,
|
|
52
|
+
is_feature_incremental: bool = False,
|
|
53
|
+
device: str = "cpu",
|
|
54
|
+
seed: int = 42,
|
|
55
|
+
window_size: int = 10,
|
|
56
|
+
is_sequence_model: bool = False,
|
|
57
|
+
gradient_clip_value: float | None = 1.0,
|
|
58
|
+
**kwargs,
|
|
59
|
+
):
|
|
60
|
+
if window_size < 1:
|
|
61
|
+
raise ValueError("window_size must be at least 1")
|
|
62
|
+
|
|
63
|
+
self.window_size = window_size
|
|
64
|
+
self.is_sequence_model = is_sequence_model
|
|
65
|
+
self._y_window: Deque[float] = collections.deque(maxlen=window_size)
|
|
66
|
+
|
|
67
|
+
super().__init__(
|
|
68
|
+
module=module,
|
|
69
|
+
loss_fn=loss_fn,
|
|
70
|
+
optimizer_fn=optimizer_fn,
|
|
71
|
+
lr=lr,
|
|
72
|
+
is_feature_incremental=is_feature_incremental,
|
|
73
|
+
device=device,
|
|
74
|
+
seed=seed,
|
|
75
|
+
gradient_clip_value=gradient_clip_value,
|
|
76
|
+
**kwargs,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def learn_one(self, y: float, x: dict | None = None) -> None:
|
|
80
|
+
"""Update the model with one target and optional exogenous features."""
|
|
81
|
+
x = x or {}
|
|
82
|
+
self._update_observed_features(x)
|
|
83
|
+
x_t = self._make_input_tensor(self._y_window, x)
|
|
84
|
+
y_t = torch.tensor([[y]], dtype=torch.float32, device=self.device)
|
|
85
|
+
self._learn(x_t, y_t)
|
|
86
|
+
self._y_window.append(float(y))
|
|
87
|
+
|
|
88
|
+
def forecast(self, horizon: int, xs: list[dict] | None = None) -> list:
|
|
89
|
+
"""Forecast ``horizon`` steps ahead using autoregressive rollouts."""
|
|
90
|
+
if horizon < 0:
|
|
91
|
+
raise ValueError("horizon must be non-negative")
|
|
92
|
+
if xs is None:
|
|
93
|
+
xs = [{} for _ in range(horizon)]
|
|
94
|
+
if len(xs) != horizon:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"the length of xs should be equal to the specified horizon"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
y_window = collections.deque(self._y_window, maxlen=self.window_size)
|
|
100
|
+
forecasts = []
|
|
101
|
+
|
|
102
|
+
self.module.eval()
|
|
103
|
+
with torch.inference_mode():
|
|
104
|
+
for x in xs:
|
|
105
|
+
x = x or {}
|
|
106
|
+
self._update_observed_features(x)
|
|
107
|
+
x_t = self._make_input_tensor(y_window, x)
|
|
108
|
+
y_pred = self.module(x_t)
|
|
109
|
+
if isinstance(y_pred, torch.Tensor):
|
|
110
|
+
y_pred = y_pred.detach().view(-1)[-1].cpu().item()
|
|
111
|
+
else:
|
|
112
|
+
y_pred = float(y_pred)
|
|
113
|
+
forecasts.append(y_pred)
|
|
114
|
+
y_window.append(float(y_pred))
|
|
115
|
+
|
|
116
|
+
return forecasts
|
|
117
|
+
|
|
118
|
+
def _update_observed_features(self, x):
|
|
119
|
+
"""Track exogenous features and expand the first layer when requested."""
|
|
120
|
+
if not x:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
prev_feature_count = len(self.observed_features)
|
|
124
|
+
self.observed_features.update(x.keys())
|
|
125
|
+
|
|
126
|
+
if self.is_feature_incremental and self.input_layer:
|
|
127
|
+
target_size = self._target_input_size()
|
|
128
|
+
if self._get_input_size() < target_size:
|
|
129
|
+
self._expand_layer(
|
|
130
|
+
self.input_layer, target_size=target_size, output=False
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return len(self.observed_features) > prev_feature_count
|
|
134
|
+
|
|
135
|
+
def _target_input_size(self) -> int:
|
|
136
|
+
endogenous_size = 1 if self.is_sequence_model else self.window_size
|
|
137
|
+
return endogenous_size + len(self.observed_features)
|
|
138
|
+
|
|
139
|
+
def _n_exogenous_inputs(self) -> int:
|
|
140
|
+
endogenous_size = 1 if self.is_sequence_model else self.window_size
|
|
141
|
+
return max(0, self._get_input_size() - endogenous_size)
|
|
142
|
+
|
|
143
|
+
def _lag_values(self, y_window: Deque[float]) -> list[float]:
|
|
144
|
+
values = list(y_window)[-self.window_size :]
|
|
145
|
+
return [0.0] * (self.window_size - len(values)) + values
|
|
146
|
+
|
|
147
|
+
def _exogenous_values(self, x: dict) -> list[float]:
|
|
148
|
+
values = [
|
|
149
|
+
float(x.get(feature, 0.0) or 0.0) for feature in self.observed_features
|
|
150
|
+
]
|
|
151
|
+
n_exogenous_inputs = self._n_exogenous_inputs()
|
|
152
|
+
if len(values) < n_exogenous_inputs:
|
|
153
|
+
values.extend([0.0] * (n_exogenous_inputs - len(values)))
|
|
154
|
+
return values[:n_exogenous_inputs]
|
|
155
|
+
|
|
156
|
+
def _make_input_tensor(self, y_window: Deque[float], x: dict) -> torch.Tensor:
|
|
157
|
+
lags = self._lag_values(y_window)
|
|
158
|
+
exogenous = self._exogenous_values(x)
|
|
159
|
+
|
|
160
|
+
if self.is_sequence_model:
|
|
161
|
+
rows = [[lag, *exogenous] for lag in lags]
|
|
162
|
+
return torch.tensor(
|
|
163
|
+
rows, dtype=torch.float32, device=self.device
|
|
164
|
+
).unsqueeze(1)
|
|
165
|
+
|
|
166
|
+
row = [*lags, *exogenous]
|
|
167
|
+
return torch.tensor([row], dtype=torch.float32, device=self.device)
|
|
168
|
+
|
|
169
|
+
def _get_runtime_state(self):
|
|
170
|
+
state = super()._get_runtime_state()
|
|
171
|
+
state["target_window"] = list(self._y_window)
|
|
172
|
+
return state
|
|
173
|
+
|
|
174
|
+
def _restore_runtime_state(self, state):
|
|
175
|
+
super()._restore_runtime_state(state)
|
|
176
|
+
if "target_window" in state:
|
|
177
|
+
self._y_window = collections.deque(
|
|
178
|
+
state["target_window"], maxlen=getattr(self, "window_size", 10)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def _unit_test_params(cls):
|
|
183
|
+
yield {
|
|
184
|
+
"module": _TestModule(n_features=4),
|
|
185
|
+
"window_size": 2,
|
|
186
|
+
"loss_fn": "mse",
|
|
187
|
+
"optimizer_fn": "sgd",
|
|
188
|
+
"is_feature_incremental": False,
|
|
189
|
+
}
|