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

Direct Hybrid Factor Specification #1805

Merged
merged 36 commits into from
Sep 20, 2024
Merged

Direct Hybrid Factor Specification #1805

merged 36 commits into from
Sep 20, 2024

Conversation

varunagrawal
Copy link
Collaborator

@varunagrawal varunagrawal commented Aug 22, 2024

Add tests which illustrate how we can specify hybrid factors directly instead of defining the BayesNet and calling toFactorGraph on it.

This is useful since it allows us to handle nonlinear functions in the conditionals being converted to factors. This will also help with relinearization, a long standing issue for us in this scheme.

@varunagrawal varunagrawal deleted the branch develop September 5, 2024 13:25
@varunagrawal varunagrawal reopened this Sep 5, 2024
Base automatically changed from working-hybrid to develop September 5, 2024 13:27
@dellaert
Copy link
Member

dellaert commented Sep 5, 2024

I’d like to chat about this more. While the HBN -> HFG is now very clear, the proposed API here is not. Where do the log normalizers come from? What is their correct value? What do they mean? If the answer is: “think about the conditional and its normalizer” then why not use the “likelihood” approach, or just directly add a Hybrid conditional? Let’s chat in person because I think it requires higher bandwidth than comments back and forth…

@varunagrawal varunagrawal changed the base branch from develop to hybrid-error-scalars September 15, 2024 13:47
Base automatically changed from hybrid-error-scalars to develop September 18, 2024 20:17

// Create HybridGaussianFactor
std::vector<GaussianFactorValuePair> factors{
{f0, ComputeLogNormalizer(model0)}, {f1, ComputeLogNormalizer(model1)}};
Copy link
Member

Choose a reason for hiding this comment

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

I'm still very shaky about the semantics of this scalar. I asked a question on the previous PR but I don;t think it was answered. If the scalar simply adds to the error, then is that after squaring and halving? If so, I think this needs to be better documented, with an example in the .h file even. There is also uncertainty about the sign: is the normalizer multiplying or dividing? I think the former, but again: be very clear, also in the docs of ComputeLogNormalizer. And, very important, it has to be consistent everywhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I sent you the doc with the math written up there. From the derivation, we are adding the pre-halved version. You are right that it should be better documented in the .h file. :)

ComputeLogNormalizer returns $\log(2\pi \Sigma^m)$ instead of $\log(\frac{1}{\sqrt{2\pi \Sigma^m}})$, so we should be dividing (or subtracting since it is in log-space). This is actually convenient since we can pull out $-\frac{1}{2}$ in common from both the factor error and the normalizer term. I'm still blown away by how nicely that math worked out.

Copy link
Member

Choose a reason for hiding this comment

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

OK, this answer is problematic to me: when specifying the API for hybrid factor graphs, the normalizer is just an application. Ideally, the API in the .h file is documented like:

In factor graphs the error function typically returns 0.5*|h(x)-z|^2, i.e., the negative log-likelihood for a Gaussian noise model. In hybrid factor graphs we allow *adding* an arbitrary scalar dependent on the discrete assignment. For example, adding a 70/30 mode probability is supported by providing the scalars $-log(.7)$ and $-log(.3)$. Note that adding a common constant will not make any difference in the optimization, so $-log(70)$ and $-log(30)$ work just as well.

Please consider adding the above in HNF and the linear version in HGF. And, not that I am advocating here for adding these scalars terms to the error. When designing the API for the factors, it does not make sense to tell people that a term will be subtracted: we either work in negative log space (we add) or in probability space (we multiply). I see no need to add subtraction into this lexicon.

Copy link
Member

Choose a reason for hiding this comment

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

Happy to chat in person BTW. Invite me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As discussed, I have made all the changes.

@dellaert
Copy link
Member

Please update PR comment?

@@ -45,6 +45,15 @@ using GaussianFactorValuePair = std::pair<GaussianFactor::shared_ptr, double>;
* where the set of discrete variables indexes to
* the continuous gaussian distribution.
*
* In factor graphs the error function typically returns 0.5*|h(x)-z|^2, i.e.,
Copy link
Member

Choose a reason for hiding this comment

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

Here it would be |A*x-b|

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

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.

Very close, but still one main issue:

  • I think the factor 2 is still in the wrong place for the GaussianHybridFactor

Two possibly significant performance issues:

  • unzipping and then undoing the unzip is needlessly expensive.
  • only having the Gaussian logNormalization constant might be very expensive.

If you want I can create a PR with the noiseModel changes if you finish the other things.

@@ -56,7 +56,7 @@ HybridGaussianFactor::Factors augment(
const HybridGaussianFactor::sharedFactor &gf) {
auto jf = std::dynamic_pointer_cast<JacobianFactor>(gf);
if (!jf) return gf;
// If the log_normalizer is 0, do nothing
// If the value is 0, do nothing
if (values(assignment) == 0.0) return gf;
Copy link
Member

Choose a reason for hiding this comment

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

You are calling the potentially expensive values(assignment) twice, so please assign it to a variable. But why unzip the tree and then call values() at all. Can't you just apply on the original tree and get the value for free?

A more problematic issue is that you take the square root. But is this wrong now? Should we not do sqrt(2*value) ? Because the "hidden" value c will be squared and halved: value == 0.5*c^2.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  1. So I need to unzip to normalize the values (so that we don't have negatives, else the sqrt(value) gives a nan). This brings up an interesting point though: should we normalize for the user or should the user pre-normalize? I felt like us doing it makes sense, since the sqrt to hide the variable is something we need to do and the user shouldn't have to worry about those semantics. Thoughts?

  2. We can do 2*value, but that is going to have to also change in various other places like HybridGaussianConditional::likelihood. I think what you suggest is the better thing, keeping in alignment with my previous point.

gtsam/hybrid/tests/testHybridGaussianFactor.cpp Outdated Show resolved Hide resolved
gtsam/hybrid/tests/testHybridNonlinearFactorGraph.cpp Outdated Show resolved Hide resolved
gtsam/linear/NoiseModel.cpp Outdated Show resolved Hide resolved
gtsam/linear/NoiseModel.h Outdated Show resolved Hide resolved
gtsam/linear/NoiseModel.h Outdated Show resolved Hide resolved
gtsam/linear/tests/testNoiseModel.cpp Outdated Show resolved Hide resolved
gtsam/linear/tests/testNoiseModel.cpp Outdated Show resolved Hide resolved
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.

Awesome! Liftoff!

@varunagrawal varunagrawal merged commit 276747c into develop Sep 20, 2024
33 checks passed
@varunagrawal varunagrawal deleted the direct-hybrid-fg branch September 20, 2024 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants