Skip to content

Allow Stationary covariance functions to use user defined distance functions #6965

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

bwengals
Copy link
Contributor

@bwengals bwengals commented Oct 20, 2023

This PR adds an optional argument to any covariance function that inherits from Stationary called square_dist. New feature and existing model code shouldn't be affected.

Checklist

Major / Breaking Changes

  • None

New features

  • Allow user to pass different distance functions

Bugfixes

  • None

Documentation

  • None

Maintenance

  • None

📚 Documentation preview 📚: https://pymc--6965.org.readthedocs.build/en/6965/

@codecov
Copy link

codecov bot commented Oct 20, 2023

Codecov Report

Merging #6965 (d0cd26c) into main (5f29b25) will decrease coverage by 4.35%.
Report is 5 commits behind head on main.
The diff coverage is 100.00%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #6965      +/-   ##
==========================================
- Coverage   92.12%   87.78%   -4.35%     
==========================================
  Files         100      100              
  Lines       16859    16892      +33     
==========================================
- Hits        15531    14828     -703     
- Misses       1328     2064     +736     
Files Coverage Δ
pymc/gp/cov.py 98.01% <100.00%> (+0.01%) ⬆️

... and 12 files with indirect coverage changes

@bwengals bwengals assigned bwengals and lucianopaz and unassigned bwengals Oct 20, 2023
cov2 = pm.gp.cov.Matern32(1, ls=1, square_dist=square_dist)
K1 = cov1(X).eval()
K2 = cov2(X, X).eval()
npt.assert_allclose(K1, K2, atol=1e-5)
Copy link
Member

Choose a reason for hiding this comment

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

maybe also test you can get something different from the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good call

Copy link
Member

Choose a reason for hiding this comment

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

I agree with this

@ricardoV94 ricardoV94 added enhancements GP Gaussian Process labels Oct 21, 2023
Copy link
Member

@lucianopaz lucianopaz left a comment

Choose a reason for hiding this comment

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

Thanks @bwengals. I'm not sure if the PR, as it stands, actually customizes the square_dist callable? I might have missed something, but I didn't see where it got attached to the object.

There is one theoretical thing that we need to be aware of and we could add into the docstring of square_dist: not all distance metrics yield valid covariance kernels. There's a short summary of this here in section 3.1.3. I fell into this trap once when I tried to implement a covariance kernel using the Haversine distance on the surface of a sphere, until I later found out that was wrong.

@@ -518,6 +521,7 @@ def __init__(
ls=None,
ls_inv=None,
active_dims: Optional[IntSequence] = None,
square_dist: Optional[Callable] = 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 can't see where the square_dist argument is being used later. Do you want to attach it to the covariance instance at the end of __init__? As it stands now, it looks like this isn't doing anything. Furthermore, even if you attach it to the object, would that be called instead of the __class__ square_dist accessor?

sqd = -2.0 * pt.dot(X, pt.transpose(Xs)) + (
pt.reshape(X2, (-1, 1)) + pt.reshape(Xs2, (1, -1))
)
Xs = pt.mul(Xs, 1.0 / ls)
Copy link
Member

Choose a reason for hiding this comment

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

Why did you take out the is None case? Can't you just do Xs = X if Xs is None else Xs and have a single branch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved it down to line 564 (and your one-liner is nicer). I moved it because I thought it'd be easier for someone making a different distance function if the inputs didn't have defaults.

cov2 = pm.gp.cov.Matern32(1, ls=1, square_dist=square_dist)
K1 = cov1(X).eval()
K2 = cov2(X, X).eval()
npt.assert_allclose(K1, K2, atol=1e-5)
Copy link
Member

Choose a reason for hiding this comment

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

I agree with this

@bwengals
Copy link
Contributor Author

I'm not sure if the PR, as it stands, actually customizes the square_dist callable?

No.. Sorry for the sloppiness on my end! The test you and @ricardoV94 mentioned would have caught this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancements GP Gaussian Process
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants