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

Fix ordering Transformation for batched dimensions #6255

Merged
merged 13 commits into from
Nov 22, 2022

Conversation

TimOliverMaier
Copy link
Contributor

@TimOliverMaier TimOliverMaier commented Oct 30, 2022

This PR is the continuation of @purna135 's PR #5660 addressing issue #5659 (ordering transformation fails for dim>1).

Short summary:
The initial change of the dimensionality of the jacobian determinant:

     def log_jac_det(self, value, *inputs): 
-        return at.sum(value[..., 1:], axis=-1)
+        return at.sum(value[..., 1:], axis=-1, keepdims=True)

This led to issues with multivariate distributions (as pointed out by @Sayam753 here).

Based on the suggestions of @ricardoV94, I added a simple case inside the log_jac_det method of ordered and sumto1transform like this:

+    def __init__(self, ndim_supp=0):
+        self.ndim_supp = ndim_supp

     def log_jac_det(self, value, *inputs): 
-        return at.sum(value[..., 1:], axis=-1, keepdims=True)
+        if self.ndim_supp == 0:
+            return at.sum(y, axis=-1, keepdims=True)
+        else:
+            return at.sum(y, axis=-1)

While fixing the mentioned problem with multivariate distributions, two problems remain:

  1. TestElementWiseLogP in test_transform.py fails
    due to the inconsistent dimensionality of the jacobian determinant among the possible transformations.
    I also ran all other the tests in the distribution subfolder locally. Here many tests are also failing in the main branch for me.
    I am unsure here if any of the failing tests are caused by this PR's additions.

  2. While experimenting with the multivariate distributions I realized that ordering of a MvNormal fails in main and still fails in
    this PR. In both branches it fails with an ValueError:
    "array must not contain infs or NaNs\nApply node that caused the error: SolveTriangular{lower=True, trans=0, unit_diagonal=False, check_finite=True}(TensorConstant{[[1. 0. 0...0. 0. 1.]]}, SpecifyShape.0)\nToposort index: 12\nInputs types: [TensorType(float64, (4, 4)), TensorType(float64, (4, 1))]\nInputs shapes: [(4, 4), (4, 1)]\nInputs strides: [(8, 32), (8, 8)]\nInputs values: ['not shown', array([[0.80230155],\n [ nan],\n [ nan],\n [ nan]])]\nOutputs clients: [[InplaceDimShuffle{1,0}(SolveTriangular{lower=True, trans=0, unit_diagonal=False, check_finite=True}.0)]]\n\nBacktrace when the node is created (use Aesara flag traceback__limit=N to make it longer):\n File "/home/tim/miniconda3/envs/pymc_dev/lib/python3.10/site-packages/aeppl/joint_logprob.py", line 151, in factorized_joint_logprob\n q_logprob_vars = _logprob(\n File "/home/tim/miniconda3/envs/pymc_dev/lib/python3.10/functools.py", line 889, in wrapper\n return dispatch(args[0].class)(*args, **kw)\n File "/home/tim/miniconda3/envs/pymc_dev/lib/python3.10/site-packages/aeppl/transforms.py", line 611, in transformed_logprob\n logprob = _logprob(rv_op, values, *inputs, **kwargs)\n File "/home/tim/miniconda3/envs/pymc_dev/lib/python3.10/functools.py", line 889, in wrapper\n return dispatch(args[0].class)(*args, **kw)\n File "/home/tim/Workspaces/pymc/pymc/distributions/distribution.py", line 123, in logp\n return class_logp(value, *dist_params)\n File "/home/tim/Workspaces/pymc/pymc/distributions/multivariate.py", line 288, in logp\n quaddist, logdet, ok = quaddist_parse(value, mu, cov)\n File "/home/tim/Workspaces/pymc/pymc/distributions/multivariate.py", line 156, in quaddist_parse\n dist, logdet, ok = quaddist_chol(delta, chol_cov)\n File "/home/tim/Workspaces/pymc/pymc/distributions/multivariate.py", line 173, in quaddist_chol\n delta_trans = solve_lower(chol_cov, delta.T).T\n\nHINT: Use the Aesara flag exception_verbosity=high for a debug print-out and storage map footprint of this Apply node."

For reproduction, see code below.

with pm.Model() as model:
    kappa_MV = pm.MvNormal("kappa_mv",
                           mu=[1,1,1,1],
                           cov = np.eye(4),
                           initval=[2,1,0,2],
                           transform=pm.distributions.transforms.ordered
                           )
    pm.sample()

I want to share my progress here as draft to make collaboration possible ;) .

Cheers!

Checklist

Bugfixes / New features

  • ordering transformation for RVs with number of dimensions > 1

@codecov
Copy link

codecov bot commented Oct 30, 2022

Codecov Report

