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

Python optimizer iteration hook #1127

Merged
merged 10 commits into from
May 13, 2022
Merged

Conversation

gchenfc
Copy link
Member

@gchenfc gchenfc commented Mar 8, 2022

In c++ there's an iteration hook that can be called on each iteration of the optimizer for logging/debugging purposes. Previously it wasn't wrapped in python.

It's a super easy addition to wrap in python thanks to the wonderful lambda's machinery of pybind11 / gtwrap.

While I was adding a unit test for the iteration hook, I also minorly refactored the python nonlinear optimizer unit test to put each test in its own function so that, in the event any fail, unittest will print out more obviously which one was the one that failed.

@gchenfc gchenfc added python Related to python wrapper quick-review Quick and easy PR to review labels Mar 8, 2022
@dellaert
Copy link
Member

How does it relate to the exiting logging_optimizer.py?

@gchenfc
Copy link
Member Author

gchenfc commented Mar 10, 2022

@dellaert Oh I didn't realize logging_optimizer.py existed.

Although I think logging_optimizer would have done what I wanted, I do think this has a couple (minor) merits:

  • this proposed change is a one-line addition to the .i file to enable the "real" IterationHook built-in to the c++ NonlinearOptimizationParams. I would consider this stylistically the "right" way to do things since the iteration hook functionality already exists in C++, and logging_optimizer feels like a workaround to add an iteration hook to python without needing to add callback support to the python wrapper.
  • I think this is less hacky, specifically in regards to the convergence checking criterion for different optimizers since logging_optimizer.py is re-implementing the stopping criterion in python. In contrast, this runs the optimizer as normal so we don't need any special code to check convergence criteria etc. Maybe, this might be easier to use with e.g. iSAM as well since it still works when the optimizer is called internally in c++, but I haven't checked to make sure
  • This might be more efficient (not sure, haven't benchmarked) since the iterating loop is happening in c++ instead of python.

One downside: the c++ iterationHook uses callback function arguments (iteration #, errorBefore, errorAfter) whereas the logging_optimizer uses arguments (optimizer, currentError). I found that having access to the optimizer object can be convenient, though not strictly necessary since it can just be captured in the callback function.

Usage

This proposed PR:

optimization_history = []
def iteration_hook(iter, error_before, error_after):
    optimization_history.append(gtsam.Values(optimizer.values()))

params = gtsam.LevenbergMarquardtParams()
params.iterationHook = iteration_hook

optimizer = gtsam.LevenbergMarquardtOptimizer(graph, initial_values, params)
sol = optimizer.optimize()

logging_optimizer.py:

optimization_history = []
def iteration_hook(optimizer, error):
    optimization_history.append(gtsam.Values(optimizer.values()))

params = gtsam.LevenbergMarquardtParams()

optimizer = gtsam.LevenbergMarquardtOptimizer(graph, initial_values, params)
sol = gtsam.utils.logging_optimizer.gtsam_optimize(optimizer, params, iteration_hook)

@dellaert
Copy link
Member

Ok, that’s cool! Maybe it makes sense then to in this PR also modify logging_optimizer.py to use the c++ version?

@dellaert
Copy link
Member

@gchenfc ping

@varunagrawal varunagrawal added this to the GTSAM 4.2 milestone Apr 19, 2022
@gchenfc
Copy link
Member Author

gchenfc commented Apr 19, 2022

Quick timing benchmark (SFMExample_bal.py, with dubrovnik-16-22106-pre) to make sure there's no timing overhead:

  • Original: 18.22s
  • Compiling with this change in wrapper but not actually using the logging functionality: 18.71s
  • Using this logging functionality (with pass in hook): 18.55s
  • Using this logging functionality (with counter in hook): 18.29s

Seems they're all comparable and within a margin of error.

@gchenfc
Copy link
Member Author

gchenfc commented Apr 19, 2022

FYI, logging_optimizer.optimize isn't using params.iterationHook because we don't have access to the params object.

logging_optimizer.gtsam_optimize does use params.iterationHook, but the params that is passed in must be the same as the one that was used to construct optimizer, which seems like a pretty reasonable assumption IMO.

Allowing non-const access to the params of an optimizer object would require adding a non-const "getter" virtual function and implementing for every optimizer.

@gchenfc gchenfc requested review from dellaert and removed request for dellaert April 19, 2022 20:14
Copy link
Member

@dellaert dellaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

@gchenfc
Copy link
Member Author

gchenfc commented Apr 20, 2022

@dellaert Actually I made a mistake (there's a cmake regeneration dependency missing, #1176 ) and neither of the existing logging functions can easily support using this c++ iteration hook version.

I added a 3rd new function inside gtsam.logging_optimizer.py whose usage syntax is like:

solution = optimize_using(gtsam.LevenbergMarquardtOptimizer, hook)(self.graph, self.initial)

Let me know how you feel about this - if it's undesired then I'll just revert the last commit then merge.

@dellaert
Copy link
Member

dellaert commented May 7, 2022

I think we talked about the solution?

@dellaert
Copy link
Member

ping

@gchenfc gchenfc requested a review from dellaert May 13, 2022 16:19
Copy link
Member

@dellaert dellaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks ! I will merge

@dellaert dellaert merged commit 06bc781 into develop May 13, 2022
@dellaert dellaert deleted the feature/python/iterationHook branch May 13, 2022 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
python Related to python wrapper quick-review Quick and easy PR to review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants