-
Notifications
You must be signed in to change notification settings - Fork 45
Add sparse Hessian decompression #190
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
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
9788265
start adding sparse Hessian stuff
ElOceanografo 35049db
Merge branch 'master' of https://github.com/JuliaDiff/SparseDiffTools…
ElOceanografo 94adc3f
basic in-place and mutating sparse hessians
ElOceanografo c7688e1
Sort out some method signatures, add tests
ElOceanografo 8f199b1
Merge branch 'master' of https://github.com/JuliaDiff/SparseDiffTools…
ElOceanografo 237aeb8
fix typo
ElOceanografo f437dac
Make perturbation of x in-place
ElOceanografo a1b69f6
remove unnecessary SparsityDetection dep
ElOceanografo f926d5d
Cache GradientConfig and simplify call signatures
ElOceanografo 09f8caf
Add more tests
ElOceanografo 34c4a22
add (inneficient) dense default arguments
ElOceanografo 46a0375
add colorvec/sparsity method for allocating forwarddiff_color_hessian
ElOceanografo fd954f1
remove DrBronnerArray
ElOceanografo 71aedbe
Add checks on user-define gradient function
ElOceanografo c2a1a6d
Add some text and Hessian examples to README
ElOceanografo a30fd7d
change sqrt to cbrt
ElOceanografo 13b2dca
Condense signature for default gradient function
ElOceanografo d4911a7
"safe" option to make sure hessian is overwritten
ElOceanografo 0a47364
Rename forwarddiff_color_hessian to numauto_color_hessian
ElOceanografo 40e877c
Change to centered difference
ElOceanografo e1c8eaf
Remove timing test that randomly fails once in a while
ElOceanografo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
struct ForwardColorHesCache{THS, THC, TI<:Integer, TD, TGF, TGC, TG} | ||
sparsity::THS | ||
colors::THC | ||
ncolors::TI | ||
D::TD | ||
buffer::TD | ||
grad!::TGF | ||
grad_config::TGC | ||
G1::TG | ||
G2::TG | ||
end | ||
|
||
function make_hessian_buffers(colorvec, x) | ||
ncolors = maximum(colorvec) | ||
D = hcat([float.(i .== colorvec) for i in 1:ncolors]...) | ||
buffer = similar(D) | ||
G1 = similar(x) | ||
G2 = similar(x) | ||
return (;ncolors, D, buffer, G1, G2) | ||
end | ||
|
||
function ForwardColorHesCache(f, | ||
x::AbstractVector{<:Number}, | ||
colorvec::AbstractVector{<:Integer}=eachindex(x), | ||
sparsity::Union{AbstractMatrix, Nothing}=nothing, | ||
g! = (G, x, grad_config) -> ForwardDiff.gradient!(G, f, x, grad_config)) | ||
ncolors, D, buffer, G, G2 = make_hessian_buffers(colorvec, x) | ||
grad_config = ForwardDiff.GradientConfig(f, x) | ||
|
||
# If user supplied their own gradient function, make sure it has the right | ||
# signature (i.e. g!(G, x) or g!(G, x, grad_config::ForwardDiff.GradientConfig)) | ||
if ! hasmethod(g!, (typeof(G), typeof(G), typeof(grad_config))) | ||
if ! hasmethod(g!, (typeof(G), typeof(G))) | ||
throw(ArgumentError("Signature of `g!` must be either `g!(G, x)` or `g!(G, x, grad_config::ForwardDiff.GradientConfig)`")) | ||
end | ||
# define new method that takes a GradientConfig but doesn't use it | ||
g1!(G, x, grad_config) = g!(G, x) | ||
else | ||
g1! = g! | ||
end | ||
|
||
if sparsity === nothing | ||
sparsity = sparse(ones(length(x), length(x))) | ||
end | ||
return ForwardColorHesCache(sparsity, colorvec, ncolors, D, buffer, g1!, grad_config, G, G2) | ||
end | ||
|
||
function numauto_color_hessian!(H::AbstractMatrix{<:Number}, | ||
f, | ||
x::AbstractArray{<:Number}, | ||
hes_cache::ForwardColorHesCache; | ||
safe = true) | ||
ϵ = cbrt(eps(eltype(x))) | ||
for j in 1:hes_cache.ncolors | ||
x .+= ϵ .* @view hes_cache.D[:, j] | ||
hes_cache.grad!(hes_cache.G2, x, hes_cache.grad_config) | ||
x .-= 2ϵ .* @view hes_cache.D[:, j] | ||
hes_cache.grad!(hes_cache.G1, x, hes_cache.grad_config) | ||
hes_cache.buffer[:, j] .= (hes_cache.G2 .- hes_cache.G1) ./ 2ϵ | ||
x .+= ϵ .* @view hes_cache.D[:, j] #reset to original value | ||
end | ||
ii, jj, vv = findnz(hes_cache.sparsity) | ||
if safe | ||
fill!(H, false) | ||
end | ||
for (i, j) in zip(ii, jj) | ||
H[i, j] = hes_cache.buffer[i, hes_cache.colors[j]] | ||
end | ||
return H | ||
end | ||
|
||
function numauto_color_hessian!(H::AbstractMatrix{<:Number}, | ||
f, | ||
x::AbstractArray{<:Number}, | ||
colorvec::AbstractVector{<:Integer}=eachindex(x), | ||
sparsity::Union{AbstractMatrix, Nothing}=nothing) | ||
hes_cache = ForwardColorHesCache(f, x, colorvec, sparsity) | ||
numauto_color_hessian!(H, f, x, hes_cache) | ||
return H | ||
end | ||
|
||
function numauto_color_hessian(f, | ||
x::AbstractArray{<:Number}, | ||
hes_cache::ForwardColorHesCache) | ||
H = convert.(eltype(x), hes_cache.sparsity) | ||
numauto_color_hessian!(H, f, x, hes_cache) | ||
return H | ||
end | ||
|
||
function numauto_color_hessian(f, | ||
x::AbstractArray{<:Number}, | ||
colorvec::AbstractVector{<:Integer}=eachindex(x), | ||
sparsity::Union{AbstractMatrix, Nothing}=nothing) | ||
hes_cache = ForwardColorHesCache(f, x, colorvec, sparsity) | ||
H = convert.(eltype(x), hes_cache.sparsity) | ||
numauto_color_hessian!(H, f, x, hes_cache) | ||
return H | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
## Hessian tests | ||
using SparsityDetection, SparseDiffTools | ||
using ForwardDiff | ||
using LinearAlgebra, SparseArrays | ||
|
||
function fscalar(x) | ||
return -dot(x, x) + 2 * x[2] * x[3]^2 | ||
end | ||
|
||
x = randn(5) | ||
sparsity = hessian_sparsity(fscalar, x) | ||
colors = matrix_colors(tril(sparsity)) | ||
ncolors = maximum(colors) | ||
D = hcat([float.(i .== colors) for i in 1:ncolors]...) | ||
buffer = similar(D) | ||
G1 = zero(x) | ||
G2 = zero(x) | ||
|
||
buffers_tup = SparseDiffTools.make_hessian_buffers(colors, x) | ||
@test buffers_tup.ncolors == ncolors | ||
@test buffers_tup.D == D | ||
@test size(buffers_tup.buffer) == size(buffer) | ||
@test eltype(buffers_tup.buffer) == eltype(buffer) | ||
@test typeof(buffers_tup.buffer) == typeof(buffer) | ||
@test size(buffers_tup.G1) == size(G1) | ||
@test eltype(buffers_tup.G1) == eltype(G1) | ||
@test size(buffers_tup.G2) == size(G2) | ||
@test eltype(buffers_tup.G2) == eltype(G2) | ||
|
||
|
||
gconfig = ForwardDiff.GradientConfig(fscalar, x) | ||
g(x) = ForwardDiff.gradient(fscalar, x) # allocating | ||
g!(G, x, gconfig) = ForwardDiff.gradient!(G, fscalar, x, gconfig) # non-allocating | ||
|
||
hescache1 = ForwardColorHesCache(sparsity, colors, ncolors, D, buffer, g!, gconfig, G1, G2) | ||
hescache2 = ForwardColorHesCache(fscalar, x, colors, sparsity, g!) | ||
hescache3 = ForwardColorHesCache(fscalar, x, colors, sparsity) | ||
# custom gradient function | ||
hescache4 = ForwardColorHesCache(fscalar, x, colors, sparsity, | ||
(G, x) -> ForwardDiff.gradient!(G, fscalar, x)) | ||
hescache5 = ForwardColorHesCache(fscalar, x) | ||
# custom gradient has to have 2 or 3 arguments... | ||
@test_throws ArgumentError ForwardColorHesCache(fscalar, x, colors, sparsity, (a) -> 1.0) | ||
@test_throws ArgumentError ForwardColorHesCache(fscalar, x, colors, sparsity, (a, b, c, d) -> 1.0) | ||
# ...and needs to accept (Vector, Vector, ForwardDiff.GradientConfig) | ||
@test_throws ArgumentError ForwardColorHesCache(fscalar, x, colors, sparsity, (a::Int, b::Int) -> 1.0,) | ||
@test_throws ArgumentError ForwardColorHesCache(fscalar, x, colors, sparsity, (a::Int, b::Int, c::Int) -> 1.0) | ||
|
||
for name in [:sparsity, :colors, :ncolors, :D] | ||
@eval @test hescache1.$name == hescache2.$name | ||
@eval @test hescache1.$name == hescache3.$name | ||
@eval @test hescache1.$name == hescache4.$name | ||
# hescache5 is the default dense version, so only first axis will match | ||
@eval @test size(hescache1.$name, 1) == size(hescache5.$name, 1) | ||
end | ||
for name in [:buffer, :G1, :G2] | ||
@eval @test size(hescache1.$name) == size(hescache2.$name) | ||
@eval @test size(hescache1.$name) == size(hescache3.$name) | ||
@eval @test size(hescache1.$name) == size(hescache4.$name) | ||
# hescache5 is the default dense version, so only first axis will match | ||
@eval @test size(hescache1.$name, 1) == size(hescache5.$name, 1) | ||
|
||
@eval @test eltype(hescache1.$name) == eltype(hescache2.$name) | ||
@eval @test eltype(hescache1.$name) == eltype(hescache3.$name) | ||
@eval @test eltype(hescache1.$name) == eltype(hescache4.$name) | ||
@eval @test eltype(hescache1.$name) == eltype(hescache5.$name) | ||
end | ||
|
||
Hforward = ForwardDiff.hessian(fscalar, x) | ||
for (i, hescache) in enumerate([hescache1, hescache2, hescache3, hescache4, hescache5]) | ||
H = numauto_color_hessian(fscalar, x, colors, sparsity) | ||
H1 = numauto_color_hessian(fscalar, x, hescache) | ||
H2 = numauto_color_hessian(fscalar, x) | ||
@test all(isapprox.(Hforward, H, rtol=1e-6)) | ||
@test all(isapprox.(H, H1, rtol=1e-6)) | ||
@test all(isapprox.(H2, H1, rtol=1e-6)) | ||
|
||
H1 = similar(H) | ||
numauto_color_hessian!(H1, fscalar, x, collect(hescache.colors), hescache.sparsity) | ||
@test all(isapprox.(H1, H)) | ||
|
||
numauto_color_hessian!(H2, fscalar, x) | ||
@test all(isapprox.(H2, H)) | ||
|
||
numauto_color_hessian!(H1, fscalar, x, hescache) | ||
@test all(isapprox.(H1, H)) | ||
|
||
numauto_color_hessian!(H1, fscalar, x, hescache, safe=false) | ||
@test all(isapprox.(H1, H)) | ||
|
||
# the following tests usually pass, but once in a while don't (it's not a big difference | ||
# in timing on these small matrices, and sometimes its less than the timing variability). | ||
# Commenting out for now to avoid rare stochastic test failures. | ||
# # confirm unsafe is faster | ||
# t_safe = minimum(@elapsed(numauto_color_hessian!(H1, fscalar, x, hescache, safe=true)) | ||
# for _ in 1:100) | ||
# t_unsafe = minimum(@elapsed(numauto_color_hessian!(H1, fscalar, x, hescache, safe=false)) | ||
# for _ in 1:100) | ||
# @test t_unsafe <= t_safe | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.