Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
2from scipy.optimize import minimize
3from scipy.linalg import solve_triangular, cho_factor, cho_solve
4from ase.optimize.gpmin.kernel import SquaredExponential
5from ase.optimize.gpmin.prior import ZeroPrior
8class GaussianProcess():
9 """Gaussian Process Regression
10 It is recommended to be used with other Priors and Kernels from
11 ase.optimize.gpmin
13 Parameters:
15 prior: Prior class, as in ase.optimize.gpmin.prior
16 Defaults to ZeroPrior
18 kernel: Kernel function for the regression, as in
19 ase.optimize.gpmin.kernel
20 Defaults to the Squared Exponential kernel with derivatives
21 """
22 def __init__(self, prior=None, kernel=None):
23 if kernel is None:
24 self.kernel = SquaredExponential()
25 else:
26 self.kernel = kernel
28 if prior is None:
29 self.prior = ZeroPrior()
30 else:
31 self.prior = prior
33 def set_hyperparams(self, params):
34 """Set hyperparameters of the regression.
35 This is a list containing the parameters of the
36 kernel and the regularization (noise)
37 of the method as the last entry.
38 """
39 self.hyperparams = params
40 self.kernel.set_params(params[:-1])
41 self.noise = params[-1]
43 def train(self, X, Y, noise=None):
44 """Produces a PES model from data.
46 Given a set of observations, X, Y, compute the K matrix
47 of the Kernel given the data (and its cholesky factorization)
48 This method should be executed whenever more data is added.
50 Parameters:
52 X: observations (i.e. positions). numpy array with shape: nsamples x D
53 Y: targets (i.e. energy and forces). numpy array with
54 shape (nsamples, D+1)
55 noise: Noise parameter in the case it needs to be restated.
56 """
57 if noise is not None:
58 self.noise = noise # Set noise attribute to a different value
60 self.X = X.copy() # Store the data in an attribute
61 n = self.X.shape[0]
62 D = self.X.shape[1]
63 regularization = np.array(n * ([self.noise * self.kernel.l] +
64 D * [self.noise]))
66 K = self.kernel.kernel_matrix(X) # Compute the kernel matrix
67 K[range(K.shape[0]), range(K.shape[0])] += regularization**2
69 self.m = self.prior.prior(X)
70 self.a = Y.flatten() - self.m
71 self.L, self.lower = cho_factor(K, lower=True, check_finite=True)
72 cho_solve((self.L, self.lower), self.a, overwrite_b=True,
73 check_finite=True)
75 def predict(self, x, get_variance=False):
76 """Given a trained Gaussian Process, it predicts the value and the
77 uncertainty at point x. It returns f and V:
78 f : prediction: [y, grady]
79 V : Covariance matrix. Its diagonal is the variance of each component
80 of f.
82 Parameters:
84 x (1D np.array): The position at which the prediction is computed
85 get_variance (bool): if False, only the prediction f is returned
86 if True, the prediction f and the variance V are
87 returned: Note V is O(D*nsample2)
88 """
89 n = self.X.shape[0]
90 k = self.kernel.kernel_vector(x, self.X, n)
91 f = self.prior.prior(x) + np.dot(k, self.a)
92 if get_variance:
93 v = solve_triangular(self.L, k.T.copy(), lower=True,
94 check_finite=False)
95 variance = self.kernel.kernel(x, x)
96 # covariance = np.matmul(v.T, v)
97 covariance = np.tensordot(v, v, axes=(0, 0))
98 V = variance - covariance
99 return f, V
100 return f
102 def neg_log_likelihood(self, params, *args):
103 """Negative logarithm of the marginal likelihood and its derivative.
104 It has been built in the form that suits the best its optimization,
105 with the scipy minimize module, to find the optimal hyperparameters.
107 Parameters:
109 l: The scale for which we compute the marginal likelihood
110 *args: Should be a tuple containing the inputs and targets
111 in the training set-
112 """
113 X, Y = args
114 # Come back to this
115 self.kernel.set_params(np.array([params[0], params[1], self.noise]))
116 self.train(X, Y)
117 y = Y.flatten()
119 # Compute log likelihood
120 logP = (-0.5 * np.dot(y - self.m, self.a) -
121 np.sum(np.log(np.diag(self.L))) -
122 X.shape[0] * 0.5 * np.log(2 * np.pi))
124 # Gradient of the loglikelihood
125 grad = self.kernel.gradient(X)
127 # vectorizing the derivative of the log likelihood
128 D_P_input = np.array([np.dot(np.outer(self.a, self.a), g)
129 for g in grad])
130 D_complexity = np.array([cho_solve((self.L, self.lower), g)
131 for g in grad])
133 DlogP = 0.5 * np.trace(D_P_input - D_complexity, axis1=1, axis2=2)
134 return -logP, -DlogP
136 def fit_hyperparameters(self, X, Y, tol=1e-2, eps=None):
137 """Given a set of observations, X, Y; optimize the scale
138 of the Gaussian Process maximizing the marginal log-likelihood.
139 This method calls TRAIN there is no need to call the TRAIN method
140 again. The method also sets the parameters of the Kernel to their
141 optimal value at the end of execution
143 Parameters:
145 X: observations(i.e. positions). numpy array with shape: nsamples x D
146 Y: targets (i.e. energy and forces).
147 numpy array with shape (nsamples, D+1)
148 tol: tolerance on the maximum component of the gradient of the
149 log-likelihood.
150 (See scipy's L-BFGS-B documentation:
151 https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html)
152 eps: include bounds to the hyperparameters as a +- a percentage
153 if eps is None there are no bounds in the optimization
155 Returns:
157 result (dict) :
158 result = {'hyperparameters': (numpy.array) New hyperparameters,
159 'converged': (bool) True if it converged,
160 False otherwise
161 }
162 """
163 params = np.copy(self.hyperparams)[:2]
164 arguments = (X, Y)
165 if eps is not None:
166 bounds = [((1 - eps) * p, (1 + eps) * p) for p in params]
167 else:
168 bounds = None
170 result = minimize(self.neg_log_likelihood, params, args=arguments,
171 method='L-BFGS-B', jac=True, bounds=bounds,
172 options={'gtol': tol, 'ftol': 0.01 * tol})
174 if not result.success:
175 converged = False
176 else:
177 converged = True
178 self.hyperparams = np.array([result.x.copy()[0],
179 result.x.copy()[1], self.noise])
180 self.set_hyperparams(self.hyperparams)
181 return {'hyperparameters': self.hyperparams, 'converged': converged}