├── environment.yml ├── GRUWithWindow ├── appconf │ ├── fridge.json │ ├── kettle.json │ ├── microwave.json │ ├── dish_washer.json │ └── washing_machine.json ├── model.py ├── README.md ├── metrics.py ├── gen.py └── experiment.py ├── ShortSeq2Point ├── appconf │ ├── fridge.json │ ├── kettle.json │ ├── microwave.json │ ├── dish_washer.json │ └── washing_machine.json ├── README.md ├── model.py ├── metrics.py ├── gen.py └── experiment.py └── README.md /environment.yml: -------------------------------------------------------------------------------- 1 | name: nilm 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - python=3.8 7 | - nilmtk 8 | - keras 9 | prefix: /home/username/miniconda3/envs/nilm 10 | -------------------------------------------------------------------------------- /GRUWithWindow/appconf/fridge.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "fridge", 3 | "nilmtk_key": "fridge", 4 | "memax": 200, 5 | "std": 400, 6 | "mean": 200, 7 | "on_threshold": 50, 8 | "train_buildings": [1,2,4], 9 | "test_building": 5, 10 | "lookback": 50, 11 | "save_path": "experiments/fridge/" 12 | } 13 | -------------------------------------------------------------------------------- /ShortSeq2Point/appconf/fridge.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "fridge", 3 | "nilmtk_key": "fridge", 4 | "memax": 200, 5 | "std": 400, 6 | "mean": 200, 7 | "on_threshold": 50, 8 | "train_buildings": [1,2,4], 9 | "test_building": 5, 10 | "lookback": 50, 11 | "save_path": "experiments/fridge/" 12 | } 13 | -------------------------------------------------------------------------------- /GRUWithWindow/appconf/kettle.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "kettle", 3 | "nilmtk_key": "kettle", 4 | "memax": 3000, 5 | "std": 1000, 6 | "mean": 700, 7 | "on_threshold": 1600, 8 | "train_buildings": [1,2,3,4], 9 | "test_building": 5, 10 | "lookback": 50, 11 | "save_path": "experiments/kettle/" 12 | } 13 | -------------------------------------------------------------------------------- /ShortSeq2Point/appconf/kettle.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "kettle", 3 | "nilmtk_key": "kettle", 4 | "memax": 3000, 5 | "std": 1000, 6 | "mean": 700, 7 | "on_threshold": 1700, 8 | "train_buildings": [1,2,3,4], 9 | "test_building": 5, 10 | "lookback": 100, 11 | "save_path": "experiments/kettle/" 12 | } 13 | -------------------------------------------------------------------------------- /GRUWithWindow/appconf/microwave.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "microwave", 3 | "nilmtk_key": "microwave", 4 | "memax": 3000, 5 | "std": 800, 6 | "mean": 500, 7 | "on_threshold": 200, 8 | "train_buildings": [1,2], 9 | "test_building": 5, 10 | "lookback": 50, 11 | "save_path": "experiments/microwave/" 12 | } 13 | -------------------------------------------------------------------------------- /ShortSeq2Point/appconf/microwave.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "microwave", 3 | "nilmtk_key": "microwave", 4 | "memax": 3000, 5 | "std": 800, 6 | "mean": 500, 7 | "on_threshold": 200, 8 | "train_buildings": [1,2], 9 | "test_building": 5, 10 | "lookback": 50, 11 | "save_path": "experiments/microwave/" 12 | } 13 | -------------------------------------------------------------------------------- /GRUWithWindow/appconf/dish_washer.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "dish_washer", 3 | "nilmtk_key": "dish washer", 4 | "memax": 3000, 5 | "std": 1000, 6 | "mean": 700, 7 | "on_threshold": 10, 8 | "train_buildings": [1,2], 9 | "test_building": 5, 10 | "lookback": 50, 11 | "save_path": "experiments/dish_washer/" 12 | } 13 | -------------------------------------------------------------------------------- /ShortSeq2Point/appconf/dish_washer.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "dish_washer", 3 | "nilmtk_key": "dish washer", 4 | "memax": 3000, 5 | "std": 1000, 6 | "mean": 700, 7 | "on_threshold": 10, 8 | "train_buildings": [1,2], 9 | "test_building": 5, 10 | "lookback": 100, 11 | "save_path": "experiments/dish_washer/" 12 | } 13 | -------------------------------------------------------------------------------- /GRUWithWindow/appconf/washing_machine.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "washing_machine", 3 | "nilmtk_key": "washer dryer", 4 | "memax": 2500, 5 | "std": 700, 6 | "mean":400, 7 | "on_threshold": 20, 8 | "train_buildings": [1,5], 9 | "test_building": 2, 10 | "lookback": 100, 11 | "save_path": "experiments/washing_machine/" 12 | } 13 | -------------------------------------------------------------------------------- /ShortSeq2Point/appconf/washing_machine.json: -------------------------------------------------------------------------------- 1 | { 2 | "synth_key": "washing_machine", 3 | "nilmtk_key": "washer dryer", 4 | "memax": 2500, 5 | "std": 700, 6 | "mean":400, 7 | "on_threshold": 20, 8 | "train_buildings": [1,5], 9 | "test_building": 2, 10 | "lookback": 200, 11 | "save_path": "experiments/washing_machine/" 12 | } 13 | -------------------------------------------------------------------------------- /ShortSeq2Point/README.md: -------------------------------------------------------------------------------- 1 | # ShortSeq2Point 2 | A modified version of the Sequence-to-point network. 3 | 4 | Improvements: 5 | 6 | - Smaller window lengths (5-10 mins) to make it suitable for real-time applications. 7 | - Added dropout 8 | 9 | ## To set up the project 10 | Run 11 | ```bash 12 | python gen.py 13 | ``` 14 | This will create the trainsets and download the Neural NILM test set. The trainset comes from the data used in Neural NILM. This may take some time. 15 | 16 | ## To train and test the network 17 | Run 18 | ```bash 19 | python experiment.py 20 | ``` 21 | Where device can be 22 | * ```dishwasher``` 23 | * ```fridge``` 24 | * ```kettle``` 25 | * ```microwave``` 26 | * ```washing_machine``` 27 | 28 | __OR__ 29 | 30 | import the experiment function and use it in your code 31 | ```python 32 | from experiment import experiment 33 | experiment('kettle', 0, 120) 34 | ``` 35 | This will train the network for 120 epochs. You can resume training like this: 36 | ```python 37 | experiment('kettle', 120, 130) 38 | ``` 39 | -------------------------------------------------------------------------------- /GRUWithWindow/model.py: -------------------------------------------------------------------------------- 1 | from keras.models import Sequential, load_model 2 | from keras.layers import Dense, Conv1D, GRU, Bidirectional, Dropout 3 | from keras.utils import plot_model 4 | 5 | def create_model(input_window): 6 | '''Creates and returns the Neural Network 7 | ''' 8 | model = Sequential() 9 | 10 | # 1D Conv 11 | model.add(Conv1D(16, 4, activation='relu', input_shape=(input_window,1), padding="same", strides=1)) 12 | 13 | #Bi-directional GRUs 14 | model.add(Bidirectional(GRU(64, activation='relu', return_sequences=True), merge_mode='concat')) 15 | model.add(Dropout(0.5)) 16 | model.add(Bidirectional(GRU(128, activation='relu', return_sequences=False), merge_mode='concat')) 17 | model.add(Dropout(0.5)) 18 | 19 | # Fully Connected Layers 20 | model.add(Dense(128, activation='relu')) 21 | model.add(Dropout(0.5)) 22 | model.add(Dense(1, activation='linear')) 23 | 24 | model.compile(loss='mse', optimizer='adam') 25 | print(model.summary()) 26 | plot_model(model, to_file='model.png', show_shapes=True, show_layer_names=False) 27 | 28 | return model 29 | -------------------------------------------------------------------------------- /ShortSeq2Point/model.py: -------------------------------------------------------------------------------- 1 | from keras.models import Sequential, load_model 2 | from keras.layers import Dense, Conv1D, GRU, Bidirectional, Dropout, Flatten 3 | from keras.utils import plot_model 4 | 5 | def create_model(input_window): 6 | '''Creates and returns the ShortSeq2Point Network 7 | Based on: https://arxiv.org/pdf/1612.09106v3.pdf 8 | ''' 9 | model = Sequential() 10 | 11 | # 1D Conv 12 | model.add(Conv1D(30, 10, activation='relu', input_shape=(input_window,1), padding="same", strides=1)) 13 | model.add(Dropout(0.5)) 14 | model.add(Conv1D(30, 8, activation='relu', padding="same", strides=1)) 15 | model.add(Dropout(0.5)) 16 | model.add(Conv1D(40, 6, activation='relu', padding="same", strides=1)) 17 | model.add(Dropout(0.5)) 18 | model.add(Conv1D(50, 5, activation='relu', padding="same", strides=1)) 19 | model.add(Dropout(0.5)) 20 | # Fully Connected Layers 21 | model.add(Flatten()) 22 | model.add(Dense(1024, activation='relu')) 23 | model.add(Dropout(0.5)) 24 | model.add(Dense(1, activation='linear')) 25 | 26 | model.compile(loss='mse', optimizer='adam') 27 | plot_model(model, to_file='model.png', show_shapes=True) 28 | 29 | return model 30 | -------------------------------------------------------------------------------- /GRUWithWindow/README.md: -------------------------------------------------------------------------------- 1 | # Recurrent network with GRU and sliding window input 2 | 3 | This is a 'lightweight' recurrent neural network. Inspired by the RNN in NeuralNILM, this one has a similar architecture. However, it uses layers with less neurons. Also the LSTM neurons were replaced with GRU. This makes for less parameters and thus faster training. 4 | 5 | ## To set up the project 6 | Run 7 | ```bash 8 | python gen.py 9 | ``` 10 | 11 | This will create the trainsets and download the Neural NILM test set. The trainset comes from the data used in Neural NILM. This may take some time. 12 | 13 | ## To train and test the network 14 | Run 15 | ```bash 16 | python experiment.py 17 | ``` 18 | Where device can be 19 | * ```dishwasher``` 20 | * ```fridge``` 21 | * ```kettle``` 22 | * ```microwave``` 23 | * ```washing_machine``` 24 | 25 | __OR__ 26 | 27 | import the experiment function and use it in your code 28 | ```python 29 | from experiment import experiment 30 | experiment('kettle', 0, 120) 31 | ``` 32 | This will train the network for 120 epochs. You can resume training like this: 33 | ```python 34 | experiment('kettle', 120, 130) 35 | ``` 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Online NILM 2 | Here you can find the code for the two proposed Neural Network architectures from 3 | 4 | > Sliding Window Approach for Online Energy Disaggregation Using Artificial Neural Networks, O. Krystalakos, C. Nalmpantis and D. Vrakas, SETN, 2018 5 | 6 | Full Paper: [3200947.3201011](https://dl.acm.org/citation.cfm?doid=3200947.3201011) 7 | 8 | All code is written using Keras and Tensorflow. 9 | 10 | You can find NILMTK-compatible versions of these networks on https://github.com/OdysseasKr/neural-disaggregator. 11 | 12 | **Requires NILMTK to run. You can find it here:** https://github.com/nilmtk/nilmtk. 13 | 14 | ## The networks 15 | In each folder you can find a README with instructions on how to run the experiments. For every network you will find 4 Python files: 16 | 17 | - experiment.py: Code for running the experiment. Each experiment includes training and evaluating the network. 18 | - gen.py: Downloads all necessary resources and generates a trainset and a testset. 19 | - model.py: The network architecture. 20 | - metrics.py: The metrics used to evaluate the network. 21 | 22 | The experiments are available for the following appliances from the UKDALE dataset: 23 | - Dishwasher 24 | - Fridge 25 | - Kettle 26 | - Microwave 27 | - Washing Machine 28 | 29 | ## Related works 30 | Neural NILM: https://arxiv.org/pdf/1507.06594.pdf 31 | Original Sequence-to-point: https://arxiv.org/pdf/1612.09106v3.pdf. 32 | -------------------------------------------------------------------------------- /GRUWithWindow/metrics.py: -------------------------------------------------------------------------------- 1 | from nilmtk.electric import align_two_meters 2 | import numpy as np 3 | 4 | def tp_tn_fp_fn(states_pred, states_ground): 5 | tp = np.sum(np.logical_and(states_pred == 1, states_ground == 1)) 6 | fp = np.sum(np.logical_and(states_pred == 1, states_ground == 0)) 7 | fn = np.sum(np.logical_and(states_pred == 0, states_ground == 1)) 8 | tn = np.sum(np.logical_and(states_pred == 0, states_ground == 0)) 9 | return tp, tn, fp, fn 10 | 11 | def recall_precision_accuracy_f1(pred, ground,threshold): 12 | pr = np.array([0 if (p)0: 70 | model = load_model(save_path+"CHECKPOINT-{}-{}epochs.hdf5".format(key_name, start_e)) 71 | 72 | if end_e > start_e: 73 | filepath = save_path+"CHECKPOINT-"+key_name+"-{epoch:01d}epochs.hdf5" 74 | checkpoint = ModelCheckpoint(filepath, verbose=1, save_best_only=False) 75 | history = model.fit(X_train, y_train, batch_size=128, epochs=end_e, shuffle=True, initial_epoch=start_e, callbacks=[checkpoint]) 76 | losses = history.history['loss'] 77 | 78 | model.save("{}CHECKPOINT-{}-{}epochs.hdf5".format(save_path, key_name, end_e),model) 79 | 80 | # Save training loss per epoch 81 | try: 82 | a = np.loadtxt("{}losses.csv".format(save_path)) 83 | losses = np.append(a,losses) 84 | except: 85 | pass 86 | np.savetxt("{}losses.csv".format(save_path), losses, delimiter=",") 87 | 88 | # ======= Disaggregation phase 89 | mains, meter = opends(test_building, key_name) 90 | X_test = normalize(mains, mamax, mean, std) 91 | y_test = meter 92 | 93 | # Predict data 94 | X_batch, Y_batch = gen_batch(X_test, y_test, len(X_test)-input_window, 0, input_window) 95 | pred = model.predict(X_batch) 96 | pred = denormalize(pred, memax, mean, std) 97 | pred[pred<0] = 0 98 | pred = np.transpose(pred)[0] 99 | # Save results 100 | np.save("{}pred-{}-epochs{}".format(save_path, key_name, end_e), pred) 101 | 102 | rpaf = metrics.recall_precision_accuracy_f1(pred, Y_batch, threshold) 103 | rete = metrics.relative_error_total_energy(pred, Y_batch) 104 | mae = metrics.mean_absolute_error(pred, Y_batch) 105 | 106 | print("============ Recall: {}".format(rpaf[0])) 107 | print("============ Precision: {}".format(rpaf[1])) 108 | print("============ Accuracy: {}".format(rpaf[2])) 109 | print("============ F1 Score: {}".format(rpaf[3])) 110 | 111 | print("============ Relative error in total energy: {}".format(rete)) 112 | print("============ Mean absolute error(in Watts): {}".format(mae)) 113 | 114 | res_out = open("{}results-pred-{}-{}epochs".format(save_path, key_name, end_e), 'w') 115 | for r in rpaf: 116 | res_out.write(str(r)) 117 | res_out.write(',') 118 | res_out.write(str(rete)) 119 | res_out.write(',') 120 | res_out.write(str(mae)) 121 | res_out.close() 122 | 123 | 124 | if __name__ == "__main__": 125 | if len(sys.argv) == 1 or sys.argv[1] == "": 126 | print(" Usage: experiment.py ") 127 | print(" Available device names: {}", allowed_key_names) 128 | exit() 129 | 130 | key_name = sys.argv[1] 131 | experiment(key_name, 0, 7) 132 | -------------------------------------------------------------------------------- /GRUWithWindow/experiment.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | from warnings import warn, filterwarnings 3 | 4 | import random 5 | import sys 6 | import numpy as np 7 | import time 8 | import json 9 | 10 | from keras.models import Sequential, load_model 11 | from keras.callbacks import ModelCheckpoint 12 | 13 | from nilmtk import DataSet 14 | import metrics 15 | from gen import opends, gen_batch 16 | from model import create_model 17 | 18 | allowed_key_names = ['fridge','microwave','dish_washer','kettle','washing_machine'] 19 | 20 | def normalize(data, mmax, mean, std): 21 | return data / mmax 22 | 23 | def denormalize(data, mmax, mean, std): 24 | return data * mmax 25 | 26 | def experiment(key_name, start_e, end_e): 27 | '''Trains a network and disaggregates the testset 28 | Displays the metrics for the disaggregated part 29 | 30 | Parameters 31 | ---------- 32 | key_name : The string key of the appliance 33 | start_e : The starting number of epochs for Training 34 | end_e: The ending number of epochs for Training 35 | ''' 36 | 37 | # ======= Open configuration file 38 | if (key_name not in allowed_key_names): 39 | print(" Device {} not available".format(key_name)) 40 | print(" Available device names: {}", allowed_key_names) 41 | conf_filename = "appconf/{}.json".format(key_name) 42 | with open(conf_filename) as data_file: 43 | conf = json.load(data_file) 44 | 45 | input_window = conf['lookback'] 46 | threshold = conf['on_threshold'] 47 | mamax = 5000 48 | memax = conf['memax'] 49 | mean = conf['mean'] 50 | std = conf['std'] 51 | train_buildings = conf['train_buildings'] 52 | test_building = conf['test_building'] 53 | on_threshold = conf['on_threshold'] 54 | meter_key = conf['nilmtk_key'] 55 | save_path = conf['save_path'] 56 | 57 | # ======= Training phase 58 | print("Training for device: {}".format(key_name)) 59 | print(" train_buildings: {}".format(train_buildings)) 60 | 61 | # Open train sets 62 | X_train = np.load("dataset/trainsets/X-{}.npy".format(key_name)) 63 | X_train = normalize(X_train, mamax, mean, std) 64 | y_train = np.load("dataset/trainsets/Y-{}.npy".format(key_name)) 65 | y_train = normalize(y_train, memax, mean, std) 66 | model = create_model(input_window) 67 | 68 | # Train model and save checkpoints 69 | if start_e > 0: 70 | model = load_model(save_path+"CHECKPOINT-{}-{}epochs.hdf5".format(key_name, start_e)) 71 | 72 | if end_e > start_e: 73 | filepath = save_path+"CHECKPOINT-"+key_name+"-{epoch:01d}epochs.hdf5" 74 | checkpoint = ModelCheckpoint(filepath, verbose=1, save_best_only=False) 75 | history = model.fit(X_train, y_train, batch_size=128, epochs=(end_e - start_e), shuffle=True, initial_epoch=start_e, callbacks=[checkpoint]) 76 | losses = history.history['loss'] 77 | 78 | model.save("{}CHECKPOINT-{}-{}epochs.hdf5".format(save_path, key_name, end_e),model) 79 | 80 | # Save training loss per epoch 81 | try: 82 | a = np.loadtxt("{}losses.csv".format(save_path)) 83 | losses = np.append(a,losses) 84 | except: 85 | pass 86 | np.savetxt("{}losses.csv".format(save_path), losses, delimiter=",") 87 | 88 | # ======= Disaggregation phase 89 | mains, meter = opends(test_building, key_name) 90 | X_test = normalize(mains, mamax, mean, std) 91 | y_test = meter 92 | 93 | # Predict data 94 | X_batch, Y_batch = gen_batch(X_test, y_test, len(X_test)-input_window, 0, input_window) 95 | pred = model.predict(X_batch) 96 | pred = denormalize(pred, memax, mean, std) 97 | pred[pred<0] = 0 98 | pred = np.transpose(pred)[0] 99 | # Save results 100 | np.save("{}pred-{}-epochs{}".format(save_path, key_name, end_e), pred) 101 | 102 | rpaf = metrics.recall_precision_accuracy_f1(pred, Y_batch, threshold) 103 | rete = metrics.relative_error_total_energy(pred, Y_batch) 104 | mae = metrics.mean_absolute_error(pred, Y_batch) 105 | 106 | print("============ Recall: {}".format(rpaf[0])) 107 | print("============ Precision: {}".format(rpaf[1])) 108 | print("============ Accuracy: {}".format(rpaf[2])) 109 | print("============ F1 Score: {}".format(rpaf[3])) 110 | 111 | print("============ Relative error in total energy: {}".format(rete)) 112 | print("============ Mean absolute error(in Watts): {}".format(mae)) 113 | 114 | res_out = open("{}results-pred-{}-{}epochs".format(save_path, key_name, end_e), 'w') 115 | for r in rpaf: 116 | res_out.write(str(r)) 117 | res_out.write(',') 118 | res_out.write(str(rete)) 119 | res_out.write(',') 120 | res_out.write(str(mae)) 121 | res_out.close() 122 | 123 | 124 | if __name__ == "__main__": 125 | if len(sys.argv) == 1 or sys.argv[1] == "": 126 | print(" Usage: experiment.py ") 127 | print(" Available device names: {}", allowed_key_names) 128 | exit() 129 | 130 | key_name = sys.argv[1] 131 | experiment(key_name, 0, 3) 132 | --------------------------------------------------------------------------------