Skip to content

Commit 35e34b0

Browse files
committed
Introduce mintegrate and mintegrate_exp
Removes the integral operators from MeasureBase, to be re-introduced in the submodule MeasureOperators. Also improves the likelihood documentation.
1 parent d26c04e commit 35e34b0

File tree

3 files changed

+149
-89
lines changed

3 files changed

+149
-89
lines changed

src/combinators/likelihood.jl

Lines changed: 82 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ abstract type AbstractLikelihood end
1111
# insupport(ℓ::AbstractLikelihood, p) = insupport(ℓ.k(p), ℓ.x)
1212

1313
@doc raw"""
14-
Likelihood(k::AbstractTransitionKernel, x)
14+
Likelihood(k, x)
1515
16-
"Observe" a value `x`, yielding a function from the parameters to ℝ.
16+
Default result of [`likelihoodof(k, x)`](@ref).
1717
1818
Likelihoods are most commonly used in conjunction with an existing _prior_
1919
measure to yield a new measure, the _posterior_. In Bayes's Law, we have
@@ -91,34 +91,35 @@ Similarly to the above, we have
9191
9292
Finally, let's return to the expression for Bayes's Law,
9393
94-
``P(θ|x) ∝ P(θ) P(x|θ)``
94+
``P(θ|x) ∝ P(x|θ) P(θ)``
9595
96-
The product on the right side is computed pointwise. To work with this in
97-
MeasureBase, we have a "pointwise product" `⊙`, which takes a measure and a
98-
likelihood, and returns a new measure, that is, the unnormalized posterior that
99-
has density ``P(θ) P(x|θ)`` with respect to the base measure of the prior.
96+
In measure theory, the product on the right side is actually the Lebesgue integral,
97+
of the likelihood with respect to the prior.
10098
10199
For example, say we have
102100
103101
μ ~ Normal()
104102
x ~ Normal(μ,σ)
105103
σ = 1
106104
107-
and we observe `x=3`. We can compute the posterior measure on `μ` as
108-
109-
julia> post = Normal() ⊙ Likelihood(Normal{(:μ, :σ)}, (σ=1,), 3)
110-
Normal() ⊙ Likelihood(Normal{(:μ, :σ), T} where T, (σ = 1,), 3)
105+
and we observe `x=3`. We can compute the (non-normalized) posterior measure on
106+
`μ` as
111107
112-
julia> logdensity_def(post, 2)
113-
-2.5
108+
julia> prior = Normal()
109+
julia> likelihood = Likelihood(μ -> Normal(μ, 1), 3)
110+
julia> post = mintegrate(likelihood, prior)
111+
julia> post isa MeasureBase.DensityMeasure
112+
true
113+
julia> logdensity_rel(post, Lebesgue(), 2)
114+
-4.337877066409345
114115
"""
115116
struct Likelihood{K,X} <: AbstractLikelihood
116117
k::K
117118
x::X
118119

119-
Likelihood(k::K, x::X) where {K<:AbstractTransitionKernel,X} = new{K,X}(k, x)
120-
Likelihood(k::K, x::X) where {K<:Function,X} = new{K,X}(k, x)
121-
Likelihood(μ, x) = Likelihood(kernel(μ), x)
120+
Likelihood(k::K, x::X) where {K,X} = new{K,X}(k, x)
121+
#!!!!!!!!!!! # For type stability if `K isa UnionAll (e.g. a parameterized MeasureType)`
122+
Likelihood(::Type{K}, x::X) where {K<:AbstractMeasure,X} = new{K,X}(K, x)
122123
end
123124

124125
(lik::AbstractLikelihood)(p) = exp(ULogarithmic, logdensityof(lik.k(p), lik.x))
@@ -150,58 +151,87 @@ end
150151

151152
export likelihoodof
152153

