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

Adding logdet #1777

Merged
merged 9 commits into from
Feb 17, 2017
Merged

Adding logdet #1777

merged 9 commits into from
Feb 17, 2017

Conversation

bhargavvader
Copy link
Contributor

This is with regard to #1012.
The code is copy pasted from Theano PR #3959 as suggested in the issue, with the function renamed to logdet.

Should there be tests made for this as well?

@twiecki
Copy link
Member

twiecki commented Feb 13, 2017

Thanks @bhargavvader! Can we just copy the test from the PR?

pymc3/theanof.py Outdated
matrix M, log(abs(det(M))), on CPU. Avoids det(M) overflow/
underflow.

Note: Once PR #3959 in the Theano repo is merged, this must be removed.
Copy link
Member

Choose a reason for hiding this comment

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

Should add specific link to PR and also mention the original author.

@twiecki
Copy link
Member

twiecki commented Feb 13, 2017

Moreover, would be great if this functionality would already be used in ADVI as part of this PR.

@@ -18,6 +19,33 @@ def integers_ndim(ndim):
yield np.ones((2,) * ndim) * i
i += 1

class TestLogDet(unittest.TestCase):
Copy link
Member

Choose a reason for hiding this comment

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

Should inherit from SeededTest

@bhargavvader
Copy link
Contributor Author

Addressed code comments.
What changes to ADVI would be needed? Is it in the ELBO part of the code?

@twiecki
Copy link
Member

twiecki commented Feb 13, 2017

https://github.com/pymc-devs/pymc3/blob/master/pymc3/distributions/multivariate.py#L116 here is where we want to use it. Was wrong about ADVI.

@bhargavvader
Copy link
Contributor Author

bhargavvader commented Feb 13, 2017

Cool - so would that be doing result = k * tt.log(2 * np.pi) - logdet(tau) ?
Edit: here, tt.log(1. / det(tau)) = - logdet(tau), right?

Also, the tests are failing, but I think it's related to #1773. The theanof tests seem to be fine.

@twiecki
Copy link
Member

twiecki commented Feb 13, 2017

Edit: here, tt.log(1. / det(tau)) = - logdet(tau), right?

Yes, that looks right.

@bhargavvader
Copy link
Contributor Author

bhargavvader commented Feb 15, 2017

@twiecki , made the changes to multivariate and added logdet to __all__. Could you review it?

@@ -12,6 +12,8 @@
from theano.tensor.nlinalg import det, matrix_inverse, trace

import pymc3 as pm

from pycm3.theanof import logdet
Copy link
Member

Choose a reason for hiding this comment

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

typo pymc3

@twiecki
Copy link
Member

twiecki commented Feb 15, 2017

@bhargavvader Make sure that unittests (specifically the distribution ones) still pass locally.

@bhargavvader
Copy link
Contributor Author

I've corrected the typo - had a few doubts about running the tests locally, working on it now.

@twiecki
Copy link
Member

twiecki commented Feb 16, 2017

This is great. I want a second set of eyes on this: @ColCarroll @taku-y @fonnesbeck do you have time to take a look?

@bhargavvader
Copy link
Contributor Author

bhargavvader commented Feb 16, 2017

Update: when I ran nosetests pymc3/tests, there were 9 errors. Both test_distributions.py and test_theanof.py failed with ImportError: No module named nose_parameterized. The other fails were with pymc3.tests.test_stats.TestStats which I reckon is unrelated.

Edit: is it possible to run only certain files using nosetest?
Figured it out.

@ColCarroll
Copy link
Member

ColCarroll commented Feb 16, 2017 via email

@bhargavvader
Copy link
Contributor Author

