-
Notifications
You must be signed in to change notification settings - Fork 0
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
Bayesian model dimensionality #37
base: master
Are you sure you want to change the base?
Changes from all commits
6127eb5
d28419e
99b4f46
9a15e22
5bf5fb4
ca4d0d2
5a82db7
c091a36
d0e083b
362b06a
ba3abce
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 |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = "0.12.0" | ||
__version__ = "0.13.0" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ | |
from numpy.linalg import inv, solve | ||
from scipy.special import logsumexp | ||
|
||
from lsbi.stats import dkl, mixture_normal, multivariate_normal | ||
from lsbi.stats import bmd, dkl, mixture_normal, multivariate_normal | ||
from lsbi.utils import alias, dediagonalise, logdet | ||
|
||
|
||
|
@@ -261,17 +261,108 @@ def ppd(self, D0): | |
"""P(D|D0) as a distribution object.""" | ||
return self.update(D0).evidence() | ||
|
||
def dkl(self, D, n=0): | ||
def dkl(self, D, N=0): | ||
"""KL divergence between the posterior and prior. | ||
|
||
Analytically this is | ||
|
||
<log P(θ|D)/P(θ)>_P(θ|D) = 1/2 (log|1 + M Σ M'/ C| | ||
- tr M Σ M'/ (C+M Σ M') + (μ - μ_P)' Σ^-1 (μ - μ_P)) | ||
|
||
Parameters | ||
---------- | ||
D : array_like, shape (..., d) | ||
Data to form the posterior | ||
N : int, optional | ||
Number of samples for a monte carlo estimate, defaults to 0 | ||
""" | ||
return dkl(self.posterior(D), self.prior(), N) | ||
|
||
def bmd(self, D, N=0): | ||
"""Bayesian model dimensionality. | ||
|
||
Analytically this is | ||
|
||
var(log P(θ|D)/P(θ))_P(θ|D) = 1/2 tr(M Σ M'/ (C+M Σ M'))^2 | ||
+ (μ - μ_P)' Σ^-1 Σ_P Σ^-1(μ - μ_P) | ||
|
||
Parameters | ||
---------- | ||
D : array_like, shape (..., d) | ||
Data to form the posterior | ||
n : int, optional | ||
Number of samples for a monte carlo estimate, defaults to 0 | ||
""" | ||
return dkl(self.posterior(D), self.prior(), n) | ||
return bmd(self.posterior(D), self.prior(), N) | ||
|
||
def mutual_information(self, N=0, mcerror=False): | ||
"""Mutual information between the parameters and the data. | ||
|
||
Analytically this is | ||
|
||
<log P(D|θ)/P(D)>_P(D,θ) = log|1 + M Σ M'/ C|/2 | ||
|
||
Parameters | ||
---------- | ||
N : int, optional | ||
Number of samples for a monte carlo estimate, defaults to 0 | ||
mcerror: bool, optional | ||
Produce a monte carlo error estimate | ||
""" | ||
if N > 0: | ||
N = int(N**0.5) | ||
D = self.evidence().rvs(N) | ||
θ = self.posterior(D).rvs() | ||
logR = self.posterior(D).logpdf(θ, broadcast=True) - self.prior().logpdf( | ||
θ, broadcast=True | ||
) | ||
ans = logR.mean(axis=0) | ||
if mcerror: | ||
err = (logR.var(axis=0) / (N - 1)) ** 0.5 | ||
ans = (ans, err) | ||
return ans | ||
|
||
MΣM = np.einsum("...ja,...ab,...kb->...jk", self._M, self._Σ, self._M) | ||
C = self._C | ||
return np.broadcast_to(logdet(C + MΣM) / 2 - logdet(C) / 2, self.shape) | ||
|
||
def dimensionality(self, N=0, mcerror=False): | ||
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 may need a more informative -- and I would suggest even a more colloquial -- comment (or just a write up of this somewhere!) as it took me a minute to remind myself the bmd averaged over the evidence. Same goes for the mutual information. |
||
"""Model dimensionality. | ||
|
||
Analytically this is | ||
|
||
<bmd/2>_P(D) = tr(M Σ M'/ (C+M Σ M')) - 1/2 tr(M Σ M'/ (C+M Σ M'))^2 | ||
|
||
Parameters | ||
---------- | ||
N : int, optional | ||
Number of samples for a monte carlo estimate, defaults to 0 | ||
mcerror: bool, optional | ||
Produce a monte carlo error estimate | ||
""" | ||
if N > 0: | ||
N = int(N**0.5) | ||
D = self.evidence().rvs(N) | ||
θ = self.posterior(D).rvs(N) | ||
logR = self.posterior(D).logpdf(θ, broadcast=True) - self.prior().logpdf( | ||
θ, broadcast=True | ||
) | ||
ans = logR.var(axis=0).mean(axis=0) * 2 | ||
if mcerror: | ||
err = logR.var(axis=0) * (2 / (N - 1)) ** 0.5 * 2 | ||
err = ((err**2).sum(axis=0) / (N - 1)) ** 0.5 | ||
ans = (ans, err) | ||
return ans | ||
|
||
MΣM = np.einsum("...ja,...ab,...kb->...jk", self._M, self._Σ, self._M) | ||
C = self._C | ||
invCpMΣM = inv(C + MΣM) | ||
|
||
return np.broadcast_to( | ||
2 * np.einsum("...ij,...ji->...", MΣM, invCpMΣM) | ||
- np.einsum("...ij,...jk,...kl,...li->...", MΣM, invCpMΣM, MΣM, invCpMΣM), | ||
self.shape, | ||
) | ||
|
||
@property | ||
def _M(self): | ||
|
@@ -445,24 +536,70 @@ def update(self, D, inplace=False): | |
if not inplace: | ||
return dist | ||
|
||
def dkl(self, D, n=0): | ||
def dkl(self, D, N=0): | ||
"""KL divergence between the posterior and prior. | ||
|
||
Parameters | ||
---------- | ||
D : array_like, shape (..., d) | ||
Data to form the posterior | ||
n : int, optional | ||
N : int, optional | ||
Number of samples for a monte carlo estimate, defaults to 0 | ||
""" | ||
if n == 0: | ||
raise ValueError("MixtureModel requires a monte carlo estimate. Use n>0.") | ||
if N == 0: | ||
raise ValueError("MixtureModel requires a monte carlo estimate. Use N>0.") | ||
|
||
p = self.posterior(D) | ||
q = self.prior() | ||
x = p.rvs(size=(n, *self.shape[:-1]), broadcast=True) | ||
x = p.rvs(size=(N, *self.shape[:-1]), broadcast=True) | ||
return (p.logpdf(x, broadcast=True) - q.logpdf(x, broadcast=True)).mean(axis=0) | ||
|
||
def bmd(self, D, N=0): | ||
"""Bayesian model dimensionality. | ||
|
||
Parameters | ||
---------- | ||
D : array_like, shape (..., d) | ||
Data to form the posterior | ||
N : int, optional | ||
Number of samples for a monte carlo estimate, defaults to 0 | ||
""" | ||
if N == 0: | ||
raise ValueError("MixtureModel requires a monte carlo estimate. Use N>0.") | ||
|
||
p = self.posterior(D) | ||
q = self.prior() | ||
x = p.rvs(size=(N, *self.shape[:-1]), broadcast=True) | ||
return (p.logpdf(x, broadcast=True) - q.logpdf(x, broadcast=True)).var(axis=0) | ||
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. is an "mcerror" also possible on these numeric estimates? I guess some kind of resampling of some percentage of N can generate some kind of error but perhaps there is something more principled |
||
|
||
def mutual_information(self, N=0, mcerror=False): | ||
"""Mutual information between the parameters and the data. | ||
|
||
Parameters | ||
---------- | ||
N : int, optional | ||
Number of samples for a monte carlo estimate, defaults to 0 | ||
mcerror: bool, optional | ||
Produce a monte carlo error estimate | ||
""" | ||
if N == 0: | ||
raise ValueError("MixtureModel requires a monte carlo estimate. Use N>0.") | ||
return super().mutual_information(N, mcerror) | ||
|
||
def dimensionality(self, N=0, mcerror=False): | ||
"""Model dimensionality. | ||
|
||
Parameters | ||
---------- | ||
N : int, optional | ||
Number of samples for a monte carlo estimate, defaults to 0 | ||
mcerror: bool, optional | ||
Produce a monte carlo error estimate | ||
""" | ||
if N == 0: | ||
raise ValueError("MixtureModel requires a monte carlo estimate. Use N>0.") | ||
return super().dimensionality(N, mcerror) | ||
|
||
|
||
class ReducedLinearModel(object): | ||
"""A model with no data. | ||
|
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.
Should
mcerror
be accessible from the model?