nilmtk / nilmtk

Non-Intrusive Load Monitoring Toolkit (nilmtk)
http://nilmtk.github.io
Apache License 2.0
833 stars 461 forks source link

Define Disaggregator superclass #271

Closed JackKelly closed 5 years ago

JackKelly commented 9 years ago

Define Disaggregator superclass to hold common behaviour & to specify common interface to NILM algorithms & common docstrings.

@oliparson discovered how to specify common common docstrings using a superclass. Described in this comment.

nipunbatra commented 9 years ago

I am chalking down some ideas up here of what this class should have. Please feel free to add/remove. I am scarcely clear about writing the interface for a disaggregator to handle multiple cases- train and disaggregate on same home; train on some home(s) and disaggregate on other(s)

A simple design which comes to my mind is the following (based on writing disaggregators for v0.1 and current version):

  1. Input: generator of DataFrames. At this point, I am assuming all preprocessing has been done. I would really think of the core algorithms (and their writers) bother about alignment between appliances and mains, missing data and so forth
  2. Each disaggregator should have a method disaggregate_chunk which disaggregates a chunk (a single mains dataframe into a corresponding dataframe per appliance/meter. Columns on both the disaggregated and mains dataframe should ideally be the same. This method also allows a user to specify other attributes such as confidence in my prediction. This method should be sufficiently simple for people outside nilmtk to just call on a dataframe as long as the dataframe adheres to nilmtk vocabulary.
  3. There should be a method which writes the above disaggregated dataframe per appliance to Datastore
  4. A method which iterates over the generated dataframes and calls the function mentioned in point number 2 and 3
  5. Each disaggregator should have an attribute meters where it stores the meters it is going to process/disaggregate. This is needed by the metadata
  6. A function which 'smartly' gets the required metadata and writes it to disk. I am not very clear about this at the moment.
  7. Each disaggregator should have an attribute model where it stores its model. There should not be any hard and fast rules for this attribute. Different disaggregators would like high customasibility.
  8. Each disaggregator should ensure repeatibility of experiments. This is done by fixing the random seed.
JackKelly commented 9 years ago

Excellent work on listing important requirements for a disaggregator interface! I agree with all your points. (It would be good to weight up the pros and cons of passing in a generator of DataFrames instead of an entire ElecMeter or MeterGroup object representing the mains... if we pass in an instance of an Electric subclass then the Disaggregator will have access to important metadata such as building instance, dataset name, the meter's sample rate etc... and the Disaggregator can choose whether to load all AC types from disk or just one etc...)

Some very quick replies to your email...

Quoting your email...

I have not been able to test Hart85 algo on 2 dimensions, although it is supposed to work. Part of the reason is that we currently engineered for 1d data and other 2 disaggregators work on 1d data only. So, it would require some effort to ensure all 3 of them use the same interface, which I think is helpful as it helps people in the future to contribute disaggregators. Although, it also binds them. Say, if tomorrow I wish to write a disaggregator which takes weather into consideration, in the proposed design I won't be able to do the same.

Regarding 1-D versus n-D power data... it should be possible to define a common interface to accept either 1-D or n-D power data. If disaggregate() expects a generator of DataFrames (instead of a generator of Series) or an instance of an Electric subclass (ElecMeter or MeterGroup) then it should be able to accept 1-D or n-D data.

Regarding having NILM algorithms which want additional inputs (e.g. weather data)... I think the Python mechanisms for forcing subclasses to implement certain methods can only force subclasses to implement those methods, it can't force subclasses to use the exact same method signature. So, for example, the superclass would just say "all subclasses must implement a disaggregate and a train method and they can (but I can't force them to) use these specific function signatures and docstrings". So, as long as your disaggregator implements disaggregate, it will be allowed by Python. Because the subclass doesn't have to implement the exact function signature of the superclass, each NILM algorithm can add new inputs (e.g. a generator of DataFrames of weather data) to any method. At least, I think that's the way it works!

JackKelly commented 9 years ago

It shouldn't be too hard to write a Disaggregator superclass and hence massively simplify the implementation of new NILM algorithms. I'll try to write a draft Disaggregator class within the next couple of weeks so we can try to get it into v0.3. I think this is pretty essential. We've had great success with encouraging other developers to add dataset converters to NILMTK but no one has yet added their own NILM algorithm to NILMTK. This is a big issue and, I think, needs addressing before we release v0.3. The basic idea would be, as outlined by Nipun, that a new NILM algorithm would only have to override the train_on_chunk() and disaggregate_chunk() methods.

nipunbatra commented 9 years ago

I see that this is not a huge change and has several benefits. I am happy to have this design before v0.3; especially given the fact that it would get tougher to change later!

oliparson commented 9 years ago

Pasting from Nipun's email:

I guess if the disaggregator class is in place, then all the algo writer needs to do is to take a DataFrame input and output a data frame of all appliances. So, the first step for any algo writer could be to provide a function which takes an in-memory data frame of mains readings an disaggregates it, without having to worry about some of the specifics of nilmtk.

oliparson commented 9 years ago

Vision for the Disaggregator superclass

Disaggregation algorithms will be implemented by extending the Disaggregator superclass. This allows disaggregation algorithms to implement a common interface, while also reusing generic NILMTK code.

N.B. only the method signatures and docstrings for the Disaggregator class exist at the moment, and hopefully the implementation will follow shortly :)

