You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hm, do we want logfuncdensity to act an "alias" (for two arguments) if MeasureTheory (resp. MeasureBase) already provides ∫exp?
If the second argument will be a Measure, then MeasureBase seems to be the natural place to define the function (after all, it already does). Why not have different names for the two functions? The the case with the measure is more specific (which we want, if course, if a Measure is available) so maybe it's good if it has a less generic name.
It would be great if the return value of MeasureTheory.∫exp would support DensityInterface.hasdensity and DensityInterface.logdensityof, though. I think that would already give us interoperability.
The issue is that logfuncdensity(log_f) doesn't make sense at all for MeasureTheory, because a function alone isn't enough to specify a measure. I guess we could just not use that function, but I think a desirable quality of an interface is for it to make sense across packages.
Yes, but shouldn't that function be called logfuncmeasure(log_f, measure) instead of logfuncdensity, since the return value is a measure, not a density (whereas logfuncdensity(log_f) will really return "just" a density)?
logdensityof is flexible on purpose, regarding it's input (can be a density or something that has a base measure), to help with generic programming. But for construction of densities and measure, shouldn't we be more specific?
In general logfuncdensity just returns a density that is defined by a log-function. Like a likelihood density, defined by a log-likelihood function.
Lets say we have a distribution d. logdensityof(d) will return a function that computes the log of the probability density. But given an arbitrary "black-box" function log_f that computes a the log of a probability density at given points, logdensityof can only create a density object (the probability density), not a distribution - not enough information.
Except if log_f was the result of calling logdensityof(d), which returns Base.Fix1(logdensityof, d), so the information is available.
I guess we should say logfuncdensity can return a measure if there's an implied base measure for log_f - but in most cases, it will just return a density.
we add something like this to MeasureBase (caveat - I didn't test this, and some of it may already be there):
const MeasureLike = Union{MeasureBase.Measure, Distributions.Distribution}
# ParameterFreeMeasure means a Measure subtype that's singleton type:abstract type ParameterFreeMeasure <:MeasureBase.Measureendstruct LebesgueMeasure <:MeasureBase.ParameterFreeMeasureendstruct CountingMeasure <:MeasureBase.ParameterFreeMeasureend
MeasureBase.logdensity(
μ::Distributions.ContinuousDistribution, ::LebesgueMeasure, x
) =logpdf(μ, x)
MeasureBase.logdensity(
μ::Distributions.DiscreteDistribution, ::CountingMeasure, x
) =logpdf(μ, x)
struct LogFuncRDIntegral{LogF,M<:MeasureLike} <:MeasureBase.Measure
log_f::LogF
μ::Mend# Do whatever MeasureBase/MeasureTheory currently does here:
MeasureBase.logdensity(μ::LogFuncRDIntegral, ν::Measure, x) =
MeasureBase.logdensity(MeasureBase.∫exp(μ.log_f, μ.μ), ν, x)
function logfuncmeasure end
MeasureBase.logfuncmeasure(log_f, μ::MeasureLike) =
MeasureBase.LogFuncRDIntegral(log_f, μ)
MeasureBase.logfuncmeasure(
log_f::Base.Fix1{
Union{typeof(logdensityof),typeof(logpdf)},
<:Distributions.ContinuousDistribution
},
::LebesgueMeasure
) = log_f.x
MeasureBase.logfuncmeasure(
log_f::Base.Fix1{
Union{typeof(logdensityof),typeof(logpdf)},
<:Distributions.DiscreteDistribution
},
::CountingMeasure
) = log_f.x
function basemeasure end
MeasureBase.basemeasure(m::MeasureBase.LogFuncRDIntegral(m)) = m.μ
MeasureBase.basemeasure(m::Distributions.ContinuousDistribution) =
MeasureBase.LebesgueMeasure()
MeasureBase.basemeasure(m::Distributions.DiscreteDistribution) =
MeasureBase.CountingMeasure()
struct RadonNikodymDerivative{Mu,Nu}
μ::Mu
ν::Nuend
DensityInterface.hasdensity(::RadonNikodymDerivative) =true# If the μ is a singleton, we can do logfuncmeasure based on type:
MeasureBase.logfuncmeasure(
log_f::Base.Fix1{typeof(logdensity),RadonNikodymDerivative{Mu,Nu}},
μ::Nu
) where {Mu,Nu<:ParameterFreeMeasure} = log_f.x
function rd_derivative end
MeasureBase.rd_derivative(ν::MeasureLike, μ::MeasureLike) =
MeasureBase.RadonNikodymDerivative(ν, μ)
# If the ν is a singleton, we can do rd_derivative based on type:
MeasureBase.rd_derivative(
μ::LogFuncRDIntegral{LogF,M}, ν::M
) where {LogF,M<:ParameterFreeMeasure} = DensityInterface.logfuncdensity(ν.log_f)
DensityInterface.logdensityof(m::LogFuncRDIntegral, x) =
m.log_f(x) + DensityInterface.logdensityof(m.μ, x)
DensityInterface.logdensityof(m::MeasureBase.RadonNikodymDerivative, x) =
MeasureBase.logdensity(m.μ, m.ν, x)
# Maybe rename `MeasureBase.logdensity` to `MeasureBase.eval_log_rd_derivative` or so# to make the function names less similar?
DensityInterface.logdensityof(m::MeasureBase.Measure) =
DensityInterface.logdensityof(
MeasureBase.rd_derivative(m, MeasureBase.basemeasure(m))
)
DensityInterface.logdensityof(m::MeasureBase.Measure, x) =
DensityInterface.logdensityof(m)(x)
DensityInterface.logdensityof(
m::RadonNikodymDerivative{<:Distributions.ContinuousDistribution,LebesgueMeasure},
x...
) =logdensityof(m.μ, x...)
DensityInterface.logdensityof(
m::RadonNikodymDerivative{<:Distributions.DiscreteDistribution,CountingMeasure},
x...
) =logdensityof(m.μ, x...)
(I'm prefixing functions and types with package name a lot here for clarity).
Then this should work:
using DensityInterface, MeasureBase
μ =LebesgueMeasure() # Or another ParameterFreeMeasure compatible with log_f
m =logfuncmeasure(log_f, μ)
logdensityof(rd_derivative(m, μ), x) ==log_f(x)
logdensityof(rd_derivative(m, μ)) === log_f
logdensityof(m, x) ==log_f(x)
logdensityof(m) === log_f
It would the measure-theoretically explicit analog of (this would also work)
d =logfuncdensity(log_f)
logdensityof(d, x) ==log_f(x)
logdensityof(d) === log_f
And this should also work:
d =SomeContinousDistribution()
log_f =logdensityof(rd_derivative(d, LebesgueMeasure())
logfuncmeasure(log_f, LebesgueMeasure()) === d
log_f = Base.Fix1(logpdf, d)
logfuncmeasure(log_f, LebesgueMeasure()) === d
It would be the measure-theoretically explicit analog of (this would also work)
log_f =logdensityof(d)
logfuncdensity(log_f) === d
log_f =logpdf(d)
logfuncdensity(log_f) === d
One could also think about making the measure theory API at least partially trait-based and replace MeasureLike by Any. Then I could define something like this in BAT:
MeasureBase.basemeasure(m::BAT.NonNormalizedPosterior) = m.prior
function MeasureBase.rd_derivative(μ::BAT.NonNormalizedPosterior{Li,Pr}, ν::Pr) where {Li,Pr}
ν == ν.prior ||throw(ArgumentError("ν must equal prior in rd_derivative of posterior"))
ν.likelihood
end
(Note: The type is actually called BAT.PosteriorDensity, I use BAT.NonNormalizedPosterior here for clarity - though maybe I should rename it to that at some point).
Maybe similar things would be possible in Turing/PPL?
Sorry, I know this is a lengthy post. On the plus side, I think the code above may be quite complete already.
Thanks for working through this @oschulz . I'll have a look and discuss with @mschauer, and get back to you soon.
Thanks!
Regarding
[logdensityof/logfuncdensity] I thought these were inverses, how can that be if they both return densities?
logdensityof returns a function that computes the log-value of something that is or has a density. So it does not return a "density object", it returns a log-function (and logdensityof(logdensityof(d)) would not make sense).
logfuncdensity, on the other hand, takes a log-function (assumed to be the log-function of a density) and returns a corresponding "density object" (which is usually not a function, and even if it is, that function should certainly not compute the log-density value).
Actually, to make my proposal above work nicely, we should say that logfuncdensity returns a density or something that has a density (e.g. a measure). The latter will only happen in special(ized) cases, though (e.g. when it's input log-density function originated from a distribution or measure and still carries that information in a dispatchable way).
A major aim of DensityInterface is to let people work with "density objects" that do not have to be named log_... in the code to make clear if it's (already) a log or not:
It's so awkward to write log_likelihood = some_func; prior = SomeDistribution(). I had that in examples during the early days of BAT.jl and several users asked "why one with log_ and not the other?". On the other hand, likelihood = some_func; prior = SomeDistribution() is misleading, dangerous and wrong, if some_func returns the log of the likelihood.
But likelihood = logfuncdensity(some_func); prior = SomeDistribution() is clear to the reader. And it's safe, the code using likelihood and prior (and the posterior, and so on) can use logdensityof (resp. densityof) and be sure to get the log (resp. not the log).
to my proposal above, to take advantage of the fact that things that implement the DensityInterface API (d in this case) don't need to have log in the name (on the LHS here).
To make things even nicer, we could replace the log_f-based struct LogFuncRDIntegral in my proposal with
In a few places, you define a logdensity in terms of the three-argument method. This was how we started out, but it doesn't work well because
It forces the base measure to always be explicit. Especially for new users, it's much simpler if the base measure can be a separate function associated with a measure
This requires three-argument methods for all pairs of measures, which is intractable.
The three-argument methods are then derived from this. If you take every possible measure and repeatedly apply basemeasure, you get a tree. The three-arugment logdensity is in terms of walking this tree. This approach allows us to limit ourselves to methods between "primitive" measures for definition of three-argument logdensity methods.
We can't just ignore the base measure here, that will lead to incorrect results. Maybe we can talk about the x... in a call, I don't understand what's intended here.
A ContinuousDistribution doesn't necessarily have Lebesgue as its base measure. For example, a Dirichlet in three dimensions is a two-dimensional manifold (with boundary), and is singular with respect to Lebesgue measure.
logdensityof returns a function that computes the log-value of something that is or has a density. So it does not return a "density object", it returns a log-function (and logdensityof(logdensityof(d)) would not make sense).
logfuncdensity, on the other hand, takes a log-function (assumed to be the log-function of a density) and returns a corresponding "density object" (which is usually not a function, and even if it is, that function should certainly not compute the log-density value).
Actually, to make my proposal above work nicely, we should say that logfuncdensity returns a density or something that has a density (e.g. a measure). The latter will only happen in special(ized) cases, though (e.g. when it's input log-density function originated from a distribution or measure and still carries that information in a dispatchable way).
We can't define a logfuncdensity like this without knowing a base measure to use for it. That's not just a MeasureTheory thing. In general, you either specify a base measure, or you sacrifice expressiveness (say, assuming everything is with respect to Lebesgue measure).
The function-like object passed to logfuncdensity might "know" the appropriate base measure. But it seems dangerous to not have that available as part of the interface.
A general observation... We've talked about the goal of expressing "function-like" objects that can be evaluated in either log space or linear space. But without a base measure, it's not really a density.
In MeasureTheory we have a Likelihood type that's a lot like this. It's not a measure, but it can "act on" one measure to yield a new one using pointwise multiplication, ⊙. So for a measure μ and Likelihood ℓ,
μ ⊙ ℓ
gives a new measure. If μ is considered as a prior, then μ ⊙ ℓ is a posterior. But everything still works if μ is Lebesgue or CountingMeasure. The effect is to take enrich ℓ, so you end up with a new measure.
Thanks @cscherrer , that cleared things up a bit more for me. I think we're on to something that could fit together very naturally here.
Instead we define logdensity(::SomeMeasure, x) basemeasure(::SomeMeasure)
Oh, I thought the three argument methods were the primary implementation for new densities. Nice, I think that will simplify integrating MeasureTheory with DensityInterface.
We do need a good name for the three-argument function though, IMHO. If something like rd_derivative and the like seems to technical, how about reldensity (for "relative density" resp. "related density") or so? Would that be a correct term?
Question: Does MeasureTheory allow for measures that don't define basemeasure? Or is that not allowed, do they all have to? The latter would make things easier here, I think.
The three-argument methods are then derived from this. [...]
So the three-argument logdensity function does not get specialized by new measure types, right?
A ContinuousDistribution doesn't necessarily have Lebesgue as its base measure. For example, a Dirichlet
we should say that logfuncdensity returns a density or something that has a density (e.g. a measure)
We can't define a logfuncdensity like this without knowing a base measure to use for it. That's not just a MeasureTheory thing.
You mean for the case where logfuncdensity returns a density, or for the case where it returns something that has a density (like for logfuncdensity(logdensityof(d::Distribution)))?
I assume it's just a problem in the latter case (returning something that has a density)? Since logfuncdensity returning something that is a density just means turning an existing log-density function into a log/non-log-independent "density object" without adding information or changing semantics?
The function-like object passed to logfuncdensity might "know" the appropriate base measure. But it seems dangerous to not have that available as part of the interface.
Yes, I was thinking about the same thing. For the case logfuncdensity(logdensityof(d::Distribution)) == d we could say that
logdensity_of_d = logdensityof(d::Distribution) "turns" d into a density via it's base measure (RD-derivative).
logfuncdensity(logdensity_of_d) "turns" logdensity_of_d into a measure via an inherent base measure, it remembers where it "came from" (RD-integral).
Would it be Ok to allow basemeasure to operate on both measures and (log-)density-functions? If so, I think that would solve this in a clean fashion. In this case, we'd have basemeasure(d) == basemeasure(logdensity_of_d).
Naturally, plain black-box log-density functions that don't support basemeasure would not get turned into measures by logfuncdensity, just into "density objects" (no change in what it is, just not fixed in term of log/non-log anymore).
This approach would also mean that for any object (e.g. a measure) m that supports logdensityof and basemeasure we could easily provide:
logfuncdensity(logdensityof(m)) == m
basemeasure(logdensityof(m)) ==basemeasure(m)
The first of these DensityInterface already provides, by default, via
If it's Ok to say that density functions (can) have a base measure, that would consitute a strong argument for adding basemeasure to DensityInterface instead of a secondary package. It we did, would we also want/need a function hasbasemeasure, to complement hasdensity?
A general observation... We've talked about the goal of expressing "function-like" objects that can be evaluated in either log space or linear space. But without a base measure, it's not really a density.
You mean because it's not clear what space it lives on?
Does that mean I can't do μ ⊙ ℓ if ℓ has no base measure? Or would the base measure of ℓ just be inferred from the base measure of μ?
The latter is actually what BAT.jl does right now: The user can pass the likelihood as a plain log-function (wrapped in logfuncdensity), and the space it lives on (e.g. degrees of freedom) are inferred from the prior, if not specified for the likelihood.
In MeasureTheory we have a Likelihood type that's a lot like this. It's not a measure, but it can "act on" one measure to yield a new one using pointwise multiplication, ⊙. So for a measure μ and Likelihood ℓ,
Is Likelihood required to provide base measure information?
If we go this road: I think the name of logdensityof fit it's semantics. But I do realize that logfuncdensity is a bit of an unfortunate name for something that may return something that is a measure. But maybe we can come up with a better name for "something-that-has-a-density-derived-from-this-log-density-function". It's not widely used in the ecosystem yet (just in BAT.jl so far, I think) and I'd be open to renaming it.
Just to make sure I'm understanding things correctly - which of these statements could you subscribe to, from a MeasureTheory point of view (without cringing too much)?
Every measure ν has exactly one (primary) base measure μ.
Every measure ν has exactly one "natural" density (function) f = dν/dμ, via ν's (primary) base measure μ.
Every (useful) density (function) f stems from a Radon–Nikodym derivative f = dν/dμ of two measures.
Every (useful) density (function) f can be said to have exactly one (primary) base measure. The base measure of f = dν/dμ is μ.
A density (function) can only be evaluated in the "context" of a measure. For a density function f = dν/dμ this can be μ or any of μ's recursive base measures.
Oh, a question in that context: What is the base measure of the Lebesgue measure? Or are there "root" measures that don't have a base measure?
I doubt Distributions will want to have a system for pushforwards.
If I understand the term correctly, that's what I want to provide with VariateTransformations (should be ready soon, the code comes from BAT.jl, just needs to be refactored a bit more). So it wouldn't be in Distributions, but an add-on package, but maybe that would still work in terms of integrating with MeasureBase?
but I think for MeasureTheory "density" should be literally a density. We can always loosen this later if it makes sense for us, but overloading the term too much now will make it very hard to roll back later, and there are the pedagogical concerns we've discussed.
Quite a few of the use cases that motivated DensityInterface deal with (more or less) "black-box" densities, i.e. they are written in arbitrary code or stem from a non-measure-theory DSL. In a measure-theoretical sense, one could probably gives labels to the measures they are the RD-derivative of - but those measures are not reified in code, and even their names are not available. There's just many use cases where users "just want to code a likelihood" without adding information about measures.
Such "black-box" densities should of course be considered densities in the sense of DensityInterface, with isdensity(d) == true && ismeasure(d) == false (with the proposal above).
I don't think this would be a problem for MeasureBase: MeasureBase would have a concrete type Density for it's densities (a more user-friendly name for RadonNikodymDerivative). So MeasureBase can always tests if it's a density it can work with - or maybe, some things MeasureTheory can do can also work with "black-box" densities, just not all of them? In any case, MeasureBase could define a function is_rd_derivative (or similar name), maybe even functions get_nu and get_mu or so.
@cscherrer woud you have a minimal example of what methods currently have to be implemented for a MeasureTheory measure? Maybe a multivaritate-normal probability measure?
I doubt Distributions will want to have a system for pushforwards.
If I understand the term correctly, that's what I want to provide with VariateTransformations (should be ready soon, the code comes from BAT.jl, just needs to be refactored a bit more). So it wouldn't be in Distributions, but an add-on package, but maybe that would still work in terms of integrating with MeasureBase?
Yes, but the concern here is that Distributions will need to be able to express each base measure, and in some cases this is singular with respect to Lebesgue measure.
Quite a few of the use cases that motivated DensityInterface deal with (more or less) "black-box" densities, i.e. they are written in arbitrary code or stem from a non-measure-theory DSL. In a measure-theoretical sense, one could probably gives labels to the measures they are the RD-derivative of - but those measures are not reified in code, and even their names are not available. There's just many use cases where users "just want to code a likelihood" without adding information about measures.
I think there are two things that can happen in these cases:
The density is implicitly with respect to Lebesgue measure
The density is implicitly with respect to a "Lebesgue-like" measure (probably Hausdorff measure) on its support
If everything in your world is case (1), you're safe. If everything is (2) and the base measure is fixed, you're probably also safe. But things can get really screwy when you try to mix (1) and (2), or different kinds of (2)s. From this perspective, being a little more rigorous about base measures gives you better composability.
@cscherrer woud you have a minimal example of what methods currently have to be implemented for a MeasureTheory measure? Maybe a multivaritate-normal probability measure?
This is a great idea, and something we should document anyway. I'll open a separate discussion for this.
The density is implicitly with respect to Lebesgue measure [...] The density is implicitly with respect to Lebesgue measure
Yes, that true for a not small number of practical use cases - everybody happily "lives on" a Lebesgue measure, implicitly.
This is also the case for BAT.jl at the moment - though I would very much like to support discrete parameters in the future. But since there will always be a prior, the necessary information is always there. Basically, by combining a likelihood (density) with a prior, the user will implicitly state what space the likelihood lives on. This approach has worked out very well in practice, so far (i.e. users are happy with it :-) ).
MeasureBase/MeasureTheory will of course want to be more strict about things - but I think that can happen quite naturally.
Distribution certainly has a corresponding notion encoded in the type parameters. We have to figure out what we need there: Translate Distribution's notion base measure one-to-one or let base measure return a more abstract summary notion of Volume, CountingMeasure which is shared more widely in the system (and we need to devise something the stranger ones, like Dirichlet). All that has influence in which package the objects returned by basemeasure have to live.
Does Distributions currently have a notion of a base measure?
Only implicitly, through VariateForm and ValueSupport. In some cases that's not enough, e.g. Dirichlet.
How lightweight could the definition of primitive measures like Lebesgue and CountingMeasure be? Which methods would have to be implemented for them?
If we assume Lebesgue is always on ℝⁿ, we could simplify our current implementation, and have
# Parameterized to allow e.g. Lebesgue(static(3))struct Lebesgue{N}
dimension: N
end# Some subtleties here, e.g. safety of checking dimensionality, vs speed and flexibility (symbolic arrays, etc)
DensityInterface.logdensityof(::Lebesgue, x) =0.0basemeasure(d::Lebesgue) = d
There might be a little more needed, but I think not much.
Then at least in MeasureBase, we need to have some code for relative densities between CountingMeasure and Lebesgue.
Also, @mschauer suggested it might be more palatable to have a wider audience name like VolumeMeasure instead of Lebesgue. Exactly how this should be set up is important, but I think we can defer this detail for now.
@mschauer , could you add a PR on Distributions.jl to complement #9 and JuliaMath/MeasureBase.jl#27 ? I'm not a Distributions maintainer and it would be easier to try things out via Pkg if all the PR's live on branches in the main repos of DensityInterface, Distributions and MeasureBase.
Content-wise, the Distributions PR can just remove @test logfuncdensity(logdensityof(d)) === d from the tests as a first step.
We probably shouldn't force Float64 though, it might propagate though a whole calculation that people may want to run on a (cheap) GPU without fast 64-bit floats. :-)
Lebesgue{N}
I don't think we should to that - there are many possible applications that might need to use Lebesgue for many (maybe millions) of different N, the compiler would explode. :-) It think it would have to be Lebesgue(N).
Let's continues this in #9 though, so we can represent the state of the discussion in evolving code?
Quick update, @cscherrer, @mschauer and me had a brainstorming session regarding what the base measure of ∫(f, μ) should be for DensityInterface and MeasureTheory. I'm pretty sure we'll want it to be the base measure of μ for DenstiyInterface, or we'll surprise users, but in MeasureTheory it's currently μ itself. Not clear yet if that should be the long-term choice for MeasureTheory or not (μ may or may not be the better choice). We can deal with both cases, we'll just have to clearly document each behavior (the "get-me-the-log-density" functions would have different names if they use different implied base measures). We'll report back.
Activity
oschulz commentedon Nov 4, 2021
Hm, do we want
logfuncdensityto act an "alias" (for two arguments) ifMeasureTheory(resp. MeasureBase) already provides∫exp?If the second argument will be a
Measure, thenMeasureBaseseems to be the natural place to define the function (after all, it already does). Why not have different names for the two functions? The the case with the measure is more specific (which we want, if course, if aMeasureis available) so maybe it's good if it has a less generic name.It would be great if the return value of
MeasureTheory.∫expwould supportDensityInterface.hasdensityandDensityInterface.logdensityof, though. I think that would already give us interoperability.cscherrer commentedon Nov 4, 2021
The issue is that
logfuncdensity(log_f)doesn't make sense at all for MeasureTheory, because a function alone isn't enough to specify a measure. I guess we could just not use that function, but I think a desirable quality of an interface is for it to make sense across packages.oschulz commentedon Nov 4, 2021
Yes, but shouldn't that function be called
logfuncmeasure(log_f, measure)instead oflogfuncdensity, since the return value is a measure, not a density (whereaslogfuncdensity(log_f)will really return "just" a density)?oschulz commentedon Nov 4, 2021
logdensityofis flexible on purpose, regarding it's input (can be a density or something that has a base measure), to help with generic programming. But for construction of densities and measure, shouldn't we be more specific?cscherrer commentedon Nov 4, 2021
I thought it went
logdensityof(d)returns a (a density) functionlogfuncdensity(f)takes a function, treats is as a density, and returns a measure (even if you don't call it that)If not, then what does
logfuncdensitydo?oschulz commentedon Nov 4, 2021
In general
logfuncdensityjust returns a density that is defined by a log-function. Like a likelihood density, defined by a log-likelihood function.Lets say we have a distribution
d.logdensityof(d)will return a function that computes the log of the probability density. But given an arbitrary "black-box" functionlog_fthat computes a the log of a probability density at given points,logdensityofcan only create a density object (the probability density), not a distribution - not enough information.Except if
log_fwas the result of callinglogdensityof(d), which returnsBase.Fix1(logdensityof, d), so the information is available.I guess we should say
logfuncdensitycan return a measure if there's an implied base measure forlog_f- but in most cases, it will just return a density.cscherrer commentedon Nov 4, 2021
I thought these were inverses, how can that be if they both return densities?
oschulz commentedon Nov 4, 2021
How about this - after we add this to Distributions (JuliaStats/Distributions.jl#1416):
we add something like this to MeasureBase (caveat - I didn't test this, and some of it may already be there):
(I'm prefixing functions and types with package name a lot here for clarity).
Then this should work:
It would the measure-theoretically explicit analog of (this would also work)
And this should also work:
It would be the measure-theoretically explicit analog of (this would also work)
One could also think about making the measure theory API at least partially trait-based and replace
MeasureLikebyAny. Then I could define something like this in BAT:(Note: The type is actually called
BAT.PosteriorDensity, I useBAT.NonNormalizedPosteriorhere for clarity - though maybe I should rename it to that at some point).Maybe similar things would be possible in Turing/PPL?
Sorry, I know this is a lengthy post. On the plus side, I think the code above may be quite complete already.
@cscherrer, what do you think of this?
@devmotion and @phipsgabler, how do you see this from a Distributions and Turing/PPL point of view?
cscherrer commentedon Nov 4, 2021
Thanks for working through this @oschulz . I'll have a look and discuss with @mschauer, and get back to you soon.
oschulz commentedon Nov 4, 2021
Thanks!
Regarding
logdensityofreturns a function that computes the log-value of something that is or has a density. So it does not return a "density object", it returns a log-function (andlogdensityof(logdensityof(d))would not make sense).logfuncdensity, on the other hand, takes a log-function (assumed to be the log-function of a density) and returns a corresponding "density object" (which is usually not a function, and even if it is, that function should certainly not compute the log-density value).Actually, to make my proposal above work nicely, we should say that
logfuncdensityreturns a density or something that has a density (e.g. a measure). The latter will only happen in special(ized) cases, though (e.g. when it's input log-density function originated from a distribution or measure and still carries that information in a dispatchable way).A major aim of
DensityInterfaceis to let people work with "density objects" that do not have to be namedlog_...in the code to make clear if it's (already) a log or not:It's so awkward to write
log_likelihood = some_func; prior = SomeDistribution(). I had that in examples during the early days of BAT.jl and several users asked "why one with log_ and not the other?". On the other hand,likelihood = some_func; prior = SomeDistribution()is misleading, dangerous and wrong, ifsome_funcreturns the log of the likelihood.But
likelihood = logfuncdensity(some_func); prior = SomeDistribution()is clear to the reader. And it's safe, the code usinglikelihoodandprior(and the posterior, and so on) can uselogdensityof(resp.densityof) and be sure to get the log (resp. not the log).oschulz commentedon Nov 4, 2021
Maybe we actually should add something like
to my proposal above, to take advantage of the fact that things that implement the
DensityInterfaceAPI (din this case) don't need to havelogin the name (on the LHS here).To make things even nicer, we could replace the
log_f-based structLogFuncRDIntegralin my proposal withrequiring
dto supportlogdensityof(can e.g. be the result oflogfuncdensity(log_f)).RadonNikodymIntegralwould form an elegant symmetry withRadonNikodymDerivative, name-wise.[-]Optional "base" argument for logfuncdensity[/-][+]Interoperability with MeasureTheory[/+]oschulz commentedon Nov 5, 2021
I updated the proposal above a bit, renaming
LogFuncMeasuretoLogFuncRDIntegraland addingWould indeed by nicer with a struct
RadonNikodymIntegral, I think, then it could becscherrer commentedon Nov 8, 2021
Hi @oschulz , just going through this...
In a few places, you define a
logdensityin terms of the three-argument method. This was how we started out, but it doesn't work well becauseInstead we define
The three-argument methods are then derived from this. If you take every possible measure and repeatedly apply
basemeasure, you get a tree. The three-arugmentlogdensityis in terms of walking this tree. This approach allows us to limit ourselves to methods between "primitive" measures for definition of three-argumentlogdensitymethods.We can't just ignore the base measure here, that will lead to incorrect results. Maybe we can talk about the
x...in a call, I don't understand what's intended here.A
ContinuousDistributiondoesn't necessarily haveLebesgueas its base measure. For example, aDirichletin three dimensions is a two-dimensional manifold (with boundary), and is singular with respect to Lebesgue measure.We can't define a
logfuncdensitylike this without knowing a base measure to use for it. That's not just a MeasureTheory thing. In general, you either specify a base measure, or you sacrifice expressiveness (say, assuming everything is with respect to Lebesgue measure).The function-like object passed to
logfuncdensitymight "know" the appropriate base measure. But it seems dangerous to not have that available as part of the interface.A general observation... We've talked about the goal of expressing "function-like" objects that can be evaluated in either log space or linear space. But without a base measure, it's not really a density.
In MeasureTheory we have a
Likelihoodtype that's a lot like this. It's not a measure, but it can "act on" one measure to yield a new one using pointwise multiplication,⊙. So for a measureμand Likelihoodℓ,μ ⊙ ℓgives a new measure. If
μis considered as a prior, thenμ ⊙ ℓis a posterior. But everything still works ifμisLebesgueorCountingMeasure. The effect is to take enrichℓ, so you end up with a new measure.oschulz commentedon Nov 9, 2021
Thanks @cscherrer , that cleared things up a bit more for me. I think we're on to something that could fit together very naturally here.
Oh, I thought the three argument methods were the primary implementation for new densities. Nice, I think that will simplify integrating MeasureTheory with DensityInterface.
We do need a good name for the three-argument function though, IMHO. If something like
rd_derivativeand the like seems to technical, how aboutreldensity(for "relative density" resp. "related density") or so? Would that be a correct term?Question: Does MeasureTheory allow for measures that don't define
basemeasure? Or is that not allowed, do they all have to? The latter would make things easier here, I think.So the three-argument logdensity function does not get specialized by new measure types, right?
You mean for the case where
logfuncdensityreturns a density, or for the case where it returns something that has a density (like forlogfuncdensity(logdensityof(d::Distribution)))?I assume it's just a problem in the latter case (returning something that has a density)? Since
logfuncdensityreturning something that is a density just means turning an existing log-density function into a log/non-log-independent "density object" without adding information or changing semantics?Yes, I was thinking about the same thing. For the case
logfuncdensity(logdensityof(d::Distribution)) == dwe could say thatlogdensity_of_d = logdensityof(d::Distribution)"turns"dinto a density via it's base measure (RD-derivative).logfuncdensity(logdensity_of_d)"turns"logdensity_of_dinto a measure via an inherent base measure, it remembers where it "came from" (RD-integral).Would it be Ok to allow
basemeasureto operate on both measures and (log-)density-functions? If so, I think that would solve this in a clean fashion. In this case, we'd havebasemeasure(d) == basemeasure(logdensity_of_d).Naturally, plain black-box log-density functions that don't support
basemeasurewould not get turned into measures bylogfuncdensity, just into "density objects" (no change in what it is, just not fixed in term of log/non-log anymore).This approach would also mean that for any object (e.g. a measure)
mthat supportslogdensityofandbasemeasurewe could easily provide:The first of these DensityInterface already provides, by default, via
The second one we can provide just as easily via
If it's Ok to say that density functions (can) have a base measure, that would consitute a strong argument for adding
basemeasureto DensityInterface instead of a secondary package. It we did, would we also want/need a functionhasbasemeasure, to complementhasdensity?You mean because it's not clear what space it lives on?
Does that mean I can't do μ ⊙ ℓ if ℓ has no base measure? Or would the base measure of ℓ just be inferred from the base measure of μ?
The latter is actually what BAT.jl does right now: The user can pass the likelihood as a plain log-function (wrapped in
logfuncdensity), and the space it lives on (e.g. degrees of freedom) are inferred from the prior, if not specified for the likelihood.Is
Likelihoodrequired to provide base measure information?If we go this road: I think the name of
logdensityoffit it's semantics. But I do realize thatlogfuncdensityis a bit of an unfortunate name for something that may return something that is a measure. But maybe we can come up with a better name for "something-that-has-a-density-derived-from-this-log-density-function". It's not widely used in the ecosystem yet (just in BAT.jl so far, I think) and I'd be open to renaming it.Just to make sure I'm understanding things correctly - which of these statements could you subscribe to, from a
MeasureTheorypoint of view (without cringing too much)?Oh, a question in that context: What is the base measure of the Lebesgue measure? Or are there "root" measures that don't have a base measure?
8 remaining items
cscherrer commentedon Nov 10, 2021
Sounds good. Ours is here
JuliaMath/MeasureBase.jl#27
oschulz commentedon Nov 10, 2021
If I understand the term correctly, that's what I want to provide with VariateTransformations (should be ready soon, the code comes from BAT.jl, just needs to be refactored a bit more). So it wouldn't be in Distributions, but an add-on package, but maybe that would still work in terms of integrating with MeasureBase?
oschulz commentedon Nov 10, 2021
Nice, I'll get the DensityInterface and Distributions one set up, then. Will try to get this done tonight (have some calls as well).
oschulz commentedon Nov 10, 2021
Quite a few of the use cases that motivated DensityInterface deal with (more or less) "black-box" densities, i.e. they are written in arbitrary code or stem from a non-measure-theory DSL. In a measure-theoretical sense, one could probably gives labels to the measures they are the RD-derivative of - but those measures are not reified in code, and even their names are not available. There's just many use cases where users "just want to code a likelihood" without adding information about measures.
Such "black-box" densities should of course be considered densities in the sense of DensityInterface, with
isdensity(d) == true && ismeasure(d) == false(with the proposal above).I don't think this would be a problem for MeasureBase: MeasureBase would have a concrete type
Densityfor it's densities (a more user-friendly name forRadonNikodymDerivative). SoMeasureBasecan always tests if it's a density it can work with - or maybe, some thingsMeasureTheorycan do can also work with "black-box" densities, just not all of them? In any case,MeasureBasecould define a functionis_rd_derivative(or similar name), maybe even functionsget_nuandget_muor so.oschulz commentedon Nov 10, 2021
@cscherrer woud you have a minimal example of what methods currently have to be implemented for a MeasureTheory measure? Maybe a multivaritate-normal probability measure?
cscherrer commentedon Nov 10, 2021
Yes, but the concern here is that Distributions will need to be able to express each base measure, and in some cases this is singular with respect to Lebesgue measure.
I think there are two things that can happen in these cases:
If everything in your world is case (1), you're safe. If everything is (2) and the base measure is fixed, you're probably also safe. But things can get really screwy when you try to mix (1) and (2), or different kinds of (2)s. From this perspective, being a little more rigorous about base measures gives you better composability.
This is a great idea, and something we should document anyway. I'll open a separate discussion for this.
oschulz commentedon Nov 10, 2021
I'm not a Distributions maintainer, but I would definitely support a proposal that
Distributions should define their base measure.oschulz commentedon Nov 10, 2021
Yes, that true for a not small number of practical use cases - everybody happily "lives on" a Lebesgue measure, implicitly.
This is also the case for BAT.jl at the moment - though I would very much like to support discrete parameters in the future. But since there will always be a prior, the necessary information is always there. Basically, by combining a likelihood (density) with a prior, the user will implicitly state what space the likelihood lives on. This approach has worked out very well in practice, so far (i.e. users are happy with it :-) ).
MeasureBase/MeasureTheory will of course want to be more strict about things - but I think that can happen quite naturally.
mschauer commentedon Nov 10, 2021
Distribution certainly has a corresponding notion encoded in the type parameters. We have to figure out what we need there: Translate Distribution's notion
base measureone-to-one or letbase measurereturn a more abstract summary notion ofVolume,CountingMeasurewhich is shared more widely in the system (and we need to devise something the stranger ones, like Dirichlet). All that has influence in which package the objects returned bybasemeasurehave to live.oschulz commentedon Nov 10, 2021
Does Distributions currently have a notion of a base measure?
oschulz commentedon Nov 10, 2021
How lightweight could the definition of primitive measures like
LebesgueandCountingMeasurebe? Which methods would have to be implemented for them?cscherrer commentedon Nov 10, 2021
Only implicitly, through
VariateFormandValueSupport. In some cases that's not enough, e.g. Dirichlet.If we assume
Lebesgueis always onℝⁿ, we could simplify our current implementation, and haveThere might be a little more needed, but I think not much.
Then at least in MeasureBase, we need to have some code for relative densities between
CountingMeasureandLebesgue.Also, @mschauer suggested it might be more palatable to have a wider audience name like
VolumeMeasureinstead ofLebesgue. Exactly how this should be set up is important, but I think we can defer this detail for now.oschulz commentedon Nov 10, 2021
Ok, here goes: #9
@mschauer , could you add a PR on Distributions.jl to complement #9 and JuliaMath/MeasureBase.jl#27 ? I'm not a Distributions maintainer and it would be easier to try things out via Pkg if all the PR's live on branches in the main repos of DensityInterface, Distributions and MeasureBase.
Content-wise, the Distributions PR can just remove
@test logfuncdensity(logdensityof(d)) === dfrom the tests as a first step.oschulz commentedon Nov 10, 2021
We probably shouldn't force
Float64though, it might propagate though a whole calculation that people may want to run on a (cheap) GPU without fast 64-bit floats. :-)I don't think we should to that - there are many possible applications that might need to use
Lebesguefor many (maybe millions) of different N, the compiler would explode. :-) It think it would have to beLebesgue(N).Let's continues this in #9 though, so we can represent the state of the discussion in evolving code?
oschulz commentedon Nov 11, 2021
Quick update, @cscherrer, @mschauer and me had a brainstorming session regarding what the base measure of
∫(f, μ)should be for DensityInterface and MeasureTheory. I'm pretty sure we'll want it to be the base measure ofμfor DenstiyInterface, or we'll surprise users, but in MeasureTheory it's currentlyμitself. Not clear yet if that should be the long-term choice for MeasureTheory or not (μmay or may not be the better choice). We can deal with both cases, we'll just have to clearly document each behavior (the "get-me-the-log-density" functions would have different names if they use different implied base measures). We'll report back.