-
Notifications
You must be signed in to change notification settings - Fork 78
Stabilizing multivariate normal approximation #380
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
Changes from 8 commits
34c7f2a
84ed002
fbc01f5
eebf950
42c6806
d57970a
ddfdbdc
2b38c21
1405ee5
f1e1ba1
9d87656
4bbbffa
fe201aa
02ea22c
9b46601
5cb8995
303127d
93e8833
7bfacff
d87b0b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import keras | ||
|
|
||
| # import numpy as np | ||
han-ol marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| from keras.saving import register_keras_serializable as serializable | ||
|
|
||
| from bayesflow.types import Tensor | ||
| from bayesflow.utils import keras_kwargs, fill_triangular_matrix | ||
|
|
||
|
|
||
| @serializable(package="bayesflow.links") | ||
| class PositiveDefinite(keras.Layer): | ||
| """Activation function to link from flat elements of a lower triangular matrix to a positive definite matrix.""" | ||
|
|
||
| def __init__(self, **kwargs): | ||
| super().__init__(**keras_kwargs(kwargs)) | ||
| self.built = True | ||
|
|
||
| def call(self, inputs: Tensor) -> Tensor: | ||
| # Build cholesky factor from inputs | ||
| L = fill_triangular_matrix(inputs, positive_diag=True) | ||
|
|
||
| # calculate positive definite matrix from cholesky factors | ||
| psd = keras.ops.matmul( | ||
| L, | ||
| keras.ops.moveaxis(L, -2, -1), # L transposed | ||
| ) | ||
| return psd | ||
|
|
||
| def compute_output_shape(self, input_shape): | ||
| m = input_shape[-1] | ||
| n = int((0.25 + 2.0 * m) ** 0.5 - 0.5) | ||
| return input_shape[:-1] + (n, n) | ||
|
|
||
| def compute_input_shape(self, output_shape): | ||
| """ | ||
| Returns the shape of parameterization of a cholesky factor triangular matrix. | ||
| There are m nonzero elements of a lower triangular nxn matrix with m = n * (n + 1) / 2. | ||
| Example | ||
| ------- | ||
| >>> PositiveDefinite().compute_output_shape((None, 3, 3)) | ||
| 6 | ||
| """ | ||
| n = output_shape[-1] | ||
| m = int(n * (n + 1) / 2) | ||
| return output_shape[:-2] + (m,) | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,8 @@ def __init__( | |
| self.subnets_kwargs = subnets_kwargs or {} | ||
| self.links = links or {} | ||
|
|
||
| self.not_transforming_like_vector = [] | ||
|
||
|
|
||
| self.config = {"subnets_kwargs": self.subnets_kwargs} | ||
|
|
||
| def get_config(self): | ||
|
|
@@ -95,14 +97,14 @@ def get_link(self, key: str) -> keras.Layer: | |
| else: | ||
| return self.links[key] | ||
|
|
||
| def get_head(self, key: str, shape: Shape) -> keras.Sequential: | ||
| def get_head(self, key: str, output_shape: Shape) -> keras.Sequential: | ||
| """For a specified head key and shape, request corresponding head network. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| key : str | ||
| Name of head for which to request a link. | ||
| shape: Shape | ||
| output_shape: Shape | ||
| The necessary shape for the point estimators. | ||
han-ol marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Returns | ||
|
|
@@ -111,10 +113,19 @@ def get_head(self, key: str, shape: Shape) -> keras.Sequential: | |
| Head network consisting of a learnable projection, a reshape and a link operation | ||
| to parameterize estimates. | ||
| """ | ||
| subnet = self.get_subnet(key) | ||
| dense = keras.layers.Dense(units=math.prod(shape)) | ||
| reshape = keras.layers.Reshape(target_shape=shape) | ||
| # initialize head components back to front | ||
| link = self.get_link(key) | ||
|
|
||
| # link input shape can differ from output shape | ||
| if hasattr(link, "compute_input_shape"): | ||
| link_input_shape = link.compute_input_shape(output_shape) | ||
| else: | ||
| link_input_shape = output_shape | ||
|
|
||
| reshape = keras.layers.Reshape(target_shape=link_input_shape) | ||
| dense = keras.layers.Dense(units=math.prod(link_input_shape)) | ||
| subnet = self.get_subnet(key) | ||
|
|
||
| return keras.Sequential([subnet, dense, reshape, link]) | ||
|
|
||
| def score(self, estimates: dict[str, Tensor], targets: Tensor, weights: Tensor) -> Tensor: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add this function to the ContinuousApproximator, if it is identical between it and the Point Approximator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! This and similar refactoring of the ContinuousApproximator is a good idea (but I would keep them out of this PR).
There is also the option of moving the conversion to tensor into the adapter. Possibly with an optional bool flag convert_to_tensor that is by default False.