In [1]:
from __future__ import print_function, division
from functools import partial
from StringIO import StringIO
import numpy as np
import pandas as pd
import theano
import theano.tensor as T
In [2]:
### utils
def sfloatX(data):
"""Convert scalar to floatX"""
return getattr(np, theano.config.floatX)(data)
def none_to_dict(data):
return {} if data is None else data
def ndim_tensor(name, ndim, dtype=theano.config.floatX):
tensor_type = T.TensorType(dtype=dtype, broadcastable=((False,) * ndim))
return tensor_type(name=name)
In [3]:
from lasagne.layers.helper import get_all_layers, get_all_params, get_output
class Net(object):
def __init__(self, output_layer):
self.layers = get_all_layers(output_layer)
self._deterministic_output_func = None
@property
def deterministic_output_func(self):
if self._deterministic_output_func is None:
self._deterministic_output_func = self._compile_deterministic_output_func()
return self._deterministic_output_func
def _compile_deterministic_output_func(self):
network_input = self.get_symbolic_input()
deterministic_output = self.get_symbolic_output(deterministic=True)
net_output_func = theano.function(
inputs=[network_input],
outputs=deterministic_output,
on_unused_input='warn',
allow_input_downcast=True
)
return net_output_func
def get_symbolic_input(self):
network_input = self.layers[0].input_var
return network_input
def get_symbolic_output(self, deterministic=False):
network_input = self.get_symbolic_input()
network_output = get_output(
self.layers[-1], network_input, deterministic=deterministic)
return network_output
In [4]:
from lasagne.updates import nesterov_momentum
from lasagne.objectives import aggregate, squared_error
class Trainer(object):
def __init__(self, net, data_pipeline,
loss_func=squared_error,
loss_aggregation_mode='mean',
updates_func=partial(nesterov_momentum, momentum=0.9),
learning_rate=1e-2,
callbacks=None,
repeat_callbacks=None):
self.net = net
self.data_pipeline = data_pipeline
self.validation_batch = self.data_pipeline.get_batch(validation=True)
def _loss_func(prediction, target):
loss = loss_func(prediction, target)
return aggregate(loss, mode=loss_aggregation_mode)
self.loss_func = _loss_func
self.updates_func = updates_func
self._learning_rate = theano.shared(sfloatX(learning_rate), name='learning_rate')
# Callbacks
def callbacks_dataframe(lst):
return pd.DataFrame(lst, columns=['iteration', 'function'])
self.callbacks = callbacks_dataframe(callbacks)
self.repeat_callbacks = callbacks_dataframe(repeat_callbacks)
# Training and validation state
self._train_func = None
self._validation_cost_func = None
self.training_costs = []
self.validation_costs = []
self.iteration = 0
@property
def train_func(self):
if self._train_func is None:
self._trian_func = self._compile_train_func()
return self._train_func
@property
def validation_cost_func(self):
if self._validation_cost_func is None:
self._validation_cost_func = self._compile_validation_cost_func()
return self._validation_cost_func
def _compile_train_func(self):
network_input = self.net.get_symbolic_input()
train_output = self.net.get_symbolic_output(deterministic=False)
target_var = ndim_tensor(name='target', ndim=train_output.ndim)
train_loss = self.loss_func(train_output, target_var)
all_params = get_all_params(self.layers[-1], trainable=True)
updates = self.updates_func(train_loss, all_params, learning_rate=self._learning_rate)
train_func = theano.function(
inputs=[network_input, target_var],
outputs=train_loss,
updates=updates,
on_unused_input='warn',
allow_input_downcast=True)
return train_func
def _compile_validation_cost_func(self):
network_input = self.net.get_symbolic_input()
deterministic_output = self.net.get_symbolic_output(deterministic=True)
validation_loss = self.loss_func(deterministic_output, target_var)
validation_cost_func = theano.function(
inputs=[network_input, target_var],
outputs=[validation_loss, deterministic_output],
on_unused_input='warn',
allow_input_downcast=True)
return validation_cost_func
@property
def learning_rate(self):
return self._learning_rate.get_value()
@learning_rate.setter
def learning_rate(self, rate):
rate = sfloatX(rate)
self.logger.info(
"Iteration {:d}: Change learning rate to {:.1E}"
.format(self.n_iterations(), rate))
self._learning_rate.set_value(rate)
def fit(self, num_iterations=None):
while self.iteration != num_iterations:
self.iteration = len(training_costs)
# Training
self.train_batch = self.data_pipeline.get_batch()
train_cost = self.train_func(self.train_batch.input, self.train_batch.target)
self.training_costs.append(train_cost.flatten()[0])
# Callbacks
def run_callbacks(df):
for callback in df['function']:
callback(self)
repeat_callbacks = self.repeat_callbacks[(iteration % self.repeat_callbacks['iteration']) == 0]
run_callbacks(repeat_callbacks)
callbacks = self.callbacks[self.callbacks['iteration'] == iteration]
run_callbacks(callbacks)
def validate(self):
pass
In [5]:
APPLIANCE_PARAMS_CSV = StringIO("""
on_power_threshold, max_power, min_on_duration, min_off_duration
kettle, 10, 2500, 20, 30
toaster, 100, 2000, 20, 30
""")
APPLIANCE_PARAMS = pd.read_csv(APPLIANCE_PARAMS_CSV, index_col=0)
APPLIANCE_PARAMS
Out[5]:
In [6]:
from lasagne.layers import InputLayer, RecurrentLayer, DenseLayer, ReshapeLayer
def get_net_0(input_shape, target_shape=None):
NUM_UNITS = {'dense_layer_0': 100}
if target_shape is None:
target_shape = input_shape
# Define layers
input_layer = InputLayer(
shape=input_shape
)
dense_layer_0 = DenseLayer(
input_layer,
num_units=NUM_UNITS['dense_layer_0']
)
final_dense_layer = DenseLayer(
dense_layer_0,
num_units=target_shape[1] * target_shape[2]
)
output_layer = ReshapeLayer(
final_dense_layer,
shape=target_shape
)
return output_layer
In [7]:
output_layer = get_net_0((4, 16, 1))
net = Net(output_layer)
In [8]:
class Disaggregator(object):
def __init__(self, mains, output_path, stride=1):
self.mains = mains
self.stride = stride
self.output_path = output_path
seq_length = data_pipeline.seq_length
num_seq_per_batch = data_pipeline.num_seq_per_batch
def _get_mains_data_pipeline(self, trainer):
data_pipeline = trainer.data_pipeline
mains_source = MainsSource(
self.mains,
seq_length=data_pipeline.source.seq_length,
stride=self.stride
)
mains_data_pipeline = DataPipeline(
mains_source,
num_seq_per_batch=data_pipeline.num_seq_per_batch,
input_processsing=data_pipeline.input_processing
)
return mains_data_pipeline
def disaggregate(self, trainer):
net = trainer.net
mains_data_pipeline = self._get_mains_data_pipeline(trainer)
while True:
try:
batch = mains_data_pipeline.get_batch()
except:
break
output_for_batch = net.deterministic_output_func(batch.input)
output_for_batch = mains_data_pipeline.convert_to_watts(output_for_batch)
# TODO:
# merge overlapping segments
# save disag output to self.output_path
In [ ]:
plotter = Plotter()
disaggregator = Disaggregator()
trainer = Trainer(
net,
data_pipeline=None,
repeat_callbacks=[
( 10, Trainer.validate),
(1000, plotter.plot),
(1000, disaggregator.disaggregate),
(1000, disag_metrics.run_metrics)
]
)
In [ ]:
def get_data_pipeline_0():
APPLIANCES = ['fridge', 'kettle', 'toaster']
TARGET_APPLIANCE = 'kettle'
NILMTK_FILENAME = '/data/mine/vadeec/merged/ukdale.h5'
SEQ_LENGTH = 1024
CONFIG = {
'nilmtk_activations': {
'appliances': APPLIANCES,
'filename': NILMTK_FILENAME,
'window_per_building': {},
'buildings': [1]
},
'tracebase_activations': {
'appliances': APPLIANCES,
'path': '/data/tracebase'
}
}
nilmtk_activations = load_nilmtk_activations(**CONFIG['nilmtk_activations'])
tracebase_activations = load_tracebase_activations(**CONFIG['tracebase_activations'])
# These activations are dicts of the form:
# {'kettle': {'UK-DALE_house_1': [], 'TraceBase_house_1': []}}
all_activations = merge_activations(nilmtk_activations, tracebase_activations)
synthetic_source = SyntheticAggregateSource(
all_activations,
target_appliance=TARGET_APPLIANCE,
uniform_prob_of_selecting_each_building=True,
seq_length=SEQ_LENGTH
)
# Real data
real_source = NILMTKSource(
filename=NILMTK_FILENAME,
target_appliance=TARGET_APPLIANCE,
train_window_per_building={},
train_building=[1, 2, 3],
validation_buildings=[5],
seq_length=SEQ_LENGTH
)
# Get standard deviation of input
sample_sequences = []
NUM_SEQ_TO_SAMPLE = 512
for i in range(NUM_SEQ_TO_SAMPLE):
sample_sequences.append(real_source.get_data().flatten())
sample = np.concatenate(sample_sequences)
del sample_sequences
input_std = sample.std()
del sample
data_pipeline = DataPipeline(
sources=[synthetic_source, real_source],
train_probs=[0.5, 0.5],
validation_probs=[0.0, 1.0],
num_seq_per_batch=64,
input_processing=[
DivideBy(input_std),
Downsample(3),
IndepdendentlyCentre()
],
target_processing=[
DivideBy(APPLIANCE_PARAMS['max_power'][TARGET_APPLIANCE])
]
)
data_pipeline_thread = DataPipeLineThread(data_pipeline)
data_pipeline_thread.start()
return data_pipeline_thread
experiments = [
{
'data_pipeline': get_data_pipeline_0,
'net': get_net_0
}
]
def run_experiments():
for experiment in experiments:
data_pipeline = experiment['data_pipeline']()
validation_data = pipeline.get_data(validation=True)
input_shape = validation_data.shape
output_layer = experiment['net'](input_shape)
net = Net(output_layer)
trainer = Trainer(
net=net,
data_pipeline=data_pipeline,
loss_function=squared_error,
repeat_callbacks={100: [plotter.plot_estimates, save_parameters, PlotTSNE(layers=[2])]}
)