Enhance multioutput grad obs (#995)

* multiplied RBF kernels can now be used with gradient observations

* standard periodic kernels can now be used with gradient observations

* predictive gradients (derivatives of posterior means and variances) can now be calculated when using gradient observations

* simplified and commented RBF & StdP kernel derivatives

* updated kernel slicing and commented prod kernel derivatives

* removed caching from stdp kern, as it breaks optimization for some reason

* fixed hyperparameter optimization for prod kernel

* improved code readability

* added unit tests for gradient observing MultioutputGP models

* added predictions check to unit tests

* bugfix for multioutput_kern

* improved testing coverage

* reduced size of some tests; led to an issue in an unrelated test

* updated testing

* added gradient MultioutputGP prod kernel example

* added keywords and plotting to example
This commit is contained in:
mgranit 2023-09-21 16:53:42 +03:00 committed by GitHub
parent 2c22f1e9c5
commit 9c1db7aa34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1494 additions and 164 deletions

View file

@ -23,24 +23,42 @@ class DiffKern(Kern):
self.base_kern.parameters_changed()
@Cache_this(limit=3, ignore_args=())
def K(self, X, X2=None, dimX2 = None): #X in dimension self.dimension
def K(self, X, X2=None, dimX2=None): #X in dimension self.dimension
if X2 is None:
X2 = X
if dimX2 is None:
dimX2 = self.dimension
return self.base_kern.dK2_dXdX2(X,X2, self.dimension, dimX2)
return self.base_kern.dK2_dXdX2(X, X2, self.dimension, dimX2)
@Cache_this(limit=3, ignore_args=())
def dK_dX(self, X, X2, dimX, dimX2=None):
if dimX2 is None:
dimX2 = self.dimension
return self.base_kern.dK3_dXdXdX2(X, X2, dimX, self.dimension, dimX2)
@Cache_this(limit=3, ignore_args=())
def Kdiag(self, X):
return np.diag(self.base_kern.dK2_dXdX2(X,X, self.dimension, self.dimension))
return self.base_kern.dK2_dXdX2diag(X, self.dimension, self.dimension)
@Cache_this(limit=3, ignore_args=())
def dK_dXdiag(self, X, dimX):
return self.base_kern.dK3_dXdXdX2diag(X, dimX, self.dimension, self.dimension)
@Cache_this(limit=3, ignore_args=())
def dK_dX_wrap(self, X, X2): #X in dimension self.dimension
return self.base_kern.dK_dX(X,X2, self.dimension)
return self.base_kern.dK_dX(X, X2, self.dimension)
@Cache_this(limit=3, ignore_args=())
def dK_dX2_wrap(self, X, X2): #X in dimension self.dimension
return self.base_kern.dK_dX2(X,X2, self.dimension)
return self.base_kern.dK_dX2(X, X2, self.dimension)
@Cache_this(limit=3, ignore_args=())
def dK2_dXdX2_wrap(self, X, X2, dimX):
return self.base_kern.dK2_dXdX2(X, X2, dimX, self.dimension)
@Cache_this(limit=3, ignore_args=())
def dK2_dXdX_wrap(self, X, X2, dimX):
return self.base_kern.dK2_dXdX(X, X2, dimX, self.dimension)
def reset_gradients(self):
self.base_kern.reset_gradients()
@ -56,33 +74,33 @@ class DiffKern(Kern):
def update_gradients_full(self, dL_dK, X, X2=None, dimX2=None):
if dimX2 is None:
dimX2 = self.dimension
gradients = self.base_kern.dgradients2_dXdX2(X,X2,self.dimension,dimX2)
gradients = self.base_kern.dgradients2_dXdX2(X, X2, self.dimension, dimX2)
self.base_kern.update_gradients_direct(*[self._convert_gradients(dL_dK, gradient) for gradient in gradients])
def update_gradients_diag(self, dL_dK_diag, X):
gradients = self.base_kern.dgradients2_dXdX2(X,X, self.dimension, self.dimension)
gradients = self.base_kern.dgradients2_dXdX2(X, X, self.dimension, self.dimension)
self.base_kern.update_gradients_direct(*[self._convert_gradients(dL_dK_diag, gradient, f=np.diag) for gradient in gradients])
def update_gradients_dK_dX(self, dL_dK, X, X2=None):
if X2 is None:
X2 = X
gradients = self.base_kern.dgradients_dX(X,X2, self.dimension)
gradients = self.base_kern.dgradients_dX(X, X2, self.dimension)
self.base_kern.update_gradients_direct(*[self._convert_gradients(dL_dK, gradient) for gradient in gradients])
def update_gradients_dK_dX2(self, dL_dK, X, X2=None):
gradients = self.base_kern.dgradients_dX2(X,X2, self.dimension)
gradients = self.base_kern.dgradients_dX2(X, X2, self.dimension)
self.base_kern.update_gradients_direct(*[self._convert_gradients(dL_dK, gradient) for gradient in gradients])
def gradients_X(self, dL_dK, X, X2):
tmp = self.base_kern.gradients_XX(dL_dK, X, X2)[:,:,:, self.dimension]
tmp = self.base_kern.gradients_XX(dL_dK, X, X2)[:,:,:,self.dimension]
return np.sum(tmp, axis=1)
def gradients_X2(self, dL_dK, X, X2):
tmp = self.base_kern.gradients_XX(dL_dK, X, X2)[:, :, self.dimension, :]
tmp = self.base_kern.gradients_XX(dL_dK, X, X2)[:,:,self.dimension,:]
return np.sum(tmp, axis=1)
def _convert_gradients(self, l,g, f = lambda x:x):
def _convert_gradients(self, l, g, f=lambda x:x):
if type(g) is np.ndarray:
return np.sum(f(l)*f(g))
else:
return np.array([np.sum(f(l)*f(gi)) for gi in g])
return np.array([np.sum(f(l)*f(gi)) for gi in g])

View file

@ -22,7 +22,14 @@ class KernCallsViaSlicerMeta(ParametersChangedMeta):
put_clean(dct, 'dK_dX', _slice_dK_dX)
put_clean(dct, 'dK_dX2', _slice_dK_dX)
put_clean(dct, 'dK2_dXdX2', _slice_dK2_dXdX2)
put_clean(dct, 'dK2_dXdX', _slice_dK2_dXdX2)
put_clean(dct, 'dK3_dXdXdX2', _slice_dK3_dXdXdX2)
put_clean(dct, 'Kdiag', _slice_Kdiag)
put_clean(dct, 'dK_dXdiag', _slice_dK_dXdiag)
put_clean(dct, 'dK_dX2diag', _slice_dK_dXdiag)
put_clean(dct, 'dK2_dXdX2diag', _slice_dK2_dXdX2diag)
put_clean(dct, 'dK2_dXdXdiag', _slice_dK2_dXdX2diag)
put_clean(dct, 'dK3_dXdXdX2diag', _slice_dK3_dXdXdX2diag)
put_clean(dct, 'phi', _slice_Kdiag)
put_clean(dct, 'update_gradients_full', _slice_update_gradients_full)
put_clean(dct, 'update_gradients_diag', _slice_update_gradients_diag)
@ -35,9 +42,10 @@ class KernCallsViaSlicerMeta(ParametersChangedMeta):
put_clean(dct, 'gradients_XX_diag', _slice_gradients_XX_diag)
put_clean(dct, 'gradients_X_diag', _slice_gradients_X_diag)
put_clean(dct, 'dgradients_dX',_slice_partial_gradients_list_X)
put_clean(dct, 'dgradients_dX2',_slice_partial_gradients_list_X)
put_clean(dct, 'dgradients2_dXdX2',_slice_partial_gradients_list_XX)
put_clean(dct, 'dgradients', _slice_partial_gradients_list)
put_clean(dct, 'dgradients_dX', _slice_partial_gradients_list_X)
put_clean(dct, 'dgradients_dX2', _slice_partial_gradients_list_X)
put_clean(dct, 'dgradients2_dXdX2', _slice_partial_gradients_list_XX)
put_clean(dct, 'psi0', _slice_psi)
put_clean(dct, 'psi1', _slice_psi)
@ -155,6 +163,18 @@ def _slice_dK_dX(f):
return ret
return wrap
def _slice_dK_dXdiag(f):
@wraps(f)
def wrap(self, X, dim, *a, **kw):
with _Slice_wrap(self, X, None) as s:
d = s.k._project_dim(dim)
if d is None:
ret = np.zeros(X.shape[0])
else:
ret = f(self, s.X, dim, *a, **kw)
return ret
return wrap
def _slice_dK2_dXdX2(f):
@wraps(f)
def wrap(self, X, X2, dimX, dimX2, *a, **kw):
@ -168,6 +188,59 @@ def _slice_dK2_dXdX2(f):
return ret
return wrap
def _slice_dK2_dXdX2diag(f):
@wraps(f)
def wrap(self, X, dimX, dimX2, *a, **kw):
with _Slice_wrap(self, X, None) as s:
d = s.k._project_dim(dimX)
d2 = s.k._project_dim(dimX2)
if (d is None) or (d2 is None):
ret = np.zeros(X.shape[0])
else:
ret = f(self, s.X, d, d2, *a, **kw)
return ret
return wrap
def _slice_dK3_dXdXdX2(f):
@wraps(f)
def wrap(self, X, X2, dim, dimX, dimX2, *a, **kw):
with _Slice_wrap(self, X, X2) as s:
D = s.k._project_dim(dim)
d = s.k._project_dim(dimX)
d2 = s.k._project_dim(dimX2)
if (D is None) or (d is None) or (d2 is None):
ret = np.zeros((X.shape[0], X2.shape[0]))
else:
ret = f(self, s.X, s.X2, D, d, d2, *a, **kw)
return ret
return wrap
def _slice_dK3_dXdXdX2diag(f):
@wraps(f)
def wrap(self, X, dim, dimX, dimX2, *a, **kw):
with _Slice_wrap(self, X, None) as s:
D = s.k._project_dim(dim)
d = s.k._project_dim(dimX)
d2 = s.k._project_dim(dimX2)
if (D is None) or (d is None) or (d2 is None):
ret = np.zeros(X.shape[0])
else:
ret = f(self, s.X, D, d, d2, *a, **kw)
return ret
return wrap
def _slice_partial_gradients_list(f):
@wraps(f)
def wrap(self, X, X2):
if X2 is None:
N, M = X.shape[0], X.shape[0]
else:
N, M = X.shape[0], X2.shape[0]
with _Slice_wrap(self, X, X2, ret_shape=(N, M)) as s:
ret = f(self, s.X, s.X2)
return ret
return wrap
def _slice_partial_gradients_X(f):
@wraps(f)
def wrap(self, X, X2, dim):

View file

@ -7,20 +7,24 @@ import numpy as np
from functools import partial
class KernWrapper(Kern):
def __init__(self, fk, fug, fg, base_kern):
def __init__(self, fk, fdk, fug, fg, base_kern):
self.fk = fk
self.fdk = fdk
self.fug = fug
self.fg = fg
self.base_kern = base_kern
super(KernWrapper, self).__init__(base_kern.active_dims.size, base_kern.active_dims, name='KernWrapper',useGPU=False)
super(KernWrapper, self).__init__(base_kern.active_dims.size, base_kern.active_dims, name='KernWrapper', useGPU=False)
def K(self, X, X2=None):
return self.fk(X,X2=X2)
return self.fk(X, X2=X2)
def dK_dX(self, X, X2, dimX):
return self.fdk(X, X2, dimX)
def update_gradients_full(self,dL_dK, X, X2=None):
def update_gradients_full(self, dL_dK, X, X2=None):
return self.fug(dL_dK, X, X2=X2)
def gradients_X(self,dL_dK, X, X2=None):
def gradients_X(self, dL_dK, X, X2=None):
return self.fg(dL_dK, X, X2=X2)
@property
@ -57,28 +61,46 @@ class MultioutputDerivativeKern(MultioutputKern):
#build covariance structure
covariance = [[None for i in range(nl)] for j in range(nl)]
linked = []
for i in range(0,nl):
unique=True
for j in range(0,nl):
if i==j or (kernels[i] is kernels[j]):
for i in range(0, nl):
unique = True
for j in range(0, nl):
if (i == j) or (kernels[i] is kernels[j]):
kern = kernels[i]
if i>j:
unique=False
if i > j:
unique = False
elif cross_covariances.get((i,j)) is not None: #cross covariance is given
kern = cross_covariances.get((i,j))
elif kernels[i].name == 'DiffKern' and kernels[i].base_kern == kernels[j]: # one is derivative of other
kern = KernWrapper(kernels[i].dK_dX_wrap,kernels[i].update_gradients_dK_dX,kernels[i].gradients_X, kernels[j])
elif (kernels[i].name == 'DiffKern') and (kernels[i].base_kern == kernels[j]): # one is derivative of other
kern = KernWrapper(
kernels[i].dK_dX_wrap,
kernels[i].dK2_dXdX_wrap,
kernels[i].update_gradients_dK_dX,
kernels[i].gradients_X,
kernels[j]
)
unique=False
elif kernels[j].name == 'DiffKern' and kernels[j].base_kern == kernels[i]: # one is derivative of other
kern = KernWrapper(kernels[j].dK_dX2_wrap,kernels[j].update_gradients_dK_dX2,kernels[j].gradients_X2, kernels[i])
elif kernels[i].name == 'DiffKern' and kernels[j].name == 'DiffKern' and kernels[i].base_kern == kernels[j].base_kern: #both are partial derivatives
kern = KernWrapper(partial(kernels[i].K, dimX2=kernels[j].dimension), partial(kernels[i].update_gradients_full, dimX2=kernels[j].dimension),None, kernels[i].base_kern)
if i>j:
unique=False
elif (kernels[j].name == 'DiffKern') and (kernels[j].base_kern == kernels[i]): # one is derivative of other
kern = KernWrapper(
kernels[j].dK_dX2_wrap,
kernels[j].dK2_dXdX2_wrap,
kernels[j].update_gradients_dK_dX2,
kernels[j].gradients_X2,
kernels[i]
)
elif (kernels[i].name == 'DiffKern') and (kernels[j].name == 'DiffKern') and (kernels[i].base_kern == kernels[j].base_kern): #both are partial derivatives
kern = KernWrapper(
partial(kernels[i].K, dimX2=kernels[j].dimension),
partial(kernels[i].dK_dX, dimX2=kernels[j].dimension),
partial(kernels[i].update_gradients_full, dimX2=kernels[j].dimension),
None,
kernels[i].base_kern
)
if i > j:
unique = False
else:
kern = ZeroKern()
covariance[i][j] = kern
if unique is True:
linked.append(i)
self.covariance = covariance
self.link_parameters(*[kernels[i] for i in linked])
self.link_parameters(*[kernels[i] for i in linked])

View file

@ -85,21 +85,63 @@ class MultioutputKern(CombinationKernel):
self.link_parameters(*[kernels[i] for i in linked])
@Cache_this(limit=3, ignore_args=())
def K(self, X ,X2=None):
def K(self, X, X2=None):
if X2 is None:
X2 = X
slices = index_to_slices(X[:,self.index_dim])
slices2 = index_to_slices(X2[:,self.index_dim])
target = np.zeros((X.shape[0], X2.shape[0]))
[[[[ target.__setitem__((slices[i][k],slices2[j][l]), self.covariance[i][j].K(X[slices[i][k],:],X2[slices2[j][l],:])) for k in range( len(slices[i]))] for l in range(len(slices2[j])) ] for i in range(len(slices))] for j in range(len(slices2))]
for j in range(len(slices2)):
for i in range(len(slices)):
for l in range(len(slices2[j])):
for k in range(len(slices[i])):
cov_K = self.covariance[i][j].K(X[slices[i][k],:], X2[slices2[j][l],:])
target.__setitem__((slices[i][k], slices2[j][l]), cov_K)
return target
@Cache_this(limit=3, ignore_args=())
def Kdiag(self,X):
def Kdiag(self, X):
slices = index_to_slices(X[:,self.index_dim])
kerns = itertools.repeat(self.kern) if self.single_kern else self.kern
target = np.zeros(X.shape[0])
[[np.copyto(target[s], kern.Kdiag(X[s])) for s in slices_i] for kern, slices_i in zip(kerns, slices)]
for kern, slices_i in zip(kerns, slices):
for s in slices_i:
np.copyto(target[s], kern.Kdiag(X[s]))
return target
@Cache_this(limit=3, ignore_args=())
def dK_dX(self, X, X2, dimX):
"""
Compute the derivative of K with respect to:
dimension dimX of set X.
"""
if X2 is None:
X2 = X
slices = index_to_slices(X[:,self.index_dim])
slices2 = index_to_slices(X2[:,self.index_dim])
target = np.zeros((X.shape[0], X2.shape[0]))
for j in range(len(slices2)):
for i in range(len(slices)):
for l in range(len(slices2[j])):
for k in range(len(slices[i])):
cov_dK_dX = self.covariance[i][j].dK_dX(X[slices[i][k],:], X2[slices2[j][l],:], dimX)
target.__setitem__((slices[i][k], slices2[j][l]), cov_dK_dX)
return target
@Cache_this(limit=3, ignore_args=())
def dK_dXdiag(self, X, dimX):
"""
Compute the derivative of K with respect to:
dimension dimX of set X.
"""
slices = index_to_slices(X[:,self.index_dim])
kerns = itertools.repeat(self.kern) if self.single_kern else self.kern
target = np.zeros(X.shape[0])
for kern, slices_i in zip(kerns, slices):
for s in slices_i:
np.copyto(target[s], kern.dK_dXdiag(X[s], dimX))
return target
def _update_gradients_full_wrapper(self, kern, dL_dK, X, X2):
@ -115,19 +157,35 @@ class MultioutputKern(CombinationKernel):
def reset_gradients(self):
for kern in self.kern: kern.reset_gradients()
def update_gradients_full(self,dL_dK, X, X2=None):
self.reset_gradients()
def update_gradients_full(self, dL_dK, X, X2=None):
if X2 is None:
X2 = X
slices = index_to_slices(X[:,self.index_dim])
if X2 is not None:
slices2 = index_to_slices(X2[:,self.index_dim])
[[[[ self._update_gradients_full_wrapper(self.covariance[i][j], dL_dK[slices[i][k],slices2[j][l]], X[slices[i][k],:], X2[slices2[j][l],:]) for k in range(len(slices[i]))] for l in range(len(slices2[j]))] for i in range(len(slices))] for j in range(len(slices2))]
else:
[[[[ self._update_gradients_full_wrapper(self.covariance[i][j], dL_dK[slices[i][k],slices[j][l]], X[slices[i][k],:], X[slices[j][l],:]) for k in range(len(slices[i]))] for l in range(len(slices[j]))] for i in range(len(slices))] for j in range(len(slices))]
slices2 = index_to_slices(X2[:,self.index_dim])
self.reset_gradients()
for j in range(len(slices2)):
for i in range(len(slices)):
for l in range(len(slices2[j])):
for k in range(len(slices[i])):
self._update_gradients_full_wrapper(
self.covariance[i][j],
dL_dK[slices[i][k],slices2[j][l]],
X[slices[i][k],:],
X2[slices2[j][l],:]
)
def update_gradients_diag(self, dL_dKdiag, X):
self.reset_gradients()
slices = index_to_slices(X[:,self.index_dim])
[[ self._update_gradients_diag_wrapper(self.covariance[i][i], dL_dKdiag[slices[i][k]], X[slices[i][k],:]) for k in range(len(slices[i]))] for i in range(len(slices))]
self.reset_gradients()
for i in range(len(slices)):
for k in range(len(slices[i])):
self._update_gradients_diag_wrapper(
self.covariance[i][i],
dL_dKdiag[slices[i][k]],
X[slices[i][k],:]
)
def gradients_X(self,dL_dK, X, X2=None):
slices = index_to_slices(X[:,self.index_dim])
@ -137,4 +195,4 @@ class MultioutputKern(CombinationKernel):
[[[[ target.__setitem__((slices[i][k]), target[slices[i][k],:] + self.covariance[i][j].gradients_X(dL_dK[slices[i][k],slices2[j][l]], X[slices[i][k],:], X2[slices2[j][l],:]) ) for k in range(len(slices[i]))] for l in range(len(slices2[j]))] for i in range(len(slices))] for j in range(len(slices2))]
else:
[[[[ target.__setitem__((slices[i][k]), target[slices[i][k],:] + self.covariance[i][j].gradients_X(dL_dK[slices[i][k],slices[j][l]], X[slices[i][k],:], (None if (i==j and k==l) else X[slices[j][l],:] )) ) for k in range(len(slices[i]))] for l in range(len(slices[j]))] for i in range(len(slices))] for j in range(len(slices))]
return target
return target

View file

@ -70,6 +70,310 @@ class Prod(CombinationKernel):
which_parts = self.parts
return reduce(np.multiply, (p.Kdiag(X) for p in which_parts))
def reset_gradients(self):
for part in self.parts:
part.reset_gradients()
@Cache_this(limit=3, force_kwargs=['which_parts'])
def dK_dX(self, X, X2, dimX, which_parts=None):
"""
Compute the derivative of K with respect to:
dimension dimX of set X.
"""
if which_parts is None:
which_parts = self.parts
prod_sum = np.zeros((X.shape[0], X2.shape[0]))
for combination in itertools.combinations(which_parts, len(which_parts) - 1):
if len(combination) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination])
else:
prod = np.ones(prod_sum.shape)
to_update = list(set(which_parts) - set(combination))[0]
prod_sum += prod*to_update.dK_dX(X, X2, dimX)
return prod_sum
@Cache_this(limit=3, force_kwargs=['which_parts'])
def dK_dXdiag(self, X, dimX, which_parts=None):
"""
Compute the derivative of K with respect to:
dimension dimX of set X.
Returns only diagonal elements.
"""
if which_parts is None:
which_parts = self.parts
prod_sum = np.zeros(X.shape[0])
for combination in itertools.combinations(which_parts, len(which_parts) - 1):
if len(combination) > 0:
prod = reduce(np.multiply, [p.Kdiag(X) for p in combination])
else:
prod = np.ones(prod_sum.shape)
to_update = list(set(which_parts) - set(combination))[0]
prod_sum += prod*to_update.dK_dXdiag(X, dimX)
return prod_sum
@Cache_this(limit=3, force_kwargs=['which_parts'])
def dK_dX2(self, X, X2, dimX2, which_parts=None):
"""
Compute the derivative of K with respect to:
dimension dimX2 of set X2.
"""
if which_parts is None:
which_parts = self.parts
prod_sum = np.zeros((X.shape[0], X2.shape[0]))
for combination in itertools.combinations(which_parts, len(which_parts) - 1):
if len(combination) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination])
else:
prod = np.ones(prod_sum.shape)
to_update = list(set(which_parts) - set(combination))[0]
prod_sum += prod*to_update.dK_dX2(X, X2, dimX2)
return prod_sum
@Cache_this(limit=3, force_kwargs=['which_parts'])
def dK2_dXdX2(self, X, X2, dimX, dimX2, which_parts=None):
"""
Compute the second derivative of K with respect to:
dimension dimX of set X, and
dimension dimX2 of set X2.
"""
if which_parts is None:
which_parts = self.parts
prod_sum = np.zeros((X.shape[0], X2.shape[0]))
for combination1 in itertools.combinations(which_parts, len(which_parts) - 1):
if len(combination1) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination1])
else:
prod = np.ones(prod_sum.shape)
to_update1 = list(set(which_parts) - set(combination1))[0]
prod_sum += prod*to_update1.dK2_dXdX2(X, X2, dimX, dimX2)
if len(which_parts) > 1:
for combination2 in itertools.combinations(combination1, len(combination1) - 1):
if len(combination2) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination2])
else:
prod = np.ones(prod_sum.shape)
to_update2 = list(set(combination1) - set(combination2))[0]
prod_sum += prod*to_update1.dK_dX(X, X2, dimX)*to_update2.dK_dX2(X, X2, dimX2)
return prod_sum
@Cache_this(limit=3, force_kwargs=['which_parts'])
def dK2_dXdX2diag(self, X, dimX, dimX2, which_parts=None):
"""
Compute the second derivative of K with respect to:
dimension dimX of set X, and
dimension dimX2 of set X2.
Returns only diagonal elements.
"""
if which_parts is None:
which_parts = self.parts
prod_sum = np.zeros(X.shape[0])
for combination1 in itertools.combinations(which_parts, len(which_parts) - 1):
if len(combination1) > 0:
prod = reduce(np.multiply, [p.Kdiag(X) for p in combination1])
else:
prod = np.ones(prod_sum.shape)
to_update1 = list(set(which_parts) - set(combination1))[0]
prod_sum += prod*to_update1.dK2_dXdX2diag(X, dimX, dimX2)
if len(which_parts) > 1:
for combination2 in itertools.combinations(combination1, len(combination1) - 1):
if len(combination2) > 0:
prod = reduce(np.multiply, [p.Kdiag(X) for p in combination2])
else:
prod = np.ones(prod_sum.shape)
to_update2 = list(set(combination1) - set(combination2))[0]
prod_sum += prod*to_update1.dK_dXdiag(X, dimX)*to_update2.dK_dX2diag(X, dimX)
return prod_sum
@Cache_this(limit=3, force_kwargs=['which_parts'])
def dK2_dXdX(self, X, X2, dimX_0, dimX_1, which_parts=None):
"""
Compute the second derivative of K with respect to:
dimension dimX_0 of set X, and
dimension dimX_1 of set X.
"""
if which_parts is None:
which_parts = self.parts
prod_sum = np.zeros((X.shape[0], X2.shape[0]))
for combination1 in itertools.combinations(which_parts, len(which_parts) - 1):
if len(combination1) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination1])
else:
prod = np.ones(prod_sum.shape)
to_update1 = list(set(which_parts) - set(combination1))[0]
prod_sum += prod*to_update1.dK2_dXdX(X, X2, dimX_0, dimX_1)
if len(which_parts) > 1:
for combination2 in itertools.combinations(combination1, len(combination1) - 1):
if len(combination2) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination2])
else:
prod = np.ones(prod_sum.shape)
to_update2 = list(set(combination1) - set(combination2))[0]
prod_sum += prod*to_update1.dK_dX(X, X2, dimX_0)*to_update2.dK_dX(X, X2, dimX_1)
return prod_sum
@Cache_this(limit=3, force_kwargs=['which_parts'])
def dK3_dXdXdX2(self, X, X2, dimX_0, dimX_1, dimX2, which_parts=None):
"""
Compute the third derivative of K with respect to:
dimension dimX_0 of set X,
dimension dimX_1 of set X, and
dimension dimX2 of set X2.
"""
if which_parts is None:
which_parts = self.parts
prod_sum = np.zeros((X.shape[0], X2.shape[0]))
for combination1 in itertools.combinations(which_parts, len(which_parts) - 1):
if len(combination1) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination1])
else:
prod = np.ones(prod_sum.shape)
to_update1 = list(set(which_parts) - set(combination1))[0]
prod_sum += prod*to_update1.dK3_dXdXdX2(X, X2, dimX_0, dimX_1, dimX2)
if len(which_parts) > 1:
for combination2 in itertools.combinations(combination1, len(combination1) - 1):
if len(combination2) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination2])
else:
prod = np.ones(prod_sum.shape)
to_update2 = list(set(combination1) - set(combination2))[0]
prod_sum += prod*to_update1.dK2_dXdX2(X, X2, dimX_0, dimX2)*to_update2.dK_dX(X, X2, dimX_1)
prod_sum += prod*to_update1.dK2_dXdX(X, X2, dimX_0, dimX_1)*to_update2.dK_dX2(X, X2, dimX2)
prod_sum += prod*to_update1.dK_dX(X, X2, dimX_0)*to_update2.dK2_dXdX2(X, X2, dimX_1, dimX2)
if len(which_parts) > 2:
for combination3 in itertools.combinations(combination2, len(combination2) - 1):
if len(combination3) > 0:
prod = reduce(np.multiply, [p.K(X, X2) for p in combination3])
else:
prod = np.ones(prod_sum.shape)
to_update3 = list(set(combination2) - set(combination3))[0]
prod_sum += prod*to_update1.dK_dX(X, X2, dimX_0)*to_update2.dK_dX2(X, X2, dimX2)*to_update3.dK_dX(X, X2, dimX_1)
return prod_sum
@Cache_this(limit=3, force_kwargs=['which_parts'])
def dK3_dXdXdX2diag(self, X, dimX_0, dimX_1, dimX2, which_parts=None):
"""
Compute the third derivative of K with respect to:
dimension dimX_0 of set X,
dimension dimX_1 of set X, and
dimension dimX2 of set X2.
Returns only diagonal elements of the covariance matrix.
"""
if which_parts is None:
which_parts = self.parts
prod_sum = np.zeros(X.shape[0])
for combination1 in itertools.combinations(which_parts, len(which_parts) - 1):
if len(combination1) > 0:
prod = reduce(np.multiply, [p.Kdiag(X) for p in combination1])
else:
prod = np.ones(prod_sum.shape)
to_update1 = list(set(which_parts) - set(combination1))[0]
prod_sum += prod*to_update1.dK3_dXdXdX2diag(X, dimX_0, dimX_1, dimX2)
if len(which_parts) > 1:
for combination2 in itertools.combinations(combination1, len(combination1) - 1):
if len(combination2) > 0:
prod = reduce(np.multiply, [p.Kdiag(X) for p in combination2])
else:
prod = np.ones(prod_sum.shape)
to_update2 = list(set(combination1) - set(combination2))[0]
prod_sum += prod*to_update1.dK2_dXdX2diag(X, dimX_0, dimX2)*to_update2.dK_dXdiag(X, dimX_1)
prod_sum += prod*to_update1.dK2_dXdXdiag(X, dimX_0, dimX_1)*to_update2.dK_dX2diag(X, dimX2)
prod_sum += prod*to_update1.dK_dXdiag(X, dimX_0)*to_update2.dK2_dXdX2diag(X, dimX_1, dimX2)
if len(which_parts) > 2:
for combination3 in itertools.combinations(combination2, len(combination2) - 1):
if len(combination3) > 0:
prod = reduce(np.multiply, [p.Kdiag(X) for p in combination3])
else:
prod = np.ones(prod_sum.shape)
to_update3 = list(set(combination2) - set(combination3))[0]
prod_sum += prod*to_update1.dK_dXdiag(X, dimX_0)*to_update2.dK_dX2diag(X, dimX2)*to_update3.dK_dXdiag(X, dimX_1)
return prod_sum
def update_gradients_direct(self, *args):
for i, (g,p) in enumerate(zip(args, self.parts)):
p.update_gradients_direct(*g)
def dgradients_dX(self, X, X2, dimX, parts=None):
"""
Compute the hyperparameter gradients of:
the derivative of K with respect to dimension dimX of set X
("dK_dX").
"""
if parts is None:
parts = self.parts
gradients = []
for part in parts:
neq_parts = [p for p in parts if p is not part]
if len(neq_parts) > 0:
K = self.K(X, X2, which_parts=neq_parts)
K_dx = self.dK_dX(X, X2, dimX, which_parts=neq_parts)
else:
K = np.ones((X.shape[0], X2.shape[0]))
K_dx = np.zeros((X.shape[0], X2.shape[0]))
g = part.dgradients(X, X2)
g_dx = part.dgradients_dX(X, X2, dimX)
gradients += [[(g_i*K_dx + g_dx_i*K) for (g_i, g_dx_i) in zip(g, g_dx)]]
return gradients
def dgradients_dX2(self, X, X2, dimX2, parts=None):
"""
Compute the hyperparameter gradients of:
the derivative of K with respect to dimension dimX2 of set X2
("dK_dX2").
"""
if parts is None:
parts = self.parts
gradients = []
for part in parts:
neq_parts = [p for p in parts if p is not part]
if len(neq_parts) > 0:
K = self.K(X, X2, which_parts=neq_parts)
K_dx2 = self.dK_dX2(X, X2, dimX2, which_parts=neq_parts)
else:
K = np.ones((X.shape[0], X2.shape[0]))
K_dx2 = np.zeros((X.shape[0], X2.shape[0]))
g = part.dgradients(X, X2)
g_dx2 = part.dgradients_dX2(X, X2, dimX2)
gradients += [[(g_i*K_dx2 + g_dx2_i*K) for (g_i, g_dx2_i) in zip(g, g_dx2)]]
return gradients
def dgradients2_dXdX2(self, X, X2, dimX, dimX2, parts=None):
"""
Compute the hyperparameter gradients of:
the second derivative of K with respect to:
dimension dimX of set X, and
dimension dimX2 of set X2
("dK2_dXdX2").
"""
if parts is None:
parts = self.parts
gradients = []
for part in parts:
neq_parts = [p for p in parts if p is not part]
K = self.K(X, X2, which_parts=neq_parts)
K_dx = self.dK_dX(X, X2, dimX, which_parts=neq_parts)
K_dx2 = self.dK_dX2(X, X2, dimX2, which_parts=neq_parts)
K_dxdx2 = self.dK2_dXdX2(X, X2, dimX, dimX2, which_parts=neq_parts)
g = part.dgradients(X, X2)
g_dx = part.dgradients_dX(X, X2, dimX)
g_dx2 = part.dgradients_dX2(X, X2, dimX2)
g_dxdx2 = part.dgradients2_dXdX2(X, X2, dimX, dimX2)
gradients += [[(g_i*K_dxdx2 + g_dx_i*K_dx2 + g_dx2_i*K_dx + g_dxdx2_i*K) for (g_i, g_dx_i, g_dx2_i, g_dxdx2_i) in zip(g, g_dx, g_dx2, g_dxdx2)]]
return gradients
def update_gradients_full(self, dL_dK, X, X2=None):
if len(self.parts)==2:
self.parts[0].update_gradients_full(dL_dK*self.parts[1].K(X,X2), X, X2)