153-
"""
154-
likelihoodof(k::AbstractTransitionKernel, x; constraints...)
155-
likelihoodof(k::AbstractTransitionKernel, x, constraints::NamedTuple)
154+
@doc raw"""
155+
likelihoodof(k, x)
156156
157-
A likelihood is *not* a measure. Rather, a likelihood acts on a measure, through
158-
the "pointwise product" `⊙`, yielding another measure.
159-
"""
160-
function likelihoodof end
157+
Returns the likelihood of observing `x` under a family of probability
158+
measures that is generated by a transition kernel `k(θ)`.
159+
160+
`k(θ)` maps points in the parameter space to measures (resp. objects that can
161+
be converted to measures) on a implicit set `Χ` that contains values like `x`.
162+
163+
`likelihoodof(k, x)` returns a likelihood object. A likelihhood is **not** a
164+
measure, it is a function from the parameter space to `ℝ₊`. Likelihood
165+
objects can also be interpreted as "generic densities" (but **not** as
166+
probability densities).
161167
162-
likelihoodof(k, x, ::NamedTuple{()}) = Likelihood(k, x)
168+
`likelihoodof(k, x)` implicitly chooses `ξ = rootmeasure(k(θ))` as the
169+
reference measure on the observation set `Χ`. Note that this implicit
170+
`ξ` **must** be independent of `θ`.
163171
164-
likelihoodof(k, x; kwargs...) = likelihoodof(k, x, NamedTuple(kwargs))
172+
`ℒₓ = likelihoodof(k, x)` has the mathematical interpretation
165173
166-
likelihoodof(k, x, pars::NamedTuple) = likelihoodof(kernel(k, pars), x)
174+
```math
175+
\mathcal{L}_x(\theta) = \frac{\rm{d}\, k(\theta)}{\rm{d}\, \chi}(x)
176+
```
167177
168-
likelihoodof(k::AbstractTransitionKernel, x) = Likelihood(k, x)
178+
`likelihoodof` must return an object that implements the
179+
[`DensityInterface`](https://github.com/JuliaMath/DensityInterface.jl)` API
180+
and `ℒₓ = likelihoodof(k, x)` must satisfy
169181
170-
export log_likelihood_ratio
182+
```julia
183+
log(ℒₓ(θ)) == logdensityof(ℒₓ, θ) ≈ logdensityof(k(θ), x)
171184
185+
DensityKind(ℒₓ) isa IsDensity
186+
```
187+
188+
By default, an instance of [`MeasureBase.Likelihood`](@ref) is returned.
172189
"""
173-
log_likelihood_ratio(ℓ::Likelihood, p, q)
190+
function likelihoodof end
174191

175-
Compute the log of the likelihood ratio, in order to compare two choices for
176-
parameters. This is computed as
192+
likelihoodof(k, x) = Likelihood(k, x)
177193

178-
logdensity_rel(ℓ.k(p), ℓ.k(q), ℓ.x)
179194

180-
Since `logdensity_rel` can leave common base measure unevaluated, this can be
181-
more efficient than
195+
###############################################################################
196+
# At the least, we need to think through in some more detail whether
197+
# (log-)likelihood ratios expressed in this way are correct and useful. For now
198+
# this code is commented out; we may remove it entirely in the future.
182199

183-
logdensityof(ℓ.k(p), ℓ.x) - logdensityof(ℓ.k(q), ℓ.x)
184-
"""
185-
log_likelihood_ratio(ℓ::Likelihood, p, q) = logdensity_rel(ℓ.k(p), ℓ.k(q), ℓ.x)
200+
# export log_likelihood_ratio
186201

187-
# likelihoodof(k, x; kwargs...) = likelihoodof(k, x, NamedTuple(kwargs))
202+
# """
203+
# log_likelihood_ratio(ℓ::Likelihood, p, q)
188204

189-
export likelihood_ratio
205+
# Compute the log of the likelihood ratio, in order to compare two choices for
206+
# parameters. This is computed as
190207

191-
"""
192-
likelihood_ratio(ℓ::Likelihood, p, q)
208+
# logdensity_rel(ℓ.k(p), ℓ.k(q), ℓ.x)
193209

194-
Compute the log of the likelihood ratio, in order to compare two choices for
195-
parameters. This is equal to
210+
# Since `logdensity_rel` can leave common base measure unevaluated, this can be
211+
# more efficient than
196212

197-
density_rel(ℓ.k(p), ℓ.k(q), ℓ.x)
213+
# logdensityof(ℓ.k(p), ℓ.x) - logdensityof(ℓ.k(q), ℓ.x)
214+
# """
215+
# log_likelihood_ratio(ℓ::Likelihood, p, q) = logdensity_rel(ℓ.k(p), ℓ.k(q), ℓ.x)
198216

199-
but is computed using LogarithmicNumbers.jl to avoid underflow and overflow.
200-
Since `density_rel` can leave common base measure unevaluated, this can be
201-
more efficient than
217+
# # likelihoodof(k, x; kwargs...) = likelihoodof(k, x, NamedTuple(kwargs))
202218

