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