Skip to content

Commit

Permalink
Merge pull request #19 from JohnGoertz/develop
Browse files Browse the repository at this point in the history
Minor refactors and allows toggling ARD kernel
  • Loading branch information
JohnGoertz authored Feb 28, 2022
2 parents 7ac2a47 + 4e36623 commit c3d1716
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 25 deletions.
10 changes: 5 additions & 5 deletions gumbi/arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def __getitem__(self, item):
default = super().__getitem__(item)
if isinstance(item, str):
arrays = {item: default}
elif isinstance(item, int) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
elif isinstance(item, (int, np.int32, np.int64)) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
arrays = {name: value for name, value in zip(default.dtype.names, default)}
elif isinstance(item, slice):
arrays = {layer.names[0]: layer.values() for layer in default.as_list()}
Expand Down Expand Up @@ -380,7 +380,7 @@ def __getitem__(self, item):
default = super(LayeredArray, self).__getitem__(item)
if isinstance(item, str):
arrays = {item: default}
elif isinstance(item, int) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
elif isinstance(item, (int, np.int32, np.int64)) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
arrays = {name: value for name, value in zip(default.dtype.names, default)}
elif isinstance(item, slice):
arrays = {layer.names[0]: layer.values() for layer in default.as_list()}
Expand Down Expand Up @@ -733,7 +733,7 @@ def __repr__(self):

def __getitem__(self, item):
default = super().__getitem__(item)
if isinstance(item, int) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
if isinstance(item, (int, np.int32, np.int64)) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
arrays = {name: value for name, value in zip(default.dtype.names, default)}
elif isinstance(item, slice):
# arrays = {layer.names[0]: layer.values() for layer in default.as_list()}
Expand Down Expand Up @@ -1077,7 +1077,7 @@ def _warn_if_poorly_defined(self):

def __getitem__(self, item):
default = super(UncertainArray, self).__getitem__(item)
if isinstance(item, int) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
if isinstance(item, (int, np.int32, np.int64)) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
arrays = {name: value for name, value in zip(default.dtype.names, default)}
elif isinstance(item, slice):
# arrays = {layer.names[0]: layer.values() for layer in default.as_list()}
Expand Down Expand Up @@ -1257,7 +1257,7 @@ def __repr__(self):

def __getitem__(self, item):
default = super().__getitem__(item)
if isinstance(item, int) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
if isinstance(item, (int, np.int32, np.int64)) or (isinstance(item, tuple) and all(isinstance(val, int) for val in item)):
arrays = [self.get(name)[item] for name in self.names]
elif isinstance(item, slice):
# arrays = [self.get(name)[item] for name in self.names]
Expand Down
42 changes: 28 additions & 14 deletions gumbi/regression/pymc3/GP.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def __init__(self, dataset: DataSet, outputs=None, seed=2021):