Merging #6255 (9daae35) into main (4acd98e) will decrease coverage by 3.72%.
The diff coverage is 100.00%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #6255      +/-   ##
==========================================
- Coverage   94.17%   90.44%   -3.73%     
==========================================
  Files         111      111              
  Lines       23908    23985      +77     
==========================================
- Hits        22515    21693     -822     
- Misses       1393     2292     +899     
Impacted Files Coverage Δ
pymc/distributions/transforms.py 99.37% <100.00%> (-0.63%) ⬇️
pymc/tests/distributions/test_transform.py 100.00% <100.00%> (ø)
pymc/tests/distributions/test_timeseries.py 0.00% <0.00%> (-99.51%) ⬇️
pymc/tests/step_methods/hmc/test_quadpotential.py 0.00% <0.00%> (-95.82%) ⬇️
pymc/distributions/timeseries.py 28.46% <0.00%> (-66.00%) ⬇️
pymc/model_graph.py 66.47% <0.00%> (-12.36%) ⬇️
pymc/step_methods/hmc/quadpotential.py 73.84% <0.00%> (-6.78%) ⬇️
pymc/distributions/logprob.py 61.72% <0.00%> (-5.56%) ⬇️
pymc/distributions/shape_utils.py 97.44% <0.00%> (-0.43%) ⬇️
... and 17 more

@TimOliverMaier
Copy link
Contributor Author

TimOliverMaier commented Oct 31, 2022

Hey.

The instantiations' transforms.orderedand transforms.sum_to_1 member ndim_supp is set to 1, thereby the old behavior is mimicked. This fixes the two failing tests. It seems the other failing tests in the distribution folder were due to local issues on my machine. So no problem here :) .

Regarding the ValueError when sampling multivariate ordered distributions, I found out it comes from a initval, that is not ordered. I think this is a different issue.

@TimOliverMaier TimOliverMaier marked this pull request as ready for review October 31, 2022 12:28
@TimOliverMaier
Copy link
Contributor Author