203-
logdensityof(ℓ.k(p), ℓ.x) - logdensityof(ℓ.k(q), ℓ.x)
204-
"""
205-
function likelihood_ratio(ℓ::Likelihood, p, q)
206-
exp(ULogarithmic, logdensity_rel(ℓ.k(p), ℓ.k(q), ℓ.x))
207-
end
219+
# export likelihood_ratio
220+
221+
# """
222+
# likelihood_ratio(ℓ::Likelihood, p, q)
223+
224+
# Compute the log of the likelihood ratio, in order to compare two choices for
225+
# parameters. This is equal to
226+
227+
# density_rel(ℓ.k(p), ℓ.k(q), ℓ.x)
228+
229+
# but is computed using LogarithmicNumbers.jl to avoid underflow and overflow.
230+
# Since `density_rel` can leave common base measure unevaluated, this can be
231+
# more efficient than
232+
233+
# logdensityof(ℓ.k(p), ℓ.x) - logdensityof(ℓ.k(q), ℓ.x)
234+
# """
235+
# function likelihood_ratio(ℓ::Likelihood, p, q)
236+
# exp(ULogarithmic, logdensity_rel(ℓ.k(p), ℓ.k(q), ℓ.x))
237+
# end

src/density.jl

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,13 @@ DensityInterface.funcdensity(d::LogDensity) = throw(MethodError(funcdensity, (d,
9898
base :: B
9999
end
100100
101-
A `DensityMeasure` is a measure defined by a density or log-density with respect
102-
to some other "base" measure.
101+
A `DensityMeasure` is a measure defined by a density or log-density with
102+
respect to some other "base" measure.
103103
104-
Users should not call `DensityMeasure` directly, but should instead call `∫(f,
105-
base)` (if `f` is a density function or `DensityInterface.IsDensity` object) or
106-
`∫exp(f, base)` (if `f` is a log-density function).
104+
Users should not instantiate `DensityMeasure` directly, but should instead
105+
call `mintegral_exp(f, base)` (if `f` is a density function or
106+
`DensityInterface.IsDensity` object) or `mintegral_exp(f, base)` (if `f`
107+
is a log-density function).
107108
"""
108109
struct DensityMeasure{F,B} <: AbstractMeasure
109110
f::F
@@ -120,48 +121,77 @@ end
120121
end
121122

122123
function Pretty.tile::DensityMeasure{F,B}) where {F,B}
123-
result = Pretty.literal("DensityMeasure ∫(")
124+
result = Pretty.literal("mintegrate(")
124125
result *= Pretty.pair_layout(Pretty.tile.f), Pretty.tile.base); sep = ", ")
125126
result *= Pretty.literal(")")
126127
end
127128

128-
export
129+
basemeasure::DensityMeasure) = μ.base
130+
131+
logdensity_def::DensityMeasure, x) = logdensityof.f, x)
132+
133+
density_def::DensityMeasure, x) = densityof.f, x)
129134

130-
"""
131-
∫(f, base::AbstractMeasure)
132135

133-
Define a new measure in terms of a density `f` over some measure `base`.
136+
137+
@doc raw"""
138+
mintegrate(f, μ::AbstractMeasure)::AbstractMeasure
139+
140+
Returns a new measure that represents the indefinite
141+
[integral](https://en.wikipedia.org/wiki/Radon%E2%80%93Nikodym_theorem)
142+
of `f` with respect to `μ`.
143+
144+
`ν = mintegrate(f, μ)` generates a measure `ν` that has the mathematical
145+
interpretation
146+
147+
math```
148+
\nu(A) = \int_A f(a) \, \rm{d}\mu(a)
149+
```
134150
"""
135-
(f, base) = _densitymeasure(f, base, DensityKind(f))
151+
function mintegrate end
152+
export mintegrate
153+
154+
mintegrate(f, μ::AbstractMeasure) = _mintegrate_impl(f, μ, DensityKind(f))
136155

137-
_densitymeasure(f, base, ::IsDensity) = DensityMeasure(f, base)
138-
function _densitymeasure(f, base, ::HasDensity)
139-
@error "`(f, base)` requires `DensityKind(f)` to be `IsDensity()` or `NoDensity()`."
156+
_mintegrate_impl(f, μ, ::IsDensity) = DensityMeasure(f, μ)
157+
function _mintegrate_impl(f, μ, ::HasDensity)
158+
throw(ArgumentError( "`mintegrate(f, mu)` requires `DensityKind(f)` to be `IsDensity()` or `NoDensity()`."))
140159
end
141-
_densitymeasure(f, base, ::NoDensity) = DensityMeasure(funcdensity(f), base)
160+
_mintegrate_impl(f, μ, ::NoDensity) = DensityMeasure(funcdensity(f), μ)
142161

143-
export ∫exp
144162

145-
"""
146-
∫exp(f, base::AbstractMeasure)
163+
@doc raw"""
164+
mintegrate_exp(log_f, μ::AbstractMeasure)
165+
166+
Given a function `log_f` that semantically represents the log of a function
167+
`f`, `mintegrate` returns a new measure that represents the indefinite
168+
[integral](https://en.wikipedia.org/wiki/Radon%E2%80%93Nikodym_theorem)
169+
of `f` with respect to `μ`.
170+
171+
`ν = mintegrate_exp(log_f, μ)` generates a measure `ν` that has the
172+
mathematical interpretation
147173
148-
Define a new measure in terms of a log-density `f` over some measure `base`.
174+
math```
175+
\nu(A) = \int_A e^{log(f(a))} \, \rm{d}\mu(a) = \int_A f(a) \, \rm{d}\mu(a)
176+
```
177+
178+
Note that `exp(log_f(...))` is usually not run explicitly, calculations that
179+
involve the resulting measure are typically performed in log-space,
180+
internally.
149181
"""
150-
∫exp(f, base) = _logdensitymeasure(f, base, DensityKind(f))
182+
function mintegrate_exp end
183+
export mintegrate_exp
184+
185+
mintegrate_exp(log_f, μ::AbstractMeasure) = _mintegrate_exp_impl(log_f, μ, DensityKind(log_f))
151186

152-
function _logdensitymeasure(f, base, ::IsDensity)
153-
@error "`∫exp(f, base)` is not valid when `DensityKind(f) == IsDensity()`. Use `∫(f, base)` instead."
187+
function _mintegrate_exp_impl(log_f, μ, ::IsDensity)
188+
throw(ArgumentError("`mintegrate_exp(log_f, μ)` is not valid when `DensityKind(log_f) == IsDensity()`. Use `mintegral(log_f, μ)` instead."))
154189
end
155-
function _logdensitymeasure(f, base, ::HasDensity)
156-
@error "`∫exp(f, base)` is not valid when `DensityKind(f) == HasDensity()`."
190+
function _mintegrate_exp_impl(log_f, μ, ::HasDensity)
191+
throw(ArgumentError("`mintegrate_exp(log_f, μ)` is not valid when `DensityKind(log_f) == HasDensity()`."))
157192
end
158-
_logdensitymeasure(f, base, ::NoDensity) = DensityMeasure(logfuncdensity(f), base)
193+
_mintegrate_exp_impl(log_f, μ, ::NoDensity) = DensityMeasure(logfuncdensity(log_f), μ)
159194

160-
basemeasure::DensityMeasure) = μ.base
161-
162-
logdensity_def::DensityMeasure, x) = logdensityof.f, x)
163-
164-
density_def::DensityMeasure, x) = densityof.f, x)
165195

166196
"""
167197
rebase(μ, ν)
@@ -172,4 +202,4 @@ basemeasure(rebase(μ, ν)) == ν
172202
density(rebase(μ, ν)) == 𝒹(μ,ν)
173203
```
174204
"""
175-
rebase(μ, ν) = (𝒹(μ, ν), ν)
205+
rebase(μ, ν) = mintegrate(density_rel(μ, ν), ν)

test/runtests.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ include("static.jl")
2222
# # detect_ambiguities_options...,
2323
# )
2424

25-
d = ∫exp(x -> -x^2, Lebesgue(ℝ))
25+
d = mintegrate_exp(x -> -x^2, Lebesgue(ℝ))
2626

2727
# function draw2(μ)
2828
# x = rand(μ)
@@ -148,7 +148,7 @@ end
148148
@test logdensityof(Lebesgue()^3, 2) == logdensityof(Lebesgue()^(3, 1), (2, 0))
149149
end
150150

151-
Normal() = ∫exp(x -> -0.5x^2, Lebesgue(ℝ))
151+
Normal() = mintegrate_exp(x -> -0.5x^2, Lebesgue(ℝ))
152152

153153
@testset "Half" begin
154154
HalfNormal() = Half(Normal())
@@ -159,7 +159,7 @@ end
159159

160160
@testset "Likelihood" begin
161161
= Likelihood(3) do (μ,)
162-
∫exp(Lebesgue(ℝ)) do x
162+
mintegrate_exp(Lebesgue(ℝ)) do x
163163
-(x - μ)^2
164164
end
165165
end
@@ -236,13 +236,13 @@ end
236236
@testset "Density measures and Radon-Nikodym" begin
237237
x = randn()
238238
f(x) = x^2
239-
@test log(𝒹(∫exp(f, Lebesgue()), Lebesgue())(x)) f(x)
239+
@test log(𝒹(mintegrate_exp(f, Lebesgue()), Lebesgue())(x)) f(x)
240240

241-
let f = 𝒹(∫exp(x -> x^2, Lebesgue()), Lebesgue())
241+
let f = 𝒹(mintegrate_exp(x -> x^2, Lebesgue()), Lebesgue())
242242
@test log(f(x)) x^2
243243
end
244244

245-
let f = log𝒹(∫exp(x -> x^2, Normal()), Normal())
245+
let f = log𝒹(mintegrate_exp(x -> x^2, Normal()), Normal())
246246
@test f(x) x^2
247247
end
248248
end

0 commit comments

Comments
 (0)