def fit(self, outputs=None, linear_dims=None, continuous_dims=None, continuous_levels=None, continuous_coords=None,
categorical_dims=None, categorical_levels=None, additive=False, seed=None, heteroskedastic_inputs=False,
heteroskedastic_outputs=True, sparse=False, n_u=100, **MAP_kwargs):
heteroskedastic_outputs=True, sparse=False, n_u=100, ARD=True, **MAP_kwargs):
"""Fits a GP surface
Parses inputs, compiles a Pymc3 model, then finds the MAP value for the hyperparameters. `{}_dims` arguments
Expand Down Expand Up @@ -279,8 +279,16 @@ def fit(self, outputs=None, linear_dims=None, continuous_dims=None, continuous_l
Whether to use a `sparse approximation`_ to the GP.
n_u: int, default 100
Number of inducing points to use for the sparse approximation, if required.
ARD: bool, default True
Whether to use "Automatic Relevance Determination" in the continuous kernel. If _True_, each continuous
dimension receives its own lengthscale; otherwise a single lengthscale is used for all continuous
dimensions.
**MAP_kwargs
Additional keyword arguments passed to :func:`pm.find_MAP`.
ARD: bool, default True
Whether to use "Automatic Relevance Determination" in the continuous kernel. If _True_, each continuous
dimension receives its own lengthscale; otherwise a single lengthscale is used for all continuous
dimensions.
Returns
-------
Expand All @@ -295,16 +303,16 @@ def fit(self, outputs=None, linear_dims=None, continuous_dims=None, continuous_l
self.build_model(seed=seed,
heteroskedastic_inputs=heteroskedastic_inputs,
heteroskedastic_outputs=heteroskedastic_outputs,
sparse=sparse, n_u=n_u)
sparse=sparse, n_u=n_u, ARD=ARD)

self.find_MAP(**MAP_kwargs)

return self

def _make_continuous_cov(self, continuous_cov_func, D_in, idx_s, n_s, ℓ_μ, ℓ_σ, ARD=True, stabilize=True, eps=1e-6):

shape = n_s if ARD else 1
def continuous_cov(suffix):
shape = n_s if ARD else 1
# ℓ = pm.InverseGamma(f'ℓ_{suffix}', mu=ℓ_μ, sigma=ℓ_σ, shape=shape)
= pm.Gamma(f'ℓ_{suffix}', alpha=2, beta=1, shape=shape)
η = pm.Gamma(f'η_{suffix}', alpha=2, beta=1)
Expand Down Expand Up @@ -641,7 +649,17 @@ def predict(self, points_array, with_noise=True, additive_level='total', **kwarg

return predictions

def draw_point_samples(self, points, source=None, output=None, var_name='posterior_samples', additive_level='total', increment_var=True):
def _recursively_append(self, var_name, suffix='_', increment_var=True):
if var_name in [v.name for v in self.model.vars]:
if increment_var:
var_name += suffix
return self._recursively_append(var_name)
else:
raise ValueError(f'The variable name "{var_name}" already exists in model.')
else:
return var_name

def draw_point_samples(self, points, *args, source=None, output=None, var_name='posterior_samples', additive_level='total', increment_var=True, **kwargs):
"""Draw posterior samples at supplied points
Parameters
Expand Down Expand Up @@ -680,25 +698,21 @@ def draw_point_samples(self, points, source=None, output=None, var_name='posteri
elif self.trace is not None:
source = self.trace

if var_name in [v.name for v in self.model.vars]:
if increment_var:
var_name += '_'
else:
raise ValueError(f'The variable name "{var_name}" already exists in model.')
var_name = self._recursively_append(var_name, increment_var=increment_var)

with self.model:
_ = self.gp_dict[additive_level].conditional(var_name, points_array)

with self.model:
samples = pm.sample_posterior_predictive(source, var_names=[var_name])
samples = pm.sample_posterior_predictive(*args, source, var_names=[var_name], **kwargs)

self.predictions = self.parray(**{var_name: samples[var_name]}, stdzd=True)
self.predictions_X = points

return self.predictions

def draw_grid_samples(self, source=None, output=None, categorical_levels=None, var_name='posterior_samples',
additive_level='total', increment_var=True):
def draw_grid_samples(self, *args, source=None, output=None, categorical_levels=None, var_name='posterior_samples',
additive_level='total', increment_var=True, **kwargs):
"""Draw posterior samples at points defined by :meth:`prepare_grid`.
Parameters
Expand Down Expand Up @@ -729,8 +743,8 @@ def draw_grid_samples(self, source=None, output=None, categorical_levels=None, v
if self.categorical_dims:
points = self.append_categorical_points(points, categorical_levels=categorical_levels)

samples = self.draw_point_samples(points=points, output=output, source=source, var_name=var_name,
additive_level=additive_level, increment_var=increment_var)
samples = self.draw_point_samples(*args, points=points, output=output, source=source, var_name=var_name,
additive_level=additive_level, increment_var=increment_var, **kwargs)
self.predictions = samples.reshape(-1, *self.grid_parray.shape)
self.predictions_X = self.predictions_X.reshape(self.grid_parray.shape)

Expand Down
14 changes: 8 additions & 6 deletions gumbi/regression/pymc3/extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class GPC(GP):

@wraps(GP.build_model)
def build_model(self, seed=None, continuous_kernel='ExpQuad', heteroskedastic_inputs=False,
heteroskedastic_outputs=False, sparse=False, n_u=100):
heteroskedastic_outputs=False, sparse=False, n_u=100, eps=1e-6):

if heteroskedastic_inputs:
raise NotImplementedError('The GP Classifier does not support heteroskedastic inputs.')
Expand All @@ -21,7 +21,7 @@ def build_model(self, seed=None, continuous_kernel='ExpQuad', heteroskedastic_in
if sparse:
raise NotImplementedError('The GP Classifier does not support sparse structure (yet).')

self.build_latent(seed=seed, continuous_kernel=continuous_kernel)
self.build_latent(seed=seed, continuous_kernel=continuous_kernel, eps=eps)

_, y = self.get_shaped_data('mean')

Expand All @@ -35,11 +35,13 @@ def build_model(self, seed=None, continuous_kernel='ExpQuad', heteroskedastic_in
return self

@wraps(GP.draw_point_samples)
def draw_point_samples(self, points, source=None, output=None, var_name='posterior_samples', additive_level='total',
increment_var=True):
def draw_point_samples(self, points, *args, source=None, output=None, var_name='posterior_samples', additive_level='total',
increment_var=True, **kwargs):

var_name = self._recursively_append(var_name, increment_var=increment_var)

# A
self.stdzr.logit_vars += [var_name]

return super(GPC, self).draw_point_samples(points, source=source, output=output, var_name=var_name,
additive_level=additive_level, increment_var=increment_var)
return super(GPC, self).draw_point_samples(points, *args, source=source, output=output, var_name=var_name,
additive_level=additive_level, increment_var=True, **kwargs)
1 change: 1 addition & 0 deletions tests/test_arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def test_parray():
# Parameter found in stdzr
# TODO: Update parray test when change stdzr defaults
rpa = parray(d=np.arange(5, 10) / 10, stdzr=stdzr)
assert rpa
assert np.allclose(rpa, np.arange(5, 10) / 10)
assert np.allclose(rpa.values(), np.arange(5, 10) / 10)
assert np.allclose(rpa.t, np.array([-0.69314718, -0.51082562, -0.35667494, -0.22314355, -0.10536052]))
Expand Down

0 comments on commit c3d1716

Please sign in to comment.