# -*- coding: utf-8 -*-
"""
    File name:      predict_act.py
    Author:         Tetsuro Tatsuoka
    Date created:   04/12/2018
    Last modified:  04/12/2018
    Version:        1.0.0
    Python version: 3.6.3
    Description:    Confirm classification of activity of daily living (ADL) 
    Thanks to:
        https://github.com/servomac/Human-Activity-Recognition
        https://github.com/guillaume-chevalier/LSTM-Human-Activity-Recognition/blob/master/LSTM.ipynb
        http://scikit-learn.org/stable/auto_examples/model_selection/plot_confusion_matrix.html
        
    Revision history:
        1.0.0   04/12/2018  - Initial coding.
"""
import numpy as np
import pandas as pd
import pickle
import matplotlib.pyplot as plt

from load_data_act import load_data

import itertools
from sklearn.metrics import confusion_matrix

INPUT_SIZE = 9
HIDDEN_SIZE = 32
HIDDEN_GATE = 4
OUTPUT_SIZE = 6

def sigmoid(x):
    """ Calculate sigmoid function (logistic function)
    """
    return 1 / (1 + np.exp(-x))

def hard_sigmoid(x):
    """ Calculate hard sigmoid function
    """
    x = (x * 0.2) + 0.5
    z = np.maximum(0, x)
    z = np.minimum(1, z)
    return z

def calc_conv(w, x, u, h, b):
    """ Calculate convolution
    """
    z = w.T @ x + u.T @ h + b
    return z

# Load data    
X_train, X_test, Y_train, Y_test = load_data()

# Load weight
weight = np.array(pickle.load(open("model_weights_lstm.pkl", "rb")))

# 9 x 32 weights for input
Wi = weight[0][:, 0:HIDDEN_SIZE]
Wf = weight[0][:, HIDDEN_SIZE:HIDDEN_SIZE*2]
Wc = weight[0][:, HIDDEN_SIZE*2:HIDDEN_SIZE*3]
Wo = weight[0][:, HIDDEN_SIZE*3:HIDDEN_SIZE*4]

# 32 x 32 weights for hidden
Ui = weight[1][:, 0:HIDDEN_SIZE]
Uf = weight[1][:, HIDDEN_SIZE:HIDDEN_SIZE*2]
Uc = weight[1][:, HIDDEN_SIZE*2:HIDDEN_SIZE*3]
Uo = weight[1][:, HIDDEN_SIZE*3:HIDDEN_SIZE*4]

# 32 biases
Bi = weight[2][0:HIDDEN_SIZE]
Bf = weight[2][HIDDEN_SIZE:HIDDEN_SIZE*2]
Bc = weight[2][HIDDEN_SIZE*2:HIDDEN_SIZE*3]
Bo = weight[2][HIDDEN_SIZE*3:HIDDEN_SIZE*4]

# 32 x 6 Weights for output layer
W = weight[3]

# 6 biases
B = weight[4]

# predict
IG = np.zeros(HIDDEN_SIZE)          # Input gate
FG = np.zeros(HIDDEN_SIZE)          # Forget gate
OG = np.zeros(HIDDEN_SIZE)          # Output gate
CX = np.zeros(HIDDEN_SIZE)          # CEC candidate
CEC = np.zeros(HIDDEN_SIZE)         # CEC hold value
HL = np.zeros(HIDDEN_SIZE)          # Hidden layer

Y = np.zeros([X_test.shape[0], OUTPUT_SIZE]) # Output layer

for k in range(X_test.shape[0]):
    CEC = np.zeros(HIDDEN_SIZE)
    HL = np.zeros(HIDDEN_SIZE)
    for i in range(X_test.shape[1]):
        X = X_test[k, i, :]
        IG = hard_sigmoid(  calc_conv(Wi, X, Ui, HL, Bi) )
        CX = np.tanh(       calc_conv(Wc, X, Uc, HL, Bc) )
        FG = hard_sigmoid(  calc_conv(Wf, X, Uf, HL, Bf) )
        CEC = IG * CX + FG * CEC
        
        OG = hard_sigmoid(  calc_conv(Wo, X, Uo, HL, Bo) )
        HL = OG * np.tanh(CEC)
        
    Y[k,:] = sigmoid(W.T @ HL + B)

# Result
Y_res = Y
Y_true = np.argmax(Y_test, axis=1)
Y_pred = np.argmax(Y, axis=1)
prob = np.max(Y, axis=1) / np.sum(Y, axis=1)

correct = np.equal(Y_true, Y_pred)
accuracy = np.sum(correct) / len(correct)
print('\nAccuracy: %.3f %%\n' %(accuracy * 100.0))

# Confusion matrix
ACTIVITIES = {
    0: 'WALKING',
    1: 'WALKING_UP',    # 1: 'WALKING_UPSTAIRS',
    2: 'WALKING_DOWN',  # 2: 'WALKING_DOWNSTAIRS',
    3: 'SITTING',
    4: 'STANDING',
    5: 'LAYING',
}

y_true = pd.Series([ACTIVITIES[y] for y in np.argmax(Y_test, axis=1)])
y_pred = pd.Series([ACTIVITIES[y] for y in np.argmax(Y_res, axis=1)])
print(pd.crosstab(y_true, y_pred, rownames=['True'], colnames=['Pred']))

# Visualize confusion matrix
class_names = np.array([
    'LAYING',
    'SITTING',
    'STANDING',
    'WALKING',
    'WALKING_DOWN',
    'WALKING_UP',
])

def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    """
    This function prints and plots the confusion matrix.
    Normalization can be applied by setting `normalize=True`.
    """
    if normalize:
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
        print("Normalized confusion matrix")
    else:
        print('Confusion matrix, without normalization')

    print(cm)

    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=90)    # rotation 45 -> 90
    plt.yticks(tick_marks, classes)

    fmt = '.3f' if normalize else 'd'               # '.2f' -> '.3f'
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, format(cm[i, j], fmt),
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.subplots_adjust(bottom=0.32)                # Add specified margin
    plt.ylabel('True label')
    plt.xlabel('Predicted label')

cnf_matrix = confusion_matrix(y_true, y_pred)
np.set_printoptions(precision=3)                    # precision 2 -> 3

# Plot non-normalized confusion matrix
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      cmap=plt.cm.YlGnBu,
                      title='Confusion matrix, without normalization')
plt.show()

# Plot normalized confusion matrix
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names, normalize=True, 
                      cmap=plt.cm.YlGnBu,
                      title='Normalized confusion matrix')
plt.show()