All of test_distributions.py, test_distributions_timeseries.py and test_distributions_random.py pass with no errors. (I don't think these run on travis?)

@twiecki
Copy link
Member

twiecki commented Feb 16, 2017

What makes you say they don't run on travis?

Another thing occurred to me: missing GPU support. We're inducing a blocker to run any model with MvNormal on the GPU. I see two solutions:

  • Add GPU support to logdet (not sure how big of a task that would be)
  • Add a flag to MvNormal to either use the numerically stable logdet or the manual version.

@bhargavvader
Copy link
Contributor Author

Oh my bad - I noticed that one of the travis checks doesn't include them, but the other one does.

About GPU support, I would be up to the task, but not immediately - I am caught up with some school work and can't devote too much time at the moment. We can add a flag for the moment if that makes sense. Would it be a log, or a warning?

@fonnesbeck
Copy link
Member

I'm happy with merging this. Perhaps GPU support can be added after the Theano PR is merged?

@twiecki
Copy link
Member

twiecki commented Feb 16, 2017

Here's what I think we should do:

  • Add a flag to MvNormal: gpu_compat=False with a doc-string explaining the behavior
  • In __init__ check if gpu_compat is False and theano.config.device == 'gpu' and raise a warning alerting of the incompatibility and notifying about the gpu_compat flag at cost of numerical accuracy.

@fonnesbeck
Copy link
Member

@twiecki Is that something we think we'd use again? If so, maybe write a decorator that could be added and removed whenever we have (temporary) GPU compatibility issues.

@twiecki
Copy link
Member

twiecki commented Feb 16, 2017

@fonnesbeck Good point. We could put that logic into the Op itself maybe. We might also want to automatically switch in case of GPU and give a warning.

@taku-y
Copy link
Contributor

taku-y commented Feb 16, 2017

I looked over the code and it looks good to me. It would be nice to merge this PR even if GPU has not been supported yet.

@bhargavvader
Copy link
Contributor Author

Cool - I've added the flag and the warning. I agree with the idea of having a decorator for GPU compatibility issues but I'd rather take it up in a new PR (if we do want it).

@@ -113,7 +123,7 @@ def logp(self, value):
delta = value - mu
k = tau.shape[0]

result = k * tt.log(2 * np.pi) + tt.log(1. / det(tau))
result = k * tt.log(2 * np.pi) - logdet(tau)
Copy link
Member

Choose a reason for hiding this comment

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

Need to actually check if gpu_compat is true and use the previous statement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Missed it before - just fixed now.

@@ -113,7 +124,10 @@ def logp(self, value):
delta = value - mu
k = tau.shape[0]

result = k * tt.log(2 * np.pi) + tt.log(1. / det(tau))
if self.gpu_compat is False:
Copy link
Member

Choose a reason for hiding this comment

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

can just do if self.gpu_compat: ... else: ...

@twiecki
Copy link
Member

twiecki commented Feb 17, 2017

Merging once tests pass.

@twiecki twiecki merged commit f4ab79b into pymc-devs:master Feb 17, 2017
@twiecki twiecki mentioned this pull request Feb 17, 2017
@bhargavvader
Copy link
Contributor Author

👍

@twiecki
Copy link
Member

twiecki commented Feb 17, 2017

@bhargavvader I just realized that I gave bad advice with placing logdet into theanof, it should really be in math.py. Would it be too much to ask to change that?

if gpu_compat is False:
result = k * tt.log(2 * np.pi) - logdet(tau)
elif gpu_compat is True:
result = k * tt.log(2 * np.pi) + tt.log(1. / det(tau))
Copy link
Member

Choose a reason for hiding this comment

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

result = k * tt.log(2 * np.pi) - tt.log(det(tau)) maybe

Copy link
Member

Choose a reason for hiding this comment

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

or maybe even move the previous part on top and only do result -= logdet(tau)

o = theano.tensor.scalar(dtype=x.dtype)
return Apply(self, [x], [o])

def perform(self, node, inputs, outputs):
Copy link
Member

Choose a reason for hiding this comment

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

Signature of overriden method changed, should be perform(self, node, inputs, output_storage, params=None) for consistency

Copy link
Member

Choose a reason for hiding this comment

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

That's not crucial thing, so you can take it in account only if you decide moving logdet to math.py

@bhargavvader
Copy link
Contributor Author

I wouldn't mind changing it over to math.py. - should I open a different PR, or will you undo this one? I also wanted to know if there would be interest for a non-GPU compatible decorator like @fonnesbeck was talking about - in that case I can open the 2 PRs in a few days.

@twiecki
Copy link
Member

twiecki commented Feb 18, 2017 via email

@@ -113,7 +124,10 @@ def logp(self, value):
delta = value - mu
k = tau.shape[0]

result = k * tt.log(2 * np.pi) + tt.log(1. / det(tau))
if self.gpu_compat:
result = k * tt.log(2 * np.pi) + tt.log(1. / det(tau))
Copy link
Member

Choose a reason for hiding this comment

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

the first term can be moved up top, then the if-clause just does -logdet or -tt.log(det(tau)

Copy link
Member

Choose a reason for hiding this comment

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

You can use this code in future, it worked for me

@twiecki
Copy link
Member

twiecki commented Feb 25, 2017

Seems like travis is unhappy about this all of a sudden:

======================================================================
ERROR: test_basic (pymc3.tests.test_math.TestLogDet)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/theano/gof/op.py", line 614, in __call__
    storage_map[ins] = [self._get_test_value(ins)]
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/theano/gof/op.py", line 570, in _get_test_value
    raise AttributeError('%s has no test value %s' % (v, detailed_err_msg))
AttributeError: <TensorType(float64, matrix)> has no test value  
Backtrace when that variable is created:
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/nose/suite.py", line 224, in run
    test(orig)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/nose/case.py", line 45, in __call__
    return self.run(*arg, **kwarg)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/nose/case.py", line 133, in run
    self.runTest(result)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/nose/case.py", line 151, in runTest
    test(result)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/unittest/case.py", line 649, in __call__
    return self.run(*args, **kwds)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/unittest/case.py", line 601, in run
    testMethod()
  File "/home/travis/build/pymc-devs/pymc3/pymc3/tests/test_math.py", line 31, in test_basic
    self.validate(test_case_1.astype(theano.config.floatX))
  File "/home/travis/build/pymc-devs/pymc3/pymc3/tests/test_math.py", line 15, in validate
    x = theano.tensor.matrix()
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/home/travis/build/pymc-devs/pymc3/pymc3/tests/test_math.py", line 31, in test_basic
    self.validate(test_case_1.astype(theano.config.floatX))
  File "/home/travis/build/pymc-devs/pymc3/pymc3/tests/test_math.py", line 16, in validate
    f = theano.function([x], self.op(x))
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/theano/gof/op.py", line 628, in __call__
    (i, ins, node, detailed_err_msg))
ValueError: Cannot compute test value: input 0 (<TensorType(float64, matrix)>) of Op LogDet(<TensorType(float64, matrix)>) missing default value.  
Backtrace when that variable is created:
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/nose/suite.py", line 224, in run
    test(orig)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/nose/case.py", line 45, in __call__
    return self.run(*arg, **kwarg)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/nose/case.py", line 133, in run
    self.runTest(result)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/site-packages/nose/case.py", line 151, in runTest
    test(result)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/unittest/case.py", line 649, in __call__
    return self.run(*args, **kwds)
  File "/home/travis/miniconda3/envs/testenv/lib/python3.6/unittest/case.py", line 601, in run
    testMethod()
  File "/home/travis/build/pymc-devs/pymc3/pymc3/tests/test_math.py", line 31, in test_basic
    self.validate(test_case_1.astype(theano.config.floatX))
  File "/home/travis/build/pymc-devs/pymc3/pymc3/tests/test_math.py", line 15, in validate
    x = theano.tensor.matrix()

Can you take a look @bhargavvader

@ferrine
Copy link
Member

ferrine commented Feb 25, 2017

x = tt.matrix()
x ** 2

fails with the same error

x = tt.matrix()
x.tag.test_value = np.array([[1,1], [2,2]])
x ** 2

not

@bhargavvader
Copy link
Contributor Author

Uh oh. I'll have a look at this.
@ferrine , does it not fail when you set a test value? The error does mention it's missing a default.

@bhargavvader
Copy link
Contributor Author

I'm a little confused about which test_value to use, and what the significance of it is; this link talks about how it's used to help debug, but I don't see how it's relevant here.
When I try running the test after setting a random (2,2) matrix to test_value I get this error ValueError: Cannot compute test value: input 0 (input 0) of Op LogDet(input 0) missing default value.

This is similar to an issue raised by @fonnesbeck , where the solution is just to delete the cache (.theano directory). Do you think it might be a similar issue? Considering how it was fine before but failing now?

@bhargavvader
Copy link
Contributor Author

@twiecki , what's the status of this? Is it still failing?

@twiecki
Copy link
Member

twiecki commented Mar 13, 2017

Seems OK, not sure what caused this.

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

Successfully merging this pull request may close these issues.

6 participants