View file

@ -53,24 +53,126 @@ class RBF(Stationary):
@Cache_this(limit=3, ignore_args=())
def dK_dX(self, X, X2, dimX):
r = self._scaled_dist(X, X2)
K = self.K_of_r(r)
dist = X[:,None,dimX]-X2[None,:,dimX]
lengthscale2inv = (np.ones((X.shape[1]))/(self.lengthscale**2))[dimX]
return -1.*K*dist*lengthscale2inv
"""
Compute the derivative of K with respect to:
dimension dimX of set X.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX]
dist = X[:,None,dimX] - X2[None,:,dimX]
return -dist*(lengthscaleinv**2)*self._clean_K(X, X2)
@Cache_this(limit=3, ignore_args=())
def dK_dXdiag(self, X, dimX):
"""
Compute the derivative of K with respect to:
dimension dimX of set X.
Returns only diagonal elements.
"""
return np.zeros(X.shape[0])
@Cache_this(limit=3, ignore_args=())
def dK_dX2(self, X, X2, dimX2):
return -self.dK_dX(X,X2, dimX2)
"""
Compute the derivative of K with respect to:
dimension dimX2 of set X2.
"""
return -self._clean_dK_dX(X, X2, dimX2)
@Cache_this(limit=3, ignore_args=())
def dK_dX2diag(self, X, dimX2):
"""
Compute the derivative of K with respect to:
dimension dimX2 of set X2.
Returns only diagonal elements.
"""
return np.zeros(X.shape[0])
@Cache_this(limit=3, ignore_args=())
def dK2_dXdX2(self, X, X2, dimX, dimX2):
r = self._scaled_dist(X, X2)
K = self.K_of_r(r)
if X2 is None:
X2=X
dist = X[:,None,:]-X2[None,:,:]
lengthscale2inv = np.ones((X.shape[1]))/(self.lengthscale**2)
return -1.*K*dist[:,:,dimX]*dist[:,:,dimX2]*lengthscale2inv[dimX]*lengthscale2inv[dimX2] + (dimX==dimX2)*K*lengthscale2inv[dimX]
"""
Compute the second derivative of K with respect to:
dimension dimX of set X, and
dimension dimX2 of set X2.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))
dist = np.rollaxis(X[:,None,:] - X2[None,:,:], 2, 0)
term = dist[dimX]*(lengthscaleinv[dimX]**2)
term *= dist[dimX2]*(lengthscaleinv[dimX2]**2)
if dimX == dimX2:
term -= (lengthscaleinv[dimX]**2)
return -term*self._clean_K(X, X2)
@Cache_this(limit=3, ignore_args=())
def dK2_dXdX2diag(self, X, dimX, dimX2):
"""
Compute the second derivative of K with respect to:
dimension dimX of set X, and
dimension dimX2 of set X2.
Returns only diagonal elements.
"""
if dimX == dimX2:
lengthscaleinv = np.ones((X.shape[1]))/(self.lengthscale)
return np.ones(X.shape[0])*(lengthscaleinv[dimX]**2)*self.variance
else:
return np.zeros(X.shape[0])
@Cache_this(limit=3, ignore_args=())
def dK2_dXdX(self, X, X2, dimX_0, dimX_1):
"""
Compute the second derivative of K with respect to:
dimension dimX_0 of set X, and
dimension dimX_1 of set X.
"""
return -self._clean_dK2_dXdX2(X, X2, dimX_0, dimX_1)
@Cache_this(limit=3, ignore_args=())
def dK2_dXdXdiag(self, X, dimX_0, dimX_1):
"""
Compute the second derivative of K with respect to:
dimension dimX_0 of set X, and
dimension dimX_1 of set X.
Returns only diagonal elements.
"""
return -self._clean_dK2_dXdX2diag(X, dimX_0, dimX_1)
@Cache_this(limit=3, ignore_args=())
def dK3_dXdXdX2(self, X, X2, dimX_0, dimX_1, dimX2):
"""
Compute the third derivative of K with respect to:
dimension dimX_0 of set X,
dimension dimX_1 of set X, and
dimension dimX2 of set X2.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))
dist = np.rollaxis(X[:,None,:] - X2[None,:,:], 2, 0)
term = dist[dimX_0]*(lengthscaleinv[dimX_0]**2)
term *= dist[dimX_1]*(lengthscaleinv[dimX_1]**2)
term *= dist[dimX2]*(lengthscaleinv[dimX2]**2)
if dimX_0 == dimX_1:
term -= dist[dimX2]*(lengthscaleinv[dimX2]**2)*(lengthscaleinv[dimX_0]**2)
if dimX_0 == dimX2:
term -= dist[dimX_1]*(lengthscaleinv[dimX_1]**2)*(lengthscaleinv[dimX_0]**2)
if dimX_1 == dimX2:
term -= dist[dimX_0]*(lengthscaleinv[dimX_0]**2)*(lengthscaleinv[dimX_1]**2)
return term*self._clean_K(X, X2)
@Cache_this(limit=3, ignore_args=())
def dK3_dXdXdX2diag(self, X, dimX_0, dimX_1, dimX2):
"""
Compute the third derivative of K with respect to:
dimension dimX_0 of set X,
dimension dimX_1 of set X, and
dimension dimX2 of set X2.
Returns only diagonal elements of the covariance matrix.
"""
return np.zeros(X.shape[0])
def dK_dr(self, r):
return -r*self.K_of_r(r)
@ -80,73 +182,132 @@ class RBF(Stationary):
def dK2_drdr_diag(self):
return -self.variance # as the diagonal of r is always filled with zeros
@Cache_this(limit=3, ignore_args=())
def dK_dvariance(self,X,X2):
return self.K(X,X2)/self.variance
def dK_dvariance(self, X, X2):
"""
Compute the derivative of K with respect to variance.
"""
return self._clean_K(X, X2)/self.variance
@Cache_this(limit=3, ignore_args=())
def dK2_dvariancedX(self, X, X2, dim):
return self.dK_dX(X,X2, dim)/self.variance
def dK_dlengthscale(self, X, X2):
"""
Compute the derivative(s) of K with respect to lengthscale(s).
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))
dist = np.rollaxis(X[:,None,:] - X2[None,:,:], 2, 0)
K = self._clean_K(X, X2)
if self.ARD:
g = []
for diml in range(self.input_dim):
g += [(dist[diml]**2)*(lengthscaleinv[diml]**3)*K]
else:
g = (lengthscaleinv[0]**3)*np.sum(dist**2, axis=0)*K
return g
@Cache_this(limit=3, ignore_args=())
def dK2_dvariancedX2(self, X, X2, dim):
return self.dK_dX2(X,X2, dim)/self.variance
def dK2_dvariancedX(self, X, X2, dimX):
"""
Compute the second derivative of K with respect to:
variance, and
dimension dimX of set X.
"""
return self._clean_dK_dX(X, X2, dimX)/self.variance
@Cache_this(limit=3, ignore_args=())
def dK3_dvariancedXdX2(self, X, X2, dim, dimX2):
return self.dK2_dXdX2(X, X2, dim, dimX2)/self.variance
def dK2_dvariancedX2(self, X, X2, dimX2):
"""
Compute the second derivative of K with respect to:
variance, and
dimension dimX2 of set X2.
"""
return -self.dK2_dvariancedX(X, X2, dimX2)
@Cache_this(limit=3, ignore_args=())
def dK2_dlengthscaledX(self, X, X2, dimX):
r = self._scaled_dist(X, X2)
K = self.K_of_r(r)
if X2 is None:
X2=X
dist = X[:,None,:]-X2[None,:,:]
lengthscaleinv = np.ones((X.shape[1]))/(self.lengthscale)
"""
Compute the second derivative(s) of K with respect to:
lengthscale(s), and
dimension dimX of set X.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))
dist = np.rollaxis(X[:,None,:] - X2[None,:,:], 2, 0)
dK_dX = self._clean_dK_dX(X, X2, dimX)
dK_dl = self.dK_dlengthscale(X, X2)
if self.ARD:
g = []
for diml in range(X.shape[1]):
g += [-1.*K*dist[:,:,dimX]*(dist[:,:,diml]**2)*(lengthscaleinv[dimX]**2)*(lengthscaleinv[diml]**3) + 2.*dist[:,:,dimX]*(lengthscaleinv[diml]**3)*K*(dimX == diml)]
for diml in range(self.input_dim):
term = -dist[dimX]*(lengthscaleinv[dimX]**2)*dK_dl[diml]
if diml == dimX:
term -= 2*lengthscaleinv[dimX]*dK_dX
g += [term]
else:
g = -1.*K*dist[:,:,dimX]*np.sum(dist**2, axis=2)*(lengthscaleinv[dimX]**5) + 2.*dist[:,:,dimX]*(lengthscaleinv[dimX]**3)*K
term = -dist[dimX]*(lengthscaleinv[0]**2)*dK_dl
term -= 2*lengthscaleinv[0]*dK_dX
g = term
return g
@Cache_this(limit=3, ignore_args=())
def dK2_dlengthscaledX2(self, X, X2, dimX2):
tmp = self.dK2_dlengthscaledX(X, X2, dimX2)
"""
Compute the second derivative(s) of K with respect to:
lengthscale(s), and
dimension dimX2 of set X2.
"""
dK2_dlengthscaledX = self.dK2_dlengthscaledX(X, X2, dimX2)
if self.ARD:
return [-1.*g for g in tmp]
return [-1.*g for g in dK2_dlengthscaledX]
else:
return -1*tmp
return -1*dK2_dlengthscaledX
@Cache_this(limit=3, ignore_args=())
def dK3_dvariancedXdX2(self, X, X2, dimX, dimX2):
"""
Compute the third derivative of K with respect to:
variance,
dimension dimX of set X, and
dimension dimX2 of set X2.
"""
return self._clean_dK2_dXdX2(X, X2, dimX, dimX2)/self.variance
@Cache_this(limit=3, ignore_args=())
def dK3_dlengthscaledXdX2(self, X, X2, dimX, dimX2):
r = self._scaled_dist(X, X2)
K = self.K_of_r(r)
if X2 is None:
X2=X
dist = X[:,None,:]-X2[None,:,:]
lengthscaleinv = np.ones((X.shape[1]))/(self.lengthscale)
lengthscale2inv = lengthscaleinv**2
"""
Compute the third derivative(s) of K with respect to:
lengthscale(s),
dimension dimX of set X, and
dimension dimX2 of set X2.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))
dist = np.rollaxis(X[:,None,:] - X2[None,:,:], 2, 0)
K = self._clean_K(X, X2)
dK_dX = self._clean_dK_dX(X, X2, dimX)
dK_dX2 = self._clean_dK_dX(X, X2, dimX2)
dK2_dXdX2 = self._clean_dK2_dXdX2(X, X2, dimX, dimX2)
if self.ARD:
g = []
for diml in range(X.shape[1]):
tmp = -1.*K*dist[:,:,dimX]*dist[:,:,dimX2]*(dist[:,:,diml]**2)*lengthscale2inv[dimX]*lengthscale2inv[dimX2]*(lengthscaleinv[diml]**3)
if dimX == dimX2:
tmp += K*lengthscale2inv[dimX]*(lengthscaleinv[diml]**3)*(dist[:,:,diml]**2)
for diml in range(self.input_dim):
term = (dist[diml]**2)*(lengthscaleinv[diml]**3)*dK2_dXdX2
if diml == dimX:
tmp += 2.*K*dist[:,:,dimX]*dist[:,:,dimX2]*lengthscale2inv[dimX2]*(lengthscaleinv[dimX]**3)
term -= 2*dist[dimX]*(lengthscaleinv[dimX]**3)*dK_dX2
if diml == dimX2:
tmp += 2.*K*dist[:,:,dimX]*dist[:,:,dimX2]*lengthscale2inv[dimX]*(lengthscaleinv[dimX2]**3)
if dimX == dimX2:
tmp += -2.*K*(lengthscaleinv[dimX]**3)
g += [tmp]
term -= 2*dist[dimX2]*(lengthscaleinv[dimX2]**3)*dK_dX
if diml == dimX == dimX2:
term -= 2*(lengthscaleinv[dimX]**3)*K
g += [term]
else:
g = -1.*K*dist[:,:,dimX]*dist[:,:,dimX2]*np.sum(dist**2, axis=2)*(lengthscaleinv[dimX]**7) +4*K*dist[:,:,dimX]*dist[:,:,dimX2]*(lengthscaleinv[dimX]**5)
term = np.sum(dist**2, axis=0)*dK2_dXdX2
term -= 4*dist[dimX2]*dK_dX
if dimX == dimX2:
g += -2.*K*(lengthscaleinv[dimX]**3) + K*(lengthscaleinv[dimX]**5)*np.sum(dist**2, axis=2)
term -= 2*K
g = (lengthscaleinv[0]**3)*term
return g
def __getstate__(self):

