# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# +
import pandas as pd
import numpy as np
import sklearn
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import time

df = pd.read_excel (r'RealEstate.xlsx')
no = pd.DataFrame(df, columns= ['X1 transaction date'])

# -

# ## Preproccesing Excel Data

df.reset_index().values
no = df['No'].to_numpy(np.float64)
x1 = df['X1 transaction date'].to_numpy(np.float64)
x2 = df['X2 house age'].to_numpy(np.float64)
x3 = df['X3 distance to the nearest MRT station'].to_numpy(np.float64)
x4 = df['X4 number of convenience stores'].to_numpy(np.float64)
x5 = df['X5 latitude'].to_numpy(np.float64)
x6 = df['X6 longitude'].to_numpy(np.float64)
y = df['Y house price of unit area'].to_numpy(np.float64)
y = y.reshape(len(y),1)
y.shape

# +
dataArr = np.empty([0,6])

for i in range(len(x1)):
    row = np.array([x1[i],x2[i],x3[i],x4[i],x5[i],x6[i]])
    dataArr = np.append(dataArr,np.array([row]),axis=0)
         
dataArr.shape
# -

## Scaling Output
scaler = MinMaxScaler((0, 1))
y_shape = y.shape
y = scaler.fit_transform(y.reshape(-1, 1))
y = y.reshape(y_shape)

x_train, x_test, y_train, y_test = train_test_split(dataArr, y, test_size=114, random_state=42) 

# +
## applying scaling to inputs

x_train_shape = x_train.shape
x_train = scaler.fit_transform(x_train.reshape(-1, 1))
x_train = x_train.reshape(x_train_shape)

x_test_shape = x_test.shape
x_test = scaler.transform(x_test.reshape(-1, 1))
x_test = x_test.reshape(x_test_shape)
# -

# ## Creating NN

import torch
from torch.autograd import Variable
import torch.nn.functional as F
import torch.utils.data as Data
import matplotlib.pyplot as plt


x_train.shape

# +
## Scaled Output NN

inputs = 6
firstLayer = 60
output = 1

net = torch.nn.Sequential(
        torch.nn.Linear(inputs, firstLayer),
        torch.nn.Sigmoid(),
        torch.nn.Linear(firstLayer, output),
        )

# +
optimizer = torch.optim.Adam(net.parameters(), lr=0.1)#lr of 1
loss_func = torch.nn.MSELoss(reduction='mean')  # this is for regression mean squared loss ---> can change to sum. Is mean currently

BATCH_SIZE = 150#len(x_train) #50 #40 
EPOCH = 1000 #40 #200

x_train = torch.from_numpy(x_train).float().detach()
y_train = torch.from_numpy(y_train).float()


torch_dataset = Data.TensorDataset(x_train, y_train)

x_train = x_train.float()
y_train = y_train.float()

loader = Data.DataLoader(
    dataset=torch_dataset, 
    batch_size=BATCH_SIZE, 
    shuffle=True, num_workers=2,)

# +
## Pytorch Grad Descent 

loss_graph = torch.empty(0)
start_time = time.perf_counter()
for epoch in range(EPOCH):
    for step, (batch_x, batch_y) in enumerate(loader): # for each training step
        
        b_x = Variable(batch_x,requires_grad=False)
        b_y = Variable(batch_y)

        y_pred = net(b_x.float())     # input x and predict based on x
        
#         loss =  loss_func(y_pred, b_y)     # must be (1. nn output, 2. target)
#         loss_graph = torch.cat((loss_graph, loss.detach().flatten()),0)
        
        ##Scaled Output Change
#         loss1 =  loss_func(torch.from_numpy(scaler.inverse_transform(y_pred.detach().numpy())), torch.from_numpy(scaler.inverse_transform(b_y.detach().numpy()))) 
        loss =  loss_func(y_pred, b_y) 
        loss_graph = torch.cat((loss_graph, loss.detach().flatten()),0) 
        print("error at epoch ",epoch,"is ",loss)

#         print("epoch: ",epoch," loss: ",loss.detach().numpy())
        optimizer.zero_grad()   # clear gradients for next train
        loss.backward()         # backpropagation, compute gradients
        optimizer.step()        # apply gradients

    if ((loss_graph[2*epoch+1]+loss_graph[2*epoch])/2 <0.007):
        break
        
print("time taken to execute: ",time.perf_counter()-start_time)
plt.plot(loss_graph.numpy()) 


