Skip to content

Make axis keyword to squeeze() positional #100

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

Merged
merged 1 commit into from
May 11, 2021

Conversation

rgommers
Copy link
Member

As suggested by @shoyer in #97 (comment). This makes it possible to predict resulting rank of output array, which is otherwise undetermined (see discussion in gh-97).

Using squeeze without specifying the axis in library code often results in unintuitive behaviour. For the common use case of turning a size-1 2-D array into a 0-D, this gets a little more verbose (e.g. np.squeeze(np.array([[1]]), axis=(0, 1))), but that's probably a price worth paying for the extra clarity.

Also changes specified behaviour for a given axis not having size 1 to raising a ValueError, which is what NumPy does. This wasn't considered before, and the current description seems simply incorrect.

Finally, this makes squeeze the exact inverse of expand_dims, which is probably a good thing.

The caveats:

  • This deviates from what libraries currently do
  • Most existing uses of squeeze (e.g. in SciPy) do not use the axis keyword. The counter-argument for that is that many of those instances are hard to understand and have no comment for why squeeze is used.

As suggested by @shoyer in data-apis#97 (comment)
This makes it possible to predict resulting rank of output array, which is
otherwise undetermined (see discussion in data-apisgh-97).

Using squeeze without specifying the axis in library code often results in
unintuitive behaviour. For the common use case of extracting a scalar from a size-1 2-D array,
this gets a little more verbose (e.g. `np.squeeze(np.array([[1]]), axis=(0, 1))`),
but that's probably a price worth paying for the extra clarity.

Also changes specified behaviour for a given axis not having size 1 to raising
a `ValueError`, which is what NumPy does. This wasn't considered before, and the current
description seems simply incorrect.

Finally, this makes `squeeze` the exact inverse of `expand_dims`, which is probably
a good thing.
@kgryte
Copy link
Contributor

kgryte commented Dec 17, 2020

@rgommers I believe the "unchanged behavior" stems from Torch, as documented here. I missed that NumPy raised a ValueError. This should probably be discussed.

Copy link
Contributor

@shoyer shoyer left a comment

Choose a reason for hiding this comment

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

Looks great to me, thanks!

@rgommers
Copy link
Member Author

@rgommers I believe the "unchanged behavior" stems from Torch, as documented here. I missed that NumPy raised a ValueError. This should probably be discussed.

I did a bit of searching, NumPy has done this for a very long time, the axis keyword was added 10 years ago in numpy/numpy@a112fc4, and it behaved like this already. It's a separate function in C, PyArray_SqueezeSelected. Also note that that was in the pre-GitHub days, so there may not have been a thorough discussion on raising yes or no.

The PyTorch docs have a warning: "If the tensor has a batch dimension of size 1, then squeeze(input) will also remove the batch dimension, which can lead to unexpected errors."

So there's two things to decide:

  1. Is axis=None allowed?
  2. Raise or not if a specified axis is not of size 1?

The PR in its current state (which answers with "No, Raise") makes squeeze the inverse of expand_dims. Being more flexible makes it potentially more useful for end users, but is also a bit of a footgun as the pytorch docs warning shows.

There's not much data on (2) - I haven't seen any complaints from numpy users, but that's because they mostly use the default axis=None. Either way, the semantics being "inverse of expand_dims" or "clean up size-1 dimensions if they exist" are very different; likely those should have been two separate functions.

@rgommers rgommers added the API change Changes to existing functions or objects in the API. label Mar 20, 2021
@rgommers rgommers force-pushed the main branch 3 times, most recently from 0607525 to 138e963 Compare April 19, 2021 20:25
@kgryte
Copy link
Contributor

kgryte commented May 11, 2021

Given the divergence in semantics, would it make sense to do a bit of innovating here and actually split the use cases as separate APIs?

@shoyer
Copy link
Contributor

shoyer commented May 11, 2021

I don't think we need "clean up size-1 dimensions if they exist" in the API standard. Squeezing without an axis really only makes sense in an interactive context, but our primary audience here is library developers.

@rgommers
Copy link
Member Author

I don't think we need "clean up size-1 dimensions if they exist" in the API standard. Squeezing without an axis really only makes sense in an interactive context, but our primary audience here is library developers.

I agree. Although the proof will be in the pudding. There's quite a bit of squeeze() usage floating around in the likes of SciPy. A lot of that will be design mistakes, so not supporting it may flush out if there's an actual need.

How about we merge this as is, and then see how it works in practice? We can always add something later, but once we put a function in we can't remove it anymore.

@shoyer
Copy link
Contributor

shoyer commented May 11, 2021

I agree. Although the proof will be in the pudding. There's quite a bit of squeeze() usage floating around in the likes of SciPy. A lot of that will be design mistakes, so not supporting it may flush out if there's an actual need.

Agreed about both!

How about we merge this as is, and then see how it works in practice? We can always add something later, but once we put a function in we can't remove it anymore.

Sounds good to me.

To be honest, it is somewhat questionable whether we need squeeze() in either form. The canonical implementation of either version is ~5 lines of code. The most annoying part is probably formatting the error message :)

@rgommers
Copy link
Member Author

To be honest, it is somewhat questionable whether we need squeeze() in either form.

Agreed, I'd say it's borderline. The symmetry with expand_dims and it being already commonly implemented are probably the main argument for leaving it in.

Okay, this has been open for a long time, let's push the button on it:)

@rgommers rgommers merged commit 4d74293 into data-apis:main May 11, 2021
@rgommers rgommers deleted the squeeze-axis-kw branch May 11, 2021 19:49
asmeurer added a commit to data-apis/numpy that referenced this pull request Aug 7, 2021
leofang pushed a commit to leofang/cupy that referenced this pull request Sep 5, 2021
asmeurer added a commit to data-apis/array-api-strict that referenced this pull request Jan 22, 2024
…l-only

See data-apis/array-api#100.

Original NumPy Commit: 1ae808401951bf8c4cbff97a30505f08741d811f
asmeurer added a commit to data-apis/array-api-strict that referenced this pull request Jan 22, 2024
…l-only

See data-apis/array-api#100.

Original NumPy Commit: 1ae808401951bf8c4cbff97a30505f08741d811f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API change Changes to existing functions or objects in the API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants