¿Que significa MLE?

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:

- OnPower: valor delta cuando el aparato está encendido. 
- OffPOwer: valor delta cuando el aparato está apagado. 
- Duración: duración entre encendido y apagado.

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

Cargando datos

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')
loaded 5 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

mle = MLE()

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.

mle.train(metergroup)
('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.

mle.no_overfitting()
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:

mle.featuresHist()

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()

Deja una respuesta