class ProbabilityOfImprovement(Acquisition): """ Probability of Improvement acquisition function for single-objective global optimization. .. math:: \\alpha(\\mathbf x_{\\star}) = \\int_{-\\infty}^{f_{\\min}} \\, p( f_{\\star}\\,|\\, \\mathbf x, \\mathbf y, \\mathbf x_{\\star} ) \\, d f_{\\star} """ def __init__(self, model): """ :param model: GPflow model (single output) representing our belief of the objective """ super(ProbabilityOfImprovement, self).__init__(model) self.fmin = DataHolder(np.zeros(1)) self.setup() def setup(self): super(ProbabilityOfImprovement, self).setup() samples_mean, _ = self.models[0].predict_f(self.data[0]) self.fmin.set_data(np.min(samples_mean, axis=0)) def build_acquisition(self, Xcand): candidate_mean, candidate_var = self.models[0].build_predict(Xcand) candidate_var = tf.maximum(candidate_var, stability) normal = tf.contrib.distributions.Normal(candidate_mean, tf.sqrt(candidate_var)) return normal.cdf(self.fmin, name=self.__class__.__name__)
def __init__(self, model): """ :param model: GPflow model (single output) representing our belief of the objective """ super(ExpectedImprovement, self).__init__(model) self.fmin = DataHolder(np.zeros(1)) self.setup()
def _diag_tr_helper(self, node_features, adj_mat, x_tr): z = np.asarray([node_features[a == 1.] for a in adj_mat[x_tr.flatten()]]) max_n = np.max([t.shape[0] for t in z]) out = np.zeros((len(z), max_n, node_features.shape[1])) masks = np.zeros((len(z), max_n, max_n), dtype=np_float_type) for i in range(len(z)): out[i,:len(z[i]),:] = z[i] masks[i, :len(z[i]), :len(z[i])] = 1 return DataHolder(out), DataHolder(masks), DataHolder(np.sum(np.sum(masks, 2), 1))
class ExpectedImprovement(Acquisition): """ Expected Improvement acquisition function for single-objective global optimization. Introduced by (Mockus et al, 1975). Key reference: :: @article{Jones:1998, title={Efficient global optimization of expensive black-box functions}, author={Jones, Donald R and Schonlau, Matthias and Welch, William J}, journal={Journal of Global optimization}, volume={13}, number={4}, pages={455--492}, year={1998}, publisher={Springer} } This acquisition function is the expectation of the improvement over the current best observation w.r.t. the predictive distribution. The definition is closely related to the :class:`.ProbabilityOfImprovement`, but adds a multiplication with the improvement w.r.t the current best observation to the integral. .. math:: \\alpha(\\mathbf x_{\\star}) = \\int \\max(f_{\\min} - f_{\\star}, 0) \\, p( f_{\\star}\\,|\\, \\mathbf x, \\mathbf y, \\mathbf x_{\\star} ) \\, d f_{\\star} """ def __init__(self, model): """ :param model: GPflow model (single output) representing our belief of the objective """ super(ExpectedImprovement, self).__init__(model) assert (isinstance(model, Model)) self.fmin = DataHolder(np.zeros(1)) self.setup() def setup(self): super(ExpectedImprovement, self).setup() # Obtain the lowest posterior mean for the previous - feasible - evaluations feasible_samples = self.data[0][ self.highest_parent.feasible_data_index(), :] samples_mean, _ = self.models[0].predict_f(feasible_samples) self.fmin.set_data(np.min(samples_mean, axis=0)) def build_acquisition(self, Xcand): # Obtain predictive distributions for candidates candidate_mean, candidate_var = self.models[0].build_predict(Xcand) candidate_var = tf.maximum(candidate_var, stability) # Compute EI normal = tf.contrib.distributions.Normal(candidate_mean, tf.sqrt(candidate_var)) t1 = (self.fmin - candidate_mean) * normal.cdf(self.fmin) t2 = candidate_var * normal.prob(self.fmin) return tf.add(t1, t2, name=self.__class__.__name__)
def __init__(self, lb, ub): """ Construct bounded volumes. :param lb: the lowerbounds of the volumes :param ub: the upperbounds of the volumes """ super(BoundedVolumes, self).__init__() assert np.all(lb.shape == lb.shape) self.lb = DataHolder(np.atleast_2d(lb), 'pass') self.ub = DataHolder(np.atleast_2d(ub), 'pass')
def Y(self): """ Returns the output data of the wrapped model, unscaled. :return: :class:`.DataHolder`: unscaled output data """ return DataHolder(self.output_transform.backward(self.wrapped.Y.value))
def X(self): """ Returns the input data of the model, unscaled. :return: :class:`.DataHolder`: unscaled input data """ return DataHolder(self.input_transform.backward(self.wrapped.X.value))
def __init__(self, models): """ :param models: A list of (possibly multioutput) GPflow representing our belief of the objectives. """ super(HVProbabilityOfImprovement, self).__init__(models) assert self.data[1].shape[1] > 1 self.pareto = Pareto(np.hstack((m.predict_f(self.data[0])[0] for m in self.models))) self.reference = DataHolder(self._estimate_reference())
def __init__(self, X, Y, Z, kernels, likelihood, num_latent_Y=None, minibatch_size=None, num_samples=1, mean_function=Zero()): Model.__init__(self) assert X.shape[0] == Y.shape[0] assert Z.shape[1] == X.shape[1] assert kernels[0].input_dim == X.shape[1] self.num_data, D_X = X.shape self.num_samples = num_samples self.D_Y = num_latent_Y or Y.shape[1] self.dims = [k.input_dim for k in kernels] + [ self.D_Y, ] q_mus, q_sqrts, Zs, mean_functions = init_layers( X, Z, self.dims, mean_function) layers = [] for q_mu, q_sqrt, Z, mean_function, kernel in zip( q_mus, q_sqrts, Zs, mean_functions, kernels): layers.append(Layer(kernel, q_mu, q_sqrt, Z, mean_function)) self.layers = ParamList(layers) for layer in self.layers[:-1]: # fix the inner layer mean functions layer.mean_function.fixed = True self.likelihood = likelihood if minibatch_size is not None: self.X = MinibatchData(X, minibatch_size) self.Y = MinibatchData(Y, minibatch_size) else: self.X = DataHolder(X) self.Y = DataHolder(Y)
def __init__(self, Y, latent_dim, X_mean=None, kern=None, back_kern=None, mean_function=Zero()): """ Initialise GPLVM object. This method only works with a Gaussian likelihood. :param Y: data matrix (N x D) :param X_mean: latent positions (N x Q), by default initialized using PCA. :param kern: kernel specification, by default RBF :param mean_function: mean function, by default None. """ # define kernel function if kern is None: kern = kernels.RBF(latent_dim) back_kern = kernels.RBF(latent_dim) # initialize latent_positions if X_mean is None: X_mean = PCA_reduce(Y, latent_dim) # initialize variables self.num_latent = X_mean.shape[1] # initialize variables likelihood = likelihoods.Gaussian() Y = DataHolder(Y, on_shape_change='pass') X = DataHolder(X_mean, on_shape_change='pass') # initialize parent GPModel GPModel.__init__(self, X, Y, kern, likelihood, mean_function) # initialize back constraint model self.back_kern = back_kern self.back_mean_function = Zero() self.back_likelihood = likelihoods.Gaussian() # set latent positions as model param del self.X self.X = Param(X_mean)
def __init__(self, denseAdjMat, denseFeatureMat, X_tr, degree=3.0, variance=1.0, offset=1.0): GPflow.kernels.Kern.__init__(self, 1) self.degree = degree self.offset = Param(offset, transform=transforms.positive) self.variance = Param(variance, transforms.positive) denseAdjMat[np.diag_indices(len(denseAdjMat))] = 1. self.tr_features, self.tr_masks, self.tr_masks_counts = self._diag_tr_helper(denseFeatureMat, denseAdjMat, X_tr) self.sparse_P = SparseDataHolder(sparse_to_tuple(sparse.csr_matrix( denseAdjMat/np.sum(denseAdjMat, 1, keepdims=True)))) self.sparseFeatureMat = SparseDataHolder(sparse_to_tuple(sparse.csr_matrix(denseFeatureMat))) self.denseFeatureMat = DataHolder(denseFeatureMat)
def __init__(self, A, b): """ :param A: scaling matrix. Either a P-dimensional vector, or a P x P transformation matrix. For the latter, the inverse and backward methods are not guaranteed to work as A must be invertible. It is also possible to specify a matrix with size P x Q with Q != P to achieve a lower dimensional representation of X. In this case, A is not invertible, hence inverse and backward transforms are not supported. :param b: A P-dimensional offset vector. """ super(LinearTransform, self).__init__() assert A is not None assert b is not None b = np.atleast_1d(b) A = np.atleast_1d(A) if len(A.shape) == 1: A = np.diag(A) assert (len(b.shape) == 1) assert (len(A.shape) == 2) self.A = DataHolder(A) self.b = DataHolder(b)
def __init__(self, Y, threshold=0): """ Construct a Pareto set. Stores a Pareto set and calculates the cell bounds covering the non-dominated region. The latter is needed for certain multiobjective acquisition functions. E.g., the :class:`~.acquisition.HVProbabilityOfImprovement`. :param Y: output data points, size N x R :param threshold: approximation threshold for the generic divide and conquer strategy (default 0: exact calculation) """ super(Pareto, self).__init__() self.threshold = threshold self.Y = Y # Setup data structures self.bounds = BoundedVolumes.empty(Y.shape[1], np_int_type) self.front = DataHolder(np.zeros((0, Y.shape[1])), 'pass') # Initialize self.update()
class LinearTransform(DataTransform): """ A simple linear transform of the form .. math:: \\mathbf Y = (\\mathbf A \\mathbf X^{T})^{T} + \\mathbf b \\otimes \\mathbf 1_{N}^{T} """ def __init__(self, A, b): """ :param A: scaling matrix. Either a P-dimensional vector, or a P x P transformation matrix. For the latter, the inverse and backward methods are not guaranteed to work as A must be invertible. It is also possible to specify a matrix with size P x Q with Q != P to achieve a lower dimensional representation of X. In this case, A is not invertible, hence inverse and backward transforms are not supported. :param b: A P-dimensional offset vector. """ super(LinearTransform, self).__init__() assert A is not None assert b is not None b = np.atleast_1d(b) A = np.atleast_1d(A) if len(A.shape) == 1: A = np.diag(A) assert (len(b.shape) == 1) assert (len(A.shape) == 2) self.A = DataHolder(A) self.b = DataHolder(b) def build_forward(self, X): return tf.matmul(X, tf.transpose(self.A)) + self.b @AutoFlow((float_type, [None, None])) def backward(self, Y): """ Overwrites the default backward approach, to avoid an explicit matrix inversion. """ return self.build_backward(Y) def build_backward(self, Y): """ TensorFlow implementation of the inverse mapping """ L = tf.cholesky(tf.transpose(self.A)) XT = tf.cholesky_solve(L, tf.transpose(Y-self.b)) return tf.transpose(XT) def build_backward_variance(self, Yvar): """ Additional method for scaling variance backward (used in :class:`.Normalizer`). Can process both the diagonal variances returned by predict_f, as well as full covariance matrices. :param Yvar: size N x N x P or size N x P :return: Yvar scaled, same rank and size as input """ rank = tf.rank(Yvar) # Because TensorFlow evaluates both fn1 and fn2, the transpose can't be in the same line. If a full cov # matrix is provided fn1 turns it into a rank 4, then tries to transpose it as a rank 3. # Splitting it in two steps however works fine. Yvar = tf.cond(tf.equal(rank, 2), lambda: tf.matrix_diag(tf.transpose(Yvar)), lambda: Yvar) Yvar = tf.cond(tf.equal(rank, 2), lambda: tf.transpose(Yvar, perm=[1, 2, 0]), lambda: Yvar) N = tf.shape(Yvar)[0] D = tf.shape(Yvar)[2] L = tf.cholesky(tf.square(tf.transpose(self.A))) Yvar = tf.reshape(Yvar, [N * N, D]) scaled_var = tf.reshape(tf.transpose(tf.cholesky_solve(L, tf.transpose(Yvar))), [N, N, D]) return tf.cond(tf.equal(rank, 2), lambda: tf.reduce_sum(scaled_var, axis=1), lambda: scaled_var) def assign(self, other): """ Assign the parameters of another :class:`LinearTransform`. Can be useful to avoid graph re-compilation. :param other: :class:`.LinearTransform` object """ assert other is not None assert isinstance(other, LinearTransform) self.A.set_data(other.A.value) self.b.set_data(other.b.value) def __invert__(self): A_inv = np.linalg.inv(self.A.value.T) return LinearTransform(A_inv, -np.dot(self.b.value, A_inv)) def __str__(self): return 'XA + b'