-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make likelihoods a type of cost #230
Changes from all commits
44e5431
af757d4
026b5ae
7bcaaea
9c1335d
b5e8d1f
a9683f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -29,7 +29,7 @@ | |||||
Initial parameter values for the optimization. | ||||||
bounds : dict | ||||||
Dictionary containing the parameter bounds with keys 'lower' and 'upper'. | ||||||
n_parameters : int | ||||||
_n_parameters : int | ||||||
Number of parameters in the optimization problem. | ||||||
sigma0 : float or sequence | ||||||
Initial step size or standard deviation for the optimiser. | ||||||
|
@@ -40,27 +40,24 @@ | |||||
def __init__( | ||||||
self, | ||||||
cost, | ||||||
x0=None, | ||||||
optimiser=None, | ||||||
sigma0=None, | ||||||
verbose=False, | ||||||
physical_viability=True, | ||||||
allow_infeasible_solutions=True, | ||||||
): | ||||||
self.cost = cost | ||||||
self.x0 = x0 | ||||||
self.x0 = cost.x0 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned above. This keeps it open :)
Suggested change
|
||||||
self.optimiser = optimiser | ||||||
self.verbose = verbose | ||||||
self.bounds = cost.bounds | ||||||
self.sigma0 = sigma0 or cost.sigma0 | ||||||
self.n_parameters = cost.n_parameters | ||||||
self._n_parameters = cost._n_parameters | ||||||
self.physical_viability = physical_viability | ||||||
self.allow_infeasible_solutions = allow_infeasible_solutions | ||||||
self.log = [] | ||||||
|
||||||
# Catch x0, and convert to pints vector | ||||||
if x0 is None: | ||||||
self.x0 = cost.x0 | ||||||
# Convert x0 to pints vector | ||||||
self._x0 = pints.vector(self.x0) | ||||||
|
||||||
# Set whether to allow infeasible locations | ||||||
|
@@ -77,12 +74,10 @@ | |||||
self._transformation = None | ||||||
|
||||||
# Check if minimising or maximising | ||||||
self._minimising = not isinstance(cost, pybop.BaseLikelihood) | ||||||
if self._minimising: | ||||||
self._function = self.cost | ||||||
else: | ||||||
self._function = pybop.ProbabilityCost(cost) | ||||||
del cost | ||||||
if isinstance(cost, pybop.BaseLikelihood): | ||||||
self.cost._minimising = False | ||||||
self._minimising = self.cost._minimising | ||||||
self._function = self.cost | ||||||
|
||||||
# Construct Optimiser | ||||||
self.pints = True | ||||||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,27 +1,19 @@ | ||||
import numpy as np | ||||
from pybop.costs.base_cost import BaseCost | ||||
|
||||
|
||||
class BaseLikelihood: | ||||
class BaseLikelihood(BaseCost): | ||||
""" | ||||
Base class for likelihoods | ||||
""" | ||||
|
||||
def __init__(self, problem, sigma=None): | ||||
self.problem = problem | ||||
super(BaseLikelihood, self).__init__(problem) | ||||
self._n_output = problem.n_outputs | ||||
self._n_times = problem.n_time_data | ||||
self.sigma0 = sigma or np.zeros(self._n_output) | ||||
self.x0 = problem.x0 | ||||
self.bounds = problem.bounds | ||||
self._n_parameters = problem.n_parameters | ||||
self._target = problem._target | ||||
|
||||
def __call__(self, x): | ||||
""" | ||||
Calls the problem.evaluate method and calculates | ||||
the log-likelihood | ||||
""" | ||||
raise NotImplementedError | ||||
self.log_likelihood = problem | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be removed as it was only required for the ProbabilityCost() wrapper.
Suggested change
|
||||
|
||||
def set_sigma(self, sigma): | ||||
""" | ||||
|
@@ -44,10 +36,6 @@ def get_n_parameters(self): | |||
""" | ||||
return self._n_parameters | ||||
|
||||
@property | ||||
def n_parameters(self): | ||||
return self._n_parameters | ||||
|
||||
|
||||
class GaussianLogLikelihoodKnownSigma(BaseLikelihood): | ||||
""" | ||||
|
@@ -68,15 +56,15 @@ def __init__(self, problem, sigma=None): | |||
self.sigma2 = self.sigma0**-2 | ||||
self._dl = np.ones(self._n_parameters) | ||||
|
||||
def __call__(self, x): | ||||
def _evaluate(self, x, grad=None): | ||||
""" | ||||
Calls the problem.evaluate method and calculates | ||||
the log-likelihood | ||||
""" | ||||
e = self._target - self.problem.evaluate(x) | ||||
return np.sum(self._offset + self._multip * np.sum(e**2, axis=0)) | ||||
|
||||
def _evaluateS1(self, x): | ||||
def _evaluateS1(self, x, grad=None): | ||||
""" | ||||
Calls the problem.evaluateS1 method and calculates | ||||
the log-likelihood | ||||
|
@@ -116,7 +104,7 @@ def __init__(self, problem): | |||
self._logpi = -0.5 * self._n_times * np.log(2 * np.pi) | ||||
self._dl = np.ones(self._n_parameters + self._n_output) | ||||
|
||||
def __call__(self, x): | ||||
def _evaluate(self, x, grad=None): | ||||
""" | ||||
Evaluates the Gaussian log-likelihood for the given parameters. | ||||
|
||||
|
@@ -140,7 +128,7 @@ def __call__(self, x): | |||
- np.sum(e**2, axis=0) / (2.0 * sigma**2) | ||||
) | ||||
|
||||
def _evaluateS1(self, x): | ||||
def _evaluateS1(self, x, grad=None): | ||||
""" | ||||
Calls the problem.evaluateS1 method and calculates | ||||
the log-likelihood | ||||
|
@@ -163,7 +151,7 @@ def _evaluateS1(self, x): | |||
) | ||||
) | ||||
e = self._target - y | ||||
likelihood = self.__call__(x) | ||||
likelihood = self._evaluate(x) | ||||
dl = np.sum((sigma**-(2.0) * np.sum((e.T * dy.T), axis=2)), axis=1) | ||||
|
||||
# Add sigma gradient to dl | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,12 +75,6 @@ def test_base_likelihood_init(self, problem): | |
assert likelihood._n_parameters == 1 | ||
assert np.array_equal(likelihood._target, problem._target) | ||
|
||
@pytest.mark.unit | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we still want this test? Looking at the above class, |
||
def test_base_likelihood_call_raises_not_implemented_error(self, problem): | ||
likelihood = pybop.BaseLikelihood(problem) | ||
with pytest.raises(NotImplementedError): | ||
likelihood(np.array([0.5, 0.5])) | ||
|
||
@pytest.mark.unit | ||
def test_base_likelihood_set_get_sigma(self, problem): | ||
likelihood = pybop.BaseLikelihood(problem) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want users to overwrite the initial conditions, right? This modification should keep that open.