View file

@ -122,7 +122,6 @@ class StdPeriodic(Kern):
pass
def K(self, X, X2=None):
"""Compute the covariance matrix between X and X2."""
if X2 is None:
@ -133,13 +132,372 @@ class StdPeriodic(Kern):
return self.variance * exp_dist
def Kdiag(self, X):
"""Compute the diagonal of the covariance matrix associated to X."""
ret = np.empty(X.shape[0])
ret[:] = self.variance
return ret
def dK_dX(self, X, X2, dimX):
"""
Compute the derivative of K with respect to:
dimension dimX of set X.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX]
periodinv = (np.ones(X.shape[1])/(self.period))[dimX]
F = 0.5*np.pi*(lengthscaleinv**2)*periodinv # multiplicative factor
dist = X[:,None,dimX] - X2[None,:,dimX]
base = np.pi*periodinv*dist
return -F*np.sin(2*base)*self._clean_K(X, X2)
def dK_dXdiag(self, X, dimX):
"""
Compute the derivative of K with respect to:
dimension dimX of set X.
Returns only diagonal elements.
"""
return np.zeros(X.shape[0])
def dK_dX2(self, X, X2, dimX2):
"""
Compute the derivative of K with respect to:
dimension dimX2 of set X2.
"""
return -self._clean_dK_dX(X, X2, dimX2)
def dK_dX2diag(self, X, dimX2):
"""
Compute the derivative of K with respect to:
dimension dimX2 of set X2.
Returns only diagonal elements.
"""
return np.zeros(X.shape[0])
def dK2_dXdX2(self, X, X2, dimX, dimX2):
"""
Compute the second derivative of K with respect to:
dimension dimX of set X, and
dimension dimX2 of set X2.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX2]
periodinv = (np.ones(X.shape[1])/(self.period))[dimX2]
F = 0.5*np.pi*(lengthscaleinv**2)*periodinv # multiplicative factor
dist = X[:,None,dimX2] - X2[None,:,dimX2]
base = np.pi*periodinv*dist
term = np.sin(2*base)*self._clean_dK_dX(X, X2, dimX)
if dimX == dimX2:
term += 2*np.pi*periodinv*np.cos(2*base)*self._clean_K(X, X2)
return F*term
def dK2_dXdX2diag(self, X, dimX, dimX2):
"""
Compute the second derivative of K with respect to:
dimension dimX of set X, and
dimension dimX2 of set X2.
Returns only diagonal elements.
"""
if dimX == dimX2:
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX2]
periodinv = (np.ones(X.shape[1])/(self.period))[dimX2]
return (np.pi**2)*(lengthscaleinv**2)*(periodinv**2)*self.variance*np.ones(X.shape[0])
else:
return np.zeros(X.shape[0])
def dK2_dXdX(self, X, X2, dimX_0, dimX_1):
"""
Compute the second derivative of K with respect to:
dimension dimX_0 of set X, and
dimension dimX_1 of set X.
"""
return -self._clean_dK2_dXdX2(X, X2, dimX_0, dimX_1)
def dK2_dXdXdiag(self, X, dimX_0, dimX_1):
"""
Compute the second derivative of K with respect to:
dimension dimX_0 of set X, and
dimension dimX_1 of set X.
Returns only diagonal elements.
"""
return -self._clean_dK2_dXdX2diag(X, dimX_0, dimX_1)
def dK3_dXdXdX2(self, X, X2, dimX_0, dimX_1, dimX2):
"""
Compute the third derivative of K with respect to:
dimension dimX_0 of set X,
dimension dimX_1 of set X, and
dimension dimX2 of set X2.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX2]
periodinv = (np.ones(X.shape[1])/(self.period))[dimX2]
F = 0.5*np.pi*(lengthscaleinv**2)*periodinv # multiplicative factor
dist = X[:,None,dimX2] - X2[None,:,dimX2]
base = np.pi*periodinv*dist
term = np.sin(2*base)*self._clean_dK2_dXdX(X, X2, dimX_0, dimX_1)
if dimX_0 == dimX2:
term += 2*np.pi*periodinv*np.cos(2*base)*self._clean_dK_dX(X, X2, dimX_1)
if dimX_1 == dimX2:
term += 2*np.pi*periodinv*np.cos(2*base)*self._clean_dK_dX(X, X2, dimX_0)
if dimX_0 == dimX_1 == dimX2:
term -= 4*(np.pi**2)*(periodinv**2)*np.sin(2*base)*self._clean_K(X, X2)
return F*term
def dK3_dXdXdX2diag(self, X, dimX_0, dimX_1, dimX2):
"""
Compute the third derivative of K with respect to:
dimension dimX_0 of set X,
dimension dimX_1 of set X, and
dimension dimX2 of set X2.
Returns only diagonal elements of the covariance matrix.
"""
return np.zeros(X.shape[0])
def dK_dvariance(self, X, X2):
"""
Compute the derivative of K with respect to variance.
"""
return self._clean_K(X, X2)/self.variance
def dK_dlengthscale(self, X, X2):
"""
Compute the derivative(s) of K with respect to lengthscale(s).
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))
periodinv = (np.ones(X.shape[1])/(self.period))
dist = np.rollaxis(X[:,None,:] - X2[None,:,:], 2, 0)
base = np.pi*periodinv[:,None,None]*dist
K = self._clean_K(X, X2)
if self.ARD2:
g = []
for diml in range(self.input_dim):
g += [(lengthscaleinv[diml]**3)*np.square(np.sin(base[diml]))*K]
else:
g = (lengthscaleinv[0]**3)*np.sum(np.square(np.sin(base)), axis=0)*K
return g
def dK_dperiod(self, X, X2):
"""
Compute the derivative(s) of K with respect to period(s).
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))
periodinv = (np.ones(X.shape[1])/(self.period))
dist = np.rollaxis(X[:,None,:] - X2[None,:,:], 2, 0)
base = np.pi*periodinv[:,None,None]*dist
K = self._clean_K(X, X2)
if self.ARD1:
g = []
for diml in range(self.input_dim):
g += [0.5*base[diml]*(lengthscaleinv[diml]**2)*periodinv[diml]*np.sin(2*base[diml])*K]
else:
g = 0.5*periodinv[0]*np.sum(base*(lengthscaleinv**2)[:,None,None]*np.sin(2*base), axis=0)*K
return g
def dK2_dvariancedX(self, X, X2, dimX):
"""
Compute the second derivative of K with respect to:
variance, and
dimension dimX of set X.
"""
return self._clean_dK_dX(X, X2, dimX)/self.variance
def dK2_dvariancedX2(self, X, X2, dimX2):
"""
Compute the second derivative of K with respect to:
variance, and
dimension dimX2 of set X2.
"""
return -self.dK2_dvariancedX(X, X2, dimX2)
def dK2_dlengthscaledX(self, X, X2, dimX):
"""
Compute the second derivative(s) of K with respect to:
lengthscale(s), and
dimension dimX of set X.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX]
periodinv = (np.ones(X.shape[1])/(self.period))[dimX]
dist = X[:,None,dimX] - X2[None,:,dimX]
base = np.pi*periodinv*dist
F = 0.5*np.pi*(lengthscaleinv**2)*periodinv # multiplicative factor
K = self._clean_K(X, X2)
dK_dl = self.dK_dlengthscale(X, X2)
if self.ARD2:
g = []
for diml in range(self.input_dim):
term = dK_dl[diml]
if diml == dimX:
term -= 2*lengthscaleinv*K
g += [-F*np.sin(2*base)*term]
else:
g = -F*np.sin(2*base)*(dK_dl - 2*lengthscaleinv*K)
return g
def dK2_dlengthscaledX2(self, X, X2, dimX2):
"""
Compute the second derivative(s) of K with respect to:
lengthscale(s), and
dimension dimX2 of set X2.
"""
dK2_dldX = self.dK2_dlengthscaledX(X, X2, dimX2)
if self.ARD2:
return [-1*g for g in dK2_dldX]
else:
return -1*dK2_dldX
def dK2_dperioddX(self, X, X2, dimX):
"""
Compute the second derivative(s) of K with respect to:
period(s), and
dimension dimX of set X.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX]
periodinv = (np.ones(X.shape[1])/(self.period))[dimX]
dist = X[:,None,dimX] - X2[None,:,dimX]
base = np.pi*periodinv*dist
F = 0.5*np.pi*(lengthscaleinv**2)*periodinv # multiplicative factor
K = self._clean_K(X, X2)
dK_dT = self.dK_dperiod(X, X2)
if self.ARD1:
g = []
for dimT in range(self.input_dim):
term = np.sin(2*base)*dK_dT[dimT]
if dimT == dimX:
term -= periodinv*(np.sin(2*base)+2*base*np.cos(2*base))*K
g += [-F*term]
else:
term = np.sin(2*base)*dK_dT
term -= periodinv*(np.sin(2*base)+2*base*np.cos(2*base))*K
g = -F*term
return g
def dK2_dperioddX2(self, X, X2, dimX2):
"""
Compute the second derivative(s) of K with respect to:
period(s), and
dimension dimX2 of set X2.
"""
dK2_dperioddX = self.dK2_dperioddX(X, X2, dimX2)
if self.ARD1:
return [-1*g for g in dK2_dperioddX]
else:
return -1*dK2_dperioddX
def dK3_dvariancedXdX2(self, X, X2, dimX, dimX2):
"""
Compute the third derivative of K with respect to:
variance,
dimension dimX of set X, and
dimension dimX2 of set X2.
"""
return self._clean_dK2_dXdX2(X, X2, dimX, dimX2)/self.variance
def dK3_dlengthscaledXdX2(self, X, X2, dimX, dimX2):
"""
Compute the third derivative(s) of K with respect to:
lengthscale(s),
dimension dimX of set X, and
dimension dimX2 of set X2.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX2]
periodinv = (np.ones(X.shape[1])/(self.period))[dimX2]
dist = X[:,None,dimX2] - X2[None,:,dimX2]
base = np.pi*periodinv*dist
F = 0.5*np.pi*(lengthscaleinv**2)*periodinv # multiplicative factor
dK2_dXdX2 = self._clean_dK2_dXdX2(X, X2, dimX, dimX2)
dK_dl = self.dK_dlengthscale(X, X2)
dK2_dldX = self.dK2_dlengthscaledX(X, X2, dimX)
if self.ARD2:
g = []
for diml in range(self.input_dim):
term = np.sin(2*base)*dK2_dldX[diml]
if dimX == dimX2:
term += 2*np.pi*periodinv*np.cos(2*base)*dK_dl[diml]
term *= F
if diml == dimX2:
term -= 2*lengthscaleinv*dK2_dXdX2
g += [term]
else:
term = np.sin(2*base)*dK2_dldX
if dimX == dimX2:
term += 2*np.pi*periodinv*np.cos(2*base)*dK_dl
term *= F
term -= 2*lengthscaleinv*dK2_dXdX2
g = term
return g
def dK3_dperioddXdX2(self, X, X2, dimX, dimX2):
"""
Compute the third derivative(s) of K with respect to:
period(s),
dimension dimX of set X, and
dimension dimX2 of set X2.
"""
lengthscaleinv = (np.ones(X.shape[1])/(self.lengthscale))[dimX2]
periodinv = (np.ones(X.shape[1])/(self.period))[dimX2]
dist = X[:,None,dimX2] - X2[None,:,dimX2]
base = np.pi*periodinv*dist
F = 0.5*np.pi*(lengthscaleinv**2)*periodinv # multiplicative factor
K = self._clean_K(X, X2)
dK_dX = self._clean_dK_dX(X, X2, dimX)
dK2_dXdX2 = self._clean_dK2_dXdX2(X, X2, dimX, dimX2)
dK_dT = self.dK_dperiod(X, X2)
dK2_dTdX = self.dK2_dperioddX(X, X2, dimX)
if self.ARD1:
g = []
for dimT in range(self.input_dim):
term = np.sin(2*base)*dK2_dTdX[dimT]
if dimT == dimX2:
term -= 2*periodinv*np.cos(2*base)*base*dK_dX
if dimX == dimX2:
term += 2*np.pi*periodinv*np.cos(2*base)*dK_dT[dimT]
if dimX == dimX2 == dimT:
term += 2*np.pi*(periodinv**2)*(2*base*np.sin(2*base)-np.cos(2*base))*K
term *= F
if dimT == dimX2:
term -= periodinv*dK2_dXdX2
g += [term]
else:
term = np.sin(2*base)*dK2_dTdX-2*periodinv*base*np.cos(2*base)*dK_dX
if dimX == dimX2:
term += 2*np.pi*periodinv*(np.cos(2*base)*dK_dT+periodinv*(2*base*np.sin(2*base)-np.cos(2*base))*K)
g = F*term-periodinv*dK2_dXdX2
return g
def update_gradients_full(self, dL_dK, X, X2=None):
"""derivative of the covariance matrix with respect to the parameters."""
if X2 is None:
@ -167,12 +525,52 @@ class StdPeriodic(Kern):
else: # same lengthscales
self.lengthscale.gradient = np.sum(dl.sum(-1) * exp_dist * dL_dK)
def update_gradients_direct(self, dL_dVar, dL_dPer, dL_dLen):
self.variance.gradient = dL_dVar
self.period.gradient = dL_dPer
self.lengthscale.gradient = dL_dLen
def reset_gradients(self):
self.variance.gradient = 0.
if not self.ARD1:
self.period.gradient = 0.
else:
self.period.gradient = np.zeros(self.input_dim)
if not self.ARD2:
self.lengthscale.gradient = 0.
else:
self.lengthscale.gradient = np.zeros(self.input_dim)
def update_gradients_diag(self, dL_dKdiag, X):
"""derivative of the diagonal of the covariance matrix with respect to the parameters."""
self.variance.gradient = np.sum(dL_dKdiag)
self.period.gradient = 0
self.lengthscale.gradient = 0
def dgradients(self, X, X2):
g1 = self.dK_dvariance(X, X2)
g2 = self.dK_dperiod(X, X2)
g3 = self.dK_dlengthscale(X, X2)
return [g1, g2, g3]
def dgradients_dX(self, X, X2, dimX):
g1 = self.dK2_dvariancedX(X, X2, dimX)
g2 = self.dK2_dperioddX(X, X2, dimX)
g3 = self.dK2_dlengthscaledX(X, X2, dimX)
return [g1, g2, g3]
def dgradients_dX2(self, X, X2, dimX2):
g1 = self.dK2_dvariancedX2(X, X2, dimX2)
g2 = self.dK2_dperioddX2(X, X2, dimX2)
g3 = self.dK2_dlengthscaledX2(X, X2, dimX2)
return [g1, g2, g3]
def dgradients2_dXdX2(self, X, X2, dimX, dimX2):
g1 = self.dK3_dvariancedXdX2(X, X2, dimX, dimX2)
g2 = self.dK3_dperioddXdX2(X, X2, dimX, dimX2)
g3 = self.dK3_dlengthscaledXdX2(X, X2, dimX, dimX2)
return [g1, g2, g3]
def gradients_X(self, dL_dK, X, X2=None):
K = self.K(X, X2)
if X2 is None:
@ -185,4 +583,4 @@ class StdPeriodic(Kern):
return np.zeros(X.shape)
def input_sensitivity(self, summarize=True):
return self.variance*np.ones(self.input_dim)/self.lengthscale**2
return self.variance*np.ones(self.input_dim)/self.lengthscale**2

View file

@ -306,7 +306,12 @@ class Stationary(Kern):
l4 = np.ones(X.shape[1])*self.lengthscale**2
return dL_dK_diag * (np.eye(X.shape[1]) * -self.dK2_drdr_diag()/(l4))[None, :,:]# np.zeros(X.shape+(X.shape[1],))
#return np.ones(X.shape) * d2L_dK * self.variance/self.lengthscale**2 # np.zeros(X.shape)
def dgradients(self, X, X2):
g1 = self.dK_dvariance(X, X2)
g2 = self.dK_dlengthscale(X, X2)
return [g1, g2]
def dgradients_dX(self, X, X2, dimX):
g1 = self.dK2_dvariancedX(X, X2, dimX)
g2 = self.dK2_dlengthscaledX(X, X2, dimX)