-
Notifications
You must be signed in to change notification settings - Fork 3
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
Interoperability with MeasureTheory #5
Comments
Hm, do we want If the second argument will be a It would be great if the return value of |
The issue is that |
Yes, but shouldn't that function be called |
|
I thought it went
If not, then what does |
In general Lets say we have a distribution Except if I guess we should say |
I thought these were inverses, how can that be if they both return densities? |
How about this - after we add this to Distributions (JuliaStats/Distributions.jl#1416): @inline DensityInterface.hasdensity(::Distribution) = true
DensityInterface.logdensityof(d::Distribution, x) = logpdf(d, x)
DensityInterface.densityof(d::Distribution, x) = pdf(d, x) 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.Measure end
struct LebesgueMeasure <: MeasureBase.ParameterFreeMeasure end
struct CountingMeasure <: MeasureBase.ParameterFreeMeasure end
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
μ::M
end
# 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
ν::Nu
end
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 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 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? |
Thanks! Regarding
Actually, to make my proposal above work nicely, we should say that A major aim of It's so awkward to write But |
Maybe we actually should add something like radon_nikodym_integral(d, μ) = logfuncmeasure(logdensityof(d), μ) to my proposal above, to take advantage of the fact that things that implement the To make things even nicer, we could replace the struct RadonNikodymIntegral{F,M<:MeasureLike} <: MeasureBase.Measure
d::D
μ::M
end requiring
|
I updated the proposal above a bit, renaming DensityInterface.logdensityof(m::LogFuncRDIntegral, x) =
m.log_f(x) + DensityInterface.logdensityof(m.μ, x) Would indeed by nicer with a struct DensityInterface.logdensityof(m::RadonNikodymIntegral, x) =
DensityInterface.logdensityof(m.d, x) + DensityInterface.logdensityof(m.μ, x) |
Hi @oschulz , just going through this... In a few places, you define a
Instead we define logdensity(::SomeMeasure, x)
basemeasure(::SomeMeasure) The three-argument methods are then derived from this. If you take every possible measure and repeatedly apply DensityInterface.logdensityof(
m::RadonNikodymDerivative{<:Distributions.ContinuousDistribution,LebesgueMeasure},
x...
) = logdensityof(m.μ, x...) We can't just ignore the base measure here, that will lead to incorrect results. Maybe we can talk about the A
We can't define a The function-like object passed to 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 μ ⊙ ℓ gives a new measure. If |
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 Question: Does MeasureTheory allow for measures that don't define
So the three-argument logdensity function does not get specialized by new measure types, right?
You mean for the case where I assume it's just a problem in the latter case (returning something that has a density)? Since
Yes, I was thinking about the same thing. For the case
Would it be Ok to allow Naturally, plain black-box log-density functions that don't support This approach would also mean that for any object (e.g. a measure) logfuncdensity(logdensityof(m)) == m
basemeasure(logdensityof(m)) == basemeasure(m) The first of these DensityInterface already provides, by default, via logfuncdensity(log_f::Base.Fix1{typeof(logdensityof)}) = log_f.x The second one we can provide just as easily via basemeasure(log_f::Base.Fix1{typeof(logdensityof)}) = basemeasure(log_f.x) If it's Ok to say that density functions (can) have a base measure, that would consitute a strong argument for adding
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
Is If we go this road: I think the name of Just to make sure I'm understanding things correctly - which of these statements could you subscribe to, from a
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? |
Hi @oschulz , I've added some more discussion in The perspective of most of the last week has been "Here's this interface, now what about MeasureTheory?". Now that things have slowed down some, I can think through what this actually looks like from the opposite perspective ("Here's MeasureTheory, now how about this interface?"). So far what I'm seeing in thinking through this is computational convenience, but some potentially serious pedagogical concerns. These are described in that issue.
This is
No, there's always a base measure. In some cases (Lebesgue, CountingMeasure) the base measure might be that measure itself.
It can, but typically no. Most often, this is only defined between measures that are their own base measure.
Having this function return two different things that are so conceptually different will cause us a lot of problems.
Maybe? It would have to always return a measure, and there would have to be clear semantics. If (log-)density functions hold two measures, this could be pretty clear.
The space, and the measure on that space.
No, as you pointed out this is the R-D integral. So we could also write it
Abstractly, a likelihood is just a function of parameters. The This is getting long, so I'll continue separately... |
Whether or how well it fits depends on the context. For MeasureTheory, we hope for users of MeasureTheory to gradually "peel the onion" just as they do for Julia itself. There's potentially a lot of danger in beginners/students starting with the "everything is a density" perspective, and later discovering that things are really completely different.
This is out
Abstractly, no. But in terms of our implementation, yes.
Same as above :)
"Stems from" is too weak, "density" and "Radon-Nikodym derivative" are exactly the same thing.
Abstractly, a density function is just a function. In terms of out implementation, the Density struct does have a
No, once you have a density it's just a function.
basemeasure(μ::Lebesgue) = μ We'd call rootmeasure(μ) = fix(basemeasure, μ) |
|
Nice, that makes things easier, I feel.
Again, that should help us here a lot.
Ok, so maybe We could add a new function that takes over the current behavior of
Or maybe a different name, like
Ok, so at least within the context of MeasureTheory, it's justified treat a measure as a density (i.e. transparently replace it with it's density) in certain situations for generic programming - though not in general. That's compatible with the current behavior of
Ok, that's in the context of MeasureTheory, while DensityInterface also allows densities to be defined via a black-box [log]-function. But I don't think that's a problem, MeasureTheory doesn't have to support that just because DensityInterface allows it.
Ok, so the current behavior of
Ahh, nice. I had a suspicion that might be the case. So we do have to defend against infinite recursion, but I assume MeasureTheory has the right tools for that in place, internally. |
How would this work out for MeasureBase/MeasureTheory: DensityInterfaceThe motto of DensityInterface will not be "everything is a density" but "everything (compatible with it) has a density". This should be perfectly compatible with MeasureTheory: MeasureTheory already has a "default" density for every measure, via the measure's base measure. DensityInterface adds (non-breaking change) @inferred ismeasure(object) = false
@inferred hasdensity(object) = ismeasure(object)
@inferred isdensity(object) = hasdensity(object) && !ismeasure(object) The owner of DensityInterface also adds function basemeasure(object) end which is intended for measures (
isdensity(logfuncdensity(f)) == true
isdensity(funcdensity(f)) == true This mildly breaking, the current default behavior is DistributionsDistributions replaces the current @inferred ismeasure(::Distribution) = true Distributions removes Distributions defines basemeasure(::Distribution{Univariate,Continuous}) = Lebesgue()
basemeasure(::Distribution{Univariate,Discrete}) = CountingMeasure()
basemeasure(::Product) = ...
...
basemeasure(::MvNormal) = Lebesgue()
basemeasure(::Multinomial) = CountingMeasure()
basemeasure(::Dirichlet) = ...
...
MeasureBaseMeasureBase defines struct Density{M,B}
μ::M
base::B
end
function DensityMeasure(d, base)
ismeasure(μ) && ismeasure(base) || throw(ArgumentError("..."))
DensityMeasure{typeof(μ),typeof(base)}(μ, base)
end
@inline DensityInterface.hasdensity(::Density) = true
@inline DensityInterface.ismeasure(::Density) = false # Default anyway
# Can MeasureBase implement this on it's own, without MeasureTheory?:
logdensityof(d::Density, x) = ...
densityof(d::Density, x) = ... and struct DensityMeasure{D,B}
d::D
base::B
end
function DensityMeasure(d, base)
isdensity(d) && ismeasure(base) || throw(ArgumentError("..."))
DensityMeasure{typeof(d),typeof(base)}(d, base)
end
@inline DensityInterface.hasdensity(::DensityMeasure) = true
@inline DensityInterface.ismeasure(::DensityMeasure) = true
@inline DensityInterface.basemeasure(m::DensityMeasure) = m.base
DensityInterface.logdensityof(m::DensityMeasure, x) = logdensityof(m.d, x)
DensityInterface.logdensityof(m::DensityMeasure) = logdensityof(m.d)
DensityInterface.densityof(m::DensityMeasure, x) = densityof(m.d, x)
DensityInterface.densityof(m::DensityMeasure) = densityof(m.d) Ideally, there will be no |
This in itself is really encouraging. I've been kind of worried that having things not match up quite right might lead to misbehavior downstream.
I want to check that @mschauer agrees before committing to this, 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.
This would be great, I'd love to pin down the expected semantics more carefully. |
👍
I like this, the biggest concern is that I need to get ready for some morning calls, more later... |
Ok, how about this: I create exploratory PRs on DensityInterface and Distributions with my proposal above, as a "scratchpad". Then we do at least have code in branches that are available to Pkg and that can be played with directly. Maybe you and @mschauer can do the same for |
Sounds good. Ours is here |
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? |
Nice, I'll get the DensityInterface and Distributions one set up, then. Will try to get this done tonight (have some calls as well). |
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 I don't think this would be a problem for MeasureBase: MeasureBase would have a concrete type |
@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? |
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. |
I'm not a Distributions maintainer, but I would definitely support a proposal that |
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 |
Does Distributions currently have a notion of a base measure? |
How lightweight could the definition of primitive measures like |
Only implicitly, through
If we assume # 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.0
basemeasure(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 Also, @mschauer suggested it might be more palatable to have a wider audience name like |
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 |
We probably shouldn't force
I don't think we should to that - there are many possible applications that might need to use 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 |
For use with MeasureTheory, it would be helpful if we could write
logfuncdensity(log_f, base)
, with expected behaviorThis would allow us to write
I know this package doesn't want base measures to be a requirement, so maybe we could find a way to make
base
optional.∫exp
is also defined for Distributions; it's not clear to me yet how to extend the interface to cover that case.The text was updated successfully, but these errors were encountered: