deep-river 0.3.1__tar.gz → 0.3.2__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.1 → deep_river-0.3.2}/PKG-INFO +142 -191
- deep_river-0.3.2/README.md +257 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/__version__.py +1 -1
- deep_river-0.3.2/deep_river/classification/__init__.py +22 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/classification/classifier.py +33 -20
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/classification/rolling_classifier.py +47 -33
- deep_river-0.3.2/deep_river/classification/zoo.py +545 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/regression/__init__.py +6 -4
- deep_river-0.3.2/deep_river/regression/multioutput.py +343 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/regression/regressor.py +30 -13
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/regression/rolling_regressor.py +30 -7
- deep_river-0.3.2/deep_river/regression/zoo.py +489 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/utils/estimator_checks.py +1 -37
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/utils/tensor_conversion.py +24 -12
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river.egg-info/PKG-INFO +142 -191
- {deep_river-0.3.1 → deep_river-0.3.2}/pyproject.toml +10 -2
- deep_river-0.3.1/README.md +0 -306
- deep_river-0.3.1/deep_river/classification/__init__.py +0 -20
- deep_river-0.3.1/deep_river/classification/zoo.py +0 -310
- deep_river-0.3.1/deep_river/regression/multioutput.py +0 -126
- deep_river-0.3.1/deep_river/regression/zoo.py +0 -288
- {deep_river-0.3.1 → deep_river-0.3.2}/LICENSE +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/__init__.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/anomaly/__init__.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/anomaly/ae.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/anomaly/probability_weighted_ae.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/anomaly/rolling_ae.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/anomaly/scaler.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/base.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/utils/__init__.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/utils/hooks.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/utils/inspect.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/utils/params.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/utils/test_estimators.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river/utils/test_tensor_conversion.py +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river.egg-info/SOURCES.txt +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river.egg-info/dependency_links.txt +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river.egg-info/requires.txt +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/deep_river.egg-info/top_level.txt +0 -0
- {deep_river-0.3.1 → deep_river-0.3.2}/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.2
|
|
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
|
|
@@ -70,8 +70,9 @@ Dynamic: license-file
|
|
|
70
70
|
</p>
|
|
71
71
|
<p align="center">
|
|
72
72
|
<img alt="PyPI" src="https://img.shields.io/pypi/v/deep-river">
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
<!-- Single Coverage Badge (Shields.io) -->
|
|
74
|
+
<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" />
|
|
75
76
|
</a>
|
|
76
77
|
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/deep-river">
|
|
77
78
|
<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/deep-river">
|
|
@@ -136,209 +137,160 @@ We build the development of neural networks on top of the <a href="https://www.r
|
|
|
136
137
|
The following example creates a simple MLP architecture based on PyTorch and incrementally predicts and trains on the website phishing dataset.
|
|
137
138
|
For further examples check out the <a href="https://online-ml.github.io/deep-river">Documentation</a>.
|
|
138
139
|
|
|
140
|
+
|
|
139
141
|
### Classification
|
|
140
142
|
|
|
141
143
|
```python
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
...
|
|
164
|
-
|
|
165
|
-
...
|
|
166
|
-
|
|
167
|
-
...
|
|
168
|
-
self.dense1 = nn.Linear(5, 2)
|
|
169
|
-
...
|
|
170
|
-
self.softmax = nn.Softmax(dim=-1)
|
|
171
|
-
...
|
|
172
|
-
...
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def forward(self, X, **kwargs):
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
...
|
|
179
|
-
X = self.nonlin(self.dense0(X))
|
|
180
|
-
...
|
|
181
|
-
X = self.nonlin(self.dense1(X))
|
|
182
|
-
...
|
|
183
|
-
X = self.softmax(X)
|
|
184
|
-
...
|
|
185
|
-
return X
|
|
186
|
-
|
|
187
|
-
>> > model_pipeline = compose.Pipeline(
|
|
188
|
-
...
|
|
189
|
-
preprocessing.StandardScaler(),
|
|
190
|
-
...
|
|
191
|
-
classification.Classifier(module=MyModule(10), loss_fn='binary_cross_entropy',
|
|
192
|
-
optimizer_fn='adam')
|
|
144
|
+
>>> import random, numpy as np
|
|
145
|
+
>>> from river import metrics, datasets, preprocessing, compose
|
|
146
|
+
>>> from deep_river.classification import Classifier
|
|
147
|
+
>>> from torch import nn, manual_seed
|
|
148
|
+
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
|
|
149
|
+
>>> first_x, _ = next(iter(datasets.Phishing()))
|
|
150
|
+
>>> n_features = len(first_x)
|
|
151
|
+
>>> class MyModule(nn.Module):
|
|
152
|
+
... def __init__(self, n_features):
|
|
153
|
+
... super().__init__()
|
|
154
|
+
... self.net = nn.Sequential(
|
|
155
|
+
... nn.Linear(n_features, 16),
|
|
156
|
+
... nn.ReLU(),
|
|
157
|
+
... nn.Linear(16, 2)
|
|
158
|
+
... )
|
|
159
|
+
... def forward(self, x):
|
|
160
|
+
... return self.net(x)
|
|
161
|
+
>>> model = compose.Pipeline(
|
|
162
|
+
... preprocessing.StandardScaler(),
|
|
163
|
+
... Classifier(
|
|
164
|
+
... module=MyModule(n_features),
|
|
165
|
+
... loss_fn='cross_entropy',
|
|
166
|
+
... optimizer_fn='adam',
|
|
167
|
+
... lr=1e-3,
|
|
168
|
+
... is_class_incremental=True
|
|
169
|
+
... )
|
|
193
170
|
... )
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
...
|
|
204
|
-
model_pipeline.learn_one(x, y) # make the model learn
|
|
205
|
-
>> > print(f"Accuracy: {metric.get():.4f}")
|
|
206
|
-
Accuracy: 0.7264
|
|
171
|
+
>>> metric = metrics.Accuracy()
|
|
172
|
+
>>> for i, (x, y) in enumerate(datasets.Phishing().take(200)):
|
|
173
|
+
... if i > 0:
|
|
174
|
+
... y_pred = model.predict_one(x)
|
|
175
|
+
... metric.update(y, y_pred)
|
|
176
|
+
... model.learn_one(x, y)
|
|
177
|
+
>>> assert 0.5 <= metric.get() <= 1.0
|
|
178
|
+
>>> print(f"Accuracy: {metric.get():.4f}") # doctest: +ELLIPSIS
|
|
179
|
+
Accuracy: ...
|
|
207
180
|
|
|
208
181
|
```
|
|
182
|
+
|
|
209
183
|
### Multi Target Regression
|
|
210
184
|
|
|
211
185
|
```python
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
...
|
|
234
|
-
|
|
235
|
-
...
|
|
236
|
-
...
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def forward(self, X, **kwargs):
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
...
|
|
243
|
-
X = self.dense0(X)
|
|
244
|
-
...
|
|
245
|
-
return X
|
|
246
|
-
|
|
247
|
-
>> > dataset = stream.iter_sklearn_dataset(
|
|
248
|
-
...
|
|
249
|
-
dataset = datasets.load_linnerud(),
|
|
250
|
-
...
|
|
251
|
-
shuffle = True,
|
|
252
|
-
...
|
|
253
|
-
seed = 42
|
|
186
|
+
>>> import random, numpy as np
|
|
187
|
+
>>> from river import stream, metrics, preprocessing, compose
|
|
188
|
+
>>> from sklearn import datasets as sk_datasets
|
|
189
|
+
>>> from deep_river.regression import MultiTargetRegressor
|
|
190
|
+
>>> from torch import nn, manual_seed
|
|
191
|
+
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
|
|
192
|
+
>>> linnerud_stream = stream.iter_sklearn_dataset(sk_datasets.load_linnerud(), shuffle=True, seed=42)
|
|
193
|
+
>>> first_x, first_y = next(iter(linnerud_stream))
|
|
194
|
+
>>> n_features, n_outputs = len(first_x), len(first_y)
|
|
195
|
+
>>> class TinyNet(nn.Module):
|
|
196
|
+
... def __init__(self, n_features, n_outputs):
|
|
197
|
+
... super().__init__()
|
|
198
|
+
... self.net = nn.Sequential(
|
|
199
|
+
... nn.Linear(n_features, 16),
|
|
200
|
+
... nn.ReLU(),
|
|
201
|
+
... nn.Linear(16, n_outputs)
|
|
202
|
+
... )
|
|
203
|
+
... def forward(self, x):
|
|
204
|
+
... return self.net(x)
|
|
205
|
+
>>> model = compose.Pipeline(
|
|
206
|
+
... preprocessing.StandardScaler(), # feature scaling stabilizes training
|
|
207
|
+
... MultiTargetRegressor(
|
|
208
|
+
... module=TinyNet(n_features, n_outputs),
|
|
209
|
+
... loss_fn='mse', optimizer_fn='adam', lr=5e-3,
|
|
210
|
+
... is_feature_incremental=False, is_target_incremental=False,
|
|
211
|
+
... gradient_clip_value=1.0,
|
|
254
212
|
... )
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
...
|
|
263
|
-
|
|
264
|
-
...
|
|
265
|
-
|
|
266
|
-
...
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
213
|
+
... )
|
|
214
|
+
>>> mae_micro = metrics.multioutput.MicroAverage(metrics.MAE())
|
|
215
|
+
>>> mae_macro = metrics.multioutput.MacroAverage(metrics.MAE())
|
|
216
|
+
>>> rmse_micro = metrics.multioutput.MicroAverage(metrics.RMSE())
|
|
217
|
+
>>> # Recreate the iterator (the first sample was consumed to infer shapes)
|
|
218
|
+
>>> linnerud_stream = stream.iter_sklearn_dataset(sk_datasets.load_linnerud(), shuffle=True, seed=42)
|
|
219
|
+
>>> for i, (x, y_dict) in enumerate(linnerud_stream):
|
|
220
|
+
... if i > 0:
|
|
221
|
+
... y_pred = model.predict_one(x)
|
|
222
|
+
... mae_micro.update(y_dict, y_pred)
|
|
223
|
+
... mae_macro.update(y_dict, y_pred)
|
|
224
|
+
... rmse_micro.update(y_dict, y_pred)
|
|
225
|
+
... model.learn_one(x, y_dict)
|
|
226
|
+
>>> assert 0.0 <= mae_micro.get() < 300.0
|
|
227
|
+
>>> assert 0.0 <= mae_macro.get() < 300.0
|
|
228
|
+
>>> assert 0.0 <= rmse_micro.get() < 400.0
|
|
229
|
+
>>> print({
|
|
230
|
+
... 'MAE_micro': round(mae_micro.get(), 4),
|
|
231
|
+
... 'MAE_macro': round(mae_macro.get(), 4),
|
|
232
|
+
... 'RMSE_micro': round(rmse_micro.get(), 4)
|
|
233
|
+
... }) # doctest: +ELLIPSIS
|
|
234
|
+
{'MAE_micro': ..., 'MAE_macro': ..., 'RMSE_micro': ...}
|
|
273
235
|
|
|
274
236
|
```
|
|
275
237
|
|
|
276
238
|
### Anomaly Detection
|
|
277
239
|
|
|
278
240
|
```python
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
...
|
|
304
|
-
|
|
305
|
-
...
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
...
|
|
310
|
-
|
|
311
|
-
...
|
|
312
|
-
...
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
...
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
...
|
|
333
|
-
score = model.score_one(x)
|
|
334
|
-
...
|
|
335
|
-
model.learn_one(x=x)
|
|
336
|
-
...
|
|
337
|
-
metric.update(y, score)
|
|
338
|
-
...
|
|
339
|
-
>> > print(f"Rolling ROCAUC: {metric.get():.4f}")
|
|
340
|
-
Rolling
|
|
341
|
-
ROCAUC: 0.8901
|
|
241
|
+
>>> import random, numpy as np
|
|
242
|
+
>>> from torch import nn, manual_seed
|
|
243
|
+
>>> from river import metrics
|
|
244
|
+
>>> from deep_river.anomaly import Autoencoder
|
|
245
|
+
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
|
|
246
|
+
>>> # Create a deterministic synthetic stream: normals (y=0) vs. anomalies (y=1)
|
|
247
|
+
>>> def synthetic_stream(n_norm=2000, n_anom=200, n_features=8, seed=42):
|
|
248
|
+
... rng = np.random.default_rng(seed)
|
|
249
|
+
... # Normals around 0, anomalies shifted
|
|
250
|
+
... X_norm = rng.normal(loc=0.0, scale=1.0, size=(n_norm, n_features))
|
|
251
|
+
... y_norm = np.zeros(n_norm, dtype=int)
|
|
252
|
+
... X_anom = rng.normal(loc=5.0, scale=1.0, size=(n_anom, n_features))
|
|
253
|
+
... y_anom = np.ones(n_anom, dtype=int)
|
|
254
|
+
... # Interleave anomalies every k steps to keep streaming flavour
|
|
255
|
+
... k = max(1, n_norm // n_anom)
|
|
256
|
+
... X, Y = [], []
|
|
257
|
+
... i_norm = i_anom = 0
|
|
258
|
+
... for t in range(n_norm + n_anom):
|
|
259
|
+
... take_anom = (t % k == 0) and (i_anom < n_anom)
|
|
260
|
+
... if take_anom:
|
|
261
|
+
... X.append(X_anom[i_anom]); Y.append(y_anom[i_anom]); i_anom += 1
|
|
262
|
+
... else:
|
|
263
|
+
... if i_norm < n_norm:
|
|
264
|
+
... X.append(X_norm[i_norm]); Y.append(y_norm[i_norm]); i_norm += 1
|
|
265
|
+
... elif i_anom < n_anom:
|
|
266
|
+
... X.append(X_anom[i_anom]); Y.append(y_anom[i_anom]); i_anom += 1
|
|
267
|
+
... for row, label in zip(X, Y):
|
|
268
|
+
... yield {f"f{i}": float(v) for i, v in enumerate(row)}, int(label)
|
|
269
|
+
>>> n_features = 8
|
|
270
|
+
>>> class MyAutoEncoder(nn.Module):
|
|
271
|
+
... def __init__(self, n_features, latent_dim=4):
|
|
272
|
+
... super().__init__()
|
|
273
|
+
... self.encoder = nn.Sequential(
|
|
274
|
+
... nn.Linear(n_features, latent_dim),
|
|
275
|
+
... nn.LeakyReLU()
|
|
276
|
+
... )
|
|
277
|
+
... self.decoder = nn.Sequential(
|
|
278
|
+
... nn.Linear(latent_dim, n_features),
|
|
279
|
+
... nn.Sigmoid()
|
|
280
|
+
... )
|
|
281
|
+
... def forward(self, x):
|
|
282
|
+
... z = self.encoder(x)
|
|
283
|
+
... return self.decoder(z)
|
|
284
|
+
>>> ae = Autoencoder(module=MyAutoEncoder(n_features), lr=5e-3, optimizer_fn='adam')
|
|
285
|
+
>>> metric = metrics.ROCAUC()
|
|
286
|
+
>>> # Train only on normal samples to keep the model focused on the normal manifold
|
|
287
|
+
>>> for x, y in synthetic_stream(n_norm=2000, n_anom=200, n_features=n_features, seed=42):
|
|
288
|
+
... score = ae.score_one(x)
|
|
289
|
+
... if y == 0:
|
|
290
|
+
... ae.learn_one(x)
|
|
291
|
+
... metric.update(y, score)
|
|
292
|
+
>>> print(f"ROCAUC: {metric.get():.4f}") # doctest: +ELLIPSIS
|
|
293
|
+
ROCAUC: ...
|
|
342
294
|
|
|
343
295
|
```
|
|
344
296
|
|
|
@@ -361,7 +313,6 @@ To acknowledge the use of the `DeepRiver` library in your research, please refer
|
|
|
361
313
|
}
|
|
362
314
|
```
|
|
363
315
|
|
|
364
|
-
|
|
365
316
|
## 🏫 Affiliations
|
|
366
317
|
|
|
367
318
|
<p align="center">
|
|
@@ -0,0 +1,257 @@
|
|
|
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">
|
|
3
|
+
</p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<img alt="PyPI" src="https://img.shields.io/pypi/v/deep-river">
|
|
6
|
+
<!-- Single Coverage Badge (Shields.io) -->
|
|
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" />
|
|
9
|
+
</a>
|
|
10
|
+
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/deep-river">
|
|
11
|
+
<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/deep-river">
|
|
12
|
+
<img alt="GitHub" src="https://img.shields.io/github/license/online-ml/deep-river">
|
|
13
|
+
<a href="https://joss.theoj.org/papers/6a76784f55e8b041d71a7fa776eb386a"><img src="https://joss.theoj.org/papers/6a76784f55e8b041d71a7fa776eb386a/status.svg"></a>
|
|
14
|
+
<a href="https://doi.org/10.5281/zenodo.14601980"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.14601980.svg" alt="DOI"></a>
|
|
15
|
+
</p>
|
|
16
|
+
<p align="center">
|
|
17
|
+
deep-river is a Python library for online deep learning.
|
|
18
|
+
deep-river's ambition is to enable <a href="https://www.wikiwand.com/en/Online_machine_learning">online machine learning</a> for neural networks.
|
|
19
|
+
It combines the <a href="https://www.riverml.xyz">river</a> API with the capabilities of designing neural networks based on <a href="https://pytorch.org">PyTorch</a>.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
## 📚 [Documentation](https://online-ml.github.io/deep-river/)
|
|
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.
|
|
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
|
+
|
|
27
|
+
## 💈 Installation
|
|
28
|
+
|
|
29
|
+
```shell
|
|
30
|
+
pip install deep-river
|
|
31
|
+
```
|
|
32
|
+
or
|
|
33
|
+
```shell
|
|
34
|
+
pip install "river[deep]"
|
|
35
|
+
```
|
|
36
|
+
You can install the latest development version from GitHub as so:
|
|
37
|
+
|
|
38
|
+
```shell
|
|
39
|
+
pip install https://github.com/online-ml/deep-river/archive/refs/heads/master.zip
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Development Environment
|
|
43
|
+
|
|
44
|
+
For contributing to deep-river, we recommend using [uv](https://docs.astral.sh/uv/) for fast dependency management and environment setup:
|
|
45
|
+
|
|
46
|
+
```shell
|
|
47
|
+
# Install uv if you haven't already
|
|
48
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
49
|
+
|
|
50
|
+
# Clone the repository
|
|
51
|
+
git clone https://github.com/online-ml/deep-river.git
|
|
52
|
+
cd deep-river
|
|
53
|
+
|
|
54
|
+
# Install all dependencies (including dev dependencies)
|
|
55
|
+
uv sync --extra dev
|
|
56
|
+
|
|
57
|
+
# Run tests
|
|
58
|
+
make test
|
|
59
|
+
|
|
60
|
+
# Format code
|
|
61
|
+
make format
|
|
62
|
+
|
|
63
|
+
# Build documentation
|
|
64
|
+
make doc
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 🍫 Quickstart
|
|
68
|
+
|
|
69
|
+
We build the development of neural networks on top of the <a href="https://www.riverml.xyz">river API</a> and refer to the rivers design principles.
|
|
70
|
+
The following example creates a simple MLP architecture based on PyTorch and incrementally predicts and trains on the website phishing dataset.
|
|
71
|
+
For further examples check out the <a href="https://online-ml.github.io/deep-river">Documentation</a>.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
### Classification
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
>>> import random, numpy as np
|
|
78
|
+
>>> from river import metrics, datasets, preprocessing, compose
|
|
79
|
+
>>> from deep_river.classification import Classifier
|
|
80
|
+
>>> from torch import nn, manual_seed
|
|
81
|
+
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
|
|
82
|
+
>>> first_x, _ = next(iter(datasets.Phishing()))
|
|
83
|
+
>>> n_features = len(first_x)
|
|
84
|
+
>>> class MyModule(nn.Module):
|
|
85
|
+
... def __init__(self, n_features):
|
|
86
|
+
... super().__init__()
|
|
87
|
+
... self.net = nn.Sequential(
|
|
88
|
+
... nn.Linear(n_features, 16),
|
|
89
|
+
... nn.ReLU(),
|
|
90
|
+
... nn.Linear(16, 2)
|
|
91
|
+
... )
|
|
92
|
+
... def forward(self, x):
|
|
93
|
+
... return self.net(x)
|
|
94
|
+
>>> model = compose.Pipeline(
|
|
95
|
+
... preprocessing.StandardScaler(),
|
|
96
|
+
... Classifier(
|
|
97
|
+
... module=MyModule(n_features),
|
|
98
|
+
... loss_fn='cross_entropy',
|
|
99
|
+
... optimizer_fn='adam',
|
|
100
|
+
... lr=1e-3,
|
|
101
|
+
... is_class_incremental=True
|
|
102
|
+
... )
|
|
103
|
+
... )
|
|
104
|
+
>>> metric = metrics.Accuracy()
|
|
105
|
+
>>> for i, (x, y) in enumerate(datasets.Phishing().take(200)):
|
|
106
|
+
... if i > 0:
|
|
107
|
+
... y_pred = model.predict_one(x)
|
|
108
|
+
... metric.update(y, y_pred)
|
|
109
|
+
... model.learn_one(x, y)
|
|
110
|
+
>>> assert 0.5 <= metric.get() <= 1.0
|
|
111
|
+
>>> print(f"Accuracy: {metric.get():.4f}") # doctest: +ELLIPSIS
|
|
112
|
+
Accuracy: ...
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Multi Target Regression
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
>>> import random, numpy as np
|
|
120
|
+
>>> from river import stream, metrics, preprocessing, compose
|
|
121
|
+
>>> from sklearn import datasets as sk_datasets
|
|
122
|
+
>>> from deep_river.regression import MultiTargetRegressor
|
|
123
|
+
>>> from torch import nn, manual_seed
|
|
124
|
+
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
|
|
125
|
+
>>> linnerud_stream = stream.iter_sklearn_dataset(sk_datasets.load_linnerud(), shuffle=True, seed=42)
|
|
126
|
+
>>> first_x, first_y = next(iter(linnerud_stream))
|
|
127
|
+
>>> n_features, n_outputs = len(first_x), len(first_y)
|
|
128
|
+
>>> class TinyNet(nn.Module):
|
|
129
|
+
... def __init__(self, n_features, n_outputs):
|
|
130
|
+
... super().__init__()
|
|
131
|
+
... self.net = nn.Sequential(
|
|
132
|
+
... nn.Linear(n_features, 16),
|
|
133
|
+
... nn.ReLU(),
|
|
134
|
+
... nn.Linear(16, n_outputs)
|
|
135
|
+
... )
|
|
136
|
+
... def forward(self, x):
|
|
137
|
+
... return self.net(x)
|
|
138
|
+
>>> model = compose.Pipeline(
|
|
139
|
+
... preprocessing.StandardScaler(), # feature scaling stabilizes training
|
|
140
|
+
... MultiTargetRegressor(
|
|
141
|
+
... module=TinyNet(n_features, n_outputs),
|
|
142
|
+
... loss_fn='mse', optimizer_fn='adam', lr=5e-3,
|
|
143
|
+
... is_feature_incremental=False, is_target_incremental=False,
|
|
144
|
+
... gradient_clip_value=1.0,
|
|
145
|
+
... )
|
|
146
|
+
... )
|
|
147
|
+
>>> mae_micro = metrics.multioutput.MicroAverage(metrics.MAE())
|
|
148
|
+
>>> mae_macro = metrics.multioutput.MacroAverage(metrics.MAE())
|
|
149
|
+
>>> rmse_micro = metrics.multioutput.MicroAverage(metrics.RMSE())
|
|
150
|
+
>>> # Recreate the iterator (the first sample was consumed to infer shapes)
|
|
151
|
+
>>> linnerud_stream = stream.iter_sklearn_dataset(sk_datasets.load_linnerud(), shuffle=True, seed=42)
|
|
152
|
+
>>> for i, (x, y_dict) in enumerate(linnerud_stream):
|
|
153
|
+
... if i > 0:
|
|
154
|
+
... y_pred = model.predict_one(x)
|
|
155
|
+
... mae_micro.update(y_dict, y_pred)
|
|
156
|
+
... mae_macro.update(y_dict, y_pred)
|
|
157
|
+
... rmse_micro.update(y_dict, y_pred)
|
|
158
|
+
... model.learn_one(x, y_dict)
|
|
159
|
+
>>> assert 0.0 <= mae_micro.get() < 300.0
|
|
160
|
+
>>> assert 0.0 <= mae_macro.get() < 300.0
|
|
161
|
+
>>> assert 0.0 <= rmse_micro.get() < 400.0
|
|
162
|
+
>>> print({
|
|
163
|
+
... 'MAE_micro': round(mae_micro.get(), 4),
|
|
164
|
+
... 'MAE_macro': round(mae_macro.get(), 4),
|
|
165
|
+
... 'RMSE_micro': round(rmse_micro.get(), 4)
|
|
166
|
+
... }) # doctest: +ELLIPSIS
|
|
167
|
+
{'MAE_micro': ..., 'MAE_macro': ..., 'RMSE_micro': ...}
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Anomaly Detection
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
>>> import random, numpy as np
|
|
175
|
+
>>> from torch import nn, manual_seed
|
|
176
|
+
>>> from river import metrics
|
|
177
|
+
>>> from deep_river.anomaly import Autoencoder
|
|
178
|
+
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
|
|
179
|
+
>>> # Create a deterministic synthetic stream: normals (y=0) vs. anomalies (y=1)
|
|
180
|
+
>>> def synthetic_stream(n_norm=2000, n_anom=200, n_features=8, seed=42):
|
|
181
|
+
... rng = np.random.default_rng(seed)
|
|
182
|
+
... # Normals around 0, anomalies shifted
|
|
183
|
+
... X_norm = rng.normal(loc=0.0, scale=1.0, size=(n_norm, n_features))
|
|
184
|
+
... y_norm = np.zeros(n_norm, dtype=int)
|
|
185
|
+
... X_anom = rng.normal(loc=5.0, scale=1.0, size=(n_anom, n_features))
|
|
186
|
+
... y_anom = np.ones(n_anom, dtype=int)
|
|
187
|
+
... # Interleave anomalies every k steps to keep streaming flavour
|
|
188
|
+
... k = max(1, n_norm // n_anom)
|
|
189
|
+
... X, Y = [], []
|
|
190
|
+
... i_norm = i_anom = 0
|
|
191
|
+
... for t in range(n_norm + n_anom):
|
|
192
|
+
... take_anom = (t % k == 0) and (i_anom < n_anom)
|
|
193
|
+
... if take_anom:
|
|
194
|
+
... X.append(X_anom[i_anom]); Y.append(y_anom[i_anom]); i_anom += 1
|
|
195
|
+
... else:
|
|
196
|
+
... if i_norm < n_norm:
|
|
197
|
+
... X.append(X_norm[i_norm]); Y.append(y_norm[i_norm]); i_norm += 1
|
|
198
|
+
... elif i_anom < n_anom:
|
|
199
|
+
... X.append(X_anom[i_anom]); Y.append(y_anom[i_anom]); i_anom += 1
|
|
200
|
+
... for row, label in zip(X, Y):
|
|
201
|
+
... yield {f"f{i}": float(v) for i, v in enumerate(row)}, int(label)
|
|
202
|
+
>>> n_features = 8
|
|
203
|
+
>>> class MyAutoEncoder(nn.Module):
|
|
204
|
+
... def __init__(self, n_features, latent_dim=4):
|
|
205
|
+
... super().__init__()
|
|
206
|
+
... self.encoder = nn.Sequential(
|
|
207
|
+
... nn.Linear(n_features, latent_dim),
|
|
208
|
+
... nn.LeakyReLU()
|
|
209
|
+
... )
|
|
210
|
+
... self.decoder = nn.Sequential(
|
|
211
|
+
... nn.Linear(latent_dim, n_features),
|
|
212
|
+
... nn.Sigmoid()
|
|
213
|
+
... )
|
|
214
|
+
... def forward(self, x):
|
|
215
|
+
... z = self.encoder(x)
|
|
216
|
+
... return self.decoder(z)
|
|
217
|
+
>>> ae = Autoencoder(module=MyAutoEncoder(n_features), lr=5e-3, optimizer_fn='adam')
|
|
218
|
+
>>> metric = metrics.ROCAUC()
|
|
219
|
+
>>> # Train only on normal samples to keep the model focused on the normal manifold
|
|
220
|
+
>>> for x, y in synthetic_stream(n_norm=2000, n_anom=200, n_features=n_features, seed=42):
|
|
221
|
+
... score = ae.score_one(x)
|
|
222
|
+
... if y == 0:
|
|
223
|
+
... ae.learn_one(x)
|
|
224
|
+
... metric.update(y, score)
|
|
225
|
+
>>> print(f"ROCAUC: {metric.get():.4f}") # doctest: +ELLIPSIS
|
|
226
|
+
ROCAUC: ...
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## 💬 Citation
|
|
231
|
+
|
|
232
|
+
To acknowledge the use of the `DeepRiver` library in your research, please refer to our [paper](https://joss.theoj.org/papers/10.21105/joss.07226) published on Journal of Open Source Software (JOSS):
|
|
233
|
+
|
|
234
|
+
```bibtex
|
|
235
|
+
@article{Kulbach2025,
|
|
236
|
+
doi = {10.21105/joss.07226},
|
|
237
|
+
url = {https://doi.org/10.21105/joss.07226},
|
|
238
|
+
year = {2025},
|
|
239
|
+
publisher = {The Open Journal},
|
|
240
|
+
volume = {10},
|
|
241
|
+
number = {105},
|
|
242
|
+
pages = {7226},
|
|
243
|
+
author = {Cedric Kulbach and Lucas Cazzonelli and Hoang-Anh Ngo and Max Halford and Saulo Martiello Mastelini},
|
|
244
|
+
title = {DeepRiver: A Deep Learning Library for Data Streams},
|
|
245
|
+
journal = {Journal of Open Source Software}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 🏫 Affiliations
|
|
250
|
+
|
|
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"/>
|
|
253
|
+
</p>
|
|
254
|
+
|
|
255
|
+
<p align="center">
|
|
256
|
+
<img src="https://lieferbotnet.de/wp-content/uploads/2022/09/LieferBotNet-Logo.png?raw=true" alt="Lieferbot net" height="200"/>
|
|
257
|
+
</p>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from deep_river.classification.classifier import Classifier
|
|
2
|
+
from deep_river.classification.rolling_classifier import (
|
|
3
|
+
RollingClassifier,
|
|
4
|
+
)
|
|
5
|
+
from deep_river.classification.zoo import (
|
|
6
|
+
LogisticRegression,
|
|
7
|
+
LSTMClassifier,
|
|
8
|
+
MultiLayerPerceptron,
|
|
9
|
+
RNNClassifier,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
This module contains the classifiers for the deep_river package.
|
|
14
|
+
"""
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Classifier",
|
|
17
|
+
"RollingClassifier",
|
|
18
|
+
"LogisticRegression",
|
|
19
|
+
"MultiLayerPerceptron",
|
|
20
|
+
"LSTMClassifier",
|
|
21
|
+
"RNNClassifier",
|
|
22
|
+
]
|