Showing preview only (338K chars total). Download the full file or copy to clipboard to get everything.
Repository: OdysseasKr/neural-disaggregator
Branch: master
Commit: 08722cdfd362
Files: 33
Total size: 324.7 KB
Directory structure:
gitextract_a130ckvp/
├── DAE/
│ ├── DAE-example.ipynb
│ ├── README.md
│ ├── daedisaggregator.py
│ ├── metrics.py
│ ├── redd-test.py
│ └── ukdale-test.py
├── GRU/
│ ├── GRU-example.ipynb
│ ├── README.md
│ ├── grudisaggregator.py
│ ├── metrics.py
│ ├── redd-test.py
│ └── ukdale-test.py
├── LICENSE
├── README.md
├── RNN/
│ ├── README.md
│ ├── RNN-example.ipynb
│ ├── metrics.py
│ ├── redd-test.py
│ ├── rnndisaggregator.py
│ └── ukdale-test.py
├── ShortSeq2Point/
│ ├── README.md
│ ├── ShortSeq2Point-example.ipynb
│ ├── metrics.py
│ ├── redd-test.py
│ ├── shortseq2pointdisaggregator.py
│ └── ukdale-test.py
├── WindowGRU/
│ ├── README.md
│ ├── Window-GRU-example.ipynb
│ ├── metrics.py
│ ├── redd-test.py
│ ├── ukdale-test.py
│ └── windowgrudisaggregator.py
└── requirements.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: DAE/DAE-example.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to use the Denoising Autoencoder with NILMTK\n",
"This is an example on how to train and use the Denoising Autoencoder (DAE) disaggregator on the [REDD](http://redd.csail.mit.edu/) dataset using [NILMTK](https://github.com/nilmtk/NILMTK/).\n",
"\n",
"This network was described in the [Neural NILM](https://arxiv.org/pdf/1507.06594.pdf) paper."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First of all, we need to train the DAEDisaggregator using the train data. For this example, both train and test data are consumption data of the fridge of the first REDD building."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import warnings; warnings.filterwarnings('ignore')\n",
"\n",
"from nilmtk import DataSet\n",
"train = DataSet('redd.h5')\n",
"train.set_window(end=\"30-4-2011\") #Use data only until 4/30/2011\n",
"train_elec = train.buildings[1].elec"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we need to define the disaggregator model. For this example, the input window will have size of 200 samples."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Using TensorFlow backend.\n"
]
}
],
"source": [
"from daedisaggregator import DAEDisaggregator\n",
"dae = DAEDisaggregator(256)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then train the model. We need to input the train data as well as their sample period. Also, we need to pass the desired number of training epochs. Finally, save the model for later use."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Epoch 1/25\n",
"3919/3919 [==============================] - 17s - loss: 3.2031e-04 \n",
"Epoch 2/25\n",
"3919/3919 [==============================] - 3s - loss: 2.4596e-04 \n",
"Epoch 3/25\n",
"3919/3919 [==============================] - 3s - loss: 2.2974e-04 \n",
"Epoch 4/25\n",
"3919/3919 [==============================] - 3s - loss: 2.3065e-04 \n",
"Epoch 5/25\n",
"3919/3919 [==============================] - 3s - loss: 2.2060e-04 \n",
"Epoch 6/25\n",
"3919/3919 [==============================] - 2s - loss: 2.0973e-04 \n",
"Epoch 7/25\n",
"3919/3919 [==============================] - 3s - loss: 2.2210e-04 \n",
"Epoch 8/25\n",
"3919/3919 [==============================] - 3s - loss: 2.1770e-04 \n",
"Epoch 9/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0082e-04 \n",
"Epoch 10/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0247e-04 \n",
"Epoch 11/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0241e-04 \n",
"Epoch 12/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0058e-04 \n",
"Epoch 13/25\n",
"3919/3919 [==============================] - 3s - loss: 1.9457e-04 \n",
"Epoch 14/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0899e-04 \n",
"Epoch 15/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0804e-04 \n",
"Epoch 16/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0194e-04 \n",
"Epoch 17/25\n",
"3919/3919 [==============================] - 3s - loss: 1.8804e-04 \n",
"Epoch 18/25\n",
"3919/3919 [==============================] - 3s - loss: 1.9298e-04 \n",
"Epoch 19/25\n",
"3919/3919 [==============================] - 3s - loss: 2.1932e-04 \n",
"Epoch 20/25\n",
"3919/3919 [==============================] - 3s - loss: 1.9278e-04 \n",
"Epoch 21/25\n",
"3919/3919 [==============================] - 3s - loss: 1.9146e-04 \n",
"Epoch 22/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0114e-04 \n",
"Epoch 23/25\n",
"3919/3919 [==============================] - 2s - loss: 1.9523e-04 \n",
"Epoch 24/25\n",
"3919/3919 [==============================] - 3s - loss: 2.0197e-04 \n",
"Epoch 25/25\n",
"3919/3919 [==============================] - 3s - loss: 1.9083e-04 \n"
]
}
],
"source": [
"train_mains = train_elec.mains().all_meters()[0] # The aggregated meter that provides the input\n",
"train_meter = train_elec.submeters()['fridge'] # The microwave meter that is used as a training target\n",
"\n",
"dae.train(train_mains, train_meter, epochs=25, sample_period=1)\n",
"dae.export_model(\"model-redd100.h5\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that the model is trained, we can use it to disaggregate energy data. Let's test it on the rest of the data from building 1.\n",
"\n",
"First we use the model to predict the fridge consumption. The results are saved automatically in a .h5 datastore."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"New sensible chunk: 121482\n",
"New sensible chunk: 112661\n",
"New sensible chunk: 87770\n",
"New sensible chunk: 54084\n",
"New sensible chunk: 2660\n",
"New sensible chunk: 33513\n",
"New sensible chunk: 138535\n",
"New sensible chunk: 32514\n",
"New sensible chunk: 27255\n",
"New sensible chunk: 34833\n",
"New sensible chunk: 100831\n"
]
}
],
"source": [
"test = DataSet('redd.h5')\n",
"test.set_window(start=\"30-4-2011\")\n",
"test_elec = test.buildings[1].elec\n",
"test_mains = test_elec.mains().all_meters()[0]\n",
"\n",
"disag_filename = 'disag-out.h5' # The filename of the resulting datastore\n",
"from nilmtk.datastore import HDFDataStore\n",
"output = HDFDataStore(disag_filename, 'w')\n",
"\n",
"# test_mains: The aggregated signal meter\n",
"# output: The output datastore\n",
"# train_meter: This is used in order to copy the metadata of the train meter into the datastore\n",
"dae.disaggregate(test_mains, output, train_meter, sample_period=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's plot the results and compare them to the ground truth signal.\n",
"\n",
"**Note:** Calling plot this way, downsamples the signal to reduce computing time. To plot the entire signal call\n",
"```\n",
"predicted.power_series_all_data().plot()\n",
"ground_truth.power_series_all_data().plot()\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\nMGAAnTt35qKLLqJnz548+uijnHPOOWzevJnzzz+/AncgGjU0lEyivc3skNrMoGrMNjzGGAYOHOja\nJiK0tbUVvr/xxhu8+uqr7LvvvoVtzv02o0aNorm5mblz59KnTx8ATj75ZA455BCamtrFh3nz5jF1\n6lQuueQSrr76agB++tOfMnToUBYtWuTK87LLLqO5uZkFCxawyy67APCTn/yE008/nSuuuIJzzz23\nJnEiamgomUKHt2aHgoRRa49GBx8hkAZbdmxhydolVT/PAb0OoPtO3SuWn4gwceJEvvCFLwSmGThw\noMvI8KO1tZXHH3+cM844o2BkABx00EEcd9xxzJo1q7Bt+vTpiAjDhw935TFixAjuvPPOwndjDFOn\nTmXo0KG0tra6vC7HH3889913HwsWLKB///6xr7dU1NBQMol6NLKDrnVS/yxZu4R+t/Sr+nnmnzef\nw3aPF7gZl/79+4cGg3pHpPixevVqWlpafA2S/fff32VovP3223Tq1Im99trLlc577KpVq9i0aRMT\nJ05kwoQJRfmKCB988EFk2SqBGhpKptAYjeygMRodhwN6HcD88+bX5Dy1plu3bjU/J0AuZ42sGzZs\nGGeddZZvmi9/+cs1KYsaGkqmKEgn2tvMDBqjUf9036l7xT0NHYnevXvTpUsX3njjjaJ9S5a4JaW9\n9tqL1tZW3n77bZdXw3tsnz592Hnnncnlcnzzm9+sTsFjosNblUyi82hkB43RUOqdTp068a1vfYv7\n77+f999/v7D95ZdfZubMma60gwYNwhjDxIkTXdtvuukmV91rbm7mlFNOYcqUKSxevLjonGvXrq3w\nVQSjHg0lU6h0kh10Pgul2lSybo0ePZojjjiCI488kuHDh9PS0sL48eM5+OCDeeWVVwrpBgwYwEkn\nncS1117LmjVr6N+/P0888QTLli0D3IbuNddcw5NPPsmAAQM499xzOfDAA/nwww95/vnnmTNnDqtW\nrapY+cNQj4aSSbTxyQ661olSLaK8V2HrlHj3HXrooUyfPp2ePXty5ZVXcvvttzN27Fi++93vFh17\n9913M3z4cB566CEuu+wy2trauOuuuzDG0LVr10K6Pn36MG/ePIYNG8b999/PiBEjGDduHBs3buT3\nv/99iVedHPVoKJlCh7dmh9RWb9Vg0EwwbNgwhg0bFri/ubnZd76MsH3HHHMM8+bNK9o+ZswY1/du\n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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x7f8e90a6e690>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"result = DataSet(disag_filename)\n",
"res_elec = result.buildings[1].elec\n",
"predicted = res_elec['fridge']\n",
"ground_truth = test_elec['fridge']\n",
"\n",
"import matplotlib.pyplot as plt\n",
"predicted.plot()\n",
"ground_truth.plot()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally let's see the metric results."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"============ Recall: 0.991529182705\n",
"============ Precision: 0.80751980409\n",
"============ Accuracy: 0.802475953548\n",
"============ F1 Score: 0.890114118342\n",
"============ Relative error in total energy: 0.755207186325\n",
"============ Mean absolute error(in Watts): 51.2799840994\n"
]
}
],
"source": [
"import metrics\n",
"rpaf = metrics.recall_precision_accuracy_f1(predicted, ground_truth)\n",
"print(\"============ Recall: {}\".format(rpaf[0]))\n",
"print(\"============ Precision: {}\".format(rpaf[1]))\n",
"print(\"============ Accuracy: {}\".format(rpaf[2]))\n",
"print(\"============ F1 Score: {}\".format(rpaf[3]))\n",
"\n",
"print(\"============ Relative error in total energy: {}\".format(metrics.relative_error_total_energy(predicted, ground_truth)))\n",
"print(\"============ Mean absolute error(in Watts): {}\".format(metrics.mean_absolute_error(predicted, ground_truth)))"
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python [conda env:nilmtk-env]",
"language": "python",
"name": "conda-env-nilmtk-env-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.12"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
================================================
FILE: DAE/README.md
================================================
# Denoising Autoencoder Energy Disaggregator
As described in:
[Neural NILM: Deep Neural Networks Applied to Energy Disaggregation](https://arxiv.org/pdf/1507.06594.pdf) by Jack Kelly and William Knottenbelt
See example experiment [here](https://github.com/OdysseasKr/neural-disaggregator/blob/master/DAE/DAE-example.ipynb).
================================================
FILE: DAE/daedisaggregator.py
================================================
from __future__ import print_function, division
import random
import sys
from matplotlib import rcParams
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import h5py
from keras.models import load_model
from keras.models import Sequential
from keras.layers import Dense, Flatten, Conv1D, Reshape, Dropout
from keras.utils import plot_model
from nilmtk.utils import find_nearest
from nilmtk.feature_detectors import cluster
from nilmtk.legacy.disaggregate import Disaggregator
from nilmtk.datastore import HDFDataStore
class DAEDisaggregator(Disaggregator):
'''Denoising Autoencoder disaggregator from Neural NILM
https://arxiv.org/pdf/1507.06594.pdf
Attributes
----------
model : keras Sequential model
sequence_length : the size of window to use on the aggregate data
mmax : the maximum value of the aggregate data
MIN_CHUNK_LENGTH : int
the minimum length of an acceptable chunk
'''
def __init__(self, sequence_length):
'''Initialize disaggregator
Parameters
----------
sequence_length : the size of window to use on the aggregate data
meter : a nilmtk.ElecMeter meter of the appliance to be disaggregated
'''
self.MODEL_NAME = "AUTOENCODER"
self.mmax = None
self.sequence_length = sequence_length
self.MIN_CHUNK_LENGTH = sequence_length
self.model = self._create_model(self.sequence_length)
def train(self, mains, meter, epochs=1, batch_size=16, **load_kwargs):
'''Train
Parameters
----------
mains : a nilmtk.ElecMeter object for the aggregate data
meter : a nilmtk.ElecMeter object for the meter data
epochs : number of epochs to train
**load_kwargs : keyword arguments passed to `meter.power_series()`
'''
main_power_series = mains.power_series(**load_kwargs)
meter_power_series = meter.power_series(**load_kwargs)
# Train chunks
run = True
mainchunk = next(main_power_series)
meterchunk = next(meter_power_series)
if self.mmax == None:
self.mmax = mainchunk.max()
while(run):
mainchunk = self._normalize(mainchunk, self.mmax)
meterchunk = self._normalize(meterchunk, self.mmax)
self.train_on_chunk(mainchunk, meterchunk, epochs, batch_size)
try:
mainchunk = next(main_power_series)
meterchunk = next(meter_power_series)
except:
run = False
def train_on_chunk(self, mainchunk, meterchunk, epochs, batch_size):
'''Train using only one chunk
Parameters
----------
mainchunk : chunk of site meter
meterchunk : chunk of appliance
epochs : number of epochs for training
'''
s = self.sequence_length
#up_limit = min(len(mainchunk), len(meterchunk))
#down_limit = max(len(mainchunk), len(meterchunk))
# Replace NaNs with 0s
mainchunk.fillna(0, inplace=True)
meterchunk.fillna(0, inplace=True)
ix = mainchunk.index.intersection(meterchunk.index)
mainchunk = mainchunk[ix]
meterchunk = meterchunk[ix]
# Create array of batches
#additional = s - ((up_limit-down_limit) % s)
additional = s - (len(ix) % s)
X_batch = np.append(mainchunk, np.zeros(additional))
Y_batch = np.append(meterchunk, np.zeros(additional))
X_batch = np.reshape(X_batch, (int(len(X_batch) / s), s, 1))
Y_batch = np.reshape(Y_batch, (int(len(Y_batch) / s), s, 1))
self.model.fit(X_batch, Y_batch, batch_size=batch_size, epochs=epochs, shuffle=True)
def train_across_buildings(self, mainlist, meterlist, epochs=1, batch_size=128, **load_kwargs):
assert len(mainlist) == len(meterlist), "Number of main and meter channels should be equal"
num_meters = len(mainlist)
mainps = [None] * num_meters
meterps = [None] * num_meters
mainchunks = [None] * num_meters
meterchunks = [None] * num_meters
for i,m in enumerate(mainlist):
mainps[i] = m.power_series(**load_kwargs)
for i,m in enumerate(meterlist):
meterps[i] = m.power_series(**load_kwargs)
for i in range(num_meters):
mainchunks[i] = next(mainps[i])
meterchunks[i] = next(meterps[i])
if self.mmax == None:
self.mmax = max([m.max() for m in mainchunks])
run = True
while(run):
mainchunks = [self._normalize(m, self.mmax) for m in mainchunks]
meterchunks = [self._normalize(m, self.mmax) for m in meterchunks]
self.train_across_buildings_chunk(mainchunks, meterchunks, epochs, batch_size)
try:
for i in range(num_meters):
mainchunks[i] = next(mainps[i])
meterchunks[i] = next(meterps[i])
except:
run = False
def train_across_buildings_chunk(self, mainchunks, meterchunks, epochs, batch_size):
num_meters = len(mainchunks)
batch_size = int(batch_size/num_meters)
num_of_batches = [None] * num_meters
s = self.sequence_length
for i in range(num_meters):
mainchunks[i].fillna(0, inplace=True)
meterchunks[i].fillna(0, inplace=True)
ix = mainchunks[i].index.intersection(meterchunks[i].index)
m1 = mainchunks[i]
m2 = meterchunks[i]
mainchunks[i] = m1[ix]
meterchunks[i] = m2[ix]
num_of_batches[i] = int(len(ix)/(s*batch_size)) - 1
for e in range(epochs):
print(e)
batch_indexes = list(range(min(num_of_batches)))
random.shuffle(batch_indexes)
for bi, b in enumerate(batch_indexes):
print("Batch {} of {}".format(bi,num_of_batches), end="\r")
sys.stdout.flush()
X_batch = np.empty((batch_size*num_meters, s, 1))
Y_batch = np.empty((batch_size*num_meters, s, 1))
for i in range(num_meters):
mainpart = mainchunks[i]
meterpart = meterchunks[i]
mainpart = mainpart[b*batch_size*s:(b+1)*batch_size*s]
meterpart = meterpart[b*batch_size*s:(b+1)*batch_size*s]
X = np.reshape(mainpart, (batch_size, s, 1))
Y = np.reshape(meterpart, (batch_size, s, 1))
X_batch[i*batch_size:(i+1)*batch_size] = np.array(X)
Y_batch[i*batch_size:(i+1)*batch_size] = np.array(Y)
p = np.random.permutation(len(X_batch))
X_batch, Y_batch = X_batch[p], Y_batch[p]
self.model.train_on_batch(X_batch, Y_batch)
print("\n")
def disaggregate(self, mains, output_datastore, meter_metadata, **load_kwargs):
'''Disaggregate mains according to the model learnt.
Parameters
----------
mains : nilmtk.ElecMeter
output_datastore : instance of nilmtk.DataStore subclass
For storing power predictions from disaggregation algorithm.
meter_metadata : metadata for the produced output
**load_kwargs : key word arguments
Passed to `mains.power_series(**kwargs)`
'''
load_kwargs = self._pre_disaggregation_checks(load_kwargs)
load_kwargs.setdefault('sample_period', 60)
load_kwargs.setdefault('sections', mains.good_sections())
timeframes = []
building_path = '/building{}'.format(mains.building())
mains_data_location = building_path + '/elec/meter1'
data_is_available = False
for chunk in mains.power_series(**load_kwargs):
if len(chunk) < self.MIN_CHUNK_LENGTH:
continue
print("New sensible chunk: {}".format(len(chunk)))
timeframes.append(chunk.timeframe)
measurement = chunk.name
chunk2 = self._normalize(chunk, self.mmax)
appliance_power = self.disaggregate_chunk(chunk2)
appliance_power[appliance_power < 0] = 0
appliance_power = self._denormalize(appliance_power, self.mmax)
# Append prediction to output
data_is_available = True
cols = pd.MultiIndex.from_tuples([chunk.name])
meter_instance = meter_metadata.instance()
df = pd.DataFrame(
appliance_power.values, index=appliance_power.index,
columns=cols, dtype="float32")
key = '{}/elec/meter{}'.format(building_path, meter_instance)
output_datastore.append(key, df)
# Append aggregate data to output
mains_df = pd.DataFrame(chunk, columns=cols, dtype="float32")
output_datastore.append(key=mains_data_location, value=mains_df)
# Save metadata to output
if data_is_available:
self._save_metadata_for_disaggregation(
output_datastore=output_datastore,
sample_period=load_kwargs['sample_period'],
measurement=measurement,
timeframes=timeframes,
building=mains.building(),
meters=[meter_metadata]
)
def disaggregate_chunk(self, mains):
'''In-memory disaggregation.
Parameters
----------
mains : pd.Series to disaggregate
Returns
-------
appliance_powers : pd.DataFrame where each column represents a
disaggregated appliance. Column names are the integer index
into `self.model` for the appliance in question.
'''
s = self.sequence_length
up_limit = len(mains)
mains.fillna(0, inplace=True)
additional = s - (up_limit % s)
X_batch = np.append(mains, np.zeros(additional))
X_batch = np.reshape(X_batch, (int(len(X_batch) / s), s ,1))
pred = self.model.predict(X_batch)
pred = np.reshape(pred, (up_limit + additional))[:up_limit]
column = pd.Series(pred, index=mains.index, name=0)
appliance_powers_dict = {}
appliance_powers_dict[0] = column
appliance_powers = pd.DataFrame(appliance_powers_dict)
return appliance_powers
def import_model(self, filename):
'''Loads keras model from h5
Parameters
----------
filename : filename for .h5 file
Returns: Keras model
'''
self.model = load_model(filename)
with h5py.File(filename, 'a') as hf:
ds = hf.get('disaggregator-data').get('mmax')
self.mmax = np.array(ds)[0]
def export_model(self, filename):
'''Saves keras model to h5
Parameters
----------
filename : filename for .h5 file
'''
self.model.save(filename)
with h5py.File(filename, 'a') as hf:
gr = hf.create_group('disaggregator-data')
gr.create_dataset('mmax', data = [self.mmax])
def _normalize(self, chunk, mmax):
'''Normalizes timeseries
Parameters
----------
chunk : the timeseries to normalize
max : max value of the powerseries
Returns: Normalized timeseries
'''
tchunk = chunk / mmax
return tchunk
def _denormalize(self, chunk, mmax):
'''Deormalizes timeseries
Note: This is not entirely correct
Parameters
----------
chunk : the timeseries to denormalize
max : max value used for normalization
Returns: Denormalized timeseries
'''
tchunk = chunk * mmax
return tchunk
def _create_model(self, sequence_len):
'''Creates the Auto encoder module described in the paper
'''
model = Sequential()
# 1D Conv
model.add(Conv1D(8, 4, activation="linear", input_shape=(sequence_len, 1), padding="same", strides=1))
model.add(Flatten())
# Fully Connected Layers
model.add(Dropout(0.2))
model.add(Dense((sequence_len-0)*8, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense((sequence_len-0)*8, activation='relu'))
model.add(Dropout(0.2))
# 1D Conv
model.add(Reshape(((sequence_len-0), 8)))
model.add(Conv1D(1, 4, activation="linear", padding="same", strides=1))
model.compile(loss='mse', optimizer='adam')
plot_model(model, to_file='model.png', show_shapes=True)
return model
================================================
FILE: DAE/metrics.py
================================================
from nilmtk.electric import align_two_meters
import numpy as np
def tp_tn_fp_fn(states_pred, states_ground):
tp = np.sum(np.logical_and(states_pred == 1, states_ground == 1))
fp = np.sum(np.logical_and(states_pred == 1, states_ground == 0))
fn = np.sum(np.logical_and(states_pred == 0, states_ground == 1))
tn = np.sum(np.logical_and(states_pred == 0, states_ground == 0))
return tp, tn, fp, fn
def recall_precision_accuracy_f1(pred, ground):
aligned_meters = align_two_meters(pred, ground)
threshold = ground.on_power_threshold()
chunk_results = []
sum_samples = 0.0
for chunk in aligned_meters:
sum_samples += len(chunk)
pr = np.array([0 if (p)<threshold else 1 for p in chunk.iloc[:,0]])
gr = np.array([0 if p<threshold else 1 for p in chunk.iloc[:,1]])
tp, tn, fp, fn = tp_tn_fp_fn(pr,gr)
p = sum(pr)
n = len(pr) - p
chunk_results.append([tp,tn,fp,fn,p,n])
if sum_samples == 0:
return None
else:
[tp,tn,fp,fn,p,n] = np.sum(chunk_results, axis=0)
res_recall = recall(tp,fn)
res_precision = precision(tp,fp)
res_f1 = f1(res_precision,res_recall)
res_accuracy = accuracy(tp,tn,p,n)
return (res_recall,res_precision,res_accuracy,res_f1)
def relative_error_total_energy(pred, ground):
aligned_meters = align_two_meters(pred, ground)
chunk_results = []
sum_samples = 0.0
for chunk in aligned_meters:
chunk.fillna(0, inplace=True)
sum_samples += len(chunk)
E_pred = sum(chunk.iloc[:,0])
E_ground = sum(chunk.iloc[:,1])
chunk_results.append([
E_pred,
E_ground
])
if sum_samples == 0:
return None
else:
[E_pred, E_ground] = np.sum(chunk_results,axis=0)
return abs(E_pred - E_ground) / float(max(E_pred,E_ground))
def mean_absolute_error(pred, ground):
aligned_meters = align_two_meters(pred, ground)
total_sum = 0.0
sum_samples = 0.0
for chunk in aligned_meters:
chunk.fillna(0, inplace=True)
sum_samples += len(chunk)
total_sum += sum(abs((chunk.iloc[:,0]) - chunk.iloc[:,1]))
if sum_samples == 0:
return None
else:
return total_sum / sum_samples
def recall(tp,fn):
return tp/float(tp+fn)
def precision(tp,fp):
return tp/float(tp+fp)
def f1(prec,rec):
return 2 * (prec*rec) / float(prec+rec)
def accuracy(tp, tn, p, n):
return (tp + tn) / float(p + n)
================================================
FILE: DAE/redd-test.py
================================================
from __future__ import print_function, division
import time
from matplotlib import rcParams
import matplotlib.pyplot as plt
from nilmtk import DataSet, TimeFrame, MeterGroup, HDFDataStore
from daedisaggregator import DAEDisaggregator
import metrics
print("========== OPEN DATASETS ============")
train = DataSet('redd.h5')
test = DataSet('redd.h5')
train.set_window(end="30-4-2011")
test.set_window(start="30-4-2011")
test_building = 1
meter_key = 'microwave'
train_elec = train.buildings[1].elec
test_elec = test.buildings[test_building].elec
train_meter = train_elec.submeters()[meter_key]
train_mains = train_elec.mains().all_meters()[0]
test_mains = test_elec.mains().all_meters()[0]
dae = DAEDisaggregator(256)
start = time.time()
print("========== TRAIN ============")
dae.train(train_mains, train_meter, epochs=5, sample_period=1)
dae.export_model("model-redd100.h5")
end = time.time()
print("Train =", end-start, "seconds.")
print("========== DISAGGREGATE ============")
disag_filename = 'disag-out.h5'
output = HDFDataStore(disag_filename, 'w')
dae.disaggregate(test_mains, output, train_meter, sample_period=1)
output.close()
print("========== RESULTS ============")
result = DataSet(disag_filename)
res_elec = result.buildings[test_building].elec
rpaf = metrics.recall_precision_accuracy_f1(res_elec[meter_key], test_elec[meter_key])
print("============ Recall: {}".format(rpaf[0]))
print("============ Precision: {}".format(rpaf[1]))
print("============ Accuracy: {}".format(rpaf[2]))
print("============ F1 Score: {}".format(rpaf[2]))
print("============ Relative error in total energy: {}".format(metrics.relative_error_total_energy(res_elec[meter_key], test_elec[meter_key])))
print("============ Mean absolute error(in Watts): {}".format(metrics.mean_absolute_error(res_elec[meter_key], test_elec[meter_key])))
================================================
FILE: DAE/ukdale-test.py
================================================
from __future__ import print_function, division
import time
from matplotlib import rcParams
import matplotlib.pyplot as plt
from nilmtk import DataSet, TimeFrame, MeterGroup, HDFDataStore
from daedisaggregator import DAEDisaggregator
import metrics
print("========== OPEN DATASETS ============")
train = DataSet('ukdale.h5')
test = DataSet('ukdale.h5')
train.set_window(start="13-4-2013", end="1-1-2014")
test.set_window(start="1-1-2014", end="30-3-2014")
train_building = 1
test_building = 1
sample_period = 6
meter_key = 'microwave'
train_elec = train.buildings[train_building].elec
test_elec = test.buildings[test_building].elec
train_meter = train_elec.submeters()[meter_key]
test_meter = test_elec.submeters()[meter_key]
train_mains = train_elec.mains()
test_mains = test_elec.mains()
dae = DAEDisaggregator(300)
start = time.time()
print("========== TRAIN ============")
epochs = 0
for i in range(3):
print("CHECKPOINT {}".format(epochs))
dae.train(train_mains, train_meter, epochs=5, sample_period=sample_period)
epochs += 5
dae.export_model("UKDALE-DAE-h{}-{}-{}epochs.h5".format(train_building,
meter_key,
epochs))
end = time.time()
print("Train =", end-start, "seconds.")
print("========== DISAGGREGATE ============")
disag_filename = "disag-out.h5"
output = HDFDataStore(disag_filename, 'w')
dae.disaggregate(test_mains, output, test_meter, sample_period=sample_period)
output.close()
print("========== RESULTS ============")
result = DataSet(disag_filename)
res_elec = result.buildings[test_building].elec
rpaf = metrics.recall_precision_accuracy_f1(res_elec[meter_key], test_meter)
print("============ Recall: {}".format(rpaf[0]))
print("============ Precision: {}".format(rpaf[1]))
print("============ Accuracy: {}".format(rpaf[2]))
print("============ F1 Score: {}".format(rpaf[2]))
print("============ Relative error in total energy: {}".format(metrics.relative_error_total_energy(res_elec[meter_key], test_meter)))
print("============ Mean absolute error(in Watts): {}".format(metrics.mean_absolute_error(res_elec[meter_key], test_meter)))
================================================
FILE: GRU/GRU-example.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to use the RNN Autoencoder with NILMTK\n",
"\n",
"This is an example on how to train and use the Recurrent Network (RNN) disaggregator on the [REDD](http://redd.csail.mit.edu/) dataset using [NILMTK](https://github.com/nilmtk/NILMTK/).\n",
"\n",
"This network was described in the [Neural NILM](https://arxiv.org/pdf/1507.06594.pdf) paper."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First of all, we need to train the RNNDisaggregator using the train data. For this example, both train and test data are consumption data of the microwave of the first REDD building."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import warnings; warnings.simplefilter('ignore')\n",
"\n",
"from nilmtk import DataSet\n",
"train = DataSet('redd.h5')\n",
"train.set_window(end=\"30-4-2011\") #Use data only until 4/30/2011\n",
"train_elec = train.buildings[1].elec"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we need to define the disaggregator model."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Using TensorFlow backend.\n"
]
}
],
"source": [
"from grudisaggregator import GRUDisaggregator\n",
"gru = GRUDisaggregator()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then train the model. We need to input the train data as well as their sample period. Also, we need to pass the desired number of training epochs. Finally, save the model for later use."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Epoch 1/5\n",
"1003066/1003066 [==============================] - 137s - loss: 7.2744e-04 \n",
"Epoch 2/5\n",
"1003066/1003066 [==============================] - 105s - loss: 6.6916e-04 \n",
"Epoch 3/5\n",
"1003066/1003066 [==============================] - 106s - loss: 6.5737e-04 \n",
"Epoch 4/5\n",
"1003066/1003066 [==============================] - 106s - loss: 6.5401e-04 \n",
"Epoch 5/5\n",
"1003066/1003066 [==============================] - 103s - loss: 6.5021e-04 \n"
]
}
],
"source": [
"train_mains = train_elec.mains().all_meters()[0] # The aggregated meter that provides the input\n",
"train_meter = train_elec.submeters()['microwave'] # The microwave meter that is used as a training target\n",
"\n",
"gru.train(train_mains, train_meter, epochs=5, sample_period=1)\n",
"gru.export_model(\"model-redd5.h5\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that the model is trained, we can use it to disaggregate energy data. Let's test it on the rest of the data from building 1.\n",
"\n",
"First we use the model to predict the microwave consumption. The results are saved automatically in a .h5 datastore."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"New sensible chunk: 121482\n",
"New sensible chunk: 112661\n",
"New sensible chunk: 87770\n",
"New sensible chunk: 54084\n",
"New sensible chunk: 2660\n",
"New sensible chunk: 33513\n",
"New sensible chunk: 138535\n",
"New sensible chunk: 32514\n",
"New sensible chunk: 27255\n",
"New sensible chunk: 34833\n",
"New sensible chunk: 100831\n"
]
}
],
"source": [
"test = DataSet('redd.h5')\n",
"test.set_window(start=\"30-4-2011\")\n",
"test_elec = test.buildings[1].elec\n",
"test_mains = test_elec.mains().all_meters()[0]\n",
"\n",
"disag_filename = 'disag-out.h5' # The filename of the resulting datastore\n",
"from nilmtk.datastore import HDFDataStore\n",
"output = HDFDataStore(disag_filename, 'w')\n",
"\n",
"# test_mains: The aggregated signal meter\n",
"# output: The output datastore\n",
"# train_meter: This is used in order to copy the metadata of the train meter into the datastore\n",
"gru.disaggregate(test_mains, output, train_meter, sample_period=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's plot the results and compare them to the ground truth signal.\n",
"\n",
"**Note:** Calling plot this way, downsamples the signal to reduce computing time. To plot the entire signal call\n",
"```\n",
"predicted.power_series_all_data().plot()\n",
"ground_truth.power_series_all_data().plot()\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\nkiWEQiF27drFmjVr4rZXVlZGf3/++efZv38/U6ZM4cknn2T8+PE88cQTXH/99dEyV111FWbG3Llz\n27Xv+eefZ/z48ZSVlQHwxhtvMHbsWPbu3cuMGTP40Y9+RG1tLRdeeCF/+tOfDvv3ShdNlImIiK9M\nnjyZ73znOzQ0NFBYWMisWbMYO3ZsuymajkybNo1PPvmEFStW8NnPfja6fcaMGe3KlpeX8/rrr8cl\nwN5zzz00NTWxdOlSjj32WACuvfZaRowYwbe+9S3efPNNACorK3nhhReij6uurqayspK1a9dSXV3N\nyJEjqamp4a9//Su33HJLtNzDDz9MYWFh9Pcbb7yR4cOH893vfpdNmzZxzDHHMHToUM4991zmzJnD\n3XffHS37zjvv8Le//S1uJOjWW29l3Lhx/OEPf4huu+WWWzj11FO55557ePXVVw/7N0sHBSMih1F7\noJaSXiXkh/RxkWCor4e1azP/OqecAsXF6X/eq666ijvvvJMFCxZw8cUXs2DBgrgpkc4455g/fz6X\nXXZZXCDSETPjpptuigtEWlpaWLhwIVdccUU0EAEYOHAgkydP5plnnmHv3r306dOHyspKfvazn7F+\n/XpOOukkqqurGT9+PP3796e6upqbb76Z6upqgLiRkdhApL6+nv379zN69GhaWlpYvXo1xxxzDACT\nJk1i6tSpfPTRRxx//PEAzJkzh6KiouhqonfffZf169dz7733snPnzri/w7hx43juuecO+zdLF51d\nRQ6j34/7cc3fX8OzVzyb7aqI9Ii1a6GiIvOvs3IlZOKefeXl5Vx00UXMmjWLffv20dLSwle+8pXD\nPu7TTz+lrq6O0047LaHXOe6449o9vr6+npNPPrld2ZEjR9LS0sLGjRsZOXIklZWVOOeorq5myJAh\nrF69mh/+8IeUl5fzyCOPAOHRktLSUk4//fTo82zcuJF7772Xl19+mZqamuh2M4vLA/nqV7/KXXfd\nxZw5c6JTSPPmzWPChAn06dMHgPXr1wNw3XXXddi+UChEbW1tdEonkxSMiCTg92t/n+0qiPSYU04J\nBwo98TqZMnnyZG666Sa2bt3KhAkT6Nu3b9pfo3fv3ik/dtCgQRx//PEsXrw4OooyevRoysvLufPO\nO9m4cSNLliyJW6Lc0tLCRRddxO7du5k+fTojRoygpKSEzZs3c/3118etIBo0aBCVlZXMnTuXadOm\nsWzZMjZs2MBPfvKTuOeDcMJrbMATqzVwyTQFIyIiEqe4ODMjFj3piiuu4JZbbmH58uXMmTMnocf0\n79+f0tJS/vKXv6T0mv3796e4uJh169a127dmzRpCoVDcqpvKykqqq6s57rjjOOOMMygpKeH000+n\nrKyMV155hVWrVsXld7z//vusX7+eZ599lq997WvR7YsWLeqwPpMmTeK2225j/fr1zJkzh5KSEi65\n5JLo/uHDhwPQt29fLrzwwpTanC5aTSMiIr5TUlLCz3/+c2bMmMGll16a0GPMjMsvv5yXX36ZVatW\nJf2aoVCIL37xi8yfP58NGzZEt2/fvp3Zs2dTWVkZN9JQWVnJRx99xNy5c6N5IWbG6NGjefTRR2lq\naorLF8nLywNodw2Vxx57rMOryF555ZWEQiFmzZrFvHnzuOSSS+JGcyoqKhg+fDg//elP2bdvX7vH\n79ixI+m/Qao0MiIiIr7Q9hoi1157bdLP8eCDD7Jw4ULGjBnDzTffzMiRI9myZQvz5s1j6dKllJaW\ndvharX7wgx+waNEizjvvPKZMmUJeXh6/+MUvaGxs5OGHH44r2xporFu3jgcffDC6fcyYMbzyyisU\nFRVx1llnRbefcsopDB8+nLvvvptNmzZRWlrKCy+8wO7duzusS//+/bngggt49NFH2bt3L5MmTYrb\nb2Y888wzTJw4kdNOO40bbriBIUOGsHnzZt58803KysqYP39+0n/DVCgYERERX0jkHjNt70XT9vfB\ngwezfPly7r33XmbNmkVdXR1Dhgxh4sSJFMcs/enstU499VSqq6uZPn06Dz30EC0tLZx77rnRK8DG\nOvnkkzn66KPZsWMHn//856PbKysrMTPOOeccCgoKotvz8/NZsGAB3/zmN3nooYcoKiriy1/+Mrfd\ndlunOR+TJk3i9ddfp7S0lIkTJ7bbP3bsWJYtW8YDDzzAU089xd69exk4cCDnnHNO3JLiTLPOorue\nZGaVwL8AFcAg4HLn3EudlP05cDNwp3Pu8ZjthcCjwCSgEHgNmOKc+ySmzBHAk8AlQAvwAnCHc679\n+FS4/Chg5cqVKxmV5ASqfT/8RnX3Zf/vK91j3zf69OrDnul7sl2VjDjq4aPYtX+X3qs+t2rVKioq\nKkjlfCbSkcO9p1r3AxXOuS7nvXIlZ6QEeBeYAnR6RjSzK4BzgM0d7H4M+BJwJTAGGEw42Ig1CxgJ\njIuUHQM83c26i4iISDfkxDSNc+5V4FUA62Tsy8yGAP8KXAz8sc2+UuDrwNXOubci224A1pjZ2c65\nFWY2MvLYCufc6kiZ24E/mNk/O+e2ZaZ1IiIi0pVcGRnpUiRA+Q/gYefcmg6KVBAOrF5v3eCcWwds\nAEZHNp0L1LQGIhGLCI/EnJOJeouIiMjheSIYAaYBjc65zq7nOzCyv67N9u2Rfa1lPond6ZxrBnbF\nlBEREZEelhPTNF0xswrgm0DXNwoQERERT8r5YAT4PNAf2BiTTpIHPGpmdzrnTgC2Ab3MrLTN6MiA\nyD4i/x4d+8RmlgccGVOmQ1OnTm13bf6qqiqqqqpSa5GIiIiPzJ49m9mzZ8dti71XzuF4IRj5D2Bh\nm23/Fdn+75HfVwJNhFfJvAhgZiOAYcCySJllQD8z+2xM3sg4wIDlXVVg5syZWgonIiLSiY6+oMcs\n7T2snAhGzKwEOJFwYABwgpmdDuxyzm0EatqUPwhsc86tB3DO1ZnZrwiPltQAe4DHgaXOuRWRMmvN\n7DXgl2Z2K9ALeAKYrZU0IiIi2ZMTwQhwJvAm4ZUtDngksv03hJfsttXRtUimAs3APMIXPXsVuK1N\nmcmEL3q2iPBFz+YBd3Sz7iIiItINORGMRK4NkvDKnkieSNttDcDtkZ/OHrcbuCaVOoqIiEhmeGVp\nr4iIiPiUghEREQmsUCjE/fffn+1qBJ6CERER8bzf/OY3hEIhQqEQb7/9dodlhg4dSigU4rLLLotu\na3vXXsmOnMgZERERSYfevXsza9YsPve5z8Vtf+utt9i8eTNFRUVx2/fv309+vrrCbNPIiIiI+MbE\niRN5/vnnaWlpids+a9YszjzzTAYOjL/7R69evQiFut8V1tfXd/s5gkzBiIiI+IKZUVVVxc6dO1m4\n8NC1Mg8ePMi8efOYPHkyzsVfGaKjnJEtW7bwj//4jwwZMoSioiJOOOEEpkyZQlNTE3BoSmjx4sVM\nmTKFAQMGMHTo0OjjV69ezYQJEygrK6Nv375cdNFFLF9+6NqatbW15Ofn8+STh263tnPnTkKhEP37\n94+ry6233srgwYOjvy9ZsoSrrrqKY489lqKiIoYNG8Zdd93FgQMHomUeeeQRQqEQGzdubPc3mj59\nOoWFhXFXR12+fDnjx4+nX79+lJSUcP7553c61ZUpCkZERMQ3jjvuOM4999y4S5P/8Y9/pK6ujquv\nvvqwj9+6dStnnXUWc+fOpaqqiieeeILrrruOxYsXtxv9mDJlCmvXruW+++5j2rRpAHzwwQeMGTOG\n999/n2nTpvG9732Pjz/+mPPPP5933nkHgLKyMj7zmc+wePHi6HMtWbKEUCjErl27WLNmTdz2ysrK\n6O/PP/88+/fvZ8qUKTz55JOMHz+eJ554guuvvz5a5qqrrsLMmDt3brv2Pf/884wfPz56i5M33niD\nsWPHsnfvXmbMmMGPfvQjamtrufDCC/nTn/502L9XumiiTEQAcM4pkU98YfLkyXznO9+hoaGBwsJC\nZs2axdixY9tN0XRk2rRpfPLJJ6xYsYLPfvbQ/VlnzJjRrmx5eTmvv/563OfmnnvuoampiaVLl3Ls\nsccCcO211zJixAi+9a1v8eabbwJQWVnJCy+8EH1cdXU1lZWVrF27lurqakaOHElNTQ1//etfueWW\nW6LlHn74YQoLC6O/33jjjQwfPpzvfve7bNq0iWOOOYahQ4dy7rnnMmfOHO6+++5o2XfeeYe//e1v\ncSNBt956K+PGjeMPf/hDdNstt9zCqaeeyj333MOrr7562L9ZOigYERGROPUH61m7Y23GX+eU8lMo\nLihO+/NeddVV3HnnnSxYsICLL76YBQsWxE2JdMY5x/z587nsssviApGOmBk33XRTXCDS0tLCwoUL\nueKKK6KBCMDAgQOZPHkyzzzzDHv37qVPnz5UVlbys5/9jPXr13PSSSdRXV3N+PHj6d+/P9XV1dx8\n881UV1cDxI2MxAYi9fX17N+/n9GjR9PS0sLq1as55phjAJg0aRJTp07lo48+4vjjjwdgzpw5FBUV\nRVcTvfvuu6xfv557772XnTt3xv0dxo0bx3PPPXfYv1m6KBgREQAcDkMjIwJrd6yl4heJ3eCsO1be\nvJJRg9J/E9Ly8nIuuugiZs2axb59+2hpaeErX/nKYR/36aefUldXx2mnnZbQ6xx33HHtHl9fX8/J\nJ5/cruzIkSNpaWlh48aNjBw5ksrKSpxzVFdXM2TIEFavXs0Pf/hDysvLeeSR8B1RqqurKS0t5fTT\nT48+z8aNG7n33nt5+eWXqak5dNs2M4vLA/nqV7/KXXfdxZw5c6JTSPPmzWPChAn06dMHgPXr1wNw\n3XXXddi+UChEbW1tu7vWZ4KCERERiXNK+SmsvHllj7xOpkyePJmbbrqJrVu3MmHCBPr27Zv21+jd\nu3fKjx00aBDHH388ixcvjo6ijB49mvLycu688042btzIkiVL4pYot7S0cNFFF7F7926mT5/OiBEj\nKCkpYfPmzVx//fVxK4gGDRpEZWUlc+fOZdq0aSxbtowNGzbwk5/8JO75IJzwGhvwxGoNXDJNwYiI\nAOGhWQ2MCEBxQXFGRix60hVXXMEtt9zC8uXLmTNnTkKP6d+/P6WlpfzlL39J6TX79+9PcXEx69at\na7dvzZo1hEKhuFU3lZWVVFdXc9xxx3HGGWdQUlLC6aefTllZGa+88gqrVq2Ky+94//33Wb9+Pc8+\n+yxf+9rXotsXLVrUYX0mTZrEbbfdxvr165kzZw4lJSVccskl0f3Dhw8HoG/fvlx44YUptTldtJpG\nRER8p6SkhJ///OfMmDGDSy+9NKHHmBmXX345L7/8MqtWrUr6NUOhEF/84heZP38+GzZsiG7fvn07\ns2fPprKyMm6kobKyko8++oi5c+dG80LMjNGjR/Poo4/S1NQUly+Sl5cH0O4aKo899liHyedXXnkl\noVCIWbNmMW/ePC655JK40ZyKigqGDx/OT3/6U/bt29fu8Tt27Ej6b5AqjYyICBDOGRHxsrbXELn2\n2muTfo4HH3yQhQsXMmbMGG6++WZGjhzJli1bmDdvHkuXLqW0tLTD12r1gx/8gEWLFnHeeecxZcoU\n8vLy+MUvfkFjYyMPP/xwXNnWQGPdunU8+OCD0e1jxozhlVdeoaioiLPOOiu6/ZRTTmH48OHcfffd\nbNq0idLSUl544QV2797dYV369+/PBRdcwKOPPsrevXuZNGlS3H4z45lnnmHixImcdtpp3HDDDQwZ\nMoTNmzfz5ptvUlZWxvz585P+G6ZCwYhIwLWeVDs7uYp4RSJL09vei6bt74MHD2b58uXce++9zJo1\ni7q6OoYMGcLEiRMpLi6Oe1xHTj31VKqrq5k+fToPPfQQLS0tnHvuudErwMY6+eSTOfroo9mxYwef\n//zno9srKysxM8455xwKCgqi2/Pz81mwYAHf/OY3eeihhygqKuLLX/4yt912W6c5H5MmTeL111+n\ntLSUiRMntts/duxYli1bxgMPPMBTTz3F3r17GThwIOecc07ckuJMM52AOmdmo4CVK1euZNSo5OZP\n7fvhN6q7T39fr7PvG3169WHP9D3ZrkpGHPnjI6k5UEPjPY0U5BUc/gHiSatWraKiooJUzmciHTnc\n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\nQq7akXgX3CwzWyEPh23AJOCI3JzvNXgArFW4VVyIYmeMnQ8SIxEwxs4H8TPGzpdUQ31t7eAn0Jfx\nLrNO4B+B7cO27XELdXt83O9BYHLYNglYjS/PvANwLXAGHsRmSG7/2+BjgLMoaC547Iyx8yXGOBhj\n52sHxtj5Uurm2DfpBJsM3APMo7Ka5biw7X7gLHw10p8ANxEC1AB3As/hi709BozJ7TPrwhtACcb+\nYmeMnS8xxsEYO187MMbOl1LXqSnDNGZ2L/AUsCKcZIuA2ZIm4V1lg81scTjxdgE+GqqeBBwGTDGz\nCeZzxrN9dobX1Wa2zmJwRSh2xtj5QjsSY4szxs4X2hE1Y+x8STXULKsHGI+fPDPD5ytxR6TVwDdD\n3hbA93GLd7su9lF4+O92ZoydLzHGwRg7Xzswxs6X0vqpaRFYzexxfErW7pKOMbPzgJm4k9Hbkgaa\n2TJgNvAjwtLwVfsoxSJTtRQ7Y+x8kBiJgDF2PoifMXa+pPWVLQrUnC+TRgJXA0OAT5jZC5J2MLOF\nTWtEHyt2xtj5IDEW3LReUex8ED9j7HxJ66qpa9OY2Qv4/PBhwF+EvIUKypdVZbGjllLsjLHzQWKM\ngTF2PoifMXa+pHVVxAGcDTwBnCJpD/CVEq2qi8YaXLm1pIqdMXY+SIxr1cKMsfNB/Iyx8yUF9W/2\nF5rZ25JmAy/iXtLRKXbG2PkgMcag2PkgfsbY+ZIqaqrPSFJSUlJSUlJStQodZ2uHcb7YGWPng8QY\ng2Lng/gZY+drd6WekaSkpKSkpKRClSzNpKSkpKSkpEKVjJGkpKSkpKSkQpWMkaSkpKSkpKRClYyR\npKSkpKSkpEKVjJGkpKSkpKSkQpWMkaSkpKSkpKRClYyRpKSkpKSkpEKVjJGkpKSkpKSkQpWMkaSk\npKSkpKRClYyRpKSkpKSkpEL1R3aeK+xT/zRhAAAAAElFTkSuQmCC\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x7f5c83d2c090>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"result = DataSet(disag_filename)\n",
"res_elec = result.buildings[1].elec\n",
"predicted = res_elec['microwave']\n",
"ground_truth = test_elec['microwave']\n",
"\n",
"import matplotlib.pyplot as plt\n",
"predicted.plot()\n",
"ground_truth.plot()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally let's see the metric results."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"============ Recall: 0.0370807004875\n",
"============ Precision: 0.813554622514\n",
"============ Accuracy: 0.278783362107\n",
"============ F1 Score: 0.0709285741419\n",
"============ Relative error in total energy: 0.768754882991\n",
"============ Mean absolute error(in Watts): 19.2988829522\n"
]
}
],
"source": [
"import metrics\n",
"rpaf = metrics.recall_precision_accuracy_f1(predicted, ground_truth)\n",
"print(\"============ Recall: {}\".format(rpaf[0]))\n",
"print(\"============ Precision: {}\".format(rpaf[1]))\n",
"print(\"============ Accuracy: {}\".format(rpaf[2]))\n",
"print(\"============ F1 Score: {}\".format(rpaf[3]))\n",
"\n",
"print(\"============ Relative error in total energy: {}\".format(metrics.relative_error_total_energy(predicted, ground_truth)))\n",
"print(\"============ Mean absolute error(in Watts): {}\".format(metrics.mean_absolute_error(predicted, ground_truth)))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python [conda env:nilmtk-env]",
"language": "python",
"name": "conda-env-nilmtk-env-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: GRU/README.md
================================================
# Recurrent (GRU) Network Energy Disaggregator
An attempt to create a "lightweight" recurrent network for energy disaggregation. Moreover, this architecture supports predicting the energy consumptions of more than one meters.
See example experiment [here](https://github.com/OdysseasKr/neural-disaggregator/blob/master/GRU/GRU-example.ipynb).
================================================
FILE: GRU/grudisaggregator.py
================================================
from __future__ import print_function, division
import random
import sys
from matplotlib import rcParams
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import h5py
from keras.models import load_model
from keras.models import Sequential
from keras.layers import Dense, Conv1D, GRU, Bidirectional, Dropout
from keras.utils import plot_model
from nilmtk.utils import find_nearest
from nilmtk.feature_detectors import cluster
from nilmtk.legacy.disaggregate import Disaggregator
from nilmtk.datastore import HDFDataStore
class GRUDisaggregator(Disaggregator):
'''Attempt to create a RNN Disaggregator
Attributes
----------
model : keras Sequential model
mmax : the maximum value of the aggregate data
MIN_CHUNK_LENGTH : int
the minimum length of an acceptable chunk
'''
def __init__(self):
'''Initialize disaggregator
'''
self.MODEL_NAME = "GRU"
self.mmax = None
self.MIN_CHUNK_LENGTH = 100
self.model = self._create_model()
def train(self, mains, meter, epochs=1, batch_size=128, **load_kwargs):
'''Train
Parameters
----------
mains : a nilmtk.ElecMeter object for the aggregate data
meter : a nilmtk.ElecMeter object for the meter data
epochs : number of epochs to train
batch_size : size of batch used for training
**load_kwargs : keyword arguments passed to `meter.power_series()`
'''
main_power_series = mains.power_series(**load_kwargs)
meter_power_series = meter.power_series(**load_kwargs)
# Train chunks
run = True
mainchunk = next(main_power_series)
meterchunk = next(meter_power_series)
if self.mmax == None:
self.mmax = mainchunk.max()
while(run):
mainchunk = self._normalize(mainchunk, self.mmax)
meterchunk = self._normalize(meterchunk, self.mmax)
self.train_on_chunk(mainchunk, meterchunk, epochs, batch_size)
try:
mainchunk = next(main_power_series)
meterchunk = next(meter_power_series)
except:
run = False
def train_on_chunk(self, mainchunk, meterchunk, epochs, batch_size):
'''Train using only one chunk
Parameters
----------
mainchunk : chunk of site meter
meterchunk : chunk of appliance
epochs : number of epochs for training
batch_size : size of batch used for training
'''
# Replace NaNs with 0s
mainchunk.fillna(0, inplace=True)
meterchunk.fillna(0, inplace=True)
ix = mainchunk.index.intersection(meterchunk.index)
mainchunk = np.array(mainchunk[ix])
meterchunk = np.array(meterchunk[ix])
mainchunk = np.reshape(mainchunk, (mainchunk.shape[0],1,1))
self.model.fit(mainchunk, meterchunk, epochs=epochs, batch_size=batch_size, shuffle=True)
def train_across_buildings(self, mainlist, meterlist, epochs=1, batch_size=128, **load_kwargs):
'''Train using data from multiple buildings
Parameters
----------
mainlist : a list of nilmtk.ElecMeter objects for the aggregate data of each building
meterlist : a list of nilmtk.ElecMeter objects for the meter data of each building
batch_size : size of batch used for training
epochs : number of epochs to train
**load_kwargs : keyword arguments passed to `meter.power_series()`
'''
assert len(mainlist) == len(meterlist), "Number of main and meter channels should be equal"
num_meters = len(mainlist)
mainps = [None] * num_meters
meterps = [None] * num_meters
mainchunks = [None] * num_meters
meterchunks = [None] * num_meters
# Get generators of timeseries
for i,m in enumerate(mainlist):
mainps[i] = m.power_series(**load_kwargs)
for i,m in enumerate(meterlist):
meterps[i] = m.power_series(**load_kwargs)
# Get a chunk of data
for i in range(num_meters):
mainchunks[i] = next(mainps[i])
meterchunks[i] = next(meterps[i])
if self.mmax == None:
self.mmax = max([m.max() for m in mainchunks])
run = True
while(run):
# Normalize and train
mainchunks = [self._normalize(m, self.mmax) for m in mainchunks]
meterchunks = [self._normalize(m, self.mmax) for m in meterchunks]
self.train_across_buildings_chunk(mainchunks, meterchunks, epochs, batch_size)
# If more chunks, repeat
try:
for i in range(num_meters):
mainchunks[i] = next(mainps[i])
meterchunks[i] = next(meterps[i])
except:
run = False
def train_across_buildings_chunk(self, mainchunks, meterchunks, epochs, batch_size):
'''Train using only one chunk of data. This chunk consists of data from
all buildings.
Parameters
----------
mainchunk : chunk of site meter
meterchunk : chunk of appliance
epochs : number of epochs for training
batch_size : size of batch used for training
'''
num_meters = len(mainchunks)
batch_size = int(batch_size/num_meters)
num_of_batches = [None] * num_meters
# Find common parts of timeseries
for i in range(num_meters):
mainchunks[i].fillna(0, inplace=True)
meterchunks[i].fillna(0, inplace=True)
ix = mainchunks[i].index.intersection(meterchunks[i].index)
m1 = mainchunks[i]
m2 = meterchunks[i]
mainchunks[i] = m1[ix]
meterchunks[i] = m2[ix]
num_of_batches[i] = int(len(ix)/batch_size) - 1
for e in range(epochs): # Iterate for every epoch
print(e)
batch_indexes = list(range(min(num_of_batches)))
random.shuffle(batch_indexes)
for bi, b in enumerate(batch_indexes): # Iterate for every batch
print("Batch {} of {}".format(bi,num_of_batches), end="\r")
sys.stdout.flush()
X_batch = np.empty((batch_size*num_meters, 1, 1))
Y_batch = np.empty((batch_size*num_meters, 1))
# Create a batch out of data from all buildings
for i in range(num_meters):
mainpart = mainchunks[i]
meterpart = meterchunks[i]
mainpart = mainpart[b*batch_size:(b+1)*batch_size]
meterpart = meterpart[b*batch_size:(b+1)*batch_size]
X = np.reshape(mainpart, (batch_size, 1, 1))
Y = np.reshape(meterpart, (batch_size, 1))
X_batch[i*batch_size:(i+1)*batch_size] = np.array(X)
Y_batch[i*batch_size:(i+1)*batch_size] = np.array(Y)
# Shuffle data
p = np.random.permutation(len(X_batch))
X_batch, Y_batch = X_batch[p], Y_batch[p]
self.model.train_on_batch(X_batch, Y_batch)
print("\n")
def disaggregate(self, mains, output_datastore, meter_metadata, **load_kwargs):
'''Disaggregate mains according to the model learnt previously.
Parameters
----------
mains : nilmtk.ElecMeter
meter_metadata: a nilmtk.ElecMeter of the observed meter used for storing the metadata
output_datastore : instance of nilmtk.DataStore subclass
For storing power predictions from disaggregation algorithm.
**load_kwargs : key word arguments
Passed to `mains.power_series(**kwargs)`
'''
load_kwargs = self._pre_disaggregation_checks(load_kwargs)
load_kwargs.setdefault('sample_period', 60)
load_kwargs.setdefault('sections', mains.good_sections())
timeframes = []
building_path = '/building{}'.format(mains.building())
mains_data_location = building_path + '/elec/meter1'
data_is_available = False
for chunk in mains.power_series(**load_kwargs):
if len(chunk) < self.MIN_CHUNK_LENGTH:
continue
print("New sensible chunk: {}".format(len(chunk)))
timeframes.append(chunk.timeframe)
measurement = chunk.name
chunk2 = self._normalize(chunk, self.mmax)
appliance_power = self.disaggregate_chunk(chunk2)
appliance_power[appliance_power < 0] = 0
appliance_power = self._denormalize(appliance_power, self.mmax)
# Append prediction to output
data_is_available = True
cols = pd.MultiIndex.from_tuples([chunk.name])
meter_instance = meter_metadata.instance()
df = pd.DataFrame(
appliance_power.values, index=appliance_power.index,
columns=cols, dtype="float32")
key = '{}/elec/meter{}'.format(building_path, meter_instance)
output_datastore.append(key, df)
# Append aggregate data to output
mains_df = pd.DataFrame(chunk, columns=cols, dtype="float32")
output_datastore.append(key=mains_data_location, value=mains_df)
# Save metadata to output
if data_is_available:
self._save_metadata_for_disaggregation(
output_datastore=output_datastore,
sample_period=load_kwargs['sample_period'],
measurement=measurement,
timeframes=timeframes,
building=mains.building(),
meters=[meter_metadata]
)
def disaggregate_chunk(self, mains):
'''In-memory disaggregation.
Parameters
----------
mains : pd.Series
Returns
-------
appliance_powers : pd.DataFrame where each column represents a
disaggregated appliance. Column names are the integer index
into `self.model` for the appliance in question.
'''
up_limit = len(mains)
mains.fillna(0, inplace=True)
X_batch = np.array(mains)
X_batch = np.reshape(X_batch, (X_batch.shape[0],1,1))
pred = self.model.predict(X_batch, batch_size=128)
pred = np.reshape(pred, (len(pred)))
column = pd.Series(pred, index=mains.index[:len(X_batch)], name=0)
appliance_powers_dict = {}
appliance_powers_dict[0] = column
appliance_powers = pd.DataFrame(appliance_powers_dict)
return appliance_powers
def import_model(self, filename):
'''Loads keras model from h5
Parameters
----------
filename : filename for .h5 file
Returns: Keras model
'''
self.model = load_model(filename)
with h5py.File(filename, 'a') as hf:
ds = hf.get('disaggregator-data').get('mmax')
self.mmax = np.array(ds)[0]
def export_model(self, filename):
'''Saves keras model to h5
Parameters
----------
filename : filename for .h5 file
'''
self.model.save(filename)
with h5py.File(filename, 'a') as hf:
gr = hf.create_group('disaggregator-data')
gr.create_dataset('mmax', data = [self.mmax])
def _normalize(self, chunk, mmax):
'''Normalizes timeseries
Parameters
----------
chunk : the timeseries to normalize
max : max value of the powerseries
Returns: Normalized timeseries
'''
tchunk = chunk / mmax
return tchunk
def _denormalize(self, chunk, mmax):
'''Deormalizes timeseries
Note: This is not entirely correct
Parameters
----------
chunk : the timeseries to denormalize
max : max value used for normalization
Returns: Denormalized timeseries
'''
tchunk = chunk * mmax
return tchunk
def _create_model(self):
'''Creates the ANN model
'''
model = Sequential()
# 1D Conv
model.add(Conv1D(16, 4, activation="relu", padding="same", strides=1, input_shape=(1,1)))
model.add(Conv1D(8, 4, activation="relu", padding="same", strides=1))
# Bi-directional LSTMs
model.add(Bidirectional(GRU(64, return_sequences=True, stateful=False), merge_mode='concat'))
model.add(Bidirectional(GRU(128, return_sequences=False, stateful=False), merge_mode='concat'))
# Fully Connected Layers
model.add(Dense(64, activation='relu'))
model.add(Dense(1, activation='linear'))
model.compile(loss='mse', optimizer='adam')
plot_model(model, to_file='model.png', show_shapes=True)
return model
================================================
FILE: GRU/metrics.py
================================================
from nilmtk.electric import align_two_meters
import numpy as np
def tp_tn_fp_fn(states_pred, states_ground):
tp = np.sum(np.logical_and(states_pred == 1, states_ground == 1))
fp = np.sum(np.logical_and(states_pred == 1, states_ground == 0))
fn = np.sum(np.logical_and(states_pred == 0, states_ground == 1))
tn = np.sum(np.logical_and(states_pred == 0, states_ground == 0))
return tp, tn, fp, fn
def recall_precision_accuracy_f1(pred, ground):
aligned_meters = align_two_meters(pred, ground)
threshold = ground.on_power_threshold()
chunk_results = []
sum_samples = 0.0
for chunk in aligned_meters:
sum_samples += len(chunk)
pr = np.array([0 if (p)<threshold else 1 for p in chunk.iloc[:,0]])
gr = np.array([0 if p<threshold else 1 for p in chunk.iloc[:,1]])
tp, tn, fp, fn = tp_tn_fp_fn(pr,gr)
p = sum(pr)
n = len(pr) - p
chunk_results.append([tp,tn,fp,fn,p,n])
if sum_samples == 0:
return None
else:
[tp,tn,fp,fn,p,n] = np.sum(chunk_results, axis=0)
res_recall = recall(tp,fn)
res_precision = precision(tp,fp)
res_f1 = f1(res_precision,res_recall)
res_accuracy = accuracy(tp,tn,p,n)
return (res_recall,res_precision,res_accuracy,res_f1)
def relative_error_total_energy(pred, ground):
aligned_meters = align_two_meters(pred, ground)
chunk_results = []
sum_samples = 0.0
for chunk in aligned_meters:
chunk.fillna(0, inplace=True)
sum_samples += len(chunk)
E_pred = sum(chunk.iloc[:,0])
E_ground = sum(chunk.iloc[:,1])
chunk_results.append([
E_pred,
E_ground
])
if sum_samples == 0:
return None
else:
[E_pred, E_ground] = np.sum(chunk_results,axis=0)
return abs(E_pred - E_ground) / float(max(E_pred,E_ground))
def mean_absolute_error(pred, ground):
aligned_meters = align_two_meters(pred, ground)
total_sum = 0.0
sum_samples = 0.0
for chunk in aligned_meters:
chunk.fillna(0, inplace=True)
sum_samples += len(chunk)
total_sum += sum(abs((chunk.iloc[:,0]) - chunk.iloc[:,1]))
if sum_samples == 0:
return None
else:
return total_sum / sum_samples
def recall(tp,fn):
return tp/float(tp+fn)
def precision(tp,fp):
return tp/float(tp+fp)
def f1(prec,rec):
return 2 * (prec*rec) / float(prec+rec)
def accuracy(tp, tn, p, n):
return (tp + tn) / float(p + n)
================================================
FILE: GRU/redd-test.py
================================================
from __future__ import print_function, division
import time
from matplotlib import rcParams
import matplotlib.pyplot as plt
from nilmtk import DataSet, TimeFrame, MeterGroup, HDFDataStore
from nilmtk.elecmeter import ElecMeterID
import metrics
from grudisaggregator import GRUDisaggregator
print("========== OPEN DATASETS ============")
train = DataSet('redd.h5')
train.set_window(end="30-4-2011")
test = DataSet('redd.h5')
test.set_window(start="30-4-2011")
train_building = 1
test_building = 1
sample_period = 6
meter_key = 'fridge'
train_elec = train.buildings[train_building].elec
test_elec = test.buildings[test_building].elec
train_meter = train_elec.submeters()[meter_key]
train_mains = train_elec.mains().all_meters()[0]
test_mains = test_elec.mains().all_meters()[0]
gru = GRUDisaggregator()
start = time.time()
print("========== TRAIN ============")
epochs = 0
for i in range(3):
gru.train(train_mains, train_meter, epochs=5, sample_period=sample_period)
epochs += 5
gru.export_model("REDD-GRU-h{}-{}-{}epochs.h5".format(train_building,
meter_key,
epochs))
print("CHECKPOINT {}".format(epochs))
end = time.time()
print("Train =", end-start, "seconds.")
print("========== DISAGGREGATE ============")
disag_filename = 'disag-out.h5'
output = HDFDataStore(disag_filename, 'w')
gru.disaggregate(test_mains, output, train_meter, sample_period=sample_period)
output.close()
print("========== RESULTS ============")
result = DataSet(disag_filename)
res_elec = result.buildings[test_building].elec
rpaf = metrics.recall_precision_accuracy_f1(res_elec[meter_key], test_elec[meter_key])
print("============ Recall: {}".format(rpaf[0]))
print("============ Precision: {}".format(rpaf[1]))
print("============ Accuracy: {}".format(rpaf[2]))
print("============ F1 Score: {}".format(rpaf[2]))
print("============ Relative error in total energy: {}".format(metrics.relative_error_total_energy(res_elec[meter_key], test_elec[meter_key])))
print("============ Mean absolute error(in Watts): {}".format(metrics.mean_absolute_error(res_elec[meter_key], test_elec[meter_key])))
================================================
FILE: GRU/ukdale-test.py
================================================
from __future__ import print_function, division
import time
from matplotlib import rcParams
import matplotlib.pyplot as plt
from nilmtk import DataSet, TimeFrame, MeterGroup, HDFDataStore
from grudisaggregator import GRUDisaggregator
import metrics
print("========== OPEN DATASETS ============")
train = DataSet('ukdale.h5')
test = DataSet('ukdale.h5')
train.set_window(start="13-4-2013", end="1-1-2014")
test.set_window(start="1-1-2014", end="30-3-2014")
train_building = 1
test_building = 1
sample_period = 6
meter_key = 'microwave'
train_elec = train.buildings[train_building].elec
test_elec = test.buildings[test_building].elec
train_meter = train_elec.submeters()[meter_key]
train_mains = train_elec.mains()
test_mains = test_elec.mains()
gru = GRUDisaggregator()
start = time.time()
print("========== TRAIN ============")
epochs = 0
for i in range(3):
gru.train(train_mains, train_meter, epochs=5, sample_period=sample_period)
epochs += 5
gru.export_model("UKDALE-GRU-h{}-{}-{}epochs.h5".format(train_building,
meter_key,
epochs))
print("CHECKPOINT {}".format(epochs))
end = time.time()
print("Train =", end-start, "seconds.")
print("========== DISAGGREGATE ============")
disag_filename = "disag-out.h5"
output = HDFDataStore(disag_filename, 'w')
gru.disaggregate(test_mains, output, train_meter, sample_period=sample_period)
output.close()
print("========== RESULTS ============")
result = DataSet(disag_filename)
res_elec = result.buildings[test_building].elec
rpaf = metrics.recall_precision_accuracy_f1(res_elec[meter_key], test_elec[meter_key])
print("============ Recall: {}".format(rpaf[0]))
print("============ Precision: {}".format(rpaf[1]))
print("============ Accuracy: {}".format(rpaf[2]))
print("============ F1 Score: {}".format(rpaf[2]))
print("============ Relative error in total energy: {}".format(metrics.relative_error_total_energy(res_elec[meter_key], test_elec[meter_key])))
print("============ Mean absolute error(in Watts): {}".format(metrics.mean_absolute_error(res_elec[meter_key], test_elec[meter_key])))
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Odysseas (Ody) Krystalakos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# neural-disaggregator
Implementations of NILM disaggegregators using Neural Networks, using [NILMTK](https://github.com/NILMTK/NILMTK) and Keras.
Most of the architectures are based on [Neural NILM: Deep Neural Networks Applied to Energy Disaggregation](https://arxiv.org/pdf/1507.06594.pdf) by Jack Kelly and William Knottenbelt.
The implemented models are:
- Denoising autoencoder (DAE) as mentioned in [Neural NILM](https://arxiv.org/pdf/1507.06594.pdf) (see [example](https://github.com/OdysseasKr/neural-disaggregator/blob/master/DAE/DAE-example.ipynb))
- Recurrent network with LSTM neurons as mentioned in [Neural NILM](https://arxiv.org/pdf/1507.06594.pdf) (see [example](https://github.com/OdysseasKr/neural-disaggregator/tree/master/RNN/RNN-example.ipynb))
- Recurrent network with GRU. A variation of the LSTM network in order to compare the two types of RNNs (see [example](https://github.com/OdysseasKr/neural-disaggregator/blob/master/GRU/GRU-example.ipynb))
- Window GRU. A variation of the GRU network in that uses a window of data as input. As described in [Sliding Window Approach for Online Energy Disaggregation Using Artificial Neural Networks](https://dl.acm.org/citation.cfm?id=3201011) by Krystalakos, Nalmpantis and Vrakas (see [example](https://github.com/OdysseasKr/neural-disaggregator/blob/master/WindowGRU/Window-GRU-example.ipynb))
- Short Sequence to Point Network based on the architecture in [original paper](https://arxiv.org/abs/1612.09106) (see [example](https://github.com/OdysseasKr/neural-disaggregator/blob/master/ShortSeq2Point/ShortSeq2Point-example.ipynb))
_Note: If you are looking for the LookbackGRU folder, it has been moved to http://github.com/OdysseasKr/online-nilm. I try to keep this repo clean by using the same train and test sets for all architectures._
================================================
FILE: RNN/README.md
================================================
# Recurrent(LSTM) Energy Disaggregator
As described in:
[Neural NILM: Deep Neural Networks Applied to Energy Disaggregation](https://arxiv.org/pdf/1507.06594.pdf) by Jack Kelly and William Knottenbelt
See example experiment [here](https://github.com/OdysseasKr/neural-disaggregator/blob/master/RNN/RNN-example.ipynb).
================================================
FILE: RNN/RNN-example.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to use the RNN Disaggregator with NILMTK\n",
"\n",
"This is an example on how to train and use the Recurrent Network (RNN) disaggregator on the [REDD](http://redd.csail.mit.edu/) dataset using [NILMTK](https://github.com/nilmtk/NILMTK/).\n",
"\n",
"This network was described in the [Neural NILM](https://arxiv.org/pdf/1507.06594.pdf) paper."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First of all, we need to train the RNNDisaggregator using the train data. For this example, both train and test data are consumption data of the microwave of the first REDD building."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import warnings; warnings.filterwarnings('ignore')\n",
"\n",
"from nilmtk import DataSet\n",
"train = DataSet('redd.h5')\n",
"train.set_window(end=\"30-4-2011\") #Use data only until 4/30/2011\n",
"train_elec = train.buildings[1].elec"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we need to define the disaggregator model."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Using TensorFlow backend.\n"
]
}
],
"source": [
"from rnndisaggregator import RNNDisaggregator\n",
"rnn = RNNDisaggregator()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then train the model. We need to input the train data as well as their sample period. Also, we need to pass the desired number of training epochs. Finally, save the model for later use."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Epoch 1/5\n",
"1003066/1003066 [==============================] - 127s - loss: 6.1677e-04 \n",
"Epoch 2/5\n",
"1003066/1003066 [==============================] - 126s - loss: 6.1496e-04 \n",
"Epoch 3/5\n",
"1003066/1003066 [==============================] - 118s - loss: 6.1347e-04 \n",
"Epoch 4/5\n",
"1003066/1003066 [==============================] - 114s - loss: 6.1133e-04 \n",
"Epoch 5/5\n",
"1003066/1003066 [==============================] - 114s - loss: 6.1094e-04 \n"
]
}
],
"source": [
"train_mains = train_elec.mains().all_meters()[0] # The aggregated meter that provides the input\n",
"train_meter = train_elec.submeters()['microwave'] # The microwave meter that is used as a training target\n",
"\n",
"rnn.train(train_mains, train_meter, epochs=5, sample_period=1)\n",
"rnn.export_model(\"model-redd5.h5\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that the model is trained, we can use it to disaggregate energy data. Let's test it on the rest of the data from building 1.\n",
"\n",
"First we use the model to predict the microwave consumption. The results are saved automatically in a .h5 datastore."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"New sensible chunk: 121482\n",
"New sensible chunk: 112661\n",
"New sensible chunk: 87770\n",
"New sensible chunk: 54084\n",
"New sensible chunk: 2660\n",
"New sensible chunk: 33513\n",
"New sensible chunk: 138535\n",
"New sensible chunk: 32514\n",
"New sensible chunk: 27255\n",
"New sensible chunk: 34833\n",
"New sensible chunk: 100831\n"
]
}
],
"source": [
"test = DataSet('redd.h5')\n",
"test.set_window(start=\"30-4-2011\")\n",
"test_elec = test.buildings[1].elec\n",
"test_mains = test_elec.mains().all_meters()[0]\n",
"\n",
"disag_filename = 'disag-out.h5' # The filename of the resulting datastore\n",
"from nilmtk.datastore import HDFDataStore\n",
"output = HDFDataStore(disag_filename, 'w')\n",
"\n",
"# test_mains: The aggregated signal meter\n",
"# output: The output datastore\n",
"# train_meter: This is used in order to copy the metadata of the train meter into the datastore\n",
"rnn.disaggregate(test_mains, output, train_meter, sample_period=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's plot the results and compare them to the ground truth signal.\n",
"\n",
"**Note:** Calling plot this way, downsamples the signal to reduce computing time. To plot the entire signal call\n",
"```\n",
"predicted.power_series_all_data().plot()\n",
"ground_truth.power_series_all_data().plot()\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x7f68201d06d0>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"result = DataSet(disag_filename)\n",
"res_elec = result.buildings[1].elec\n",
"predicted = res_elec['microwave']\n",
"ground_truth = test_elec['microwave']\n",
"\n",
"import matplotlib.pyplot as plt\n",
"predicted.plot()\n",
"ground_truth.plot()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally let's see the metric results."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"============ Recall: 0.997835349341\n",
"============ Precision: 0.742378777703\n",
"============ Accuracy: 0.741308963402\n",
"============ F1 Score: 0.851357054837\n",
"============ Relative error in total energy: 0.871686427835\n",
"============ Mean absolute error(in Watts): 32.2338755931\n"
]
}
],
"source": [
"import metrics\n",
"rpaf = metrics.recall_precision_accuracy_f1(predicted, ground_truth)\n",
"print(\"============ Recall: {}\".format(rpaf[0]))\n",
"print(\"============ Precision: {}\".format(rpaf[1]))\n",
"print(\"============ Accuracy: {}\".format(rpaf[2]))\n",
"print(\"============ F1 Score: {}\".format(rpaf[3]))\n",
"\n",
"print(\"============ Relative error in total energy: {}\".format(metrics.relative_error_total_energy(predicted, ground_truth)))\n",
"print(\"============ Mean absolute error(in Watts): {}\".format(metrics.mean_absolute_error(predicted, ground_truth)))"
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python [conda env:nilmtk-env]",
"language": "python",
"name": "conda-env-nilmtk-env-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: RNN/metrics.py
================================================
from nilmtk.electric import align_two_meters
import numpy as np
def tp_tn_fp_fn(states_pred, states_ground):
tp = np.sum(np.logical_and(states_pred == 1, states_ground == 1))
fp = np.sum(np.logical_and(states_pred == 1, states_ground == 0))
fn = np.sum(np.logical_and(states_pred == 0, states_ground == 1))
tn = np.sum(np.logical_and(states_pred == 0, states_ground == 0))
return tp, tn, fp, fn
def recall_precision_accuracy_f1(pred, ground):
aligned_meters = align_two_meters(pred, ground)
threshold = ground.on_power_threshold()
chunk_results = []
sum_samples = 0.0
for chunk in aligned_meters:
sum_samples += len(chunk)
pr = np.array([0 if (p)<threshold else 1 for p in chunk.iloc[:,0]])
gr = np.array([0 if p<threshold else 1 for p in chunk.iloc[:,1]])
tp, tn, fp, fn = tp_tn_fp_fn(pr,gr)
p = sum(pr)
n = len(pr) - p
chunk_results.append([tp,tn,fp,fn,p,n])
if sum_samples == 0:
return None
else:
[tp,tn,fp,fn,p,n] = np.sum(chunk_results, axis=0)
res_recall = recall(tp,fn)
res_precision = precision(tp,fp)
res_f1 = f1(res_precision,res_recall)
res_accuracy = accuracy(tp,tn,p,n)
return (res_recall,res_precision,res_accuracy,res_f1)
def relative_error_total_energy(pred, ground):
aligned_meters = align_two_meters(pred, ground)
chunk_results = []
sum_samples = 0.0
for chunk in aligned_meters:
chunk.fillna(0, inplace=True)
sum_samples += len(chunk)
E_pred = sum(chunk.iloc[:,0])
E_ground = sum(chunk.iloc[:,1])
chunk_results.append([
E_pred,
E_ground
])
if sum_samples == 0:
return None
else:
[E_pred, E_ground] = np.sum(chunk_results,axis=0)
return abs(E_pred - E_ground) / float(max(E_pred,E_ground))
def mean_absolute_error(pred, ground):
aligned_meters = align_two_meters(pred, ground)
total_sum = 0.0
sum_samples = 0.0
for chunk in aligned_meters:
chunk.fillna(0, inplace=True)
sum_samples += len(chunk)
total_sum += sum(abs((chunk.iloc[:,0]) - chunk.iloc[:,1]))
if sum_samples == 0:
return None
else:
return total_sum / sum_samples
def recall(tp,fn):
return tp/float(tp+fn)
def precision(tp,fp):
return tp/float(tp+fp)
def f1(prec,rec):
return 2 * (prec*rec) / float(prec+rec)
def accuracy(tp, tn, p, n):
return (tp + tn) / float(p + n)
================================================
FILE: RNN/redd-test.py
================================================
from __future__ import print_function, division
import time
from matplotlib import rcParams
import matplotlib.pyplot as plt
from nilmtk import DataSet, TimeFrame, MeterGroup, HDFDataStore
from nilmtk.elecmeter import ElecMeterID
import metrics
from rnndisaggregator import RNNDisaggregator
print("========== OPEN DATASETS ============")
train = DataSet('redd.h5')
train.set_window(end="30-4-2011")
test = DataSet('redd.h5')
test.set_window(start="30-4-2011")
train_building = 1
test_building = 1
sample_period = 6
meter_key = 'fridge'
train_elec = train.buildings[train_building].elec
test_elec = test.buildings[test_building].elec
train_meter = train_elec.submeters()[meter_key]
train_mains = train_elec.mains().all_meters()[0]
test_mains = test_elec.mains().all_meters()[0]
rnn = RNNDisaggregator()
start = time.time()
print("========== TRAIN ============")
epochs = 0
for i in range(3):
rnn.train(train_mains, train_meter, epochs=5, sample_period=sample_period)
epochs += 5
rnn.export_model("REDD-RNN-h{}-{}-{}epochs.h5".format(train_building,
meter_key,
epochs))
print("CHECKPOINT {}".format(epochs))
end = time.time()
print("Train =", end-start, "seconds.")
print("========== DISAGGREGATE ============")
disag_filename = 'disag-out.h5'
output = HDFDataStore(disag_filename, 'w')
rnn.disaggregate(test_mains, output, train_meter, sample_period=sample_period)
output.close()
print("========== RESULTS ============")
result = DataSet(disag_filename)
res_elec = result.buildings[test_building].elec
rpaf = metrics.recall_precision_accuracy_f1(res_elec[meter_key], test_elec[meter_key])
print("============ Recall: {}".format(rpaf[0]))
print("============ Precision: {}".format(rpaf[1]))
print("============ Accuracy: {}".format(rpaf[2]))
print("============ F1 Score: {}".format(rpaf[2]))
print("============ Relative error in total energy: {}".format(metrics.relative_error_total_energy(res_elec[meter_key], test_elec[meter_key])))
print("============ Mean absolute error(in Watts): {}".format(metrics.mean_absolute_error(res_elec[meter_key], test_elec[meter_key])))
================================================
FILE: RNN/rnndisaggregator.py
================================================
from __future__ import print_function, division
import random
import sys
from matplotlib import rcParams
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import h5py
from keras.models import load_model
from keras.models import Sequential
from keras.layers import Dense, Conv1D, LSTM, Bidirectional, Dropout
from keras.utils import plot_model
from nilmtk.utils import find_nearest
from nilmtk.feature_detectors import cluster
from nilmtk.legacy.disaggregate import Disaggregator
from nilmtk.datastore import HDFDataStore
class RNNDisaggregator(Disaggregator):
'''Attempt to create a RNN Disaggregator
Attributes
----------
model : keras Sequential model
mmax : the maximum value of the aggregate data
MIN_CHUNK_LENGTH : int
the minimum length of an acceptable chunk
'''
def __init__(self):
'''Initialize disaggregator
'''
self.MODEL_NAME = "LSTM"
self.mmax = None
self.MIN_CHUNK_LENGTH = 100
self.model = self._create_model()
def train(self, mains, meter, epochs=1, batch_size=128, **load_kwargs):
'''Train
Parameters
----------
mains : a nilmtk.ElecMeter object for the aggregate data
meter : a nilmtk.ElecMeter object for the meter data
epochs : number of epochs to train
batch_size : size of batch used for training
**load_kwargs : keyword arguments passed to `meter.power_series()`
'''
main_power_series = mains.power_series(**load_kwargs)
meter_power_series = meter.power_series(**load_kwargs)
# Train chunks
run = True
mainchunk = next(main_power_series)
meterchunk = next(meter_power_series)
if self.mmax == None:
self.mmax = mainchunk.max()
while(run):
mainchunk = self._normalize(mainchunk, self.mmax)
meterchunk = self._normalize(meterchunk, self.mmax)
self.train_on_chunk(mainchunk, meterchunk, epochs, batch_size)
try:
mainchunk = next(main_power_series)
meterchunk = next(meter_power_series)
except:
run = False
def train_on_chunk(self, mainchunk, meterchunk, epochs, batch_size):
'''Train using only one chunk
Parameters
----------
mainchunk : chunk of site meter
meterchunk : chunk of appliance
epochs : number of epochs for training
batch_size : size of batch used for training
'''
# Replace NaNs with 0s
mainchunk.fillna(0, inplace=True)
meterchunk.fillna(0, inplace=True)
ix = mainchunk.index.intersection(meterchunk.index)
mainchunk = np.array(mainchunk[ix])
meterchunk = np.array(meterchunk[ix])
mainchunk = np.reshape(mainchunk, (mainchunk.shape[0],1,1))
self.model.fit(mainchunk, meterchunk, epochs=epochs, batch_size=batch_size, shuffle=True)
def train_across_buildings(self, mainlist, meterlist, epochs=1, batch_size=128, **load_kwargs):
'''Train using data from multiple buildings
Parameters
----------
mainlist : a list of nilmtk.ElecMeter objects for the aggregate data of each building
meterlist : a list of nilmtk.ElecMeter objects for the meter data of each building
batch_size : size of batch used for training
epochs : number of epochs to train
**load_kwargs : keyword arguments passed to `meter.power_series()`
'''
assert len(mainlist) == len(meterlist), "Number of main and meter channels should be equal"
num_meters = len(mainlist)
mainps = [None] * num_meters
meterps = [None] * num_meters
mainchunks = [None] * num_meters
meterchunks = [None] * num_meters
# Get generators of timeseries
for i,m in enumerate(mainlist):
mainps[i] = m.power_series(**load_kwargs)
for i,m in enumerate(meterlist):
meterps[i] = m.power_series(**load_kwargs)
# Get a chunk of data
for i in range(num_meters):
mainchunks[i] = next(mainps[i])
meterchunks[i] = next(meterps[i])
if self.mmax == None:
self.mmax = max([m.max() for m in mainchunks])
run = True
while(run):
# Normalize and train
mainchunks = [self._normalize(m, self.mmax) for m in mainchunks]
meterchunks = [self._normalize(m, self.mmax) for m in meterchunks]
self.train_across_buildings_chunk(mainchunks, meterchunks, epochs, batch_size)
# If more chunks, repeat
try:
for i in range(num_meters):
mainchunks[i] = next(mainps[i])
meterchunks[i] = next(meterps[i])
except:
run = False
def train_across_buildings_chunk(self, mainchunks, meterchunks, epochs, batch_size):
'''Train using only one chunk of data. This chunk consists of data from
all buildings.
Parameters
----------
mainchunk : chunk of site meter
meterchunk : chunk of appliance
epochs : number of epochs for training
batch_size : size of batch used for training
'''
num_meters = len(mainchunks)
batch_size = int(batch_size/num_meters)
num_of_batches = [None] * num_meters
# Find common parts of timeseries
for i in range(num_meters):
mainchunks[i].fillna(0, inplace=True)
meterchunks[i].fillna(0, inplace=True)
ix = mainchunks[i].index.intersection(meterchunks[i].index)
m1 = mainchunks[i]
m2 = meterchunks[i]
mainchunks[i] = m1[ix]
meterchunks[i] = m2[ix]
num_of_batches[i] = int(len(ix)/batch_size) - 1
for e in range(epochs): # Iterate for every epoch
print(e)
batch_indexes = list(range(min(num_of_batches)))
random.shuffle(batch_indexes)
for bi, b in enumerate(batch_indexes): # Iterate for every batch
print("Batch {} of {}".format(bi,num_of_batches), end="\r")
sys.stdout.flush()
X_batch = np.empty((batch_size*num_meters, 1, 1))
Y_batch = np.empty((batch_size*num_meters, 1))
# Create a batch out of data from all buildings
for i in range(num_meters):
mainpart = mainchunks[i]
meterpart = meterchunks[i]
mainpart = mainpart[b*batch_size:(b+1)*batch_size]
meterpart = meterpart[b*batch_size:(b+1)*batch_size]
X = np.reshape(mainpart, (batch_size, 1, 1))
Y = np.reshape(meterpart, (batch_size, 1))
X_batch[i*batch_size:(i+1)*batch_size] = np.array(X)
Y_batch[i*batch_size:(i+1)*batch_size] = np.array(Y)
# Shuffle data
p = np.random.permutation(len(X_batch))
X_batch, Y_batch = X_batch[p], Y_batch[p]
# Train model
self.model.train_on_batch(X_batch, Y_batch)
print("\n")
def disaggregate(self, mains, output_datastore, met
gitextract_a130ckvp/ ├── DAE/ │ ├── DAE-example.ipynb │ ├── README.md │ ├── daedisaggregator.py │ ├── metrics.py │ ├── redd-test.py │ └── ukdale-test.py ├── GRU/ │ ├── GRU-example.ipynb │ ├── README.md │ ├── grudisaggregator.py │ ├── metrics.py │ ├── redd-test.py │ └── ukdale-test.py ├── LICENSE ├── README.md ├── RNN/ │ ├── README.md │ ├── RNN-example.ipynb │ ├── metrics.py │ ├── redd-test.py │ ├── rnndisaggregator.py │ └── ukdale-test.py ├── ShortSeq2Point/ │ ├── README.md │ ├── ShortSeq2Point-example.ipynb │ ├── metrics.py │ ├── redd-test.py │ ├── shortseq2pointdisaggregator.py │ └── ukdale-test.py ├── WindowGRU/ │ ├── README.md │ ├── Window-GRU-example.ipynb │ ├── metrics.py │ ├── redd-test.py │ ├── ukdale-test.py │ └── windowgrudisaggregator.py └── requirements.txt
SYMBOL INDEX (105 symbols across 10 files)
FILE: DAE/daedisaggregator.py
class DAEDisaggregator (line 22) | class DAEDisaggregator(Disaggregator):
method __init__ (line 36) | def __init__(self, sequence_length):
method train (line 50) | def train(self, mains, meter, epochs=1, batch_size=16, **load_kwargs):
method train_on_chunk (line 82) | def train_on_chunk(self, mainchunk, meterchunk, epochs, batch_size):
method train_across_buildings (line 114) | def train_across_buildings(self, mainlist, meterlist, epochs=1, batch_...
method train_across_buildings_chunk (line 149) | def train_across_buildings_chunk(self, mainchunks, meterchunks, epochs...
method disaggregate (line 194) | def disaggregate(self, mains, output_datastore, meter_metadata, **load...
method disaggregate_chunk (line 255) | def disaggregate_chunk(self, mains):
method import_model (line 286) | def import_model(self, filename):
method export_model (line 300) | def export_model(self, filename):
method _normalize (line 312) | def _normalize(self, chunk, mmax):
method _denormalize (line 325) | def _denormalize(self, chunk, mmax):
method _create_model (line 339) | def _create_model(self, sequence_len):
FILE: DAE/metrics.py
function tp_tn_fp_fn (line 4) | def tp_tn_fp_fn(states_pred, states_ground):
function recall_precision_accuracy_f1 (line 11) | def recall_precision_accuracy_f1(pred, ground):
function relative_error_total_energy (line 39) | def relative_error_total_energy(pred, ground):
function mean_absolute_error (line 59) | def mean_absolute_error(pred, ground):
function recall (line 73) | def recall(tp,fn):
function precision (line 76) | def precision(tp,fp):
function f1 (line 79) | def f1(prec,rec):
function accuracy (line 82) | def accuracy(tp, tn, p, n):
FILE: GRU/grudisaggregator.py
class GRUDisaggregator (line 22) | class GRUDisaggregator(Disaggregator):
method __init__ (line 34) | def __init__(self):
method train (line 42) | def train(self, mains, meter, epochs=1, batch_size=128, **load_kwargs):
method train_on_chunk (line 75) | def train_on_chunk(self, mainchunk, meterchunk, epochs, batch_size):
method train_across_buildings (line 97) | def train_across_buildings(self, mainlist, meterlist, epochs=1, batch_...
method train_across_buildings_chunk (line 148) | def train_across_buildings_chunk(self, mainchunks, meterchunks, epochs...
method disaggregate (line 205) | def disaggregate(self, mains, output_datastore, meter_metadata, **load...
method disaggregate_chunk (line 266) | def disaggregate_chunk(self, mains):
method import_model (line 294) | def import_model(self, filename):
method export_model (line 308) | def export_model(self, filename):
method _normalize (line 320) | def _normalize(self, chunk, mmax):
method _denormalize (line 333) | def _denormalize(self, chunk, mmax):
method _create_model (line 347) | def _create_model(self):
FILE: GRU/metrics.py
function tp_tn_fp_fn (line 4) | def tp_tn_fp_fn(states_pred, states_ground):
function recall_precision_accuracy_f1 (line 11) | def recall_precision_accuracy_f1(pred, ground):
function relative_error_total_energy (line 39) | def relative_error_total_energy(pred, ground):
function mean_absolute_error (line 59) | def mean_absolute_error(pred, ground):
function recall (line 73) | def recall(tp,fn):
function precision (line 76) | def precision(tp,fp):
function f1 (line 79) | def f1(prec,rec):
function accuracy (line 82) | def accuracy(tp, tn, p, n):
FILE: RNN/metrics.py
function tp_tn_fp_fn (line 4) | def tp_tn_fp_fn(states_pred, states_ground):
function recall_precision_accuracy_f1 (line 11) | def recall_precision_accuracy_f1(pred, ground):
function relative_error_total_energy (line 39) | def relative_error_total_energy(pred, ground):
function mean_absolute_error (line 59) | def mean_absolute_error(pred, ground):
function recall (line 73) | def recall(tp,fn):
function precision (line 76) | def precision(tp,fp):
function f1 (line 79) | def f1(prec,rec):
function accuracy (line 82) | def accuracy(tp, tn, p, n):
FILE: RNN/rnndisaggregator.py
class RNNDisaggregator (line 22) | class RNNDisaggregator(Disaggregator):
method __init__ (line 34) | def __init__(self):
method train (line 42) | def train(self, mains, meter, epochs=1, batch_size=128, **load_kwargs):
method train_on_chunk (line 75) | def train_on_chunk(self, mainchunk, meterchunk, epochs, batch_size):
method train_across_buildings (line 97) | def train_across_buildings(self, mainlist, meterlist, epochs=1, batch_...
method train_across_buildings_chunk (line 148) | def train_across_buildings_chunk(self, mainchunks, meterchunks, epochs...
method disaggregate (line 206) | def disaggregate(self, mains, output_datastore, meter_metadata, **load...
method disaggregate_chunk (line 267) | def disaggregate_chunk(self, mains):
method import_model (line 295) | def import_model(self, filename):
method export_model (line 309) | def export_model(self, filename):
method _normalize (line 321) | def _normalize(self, chunk, mmax):
method _denormalize (line 334) | def _denormalize(self, chunk, mmax):
method _create_model (line 348) | def _create_model(self):
FILE: ShortSeq2Point/metrics.py
function tp_tn_fp_fn (line 4) | def tp_tn_fp_fn(states_pred, states_ground):
function recall_precision_accuracy_f1 (line 11) | def recall_precision_accuracy_f1(pred, ground):
function relative_error_total_energy (line 39) | def relative_error_total_energy(pred, ground):
function mean_absolute_error (line 59) | def mean_absolute_error(pred, ground):
function recall (line 73) | def recall(tp,fn):
function precision (line 76) | def precision(tp,fp):
function f1 (line 79) | def f1(prec,rec):
function accuracy (line 82) | def accuracy(tp, tn, p, n):
FILE: ShortSeq2Point/shortseq2pointdisaggregator.py
class ShortSeq2PointDisaggregator (line 22) | class ShortSeq2PointDisaggregator(Disaggregator):
method __init__ (line 34) | def __init__(self, window_size=100):
method train (line 43) | def train(self, mains, meter, epochs=1, batch_size=128, **load_kwargs):
method train_on_chunk (line 76) | def train_on_chunk(self, mainchunk, meterchunk, epochs, batch_size):
method train_across_buildings (line 101) | def train_across_buildings(self, mainlist, meterlist, epochs=1, batch_...
method train_across_buildings_chunk (line 152) | def train_across_buildings_chunk(self, mainchunks, meterchunks, epochs...
method disaggregate (line 214) | def disaggregate(self, mains, output_datastore, meter_metadata, **load...
method disaggregate_chunk (line 275) | def disaggregate_chunk(self, mains):
method import_model (line 306) | def import_model(self, filename):
method export_model (line 320) | def export_model(self, filename):
method _normalize (line 332) | def _normalize(self, chunk, mmax):
method _denormalize (line 345) | def _denormalize(self, chunk, mmax):
method _create_model (line 359) | def _create_model(self):
FILE: WindowGRU/metrics.py
function tp_tn_fp_fn (line 4) | def tp_tn_fp_fn(states_pred, states_ground):
function recall_precision_accuracy_f1 (line 11) | def recall_precision_accuracy_f1(pred, ground):
function relative_error_total_energy (line 39) | def relative_error_total_energy(pred, ground):
function mean_absolute_error (line 59) | def mean_absolute_error(pred, ground):
function recall (line 73) | def recall(tp,fn):
function precision (line 76) | def precision(tp,fp):
function f1 (line 79) | def f1(prec,rec):
function accuracy (line 82) | def accuracy(tp, tn, p, n):
FILE: WindowGRU/windowgrudisaggregator.py
class WindowGRUDisaggregator (line 22) | class WindowGRUDisaggregator(Disaggregator):
method __init__ (line 34) | def __init__(self, window_size=100):
method train (line 43) | def train(self, mains, meter, epochs=1, batch_size=128, **load_kwargs):
method train_on_chunk (line 76) | def train_on_chunk(self, mainchunk, meterchunk, epochs, batch_size):
method train_across_buildings (line 101) | def train_across_buildings(self, mainlist, meterlist, epochs=1, batch_...
method train_across_buildings_chunk (line 152) | def train_across_buildings_chunk(self, mainchunks, meterchunks, epochs...
method disaggregate (line 214) | def disaggregate(self, mains, output_datastore, meter_metadata, **load...
method disaggregate_chunk (line 275) | def disaggregate_chunk(self, mains):
method import_model (line 306) | def import_model(self, filename):
method export_model (line 320) | def export_model(self, filename):
method _normalize (line 332) | def _normalize(self, chunk, mmax):
method _denormalize (line 345) | def _denormalize(self, chunk, mmax):
method _create_model (line 359) | def _create_model(self):
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (344K chars).
[
{
"path": "DAE/DAE-example.ipynb",
"chars": 65174,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# How to use the Denoising Autoenco"
},
{
"path": "DAE/README.md",
"chars": 327,
"preview": "# Denoising Autoencoder Energy Disaggregator\n\nAs described in:\n\n[Neural NILM: Deep Neural Networks Applied to Energy Dis"
},
{
"path": "DAE/daedisaggregator.py",
"chars": 12780,
"preview": "from __future__ import print_function, division\nimport random\nimport sys\n\nfrom matplotlib import rcParams\nimport matplot"
},
{
"path": "DAE/metrics.py",
"chars": 2575,
"preview": "from nilmtk.electric import align_two_meters\nimport numpy as np\n\ndef tp_tn_fp_fn(states_pred, states_ground):\n tp = n"
},
{
"path": "DAE/redd-test.py",
"chars": 1839,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "DAE/ukdale-test.py",
"chars": 2198,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "GRU/GRU-example.ipynb",
"chars": 38286,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# How to use the RNN Autoencoder wi"
},
{
"path": "GRU/README.md",
"chars": 345,
"preview": "# Recurrent (GRU) Network Energy Disaggregator\n\nAn attempt to create a \"lightweight\" recurrent network for energy disagg"
},
{
"path": "GRU/grudisaggregator.py",
"chars": 12895,
"preview": "from __future__ import print_function, division\nimport random\nimport sys\n\nfrom matplotlib import rcParams\nimport matplot"
},
{
"path": "GRU/metrics.py",
"chars": 2575,
"preview": "from nilmtk.electric import align_two_meters\nimport numpy as np\n\ndef tp_tn_fp_fn(states_pred, states_ground):\n tp = n"
},
{
"path": "GRU/redd-test.py",
"chars": 2207,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "GRU/ukdale-test.py",
"chars": 2178,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "LICENSE",
"chars": 1083,
"preview": "MIT License\n\nCopyright (c) 2021 Odysseas (Ody) Krystalakos\n\nPermission is hereby granted, free of charge, to any person "
},
{
"path": "README.md",
"chars": 1815,
"preview": "# neural-disaggregator\n\nImplementations of NILM disaggegregators using Neural Networks, using [NILMTK](https://github.co"
},
{
"path": "RNN/README.md",
"chars": 321,
"preview": "# Recurrent(LSTM) Energy Disaggregator\n\nAs described in:\n\n[Neural NILM: Deep Neural Networks Applied to Energy Disaggreg"
},
{
"path": "RNN/RNN-example.ipynb",
"chars": 38944,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# How to use the RNN Disaggregator "
},
{
"path": "RNN/metrics.py",
"chars": 2575,
"preview": "from nilmtk.electric import align_two_meters\nimport numpy as np\n\ndef tp_tn_fp_fn(states_pred, states_ground):\n tp = n"
},
{
"path": "RNN/redd-test.py",
"chars": 2207,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "RNN/rnndisaggregator.py",
"chars": 12917,
"preview": "from __future__ import print_function, division\nimport random\nimport sys\n\nfrom matplotlib import rcParams\nimport matplot"
},
{
"path": "RNN/ukdale-test.py",
"chars": 2176,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "ShortSeq2Point/README.md",
"chars": 490,
"preview": "# Short Sequence to Point Energy Disaggregator\n\nBased on [Sequence-to-point learning with neural networks for nonintrusi"
},
{
"path": "ShortSeq2Point/ShortSeq2Point-example.ipynb",
"chars": 41891,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# How to use the Short Sequence-to-"
},
{
"path": "ShortSeq2Point/metrics.py",
"chars": 2575,
"preview": "from nilmtk.electric import align_two_meters\nimport numpy as np\n\ndef tp_tn_fp_fn(states_pred, states_ground):\n tp = n"
},
{
"path": "ShortSeq2Point/redd-test.py",
"chars": 2290,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "ShortSeq2Point/shortseq2pointdisaggregator.py",
"chars": 14117,
"preview": "from __future__ import print_function, division\nimport random\nimport sys\n\nfrom matplotlib import rcParams\nimport matplot"
},
{
"path": "ShortSeq2Point/ukdale-test.py",
"chars": 2249,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "WindowGRU/README.md",
"chars": 482,
"preview": "# GRU with Sliding Window Disaggregator\n\nA Recurrent Neural Network disaggregator that uses a window of data as input in"
},
{
"path": "WindowGRU/Window-GRU-example.ipynb",
"chars": 40645,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# How to use the Sliding Window GRU"
},
{
"path": "WindowGRU/metrics.py",
"chars": 2575,
"preview": "from nilmtk.electric import align_two_meters\nimport numpy as np\n\ndef tp_tn_fp_fn(states_pred, states_ground):\n tp = n"
},
{
"path": "WindowGRU/redd-test.py",
"chars": 2280,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "WindowGRU/ukdale-test.py",
"chars": 2234,
"preview": "from __future__ import print_function, division\nimport time\n\nfrom matplotlib import rcParams\nimport matplotlib.pyplot as"
},
{
"path": "WindowGRU/windowgrudisaggregator.py",
"chars": 13875,
"preview": "from __future__ import print_function, division\nimport random\nimport sys\n\nfrom matplotlib import rcParams\nimport matplot"
},
{
"path": "requirements.txt",
"chars": 1406,
"preview": "attrs==19.3.0\nbackcall==0.1.0\nbleach==3.1.0\nBottleneck==1.3.1\ncertifi==2019.11.28\ncoverage==4.5.4\ncycler==0.10.0\nCython="
}
]
About this extraction
This page contains the full source code of the OdysseasKr/neural-disaggregator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (324.7 KB), approximately 161.7k tokens, and a symbol index with 105 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.