# +
##LM 

# loss_graph = torch.empty(0)
# u=0.1 #beta multiply identity matrix
# b = 10
# V_prev = 0 #intialise performance index
# start_time = time.perf_counter()
# for epoch in range(EPOCH):
#     for step, (batch_x, batch_y) in enumerate(loader): # for each training step
        
#         b_x = Variable(batch_x,requires_grad=False)
#         b_y = batch_y.float()
#         y_pred = net(b_x)     # predict outputs based on x
# #         num_param = inputs*firstLayer+firstLayer+firstLayer*secondLayer+secondLayer+secondLayer*output+output
#         num_param = inputs*firstLayer+firstLayer+firstLayer*output+output
# #         num_param = inputs*firstLayer+firstLayer+firstLayer*secondLayer+secondLayer+secondLayer*thirdLayer+thirdLayer+ thirdLayer*output+output
        
#         jacobian = torch.empty(len(batch_y), num_param) # errors by weights

#         for n in range(len(batch_y)): #batch length is num of rows
#             net.zero_grad()
#             e = b_y[n]-y_pred[n]
#             e.backward(retain_graph=True)
# #             gradients=torch.autograd.grad(e, net.parameters(), create_graph=True)
# #             print(gradients.shape)
#             with torch.no_grad():
#                 param_grads = torch.empty(0) 

#                 for param in net.parameters():
#                     param_grads = torch.cat((param_grads, torch.flatten(param.grad).detach()),0) #puts gradient of params w.r.t e

#                 for m in range(num_param): #num params is num of coloumns
#                     jacobian[n][m] = param_grads[m]
#         with torch.no_grad():
#             #determine dParam  
#             V_current = 0.5*torch.sum(torch.square(b_y-y_pred)) #performance index
            
# #             print("V: ",V_current, "u is: ",u)
#             dParam = torch.matmul(torch.matmul(torch.inverse(torch.matmul(torch.transpose(jacobian,-1,0),jacobian) + u*torch.eye(num_param, num_param)),torch.transpose(jacobian,-1,0)),b_y-y_pred)
#             if(V_current > V_prev):
#                 u *= b
#             else:
#                 u /= b
#             V_prev = V_current
        


#             loss =  loss_func(y_pred, b_y) 
#             loss_graph = torch.cat((loss_graph, loss.detach().flatten()),0) 
#             print("error at epoch ",epoch,"is ",loss)

#             #update the params
#             c = 0
#         with torch.no_grad():    
#             for param in net.parameters():
#                 s = param.shape
#                 dParamUpdate = dParam[c:c+len(param.flatten())]
#                 c+= len(dParamUpdate)
#                 param -= 1*dParamUpdate.reshape(s)

            
    
#     if ((loss_graph[2*epoch+1]+loss_graph[2*epoch])/2 <0.007):
#         break
    
# print("time taken to execute: ",time.perf_counter()-start_time)   
# plt.plot(loss_graph.numpy())  
# -

y_test.dtype

x_test = torch.from_numpy(x_test).float()
y_test = torch.from_numpy(y_test).float()
y_pred = net(x_test)
y_pred.dtype

y_test = y_test.numpy()
y_pred = y_pred.detach().numpy()

# ## Plotting

# +
## Scaled Output

plt.subplot(2,1,1)


plt.plot(y_pred,'g.', y_train,'r.')

plt.ylabel('prop value')
plt.show()

# loss = torch.empty(len(y_test)) 
loss = torch.empty(len(y_test)).numpy() 

c=0;
for i in range(len(y_test)):
    loss[i] = abs(y_test[i]-y_pred[i])
    if loss[i] > (y_test[i]*0.1):
        c=c+1
#         print("Error ", c ,"is ", loss[i], "correct was",y_test[i],"predicted was ",y_pred[i])


# plt.subplot(2,1,2)
# plt.plot(loss,'bo')
# print("avg loss was", loss.sum()/len(loss)," avg val is",y_test.sum()/len(y_test))
# print("Minimum val is",min(y_test))
# print("Test set length: ",len(y_test)," % of prdeictions within 10%: ",100-c/len(y_test)*100)
# -

MSE = (np.square(loss))
print("MSE Mean: ",np.mean(MSE))
print("MSE STD Dev: ",np.std(MSE))

StarcoderdataJupyterScriptsDedupFiltered