Skip to content

Refactoring across backends - #217

Merged
JeanKossaifi merged 20 commits into
tensorly:mainfrom
aarmey:backend-refactor
Feb 26, 2021
Merged

JeanKossaifi merged 20 commits into
tensorly:mainfrom
aarmey:backend-refactor

Conversation

@aarmey

@aarmey aarmey commented Dec 24, 2020 •

Copy link
Copy Markdown
Contributor
  • Various SVD forms are backend-agnostic.
  • Norm method is backend-agnostic.
  • CuPy tests now pass.

I know there's some discussion below about changing the SVD interface now that it can be handled as a frontend feature. Perhaps we can merge this as a first step?

Comment thread tensorly/backend/mxnet_backend.py Resolved
Comment thread tensorly/backend/core.py Resolved
Comment thread tensorly/backend/core.py Resolved
Comment thread tensorly/backend/pytorch_backend.py Resolved
This was referenced Dec 27, 2020
@JeanKossaifi

JeanKossaifi commented Dec 27, 2020 •

Copy link
Copy Markdown
Member

Thanks @aarmey! I like the idea of the refactoring - SVD_FUNS was a hacky solution to a problem that appeared when introducing the backend system. So, how about adding a parameter (e.g. svd_fun) to truncated_svd and making that common to all implementations, which will then just call the various svd funs to actually perform the computation? We can pass an additional parameter to svd_fun, n_eigenvecs for instance, which can be ignored when not used (scipy's sparse eigendecomposition uses it efficiently while standard svd does not in general).

Regarding mxnet.np, indeed, I was waiting for it to be finalized to switched, we can open a separate PR for that!

Regarding kr, this implementation was much faster than other ones when I wrote it. Since we now have various tenalg backends, one core one using "regular" methods (e.g. reshapings + broadcasting and matmuls) and an einsum one that dispatches all operations to einsum, so we shouldn't force one or the other directly. Instead, all functions where we have a choice, such as khatri-rao, can be put in tensorly.tenalg (both einsum_tenalg and core_tenalg). In this case, Khatri-Rao is defined for both.

As a side note, I would like to enable dispatching tenalg calls directly to the backend in case they implement a particular function but I'm worried it can become a big mess, it's already not the most intuitive :/

Comment thread tensorly/backend/core.py

def truncated_svd(self, matrix, n_eigenvecs=None, **kwargs):
"""Computes a truncated SVD on `matrix` using pytorch's SVD
def truncated_svd(self, matrix, n_eigenvecs=None):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we could add a parameter svd_fun="svd" and carefully document it? This could then accept "numpy_svd", and other when applicable. That would probably be cleaner than what we have now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is **kwargs removed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think it was actually used anywhere but we can add it back if it breaks things downstream.

Comment thread tensorly/backend/core.py

index = Index()

@property

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we go for that solution we could have a function available_svd_funs instead of this? What do you think?

@JeanKossaifi
JeanKossaifi force-pushed the master branch 2 times, most recently from d45beab to 2aa9834 Compare January 3, 2021 14:51
Base automatically changed from master to main January 22, 2021 13:33
MarieRoald and others added 3 commits February 12, 2021 08:26
1. A matrix was initialised using SVD, should be ones
2. Created Parafac2Tensor from tuple of factors not list of factors
@codecov

codecov Bot commented Feb 12, 2021 •

Copy link
Copy Markdown

Codecov Report

Merging #217 (b0503cb) into main (dc419c5) will increase coverage by 0.02%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #217      +/-   ##
==========================================
+ Coverage   87.00%   87.02%   +0.02%     
==========================================
  Files          82       86       +4     
  Lines        3825     4055     +230     
==========================================
+ Hits         3328     3529     +201     
- Misses        497      526      +29     
Impacted Files Coverage Δ
...ecomposition/tests/test_mps_decomposition_cross.py 100.00% <100.00%> (ø)
tensorly/decomposition/tests/test_parafac2.py 100.00% <100.00%> (ø)
tensorly/random/tests/test_base.py 97.43% <100.00%> (+0.03%) ⬆️
tensorly/decomposition/_cp.py 76.06% <0.00%> (-0.39%) ⬇️
tensorly/decomposition/__init__.py 100.00% <0.00%> (ø)
tensorly/cp_tensor.py 83.72% <0.00%> (ø)
tensorly/base.py 100.00% <0.00%> (ø)
tensorly/__init__.py 100.00% <0.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update dc419c5...546f2f3. Read the comment docs.

@aarmey
aarmey marked this pull request as ready for review February 13, 2021 19:41
@aarmey
aarmey requested a review from JeanKossaifi February 19, 2021 01:25
@JeanKossaifi

Copy link
Copy Markdown
Member

Thanks a lot @aarmey, this is a large but necessary change to make the behaviour of SVD clearer to users.

Overally looks good to me, the change from _reverse to flip is great.
I think we may want to use the opportunity to add some documentation, perhaps in the user_guide, either in the backend section or in a new page? We can there explain the difference between the three provided interfaces.

I'd really rather avoid converting to NumPy as much as possible as it breaks the computational graph in some backends and causes surprising issues (e.g. if running the method on GPU).

On the API side, I'm wondering whether we want to provide a function partial_svd (or even just svd) and an additional implementation (or method?) :

def partial_svd(matrix, n_eigenvecs, implementation='truncate')

where

implementation : {'truncate', 'numpy', 'eish'}

          - truncate : uses the backend's native SVD function and truncates it
          - numpy : uses the efficient Sparse SVD from Scipy to only compute the required eigenvalues
          - symeig : uses the native backend's symeig to leverage the symmetry in the covariance matrix when computing the SVD

What do you think?

@aarmey

aarmey commented Feb 23, 2021

Copy link
Copy Markdown
Contributor Author

I think this SVD interface looks great. Indeed, I partly wanted these changes to move more of the SVD methods to the proper backend as opposed to silently going back to NumPy. I like that your suggestion will make it clear whether the method is using the indicated backend or Scipy.

Perhaps I can open an issue with your notes here? I expect that changing the SVD interface is going to be another large, complicated change.

@JeanKossaifi

Copy link
Copy Markdown
Member

Yes, that's a good idea. There are some conversions to NumPy in the reconstruction errors, would be great to remove those before merging if possible!

@aarmey

aarmey commented Feb 26, 2021

Copy link
Copy Markdown
Contributor Author

@JeanKossaifi removed.

@JeanKossaifi JeanKossaifi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @aarmey for the all great changes! 🥇
Merging.

Comment thread tensorly/decomposition/_cp.py Outdated
new_rec_error, new_tensor, new_norm_tensor = error_calc(tensor, norm_tensor, new_weights, new_factors, sparsity, mask)

if (new_rec_error / new_norm_tensor) < rec_errors[-1]:
if tl.to_numpy(new_rec_error / new_norm_tensor) < rec_errors[-1]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why convert to NumPy? I'd rather avoid doing this in the backends as this breaks the computational graph.

from tensorly.testing import assert_

skip_if_backend = pytest.mark.skipif(tl.get_backend() in ("tensorflow", "jax"),
skip_if_backend = pytest.mark.skipif(tl.get_backend() in ("tensorflow", "jax", "cupy"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are we skipping with cupy too ?

return torch.sum(tensor, dim=axis)

@staticmethod
def flip(tensor, axis=None):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Much better name than _reverse, thanks! :)

rec_X.append(T.norm(X - D - E, 2))
rec_D.append(np.max([T.norm(low_rank - D, 2) for low_rank in J]))
rec_X.append(T.to_numpy(T.norm(X - D - E, 2)))
rec_D.append(np.max([T.to_numpy(T.norm(low_rank - D, 2)) for low_rank in J]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why convert to NumPy here?

Comment thread tensorly/decomposition/_cp.py Outdated

rec_error = unnorml_rec_error / norm_tensor
rec_errors.append(rec_error)
rec_errors.append(tl.to_numpy(rec_error))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here, I'd rather avoid this.

@JeanKossaifi
JeanKossaifi merged commit c701988 into tensorly:main Feb 26, 2021
def svd(self, X, full_matrices=True):
# The torch SVD has accuracy issues. Try again when torch.linalg is stable.
ctx = self.context(X)
X = self.to_numpy(X)

@merajhashemi merajhashemi Mar 4, 2021 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pytorch 1.8 is now the stable release, should we consider replacing this with torch.linalg.svd?
As a side note, the linear algebra functions in torch will be deprecated in favour of their linalg counterparts with the release of 1.9. For example, torch.qr is already deprecated in 1.8 we should replace it with torch.linalg.qr.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, I was actually looking forward to switching torch.linalg.svd! Thanks @merajhashemi !

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the note @merajhashemi. I'll open an issue to make sure this is done.

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.

4 participants