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.ProjectionParametersType
ProjectionParameters(; 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 from Manopt.jl.
  • seed: Optional; Seed for the rng
  • rng: Optional; Random number generator
  • direction = BoundedNormUpdateRule(static(1.0): Direction update rule. Accepts Manopt.DirectionUpdateRule from Manopt.jl.
source
ExponentialFamilyProjection.getinitialpointFunction
getinitialpoint(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.

source

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.ProjectedToType
ProjectedTo(::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 for MvNormal

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 procedure
  • kwargs = nothing: Additional arguments passed to Manopt.gradient_descent! (optional). For details on gradient_descent! parameters, see the Manopt.jl documentation. Note, that kwargs passed to project_to take precedence over kwargs 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)
source

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_toFunction
project_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 to ProjectedTo 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 of argument and these distributions (optional).
  • initialpoint: Starting point for the optimization process (optional).
  • kwargs...: Additional arguments passed to Manopt.gradient_descent! (optional). For details on gradient_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
Note

Different strategies are compatible with different types of arguments. Read optimization strategies section in the documentation for more information.

source

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.DefaultStrategyType
DefaultStrategy

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, use MLEStrategy.
  • For all other types, use ControlVariateStrategy.
Note

The rules above are subject to change.

source
ExponentialFamilyProjection.ControlVariateStrategyType
ControlVariateStrategy(; 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 estimates
  • buffer = StaticTools.MallocSlabBuffer(): Advanced option; A buffer for temporary computations
Note

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.

source
ExponentialFamilyProjection.MLEStrategyType
MLEStrategy()

A strategy for gradient descent optimization and gradients computations that resembles MLE estimation.

Note

This strategy requires a collection of samples as an argument for project_to and cannot project a function. Use ControlVariateStrategy to project a function.

source
ExponentialFamilyProjection.create_state!Function
create_state!(
    strategy,
    M::AbstractManifold,
    parameters::ProjectionParameters,
    projection_argument,
    initial_ef,
    supplementary_η,
)

Creates, initializes and returns a state for the strategy with the given parameters.

source
ExponentialFamilyProjection.prepare_state!Function
prepare_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.

source
ExponentialFamilyProjection.compute_costFunction
compute_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 the strategy.
  • η: 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.
source
ExponentialFamilyProjection.compute_gradient!Function
compute_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)
source

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.1472870713875465, v=2.674174675795019)

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)
Example block output

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.0228549322632525, v=0.8218806444471601)

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)
Example block output

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.976710744476676, β=2.7316968890241955)

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)
Example block output

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.0462206590057095, 2.215291316607294]
Σ: [1.8986008425872978 0.0024570420296910777; 0.0024570420296910777 2.9753353173865804]
)

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)
Example block output

Manopt extensions

ExponentialFamilyProjection.ProjectionCostGradientObjectiveType
ProjectionCostGradientObjective

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 type ProjectionParameters
  • projection_argument: The second argument of the project_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 different projection_argument values.
  • strategy_state: The state for the strategy, usually created with create_state!
Note

This structure is internal and is subject to change.

source

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.BoundedNormUpdateRuleType
BoundedNormUpdateRule(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.

source

Index