deep-river 0.2.6__tar.gz → 0.2.7__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.2.6 → deep_river-0.2.7}/PKG-INFO +36 -34
- {deep_river-0.2.6 → deep_river-0.2.7}/README.md +3 -2
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/__version__.py +1 -1
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/anomaly/ae.py +55 -27
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/anomaly/probability_weighted_ae.py +14 -10
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/anomaly/rolling_ae.py +16 -23
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/base.py +102 -7
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/classification/classifier.py +100 -148
- deep_river-0.2.7/deep_river/classification/rolling_classifier.py +291 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/classification/zoo.py +40 -28
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/regression/multioutput.py +8 -7
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/regression/regressor.py +32 -21
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/regression/rolling_regressor.py +38 -33
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/regression/zoo.py +27 -20
- deep_river-0.2.7/deep_river/utils/layer_adaptation.py +239 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/utils/params.py +8 -7
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/utils/tensor_conversion.py +46 -23
- deep_river-0.2.7/deep_river/utils/test_layer_adaptation.py +179 -0
- deep_river-0.2.7/deep_river/utils/test_tensor_conversion.py +156 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river.egg-info/PKG-INFO +36 -34
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river.egg-info/SOURCES.txt +2 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river.egg-info/requires.txt +32 -31
- {deep_river-0.2.6 → deep_river-0.2.7}/setup.py +14 -14
- deep_river-0.2.6/deep_river/classification/rolling_classifier.py +0 -605
- deep_river-0.2.6/deep_river/utils/test_tensor_conversion.py +0 -117
- {deep_river-0.2.6 → deep_river-0.2.7}/LICENSE +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/__init__.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/anomaly/__init__.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/anomaly/scaler.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/classification/__init__.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/regression/__init__.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/utils/__init__.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/utils/estimator_checks.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/utils/hooks.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river/utils/test_estimators.py +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river.egg-info/dependency_links.txt +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/deep_river.egg-info/top_level.txt +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/pyproject.toml +0 -0
- {deep_river-0.2.6 → deep_river-0.2.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: deep_river
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Online Deep Learning for river
|
|
5
5
|
Home-page: https://online-ml.github.io/deep-river/
|
|
6
6
|
Author: Cedric Kulbach
|
|
@@ -17,42 +17,44 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
17
17
|
Requires-Python: >=3.6.0
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: scikit-learn~=1.
|
|
20
|
+
Requires-Dist: scikit-learn~=1.5.0
|
|
21
21
|
Requires-Dist: torch~=2.2.2
|
|
22
22
|
Requires-Dist: pandas~=2.2.2
|
|
23
23
|
Requires-Dist: numpy~=1.26.4
|
|
24
|
-
Requires-Dist: river~=0.21.
|
|
25
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: river~=0.21.2
|
|
25
|
+
Requires-Dist: scipy~=1.13.0
|
|
26
|
+
Requires-Dist: tqdm~=4.66.5
|
|
26
27
|
Requires-Dist: ordered-set~=4.1.0
|
|
27
28
|
Requires-Dist: torchviz~=0.0.2
|
|
28
29
|
Provides-Extra: dev
|
|
29
|
-
Requires-Dist: scikit-learn~=1.
|
|
30
|
+
Requires-Dist: scikit-learn~=1.5.0; extra == "dev"
|
|
30
31
|
Requires-Dist: torch~=2.2.2; extra == "dev"
|
|
31
32
|
Requires-Dist: pandas~=2.2.2; extra == "dev"
|
|
32
33
|
Requires-Dist: numpy~=1.26.4; extra == "dev"
|
|
33
|
-
Requires-Dist: river~=0.21.
|
|
34
|
-
Requires-Dist:
|
|
34
|
+
Requires-Dist: river~=0.21.2; extra == "dev"
|
|
35
|
+
Requires-Dist: scipy~=1.13.0; extra == "dev"
|
|
36
|
+
Requires-Dist: tqdm~=4.66.5; extra == "dev"
|
|
35
37
|
Requires-Dist: ordered-set~=4.1.0; extra == "dev"
|
|
36
38
|
Requires-Dist: torchviz~=0.0.2; extra == "dev"
|
|
37
|
-
Requires-Dist: graphviz>=0.
|
|
38
|
-
Requires-Dist: matplotlib>=3.
|
|
39
|
-
Requires-Dist: mypy>=
|
|
40
|
-
Requires-Dist: pre-commit>=
|
|
41
|
-
Requires-Dist: pytest>=
|
|
42
|
-
Requires-Dist: pytest-cov>=
|
|
43
|
-
Requires-Dist:
|
|
44
|
-
Requires-Dist:
|
|
45
|
-
Requires-Dist:
|
|
46
|
-
Requires-Dist: isort>=5.10.1; extra == "dev"
|
|
39
|
+
Requires-Dist: graphviz>=0.20.3; extra == "dev"
|
|
40
|
+
Requires-Dist: matplotlib>=3.9.2; extra == "dev"
|
|
41
|
+
Requires-Dist: mypy>=1.11.1; extra == "dev"
|
|
42
|
+
Requires-Dist: pre-commit>=3.8.0; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest>=8.3.2; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
45
|
+
Requires-Dist: black>=24.8.0; extra == "dev"
|
|
46
|
+
Requires-Dist: flake8>=7.1.1; extra == "dev"
|
|
47
|
+
Requires-Dist: isort>=5.13.2; extra == "dev"
|
|
47
48
|
Requires-Dist: jupyter>=1.0.0; extra == "dev"
|
|
48
|
-
Requires-Dist: pyupgrade==3.
|
|
49
|
+
Requires-Dist: pyupgrade==3.17.0; extra == "dev"
|
|
49
50
|
Provides-Extra: docs
|
|
50
|
-
Requires-Dist: scikit-learn~=1.
|
|
51
|
+
Requires-Dist: scikit-learn~=1.5.0; extra == "docs"
|
|
51
52
|
Requires-Dist: torch~=2.2.2; extra == "docs"
|
|
52
53
|
Requires-Dist: pandas~=2.2.2; extra == "docs"
|
|
53
54
|
Requires-Dist: numpy~=1.26.4; extra == "docs"
|
|
54
|
-
Requires-Dist: river~=0.21.
|
|
55
|
-
Requires-Dist:
|
|
55
|
+
Requires-Dist: river~=0.21.2; extra == "docs"
|
|
56
|
+
Requires-Dist: scipy~=1.13.0; extra == "docs"
|
|
57
|
+
Requires-Dist: tqdm~=4.66.5; extra == "docs"
|
|
56
58
|
Requires-Dist: ordered-set~=4.1.0; extra == "docs"
|
|
57
59
|
Requires-Dist: torchviz~=0.0.2; extra == "docs"
|
|
58
60
|
Requires-Dist: flask>=2.0.2; extra == "docs"
|
|
@@ -78,18 +80,17 @@ Requires-Dist: mkdocs-charts-plugin; extra == "docs"
|
|
|
78
80
|
Requires-Dist: python-slugify; extra == "docs"
|
|
79
81
|
Requires-Dist: watermark==2.3.1; extra == "docs"
|
|
80
82
|
Provides-Extra: all
|
|
81
|
-
Requires-Dist: graphviz>=0.
|
|
82
|
-
Requires-Dist: matplotlib>=3.
|
|
83
|
-
Requires-Dist: mypy>=
|
|
84
|
-
Requires-Dist: pre-commit>=
|
|
85
|
-
Requires-Dist: pytest>=
|
|
86
|
-
Requires-Dist: pytest-cov>=
|
|
87
|
-
Requires-Dist:
|
|
88
|
-
Requires-Dist:
|
|
89
|
-
Requires-Dist:
|
|
90
|
-
Requires-Dist: isort>=5.10.1; extra == "all"
|
|
83
|
+
Requires-Dist: graphviz>=0.20.3; extra == "all"
|
|
84
|
+
Requires-Dist: matplotlib>=3.9.2; extra == "all"
|
|
85
|
+
Requires-Dist: mypy>=1.11.1; extra == "all"
|
|
86
|
+
Requires-Dist: pre-commit>=3.8.0; extra == "all"
|
|
87
|
+
Requires-Dist: pytest>=8.3.2; extra == "all"
|
|
88
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "all"
|
|
89
|
+
Requires-Dist: black>=24.8.0; extra == "all"
|
|
90
|
+
Requires-Dist: flake8>=7.1.1; extra == "all"
|
|
91
|
+
Requires-Dist: isort>=5.13.2; extra == "all"
|
|
91
92
|
Requires-Dist: jupyter>=1.0.0; extra == "all"
|
|
92
|
-
Requires-Dist: pyupgrade==3.
|
|
93
|
+
Requires-Dist: pyupgrade==3.17.0; extra == "all"
|
|
93
94
|
Requires-Dist: flask>=2.0.2; extra == "all"
|
|
94
95
|
Requires-Dist: ipykernel>=6.9.0; extra == "all"
|
|
95
96
|
Requires-Dist: mike>=0.5.3; extra == "all"
|
|
@@ -125,6 +126,7 @@ Requires-Dist: dataclasses; python_version == "3.6"
|
|
|
125
126
|
</a>
|
|
126
127
|
<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/deep-river">
|
|
127
128
|
<img alt="GitHub" src="https://img.shields.io/github/license/online-ml/deep-river">
|
|
129
|
+
<a href="https://joss.theoj.org/papers/6a76784f55e8b041d71a7fa776eb386a"><img src="https://joss.theoj.org/papers/6a76784f55e8b041d71a7fa776eb386a/status.svg"></a>
|
|
128
130
|
</p>
|
|
129
131
|
<p align="center">
|
|
130
132
|
deep-river is a Python library for online deep learning.
|
|
@@ -194,7 +196,7 @@ For further examples check out the <a href="https://online-ml.github.io/deep-riv
|
|
|
194
196
|
... metric.update(y, y_pred) # update the metric
|
|
195
197
|
... model_pipeline.learn_one(x, y) # make the model learn
|
|
196
198
|
>>> print(f"Accuracy: {metric.get():.4f}")
|
|
197
|
-
Accuracy: 0.
|
|
199
|
+
Accuracy: 0.7264
|
|
198
200
|
|
|
199
201
|
```
|
|
200
202
|
### Multi Target Regression
|
|
@@ -274,7 +276,7 @@ MicroAverage(MAE): 34.31
|
|
|
274
276
|
... metric.update(y, score)
|
|
275
277
|
...
|
|
276
278
|
>>> print(f"ROCAUC: {metric.get():.4f}")
|
|
277
|
-
ROCAUC: 0.
|
|
279
|
+
ROCAUC: 0.7812
|
|
278
280
|
|
|
279
281
|
```
|
|
280
282
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
</a>
|
|
9
9
|
<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/deep-river">
|
|
10
10
|
<img alt="GitHub" src="https://img.shields.io/github/license/online-ml/deep-river">
|
|
11
|
+
<a href="https://joss.theoj.org/papers/6a76784f55e8b041d71a7fa776eb386a"><img src="https://joss.theoj.org/papers/6a76784f55e8b041d71a7fa776eb386a/status.svg"></a>
|
|
11
12
|
</p>
|
|
12
13
|
<p align="center">
|
|
13
14
|
deep-river is a Python library for online deep learning.
|
|
@@ -77,7 +78,7 @@ For further examples check out the <a href="https://online-ml.github.io/deep-riv
|
|
|
77
78
|
... metric.update(y, y_pred) # update the metric
|
|
78
79
|
... model_pipeline.learn_one(x, y) # make the model learn
|
|
79
80
|
>>> print(f"Accuracy: {metric.get():.4f}")
|
|
80
|
-
Accuracy: 0.
|
|
81
|
+
Accuracy: 0.7264
|
|
81
82
|
|
|
82
83
|
```
|
|
83
84
|
### Multi Target Regression
|
|
@@ -157,7 +158,7 @@ MicroAverage(MAE): 34.31
|
|
|
157
158
|
... metric.update(y, score)
|
|
158
159
|
...
|
|
159
160
|
>>> print(f"ROCAUC: {metric.get():.4f}")
|
|
160
|
-
ROCAUC: 0.
|
|
161
|
+
ROCAUC: 0.7812
|
|
161
162
|
|
|
162
163
|
```
|
|
163
164
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Callable, Type, Union
|
|
1
|
+
from typing import Any, Callable, Dict, Type, Union
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
@@ -8,6 +8,7 @@ from torch import nn
|
|
|
8
8
|
|
|
9
9
|
from deep_river.base import DeepEstimator
|
|
10
10
|
from deep_river.utils import dict2tensor
|
|
11
|
+
from deep_river.utils.layer_adaptation import expand_layer
|
|
11
12
|
from deep_river.utils.tensor_conversion import df2tensor
|
|
12
13
|
|
|
13
14
|
|
|
@@ -93,7 +94,7 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
93
94
|
... metric.update(y, score)
|
|
94
95
|
...
|
|
95
96
|
>>> print(f"ROCAUC: {metric.get():.4f}")
|
|
96
|
-
ROCAUC: 0.
|
|
97
|
+
ROCAUC: 0.7812
|
|
97
98
|
"""
|
|
98
99
|
|
|
99
100
|
def __init__(
|
|
@@ -102,6 +103,7 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
102
103
|
loss_fn: Union[str, Callable] = "mse",
|
|
103
104
|
optimizer_fn: Union[str, Callable] = "sgd",
|
|
104
105
|
lr: float = 1e-3,
|
|
106
|
+
is_feature_incremental: bool = False,
|
|
105
107
|
device: str = "cpu",
|
|
106
108
|
seed: int = 42,
|
|
107
109
|
**kwargs,
|
|
@@ -111,10 +113,12 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
111
113
|
loss_fn=loss_fn,
|
|
112
114
|
optimizer_fn=optimizer_fn,
|
|
113
115
|
lr=lr,
|
|
116
|
+
is_feature_incremental=is_feature_incremental,
|
|
114
117
|
device=device,
|
|
115
118
|
seed=seed,
|
|
116
119
|
**kwargs,
|
|
117
120
|
)
|
|
121
|
+
self.is_class_incremental = is_feature_incremental
|
|
118
122
|
|
|
119
123
|
@classmethod
|
|
120
124
|
def _unit_test_params(cls):
|
|
@@ -133,6 +137,7 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
133
137
|
"module": _TestAutoencoder,
|
|
134
138
|
"loss_fn": "mse",
|
|
135
139
|
"optimizer_fn": "sgd",
|
|
140
|
+
"is_feature_incremental": True,
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
@classmethod
|
|
@@ -148,13 +153,7 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
148
153
|
set
|
|
149
154
|
Set of checks to skip during unit testing.
|
|
150
155
|
"""
|
|
151
|
-
return
|
|
152
|
-
"check_shuffle_features_no_impact",
|
|
153
|
-
"check_emerging_features",
|
|
154
|
-
"check_disappearing_features",
|
|
155
|
-
"check_predict_proba_one",
|
|
156
|
-
"check_predict_proba_one_binary",
|
|
157
|
-
}
|
|
156
|
+
return set()
|
|
158
157
|
|
|
159
158
|
def learn_one(self, x: dict, y: Any = None, **kwargs) -> "Autoencoder":
|
|
160
159
|
"""
|
|
@@ -173,14 +172,17 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
173
172
|
The model itself.
|
|
174
173
|
"""
|
|
175
174
|
if not self.module_initialized:
|
|
176
|
-
self.
|
|
177
|
-
self.initialize_module(**self.kwargs)
|
|
178
|
-
|
|
175
|
+
self._update_observed_features(x)
|
|
176
|
+
self.initialize_module(x=x, **self.kwargs)
|
|
177
|
+
self._adapt_input_dim(x)
|
|
178
|
+
return self._learn(
|
|
179
|
+
dict2tensor(x, features=self.observed_features, device=self.device)
|
|
180
|
+
)
|
|
179
181
|
|
|
180
182
|
def _learn(self, x: torch.Tensor) -> "Autoencoder":
|
|
181
183
|
self.module.train()
|
|
182
184
|
x_pred = self.module(x)
|
|
183
|
-
loss = self.
|
|
185
|
+
loss = self.loss_func(x_pred, x)
|
|
184
186
|
loss.backward()
|
|
185
187
|
self.optimizer.step()
|
|
186
188
|
self.optimizer.zero_grad()
|
|
@@ -205,14 +207,18 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
205
207
|
"""
|
|
206
208
|
|
|
207
209
|
if not self.module_initialized:
|
|
208
|
-
self.
|
|
209
|
-
self.initialize_module(**self.kwargs)
|
|
210
|
+
self._update_observed_features(x)
|
|
211
|
+
self.initialize_module(x=x, **self.kwargs)
|
|
210
212
|
|
|
211
|
-
|
|
213
|
+
self._adapt_input_dim(x)
|
|
214
|
+
|
|
215
|
+
x_t = dict2tensor(
|
|
216
|
+
x, features=self.observed_features, device=self.device
|
|
217
|
+
)
|
|
212
218
|
self.module.eval()
|
|
213
219
|
with torch.inference_mode():
|
|
214
220
|
x_pred = self.module(x_t)
|
|
215
|
-
loss = self.
|
|
221
|
+
loss = self.loss_func(x_pred, x_t).item()
|
|
216
222
|
return loss
|
|
217
223
|
|
|
218
224
|
def learn_many(self, X: pd.DataFrame) -> "Autoencoder":
|
|
@@ -231,10 +237,13 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
231
237
|
|
|
232
238
|
"""
|
|
233
239
|
if not self.module_initialized:
|
|
234
|
-
|
|
235
|
-
self.
|
|
236
|
-
|
|
237
|
-
|
|
240
|
+
|
|
241
|
+
self._update_observed_features(X)
|
|
242
|
+
self.initialize_module(x=X, **self.kwargs)
|
|
243
|
+
|
|
244
|
+
self._adapt_input_dim(X)
|
|
245
|
+
X_t = df2tensor(X, features=self.observed_features, device=self.device)
|
|
246
|
+
return self._learn(X_t)
|
|
238
247
|
|
|
239
248
|
def score_many(self, X: pd.DataFrame) -> np.ndarray:
|
|
240
249
|
"""
|
|
@@ -253,16 +262,35 @@ class Autoencoder(DeepEstimator, AnomalyDetector):
|
|
|
253
262
|
indicate more anomalous examples.
|
|
254
263
|
"""
|
|
255
264
|
if not self.module_initialized:
|
|
256
|
-
self.
|
|
257
|
-
self.initialize_module(**self.kwargs)
|
|
258
|
-
|
|
265
|
+
self._update_observed_features(X)
|
|
266
|
+
self.initialize_module(x=X, **self.kwargs)
|
|
267
|
+
|
|
268
|
+
self._adapt_input_dim(X)
|
|
269
|
+
X_t = df2tensor(X, features=self.observed_features, device=self.device)
|
|
259
270
|
|
|
260
271
|
self.module.eval()
|
|
261
272
|
with torch.inference_mode():
|
|
262
|
-
X_pred = self.module(
|
|
273
|
+
X_pred = self.module(X_t)
|
|
263
274
|
loss = torch.mean(
|
|
264
|
-
self.
|
|
265
|
-
dim=list(range(1,
|
|
275
|
+
self.loss_func(X_pred, X_t, reduction="none"),
|
|
276
|
+
dim=list(range(1, X_t.dim())),
|
|
266
277
|
)
|
|
267
278
|
score = loss.cpu().detach().numpy()
|
|
268
279
|
return score
|
|
280
|
+
|
|
281
|
+
def _adapt_input_dim(self, x: Dict | pd.DataFrame):
|
|
282
|
+
has_new_feature = self._update_observed_features(x)
|
|
283
|
+
|
|
284
|
+
if has_new_feature and self.is_feature_incremental:
|
|
285
|
+
expand_layer(
|
|
286
|
+
self.input_layer,
|
|
287
|
+
self.input_expansion_instructions,
|
|
288
|
+
len(self.observed_features),
|
|
289
|
+
output=False,
|
|
290
|
+
)
|
|
291
|
+
expand_layer(
|
|
292
|
+
self.output_layer,
|
|
293
|
+
self.output_expansion_instructions,
|
|
294
|
+
len(self.observed_features),
|
|
295
|
+
output=True,
|
|
296
|
+
)
|
|
@@ -8,6 +8,7 @@ from scipy.special import ndtr
|
|
|
8
8
|
|
|
9
9
|
from deep_river.anomaly import ae
|
|
10
10
|
from deep_river.utils import dict2tensor
|
|
11
|
+
from deep_river.utils.tensor_conversion import df2tensor
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class ProbabilityWeightedAutoencoder(ae.Autoencoder):
|
|
@@ -87,7 +88,7 @@ class ProbabilityWeightedAutoencoder(ae.Autoencoder):
|
|
|
87
88
|
... metric.update(y, score)
|
|
88
89
|
...
|
|
89
90
|
>>> print(f"ROCAUC: {metric.get():.4f}")
|
|
90
|
-
ROCAUC: 0.
|
|
91
|
+
ROCAUC: 0.7572
|
|
91
92
|
"""
|
|
92
93
|
|
|
93
94
|
def __init__(
|
|
@@ -138,13 +139,15 @@ class ProbabilityWeightedAutoencoder(ae.Autoencoder):
|
|
|
138
139
|
The autoencoder itself.
|
|
139
140
|
"""
|
|
140
141
|
if not self.module_initialized:
|
|
141
|
-
self.
|
|
142
|
-
self.initialize_module(**self.kwargs)
|
|
143
|
-
|
|
142
|
+
self._update_observed_features(x)
|
|
143
|
+
self.initialize_module(x=x, **self.kwargs)
|
|
144
|
+
|
|
145
|
+
self._adapt_input_dim(x)
|
|
146
|
+
x_t = dict2tensor(x, self.observed_features, device=self.device)
|
|
144
147
|
|
|
145
148
|
self.module.train()
|
|
146
149
|
x_pred = self.module(x_t)
|
|
147
|
-
loss = self.
|
|
150
|
+
loss = self.loss_func(x_pred, x_t)
|
|
148
151
|
self._apply_loss(loss)
|
|
149
152
|
return self
|
|
150
153
|
|
|
@@ -173,15 +176,16 @@ class ProbabilityWeightedAutoencoder(ae.Autoencoder):
|
|
|
173
176
|
|
|
174
177
|
def learn_many(self, X: pd.DataFrame) -> "ProbabilityWeightedAutoencoder":
|
|
175
178
|
if not self.module_initialized:
|
|
176
|
-
self.
|
|
177
|
-
self.initialize_module(**self.kwargs)
|
|
178
|
-
|
|
179
|
+
self._update_observed_features(X)
|
|
180
|
+
self.initialize_module(x=X, **self.kwargs)
|
|
181
|
+
self._adapt_input_dim(X)
|
|
182
|
+
X_t = df2tensor(X, features=self.observed_features, device=self.device)
|
|
179
183
|
|
|
180
184
|
self.module.train()
|
|
181
185
|
x_pred = self.module(X)
|
|
182
186
|
loss = torch.mean(
|
|
183
|
-
self.
|
|
184
|
-
dim=list(range(1,
|
|
187
|
+
self.loss_func(x_pred, X_t, reduction="none"),
|
|
188
|
+
dim=list(range(1, X_t.dim())),
|
|
185
189
|
)
|
|
186
190
|
self._apply_loss(loss)
|
|
187
191
|
return self
|
|
@@ -26,20 +26,10 @@ class _TestLSTMAutoencoder(nn.Module):
|
|
|
26
26
|
num_layers=n_layers,
|
|
27
27
|
batch_first=batch_first,
|
|
28
28
|
)
|
|
29
|
-
self.decoder = nn.LSTM(
|
|
30
|
-
input_size=hidden_size,
|
|
31
|
-
hidden_size=n_features,
|
|
32
|
-
num_layers=n_layers,
|
|
33
|
-
batch_first=batch_first,
|
|
34
|
-
)
|
|
35
29
|
|
|
36
30
|
def forward(self, x):
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
x_flipped = torch.flip(x[1:], dims=[self.time_axis])
|
|
40
|
-
input = torch.cat((h, x_flipped), dim=self.time_axis)
|
|
41
|
-
x_hat, _ = self.decoder(input)
|
|
42
|
-
return torch.flip(x_hat, dims=[self.time_axis])
|
|
31
|
+
output, (h, c) = self.encoder(x)
|
|
32
|
+
return output
|
|
43
33
|
|
|
44
34
|
|
|
45
35
|
class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
@@ -146,7 +136,7 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
146
136
|
def _learn(self, x: torch.Tensor):
|
|
147
137
|
self.module.train()
|
|
148
138
|
x_pred = self.module(x)
|
|
149
|
-
loss = self.
|
|
139
|
+
loss = self.loss_func(x_pred, x)
|
|
150
140
|
|
|
151
141
|
self.optimizer.zero_grad()
|
|
152
142
|
loss.backward()
|
|
@@ -169,8 +159,8 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
169
159
|
The estimator itself.
|
|
170
160
|
"""
|
|
171
161
|
if not self.module_initialized:
|
|
172
|
-
self.
|
|
173
|
-
self.initialize_module(**self.kwargs)
|
|
162
|
+
self._update_observed_features(x)
|
|
163
|
+
self.initialize_module(x=x, **self.kwargs)
|
|
174
164
|
|
|
175
165
|
self._x_window.append(list(x.values()))
|
|
176
166
|
|
|
@@ -197,8 +187,8 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
197
187
|
The estimator itself.
|
|
198
188
|
"""
|
|
199
189
|
if not self.module_initialized:
|
|
200
|
-
self.
|
|
201
|
-
self.initialize_module(**self.kwargs)
|
|
190
|
+
self._update_observed_features(X)
|
|
191
|
+
self.initialize_module(x=X, **self.kwargs)
|
|
202
192
|
|
|
203
193
|
self._x_window.append(X.values.tolist())
|
|
204
194
|
if len(self._x_window) == self.window_size:
|
|
@@ -209,8 +199,10 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
209
199
|
def score_one(self, x: dict) -> float:
|
|
210
200
|
res = 0.0
|
|
211
201
|
if not self.module_initialized:
|
|
212
|
-
|
|
213
|
-
self.
|
|
202
|
+
|
|
203
|
+
self._update_observed_features(x)
|
|
204
|
+
|
|
205
|
+
self.initialize_module(x=x, **self.kwargs)
|
|
214
206
|
|
|
215
207
|
if len(self._x_window) == self.window_size:
|
|
216
208
|
x_win = self._x_window.copy()
|
|
@@ -219,7 +211,7 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
219
211
|
self.module.eval()
|
|
220
212
|
with torch.inference_mode():
|
|
221
213
|
x_pred = self.module(x_t)
|
|
222
|
-
loss = self.
|
|
214
|
+
loss = self.loss_func(x_pred, x_t)
|
|
223
215
|
res = loss.item()
|
|
224
216
|
|
|
225
217
|
if self.append_predict:
|
|
@@ -228,8 +220,9 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
228
220
|
|
|
229
221
|
def score_many(self, X: pd.DataFrame) -> List[Any]:
|
|
230
222
|
if not self.module_initialized:
|
|
231
|
-
|
|
232
|
-
self.
|
|
223
|
+
|
|
224
|
+
self._update_observed_features(X)
|
|
225
|
+
self.initialize_module(x=X, **self.kwargs)
|
|
233
226
|
|
|
234
227
|
x_win = self._x_window.copy()
|
|
235
228
|
x_win.append(X.values.tolist())
|
|
@@ -242,7 +235,7 @@ class RollingAutoencoder(RollingDeepEstimator, anomaly.base.AnomalyDetector):
|
|
|
242
235
|
with torch.inference_mode():
|
|
243
236
|
x_pred = self.module(X_t)
|
|
244
237
|
loss = torch.mean(
|
|
245
|
-
self.
|
|
238
|
+
self.loss_func(x_pred, x_pred, reduction="none"),
|
|
246
239
|
dim=list(range(1, x_pred.dim())),
|
|
247
240
|
)
|
|
248
241
|
losses = loss.detach().numpy()
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import collections
|
|
2
2
|
import inspect
|
|
3
|
-
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Any, Callable, Deque, Dict, List, Type, Union, cast
|
|
4
5
|
|
|
6
|
+
import pandas as pd
|
|
5
7
|
import torch
|
|
8
|
+
from ordered_set import OrderedSet
|
|
6
9
|
from river import base
|
|
10
|
+
from torch.utils.hooks import RemovableHandle
|
|
7
11
|
|
|
8
12
|
from deep_river.utils import get_loss_fn, get_optim_fn
|
|
13
|
+
from deep_river.utils.hooks import ForwardOrderTracker, apply_hooks
|
|
14
|
+
from deep_river.utils.layer_adaptation import (
|
|
15
|
+
SUPPORTED_LAYERS,
|
|
16
|
+
expand_layer,
|
|
17
|
+
load_instructions,
|
|
18
|
+
)
|
|
9
19
|
|
|
10
20
|
try:
|
|
11
21
|
from graphviz import Digraph
|
|
@@ -53,19 +63,29 @@ class DeepEstimator(base.Estimator):
|
|
|
53
63
|
loss_fn: Union[str, Callable] = "mse",
|
|
54
64
|
optimizer_fn: Union[str, Callable] = "sgd",
|
|
55
65
|
lr: float = 1e-3,
|
|
66
|
+
is_feature_incremental: bool = False,
|
|
56
67
|
device: str = "cpu",
|
|
57
68
|
seed: int = 42,
|
|
58
69
|
**kwargs,
|
|
59
70
|
):
|
|
60
71
|
super().__init__()
|
|
61
72
|
self.module_cls = module
|
|
62
|
-
self.module: torch.nn.Module = cast(torch.nn.Module, None)
|
|
63
|
-
self.
|
|
64
|
-
self.
|
|
73
|
+
self.module: torch.nn.Module = cast(torch.nn.Module, None)
|
|
74
|
+
self.loss_func = get_loss_fn(loss_fn)
|
|
75
|
+
self.loss_fn = loss_fn
|
|
76
|
+
self.optimizer_func = get_optim_fn(optimizer_fn)
|
|
77
|
+
self.optimizer_fn = optimizer_fn
|
|
78
|
+
self.is_feature_incremental = is_feature_incremental
|
|
79
|
+
self.is_class_incremental: bool = False
|
|
80
|
+
self.observed_features: OrderedSet[str] = OrderedSet([])
|
|
65
81
|
self.lr = lr
|
|
66
82
|
self.device = device
|
|
67
83
|
self.kwargs = kwargs
|
|
68
84
|
self.seed = seed
|
|
85
|
+
self.input_layer = cast(torch.nn.Module, None)
|
|
86
|
+
self.input_expansion_instructions = cast(Dict, None)
|
|
87
|
+
self.output_layer = cast(torch.nn.Module, None)
|
|
88
|
+
self.output_expansion_instructions = cast(Dict, None)
|
|
69
89
|
self.module_initialized = False
|
|
70
90
|
torch.manual_seed(seed)
|
|
71
91
|
|
|
@@ -103,7 +123,7 @@ class DeepEstimator(base.Estimator):
|
|
|
103
123
|
y_pred.mean(), params=dict(self.module.named_parameters())
|
|
104
124
|
)
|
|
105
125
|
|
|
106
|
-
def initialize_module(self, **kwargs):
|
|
126
|
+
def initialize_module(self, x: dict | pd.DataFrame, **kwargs):
|
|
107
127
|
"""
|
|
108
128
|
Parameters
|
|
109
129
|
----------
|
|
@@ -118,17 +138,26 @@ class DeepEstimator(base.Estimator):
|
|
|
118
138
|
instance
|
|
119
139
|
The initialized component.
|
|
120
140
|
"""
|
|
141
|
+
torch.manual_seed(self.seed)
|
|
142
|
+
if isinstance(x, Dict):
|
|
143
|
+
n_features = len(x)
|
|
144
|
+
elif isinstance(x, pd.DataFrame):
|
|
145
|
+
n_features = len(x.columns)
|
|
146
|
+
|
|
121
147
|
if not isinstance(self.module_cls, torch.nn.Module):
|
|
122
148
|
self.module = self.module_cls(
|
|
123
|
-
|
|
149
|
+
n_features=n_features,
|
|
150
|
+
**self._filter_kwargs(self.module_cls, kwargs),
|
|
124
151
|
)
|
|
125
152
|
|
|
126
153
|
self.module.to(self.device)
|
|
127
|
-
self.optimizer = self.
|
|
154
|
+
self.optimizer = self.optimizer_func(
|
|
128
155
|
self.module.parameters(), lr=self.lr
|
|
129
156
|
)
|
|
130
157
|
self.module_initialized = True
|
|
131
158
|
|
|
159
|
+
self._get_input_output_layers(n_features=n_features)
|
|
160
|
+
|
|
132
161
|
def clone(
|
|
133
162
|
self,
|
|
134
163
|
new_params: dict[Any, Any] | None = None,
|
|
@@ -160,6 +189,72 @@ class DeepEstimator(base.Estimator):
|
|
|
160
189
|
clone.__dict__.update(self.__dict__)
|
|
161
190
|
return clone
|
|
162
191
|
|
|
192
|
+
def _adapt_input_dim(self, x: Dict | pd.DataFrame):
|
|
193
|
+
has_new_feature = self._update_observed_features(x)
|
|
194
|
+
|
|
195
|
+
if has_new_feature and self.is_feature_incremental:
|
|
196
|
+
expand_layer(
|
|
197
|
+
self.input_layer,
|
|
198
|
+
self.input_expansion_instructions,
|
|
199
|
+
len(self.observed_features),
|
|
200
|
+
output=False,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _update_observed_features(self, x):
|
|
204
|
+
n_existing_features = len(self.observed_features)
|
|
205
|
+
if isinstance(x, Dict):
|
|
206
|
+
self.observed_features |= x.keys()
|
|
207
|
+
else:
|
|
208
|
+
self.observed_features |= x.columns
|
|
209
|
+
|
|
210
|
+
if len(self.observed_features) > n_existing_features:
|
|
211
|
+
self.observed_features = OrderedSet(sorted(self.observed_features))
|
|
212
|
+
return True
|
|
213
|
+
else:
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
def _get_input_output_layers(self, n_features: int):
|
|
217
|
+
handles: List[RemovableHandle] = []
|
|
218
|
+
tracker = ForwardOrderTracker()
|
|
219
|
+
apply_hooks(module=self.module, hook=tracker, handles=handles)
|
|
220
|
+
|
|
221
|
+
x_dummy = torch.empty((1, n_features), device=self.device)
|
|
222
|
+
self.module(x_dummy)
|
|
223
|
+
|
|
224
|
+
for h in handles:
|
|
225
|
+
h.remove()
|
|
226
|
+
|
|
227
|
+
if self.is_class_incremental:
|
|
228
|
+
if tracker.ordered_modules and isinstance(
|
|
229
|
+
tracker.ordered_modules[-1], SUPPORTED_LAYERS
|
|
230
|
+
):
|
|
231
|
+
|
|
232
|
+
self.output_layer = tracker.ordered_modules[-1]
|
|
233
|
+
self.output_expansion_instructions = load_instructions(
|
|
234
|
+
self.output_layer
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
warnings.warn(
|
|
238
|
+
"The model will not be able to adapt its output to new "
|
|
239
|
+
"classes since no supported output layer was found."
|
|
240
|
+
)
|
|
241
|
+
self.is_class_incremental = False
|
|
242
|
+
|
|
243
|
+
if self.is_feature_incremental:
|
|
244
|
+
if tracker.ordered_modules and isinstance(
|
|
245
|
+
tracker.ordered_modules[0], SUPPORTED_LAYERS
|
|
246
|
+
):
|
|
247
|
+
self.input_layer = tracker.ordered_modules[0]
|
|
248
|
+
self.input_expansion_instructions = load_instructions(
|
|
249
|
+
self.input_layer
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
warnings.warn(
|
|
253
|
+
"The model will not be able to adapt its input layer to "
|
|
254
|
+
"new features since no supported input layer was found."
|
|
255
|
+
)
|
|
256
|
+
self.is_feature_incremental = False
|
|
257
|
+
|
|
163
258
|
|
|
164
259
|
class RollingDeepEstimator(DeepEstimator):
|
|
165
260
|
"""
|