Updated server code with sparse GP implementation
This commit is contained in:
parent
2cea33b73c
commit
3199f50051
5 changed files with 488 additions and 170 deletions
|
@ -2,18 +2,30 @@ import casadi as cs
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
|
from helpers import get_combined_evaluator
|
||||||
|
|
||||||
# Package the resulting regression model in a CasADi callback
|
# Package the resulting regression model in a CasADi callback
|
||||||
class GPR(cs.Callback):
|
class GPR(cs.Callback):
|
||||||
def __init__(self, name, model, opts={}):
|
def __init__(self, name, model, n_in, opts={}):
|
||||||
cs.Callback.__init__(self)
|
cs.Callback.__init__(self)
|
||||||
|
|
||||||
self.model = model
|
self.model = model
|
||||||
self.n_in = model.data[0].shape[1]
|
self.combined_evaluator = get_combined_evaluator(model)
|
||||||
|
self.n_in = n_in
|
||||||
|
self.tf_var = tf.Variable(np.ones((1, self.n_in)), dtype = tf.float64)
|
||||||
|
self.grads = None
|
||||||
# Create a variable to keep all the gradient callback references
|
# Create a variable to keep all the gradient callback references
|
||||||
self.refs = []
|
self.refs = []
|
||||||
|
|
||||||
self.construct(name, opts)
|
self.construct(name, opts)
|
||||||
|
|
||||||
|
# Update tf_evaluator
|
||||||
|
def update_model(self, model):
|
||||||
|
self.model = model
|
||||||
|
self.combined_evaluator = get_combined_evaluator(model)
|
||||||
|
|
||||||
|
def uses_output(self): return True
|
||||||
|
|
||||||
# Number of inputs/outputs
|
# Number of inputs/outputs
|
||||||
def get_n_in(self): return 1
|
def get_n_in(self): return 1
|
||||||
def get_n_out(self): return 1
|
def get_n_out(self): return 1
|
||||||
|
@ -27,14 +39,15 @@ class GPR(cs.Callback):
|
||||||
|
|
||||||
|
|
||||||
def eval(self, arg):
|
def eval(self, arg):
|
||||||
inp = np.array(arg[0])
|
self.tf_var.assign(arg[0])
|
||||||
inp = tf.Variable(inp, dtype=tf.float64)
|
preds, grads = self.combined_evaluator(self.tf_var)
|
||||||
[mean, _] = self.model.predict_f(inp)
|
[mean, _] = preds
|
||||||
|
self.grads = grads
|
||||||
return [mean.numpy()]
|
return [mean.numpy()]
|
||||||
|
|
||||||
def has_reverse(self, nadj): return nadj==1
|
def has_reverse(self, nadj): return nadj==1
|
||||||
def get_reverse(self, nadj, name, inames, onames, opts):
|
def get_reverse(self, nadj, name, inames, onames, opts):
|
||||||
grad_callback = GPR_grad(name, self.model)
|
grad_callback = GPR_grad(name, self.n_in, self.combined_evaluator)
|
||||||
self.refs.append(grad_callback)
|
self.refs.append(grad_callback)
|
||||||
|
|
||||||
nominal_in = self.mx_in()
|
nominal_in = self.mx_in()
|
||||||
|
@ -43,10 +56,12 @@ class GPR(cs.Callback):
|
||||||
return cs.Function(name, nominal_in+nominal_out+adj_seed, grad_callback.call(nominal_in), inames, onames)
|
return cs.Function(name, nominal_in+nominal_out+adj_seed, grad_callback.call(nominal_in), inames, onames)
|
||||||
|
|
||||||
class GPR_grad(cs.Callback):
|
class GPR_grad(cs.Callback):
|
||||||
def __init__(self, name, model, opts={}):
|
def __init__(self, name, n_in, combined_evaluator, opts={}):
|
||||||
cs.Callback.__init__(self)
|
cs.Callback.__init__(self)
|
||||||
self.model = model
|
|
||||||
self.n_in = model.data[0].shape[1]
|
self.combined_evaluator = combined_evaluator
|
||||||
|
self.n_in = n_in
|
||||||
|
self.tf_var = tf.Variable(np.ones((1, self.n_in)), dtype = tf.float64)
|
||||||
|
|
||||||
self.construct(name, opts)
|
self.construct(name, opts)
|
||||||
|
|
||||||
|
@ -58,14 +73,10 @@ class GPR_grad(cs.Callback):
|
||||||
return cs.Sparsity.dense(1,self.n_in)
|
return cs.Sparsity.dense(1,self.n_in)
|
||||||
def get_sparsity_out(self,i):
|
def get_sparsity_out(self,i):
|
||||||
return cs.Sparsity.dense(1,self.n_in)
|
return cs.Sparsity.dense(1,self.n_in)
|
||||||
|
|
||||||
|
|
||||||
def eval(self, arg):
|
def eval(self, arg):
|
||||||
inp = np.array(arg[0])
|
self.tf_var.assign(arg[0])
|
||||||
inp = tf.Variable(inp, dtype=tf.float64)
|
_, grads = self.combined_evaluator(self.tf_var)
|
||||||
|
|
||||||
with tf.GradientTape() as tape:
|
|
||||||
preds = self.model.predict_f(inp)
|
|
||||||
|
|
||||||
grads = tape.gradient(preds, inp)
|
|
||||||
return [grads.numpy()]
|
return [grads.numpy()]
|
||||||
|
|
|
@ -8,6 +8,10 @@ import pandas as pd
|
||||||
import gpflow
|
import gpflow
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
|
from gpflow.ci_utils import ci_niter
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
from sklearn.preprocessing import MinMaxScaler
|
from sklearn.preprocessing import MinMaxScaler
|
||||||
|
|
||||||
import callbacks
|
import callbacks
|
||||||
|
@ -43,44 +47,14 @@ class PIDcontroller:
|
||||||
return sig_P + sig_I + sig_D
|
return sig_P + sig_I + sig_D
|
||||||
|
|
||||||
|
|
||||||
|
class Base_MPCcontroller(object):
|
||||||
class GP_MPCcontroller:
|
|
||||||
def __init__(self, dict_cols, model = None, scaler = None, N_horizon = 10, recover_from_crash = False):
|
def __init__(self, dict_cols, model = None, scaler = None, N_horizon = 10, recover_from_crash = False):
|
||||||
|
|
||||||
|
|
||||||
self.recover_from_crash = recover_from_crash
|
|
||||||
|
|
||||||
if self.recover_from_crash:
|
|
||||||
self.model = pickle.load(open("controller_model.pkl", 'rb'))
|
|
||||||
self.scaler = pickle.load(open("controller_scaler.pkl", 'rb'))
|
|
||||||
self.X_log = pickle.load(open("controller_X_log.pkl", 'rb'))
|
|
||||||
df = pd.read_pickle("controller_df.pkl")
|
|
||||||
self.recovery_signal = iter(df['SimulatedHeat'])
|
|
||||||
|
|
||||||
|
|
||||||
if model is not None:
|
|
||||||
# Model is already trained. Using as is.
|
|
||||||
if scaler is None: raise ValueError("Not allowed to pass a model without a scaler")
|
|
||||||
self.model = model
|
|
||||||
self.cs_model = callbacks.GPR("gpr", self.model)
|
|
||||||
self.scaler = scaler
|
|
||||||
self.scaler_helper = ScalerHelper(self.scaler)
|
|
||||||
else:
|
|
||||||
# No model has been passed. Setting up model initialization
|
|
||||||
self.model = None
|
|
||||||
self.nb_data = 500 + 1
|
|
||||||
self.ident_signal = get_random_signal(self.nb_data, signal_type = 'analog')
|
|
||||||
|
|
||||||
|
|
||||||
self.Pel = 2 * 6300
|
|
||||||
self.COP = 5.0
|
|
||||||
|
|
||||||
self.ident_signal = iter(self.Pel * self.COP * self.ident_signal)
|
|
||||||
|
|
||||||
|
|
||||||
self.dict_cols = dict_cols
|
self.dict_cols = dict_cols
|
||||||
self.max_lag = max([lag for lag,_ in self.dict_cols.values()])
|
self.max_lag = max([lag for lag,_ in self.dict_cols.values()])
|
||||||
self.N_horizon = N_horizon
|
self.N_horizon = N_horizon
|
||||||
|
self.n_states = np.sum([len(cols) * lags for lags,cols in
|
||||||
|
self.dict_cols.values()])
|
||||||
self.X_log = []
|
self.X_log = []
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,119 +65,50 @@ class GP_MPCcontroller:
|
||||||
self.data_cols += cols
|
self.data_cols += cols
|
||||||
self.data = np.empty((0, len(self.data_cols)))
|
self.data = np.empty((0, len(self.data_cols)))
|
||||||
|
|
||||||
|
# Dataset used for training
|
||||||
|
self.dataset_train_minsize = 500
|
||||||
|
self.dataset_train_maxsize = 500
|
||||||
|
self.dataset_train = np.empty((0, self.n_states))
|
||||||
|
|
||||||
# The current weather forcast
|
# The current weather forcast
|
||||||
self.weather_forecast = None
|
self.weather_forecast = None
|
||||||
|
|
||||||
# Current measurements
|
# Current measurements
|
||||||
self.w, self.u, self.y = None, None, None
|
self.w, self.u, self.y = None, None, None
|
||||||
|
|
||||||
|
# Recover from a previous crash with precomputed values and continue
|
||||||
|
self.recover_from_crash = recover_from_crash
|
||||||
|
|
||||||
|
if self.recover_from_crash:
|
||||||
|
self.model = pickle.load(open("controller_model.pkl", 'rb'))
|
||||||
|
self.scaler = pickle.load(open("controller_scaler.pkl", 'rb'))
|
||||||
|
self.scaler_helper = ScalerHelper(self.scaler)
|
||||||
|
self.X_log = pickle.load(open("controller_X_log.pkl", 'rb'))
|
||||||
|
df = pd.read_pickle("controller_df.pkl")
|
||||||
|
self.recovery_signal = iter(df['SimulatedHeat'])
|
||||||
|
return
|
||||||
|
|
||||||
###
|
# Pre-existing model passed. Load all the necessary objects
|
||||||
# GPflow model training and update
|
if model is not None:
|
||||||
###
|
# Model is already trained. Using as is.
|
||||||
def _train_model(self):
|
if scaler is None: raise ValueError("Not allowed to pass a model without a scaler")
|
||||||
""" Identify model from gathered data """
|
self.model = model
|
||||||
|
self.cs_model = callbacks.GPR("gpr", self.model, self.n_states)
|
||||||
|
self.scaler = scaler
|
||||||
|
self.scaler_helper = ScalerHelper(self.scaler)
|
||||||
|
# No pre-existing model. Set up data acquisition and model training
|
||||||
|
else:
|
||||||
|
# No model has been passed. Setting up model initialization
|
||||||
|
self.model = None
|
||||||
|
|
||||||
nb_train_pts = self.nb_data - 1
|
# Define an identification signal to be used first
|
||||||
###
|
self.Pel = 2 * 6300
|
||||||
# Dataset
|
self.COP = 5.0
|
||||||
###
|
|
||||||
df = pd.DataFrame(self.data[:nb_train_pts], columns = self.data_cols)
|
|
||||||
self.scaler = MinMaxScaler(feature_range = (-1, 1))
|
|
||||||
self.scaler_helper = ScalerHelper(self.scaler)
|
|
||||||
df_sc = get_scaled_df(df, self.dict_cols, self.scaler)
|
|
||||||
df_gpr_train = data_to_gpr(df_sc, self.dict_cols)
|
|
||||||
|
|
||||||
df_input_train = df_gpr_train.drop(columns = self.dict_cols['w'][1] + self.dict_cols['u'][1] + self.dict_cols['y'][1])
|
self.ident_signal = get_identification_signal(size = self.dataset_train_minsize)
|
||||||
df_output_train = df_gpr_train[self.dict_cols['y'][1]]
|
self.ident_signal = iter(self.COP * self.Pel * self.ident_signal)
|
||||||
|
|
||||||
np_input_train = df_input_train.to_numpy()
|
return
|
||||||
np_output_train = df_output_train.to_numpy().reshape(-1, 1)
|
|
||||||
|
|
||||||
data_train = (np_input_train, np_output_train)
|
|
||||||
|
|
||||||
df_test = pd.DataFrame(self.data[nb_train_pts:], columns = self.data_cols)
|
|
||||||
df_test_sc = get_scaled_df(df_test, self.dict_cols, self.scaler)
|
|
||||||
df_gpr_test = data_to_gpr(df_test_sc, self.dict_cols)
|
|
||||||
df_input_test = df_gpr_test.drop(columns = self.dict_cols['w'][1] + self.dict_cols['u'][1] + self.dict_cols['y'][1])
|
|
||||||
df_output_test = df_gpr_test[self.dict_cols['y'][1]]
|
|
||||||
np_input_test = df_input_test.to_numpy()
|
|
||||||
np_output_test = df_output_test.to_numpy()
|
|
||||||
|
|
||||||
###
|
|
||||||
# Kernel
|
|
||||||
###
|
|
||||||
|
|
||||||
nb_dims = np_input_train.shape[1]
|
|
||||||
rational_dims = np.arange(0, (self.dict_cols['t'][0] + 1) * len(self.dict_cols['t'][1]), 1)
|
|
||||||
nb_rational_dims = len(rational_dims)
|
|
||||||
squared_dims = np.arange(nb_rational_dims, nb_dims, 1)
|
|
||||||
nb_squared_dims = len(squared_dims)
|
|
||||||
|
|
||||||
default_lscale = 75
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
|
|
||||||
squared_l = np.linspace(default_lscale, 2*default_lscale, nb_squared_dims)
|
|
||||||
rational_l = np.linspace(default_lscale, 2*default_lscale, nb_rational_dims)
|
|
||||||
|
|
||||||
variance = tf.math.reduce_variance(np_input_train)
|
|
||||||
|
|
||||||
k0 = gpflow.kernels.SquaredExponential(lengthscales = squared_l, active_dims = squared_dims, variance = variance)
|
|
||||||
k1 = gpflow.kernels.Constant(variance = variance)
|
|
||||||
k2 = gpflow.kernels.RationalQuadratic(lengthscales = rational_l, active_dims = rational_dims, variance = variance)
|
|
||||||
k3 = gpflow.kernels.Periodic(k2)
|
|
||||||
|
|
||||||
k = (k0 + k1) * k2
|
|
||||||
k = k0
|
|
||||||
|
|
||||||
###
|
|
||||||
# Model
|
|
||||||
###
|
|
||||||
|
|
||||||
m = gpflow.models.GPR(
|
|
||||||
data = data_train,
|
|
||||||
kernel = k,
|
|
||||||
mean_function = None,
|
|
||||||
)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Training
|
|
||||||
###
|
|
||||||
print(f"Training a model with lscale:{default_lscale}")
|
|
||||||
opt = gpflow.optimizers.Scipy()
|
|
||||||
opt.minimize(m.training_loss, m.trainable_variables)
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
print(f"Failed.Increasing lengthscale")
|
|
||||||
default_lscale += 5
|
|
||||||
|
|
||||||
###
|
|
||||||
# Save model
|
|
||||||
###
|
|
||||||
self.model = m
|
|
||||||
self.n_states = self.model.data[0].shape[1]
|
|
||||||
|
|
||||||
# # Manual model validation
|
|
||||||
# import matplotlib.pyplot as plt
|
|
||||||
#
|
|
||||||
# plt.figure()
|
|
||||||
# plt.plot(data_train[1], label = 'real')
|
|
||||||
# mean, var = self.model.predict_f(data_train[0])
|
|
||||||
# plt.plot(mean, label = 'model')
|
|
||||||
# plt.legend()
|
|
||||||
# plt.show()
|
|
||||||
#
|
|
||||||
# plt.figure()
|
|
||||||
# plt.plot(np_output_test, label = 'real')
|
|
||||||
# mean, var = self.model.predict_f(np_input_test)
|
|
||||||
# plt.plot(mean, label = 'model')
|
|
||||||
# plt.legend()
|
|
||||||
# plt.show()
|
|
||||||
#
|
|
||||||
# import pdb; pdb.set_trace()
|
|
||||||
pass
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Update measurements
|
# Update measurements
|
||||||
|
@ -218,18 +123,16 @@ class GP_MPCcontroller:
|
||||||
def _add_input_measurement(self, u):
|
def _add_input_measurement(self, u):
|
||||||
self.u = np.array(u).reshape(1, -1)
|
self.u = np.array(u).reshape(1, -1)
|
||||||
|
|
||||||
|
def _add_measurement_set(self):
|
||||||
|
new_data = np.hstack([self.w, self.u, self.y])
|
||||||
|
self.data = np.vstack([self.data, new_data])
|
||||||
|
print(f"{self.data.shape[0]} data points. Newest: {new_data}")
|
||||||
|
self.w, self.u, self.y = None, None, None
|
||||||
|
|
||||||
def set_weather_forecast(self, W):
|
def set_weather_forecast(self, W):
|
||||||
assert (W.shape[0] == self.N_horizon)
|
assert (W.shape[0] == self.N_horizon)
|
||||||
self.weather_forecast = W
|
self.weather_forecast = W
|
||||||
|
|
||||||
def update_model(self):
|
|
||||||
new_data = np.hstack([self.w, self.u, self.y])
|
|
||||||
print(f"Adding new data: {new_data}")
|
|
||||||
self.data = np.vstack([self.data, new_data])
|
|
||||||
print(f"Data size: {self.data.shape[0]}")
|
|
||||||
self.w, self.u, self.y = None, None, None
|
|
||||||
print("---")
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Set up optimal problem solver
|
# Set up optimal problem solver
|
||||||
###
|
###
|
||||||
|
@ -239,10 +142,11 @@ class GP_MPCcontroller:
|
||||||
###
|
###
|
||||||
# Initialization
|
# Initialization
|
||||||
###
|
###
|
||||||
self.cs_model = callbacks.GPR("gpr", self.model)
|
self.cs_model = callbacks.GPR("gpr", self.model, self.n_states)
|
||||||
|
|
||||||
T_set = 21
|
T_set = 21
|
||||||
T_set_sc = self.scaler_helper.scale_output(T_set)
|
T_set_sc = self.scaler_helper.scale_output(T_set)
|
||||||
|
self.T_set_sc = T_set_sc
|
||||||
|
|
||||||
|
|
||||||
X = cs.MX.sym("X", self.N_horizon + 1, self.n_states)
|
X = cs.MX.sym("X", self.N_horizon + 1, self.n_states)
|
||||||
|
@ -311,7 +215,7 @@ class GP_MPCcontroller:
|
||||||
|
|
||||||
prob = {'f': J, 'x': cs.vec(X), 'g': cs.vertcat(*g), 'p': p}
|
prob = {'f': J, 'x': cs.vec(X), 'g': cs.vertcat(*g), 'p': p}
|
||||||
options = {"ipopt": {"hessian_approximation": "limited-memory", "max_iter": 100,
|
options = {"ipopt": {"hessian_approximation": "limited-memory", "max_iter": 100,
|
||||||
#"acceptable_tol": 1e-6, "tol": 1e-6,
|
"acceptable_tol": 1e-4, "tol": 1e-4,
|
||||||
#"linear_solver": "ma57",
|
#"linear_solver": "ma57",
|
||||||
#"acceptable_obj_change_tol": 1e-5,
|
#"acceptable_obj_change_tol": 1e-5,
|
||||||
#"mu_strategy": "adaptive",
|
#"mu_strategy": "adaptive",
|
||||||
|
@ -341,6 +245,7 @@ class GP_MPCcontroller:
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
# Finished passing in the pre-existing control
|
# Finished passing in the pre-existing control
|
||||||
# Switching to normal operation
|
# Switching to normal operation
|
||||||
|
self._setup_solver()
|
||||||
self.recover_from_crash = False
|
self.recover_from_crash = False
|
||||||
|
|
||||||
# Training pre-loop
|
# Training pre-loop
|
||||||
|
@ -360,6 +265,10 @@ class GP_MPCcontroller:
|
||||||
|
|
||||||
|
|
||||||
data_scaled = self.scaler.transform(self.data)
|
data_scaled = self.scaler.transform(self.data)
|
||||||
|
# Append a dummy row to data, to align data_to_gpr cols (which include measureemnt
|
||||||
|
# at time t, with the current measurements, that stop at t-1)
|
||||||
|
dummy_row = np.nan * np.ones((1, self.data.shape[1]))
|
||||||
|
data_scaled = np.vstack([data_scaled, dummy_row])
|
||||||
df = pd.DataFrame(data_scaled, columns = self.data_cols)
|
df = pd.DataFrame(data_scaled, columns = self.data_cols)
|
||||||
|
|
||||||
x0 = data_to_gpr(df, self.dict_cols).drop(
|
x0 = data_to_gpr(df, self.dict_cols).drop(
|
||||||
|
@ -384,6 +293,8 @@ class GP_MPCcontroller:
|
||||||
|
|
||||||
X = np.array(res['x'].reshape((self.N_horizon + 1, self.n_states)))
|
X = np.array(res['x'].reshape((self.N_horizon + 1, self.n_states)))
|
||||||
self.X_log.append(X)
|
self.X_log.append(X)
|
||||||
|
df_X = pd.DataFrame(X)
|
||||||
|
#import pdb; pdb.set_trace()
|
||||||
u_idx = self.dict_cols['w'][0] * len(self.dict_cols['w'][1])
|
u_idx = self.dict_cols['w'][0] * len(self.dict_cols['w'][1])
|
||||||
# Take the first control action and apply it
|
# Take the first control action and apply it
|
||||||
u = X[1, u_idx]
|
u = X[1, u_idx]
|
||||||
|
@ -403,6 +314,363 @@ class GP_MPCcontroller:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class GP_MPCcontroller(Base_MPCcontroller):
|
||||||
|
def __init__(self, dict_cols, model = None, scaler = None, N_horizon = 10, recover_from_crash = False):
|
||||||
|
super().__init__(dict_cols, model, scaler, N_horizon, recover_from_crash)
|
||||||
|
|
||||||
|
###
|
||||||
|
# GPflow model training and update
|
||||||
|
###
|
||||||
|
def _train_model(self):
|
||||||
|
""" Identify model from gathered data """
|
||||||
|
|
||||||
|
nb_train_pts = self.dataset_train_minsize - 1
|
||||||
|
nb_train_pts = 500
|
||||||
|
###
|
||||||
|
# Dataset
|
||||||
|
###
|
||||||
|
df = pd.DataFrame(self.data[:nb_train_pts], columns = self.data_cols)
|
||||||
|
self.scaler = MinMaxScaler(feature_range = (-1, 1))
|
||||||
|
self.scaler_helper = ScalerHelper(self.scaler)
|
||||||
|
df_sc = get_scaled_df(df, self.dict_cols, self.scaler)
|
||||||
|
df_gpr_train = data_to_gpr(df_sc, self.dict_cols)
|
||||||
|
|
||||||
|
df_input_train = df_gpr_train.drop(columns = self.dict_cols['w'][1] + self.dict_cols['u'][1] + self.dict_cols['y'][1])
|
||||||
|
df_output_train = df_gpr_train[self.dict_cols['y'][1]]
|
||||||
|
|
||||||
|
np_input_train = df_input_train.to_numpy()
|
||||||
|
np_output_train = df_output_train.to_numpy().reshape(-1, 1)
|
||||||
|
|
||||||
|
data_train = (np_input_train, np_output_train)
|
||||||
|
|
||||||
|
df_test = pd.DataFrame(self.data[nb_train_pts:], columns = self.data_cols)
|
||||||
|
df_test_sc = get_scaled_df(df_test, self.dict_cols, self.scaler)
|
||||||
|
df_gpr_test = data_to_gpr(df_test_sc, self.dict_cols)
|
||||||
|
df_input_test = df_gpr_test.drop(columns = self.dict_cols['w'][1] + self.dict_cols['u'][1] + self.dict_cols['y'][1])
|
||||||
|
df_output_test = df_gpr_test[self.dict_cols['y'][1]]
|
||||||
|
np_input_test = df_input_test.to_numpy()
|
||||||
|
np_output_test = df_output_test.to_numpy()
|
||||||
|
|
||||||
|
###
|
||||||
|
# Kernel
|
||||||
|
###
|
||||||
|
|
||||||
|
nb_dims = np_input_train.shape[1]
|
||||||
|
rational_dims = np.arange(0, (self.dict_cols['t'][0] + 1) * len(self.dict_cols['t'][1]), 1)
|
||||||
|
nb_rational_dims = len(rational_dims)
|
||||||
|
squared_dims = np.arange(nb_rational_dims, nb_dims, 1)
|
||||||
|
nb_squared_dims = len(squared_dims)
|
||||||
|
|
||||||
|
default_lscale = 150
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
|
||||||
|
squared_l = np.linspace(default_lscale, 2*default_lscale, nb_squared_dims)
|
||||||
|
rational_l = np.linspace(default_lscale, 2*default_lscale, nb_rational_dims)
|
||||||
|
|
||||||
|
variance = tf.math.reduce_variance(np_input_train)
|
||||||
|
|
||||||
|
k0 = gpflow.kernels.SquaredExponential(lengthscales = squared_l, active_dims = squared_dims, variance = variance)
|
||||||
|
k1 = gpflow.kernels.Constant(variance = variance)
|
||||||
|
k2 = gpflow.kernels.RationalQuadratic(lengthscales = rational_l, active_dims = rational_dims, variance = variance)
|
||||||
|
k3 = gpflow.kernels.Periodic(k2)
|
||||||
|
|
||||||
|
k = (k0 + k1) * k2
|
||||||
|
k = k0
|
||||||
|
|
||||||
|
###
|
||||||
|
# Model
|
||||||
|
###
|
||||||
|
|
||||||
|
m = gpflow.models.GPR(
|
||||||
|
data = data_train,
|
||||||
|
kernel = k,
|
||||||
|
mean_function = None,
|
||||||
|
)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Training
|
||||||
|
###
|
||||||
|
print(f"Training a model with lscale:{default_lscale}")
|
||||||
|
opt = gpflow.optimizers.Scipy()
|
||||||
|
opt.minimize(m.training_loss, m.trainable_variables)
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
print(f"Failed.Increasing lengthscale")
|
||||||
|
default_lscale += 10
|
||||||
|
|
||||||
|
###
|
||||||
|
# Save model
|
||||||
|
###
|
||||||
|
self.model = m
|
||||||
|
|
||||||
|
# plt.figure()
|
||||||
|
#
|
||||||
|
# # Testing on training data
|
||||||
|
# mean, var = m.predict_f(np_input_train)
|
||||||
|
#
|
||||||
|
# plt.plot(df_input_train.index, np_output_train[:, :], label = 'Measured data')
|
||||||
|
# plt.plot(df_input_train.index, mean[:, :], label = 'Gaussian Process Prediction')
|
||||||
|
# plt.fill_between(
|
||||||
|
# df_input_train.index,
|
||||||
|
# mean[:, 0] - 1.96 * np.sqrt(var[:, 0]),
|
||||||
|
# mean[:, 0] + 1.96 * np.sqrt(var[:, 0]),
|
||||||
|
# alpha = 0.2
|
||||||
|
# )
|
||||||
|
# plt.show()
|
||||||
|
#
|
||||||
|
# plt.figure()
|
||||||
|
# # Testing on testing data
|
||||||
|
# mean, var = m.predict_f(np_input_test)
|
||||||
|
#
|
||||||
|
# plt.plot(df_input_test.index, np_output_test[:, :], label = 'Measured data')
|
||||||
|
# plt.plot(df_input_test.index, mean[:, :], label = 'Gaussian Process Prediction')
|
||||||
|
# plt.fill_between(
|
||||||
|
# df_input_test.index,
|
||||||
|
# mean[:, 0] - 1.96 * np.sqrt(var[:, 0]),
|
||||||
|
# mean[:, 0] + 1.96 * np.sqrt(var[:, 0]),
|
||||||
|
# alpha = 0.2
|
||||||
|
# )
|
||||||
|
# plt.show()
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# import pdb; pdb.set_trace()
|
||||||
|
# plt.figure()
|
||||||
|
#
|
||||||
|
# start_idx = 25
|
||||||
|
# nb_predictions = 25
|
||||||
|
# N_pred = 20
|
||||||
|
#
|
||||||
|
# plt.figure()
|
||||||
|
#
|
||||||
|
# y_name = self.dict_cols['y'][1][0]
|
||||||
|
# for idx in range(start_idx, start_idx + nb_predictions):
|
||||||
|
# df_iter = df_input_test.iloc[idx:(idx + N_pred)].copy()
|
||||||
|
# for idxx in range(N_pred - 1):
|
||||||
|
# idx_old = df_iter.index[idxx]
|
||||||
|
# idx_new = df_iter.index[idxx+1]
|
||||||
|
# mean, var = m.predict_f(df_iter.loc[idx_old, :].to_numpy().reshape(1, -1))
|
||||||
|
# df_iter.loc[idx_new, f'{y_name}_1'] = mean.numpy().flatten()
|
||||||
|
# for lag in range(2, self.dict_cols['y'][0] + 1):
|
||||||
|
# df_iter.loc[idx_new, f"{y_name}_{lag}"] = df_iter.loc[idx_old, f"{y_name}_{lag-1}"]
|
||||||
|
#
|
||||||
|
# mean_iter, var_iter = m.predict_f(df_iter.to_numpy())
|
||||||
|
# plt.plot(df_iter.index, mean_iter.numpy(), '.-', label = 'predicted', color = 'orange')
|
||||||
|
# plt.plot(df_output_test.iloc[start_idx:start_idx + nb_predictions + N_pred], 'o-', label = 'measured', color = 'darkblue')
|
||||||
|
# plt.title(f"Prediction over {N_pred} steps")
|
||||||
|
#
|
||||||
|
# plt.show()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def update_model(self):
|
||||||
|
self._add_measurement_set()
|
||||||
|
|
||||||
|
|
||||||
|
class SVGP_MPCcontroller(Base_MPCcontroller):
|
||||||
|
def __init__(self, dict_cols, model = None, scaler = None, N_horizon = 10, recover_from_crash = False):
|
||||||
|
super().__init__(dict_cols, model, scaler, N_horizon, recover_from_crash)
|
||||||
|
|
||||||
|
# Adaptive models update parameters
|
||||||
|
self.model_update_frequency = 100
|
||||||
|
self.pts_since_update = 0
|
||||||
|
|
||||||
|
# Model log
|
||||||
|
self.model_log = []
|
||||||
|
|
||||||
|
###
|
||||||
|
# GPflow model training and update
|
||||||
|
###
|
||||||
|
def _train_model(self):
|
||||||
|
""" Identify model from gathered data """
|
||||||
|
|
||||||
|
#nb_train_pts = self.dataset_train_minsize - 1
|
||||||
|
nb_train_pts = self.data.shape[0]
|
||||||
|
###
|
||||||
|
# Dataset
|
||||||
|
###
|
||||||
|
df = pd.DataFrame(self.data[:nb_train_pts], columns = self.data_cols)
|
||||||
|
self.scaler = MinMaxScaler(feature_range = (-1, 1))
|
||||||
|
self.scaler_helper = ScalerHelper(self.scaler)
|
||||||
|
df_sc = get_scaled_df(df, self.dict_cols, self.scaler)
|
||||||
|
df_gpr_train = data_to_gpr(df_sc, self.dict_cols)
|
||||||
|
|
||||||
|
df_input_train = df_gpr_train.drop(columns = self.dict_cols['w'][1] + self.dict_cols['u'][1] + self.dict_cols['y'][1])
|
||||||
|
df_output_train = df_gpr_train[self.dict_cols['y'][1]]
|
||||||
|
|
||||||
|
np_input_train = df_input_train.to_numpy()
|
||||||
|
np_output_train = df_output_train.to_numpy().reshape(-1, 1)
|
||||||
|
|
||||||
|
data_train = (np_input_train, np_output_train)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Kernel
|
||||||
|
###
|
||||||
|
|
||||||
|
nb_dims = np_input_train.shape[1]
|
||||||
|
rational_dims = np.arange(0, (self.dict_cols['t'][0] + 1) * len(self.dict_cols['t'][1]), 1)
|
||||||
|
nb_rational_dims = len(rational_dims)
|
||||||
|
squared_dims = np.arange(nb_rational_dims, nb_dims, 1)
|
||||||
|
nb_squared_dims = len(squared_dims)
|
||||||
|
|
||||||
|
default_lscale = 1
|
||||||
|
|
||||||
|
squared_l = np.linspace(default_lscale, 2*default_lscale, nb_squared_dims)
|
||||||
|
rational_l = np.linspace(default_lscale, 2*default_lscale, nb_rational_dims)
|
||||||
|
|
||||||
|
variance = tf.math.reduce_variance(np_input_train)
|
||||||
|
|
||||||
|
k0 = gpflow.kernels.SquaredExponential(lengthscales = squared_l, active_dims = squared_dims, variance = variance)
|
||||||
|
k1 = gpflow.kernels.Constant(variance = variance)
|
||||||
|
k2 = gpflow.kernels.RationalQuadratic(lengthscales = rational_l, active_dims = rational_dims, variance = variance)
|
||||||
|
k3 = gpflow.kernels.Periodic(k2)
|
||||||
|
|
||||||
|
k = (k0 + k1) * k2
|
||||||
|
k = k0
|
||||||
|
|
||||||
|
###
|
||||||
|
# Model
|
||||||
|
###
|
||||||
|
|
||||||
|
N = data_train[0].shape[0]
|
||||||
|
M = 150 # Number of inducing locations
|
||||||
|
Z = data_train[0][:M, :].copy()
|
||||||
|
|
||||||
|
m = gpflow.models.SVGP(k, gpflow.likelihoods.Gaussian(), Z, num_data = N)
|
||||||
|
|
||||||
|
elbo = tf.function(m.elbo)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Training
|
||||||
|
###
|
||||||
|
|
||||||
|
minibatch_size = 100
|
||||||
|
train_dataset = tf.data.Dataset.from_tensor_slices(data_train).repeat().shuffle(N)
|
||||||
|
|
||||||
|
# Turn off training for inducing point locations
|
||||||
|
gpflow.set_trainable(m.inducing_variable, False)
|
||||||
|
|
||||||
|
def run_adam(model, iterations):
|
||||||
|
"""
|
||||||
|
Utility function running the Adam optimizer
|
||||||
|
|
||||||
|
:param model: GPflow model
|
||||||
|
:param interations: number of iterations
|
||||||
|
"""
|
||||||
|
# Create an Adam Optimizer action
|
||||||
|
logf = []
|
||||||
|
train_iter = iter(train_dataset.batch(minibatch_size))
|
||||||
|
training_loss = model.training_loss_closure(train_iter, compile=True)
|
||||||
|
optimizer = tf.optimizers.Adam()
|
||||||
|
|
||||||
|
@tf.function
|
||||||
|
def optimization_step():
|
||||||
|
optimizer.minimize(training_loss, model.trainable_variables)
|
||||||
|
|
||||||
|
for step in range(iterations):
|
||||||
|
optimization_step()
|
||||||
|
if step % 10 == 0:
|
||||||
|
elbo = -training_loss().numpy()
|
||||||
|
logf.append(elbo)
|
||||||
|
return logf
|
||||||
|
|
||||||
|
|
||||||
|
maxiter = ci_niter(10000)
|
||||||
|
logf = run_adam(m, maxiter)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Save model
|
||||||
|
###
|
||||||
|
self.model = m
|
||||||
|
|
||||||
|
# plt.figure()
|
||||||
|
#
|
||||||
|
# # Testing on training data
|
||||||
|
# mean, var = m.predict_f(np_input_train)
|
||||||
|
#
|
||||||
|
# plt.plot(df_input_train.index, np_output_train[:, :], label = 'Measured data')
|
||||||
|
# plt.plot(df_input_train.index, mean[:, :], label = 'Gaussian Process Prediction')
|
||||||
|
# plt.fill_between(
|
||||||
|
# df_input_train.index,
|
||||||
|
# mean[:, 0] - 1.96 * np.sqrt(var[:, 0]),
|
||||||
|
# mean[:, 0] + 1.96 * np.sqrt(var[:, 0]),
|
||||||
|
# alpha = 0.2
|
||||||
|
# )
|
||||||
|
# plt.show()
|
||||||
|
#
|
||||||
|
# plt.figure()
|
||||||
|
# # Testing on testing data
|
||||||
|
# mean, var = m.predict_f(np_input_test)
|
||||||
|
#
|
||||||
|
# plt.plot(df_input_test.index, np_output_test[:, :], label = 'Measured data')
|
||||||
|
# plt.plot(df_input_test.index, mean[:, :], label = 'Gaussian Process Prediction')
|
||||||
|
# plt.fill_between(
|
||||||
|
# df_input_test.index,
|
||||||
|
# mean[:, 0] - 1.96 * np.sqrt(var[:, 0]),
|
||||||
|
# mean[:, 0] + 1.96 * np.sqrt(var[:, 0]),
|
||||||
|
# alpha = 0.2
|
||||||
|
# )
|
||||||
|
# plt.show()
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# plt.figure()
|
||||||
|
#
|
||||||
|
# start_idx = 25
|
||||||
|
# nb_predictions = 25
|
||||||
|
# N_pred = 20
|
||||||
|
#
|
||||||
|
# plt.figure()
|
||||||
|
#
|
||||||
|
# y_name = self.dict_cols['y'][1][0]
|
||||||
|
# for idx in range(start_idx, start_idx + nb_predictions):
|
||||||
|
# df_iter = df_input_test.iloc[idx:(idx + N_pred)].copy()
|
||||||
|
# for idxx in range(N_pred - 1):
|
||||||
|
# idx_old = df_iter.index[idxx]
|
||||||
|
# idx_new = df_iter.index[idxx+1]
|
||||||
|
# mean, var = m.predict_f(df_iter.loc[idx_old, :].to_numpy().reshape(1, -1))
|
||||||
|
# df_iter.loc[idx_new, f'{y_name}_1'] = mean.numpy().flatten()
|
||||||
|
# for lag in range(2, self.dict_cols['y'][0] + 1):
|
||||||
|
# df_iter.loc[idx_new, f"{y_name}_{lag}"] = df_iter.loc[idx_old, f"{y_name}_{lag-1}"]
|
||||||
|
#
|
||||||
|
# mean_iter, var_iter = m.predict_f(df_iter.to_numpy())
|
||||||
|
# plt.plot(df_iter.index, mean_iter.numpy(), '.-', label = 'predicted', color = 'orange')
|
||||||
|
# plt.plot(df_output_test.iloc[start_idx:start_idx + nb_predictions + N_pred], 'o-', label = 'measured', color = 'darkblue')
|
||||||
|
# plt.title(f"Prediction over {N_pred} steps")
|
||||||
|
#
|
||||||
|
# plt.show()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def update_model(self):
|
||||||
|
self._add_measurement_set()
|
||||||
|
if self.model is not None and not self.recover_from_crash:
|
||||||
|
print(f"Points since last update: {self.pts_since_update}")
|
||||||
|
if self.pts_since_update < self.model_update_frequency:
|
||||||
|
self.pts_since_update += 1
|
||||||
|
else:
|
||||||
|
print(f"Updating model after {self.pts_since_update} measurements")
|
||||||
|
# Append old model to log
|
||||||
|
self.model_log.append(self.model)
|
||||||
|
# Train new model
|
||||||
|
self._train_model()
|
||||||
|
# Re-initialize CasADi solver
|
||||||
|
self._setup_solver()
|
||||||
|
self.pts_since_update = 0
|
||||||
|
|
||||||
|
# Redefine save_data since now we're also saving the model log
|
||||||
|
def save_data(self):
|
||||||
|
df = pd.DataFrame(self.data, columns = self.data_cols)
|
||||||
|
df.to_pickle("controller_df.pkl")
|
||||||
|
|
||||||
|
pickle.dump(self.scaler, open(Path("controller_scaler.pkl"), 'wb'))
|
||||||
|
pickle.dump(self.model, open(Path("controller_model.pkl"), 'wb'))
|
||||||
|
pickle.dump(self.X_log, open(Path("controller_X_log.pkl"), 'wb'))
|
||||||
|
pickle.dump(self.model_log, open(Path("controller_model_log.pkl"), 'wb'))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
class TestController:
|
class TestController:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,9 +1,36 @@
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import gpflow
|
import gpflow
|
||||||
|
import tensorflow as tf
|
||||||
|
|
||||||
from sklearn.exceptions import NotFittedError
|
from sklearn.exceptions import NotFittedError
|
||||||
|
|
||||||
|
# Generator for tensorflow functions for a given model
|
||||||
|
def get_model_evaluator(model):
|
||||||
|
@tf.function
|
||||||
|
def model_evaluator(tf_input):
|
||||||
|
preds = model.predict_f(tf_input)
|
||||||
|
return preds
|
||||||
|
return model_evaluator
|
||||||
|
|
||||||
|
def get_grad_evaluator(model):
|
||||||
|
@tf.function
|
||||||
|
def grad_evaluator(tf_input):
|
||||||
|
with tf.GradientTape() as tape:
|
||||||
|
preds = model.predict_f(tf_input)
|
||||||
|
grads = tape.gradient(preds, tf_input)
|
||||||
|
return grads
|
||||||
|
return grad_evaluator
|
||||||
|
|
||||||
|
def get_combined_evaluator(model):
|
||||||
|
@tf.function
|
||||||
|
def combined_evaluator(tf_input):
|
||||||
|
with tf.GradientTape() as tape:
|
||||||
|
preds = model.predict_f(tf_input)
|
||||||
|
grads = tape.gradient(preds, tf_input)
|
||||||
|
return preds, grads
|
||||||
|
return combined_evaluator
|
||||||
|
|
||||||
def get_random_signal(nstep, a_range = (-1, 1), b_range = (2, 10), signal_type = 'analog'):
|
def get_random_signal(nstep, a_range = (-1, 1), b_range = (2, 10), signal_type = 'analog'):
|
||||||
|
|
||||||
|
@ -47,6 +74,20 @@ def get_random_signal(nstep, a_range = (-1, 1), b_range = (2, 10), signal_type =
|
||||||
raise ValueError(signal_type)
|
raise ValueError(signal_type)
|
||||||
|
|
||||||
|
|
||||||
|
def get_identification_signal(size):
|
||||||
|
# Base random signal
|
||||||
|
rand_signal = get_random_signal(size, signal_type = 'prbs')
|
||||||
|
# Integrator (cumulative sum)
|
||||||
|
cum_signal = 3/size * np.ones((1, size))
|
||||||
|
cum_signal = np.cumsum(cum_signal)
|
||||||
|
# Combine signals and clip signal to [-1, 1] range
|
||||||
|
ident_signal = rand_signal + cum_signal
|
||||||
|
ident_signal = np.where(ident_signal < -1, -1, ident_signal)
|
||||||
|
ident_signal = np.where(ident_signal > 1, 1, ident_signal)
|
||||||
|
|
||||||
|
return ident_signal
|
||||||
|
|
||||||
|
|
||||||
def get_scaled_df(df, dict_cols, scaler):
|
def get_scaled_df(df, dict_cols, scaler):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,11 +6,12 @@ import pickle
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from controllers import GP_MPCcontroller, TestController
|
from controllers import *
|
||||||
|
|
||||||
clients = ['measure', 'control', 'weather']
|
clients = ['measure', 'control', 'weather']
|
||||||
|
|
||||||
N_horizon = 6
|
N_horizon = 8
|
||||||
|
print(f"[*] Controller Horizon {N_horizon}")
|
||||||
|
|
||||||
HOST = '127.0.0.1'
|
HOST = '127.0.0.1'
|
||||||
PORT = {
|
PORT = {
|
||||||
|
@ -55,7 +56,7 @@ dict_cols = {
|
||||||
'y': (y_lags, y_cols)
|
'y': (y_lags, y_cols)
|
||||||
}
|
}
|
||||||
|
|
||||||
controller = GP_MPCcontroller(dict_cols = dict_cols, N_horizon = N_horizon, recover_from_crash = False)
|
controller = SVGP_MPCcontroller(dict_cols = dict_cols, N_horizon = N_horizon, recover_from_crash = False)
|
||||||
|
|
||||||
# Enter TCP server loop
|
# Enter TCP server loop
|
||||||
while True:
|
while True:
|
||||||
|
|
|
@ -17,7 +17,7 @@ import casadi as cs
|
||||||
|
|
||||||
import callbacks
|
import callbacks
|
||||||
from helpers import *
|
from helpers import *
|
||||||
from controllers import GP_MPCcontroller
|
from controllers import *
|
||||||
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
@ -40,8 +40,7 @@ dict_cols = {
|
||||||
|
|
||||||
N_horizon = 10
|
N_horizon = 10
|
||||||
|
|
||||||
|
controller = SVGP_MPCcontroller(dict_cols = dict_cols, N_horizon = N_horizon)
|
||||||
controller = GP_MPCcontroller(dict_cols = dict_cols, N_horizon = N_horizon)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,6 +63,4 @@ while True:
|
||||||
|
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
if idx > 502:
|
|
||||||
import pdb; pdb.set_trace()
|
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue