Skip to content

Commit 843899f

Browse files
Add LMTR solver implementation and update benchmarks in Benchmark.jl and Benchmark.tex
1 parent db114d6 commit 843899f

File tree

4 files changed

+69
-15
lines changed

4 files changed

+69
-15
lines changed

paper/examples/Benchmark.jl

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,30 @@ function run_LM_svm!(nls_model, x0; λ = 1.0, atol = 1e-3, rtol = 1e-3, verbose
131131
)
132132
end
133133

134+
function run_LMTR_svm!(nls_model, x0; λ = 1.0, atol = 1e-3, rtol = 1e-3, verbose = 0, sub_kwargs = (;))
135+
reg_nls = RegularizedNLSModel(nls_model, RootNormLhalf(λ))
136+
solver = LMTRSolver(reg_nls)
137+
stats = RegularizedExecutionStats(reg_nls)
138+
RegularizedOptimization.solve!(solver, reg_nls, stats;
139+
x = x0, atol = atol, rtol = rtol, verbose = verbose, sub_kwargs = sub_kwargs)
140+
reset!(nls_model) # Reset counters before timing
141+
reg_nls = RegularizedNLSModel(nls_model, RootNormLhalf(λ))
142+
solver = LMTRSolver(reg_nls)
143+
t = @elapsed RegularizedOptimization.solve!(solver, reg_nls, stats;
144+
x = x0, atol = atol, rtol = rtol, verbose = verbose, sub_kwargs = sub_kwargs)
145+
return (
146+
name = "LMTR (SVM)",
147+
status = string(stats.status),
148+
time = t,
149+
iters = get(stats.solver_specific, :outer_iter, missing),
150+
fevals = neval_residual(nls_model),
151+
gevals = neval_jtprod_residual(nls_model) + neval_jprod_residual(nls_model),
152+
proxcalls = get(stats.solver_specific, :prox_evals, missing),
153+
solution = stats.solution,
154+
final_obj = obj(nls_model, stats.solution)
155+
)
156+
end
157+
134158
function bench_svm!(cfg = CFG)
135159
Random.seed!(cfg.SEED)
136160
model, nls_train, _ = RegularizedProblems.svm_train_model()
@@ -140,6 +164,7 @@ function bench_svm!(cfg = CFG)
140164
(:TR in cfg.RUN_SOLVERS) && push!(results, run_tr_svm!(model, x0; λ = cfg.LAMBDA_L0, qn = cfg.QN_FOR_TR, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, sub_kwargs = cfg.SUB_KWARGS_R2N))
141165
(:R2N in cfg.RUN_SOLVERS) && push!(results, run_r2n_svm!(model, x0; λ = cfg.LAMBDA_L0, qn = cfg.QN_FOR_R2N, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, sub_kwargs = cfg.SUB_KWARGS_R2N))
142166
(:LM in cfg.RUN_SOLVERS) && push!(results, run_LM_svm!(nls_train, x0; λ = cfg.LAMBDA_L0, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, sub_kwargs = cfg.SUB_KWARGS_R2N))
167+
(:LMTR in cfg.RUN_SOLVERS) && push!(results, run_LMTR_svm!(nls_train, x0; λ = cfg.LAMBDA_L0, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, sub_kwargs = cfg.SUB_KWARGS_R2N))
143168

144169
# Print quick summary
145170
println("\n=== SVM: solver comparison ===")
@@ -230,17 +255,17 @@ function run_r2n_nnmf!(model, x0; λ = 1.0, qn = :LBFGS, atol = 1e-3, rtol = 1e-
230255
)
231256
end
232257

233-
function run_LM_nnmf!(nls_model, x0; λ = 1.0, atol = 1e-3, rtol = 1e-3, verbose = 0, selected = nothing)
258+
function run_LM_nnmf!(nls_model, x0; λ = 1.0, atol = 1e-3, rtol = 1e-3, verbose = 0, selected = nothing, sub_kwargs = (;))
234259
reg_nls = RegularizedNLSModel(nls_model, NormL0(λ), selected)
235260
solver = LMSolver(reg_nls)
236261
stats = RegularizedExecutionStats(reg_nls)
237262
RegularizedOptimization.solve!(solver, reg_nls, stats;
238-
x = x0, atol = atol, rtol = rtol, verbose = verbose)
263+
x = x0, atol = atol, rtol = rtol, verbose = verbose, sub_kwargs = sub_kwargs)
239264
reset!(nls_model) # Reset counters before timing
240265
reg_nls = RegularizedNLSModel(nls_model, NormL0(λ), selected)
241266
solver = LMSolver(reg_nls)
242267
t = @elapsed RegularizedOptimization.solve!(solver, reg_nls, stats;
243-
x = x0, atol = atol, rtol = rtol, verbose = verbose)
268+
x = x0, atol = atol, rtol = rtol, verbose = verbose, sub_kwargs = sub_kwargs)
244269
return (
245270
name = "LM (NNMF)",
246271
status = string(stats.status),
@@ -254,6 +279,30 @@ function run_LM_nnmf!(nls_model, x0; λ = 1.0, atol = 1e-3, rtol = 1e-3, verbose
254279
)
255280
end
256281

282+
function run_LMTR_nnmf!(nls_model, x0; λ = 1.0, atol = 1e-3, rtol = 1e-3, verbose = 0, selected = nothing, sub_kwargs = (;))
283+
reg_nls = RegularizedNLSModel(nls_model, NormL0(λ), selected)
284+
solver = LMTRSolver(reg_nls)
285+
stats = RegularizedExecutionStats(reg_nls)
286+
RegularizedOptimization.solve!(solver, reg_nls, stats;
287+
x = x0, atol = atol, rtol = rtol, verbose = verbose, sub_kwargs = sub_kwargs)
288+
reset!(nls_model) # Reset counters before timing
289+
reg_nls = RegularizedNLSModel(nls_model, NormL0(λ), selected)
290+
solver = LMTRSolver(reg_nls)
291+
t = @elapsed RegularizedOptimization.solve!(solver, reg_nls, stats;
292+
x = x0, atol = atol, rtol = rtol, verbose = verbose, sub_kwargs = sub_kwargs)
293+
return (
294+
name = "LMTR (NNMF)",
295+
status = string(stats.status),
296+
time = t,
297+
iters = get(stats.solver_specific, :outer_iter, missing),
298+
fevals = neval_residual(nls_model),
299+
gevals = neval_jtprod_residual(nls_model) + neval_jprod_residual(nls_model),
300+
proxcalls = get(stats.solver_specific, :prox_evals, missing),
301+
solution = stats.solution,
302+
final_obj = obj(nls_model, stats.solution)
303+
)
304+
end
305+
257306
function bench_nnmf!(cfg = CFG2; m = 100, n = 50, k = 5)
258307
Random.seed!(cfg.SEED)
259308

@@ -268,7 +317,8 @@ function bench_nnmf!(cfg = CFG2; m = 100, n = 50, k = 5)
268317
results = NamedTuple[]
269318
(:TR in cfg.RUN_SOLVERS) && push!(results, run_tr_nnmf!(model, x0; λ = cfg.LAMBDA_L0, qn = cfg.QN_FOR_TR, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, sub_kwargs = cfg.SUB_KWARGS_R2N, selected = selected))
270319
(:R2N in cfg.RUN_SOLVERS) && push!(results, run_r2n_nnmf!(model, x0; λ = cfg.LAMBDA_L0, qn = cfg.QN_FOR_R2N, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, sub_kwargs = cfg.SUB_KWARGS_R2N, selected = selected))
271-
(:LM in cfg.RUN_SOLVERS) && push!(results, run_LM_nnmf!(nls_model, x0; λ = cfg.LAMBDA_L0, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, selected = selected))
320+
(:LM in cfg.RUN_SOLVERS) && push!(results, run_LM_nnmf!(nls_model, x0; λ = cfg.LAMBDA_L0, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, selected = selected, sub_kwargs = cfg.SUB_KWARGS_R2N))
321+
(:LMTR in cfg.RUN_SOLVERS) && push!(results, run_LMTR_nnmf!(nls_model, x0; λ = cfg.LAMBDA_L0, atol = cfg.TOL, rtol = cfg.RTOL, verbose = cfg.VERBOSE_RO, selected = selected, sub_kwargs = cfg.SUB_KWARGS_R2N))
272322

273323
println("\n=== NNMF: solver comparison ===")
274324
for m in results

paper/examples/Benchmark.tex

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
\begin{tabular}{lcrrrrr}
22
\hline
33
\textbf{Method} & \textbf{Status} & \textbf{$t$($s$)} & \textbf{$\#f$} & \textbf{$\#\nabla f$} & \textbf{$\#prox$} & \textbf{Objective} \\\hline
4-
TR (LSR1, SVM) & first\_order & 3.4768 & 347 & 291 & 4037 & 179.837 \\
5-
R2N (LSR1, SVM) & first\_order & 1.728 & 185 & 101 & 27932 & 192.493 \\
6-
LM (SVM) & first\_order & 18.0861 & 6 & 2876 & 1001 & 201.186 \\
4+
TR (LSR1, SVM) & first\_order & 3.9349 & 347 & 291 & 4037 & 179.837 \\
5+
R2N (LSR1, SVM) & first\_order & 1.9511 & 185 & 101 & 27932 & 192.493 \\
6+
LM (SVM) & first\_order & 19.7826 & 6 & 2876 & 1001 & 201.186 \\
7+
LMTR (SVM) & first\_order & 12.4967 & 11 & 1614 & 432 & 188.274 \\
78
\hline
8-
TR (LBFGS, NNMF) & first\_order & 0.1013 & 42 & 40 & 3160 & 976.06 \\
9-
R2N (LBFGS, NNMF) & first\_order & 0.4974 & 169 & 107 & 17789 & 411.727 \\
10-
LM (NNMF) & first\_order & 0.4654 & 15 & 27703 & 12320 & 131.183 \\\hline
9+
TR (LBFGS, NNMF) & first\_order & 0.1014 & 42 & 40 & 3160 & 976.06 \\
10+
R2N (LBFGS, NNMF) & first\_order & 0.4913 & 169 & 107 & 17789 & 411.727 \\
11+
LM (NNMF) & first\_order & 0.1157 & 14 & 7042 & 2601 & 131.184 \\
12+
LMTR (NNMF) & first\_order & 0.0697 & 9 & 4066 & 1435 & 131.186 \\\hline
1113
\end{tabular}

paper/examples/comparison-config.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Base.@kwdef mutable struct Config
88
MAXIT_PANOC::Int = 10000
99
VERBOSE_PANOC::Bool = false
1010
VERBOSE_RO::Int = 0
11-
RUN_SOLVERS::Vector{Symbol} = [:LM, :TR, :R2N] # mutable
11+
RUN_SOLVERS::Vector{Symbol} = [:LMTR, :LM, :TR, :R2N] # mutable
1212
QN_FOR_TR::Symbol = :LSR1
1313
QN_FOR_R2N::Symbol = :LBFGS
1414
SUB_KWARGS_R2N::NamedTuple = (; max_iter = 200)

paper/paper.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ solver = LMSolver(reg_nls) # Choose solver
167167

168168
## Numerical results
169169

170-
We compare **TR**, **R2N**, and **LM** from our library.
170+
We compare **TR**, **R2N**, **LM** and **LMTR** from our library.
171171

172172
We report the following solver statistics in the table: the convergence status of each solver, the number of evaluations of $f$, the number of evaluations of $\nabla f$, the number of proximal operator evaluations, the elapsed time in seconds and the final objective value.
173173
On the SVM and NNMF problems, we use limited-memory SR1 and BFGS Hessian approximations, respectively.
@@ -180,10 +180,12 @@ The subproblem solver is **R2**.
180180
All methods successfully reduced the optimality measure below the specified tolerance of $10^{-4}$, and thus converged to an approximate first-order stationary point.
181181
Note that, the final objective values differ due to the nonconvexity of the problems.
182182

183-
- **SVM with $\ell^{1/2}$ penalty:** **R2N** is the fastest, requiring the fewest function and gradient evaluations compared to **TR**.
183+
- **SVM with $\ell^{1/2}$ penalty:** **R2N** is the fastest, requiring the fewest gradient evaluations compared to all the other solvers.
184184
However, it requires more proximal evaluations, but these are inexpensive.
185-
**LM** requires the fewest function evaluations, but many gradient evaluations, and is the slowest.
186-
- **NNMF with constrained $\ell_0$ penalty:** **TR** is the fastest, and requires a fewer number of function and gradient evaluations than **R2N**. **LM** is competitive in terms of function calls but incurs many Jacobian–vector products; it nevertheless achieves the lowest objective value.
185+
**LMTR** and **LM** require the fewest function evaluations, but incur many Jacobian–vector products, and are the slowest.
186+
Note that here, **LMTR** achieves the lowest objective value.
187+
- **NNMF with constrained $\ell_0$ penalty:** **LMTR** is the fastest, and requires a fewer number of function evaluations than all the other solvers. Followed by **TR** which is the second fastest and requires the fewest gradient evaluations, however it achieves the highest objective value.
188+
Note that both **LMTR** and **LM** achieve the lowest objective value.
187189

188190
Additional tests (e.g., other regularizers, constraint types, and scaling dimensions) have also been conducted, and a full benchmarking campaign is currently underway.
189191

0 commit comments

Comments
 (0)