Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
*h5
# Python bytecode and caches
__pycache__/
*.py[cod]
*$py.class
.pytest_cache/
.ruff_cache/
.mypy_cache/
.coverage
htmlcov/

# Local environments and build outputs
.venv/
venv/
build/
dist/
*.egg-info/

# Notebook and editor metadata
.ipynb_checkpoints/
.vscode/

# Dataset and spreadsheet artifacts
*.h5
*.hdf5
*.xlsx
*.xls
*.xlxs

# Model checkpoints and generated training artifacts
*-temp-weights-*
*.pt
*.pth
model.json

# Legacy experiment output folders/files
Mean
mean_folder
onlineGRU
seq2point
seq2seq
rnn
dae
disaggregate/__pycache__
*hdf5
excess
.ipynb_checkpoints
.pycache
mean_folder
pre-trained-mean
prev_disaggregate
.xlsx
.xlxs
__pycache__
__pycache__/*
disaggregate/__pycache__/*
disaggregate/__pycache__/
buildsys_notebooks
.vscode
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ RUN pip install --no-cache-dir uv
# Copy project files (assumes everything is in one dir)
COPY . .

# Sync dependencies using uv (installs from pyproject.toml)
RUN uv pip install --system .
# Install the package with all optional backends. Use a narrower extra such as
# .[torch], .[tensorflow], or .[classical] for backend-specific production images.
RUN uv pip install --system ".[all]"


# Optional: install dev dependencies too
# RUN uv pip install .[dev]
# RUN uv pip install --system ".[dev]"

# Set env vars
ENV PYTHONUNBUFFERED=1
Expand Down
45 changes: 38 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NILMTK-Contrib

(Note - This package only works on Python versions <= 3.11)
(Note - This package currently supports Python >=3.11,<3.12. Python 3.12+ is unsupported until TensorFlow and NILMTK compatibility is verified.)

This repository contains all the state-of-the-art algorithms for the task of energy disaggregation implemented using NILMTK's Rapid Experimentation API. You can find the paper [here](https://doi.org/10.1145/3360322.3360844). All the notebooks that were used to can be found [here](https://github.com/nilmtk/buildsys2019-paper-notebooks).

Expand Down Expand Up @@ -55,13 +55,40 @@ For any enquiries, please contact the main authors.
## Installation Details

## UV Support
This Python package uses uv for installation. uv is a fast and modern Python package manager that replaces tools like pip and virtualenv, with support for pyproject.toml and ultra-fast dependency resolution.
This Python package uses uv for installation. uv is a fast and modern Python package manager that replaces tools like pip and virtualenv, with support for pyproject.toml and ultra-fast dependency resolution.

Install the minimal package when you only need package metadata or lightweight imports:

To install nilmtk_contrib, first install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:<br>
```
uv pip install git+https://github.com/nilmtk/nilmtk-contrib.git
```

Install a backend-specific extra for model use:

```
uv pip install "nilmtk-contrib[tensorflow] @ git+https://github.com/nilmtk/nilmtk-contrib.git"
uv pip install "nilmtk-contrib[torch] @ git+https://github.com/nilmtk/nilmtk-contrib.git"
uv pip install "nilmtk-contrib[classical] @ git+https://github.com/nilmtk/nilmtk-contrib.git"
```

Install all model backends:

```
uv pip install "nilmtk-contrib[all] @ git+https://github.com/nilmtk/nilmtk-contrib.git"
```

For development:

```
uv sync --extra dev
```

For backend development, include the relevant backend extra, for example:

```
uv sync --extra dev --extra torch
```

## Docker Support
Docker is an open-source platform for developing, shipping, and running applications in lightweight, portable containers that bundle code, runtime, libraries, and system tools into a single package. It ensures everyone runs the same environment, regardless of host OS, and keeps nilmtk-contrib’s dependencies contained without polluting the system Python.

Expand All @@ -71,6 +98,8 @@ Build and run locally
docker build -t nilmtk-contrib .
docker run --rm -it nilmtk-contrib bash
```
The default Dockerfile installs `.[all]`. Edit the Dockerfile to use `.[torch]`, `.[tensorflow]`, or `.[classical]` for a narrower backend image.

Pull the pre-built image
```
docker pull ghcr.io/enfuego27826/nilmtk-contrib:latest
Expand All @@ -81,10 +110,12 @@ Refer to this [notebook](https://github.com/nilmtk/nilmtk-contrib/tree/master/sa

## Dependencies

- NILMTK>=0.4
- scikit-learn>=0.21 (already required by NILMTK)
- Tensorflow >= 2.12.0 < 2.16.0
- cvxpy>=1.0.0
- Minimal install: no required runtime dependencies for top-level import.
- `tensorflow` extra: NILMTK, NumPy, pandas, scikit-learn, matplotlib, TensorFlow, and `tensorflow-io-gcs-filesystem`.
- `torch` extra: NILMTK, NumPy, pandas, scikit-learn, matplotlib, PyTorch, and tqdm.
- `classical` extra: NILMTK, NumPy, pandas, matplotlib, scikit-learn, SciPy, cvxpy, and hmmlearn.
- `all` extra: union of TensorFlow, PyTorch, classical, and NILMTK dependencies.
- `dev` extra: pytest, pytest-cov, black, ruff, and build.

**Note: For faster computation of neural networks, it is suggested that you install keras-gpu, since it can take advantage of GPUs. The algorithms AFHMM, AFHMM_SAC and DSC are CPU intensive, use a system with good CPU for these algorithms.**

7 changes: 1 addition & 6 deletions nilmtk_contrib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
from . import disaggregate
from .version import version as __version__
import pandas as pd

if not hasattr(pd.DataFrame, "append"):
def _df_append(self, other, ignore_index=False, verify_integrity=False, sort=False):
return pd.concat([self, other], ignore_index=ignore_index, verify_integrity=verify_integrity, sort=sort)
pd.DataFrame.append = _df_append
__all__ = ["__version__"]
32 changes: 20 additions & 12 deletions nilmtk_contrib/disaggregate/WindowGRU.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@
from tensorflow.keras.models import Sequential


from nilmtk_contrib.utils.model import initialize_runtime, legacy_print, module_logger, checkpoint_path
from nilmtk_contrib.utils.validation import train_validation_split

logger = module_logger(__name__)
_log_print = legacy_print(logger)
class WindowGRU(Disaggregator):

def __init__(self, params):
initialize_runtime(self, params, backends=("python", "numpy", "tensorflow"))

self.MODEL_NAME = "WindowGRU"
self.file_prefix = "{}-temp-weights".format(self.MODEL_NAME.lower())
Expand Down Expand Up @@ -37,28 +43,30 @@ def partial_fit(self, train_main, train_appliances, do_preprocessing=True, curre
train_appliances = new_train_appliances
for app_name, app_df in train_appliances:
if app_name not in self.models:
print("First model training for", app_name)
_log_print("First model training for", app_name)
self.models[app_name] = self.return_network()
else:
print("Started re-training model for", app_name)
_log_print("Started re-training model for", app_name)

model = self.models[app_name]
mains = train_main.reshape((-1,self.sequence_length,1))
app_reading = app_df.reshape((-1,1))
filepath = self.file_prefix + "-{}-epoch{}.h5".format(
"_".join(app_name.split()),
current_epoch,
)
checkpoint = ModelCheckpoint(filepath,monitor='val_loss',verbose=1,save_best_only=True,mode='min')
filepath = checkpoint_path(".h5")
checkpoint = ModelCheckpoint(filepath,monitor='val_loss',verbose=1 if self.verbose else 0,save_best_only=True,mode='min')
split = train_validation_split(mains, app_reading, validation_fraction=0.15, strategy='tail', allow_no_validation=True)
if not split.metadata.should_train:
continue
model.fit(
mains, app_reading,
validation_split=.15,
split.X_train, split.y_train,
validation_data=(split.X_val, split.y_val) if split.metadata.validation_enabled else None,
epochs=self.n_epochs,
batch_size=self.batch_size,
callbacks=[ checkpoint ],
callbacks=[checkpoint] if split.metadata.validation_enabled else [],
shuffle=True,
verbose=1 if self.verbose else 0,
)
model.load_weights(filepath)
if split.metadata.validation_enabled and filepath.exists():
model.load_weights(filepath)

def disaggregate_chunk(self,test_main_list,model=None,do_preprocessing=True):

Expand Down Expand Up @@ -88,7 +96,7 @@ def disaggregate_chunk(self,test_main_list,model=None,do_preprocessing=True):
def call_preprocessing(self, mains_lst, submeters_lst, method):
max_val = self.max_val
if method == 'train':
print("Training processing")
_log_print("Training processing")
processed_mains = []

for mains in mains_lst:
Expand Down
95 changes: 81 additions & 14 deletions nilmtk_contrib/disaggregate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,81 @@
from nilmtk.disaggregate import Disaggregator
from .dae import DAE
from .dsc import DSC
from .afhmm import AFHMM
from .afhmm_sac import AFHMM_SAC
from .seq2point import Seq2Point
from .seq2seq import Seq2Seq
from .WindowGRU import WindowGRU
from .rnn import RNN
from .rnn_attention import RNN_attention
from .rnn_attention_classification import RNN_attention_classification
from .resnet import ResNet
from .resnet_classification import ResNet_classification
from .bert import BERT
"""Lazy exports for TensorFlow and classical NILMTK disaggregators.

These classes require optional backend dependencies. Importing this package does
not import TensorFlow, cvxpy, hmmlearn, or NILMTK until a class is requested.
"""

from importlib import import_module

from nilmtk_contrib.utils.optional_imports import OptionalDependencyError

_EXPORTS = {
"AFHMM": ("nilmtk_contrib.disaggregate.afhmm", "classical", "AFHMM"),
"AFHMM_SAC": ("nilmtk_contrib.disaggregate.afhmm_sac", "classical", "AFHMM_SAC"),
"BERT": ("nilmtk_contrib.disaggregate.bert", "tensorflow", "BERT"),
"DAE": ("nilmtk_contrib.disaggregate.dae", "tensorflow", "DAE"),
"DSC": ("nilmtk_contrib.disaggregate.dsc", "classical", "DSC"),
"RNN": ("nilmtk_contrib.disaggregate.rnn", "tensorflow", "RNN"),
"RNN_attention": (
"nilmtk_contrib.disaggregate.rnn_attention",
"tensorflow",
"RNN_attention",
),
"RNN_attention_classification": (
"nilmtk_contrib.disaggregate.rnn_attention_classification",
"tensorflow",
"RNN_attention_classification",
),
"ResNet": ("nilmtk_contrib.disaggregate.resnet", "tensorflow", "ResNet"),
"ResNet_classification": (
"nilmtk_contrib.disaggregate.resnet_classification",
"tensorflow",
"ResNet_classification",
),
"Seq2Point": ("nilmtk_contrib.disaggregate.seq2point", "tensorflow", "Seq2Point"),
"Seq2Seq": ("nilmtk_contrib.disaggregate.seq2seq", "tensorflow", "Seq2Seq"),
"WindowGRU": ("nilmtk_contrib.disaggregate.WindowGRU", "tensorflow", "WindowGRU"),
}

_DEPENDENCY_EXTRAS = {
"cvxpy": "classical",
"hmmlearn": "classical",
"nilmtk": "nilm",
"sklearn": "classical",
"tensorflow": "tensorflow",
}

__all__ = sorted([*_EXPORTS, "Disaggregator"])


def __getattr__(name):
if name == "Disaggregator":
try:
module = import_module("nilmtk.disaggregate")
except ModuleNotFoundError as exc:
message = (
"Disaggregator requires 'nilmtk'. "
"Install nilmtk-contrib[nilm]."
)
raise OptionalDependencyError(message) from exc
value = module.Disaggregator
globals()[name] = value
return value

if name not in _EXPORTS:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

module_name, extra_name, purpose = _EXPORTS[name]
try:
module = import_module(module_name)
except ModuleNotFoundError as exc:
missing_package = exc.name or "required dependency"
install_extra = _DEPENDENCY_EXTRAS.get(missing_package, extra_name)
message = (
f"{purpose} requires '{missing_package}'. "
f"Install nilmtk-contrib[{install_extra}]."
)
raise OptionalDependencyError(message) from exc

value = getattr(module, name)
globals()[name] = value
return value
9 changes: 7 additions & 2 deletions nilmtk_contrib/disaggregate/afhmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
from hmmlearn import hmm
from multiprocessing import Process, Manager

from nilmtk_contrib.utils.model import initialize_runtime, legacy_print, module_logger, checkpoint_path

logger = module_logger(__name__)
_log_print = legacy_print(logger)
class AFHMM(Disaggregator):

def __init__(self, params):
initialize_runtime(self, params, backends=("python", "numpy"))
self.model = []
self.MODEL_NAME = 'AFHMM'
self.models = []
Expand Down Expand Up @@ -48,7 +53,7 @@ def partial_fit(self, train_main, train_appliances, **load_kwargs):
train_main = train_main.values.flatten().reshape((-1,1))

for appliance_name, power in train_appliances:
#print (appliance_name)
#_log_print(appliance_name)
# Learning the pi's and transistion probabliites for each appliance using a simple HMM
self.appliances.append(appliance_name)
X = power.values.reshape((-1,1))
Expand Down Expand Up @@ -83,7 +88,7 @@ def partial_fit(self, train_main, train_appliances, **load_kwargs):
self.pi_s_vector = pi_s_vector
self.means_vector = means_vector
self.transmat_vector = transmat_vector
print ("Finished Training")
_log_print("Finished Training")

def disaggregate_thread(self, test_mains,index,d):

Expand Down
Loading