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.
Files changed (41) hide show
  1. {deep_river-0.3.2 → deep_river-0.3.4}/PKG-INFO +14 -57
  2. {deep_river-0.3.2 → deep_river-0.3.4}/README.md +5 -5
  3. deep_river-0.3.4/deep_river/__init__.py +7 -0
  4. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/ae.py +1 -1
  5. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/probability_weighted_ae.py +2 -1
  6. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/rolling_ae.py +11 -7
  7. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/scaler.py +12 -12
  8. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/base.py +2 -2
  9. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/classification/classifier.py +3 -1
  10. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/classification/rolling_classifier.py +3 -12
  11. deep_river-0.3.4/deep_river/forecasting/__init__.py +23 -0
  12. deep_river-0.3.4/deep_river/forecasting/forecaster.py +189 -0
  13. deep_river-0.3.4/deep_river/forecasting/zoo.py +601 -0
  14. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/multioutput.py +1 -1
  15. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/regressor.py +4 -4
  16. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/rolling_regressor.py +5 -18
  17. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/estimator_checks.py +22 -7
  18. deep_river-0.3.4/deep_river/utils/inspect.py +13 -0
  19. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/tensor_conversion.py +1 -1
  20. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/test_tensor_conversion.py +2 -2
  21. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river.egg-info/PKG-INFO +14 -57
  22. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river.egg-info/SOURCES.txt +3 -1
  23. deep_river-0.3.4/deep_river.egg-info/requires.txt +13 -0
  24. {deep_river-0.3.2 → deep_river-0.3.4}/pyproject.toml +23 -63
  25. deep_river-0.3.2/deep_river/__init__.py +0 -4
  26. deep_river-0.3.2/deep_river/__version__.py +0 -3
  27. deep_river-0.3.2/deep_river/utils/inspect.py +0 -7
  28. deep_river-0.3.2/deep_river.egg-info/requires.txt +0 -46
  29. {deep_river-0.3.2 → deep_river-0.3.4}/LICENSE +0 -0
  30. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/anomaly/__init__.py +0 -0
  31. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/classification/__init__.py +0 -0
  32. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/classification/zoo.py +0 -0
  33. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/__init__.py +0 -0
  34. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/regression/zoo.py +0 -0
  35. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/__init__.py +0 -0
  36. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/hooks.py +0 -0
  37. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/params.py +0 -0
  38. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river/utils/test_estimators.py +0 -0
  39. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river.egg-info/dependency_links.txt +0 -0
  40. {deep_river-0.3.2 → deep_river-0.3.4}/deep_river.egg-info/top_level.txt +0 -0
  41. {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.2
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
- Classifier: Programming Language :: Python
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: scikit-learn~=1.5.0
22
- Requires-Dist: torch==2.2.2
23
- Requires-Dist: pandas~=2.2.2
24
- Requires-Dist: numpy~=1.26.4
25
- Requires-Dist: river
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/master/docs/img/logo.png" alt="incremental dl logo">
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=master&logo=codecov&label=coverage" />
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/master/docs/examples) of the features and functionality.
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/master.zip
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/thumb/4/44/Fzi_logo.svg/1200px-Fzi_logo.svg.png?raw=true" alt="FZI Logo" height="200"/>
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/master/docs/img/logo.png" alt="incremental dl logo">
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=master&logo=codecov&label=coverage" />
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/master/docs/examples) of the features and functionality.
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/master.zip
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/thumb/4/44/Fzi_logo.svg/1200px-Fzi_logo.svg.png?raw=true" alt="FZI Logo" height="200"/>
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">
@@ -0,0 +1,7 @@
1
+ from importlib.metadata import version as _version
2
+
3
+ from . import anomaly, classification, forecasting, regression, utils
4
+
5
+ __version__ = _version("deep-river")
6
+
7
+ __all__ = ["anomaly", "classification", "forecasting", "regression", "utils"]
@@ -188,5 +188,5 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
188
188
  self.loss_func(x_pred, x_t, reduction="none"),
189
189
  dim=list(range(1, x_t.dim())),
190
190
  )
191
- score = loss.cpu().detach().numpy()
191
+ score = np.asarray(loss.cpu().detach().tolist())
192
192
  return score
@@ -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().numpy()
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(list(x.values()))
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._x_window.append(X.values.tolist())
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(list(x.values()))
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(list(x.values()))
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.append(X.values.tolist())
229
+ x_win.extend(X.values.tolist())
226
230
  if self.append_predict:
227
- self._x_window.append(X.values.tolist())
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().numpy()
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(self) -> dict:
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
- return {"anomaly_detector": HalfSpaceTrees()}
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
- # LSTM special case
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(self, x: dict) -> dict[base.typing.ClfTarget, float]:
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._update_observed_features(X)
196
- X = X[list(self.observed_features)]
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
+ }