Skip to content
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

Extend gradlogpdf to MixtureModels #1827

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
55 changes: 47 additions & 8 deletions src/mixtures/mixturemodel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -367,15 +367,31 @@ function _mixlogpdf!(r::AbstractArray, d::AbstractMixtureModel, x)
return r
end

function _mixgradlogpdf1(d::AbstractMixtureModel, x)
pdfdx = pdf(d, x)
glp = !iszero(pdfdx) ? sum(pi * pdf(d.components[i], x) .* gradlogpdf(d.components[i], x) for (i, pi) in enumerate(probs(d)) if (!iszero(pi) && !iszero(pdf(d.components[i], x)))) / pdfdx : pdfdx
return glp
end

pdf(d::UnivariateMixture, x::Real) = _mixpdf1(d, x)
logpdf(d::UnivariateMixture, x::Real) = _mixlogpdf1(d, x)
gradlogpdf(d::UnivariateMixture, x::Real) = _mixgradlogpdf1(d, x)

function gradlogpdf(d::UnivariateMixture, x::Real)
cp = components(d)
pr = probs(d)
pdfx1 = pdf(cp[1], x)
pdfx = pr[1] * pdfx1
_glp = pdfx * gradlogpdf(cp[1], x)
glp = (!iszero(pr[1])) && (!iszero(pdfx)) ? _glp : zero(_glp)
@inbounds for i in Iterators.drop(eachindex(pr, cp), 1)
rmsrosa marked this conversation as resolved.
Show resolved Hide resolved
if !iszero(pr[i])
pdfxi = pdf(cp[i], x)
if !iszero(pdfxi)
pipdfxi = pr[i] * pdfxi
pdfx += pipdfxi
glp += pipdfxi * gradlogpdf(cp[i], x)
end
end
end
if !iszero(pdfx) # else glp is already zero
glp /= pdfx
end
Comment on lines +398 to +400
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct to return a gradlogpdf of zero if x is not in the support of the mixture distribution?

Copy link
Author

@rmsrosa rmsrosa Jan 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered about that, but decided to follow the current behavior already implemented. For example:

julia> insupport(Beta(0.5, 0.5), -1)
false

julia> logpdf(Beta(0.5, 0.5), -1)
-Inf

julia> gradlogpdf(Beta(0.5, 0.5), -1)
0.0

I don't know. If it is constant -Inf, then the derivative is zero (except that (-Inf) - (-Inf) is not defined, but what matters is that the rate of change is zero...)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should make this a separate issue and hopefully standardize the behavior.

return glp
end

_pdf!(r::AbstractArray{<:Real}, d::UnivariateMixture{Discrete}, x::UnitRange) = _mixpdf!(r, d, x)
_pdf!(r::AbstractArray{<:Real}, d::UnivariateMixture, x::AbstractArray{<:Real}) = _mixpdf!(r, d, x)
Expand All @@ -386,7 +402,30 @@ _logpdf(d::MultivariateMixture, x::AbstractVector{<:Real}) = _mixlogpdf1(d, x)
_pdf!(r::AbstractArray{<:Real}, d::MultivariateMixture, x::AbstractMatrix{<:Real}) = _mixpdf!(r, d, x)
_logpdf!(r::AbstractArray{<:Real}, d::MultivariateMixture, x::AbstractMatrix{<:Real}) = _mixlogpdf!(r, d, x)

gradlogpdf(d::MultivariateMixture, x::AbstractVector{<:Real}) = _mixgradlogpdf1(d, x)
function gradlogpdf(d::MultivariateMixture, x::AbstractVector{<:Real})
cp = components(d)
pr = probs(d)
pdfx1 = pdf(cp[1], x)
pdfx = pr[1] * pdfx1
glp = pdfx * gradlogpdf(cp[1], x)
if ( iszero(pr[1]) || iszero(pdfx) )
glp .= zero(eltype(glp))
rmsrosa marked this conversation as resolved.
Show resolved Hide resolved
end
@inbounds for i in Iterators.drop(eachindex(pr, cp), 1)
if !iszero(pr[i])
pdfxi = pdf(cp[i], x)
if !iszero(pdfxi)
pipdfxi = pr[i] * pdfxi
pdfx += pipdfxi
glp .+= pipdfxi * gradlogpdf(cp[i], x)
end
end
end
if !iszero(pdfx) # else glp is already zero
glp ./= pdfx
end
return glp
end

## component-wise pdf and logpdf

Expand Down
10 changes: 6 additions & 4 deletions test/gradlogpdf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,24 @@ using Test

# Test for gradlogpdf on univariate mixture distributions against centered finite-difference on logpdf

x = [-0.2, 0.3, 0.8, 1.3, 10.5]
delta = 0.001
x = [-0.2, 0.3, 0.8, 1.0, 1.3, 10.5]
delta = 0.0001

for d in (
MixtureModel([Normal(-4.5, 2.0)], [1.0]),
MixtureModel([Exponential(2.0)], [1.0]),
MixtureModel([Uniform(-1.0, 1.0)], [1.0]),
MixtureModel([Normal(1//1, 2//1), Beta(2//1, 3//1), Exponential(3//2)], [3//10, 4//10, 3//10]),
MixtureModel([Normal(-2.0, 3.5), Normal(-4.5, 2.0)], [0.0, 1.0]),
MixtureModel([Beta(1.5, 3.0), Chi(5.0), Chisq(7.0)], [0.4, 0.3, 0.3]),
MixtureModel([Exponential(2.0), Gamma(9.0, 0.5), Gumbel(3.5, 1.0), Laplace(7.0)], [0.3, 0.2, 0.4, 0.1]),
MixtureModel([Logistic(-6.0), LogNormal(5.5), TDist(8.0), Weibull(2.0)], [0.3, 0.2, 0.4, 0.1])
)
xs = filter(s -> insupport(d, s), x)
xs = filter(s -> all(insupport.(d, [s - delta, s, s + delta])), x)
glp1 = gradlogpdf.(d, xs)
glp2 = ( logpdf.(d, xs .+ delta) - logpdf.(d, xs .- delta) ) ./ 2delta
@test isapprox(glp1, glp2, atol = delta)
@info "Testing `gradlogpdf` on $d"
@test isapprox(glp1, glp2, atol = 0.01)
end

# Test for gradlogpdf on multivariate mixture distributions against centered finite-difference on logpdf
Expand Down
Loading