diff --git a/GPy/kern/_src/prod.py b/GPy/kern/_src/prod.py index dd9a5fe4..a3b49973 100644 --- a/GPy/kern/_src/prod.py +++ b/GPy/kern/_src/prod.py @@ -6,6 +6,20 @@ from kern import CombinationKernel from ...util.caching import Cache_this import itertools + +def numpy_invalid_op_as_exception(func): + """ + A decorator that allows catching numpy invalid operations + as exceptions (the default behaviour is raising warnings). + """ + def func_wrapper(*args, **kwargs): + np.seterr(invalid='raise') + result = func(*args, **kwargs) + np.seterr(invalid='warn') + return result + return func_wrapper + + class Prod(CombinationKernel): """ Computes the product of 2 kernels @@ -41,21 +55,35 @@ class Prod(CombinationKernel): which_parts = self.parts return reduce(np.multiply, (p.Kdiag(X) for p in which_parts)) + @numpy_invalid_op_as_exception def update_gradients_full(self, dL_dK, X, X2=None): k = self.K(X,X2)*dL_dK - for p in self.parts: - p.update_gradients_full(k/p.K(X,X2),X,X2) + try: + for p in self.parts: + p.update_gradients_full(k/p.K(X,X2),X,X2) + except FloatingPointError: + for combination in itertools.combinations(self.parts, len(self.parts) - 1): + prod = reduce(np.multiply, [p.K(X, X2) for p in combination]) + to_update = list(set(self.parts) - set(combination))[0] + to_update.update_gradients_full(dL_dK * prod, X, X2) def update_gradients_diag(self, dL_dKdiag, X): k = self.Kdiag(X)*dL_dKdiag for p in self.parts: p.update_gradients_diag(k/p.Kdiag(X),X) + @numpy_invalid_op_as_exception def gradients_X(self, dL_dK, X, X2=None): target = np.zeros(X.shape) k = self.K(X,X2)*dL_dK - for p in self.parts: - target += p.gradients_X(k/p.K(X,X2),X,X2) + try: + for p in self.parts: + target += p.gradients_X(k/p.K(X,X2),X,X2) + except FloatingPointError: + for combination in itertools.combinations(self.parts, len(self.parts) - 1): + prod = reduce(np.multiply, [p.K(X, X2) for p in combination]) + to_update = list(set(self.parts) - set(combination))[0] + target += to_update.gradients_X(dL_dK * prod, X, X2) return target def gradients_X_diag(self, dL_dKdiag, X): @@ -64,3 +92,5 @@ class Prod(CombinationKernel): for p in self.parts: target += p.gradients_X_diag(k/p.Kdiag(X),X) return target + + diff --git a/GPy/testing/kernel_tests.py b/GPy/testing/kernel_tests.py index c1bb9265..415cc7eb 100644 --- a/GPy/testing/kernel_tests.py +++ b/GPy/testing/kernel_tests.py @@ -401,11 +401,27 @@ class Coregionalize_weave_test(unittest.TestCase): GPy.util.config.config.set('weave', 'working', 'False') +class KernelTestsProductWithZeroValues(unittest.TestCase): + + def setUp(self): + self.X = np.array([[0,1],[1,0]]) + self.k = GPy.kern.Linear(2) * GPy.kern.Bias(2) + + def test_zero_valued_kernel_full(self): + self.k.update_gradients_full(1, self.X) + self.assertFalse(np.isnan(self.k['linear.variances'].gradient), + "Gradient resulted in NaN") + + def test_zero_valued_kernel_gradients_X(self): + target = self.k.gradients_X(1, self.X) + self.assertFalse(np.any(np.isnan(target)), + "Gradient resulted in NaN") if __name__ == "__main__": print "Running unit tests, please be (very) patient..." unittest.main() + # np.random.seed(0) # N0 = 3 # N1 = 9