Skip to main content

Online Deep Learning for river

Project description

incremental dl logo

PyPI Coverage PyPI - Python Version PyPI - Downloads GitHub DOI

deep-river is a Python library for online deep learning. deep-river's ambition is to enable online machine learning for neural networks. It combines the river API with the capabilities of designing neural networks based on PyTorch.

📚 Documentation

The documentation contains an overview of all features of this repository as well as the repository's full features list. In each of these, the git repo reference is listed in a section that shows examples of the features and functionality. 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

💈 Installation

pip install deep-river

or

pip install "river[deep]"

You can install the latest development version from GitHub as so:

pip install https://github.com/online-ml/deep-river/archive/refs/heads/master.zip

Development Environment

For contributing to deep-river, we recommend using uv for fast dependency management and environment setup:

# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh

# Clone the repository
git clone https://github.com/online-ml/deep-river.git
cd deep-river

# Install all dependencies (including dev dependencies)
uv sync --extra dev

# Run tests
make test

# Format code
make format

# Build documentation
make doc

🍫 Quickstart

We build the development of neural networks on top of the river API and refer to the rivers design principles. The following example creates a simple MLP architecture based on PyTorch and incrementally predicts and trains on the website phishing dataset. For further examples check out the Documentation.

Classification

>>> import random, numpy as np
>>> from river import metrics, datasets, preprocessing, compose
>>> from deep_river.classification import Classifier
>>> from torch import nn, manual_seed
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
>>> first_x, _ = next(iter(datasets.Phishing()))
>>> n_features = len(first_x)
>>> class MyModule(nn.Module):
...     def __init__(self, n_features):
...         super().__init__()
...         self.net = nn.Sequential(
...             nn.Linear(n_features, 16),
...             nn.ReLU(),
...             nn.Linear(16, 2)
...         )
...     def forward(self, x):
...         return self.net(x)
>>> model = compose.Pipeline(
...     preprocessing.StandardScaler(),
...     Classifier(
...         module=MyModule(n_features),
...         loss_fn='cross_entropy',
...         optimizer_fn='adam',
...         lr=1e-3,
...         is_class_incremental=True
...     )
... )
>>> metric = metrics.Accuracy()
>>> for i, (x, y) in enumerate(datasets.Phishing().take(200)):
...     if i > 0:
...         y_pred = model.predict_one(x)
...         metric.update(y, y_pred)
...     model.learn_one(x, y)
>>> assert 0.5 <= metric.get() <= 1.0
>>> print(f"Accuracy: {metric.get():.4f}")  # doctest: +ELLIPSIS
Accuracy: ...

Multi Target Regression

>>> import random, numpy as np
>>> from river import stream, metrics, preprocessing, compose
>>> from sklearn import datasets as sk_datasets
>>> from deep_river.regression import MultiTargetRegressor
>>> from torch import nn, manual_seed
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
>>> linnerud_stream = stream.iter_sklearn_dataset(sk_datasets.load_linnerud(), shuffle=True, seed=42)
>>> first_x, first_y = next(iter(linnerud_stream))
>>> n_features, n_outputs = len(first_x), len(first_y)
>>> class TinyNet(nn.Module):
...     def __init__(self, n_features, n_outputs):
...         super().__init__()
...         self.net = nn.Sequential(
...             nn.Linear(n_features, 16),
...             nn.ReLU(),
...             nn.Linear(16, n_outputs)
...         )
...     def forward(self, x):
...         return self.net(x)
>>> model = compose.Pipeline(
...     preprocessing.StandardScaler(),  # feature scaling stabilizes training
...     MultiTargetRegressor(
...         module=TinyNet(n_features, n_outputs),
...         loss_fn='mse', optimizer_fn='adam', lr=5e-3,
...         is_feature_incremental=False, is_target_incremental=False,
...         gradient_clip_value=1.0,
...     )
... )
>>> mae_micro = metrics.multioutput.MicroAverage(metrics.MAE())
>>> mae_macro = metrics.multioutput.MacroAverage(metrics.MAE())
>>> rmse_micro = metrics.multioutput.MicroAverage(metrics.RMSE())
>>> # Recreate the iterator (the first sample was consumed to infer shapes)
>>> linnerud_stream = stream.iter_sklearn_dataset(sk_datasets.load_linnerud(), shuffle=True, seed=42)
>>> for i, (x, y_dict) in enumerate(linnerud_stream):
...     if i > 0:
...         y_pred = model.predict_one(x)
...         mae_micro.update(y_dict, y_pred)
...         mae_macro.update(y_dict, y_pred)
...         rmse_micro.update(y_dict, y_pred)
...     model.learn_one(x, y_dict)
>>> assert 0.0 <= mae_micro.get() < 300.0
>>> assert 0.0 <= mae_macro.get() < 300.0
>>> assert 0.0 <= rmse_micro.get() < 400.0
>>> print({
...     'MAE_micro': round(mae_micro.get(), 4),
...     'MAE_macro': round(mae_macro.get(), 4),
...     'RMSE_micro': round(rmse_micro.get(), 4)
... })  # doctest: +ELLIPSIS
{'MAE_micro': ..., 'MAE_macro': ..., 'RMSE_micro': ...}