I know, tests are still missing here. I hesistate here, because, if my changes are the way to go here, then I think transforms.ordered would stay only for backward-compatibility and it`s usage in the tests should be changed to either the univariate or multivariate instantiation. After your feedback I am happy to create (and change) the tests.

@ricardoV94
Copy link
Member

ricardoV94 commented Nov 18, 2022

@TimOliverMaier I think we can remove the old ordered one. Question: should we raise ValueError for ndim_supp>1? Sorting the last axis axis only does not make a lot of sense when there are two core dimensions.

@TimOliverMaier
Copy link
Contributor Author

@ricardoV94 thank you for your response! Alright, I will alter the relevant tests then. But we keep the transforms.ordered instantiation for backwards compatibility for now, I think?

Yes, good point. I will implement in the ValueErroras well. Only out of interest: can one get ndim_supp>1 with the provided distributions in pymc?

@ricardoV94
Copy link
Member

ricardoV94 commented Nov 18, 2022

Only out of interest: can one get ndim_supp>1 with the provided distributions in pymc?

Just some edge cases: MatrixNormal and RandomWalk with multivariate innovations (RandomWalk.ndim_supp = innovation.ndim_supp + 1)

@ricardoV94
Copy link
Member

ricardoV94 commented Nov 18, 2022

But we keep the transforms.ordered instantiation for backwards compatibility for now, I think?

I am not sure that's better actually...

1. updated variable `__all__` to contain
   `univariate_ordered`,`multivariate_ordered` and
   analogous `sum_to_1` instantiations.
@TimOliverMaier
Copy link
Contributor Author

I updated the tests now to use the new instantiations and implemented the ValueError.
I wonder if we should keep the two last tests in test_transform.py :

def test_transforms_ordered():
with pm.Model() as model:
pm.Normal(
"x_univariate",
mu=[-3, -1, 1, 2],
sigma=1,
size=(10, 4),
transform=tr.univariate_ordered,
)
log_prob = model.point_logps()
np.testing.assert_allclose(list(log_prob.values()), np.array([18.69]))
def test_transforms_sumto1():
with pm.Model() as model:
pm.Normal(
"x",
mu=[-3, -1, 1, 2],
sigma=1,
size=(10, 4),
transform=tr.univariate_sum_to_1,
)
log_prob = model.point_logps()
np.testing.assert_allclose(list(log_prob.values()), np.array([-56.76]))

These were added earlier in this PR. I think the shape of the transforms is now properly tested in check_vectortransform_elementwise_logp. I'd rather put some extra lines covering the multivariate case here

def test_ordered():
check_vector_transform(tr.univariate_ordered, SortedVector(6))
check_jacobian_det(
tr.univariate_ordered, Vector(R, 2), at.dvector, np.array([0, 0]), elemwise=False
)
vals = get_values(tr.univariate_ordered, Vector(R, 3), at.dvector, np.zeros(3))
close_to_logical(np.diff(vals) >= 0, True, tol)

Copy link
Member

@ricardoV94 ricardoV94 left a comment

Choose a reason for hiding this comment

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

@TimOliverMaier we forgot to include this PR in our major release of 4.4.0 last night. As such I would agree with your initial suggestion of keeping the original transforms around for a bit longer so we can merge these fixes and release them with 4.1, without breaking backwards compatibility. I hope that's not too annoying :)

Besides that I have some small suggestions for the new tests. I wrote about the first but it applies to both. Let me know what you think

@@ -598,3 +615,31 @@ def test_discrete_trafo():
with pytest.raises(ValueError) as err:
pm.Binomial("a", n=5, p=0.5, transform="log")
err.match("Transformations for discrete distributions")


def test_transforms_ordered():
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps a more direct name?

Suggested change
def test_transforms_ordered():
def test_2d_univariate_ordered_():

transform=tr.univariate_ordered,
)

log_prob = model.point_logps()
Copy link
Member

@ricardoV94 ricardoV94 Nov 20, 2022

Choose a reason for hiding this comment

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

To make this test a bit more readable I would suggest including an equivalent x_1d = pm.Normal(..., shape=(4,)) in the model and comparing the elemwise logp of that is the same as each copy of the 2D (10, 4) one that exists now.

You can do that via m.compile_logp(sum=False)({"x_1d_ordered__": np.zeros((4,)), x_2d_ordered__": np.zeros(10, 4)}) and then asserting closeness across the last axis.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the sum_to_1 transform the logps are not the same. Is this expected? Even if the shape is the same like here:

def test_2d_univariate_sum_to_1():
    with pm.Model() as model:
        x_1d = pm.Normal(
            "x_1d",
            mu=[-3,-1,1,2],
            sigma=1,
            size=(10,4),
            transform=tr.univariate_sum_to_1,
        )
        x_2d = pm.Normal(
            "x_2d",
            mu=[-3, -1, 1, 2],
            sigma=1,
            size=(10, 4),
            transform=tr.univariate_sum_to_1,
        )

    log_p = model.compile_logp(sum=False)({"x_1d_sumto1__":np.ones((10,3))*0.25,"x_2d_sumto1__":np.zeros((10,3))*0.25})
    np.testing.assert_allclose(log_p[0],log_p[1])

Fails with:

./pymc/tests/distributions/test_transform.py::test_2d_univariate_sum_to_1 Failed: [undefined]AssertionError: 
Not equal to tolerance rtol=1e-07, atol=0

Mismatched elements: 40 / 40 (100%)
Max absolute difference: 1.03125
Max relative difference: 0.72677567
 x: array([[-6.200189, -1.700189, -1.200189, -2.450189],
       [-6.200189, -1.700189, -1.200189, -2.450189],
       [-6.200189, -1.700189, -1.200189, -2.450189],...
 y: array([[-5.418939, -1.418939, -1.418939, -1.418939],
       [-5.418939, -1.418939, -1.418939, -1.418939],
       [-5.418939, -1.418939, -1.418939, -1.418939],...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok funny. This is not the case if I use np.zeros

Copy link
Member

Choose a reason for hiding this comment

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

PR looks good otherwise, should we investigate the SumTo1?

Copy link
Member

Choose a reason for hiding this comment

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

Was this when comparing np.zeros(), vs np.ones()? I don't expect those to be equivalent...

Copy link
Contributor Author

@TimOliverMaier TimOliverMaier Nov 22, 2022

Choose a reason for hiding this comment

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

Oh boy 😆 . Maybe it was just that. I thought I was comparing np.ones() to np.ones(). will check again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. This was my mistake, sorry! Checked it properly with np.ones() and it passes.

1. Old `transforms.ordered` and `transforms.sum_to_1` instantiations
   were added again for backwards compatibility.
2. Tests for multivariate and univariate usage of `SumTo1` and `Ordered`
   transformations were added.
@TimOliverMaier TimOliverMaier requested review from ricardoV94 and removed request for lucianopaz and Sayam753 November 21, 2022 08:51
@ricardoV94 ricardoV94 changed the title Ordering Transformation for higher Dimensions Fix ordering Transformation for batched dimensions Nov 22, 2022
@ricardoV94 ricardoV94 added the bug label Nov 22, 2022
@ricardoV94
Copy link
Member

@TimOliverMaier thanks so much for the fix (and the going back and forth). Is this ready to merge on your end, or did you want to do anything else?

@TimOliverMaier
Copy link
Contributor Author

@ricardoV94 my pleasure :) . Yes, I am finished here.

@ricardoV94 ricardoV94 merged commit 58dfb35 into pymc-devs:main Nov 22, 2022
wrongu pushed a commit to wrongu/pymc that referenced this pull request Dec 1, 2022
Co-authored-by: Purna Chandra Mansingh <purnachandramansingh135@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants