Repository: tillahoffmann/util

Issue:
Use the negative log likelihood (or loss) for the samplers
To match convention with machine learning libraries that minimise the loss.

Comments:

Patch:
diff --git a/util/__init__.py b/util/__init__.py
index c22bc98..ebf2052 100644
--- a/util/__init__.py
+++ b/util/__init__.py
@@ -1,2 +1,2 @@
-from sampling import *
-from plotting import *
\ No newline at end of file
+from .sampling import *
+from .plotting import *
\ No newline at end of file
diff --git a/util/sampling/__init__.py b/util/sampling/__init__.py
index e31a4c2..06d495d 100644
--- a/util/sampling/__init__.py
+++ b/util/sampling/__init__.py
@@ -1,3 +1,2 @@
 from .metropolis import AdaptiveMetropolisSampler, MetropolisSampler
 from .hamiltonian import HamiltonianSampler
-from .adaptive_rejection import AdaptiveRejectionSampler
diff --git a/util/sampling/adaptive_rejection.py b/util/sampling/adaptive_rejection.py
index d5d1b52..0125edc 100644
--- a/util/sampling/adaptive_rejection.py
+++ b/util/sampling/adaptive_rejection.py
@@ -25,7 +25,6 @@ class AdaptiveRejectionSampler:
         lower and upper bound of the domain on which the distribution is supported. If `domain` is not given, the
         domain is the positive real line.
     """
-
     def __init__(self, fun, x0=None, args=None, jac=None, domain=None):
         # Copy the function and the Jacobian
         self.fun = fun
diff --git a/util/sampling/base.py b/util/sampling/base.py
index 970a28a..422f61e 100644
--- a/util/sampling/base.py
+++ b/util/sampling/base.py
@@ -13,7 +13,7 @@ class BaseSampler(object):
     Parameters
     ----------
     fun : callable
-        log-posterior or log-likelihood function taking a vector of parameters as its first argument
+        negative log-posterior or log-likelihood function taking a vector of parameters as its first argument
     args : array_like
         additional arguments to pass to `fun`
     parameter_names : list
@@ -62,7 +62,7 @@ class BaseSampler(object):
 
     def grid_density_plot(self, burn_in=0, parameters=None, values=None, nrows=None, ncols=None, bins=10):
         """
-        Plot the marginal densities of parameters  (and vertical lines indicating the true values).
+        Plot the marginal densities of parameters (and vertical lines indicating the true values).
 
         Parameters
         ----------
diff --git a/util/sampling/hamiltonian.py b/util/sampling/hamiltonian.py
index a9937cd..a96aa63 100644
--- a/util/sampling/hamiltonian.py
+++ b/util/sampling/hamiltonian.py
@@ -15,8 +15,8 @@ class HamiltonianSampler(BaseSampler):
     Parameters
     ----------
     fun : callable
-        log-posterior or log-likelihood function taking a vector of parameters as its first argument and its derivative
-        if `jac` is not given
+        negative log-posterior or log-likelihood function taking a vector of parameters as its first argument and its
+        derivative if `jac` is not given
     args : array_like
         additional arguments to pass to `fun`
     parameter_names : list
@@ -36,6 +36,7 @@ class HamiltonianSampler(BaseSampler):
                  leapfrog_steps=10):
         super(HamiltonianSampler, self).__init__(fun, args, parameter_names, break_on_interrupt)
         self.jac = jac
+        # Load the mass matrix from disk if given
         if isinstance(mass, str):
             self.mass = np.loadtxt(mass)
         else:
@@ -148,7 +149,7 @@ class HamiltonianSampler(BaseSampler):
 
                 for leapfrog_step in range(leapfrog_steps):
                     # Make a half step for the leapfrog algorithm
-                    momentum = momentum + 0.5 * epsilon * jac
+                    momentum = momentum - 0.5 * epsilon * jac
                     # Update the position
                     if self.mass.ndim < 2:
                         parameters_end = parameters_end + epsilon * self.inv_mass * momentum
@@ -160,7 +161,7 @@ class HamiltonianSampler(BaseSampler):
                     else:
                         fun_value_end, jac = self.fun(parameters_end, *self.args)
                     # Make another half-step
-                    momentum = momentum + 0.5 * epsilon * jac
+                    momentum = momentum - 0.5 * epsilon * jac
 
                     if full:
                         # Append parameters
@@ -182,7 +183,7 @@ class HamiltonianSampler(BaseSampler):
                 kinetic_end = self.evaluate_kinetic(momentum)
 
                 # Accept or reject the step
-                if np.log(np.random.uniform()) < fun_value_end + kinetic_end - fun_value - kinetic:
+                if np.log(np.random.uniform()) < - fun_value_end + kinetic_end + fun_value - kinetic:
                     parameters = parameters_end
                     fun_value = fun_value_end
 
diff --git a/util/sampling/metropolis.py b/util/sampling/metropolis.py
index 8c51fcc..91ea47b 100644
--- a/util/sampling/metropolis.py
+++ b/util/sampling/metropolis.py
@@ -9,7 +9,7 @@ class MetropolisSampler(BaseSampler):
     Parameters
     ----------
     fun : callable
-        log-posterior or log-likelihood function taking a vector of parameters as its first argument
+        negative log-posterior or log-likelihood function taking a vector of parameters as its first argument
     proposal_covariance : array_like
         covariance of the Gaussian proposal distribution
     args : array_like
@@ -45,7 +45,7 @@ class MetropolisSampler(BaseSampler):
                 # Compute the function at the proposed sample
                 fun_proposal = self.fun(proposal, *self.args)
                 # Accept or reject the step
-                if fun_proposal - fun_current > np.log(np.random.uniform()):
+                if fun_proposal - fun_current < np.log(np.random.uniform()):
                     # Update the log posterior and the parameter values
                     fun_current = fun_proposal
                     parameters = proposal


Test patch:
diff --git a/tests/test_sampling.py b/tests/test_sampling.py
index 70d1e98..8e14777 100644
--- a/tests/test_sampling.py
+++ b/tests/test_sampling.py
@@ -12,17 +12,17 @@ for num_parameters in [1, 3, 5]:
     covariance = np.diag(1 + np.random.gamma(1, size=num_parameters))
 
     # Create a metropolis sampler
-    sampler = sampling.MetropolisSampler(lambda x, mean=mean, covariance=covariance: log_gaussian(x, mean, covariance)[0],
+    sampler = sampling.MetropolisSampler(lambda x, mean=mean, covariance=covariance: -log_gaussian(x, mean, covariance)[0],
                                          covariance / num_parameters)
     params.append((mean, covariance, sampler))
 
     # Create an adaptive metropolis sampler
-    sampler = sampling.AdaptiveMetropolisSampler(lambda x, mean=mean, covariance=covariance: log_gaussian(x, mean, covariance)[0])
+    sampler = sampling.AdaptiveMetropolisSampler(lambda x, mean=mean, covariance=covariance: -log_gaussian(x, mean, covariance)[0])
     params.append((mean, covariance, sampler))
 
     # Create a Hamiltonian metropolis sampler
-    sampler = sampling.HamiltonianSampler(lambda x, mean=mean, covariance=covariance: log_gaussian(x, mean, covariance)[0],
-                                          jac=lambda x, mean=mean, covariance=covariance: log_gaussian(x, mean, covariance)[1],
+    sampler = sampling.HamiltonianSampler(lambda x, mean=mean, covariance=covariance: -log_gaussian(x, mean, covariance)[0],
+                                          jac=lambda x, mean=mean, covariance=covariance: -log_gaussian(x, mean, covariance)[1],
                                           mass=covariance)
     params.append((mean, covariance, sampler))