-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
HSGP misc fixes #7342
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
Merged
Merged
HSGP misc fixes #7342
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
639e3b2
fix docstrings, fix centering calculations
bwengals 2b6ab5a
Change Xs to X, because the s indicated scaled, which we dont have to…
bwengals 54bc64e
fix test fails
bwengals 4d96c28
remove c, HSGPParams tuple, fix docstring
bwengals 7640a18
add a bit more detail to docstring of set_boundary
bwengals 0ad3f9c
Compute S with more generic formula for radius
AlexAndorra 5f760b9
Compute S with more generic formula for radius
AlexAndorra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,23 +96,31 @@ class HSGPParams(NamedTuple): | |
|
||
|
||
def approx_hsgp_hyperparams( | ||
x: np.ndarray, lengthscale_range: list[float], cov_func: str | ||
x_range: list[float], lengthscale_range: list[float], cov_func: str | ||
) -> HSGPParams: | ||
"""Utility function that uses heuristics to recommend minimum `m` and `c` values, | ||
based on recommendations from Ruitort-Mayol et. al. | ||
|
||
In practice, you need to choose `c` large enough to handle the largest lengthscales, | ||
and `m` large enough to accommodate the smallest lengthscales. | ||
and `m` large enough to accommodate the smallest lengthscales. Use your prior on the | ||
lengthscale as guidance for setting the prior range. For example, if you believe | ||
that 95% of the prior mass of the lengthscale is between 1 and 5, set the | ||
`lengthscale_range` to be [1, 5], or maybe a touch wider. | ||
|
||
Also, be sure to pass in an `x` that is exemplary of the domain not just of your | ||
training data, but also where you intend to make predictions. For instance, if your | ||
training x values are from [0, 10], and you intend to predict from [7, 15], you can | ||
pass in `x_range = [0, 15]`. | ||
|
||
NB: These recommendations are based on a one-dimensional GP. | ||
|
||
Parameters | ||
---------- | ||
x : np.ndarray | ||
The input variable on which the GP is going to be evaluated. | ||
Careful: should be the X values you want to predict over, not *only* the training X. | ||
x_range : list[float] | ||
The range of the x values you intend to both train and predict over. Should be a list with | ||
two elements, [x_min, x_max]. | ||
lengthscale_range : List[float] | ||
The range of the lengthscales. Should be a list with two elements [lengthscale_min, lengthscale_max]. | ||
The range of the lengthscales. Should be a list with two elements, [lengthscale_min, lengthscale_max]. | ||
cov_func : str | ||
The covariance function to use. Supported options are "expquad", "matern52", and "matern32". | ||
|
||
|
@@ -126,7 +134,7 @@ def approx_hsgp_hyperparams( | |
Scaling factor such that L = c * S, where L is the boundary of the approximation. | ||
Increasing it helps approximate larger lengthscales, but may require increasing m. | ||
- `S` : float | ||
The value of `S`, which is half the range of `x`. | ||
The value of `S`, which is half the range, or radius, of `x`. | ||
|
||
Raises | ||
------ | ||
|
@@ -139,11 +147,12 @@ def approx_hsgp_hyperparams( | |
Practical Hilbert Space Approximate Bayesian Gaussian Processes for Probabilistic Programming | ||
""" | ||
if lengthscale_range[0] >= lengthscale_range[1]: | ||
raise ValueError("One of the boundaries out of order") | ||
raise ValueError("One of the `lengthscale_range` boundaries is out of order.") | ||
|
||
if x_range[0] >= x_range[1]: | ||
raise ValueError("One of the `x_range` boundaries is out of order.") | ||
|
||
X_center = (np.max(x, axis=0) - np.min(x, axis=0)) / 2 | ||
Xs = x - X_center | ||
S = np.max(np.abs(Xs), axis=0) | ||
S = (x_range[1] - x_range[0]) / 2.0 | ||
|
||
if cov_func.lower() == "expquad": | ||
a1, a2 = 3.2, 1.75 | ||
|
@@ -286,6 +295,7 @@ def __init__( | |
|
||
if parametrization is not None: | ||
parametrization = parametrization.lower().replace("-", "").replace("_", "") | ||
|
||
if parametrization not in ["centered", "noncentered"]: | ||
raise ValueError("`parametrization` must be either 'centered' or 'noncentered'.") | ||
|
||
|
@@ -321,7 +331,7 @@ def L(self) -> pt.TensorVariable: | |
def L(self, value: TensorLike): | ||
self._L = pt.as_tensor_variable(value) | ||
|
||
def prior_linearized(self, Xs: TensorLike): | ||
def prior_linearized(self, X: TensorLike): | ||
"""Linearized version of the HSGP. Returns the Laplace eigenfunctions and the square root | ||
of the power spectral density needed to create the GP. | ||
|
||
|
@@ -334,7 +344,7 @@ def prior_linearized(self, Xs: TensorLike): | |
|
||
Parameters | ||
---------- | ||
Xs: array-like | ||
X: array-like | ||
Function input values. | ||
|
||
Returns | ||
|
@@ -362,9 +372,9 @@ def prior_linearized(self, Xs: TensorLike): | |
# L = [10] means the approximation is valid from Xs = [-10, 10] | ||
gp = pm.gp.HSGP(m=[200], L=[10], cov_func=cov_func) | ||
|
||
# Set X as Data so it can be mutated later, and then pass it to the GP | ||
X = pm.Data("X", X) | ||
# Pass X to the GP | ||
phi, sqrt_psd = gp.prior_linearized(Xs=X) | ||
phi, sqrt_psd = gp.prior_linearized(X=X) | ||
|
||
# Specify standard normal prior in the coefficients, the number of which | ||
# is given by the number of basis vectors, saved in `n_basis_vectors`. | ||
|
@@ -394,8 +404,8 @@ def prior_linearized(self, Xs: TensorLike): | |
# Important: fix the computation of the midpoint of X. | ||
# If X is mutated later, the training midpoint will be subtracted, not the testing one. | ||
if self._X_center is None: | ||
self._X_center = (pt.max(Xs, axis=0) - pt.min(Xs, axis=0)).eval() / 2 | ||
Xs = Xs - self._X_center # center for accurate computation | ||
self._X_center = (pt.max(X, axis=0) + pt.min(X, axis=0)).eval() / 2 | ||
Xs = X - self._X_center # center for accurate computation | ||
Comment on lines
+399
to
+400
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small suggestion: Could a unit test be added to check this so that we are sure future changes won't break the signs? We could just wrap this little logic into a small axillary function so that we can easily test it. |
||
|
||
# Index Xs using input_dim and active_dims of covariance function | ||
Xs, _ = self.cov_func._slice(Xs) | ||
|
@@ -588,10 +598,11 @@ def __init__( | |
|
||
self._m = m | ||
self.scale = scale | ||
self._X_center = None | ||
|
||
super().__init__(mean_func=mean_func, cov_func=cov_func) | ||
|
||
def prior_linearized(self, Xs: TensorLike): | ||
def prior_linearized(self, X: TensorLike): | ||
"""Linearized version of the approximation. Returns the cosine and sine bases and coefficients | ||
of the expansion needed to create the GP. | ||
|
||
|
@@ -606,8 +617,8 @@ def prior_linearized(self, Xs: TensorLike): | |
|
||
Parameters | ||
---------- | ||
Xs: array-like | ||
Function input values. Assumes they have been mean subtracted or centered at zero. | ||
X: array-like | ||
Function input values. | ||
|
||
Returns | ||
------- | ||
|
@@ -631,15 +642,9 @@ def prior_linearized(self, Xs: TensorLike): | |
# m=200 means 200 basis vectors | ||
gp = pm.gp.HSGPPeriodic(m=200, scale=scale, cov_func=cov_func) | ||
|
||
# Order is important. First calculate the mean, then make X a shared variable, | ||
# then subtract the mean. When X is mutated later, the correct mean will be | ||
# subtracted. | ||
X_mean = np.mean(X, axis=0) | ||
X = pm.MutableData("X", X) | ||
Xs = X - X_mean | ||
|
||
# Pass the zero-subtracted Xs in to the GP | ||
(phi_cos, phi_sin), psd = gp.prior_linearized(Xs=Xs) | ||
# Set X as Data so it can be mutated later, and then pass it to the GP | ||
X = pm.Data("X", X) | ||
(phi_cos, phi_sin), psd = gp.prior_linearized(X=X) | ||
|
||
# Specify standard normal prior in the coefficients. The number of which | ||
# is twice the number of basis vectors minus one. | ||
|
@@ -666,6 +671,13 @@ def prior_linearized(self, Xs: TensorLike): | |
with model: | ||
ppc = pm.sample_posterior_predictive(idata, var_names=["f"]) | ||
""" | ||
# Important: fix the computation of the midpoint of X. | ||
# If X is mutated later, the training midpoint will be subtracted, not the testing one. | ||
if self._X_center is None: | ||
self._X_center = (pt.max(X, axis=0) + pt.min(X, axis=0)).eval() / 2 | ||
Xs = X - self._X_center # center for accurate computation | ||
|
||
# Index Xs using input_dim and active_dims of covariance function | ||
Xs, _ = self.cov_func._slice(Xs) | ||
|
||
phi_cos, phi_sin = calc_basis_periodic(Xs, self.cov_func.period, self._m, tl=pt) | ||
|
@@ -688,9 +700,7 @@ def prior(self, name: str, X: TensorLike, dims: str | None = None): # type: ign | |
dims: None | ||
Dimension name for the GP random variable. | ||
""" | ||
self._X_mean = pt.mean(X, axis=0) | ||
|
||
(phi_cos, phi_sin), psd = self.prior_linearized(X - self._X_mean) | ||
(phi_cos, phi_sin), psd = self.prior_linearized(X) | ||
|
||
m = self._m | ||
self._beta = pm.Normal(f"{name}_hsgp_coeffs_", size=(m * 2 - 1)) | ||
|
@@ -707,7 +717,7 @@ def prior(self, name: str, X: TensorLike, dims: str | None = None): # type: ign | |
|
||
def _build_conditional(self, Xnew): | ||
try: | ||
beta, X_mean = self._beta, self._X_mean | ||
beta, X_center = self._beta, self._X_center | ||
|
||
except AttributeError: | ||
raise ValueError( | ||
|
@@ -716,7 +726,9 @@ def _build_conditional(self, Xnew): | |
|
||
Xnew, _ = self.cov_func._slice(Xnew) | ||
|
||
phi_cos, phi_sin = calc_basis_periodic(Xnew - X_mean, self.cov_func.period, self._m, tl=pt) | ||
phi_cos, phi_sin = calc_basis_periodic( | ||
Xnew - X_center, self.cov_func.period, self._m, tl=pt | ||
) | ||
m = self._m | ||
J = pt.arange(0, m, 1) | ||
# rescale basis coefficients by the sqrt variance term | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.