-
Notifications
You must be signed in to change notification settings - Fork 0
Add ChainRulesCore extension with frule/rrule for logmeanexp, logvarexp, logstdexp #3
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| module LogStatFunctionsChainRulesCoreExt | ||
|
|
||
| using LogStatFunctions: logmeanexp, logvarexp, logstdexp | ||
| import ChainRulesCore | ||
|
|
||
| function ChainRulesCore.frule((_, Δx), ::typeof(logmeanexp), x::AbstractArray{<:Real}; dims = :) | ||
| Ω = logmeanexp(x; dims) | ||
| n = length(x) ÷ length(Ω) | ||
| ΔΩ = sum(exp.(x .- Ω) .* Δx; dims) ./ n | ||
| return Ω, ΔΩ | ||
| end | ||
|
|
||
| function ChainRulesCore.rrule(::typeof(logmeanexp), x::AbstractArray{<:Real}; dims = :) | ||
| Ω = logmeanexp(x; dims) | ||
| n = length(x) ÷ length(Ω) | ||
| return Ω, _∂x_pullback(exp.(x .- Ω) ./ n, x) | ||
| end | ||
|
|
||
| function ChainRulesCore.frule( | ||
| (_, Δx), ::typeof(logvarexp), x::AbstractArray{<:Real}; | ||
| dims = :, corrected::Bool = true, logmean = logmeanexp(x; dims) | ||
| ) | ||
| Ω = logvarexp(x; dims, corrected, logmean) | ||
| ΔΩ = sum(_∂x_logvarexp(x, logmean, dims) .* Δx; dims) | ||
| return Ω, ΔΩ | ||
| end | ||
|
|
||
| function ChainRulesCore.rrule( | ||
| ::typeof(logvarexp), x::AbstractArray{<:Real}; | ||
| dims = :, corrected::Bool = true, logmean = logmeanexp(x; dims) | ||
| ) | ||
| Ω = logvarexp(x; dims, corrected, logmean) | ||
| return Ω, _∂x_pullback(_∂x_logvarexp(x, logmean, dims), x) | ||
| end | ||
|
|
||
| function ChainRulesCore.frule( | ||
| (_, Δx), ::typeof(logstdexp), x::AbstractArray{<:Real}; | ||
| dims = :, corrected::Bool = true, logmean = logmeanexp(x; dims) | ||
| ) | ||
| Ω = logstdexp(x; dims, corrected, logmean) | ||
| ΔΩ = sum(_∂x_logvarexp(x, logmean, dims) ./ 2 .* Δx; dims) | ||
| return Ω, ΔΩ | ||
| end | ||
|
|
||
| function ChainRulesCore.rrule( | ||
| ::typeof(logstdexp), x::AbstractArray{<:Real}; | ||
| dims = :, corrected::Bool = true, logmean = logmeanexp(x; dims) | ||
| ) | ||
| Ω = logstdexp(x; dims, corrected, logmean) | ||
| return Ω, _∂x_pullback(_∂x_logvarexp(x, logmean, dims) / 2, x) | ||
| end | ||
|
|
||
| # ∂/∂xⱼ log(var(exp.(x))) = 2 exp(xⱼ) (exp(xⱼ) - m) / Σᵢ (exp(xᵢ) - m)², with m = exp(logmean). | ||
| # The m-dependence on x drops out because Σᵢ (exp(xᵢ) - m) = 0, and `corrected` only shifts | ||
| # the result by a constant, so the gradient is the same either way. | ||
| function _∂x_logvarexp(x::AbstractArray{<:Real}, logmean, dims) | ||
| d = x .- logmean | ||
| e = expm1.(d) | ||
| return (2 .* exp.(d) .* e) ./ sum(abs2, e; dims) | ||
|
Comment on lines
+57
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For nearly equal but representably distinct inputs, squaring Useful? React with 👍 / 👎. |
||
| end | ||
|
|
||
| function _∂x_pullback(∂x, x) | ||
| project_x = ChainRulesCore.ProjectTo(x) | ||
| function pullback(Ω̄) | ||
| x̄ = ChainRulesCore.InplaceableThunk( | ||
| Δ -> Δ .+= Ω̄ .* ∂x, | ||
| ChainRulesCore.@thunk(project_x(Ω̄ .* ∂x)), | ||
|
Comment on lines
+66
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an upstream ChainRules pullback supplies Useful? React with 👍 / 👎. |
||
| ) | ||
| return ChainRulesCore.NoTangent(), x̄ | ||
| end | ||
| return pullback | ||
| end | ||
|
|
||
| end # module | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| using Test: @testset | ||
| using ChainRulesTestUtils: test_frule, test_rrule | ||
| using LogStatFunctions: logmeanexp, logvarexp, logstdexp | ||
|
|
||
| @testset "chainrules" begin | ||
| for x in (randn(10), randn(10, 8)), dims in (:, 1, 1:2, 2) | ||
| dims isa Colon || all(d ≤ ndims(x) for d in dims) || continue | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the loop reaches Useful? React with 👍 / 👎. |
||
| test_frule(logmeanexp, x; fkwargs = (; dims)) | ||
| test_rrule(logmeanexp, x; fkwargs = (; dims)) | ||
| for corrected in (true, false) | ||
| test_frule(logvarexp, x; fkwargs = (; dims, corrected)) | ||
| test_rrule(logvarexp, x; fkwargs = (; dims, corrected)) | ||
| test_frule(logstdexp, x; fkwargs = (; dims, corrected)) | ||
| test_rrule(logstdexp, x; fkwargs = (; dims, corrected)) | ||
| end | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The package already supports complex arrays for
logmeanexp, but every new rule is restricted toAbstractArray{<:Real}while the added documentation says all three functions have ChainRules derivatives without stating that limitation. Consequently,rrule(logmeanexp, complex_x)has no method from this extension and complex users do not receive the advertised AD support; either add the correctly conjugated complex rule or document the real-input restriction.Useful? React with 👍 / 👎.