ExponentialFamilyProjection
The ExponentialFamilyProjection.jl
package offers a suite of functions for projecting an arbitrary (un-normalized) log probability density function onto a specified member of the exponential family (e.g., Gaussian, Beta, Bernoulli). This is achieved by optimizing the natural parameters of the exponential family member within a defined manifold. The library leverages Manopt.jl
for optimization and utilizes ExponentialFamilyManifolds.jl
to define the manifolds corresponding to the members of the exponential family.
Projection parameters
In order to project a log probability density function onto a member of the exponential family, the user first needs to specify projection parameters:
ExponentialFamilyProjection.ProjectionParameters
— TypeProjectionParameters(; kwargs...)
A type to hold the parameters for the projection procedure. The following parameters are available:
strategy = ExponentialFamilyProjection.DefaultStrategy()
: The strategy to use to compute the gradients.niterations = 100
: The number of iterations for the optimization procedure.tolerance = 1e-6
: The tolerance for the norm of the gradient.stepsize = ConstantStepsize(0.1)
: The stepsize for the optimization procedure. Accepts stepsizes fromManopt.jl
.seed
: Optional; Seed for therng
rng
: Optional; Random number generatordirection = BoundedNormUpdateRule(static(1.0)
: Direction update rule. AcceptsManopt.DirectionUpdateRule
fromManopt.jl
.
ExponentialFamilyProjection.DefaultProjectionParameters
— FunctionDefaultProjectionParameters()
Return the default parameters for the projection procedure.
ExponentialFamilyProjection.getinitialpoint
— Functiongetinitialpoint(strategy, M::AbstractManifold, parameters::ProjectionParameters)
Returns an initial point to start optimization from. By default returns a rand
point from M
, but different strategies may implement their own methods.
Read more about different optimization strategies here.
Projection family
After the parameters have been specified the user can proceed with specifying the projection type (exponential family member), its dimensionality and (optionally) the conditioner.
ExponentialFamilyProjection.ProjectedTo
— TypeProjectedTo(::Type{T}, dims...; conditioner = nothing, parameters = DefaultProjectionParameters)
A specification of a projection to an exponential family distribution.
The following arguments are required:
Type{T}
: a type of an exponential family member to project to, e.g.Beta
dims...
: dimensions of the distribution, e.g.2
forMvNormal
The following arguments are optional:
conditioner = nothing
: a conditioner to use for the projection, not all exponential family members require a conditioner, but some do, e.g.Laplace
parameters = DefaultProjectionParameters
: parameters for the projection procedurekwargs = nothing
: Additional arguments passed toManopt.gradient_descent!
(optional). For details ongradient_descent!
parameters, see the Manopt.jl documentation. Note, thatkwargs
passed toproject_to
take precedence overkwargs
specified in the parameters.
julia> using ExponentialFamily
julia> projected_to = ProjectedTo(Beta)
ProjectedTo(Beta)
julia> projected_to = ProjectedTo(Beta, parameters = ProjectionParameters(niterations = 10))
ProjectedTo(Beta)
julia> projected_to = ProjectedTo(MvNormalMeanCovariance, 2)
ProjectedTo(MvNormalMeanCovariance, dims = 2)
julia> projected_to = ProjectedTo(Laplace, conditioner = 2.0)
ProjectedTo(Laplace, conditioner = 2.0)
Projection
The projection is performed by calling the project_to
function with the specified ExponentialFamilyProjection.ProjectedTo
and log probability density function or a set of data point as the second argument.
ExponentialFamilyProjection.project_to
— Functionproject_to(to::ProjectedTo, argument::F, supplementary..., initialpoint, kwargs...)
Finds the closest projection of argument
onto the exponential family distribution specified by to
.
Arguments
to::ProjectedTo
: Configuration for the projection. Refer toProjectedTo
for detailed information.argument::F
: An (un-normalized) function representing the log-PDF of an arbitrary distribution or a list of samples.supplementary...
: Additional distributions to project the product ofargument
and these distributions (optional).initialpoint
: Starting point for the optimization process (optional).kwargs...
: Additional arguments passed toManopt.gradient_descent!
(optional). For details ongradient_descent!
parameters, see the Manopt.jl documentation.
Supplementary
The supplementary
distributions must match the type and conditioner of the target distribution specified in to
. Including supplementary distributions is equivalent to modified argument
function as follows:
f_modified = (x) -> argument(x) + logpdf(supplementary[1], x) + logpdf(supplementary[2], x) + ...
julia> using ExponentialFamily, BayesBase
julia> f = (x) -> logpdf(Beta(30.14, 2.71), x);
julia> prj = ProjectedTo(Beta; parameters = ProjectionParameters(niterations = 500))
ProjectedTo(Beta)
julia> project_to(prj, f) isa ExponentialFamily.Beta
true
julia> using ExponentialFamily, BayesBase, StableRNGs
julia> samples = rand(StableRNG(42), Beta(30.14, 2.71), 1_000);
julia> prj = ProjectedTo(Beta; parameters = ProjectionParameters(tolerance = 1e-2))
ProjectedTo(Beta)
julia> project_to(prj, samples) isa ExponentialFamily.Beta
true
Different strategies are compatible with different types of arguments. Read optimization strategies section in the documentation for more information.
Optimization strategies
The optimization procedure requires computing the expectation of the gradient to perform gradient descent in the natural parameters space. Currently, the library provides the following strategies for computing these expectations:
ExponentialFamilyProjection.DefaultStrategy
— TypeDefaultStrategy
The DefaultStrategy selects the optimal projection strategy based on the type of the second argument provided to the project_to
function.
Rules:
- If the second argument is an
AbstractArray
, useMLEStrategy
. - For all other types, use
ControlVariateStrategy
.
The rules above are subject to change.
ExponentialFamilyProjection.ControlVariateStrategy
— TypeControlVariateStrategy(; kwargs...)
A strategy for gradient descent optimization and gradients computations that resembles the REINFORCE gradient estimator.
The following parameters are available:
nsamples = 2000
: The number of samples to use for estimatesbuffer = StaticTools.MallocSlabBuffer()
: Advanced option; A buffer for temporary computations
This strategy requires a function as an argument for project_to
and cannot project a collection of samples. Use MLEStrategy
to project a collection of samples.
ExponentialFamilyProjection.MLEStrategy
— TypeMLEStrategy()
A strategy for gradient descent optimization and gradients computations that resembles MLE estimation.
This strategy requires a collection of samples as an argument for project_to
and cannot project a function. Use ControlVariateStrategy
to project a function.
ExponentialFamilyProjection.preprocess_strategy_argument
— Functionpreprocess_strategy_argument(strategy, argument)
Checks the compatibility of strategy
with argument
and returns a modified strategy and argument if needed.
ExponentialFamilyProjection.create_state!
— Functioncreate_state!(
strategy,
M::AbstractManifold,
parameters::ProjectionParameters,
projection_argument,
initial_ef,
supplementary_η,
)
Creates, initializes and returns a state for the strategy
with the given parameters.
ExponentialFamilyProjection.prepare_state!
— Functionprepare_state!(
strategy,
state,
M::AbstractManifold,
parameters::ProjectionParameters,
projection_argument,
distribution,
supplementary_η,
)
Prepares an existing state
of the strategy
for the new optimization iteration for use by setting or updating its internal parameters.
ExponentialFamilyProjection.compute_cost
— Functioncompute_cost(
M::AbstractManifold,
strategy,
state,
η,
logpartition,
gradlogpartition,
inv_fisher,
)
Compute the cost using the provided strategy
.
Arguments
M::AbstractManifold
: The manifold on which the computations are performed.strategy
: The strategy used for computation of the cost value.state
: The current state for thestrategy
.η
: Parameter vector.logpartition
: The log partition of the current point (η).gradlogpartition
: The gradient of the log partition of the current point (η).inv_fisher
: The inverse Fisher information matrix of the current point (η).
Returns
cost
: The computed cost value.
ExponentialFamilyProjection.compute_gradient!
— Functioncompute_gradient!(
M::AbstractManifold,
strategy,
state,
X,
η,
logpartition,
gradlogpartition,
inv_fisher,
)
Updates the gradient X
in-place using the provided strategy
.
Arguments
M::AbstractManifold
: The manifold on which the computations are performed.strategy
: The strategy used for computation of the gradient value.state
: The current state of the control variate strategy.X
: The storage for the gradient.η
: Parameter vector.logpartition
: The log partition of the current point (η).gradlogpartition
: The gradient of the log partition of the current point (η).inv_fisher
: The inverse Fisher information matrix of the current point (η).
Returns
X
: The computed gradient (updated in-place)
For high-dimensional distributions, adjusting the default number of samples might be necessary to achieve better performance.
Examples
Gaussian projection
In this example we project an arbitrary log probability density function onto a Gaussian distribution. The log probability density function is defined using another Gaussian
, but it can be any function:
using ExponentialFamilyProjection, ExponentialFamily, BayesBase
hiddengaussian = NormalMeanVariance(3.14, 2.71)
targetf = (x) -> logpdf(hiddengaussian, x)
prj = ProjectedTo(NormalMeanVariance)
result = project_to(prj, targetf)
ExponentialFamily.NormalMeanVariance{Float64}(μ=3.1472870713875456, v=2.6741746757950238)
We can see that the estimated result
is pretty close to the actual hiddengaussian
used to define the targetf
. We can also visualise the results using the Plots.jl
package.
using Plots
plot(-6.0:0.1:12.0, x -> pdf(hiddengaussian, x), label="real distribution", fill = 0, fillalpha = 0.2)
plot!(-6.0:0.1:12.0, x -> pdf(result, x), label="estimated projection", fill = 0, fillalpha = 0.2)
Let's also try to project an arbitrary unnormalized log probability density function onto a Gaussian distribution:
# `+ 100` to ensure that the function is unnormalized
targetf = (x) -> -0.5 * (x - 3.14)^2 + 100
result = project_to(prj, targetf)
ExponentialFamily.NormalMeanVariance{Float64}(μ=3.022854932263184, v=0.8218806444470086)
In this case, targetf
does not define any valid probability distribution since it is unnormalized, but the project_to
function is able to project it onto a closest possible Gaussian distribution. We can again visualize the results using the Plots.jl
package:
plot(-40.0:0.1:40.0, targetf, label="unnormalized logpdf", fill = 0, fillalpha = 0.2)
plot!(-40.0:0.1:40.0, (x) -> logpdf(result, x), label="estimated logpdf of a Gaussian", fill = 0, fillalpha = 0.2)
Beta projection
The experiment can be performed for other members of the exponential family as well. For example, let's project an arbitrary log probability density function onto a Beta distribution:
hiddenbeta = Beta(10, 3)
targetf = (x) -> logpdf(hiddenbeta, x)
prj = ProjectedTo(Beta)
result = project_to(prj, targetf)
Distributions.Beta{Float64}(α=8.976710744476689, β=2.7316968890242)
And let's visualize the result using the Plots.jl
package:
plot(0.0:0.01:1.0, x -> pdf(hiddenbeta, x), label="real distribution", fill = 0, fillalpha = 0.2)
plot!(0.0:0.01:1.0, x -> pdf(result, x), label="estimated projection", fill = 0, fillalpha = 0.2)
Multivariate Gaussian projection
The library also supports multivariate distributions. Let's project an arbitrary log probability density function onto a multivariate Gaussian distribution.
hiddengaussian = MvNormalMeanCovariance(
[ 3.14, 2.17 ],
[ 2.0 -0.1; -0.1 3.0 ]
)
targetf = (x) -> logpdf(hiddengaussian, x)
prj = ProjectedTo(MvNormalMeanCovariance, 2)
result = project_to(prj, targetf)
MvNormalMeanCovariance(
μ: [3.046220659005711, 2.2152913166072983]
Σ: [1.898600842587293 0.002457042029683547; 0.002457042029683547 2.9753353173865826]
)
As in previous examples the result is pretty close to the actual hiddengaussian
used to define the targetf
.
Projection with samples
The projection can be done given a set of samples instead of the function directly. For example, let's project an set of samples onto a Beta distribution:
using StableRNGs
hiddenbeta = Beta(10, 3)
samples = rand(StableRNG(42), hiddenbeta, 1_000)
prj = ProjectedTo(Beta)
result = project_to(prj, samples)
Distributions.Beta{Float64}(α=9.934683749459346, β=2.844620239774739)
plot(0.0:0.01:1.0, x -> pdf(hiddenbeta, x), label="real distribution", fill = 0, fillalpha = 0.2)
histogram!(samples, label = "samples", normalize = :pdf, fillalpha = 0.2)
plot!(0.0:0.01:1.0, x -> pdf(result, x), label="estimated projection", fill = 0, fillalpha = 0.2)
Manopt extensions
ExponentialFamilyProjection.ProjectionCostGradientObjective
— TypeProjectionCostGradientObjective
This structure provides an interface for Manopt
to compute the cost and gradients required for the optimization procedure based on manifold projection. The actual computation of costs and gradients is defined by the strategy
argument.
Arguments
projection_parameters
: The parameters for projection, must be of typeProjectionParameters
projection_argument
: The second argument of theproject_to
function.current_η
: Current optimization point.supplementary_η
: A tuple of additional natural parameters subtracted from the current point in each optimization iteration.strategy
: Specifies the method for computing costs and gradients, which may support differentprojection_argument
values.strategy_state
: The state for thestrategy
, usually created withcreate_state!
This structure is internal and is subject to change.
Bounded direction update rule
The ExponentialFamilyProjection.jl
package implements a specialized gradient direction rule that limits the norm (manifold-specific) of the gradient to a pre-specified value.
ExponentialFamilyProjection.BoundedNormUpdateRule
— TypeBoundedNormUpdateRule(limit; direction = IdentityUpdateRule())
A DirectionUpdateRule
is a direction rule that constrains the norm of the direction to a specified limit.
This rule operates in two steps:
- Initial direction computation: It first applies the specified
direction
update rule to compute an initial direction. - Norm check and scaling: The norm of the resulting direction vector is checked using
Manopt.norm(M, p, d)
, where:M
` is the manifold on which the optimization is running,p
is the point at which the direction was computed,d
is the computed direction.- If this norm exceeds the specified
limit
, the direction vector is scaled down so that its new norm exactly equals the limit. This scaling preserves the direction of the gradient while controlling its magnitude.
Read more about Manopt.DirectionUpdateRule
in the Manopt.jl
documentation.
Index
ExponentialFamilyProjection.BoundedNormUpdateRule
ExponentialFamilyProjection.ControlVariateStrategy
ExponentialFamilyProjection.DefaultStrategy
ExponentialFamilyProjection.MLEStrategy
ExponentialFamilyProjection.ProjectedTo
ExponentialFamilyProjection.ProjectionCostGradientObjective
ExponentialFamilyProjection.ProjectionParameters
ExponentialFamilyProjection.DefaultProjectionParameters
ExponentialFamilyProjection.compute_cost
ExponentialFamilyProjection.compute_gradient!
ExponentialFamilyProjection.create_state!
ExponentialFamilyProjection.getinitialpoint
ExponentialFamilyProjection.prepare_state!
ExponentialFamilyProjection.preprocess_strategy_argument
ExponentialFamilyProjection.project_to