In [1]:
from __future__ import print_function, division
import numpy as np
from os.path import join, expanduser
import matplotlib.pyplot as plt
import yaml  # for pretty-printing dict
from neuralnilm.metrics import run_metrics, across_all_appliances
import pandas as pd

# sklearn evokes warnings from numpy
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)


/home/dk3810/workspace/python/nntools/lasagne/init.py:86: UserWarning: The uniform initializer no longer uses Glorot et al.'s approach to determine the bounds, but defaults to the range (-0.01, 0.01) instead. Please use the new GlorotUniform initializer to get the old behavior. GlorotUniform is now the default for all layers.
  warnings.warn("The uniform initializer no longer uses Glorot et al.'s "

In [2]:
TRAIN_HOUSES = {
    'microwave': (1, 2),
    'fridge': (1, 2, 4),
    'dish washer': (1, 2),
    'kettle': (1, 2, 4),
    'washing machine': (1, 5)
}

TEST_HOUSES = {
    'microwave': (5,),
    'fridge': (5,),
    'dish washer': (5,),
    'kettle': (5,),
    'washing machine': (2,)
}

APPLIANCES = TRAIN_HOUSES.keys()

ON_POWER_THRESHOLDS = {
    'microwave': 200,
    'fridge': 30,
    'dish washer': 10,
    'kettle': 2000,
    'washing machine': 20
}

HOUSES = [1, 2, 3, 4, 5]

METRICS = [
    'f1_score',
    'precision_score',
    'recall_score',
    'accuracy_score',
    'relative_error_in_total_energy',
    'total_energy_correctly_assigned',
    'mean_absolute_error'
]

ALGORITHMS = ['co', 'fhmm', 'ae', 'rectangles', 'rnn']

full_algorithm_names = [
    'Combinatorial Optimisation ', 'Factorial HMM', 'Autoencoder', 'Rectangles', 'LSTM']


ESTIMATES_PATH = expanduser(
    "~/PhD/experiments/neural_nilm/data_for_BuildSys2015/disag_estimates")
GROUND_TRUTH_PATH = expanduser(
    "~/PhD/experiments/neural_nilm/data_for_BuildSys2015/ground_truth_and_mains")

PLOT_PATH = expanduser("~/PhD/writing/papers/BuildSys_2015_Neural_NILM")

In [3]:
def load(architecture, building_i, appliance):
    # load estimates
    estimates_fname = "{}_building_{}_estimates_{}.csv".format(
        architecture, building_i, appliance)
    estimates_fname = join(ESTIMATES_PATH, estimates_fname)
    y_pred = np.loadtxt(estimates_fname, delimiter=',')

    # load ground truth
    y_true_fname = "building_{}_{}.csv".format(building_i, appliance.replace(' ', '_'))
    y_true_fname = join(GROUND_TRUTH_PATH, y_true_fname)
    y_true = np.loadtxt(y_true_fname, delimiter=',')

    # load mains
    mains_fname = "building_{}_mains.csv".format(building_i)
    mains_fname = join(GROUND_TRUTH_PATH, mains_fname)
    mains = np.loadtxt(mains_fname, delimiter=',')

    return y_true, y_pred, mains

In [4]:
def plot_all(y_true, y_pred, mains, title=None):
    fig, axes = plt.subplots(nrows=3, sharex=True)
    axes[0].plot(y_pred)
    axes[0].set_title('y_pred')
    axes[1].plot(y_true)
    axes[1].set_title('y_true')
    axes[2].plot(mains)
    axes[2].set_title('mains')
    if title:
        fig.set_title(title)
    plt.show()
    return fig, axes

In [5]:
# Run metrics
def calc_metrics(houses):
    scores = pd.Panel(
        np.NaN,
        items=APPLIANCES,
        major_axis=METRICS,
        minor_axis=ALGORITHMS
    )
    
    for appliance in APPLIANCES:
        houses_for_appliance = houses[appliance]
        on_power_threshold = ON_POWER_THRESHOLDS[appliance]
        for algo in ALGORITHMS:
            house_scores = pd.DataFrame(
                np.NaN, columns=METRICS, index=houses_for_appliance)
            for house_i in houses_for_appliance:
                y_true, y_pred, mains = load(algo, house_i, appliance)
                house_scores_dict = run_metrics(
                    y_true, y_pred, mains, on_power_threshold)
                house_scores_dict.pop('sum_abs_diff')
                house_scores.loc[house_i] = house_scores_dict
            scores[appliance, :, algo].update(house_scores.dropna().mean())
    
    scores['across all appliances'] = scores.mean(axis=0)
    return scores

In [25]:
test_houses_scores = calc_metrics(TEST_HOUSES)
train_houses_scores = calc_metrics(TRAIN_HOUSES)

In [26]:
APPLIANCE = 'washing machine'
test_houses_scores[APPLIANCE]


Out[26]:
co fhmm ae rectangles rnn
f1_score 0.101690 0.077404 0.132470 0.265773 0.025179
precision_score 0.056811 0.041181 0.070952 0.293043 0.012812
recall_score 0.484127 0.642857 0.996392 0.243146 0.725108
accuracy_score 0.882384 0.789273 0.820545 0.981527 0.227960
relative_error_in_total_energy 0.734942 0.859433 0.478495 -0.738145 0.909220
total_energy_correctly_assigned 0.930384 0.881886 0.957229 0.980871 0.808618
mean_absolute_error 39.467577 66.962825 24.248299 10.844981 108.500605

In [30]:
train_houses_scores[APPLIANCE]


Out[30]:
co fhmm ae rectangles rnn
f1_score 0.128903 0.111168 0.251175 0.493818 0.087758
precision_score 0.075282 0.061310 0.148191 0.724677 0.048637
recall_score 0.564659 0.869108 0.992013 0.377078 0.617400
accuracy_score 0.687574 0.385451 0.763328 0.966354 0.311202
relative_error_in_total_energy 0.648831 0.759248 0.179166 -0.651331 0.732140
total_energy_correctly_assigned 0.920369 0.877248 0.959632 0.973316 0.882958
mean_absolute_error 87.919616 137.529573 44.060847 28.470840 132.590392

In [28]:
train_houses_scores[APPLIANCE]


Out[28]:
co fhmm ae rectangles rnn
f1_score 0.128903 0.111168 0.251175 0.493818 0.087758
precision_score 0.075282 0.061310 0.148191 0.724677 0.048637
recall_score 0.564659 0.869108 0.992013 0.377078 0.617400
accuracy_score 0.687574 0.385451 0.763328 0.966354 0.311202
relative_error_in_total_energy 0.648831 0.759248 0.179166 -0.651331 0.732140
total_energy_correctly_assigned 0.920369 0.877248 0.959632 0.973316 0.882958
mean_absolute_error 87.919616 137.529573 44.060847 28.470840 132.590392

In [33]:
y_true, y_pred, mains = load('rectangles', 5, APPLIANCE)
plot_all(y_true, y_pred, mains)


Out[33]:
(<matplotlib.figure.Figure at 0x7f95f83c8890>,
 array([<matplotlib.axes._subplots.AxesSubplot object at 0x7f95f83bd890>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7f95f81e1610>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7f95f8165150>], dtype=object))

In [34]:
# plot
COLOR = ['#5F7343', '#99A63C', '#FEC06A', '#F25430', '#E61924']
FONTSIZE = 10

def plot_scores(scores):
    appliances = list(scores.items)
    metrics = list(scores.major_axis)
    algorithms = list(scores.minor_axis)
    
    nrows = len(metrics)
    ncols = len(appliances)
    n_algorithms = len(algorithms)
    x = range(n_algorithms)
    
    fig, axes = plt.subplots(nrows=nrows, ncols=ncols, sharey='row', figsize=(8.1, 8.7))
    fig.patch.set_facecolor('white')
    for row_i, metric in enumerate(metrics):
        for col_i, appliance in enumerate(appliances):
            ax = axes[row_i, col_i]
            scores_for_algorithms = scores[appliance, metric]
            rects = ax.bar(
                x, scores_for_algorithms, color=COLOR, edgecolor=COLOR, zorder=3)

            # Numbers on the plot
            if row_i == 6:  # mean absolute error (watts)
                text_y = 90
                text_format = '{:3.0f}'
            elif row_i == 4:  # relative error in total energy
                text_y = 0
            else:
                text_y = 0.5
                text_format = '{:.2f}'

            # Draw text
            for i, rect in enumerate(rects):
                ax.text(
                    rect.get_x() + (1/6),
                    text_y,
                    text_format.format(scores_for_algorithms[i]),
                    va='center', rotation=90, fontsize=FONTSIZE)

            # Formatting
            ax.set_xticks([])
            ax.tick_params(direction='out')
            ax.yaxis.grid(
                b=True, which='major', color='white', linestyle='-', zorder=0)
            ax.patch.set_facecolor((0.85, 0.85, 0.85))

            if row_i == 4:  # relative error in total energy
                ax.set_ylim((-1, 1))

            for spine in ['top', 'right', 'left', 'bottom']:
                ax.spines[spine].set_visible(False)

            if row_i == 0:
                if appliance == 'across all appliances':
                    label = 'Across all\nappliances'
                else:
                    label = appliance.replace(' ', '\n')
                    label = label[0].capitalize() + label[1:]
                ax.set_title(label, fontsize=FONTSIZE)
            if col_i == 0:
                label = metric.replace('_', '\n')
                if label == 'mean\nabsolute\nerror':
                    label = label + '\n(watts)'
                elif label == 'total\nenergy\ncorrectly\nassigned':
                    label = 'prop. of\n' + label
                elif label == 'relative\nerror\nin\ntotal\nenergy':
                    label = 'relative\nerror in\ntotal\nenergy'
                label = label[0].capitalize() + label[1:]
                ylabel = ax.set_ylabel(label, fontsize=FONTSIZE)
                ylabel.set_rotation('horizontal')
                ylabel.set_verticalalignment('center')
                ylabel.set_horizontalalignment('center')
                ax.yaxis.labelpad = 25
                ax.tick_params(axis='y', left='on', right='off')
            else:
                ax.tick_params(axis='y', left='off', right='off')

    plt.subplots_adjust(hspace=0.3, top=0.96, bottom=0.07, left=0.13, right=0.99)
    plt.legend(rects, full_algorithm_names, ncol=n_algorithms, loc=(-7, -0.6),
               frameon=False, fontsize=FONTSIZE)
    return fig, axes

In [35]:
fig, axes = plot_scores(test_houses_scores)
#fig.suptitle('Unseen houses', fontsize=16)
plt.savefig(join(PLOT_PATH, 'unseen_houses.eps'))
plt.show()

In [36]:
fig, axes = plot_scores(train_houses_scores)
#fig.suptitle('Train houses', fontsize=16)
plt.savefig(join(PLOT_PATH, 'train_houses.eps'))
plt.show()

In [ ]: