Las siglas MLE corresponden al algoritmo de estimación de máxima verosimilitud y se usa por ejemplo para la desagregación de energía.
Es responsable cuando se desea un solo aparato desagregado en lugar de desagregar muchos. Además, el aparato debe ser mayoritariamente resistivo para lograr precisión.
Se basa en eventos emparejados:
Además, para descartar muchos eventos poco probables, se establecen tres restricciones:
- PowerNoise: valor delta mínimo por debajo del cual el delta se considera ruido. - PowerPair: diferencia máxima entre OnPower y OffPower (considerando electrodomésticos con consumo de energía constante). - timeWindow: marco de tiempo máximo entre Onpower y Offpower.
Las características antes mencionadas se modelan con gaussianas, mezclas gaussianas o Poisson. Para cada evento emparejado entrante, el algoritmo extraerá estas tres características y evaluará la probabilidad de máxima verosimilitud de que ese evento emparejado sea un dispositivo determinado.
IMPORTACIONES
import numpy as np import pandas as pd from os.path import join from pylab import rcParams import matplotlib.pyplot as plt %matplotlib inline rcParams['figure.figsize'] = (13, 6) #plt.style.use('ggplot') from datetime import datetime as datetime2 from datetime import timedelta import nilmtk from nilmtk.disaggregate.maximum_likelihood_estimation import MLE from nilmtk import DataSet, TimeFrame, MeterGroup, HDFDataStore from scipy.stats import poisson, norm from sklearn import mixture import warnings warnings.filterwarnings("ignore")
Funciones
def get_all_appliances(appliance): # Filtering by appliances: print "Fetching " + appliance + " over data loaded to nilmtk." metergroup = nilmtk.global_meter_group.select_using_appliances(type=appliance) if len(metergroup.appliances) == 0: print "None " + appliance + " found on memory." pass # Selecting only single meters: print "Filtering to get one meter for each " + appliance meters = [meter for meter in metergroup.meters if (len(meter.appliances) == 1)] metergroup = MeterGroup(meters) print metergroup print "Found " + str(len(metergroup.meters)) + " " + appliance return metergroup def get_all_trainings(appliance, train): # Filtering by appliances: print "Fetching " + appliance + " over data train data." elecs = [] for building in train.buildings: print "Building " + str(building) + "..." elec = train.buildings[building].elec[appliance] if len(elec.appliances) == 1: print elec print "Fetched elec." elecs.append(elec) else: print elec print "Groundtruth does not exist. Many appliances or None" metergroup = MeterGroup(elecs) return metergroup
path = '../../../nilmtk/data/ukdale' ukdale = train = DataSet(join(path, 'ukdale.h5'))
Y dividir en datos de prueba y de entrenaminento
In [8]:train = DataSet(join(path, 'ukdale.h5')) test = DataSet(join(path, 'ukdale.h5')) train.set_window(end="17-5-2013") test.set_window(start="17-5-2013") #zoom.set_window(start="17-5-2013") print('loaded ' + str(len(ukdale.buildings)) + ' buildings')
Obteniendo los datos de entrenamiento
Es posible que el dispositivo seleccionado no esté entrenado desde ElecMeters donde se presentan otros dispositivos, ya que podemos extraer la verdad básica
# Appliance to disaggregate: applianceName = 'kettle' # Groundtruth from the training data: metergroup = get_all_trainings(applianceName,train)
Fetching kettle over data train data. Building 1... ElecMeter(instance=10, building=1, dataset='UK-DALE', appliances=[Appliance(type='kettle', instance=1), Appliance(type='food processor', instance=1), Appliance(type='toasted sandwich maker', instance=1)]) Groundtruth does not exist. Many appliances or None Building 2... ElecMeter(instance=8, building=2, dataset='UK-DALE', appliances=[Appliance(type='kettle', instance=1)]) Fetched elec. Building 3... ElecMeter(instance=2, building=3, dataset='UK-DALE', appliances=[Appliance(type='kettle', instance=1)]) Fetched elec. Building 4... ElecMeter(instance=3, building=4, dataset='UK-DALE', appliances=[Appliance(type='kettle', instance=1), Appliance(type='radio', instance=1)]) Groundtruth does not exist. Many appliances or None Building 5... ElecMeter(instance=18, building=5, dataset='UK-DALE', appliances=[Appliance(type='kettle', instance=1)]) Fetched elec.
Algoritmo MLE
Capacitación
Primero, creamos el modelo
Luego, actualizamos el parámetro del modelo con algunos valores de adivinación.
Primero adivine las características: mezclas gaussianas de encendido y apagado y poisson de duración.
# setting parameters in the model: mle.update(appliance=applianceName, resistive=True, units=('power','active'), thDelta= 1500, thLikelihood= 1e-10, powerNoise= 50, powerPair= 100, timeWindow= 400, sample_period= '10S', sampling_method='first') # Settings the features parameters by guessing: mle.onpower = {'name':'gmm', 'model': mixture.GMM(n_components=2)} mle.offpower = {'name':'gmm', 'model': mixture.GMM(n_components=2)} mle.duration = {'name':'poisson', 'model': poisson(0)}
Updating model {'resistive': True, 'appliance': 'kettle', 'sampling_method': 'first', 'sample_period': '10S', 'thLikelihood': 1e-10, 'timeWindow': 400, 'units': ('power', 'active'), 'thDelta': 1500, 'powerNoise': 50, 'powerPair': 100}
Entrenando el modelo
Entrenamos el modelo con todas las ocurrencias de ese modelo de dispositivo que se encuentran en los datos de entrenamiento.
('kettle', 1) Training on chunk Samples of onpower: 214 Samples of offpower: 214 Samples of duration: 214 Training onpower Training offpower Training duration ('kettle', 2) Training on chunk Samples of onpower: 92 Samples of offpower: 92 Samples of duration: 92 Training onpower Training offpower Training duration ('kettle', 3) Chunk empty
Y luego visualizamos características con featureHist_colors () para ver la distribución y cuántas muestras tenemos para cada dispositivo (el mismo modelo de diferentes casas).
mle.featuresHist_colors()
A veces, tenemos más eventos de algunas casas que de otras, como vemos en la figura de arriba. Por lo tanto, necesitamos recortar información para mantener el mismo número de muestras para cada casa.
Retraining onpower Retraining offpower Retraining duration
In [15]:mle.featuresHist_colors()
Hay otra herramienta de visualización para ver cómo se ajustan las distribuciones del modelo a los datos:
Onpower y Offpower parecen encajar bien con los datos, pero necesitamos cambiar el modelo por duración
mle.duration = {'name':'gmm', 'model': mixture.GMM(n_components=10)}
Y luego volvemos a entrenar el modelo y usamos no_overfitting
mle.train(metergroup) mle.no_overfitting() mle.featuresHist()
('kettle', 1) Training on chunk Samples of onpower: 214 Samples of offpower: 214 Samples of duration: 214 Training onpower Training offpower Training duration ('kettle', 2) Training on chunk Samples of onpower: 92 Samples of offpower: 92 Samples of duration: 92 Training onpower Training offpower Training duration ('kettle', 3) Chunk empty Retraining onpower Retraining offpower Retraining duration
Una vez que tengamos la distribución del modelo final para cada característica. Necesitamos la integridad de cada distribución. Cada CDF debe estar delimitado por uno.
mle.check_cdfIntegrity(step=10)
Onpower cdf: 0.986177684776 Offpower cdf: 1.0 Duration cdf: 0.987375070732
Desagregación
# Building to disaggregate: building = 2 mains = test.buildings[building].elec.mains() # File to store the disaggregation filename= '/home/energos/Escritorio/ukdale-disag-ml.h5' output = HDFDataStore(filename, 'w')
El siguiente paso tomará unos minutos.
mle.disaggregate(mains, output)
25656 events found. 12419 onEvents found 4244 onEvents no paired. 1 chunks disaggregated
También recibimos cierta información, como el número total de eventos, el número de eventos de encendido, el número de eventos que no se han emparejado y los fragmentos desglosados.
Comparando la desagregación con la verdad básica
## Groundtruth kettle = test.buildings[building].elec.select_using_appliances(type=applianceName) output.load(mains.key).next().plot() kettle.plot() output.close()