Skip to content

Commit 33d9282

Browse files
Avoid extra objective evaluation in OptimizationOptimisers
Evaluate the objective through the optimizer loop without a discarded pre-loop call, so stochastic objectives keep the expected RNG sequence and reported evaluation counts match actual work. Preserve positive fractional maxiters values that round to zero by evaluating the initial objective only when the loop performs no iterations, and count that fallback evaluation. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
1 parent 876ddf6 commit 33d9282

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

lib/OptimizationOptimisers/Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Reexport = "1.2.2"
2424
SciMLBase = "2.122.1, 3"
2525
julia = "1.10"
2626
ComponentArrays = "0.15"
27+
CommonSolve = "0.2"
2728
ForwardDiff = "0.10, 1"
2829
Lux = "1"
2930
MLDataDevices = "1"
@@ -38,6 +39,7 @@ SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283"
3839
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
3940
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
4041
ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
42+
CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
4143
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
4244
Lux = "b2108857-7c20-44ae-9111-449ecde12c47"
4345
MLDataDevices = "7e8f7934-dd98-4c1a-8fe8-92b47a384d40"
@@ -48,4 +50,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4850
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
4951

5052
[targets]
51-
test = ["SciMLTesting", "ComponentArrays", "ForwardDiff", "Lux", "MLDataDevices", "MLUtils", "Random", "Test", "Zygote", "Printf", "SafeTestsets", "Pkg"]
53+
test = ["SciMLTesting", "ComponentArrays", "CommonSolve", "ForwardDiff", "Lux", "MLDataDevices", "MLUtils", "Random", "Test", "Zygote", "Printf", "SafeTestsets", "Pkg"]

lib/OptimizationOptimisers/src/OptimizationOptimisers.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function SciMLBase.__solve(cache::OptimizationCache{O}) where {O <: AbstractRule
6161
G = copy(θ)
6262

6363
local x, min_err, min_θ
64-
x = cache.f(cache.u0, first(data))
64+
x = nothing
6565
min_err = typemax(eltype(real(cache.u0))) #dummy variables
6666
min_opt = 1
6767
min_θ = cache.u0
@@ -151,6 +151,10 @@ function SciMLBase.__solve(cache::OptimizationCache{O}) where {O <: AbstractRule
151151
)
152152
end
153153
end
154+
if isnothing(x)
155+
x = cache.f(cache.u0, first(data))
156+
fevals += 1
157+
end
154158
cache.progress && @logmsg(
155159
LogLevel(-1), "Optimization",
156160
_id = progress_id, message = "Done", progress = 1.0

lib/OptimizationOptimisers/test/core_tests.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using OptimizationOptimisers, ForwardDiff, OptimizationBase
2+
using CommonSolve: solve
3+
using Optimisers
4+
using SciMLBase: OptimizationFunction, OptimizationProblem
25
using Test
36
using Zygote
47
using Lux, MLUtils, Random, ComponentArrays, Printf, MLDataDevices
@@ -117,6 +120,36 @@ using Lux, MLUtils, Random, ComponentArrays, Printf, MLDataDevices
117120
)
118121
end
119122

123+
@testset "Objective evaluations" begin
124+
objective_calls = Ref(0)
125+
fg_calls = Ref(0)
126+
function counted_objective(x, p)
127+
objective_calls[] += 1
128+
return sum(abs2, x)
129+
end
130+
function counted_fg!(G, x, p)
131+
fg_calls[] += 1
132+
G .= 2 .* x
133+
return sum(abs2, x)
134+
end
135+
136+
optf = OptimizationFunction(counted_objective; fg = counted_fg!)
137+
prob = OptimizationProblem(optf, ones(2), nothing)
138+
sol = solve(prob, Optimisers.Adam(); maxiters = 3, save_best = false)
139+
140+
@test sol.stats.iterations == 3
141+
@test fg_calls[] == sol.stats.iterations
142+
@test objective_calls[] == 0
143+
144+
objective_calls[] = 0
145+
fg_calls[] = 0
146+
sol = solve(prob, Optimisers.Adam(); maxiters = 0.4, save_best = false)
147+
148+
@test sol.stats.iterations == 0
149+
@test sol.stats.fevals == objective_calls[] == 1
150+
@test fg_calls[] == 0
151+
end
152+
120153
@test_throws ArgumentError sol = solve(prob, Optimisers.Adam())
121154
end
122155

0 commit comments

Comments
 (0)