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": [ "" ] }, "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)" ] }, "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)" ] }, "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)" ] }, "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.0456918965636\n", "============ Precision: 0.805732687157\n", "============ Accuracy: 0.28334183468\n", "============ F1 Score: 0.0864796608023\n", "============ Relative error in total energy: 0.840686257358\n", "============ Mean absolute error(in Watts): 25.0217610209\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: ShortSeq2Point/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)" ] }, "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: 1.0\n", "============ Precision: 0.742390932564\n", "============ Accuracy: 0.742390932564\n", "============ F1 Score: 0.852151969675\n", "============ Relative error in total energy: 0.854315367543\n", "============ Mean absolute error(in Watts): 30.7409502125\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: WindowGRU/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)