Anomaly Detection

>>> import random, numpy as np
>>> from torch import nn, manual_seed
>>> from river import metrics
>>> from deep_river.anomaly import Autoencoder
>>> _ = manual_seed(42); random.seed(42); np.random.seed(42)
>>> # Create a deterministic synthetic stream: normals (y=0) vs. anomalies (y=1)
>>> def synthetic_stream(n_norm=2000, n_anom=200, n_features=8, seed=42):
...     rng = np.random.default_rng(seed)
...     # Normals around 0, anomalies shifted
...     X_norm = rng.normal(loc=0.0, scale=1.0, size=(n_norm, n_features))
...     y_norm = np.zeros(n_norm, dtype=int)
...     X_anom = rng.normal(loc=5.0, scale=1.0, size=(n_anom, n_features))
...     y_anom = np.ones(n_anom, dtype=int)
...     # Interleave anomalies every k steps to keep streaming flavour
...     k = max(1, n_norm // n_anom)
...     X, Y = [], []
...     i_norm = i_anom = 0
...     for t in range(n_norm + n_anom):
...         take_anom = (t % k == 0) and (i_anom < n_anom)
...         if take_anom:
...             X.append(X_anom[i_anom]); Y.append(y_anom[i_anom]); i_anom += 1
...         else:
...             if i_norm < n_norm:
...                 X.append(X_norm[i_norm]); Y.append(y_norm[i_norm]); i_norm += 1
...             elif i_anom < n_anom:
...                 X.append(X_anom[i_anom]); Y.append(y_anom[i_anom]); i_anom += 1
...     for row, label in zip(X, Y):
...         yield {f"f{i}": float(v) for i, v in enumerate(row)}, int(label)
>>> n_features = 8
>>> class MyAutoEncoder(nn.Module):
...     def __init__(self, n_features, latent_dim=4):
...         super().__init__()
...         self.encoder = nn.Sequential(
...             nn.Linear(n_features, latent_dim),
...             nn.LeakyReLU()
...         )
...         self.decoder = nn.Sequential(
...             nn.Linear(latent_dim, n_features),
...             nn.Sigmoid()
...         )
...     def forward(self, x):
...         z = self.encoder(x)
...         return self.decoder(z)
>>> ae = Autoencoder(module=MyAutoEncoder(n_features), lr=5e-3, optimizer_fn='adam')
>>> metric = metrics.ROCAUC()
>>> # Train only on normal samples to keep the model focused on the normal manifold
>>> for x, y in synthetic_stream(n_norm=2000, n_anom=200, n_features=n_features, seed=42):
...     score = ae.score_one(x)
...     if y == 0:
...         ae.learn_one(x)
...     metric.update(y, score)
>>> print(f"ROCAUC: {metric.get():.4f}")  # doctest: +ELLIPSIS
ROCAUC: ...

💬 Citation

To acknowledge the use of the DeepRiver library in your research, please refer to our paper published on Journal of Open Source Software (JOSS):

@article{Kulbach2025, 
    doi = {10.21105/joss.07226}, 
    url = {https://doi.org/10.21105/joss.07226}, 
    year = {2025}, 
    publisher = {The Open Journal}, 
    volume = {10}, 
    number = {105}, 
    pages = {7226}, 
    author = {Cedric Kulbach and Lucas Cazzonelli and Hoang-Anh Ngo and Max Halford and Saulo Martiello Mastelini}, 
    title = {DeepRiver: A Deep Learning Library for Data Streams}, 
    journal = {Journal of Open Source Software} 
}

🏫 Affiliations

FZI Logo

Lieferbot net

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

deep_river-0.3.2.tar.gz (46.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deep_river-0.3.2-py3-none-any.whl (56.1 kB view details)

Uploaded Python 3

File details

Details for the file deep_river-0.3.2.tar.gz.

File metadata

  • Download URL: deep_river-0.3.2.tar.gz
  • Upload date:
  • Size: 46.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.24

File hashes

Hashes for deep_river-0.3.2.tar.gz
Algorithm Hash digest
SHA256 781b707941df056cb9396b8e25a2f6115b76695351ace286842ba735630d4d8e
MD5 13925707e6ddc5e1471a4a43938a8fe7
BLAKE2b-256 e278fdc409badf00f201050855446ef487049f382e7e9296a06d9e38f555bd45

See more details on using hashes here.

File details

Details for the file deep_river-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: deep_river-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 56.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.24

File hashes

Hashes for deep_river-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a8850739e9b2f20044a4cc4b67e765f0191619c813fa9017914f1bc49d3199b9
MD5 ae7b8b131e30bf3bda1de45859a225a0
BLAKE2b-256 cf90fb1f89190fa5368365160d5b04bad356a314b55c1a770d1ddffc72de8503

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page