There will be two (complementary) methods of implementing a disaggregation algorithm:

1. Working with Pandas Dataframes

This approach requires the train_on_chunk() and disaggregate_chunk() methods to be overridden by the subclass. This approach is strongly recommended, as it allows the disaggregation to be performed out-of-core, as well as allowing code to call this function without worrying about NILMTK data structures. Furthermore, this approach will make use of generic Disaggregator functionality to iterate over dataframes produced by a generator, as well as saving the disaggregation output to disk for each chunk.

2. Working with NILMTK MeterGroups

This approach requires the train() and disaggregate() methods to be overridden by the subclass. This approach is not recommended without also overriding the chunk-by-chunk disaggregation, as it will mean all disaggregation needs to take place in memory. However, it will be necessary to override these methods if the disaggregation algorithm is going to make use of mutual information between multiple aggregate site-meters, i.e. split-phase power.

Finally, a disaggregation sub-class can choose to implement the import_model() and export_model() methods to allow the learned model to be saved to disk, rather than re-learning the model each time a new environment is created.

JackKelly commented 9 years ago

I'm working on splitting CombinatorialOptimisation.disaggregate() into disaggregate() and disaggregate_chunk() now.

JackKelly commented 9 years ago

done

JackKelly commented 8 years ago

One issue I'm not clear on is this: how do we write a common interface for train_on_chunk? There are several requirements different types of disaggregation algorithm I can think of:

  1. Ones like CombinatorialOptimisation which only require a single appliance at once.
  2. Ones like my Neural NILM code which requires both the aggregate mains data and one appliance at a time during training.

How do we build a common function signature for train_on_chunk? Do we just always use something like train_on_chunk(self, appliance_chunk, mains_chunk=None)?

Although, to be honest, I'm not sure my Neural NILM code will ever implement train_on_chunk because it always needs full access to the MeterGroup (so it can get activations of each appliance to augment the training data etc).

oliparson commented 8 years ago

This is a really good point. Could we change the signature to take a MeterGroup, which can always just contain a single ElecMeter for supervised learning?

JackKelly commented 8 years ago

Could we change the signature to take a MeterGroup

I haven't articulated this very well yet... but I was hoping that both train_on_chunk and disaggreate_chunk would take only non-NILMTK types. e.g. just objects like Pandas DataFrames, dicts, numpy arrays, strings etc.

I think it would be nice to allow NILMTK to interact more freely with the wider scientific Python ecosystem by allowing people to more freely use NILMTK directly on DataFrames / numpy arrays etc.

For example, when I was messing around with my Neural NILM work, I only used NILMTK for data loading (and comparing with CO and FHMM). I didn't use NILMTK's Disaggregator interface at all for my Neural NILM experiments. Part of the reason was that I was doing a lot of 'augmenting' the training data (i.e. generating lots of numpy arrays) and it was easier to stay in numpy land rather than try to wrap my numpy arrays into an ElecMeter object or something (which, at the moment, isn't even possible in NILMTK without first dumping the numpy array to disk, which would have slowed things down a lot). Hence my ambition to try to allow the Disaggregator subclasses to interact either with NILMTK objects like MeterGroups or directly with DataFrame / numpy arrays.

Does that sound sensible? Maybe it's not possible?

oliparson commented 8 years ago

This sounds like a really good idea, and in general I'm happy with any changes that simplify the required knowledge for algorithm contributors. Do you think each algorithm really needs to implement train_on_chunk though so long as they're iterating through the chunks?

nipunbatra commented 8 years ago

I'd second using Pandas data frames instead of nilmtk data structures for disaggregate_chunk.

JackKelly commented 8 years ago

Do you think each algorithm really needs to implement train_on_chunk though so long as they're iterating through the chunks?

In an ideal world, yes, I think each algorithm should implement train_on_chunk and disaggregate_chunk. With a bit of thought on our end, we could perhaps design things so disag algo authors only have to implement those two methods and the superclass would handle iterating through the chunks (and the superclass would call train_on_chunk and disaggregate_chunk). But, given the diversity of NILM algorithms, we'd need to give algo authors to option to override disaggregate and train to iterate through chunks in custom ways.

JackKelly commented 8 years ago

Maybe the superclass could define a function signature for train_on_chunk which covers all the options (ie has parameters for the mains as well as the appliances) and any NILM algorithm (like CO) which doesn't need mains won't require 'mains' and will just ignore it if it is provided. That way users can write code which can use any NILM algorithm without changing the function call. Any thoughts? On 25 Nov 2015 11:47 am, "Nipun Batra" notifications@github.com wrote:

I'd second using Pandas data frames instead of nilmtk data structures for disaggregate_chunk.

— Reply to this email directly or view it on GitHub https://github.com/nilmtk/nilmtk/issues/271#issuecomment-159583741.

PMeira commented 5 years ago

Closing in favor of the new API.