def _cov_mat(self, x1, x2=None, noise=True): """ Covariance matrix for preference data using the kernel function. :param x1: datapoints for which to compute covariance matrix :param x2: if None, covariance matrix will be square for the input x1 if not None, covariance will be between x1 (rows) and x2 (cols_ :param noise: whether to add noise to the diagonal of the covariance matrix :return: """ if x2 is None: x2 = x1 else: # if x1 != x2 we don't add noise! noise = False x1 = utils_data.format_data(x1, self.num_objectives) x2 = utils_data.format_data(x2, self.num_objectives) cov_mat = self._kernel(np.repeat(x1, x2.shape[0], axis=0), np.tile(x2, (x1.shape[0], 1))) cov_mat = cov_mat.reshape((x1.shape[0], x2.shape[0])) if noise: cov_mat += self.std_noise**2 * np.eye(cov_mat.shape[0]) return cov_mat
def _kernel(self, x1, x2): """ Squared exponential kernel function :param x1: :param x2: :return: """ x1 = utils_data.format_data(x1, self.num_objectives) x2 = utils_data.format_data(x2, self.num_objectives) k = 0.8**2 * np.exp(-(1. / (2. * (self.kernel_width**2))) * np.linalg.norm(x1 - x2, axis=1)**2) return k
def get_predictive_params(self, x_new, pointwise): """ Returns the predictive parameters (mean, variance) of the Gaussian distribution at the given datapoints :param x_new: the points for which we want the predictive params :param pointwise: whether we want pointwise variance or the entire covariance matrix :return: """ # bring input points into right shape x_new = utils_data.format_data(x_new, self.num_objectives) # if we don't have any data yet, use prior GP to make predictions if self.datapoints is None or self.utility_vals is None: pred_mean, pred_var = self._evaluate_prior(x_new) # otherwise compute predictive mean and covariance else: cov_xnew_x = self._cov_mat(x_new, self.datapoints, noise=False) cov_x_xnew = self._cov_mat(self.datapoints, x_new, noise=False) cov_xnew = self._cov_mat(x_new, noise=False) pred_mean = self.prior_mean(x_new) + np.dot( np.dot(cov_xnew_x, self.cov_mat_inv), (self.utility_vals - self.prior_mean(self.datapoints))) pred_var = cov_xnew - np.dot( np.dot(cov_xnew_x, self.pred_cov_factor), cov_x_xnew) if pointwise: pred_var = pred_var.diagonal() return pred_mean, pred_var
def get_predictive_params(self, x_new, pointwise): # make sure data is in right shape x_new = utils_data.format_data(x_new, self.num_objectives) # if we don't have any data yet, use prior GP to make predictions if self.x_dx.shape[0] == 0 or self.utility_vals is None: pred_mean, pred_var = self._evaluate_prior(x_new) # otherwise compute predictive mean and covariance else: cov_in_data = self._cov_mat_incl_deriv(x1=x_new, x2=self.datapoints, dx2=self.datapoints_deriv) cov_data_in = self._cov_mat_incl_deriv(x1=self.datapoints, dx1=self.datapoints_deriv, x2=x_new) cov_lamb_inv = np.linalg.inv(self.cov_mat - self.lambda_mat_inv) pred_mean = self.prior_mean(x_new) + np.dot( np.dot(cov_in_data, self.cov_mat_inv), (self.utility_vals - self.prior_joint_mean( self.datapoints, self.datapoints_deriv))) pred_var = self._kernel(x_new, x_new) - np.dot( np.dot(cov_in_data, cov_lamb_inv), cov_data_in) if pointwise: pred_var = pred_var.diagonal() return pred_mean, pred_var
def prior_mean(self, x): """ Prior mean function :param x: num_datapoints * num_objectives :return: """ x = utils_data.format_data(x, self.num_objectives) m = np.zeros(x.shape[0]) if self.prior_mean_type == 'linear': m += np.sum(x, axis=1) / self.num_objectives else: TypeError('Prior mean type not understood.') return m
def prior_mean_deriv(self, x): """ Derivative of prior mean function :param x: :return: """ x = utils_data.format_data(x, self.num_objectives) if self.prior_mean_type == 'zero': dm = np.zeros(x.shape[0]) elif self.prior_mean_type == 'linear': dm = np.ones(x.shape[0]) / self.num_objectives else: TypeError('Prior mean type not understood.') return dm
def sample(self, sample_points): """ Get a sample from the current GP at the given points. :param sample_points: the points at which we want to take the sample :return: the values of the GP sample at the input points """ # bring sample points in right shape sample_points = utils_data.format_data(sample_points, self.num_objectives) # get the mean and the variance of the predictive (multivariate gaussian) distribution at the sample points mean, var = self.get_predictive_params(sample_points, pointwise=False) # sample from the multivariate gaussian with the given parameters f_sample = self.random_state.multivariate_normal(mean, var, 1)[0] return f_sample
def update(self, dataset, dx): """ pairwise dataset and virtual derivative points """ self.datapoints = dataset.datapoints self.comparisons = dataset.comparisons # x-values at which we make virtual derivative observations self.datapoints_deriv = utils_data.format_data(dx, self.num_objectives) # x-values for real observations and derivative observations self.x_dx = np.concatenate((self.datapoints, self.datapoints_deriv)) # update the covariance matrix self.cov_mat = self._cov_mat_incl_deriv(self.datapoints, self.datapoints_deriv) self.cov_mat_inv = np.linalg.inv(self.cov_mat) # update the map estimate of f self.utility_vals = self._compute_posterior()
def _add_single_datapoint(self, new_datapoint): """ Add single datapoint to the existing dataset if it doesn't exist yet and return index :param new_datapoint: new datapoint to add :return x_new_idx: the index of the new datapoint in the existing dataset """ new_datapoint = utl_data.format_data(new_datapoint, self.num_objectives) # if the datapoint is not in our dataset yet, add it if not utl_data.array_in_matrix(new_datapoint, self.datapoints): self.datapoints = np.vstack((self.datapoints, new_datapoint)) new_datapoint_index = self.datapoints.shape[0] - 1 # if the datapoint is already in our dataset, find its index else: new_datapoint_index = self.get_index(new_datapoint) return new_datapoint_index
def get_preference(self, x, add_noise): # bring data into right format first x = utils_data.format_data(x, self.num_objectives) # EVALUATE user preferences if self.num_objectives == 1: y = np.polyval(self.poly, x) else: y = self.utility_func(x) y = y.flatten() assert len(y) == x.shape[0] y = (y - self.min_y) / (self.max_y - self.min_y) if add_noise: # the noise is different every time the scalarisation function is called, # but we always draw from the same distribution noise = self.random_state.normal(0, self.std_noise, x.shape[0]) y += noise return y
def _kernel(self, x1, x2): x1 = utils_data.format_data(x1, self.num_objectives) x2 = utils_data.format_data(x2, self.num_objectives) k = 0.8**2 * np.exp(-(1. / (2. * (self.kernel_width**2))) * np.linalg.norm(x1 - x2, axis=1)**2) return k