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

Add a single normal form for sum-product contractions #157

Merged
merged 89 commits into from
Sep 17, 2019

Conversation

eb8680
Copy link
Member

@eb8680 eb8680 commented Jul 13, 2019

Resolves #156

As outlined in #156 I'm using this as a staging branch for the refactoring PRs associated with the proposed changes there:

Original description:
This PR takes a first step towards the goals in #156 by adding two things:

  1. A Contraction term representing finitary sum-product operations that takes advantage of multipledispatch.Variadic for pattern matching. This is also necessary for Implement recognition of affine transforms #72.
  2. A normalize interpretation that uses associative and distributive properties to put linear expressions into something like DNF, grouping operations into single Contraction terms where possible.

As a demonstration, I have included patterns and tests illustrating how Joint is equivalent to interleaving eager and normalize.

Tested:

  • I copied smoke tests and test cases verbatim from test_joint.py, test_gaussian.py, test_affine.py and test_einsum.py

@fritzo
Copy link
Member

fritzo commented Jul 17, 2019

Looks like the test failure is an unreduced 0. + Gaussian(...), the same error we noticed last night in #161. This may be due to some unrelated missing pattern only triggered after benign refactoring of patterns in this PR.

Copy link
Member

@fritzo fritzo left a comment

Choose a reason for hiding this comment

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

As we discussed offline, this looks great. What is your plan for removing the funsors that this PR obsoletes? Do you want to delete that code in this PR, or break the process into two staged PRs?

@eb8680
Copy link
Member Author

eb8680 commented Jul 17, 2019 via email

@eb8680
Copy link
Member Author

eb8680 commented Sep 16, 2019

I believe that is due to either a missing pattern or overly-eager evaluation

It's probably a missing pattern for a Cat of Joint-like Contractions



@dispatch(str, str, Variadic[Gaussian, Joint])
@dispatch(str, str, Variadic[(Gaussian, GaussianMixture)])
Copy link
Member

Choose a reason for hiding this comment

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

Is this pattern now too narrow?

Copy link
Member Author

Choose a reason for hiding this comment

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

It shouldn't be, according to this check in master

Copy link
Member Author

@eb8680 eb8680 Sep 16, 2019

Choose a reason for hiding this comment

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

I think this might be caused by normalize being a bit too aggressive with pushing down exp, so that we get Cats of terms that look like Contraction[..., Tuple[Unary[Exp, Gaussian], Tensor]]

Copy link
Member

Choose a reason for hiding this comment

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

Can we simply remove Unary[Contraction] simplification from normalize?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, it might be easier to add normalize patterns that prevent oversimplification e.g.

@normalize.register(Unary, ExpOp, GaussianMixture)
def normalize_unary_joint(op, arg):
    return None

Copy link
Member

Choose a reason for hiding this comment

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

I guess I don't see why we'd want to move unary inside. Unary ops are pointwise and are generally cheaper to apply outside of reductions.

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess I don't see why we'd want to move unary inside

The main use case here (as in optimize and Contract/Integrate already in master) is in rewriting integrals of large joint distributions. Pushing the unary ops down to the leaves makes pattern matching easier, simplifies the normal form and lets us reuse the optimizer for integrals. In these situations it's unlikely that unary ops would actually be evaluated, since they would eventually be rewritten back into Integrates, KL divergences, etc.

I don't see a reason to change this unless we also want to completely decouple Integrate from Contraction (i.e. never rewrite Integrate to Contraction or vice versa), which I suppose we could discuss separately.

funsor/joint.py Outdated Show resolved Hide resolved
Makefile Outdated Show resolved Hide resolved
funsor/cnf.py Show resolved Hide resolved
funsor/delta.py Outdated Show resolved Hide resolved
funsor/delta.py Outdated Show resolved Hide resolved
funsor/delta.py Outdated Show resolved Hide resolved
funsor/delta.py Outdated Show resolved Hide resolved
funsor/gaussian.py Outdated Show resolved Hide resolved
funsor/optimizer.py Show resolved Hide resolved
funsor/optimizer.py Outdated Show resolved Hide resolved
funsor/pyro/convert.py Outdated Show resolved Hide resolved
funsor/pyro/distribution.py Show resolved Hide resolved
test/pyro/test_convert.py Show resolved Hide resolved
@fritzo
Copy link
Member

fritzo commented Sep 17, 2019

It seems like you really want Reduce(Finitary) rather than this monolithic Contraction and NullOp. I think that would be much clearer to library users and contributors.

Could we replace Binary with Finitary?

@fritzo
Copy link
Member

fritzo commented Sep 17, 2019

It is also tempting to replace MultiDelta with a Finitary[Delta] and require a normal form like

Reduce[SumOp, Finitary[ProdOp, Finitary[ProdOp, Delta], Tensor, Gaussian]]

@eb8680
Copy link
Member Author

eb8680 commented Sep 17, 2019 via email

@eb8680
Copy link
Member Author

eb8680 commented Sep 17, 2019 via email

funsor/joint.py Outdated Show resolved Hide resolved
funsor/pyro/convert.py Outdated Show resolved Hide resolved
Copy link
Member

@fritzo fritzo left a comment

Choose a reason for hiding this comment

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

Well it works on my bart example now.

Let's try to keep future PRs small and targeted.

@eb8680
Copy link
Member Author

eb8680 commented Sep 17, 2019

Let's try to keep future PRs small and targeted.

Agreed, but I'm not sure replacing Joint could have been done in a much more small and targeted way. If this version is too invasive or is continuing to break experiments, let's just think of it as a protoype, close it, and revisit the ideas in the form of a design doc at some point after the first release.

@fritzo
Copy link
Member

fritzo commented Sep 17, 2019

Let's just merge this and forge ahead.

@eb8680 eb8680 merged commit 955e9cf into master Sep 17, 2019
jpchen pushed a commit that referenced this pull request Sep 19, 2019
@fritzo fritzo mentioned this pull request Sep 23, 2019
1 task
jpchen added a commit that referenced this pull request Sep 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unify multilinear operations
2 participants