Skip to content

[proto] Added mid-level ops and feature-based ops #6219

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 7 commits into from
Jul 6, 2022

Conversation

vfdev-5
Copy link
Collaborator

@vfdev-5 vfdev-5 commented Jun 29, 2022

Description:

  • Added functional mid-level ops
  • Added Image/BoundingBoxes/SegmentationMask ops: Image.resize etc

Source: #6205

Copy link
Contributor

@datumbox datumbox left a comment

Choose a reason for hiding this comment

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

Thanks @vfdev-5. Looks good overall. I did a high-level review on the patterns you used. I didn't check every single invocation and method call you make on the PR, assuming you complete the pattern (aka you call the right low-level kernel for every high-level one). I've added a few comments to get your input. Let me know what you think.

Comment on lines +202 to +210
def erase(self, i: int, j: int, h: int, w: int, v: torch.Tensor) -> BoundingBox:
raise TypeError("Erase transformation does not support bounding boxes")

def mixup(self, lam: float) -> BoundingBox:
raise TypeError("Mixup transformation does not support bounding boxes")

def cutmix(self, box: Tuple[int, int, int, int], lam_adjusted: float) -> BoundingBox:
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't believe these operations should be kernels. Mixup, Cutmix etc are augmentation strategies. I would even be inclined not to add erase and let people to access the functional directly. The rational is that we should keep the kernels low. As we keep adding augmentations, we do this on the Transforms side. Thoughts?

# How dangerous to do this instead of raising an error ?
return self

def resize( # type: ignore[override]
Copy link
Contributor

Choose a reason for hiding this comment

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

Why ignore[override] is needed here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

holly molly! that's not good. Does it make sense to rename the method? I can see that Tensor.resize_ exists still on the docs but can't find the resize.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

non-inplace Tensor.resize is deprecated -> no docs. IMO, we can keep both.

Copy link
Contributor

Choose a reason for hiding this comment

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

Add a big TODO to get feedback on this. cc @NicolasHug thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

A bit late to the party: looks like the original Tensor.resize was deprecated 4+ years ago, so I would agree that it's fine to override it IMHO.

Minor note: it's not super clear what the TODO above is about, i.e. it's not obvious what needs to be done about it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@NicolasHug I agree that the TODO note is unclear. I'll update in a follow-up PR. The point is to fix # type: ignore[override] and make mypy happy.

Copy link
Member

Choose a reason for hiding this comment

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

Will this be actually possible? As far as I understand, mypy will throw an error as long as the 2 resize() implementations have different signatures?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a good question. Maybe it is impossible. I was thinking about mypy overload: https://mypy.readthedocs.io/en/stable/more_types.html#function-overloading if this could help...

elif isinstance(inpt, PIL.Image.Image):
# Shouldn't we implement a fallback to tensor ?
raise RuntimeError("Not implemented")
elif isinstance(inpt, torch.Tensor):
Copy link
Contributor

Choose a reason for hiding this comment

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

How would the idiom would look like if we had a fallback? Do you plan to do something like the following?

elif isinstance(inpt, (PIL.Image.Image, torch.Tensor)):

Copy link
Collaborator Author

@vfdev-5 vfdev-5 Jun 30, 2022

Choose a reason for hiding this comment

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

we can just repeat one line:

        elif isinstance(inpt, PIL.Image.Image):
            inpt_tensor = some_pil_to_tensor_method(inpt)
            output_tensor = F.erase_image_tensor(inpt_tensor, **params)
            return some_tensor_to_pil_method(output_tensor)
        elif isinstance(inpt, torch.Tensor):
            return F.erase_image_tensor(inpt, **params)

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure but what if you have the kernel. I'm asking in general what idiom you will favour when you call the same F.

return features.OneHotLabel.new_like(input, output)
def _transform(self, inpt: Any, params: Dict[str, Any]) -> Any:
if isinstance(inpt, features._Feature):
return inpt.mixup(**params)
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious to see how this will be adapted if we adopt my recommendation above to lose the augmentation specific kernels.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

me too :)

@vfdev-5 vfdev-5 force-pushed the proto-f-mid-level-features-ops branch from b023a27 to 97acb26 Compare June 30, 2022 15:22
@@ -69,3 +70,142 @@ def to_format(self, format: Union[str, BoundingBoxFormat]) -> BoundingBox:
return BoundingBox.new_like(
self, convert_bounding_box_format(self, old_format=self.format, new_format=format), format=format
)

def horizontal_flip(self) -> BoundingBox:
from torchvision.prototype.transforms import functional as _F

Choose a reason for hiding this comment

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

Btw if this happens on a hot path, this import (even if it's not the first run) may hurt perf (from my experience of a few years back)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, this is not ideal solution. Previously I tried to add the submodule as an attribute and somehow dataloader with multiple processes hangs due to that...
If you have any better ideas, please share

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah that's a fair point. This workaround is temporary for as long as we work on the API. We should seek a better solution prior finalising it. Potentially a refactoring and reorganization of the modules might be a solution here but that can be decided later.

Copy link
Contributor

Choose a reason for hiding this comment

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

Here is an alternative approach for this at #6476

Copy link
Contributor

@datumbox datumbox left a comment

Choose a reason for hiding this comment

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

I understand that things started piling up and it will help if we do the changes incrementally. I'm approving as in principle, I believe the approach is correct. Below I have some comments and questions for your consideration.

# Just output itself
# How dangerous to do this instead of raising an error ?
def pad(
self, padding: List[int], fill: Union[int, float, Sequence[float]] = 0, padding_mode: str = "constant"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't we support Sequence[int] as well? Did you face JIT-scriptability issues related to how it handles seq of ints and floats? If we add support, we need to put them in all places.

Copy link
Collaborator Author

@vfdev-5 vfdev-5 Jul 5, 2022

Choose a reason for hiding this comment

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

I think we can add Sequence[int] to the type hint (I didn't add it as it starts looking very bulky). JIT is not concerned as pad is not scriptable (in general torch script does not recognize Sequence and we should map it to List).
In following PRs, we have to make same type hint for all fill usages.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good. You wanna put a TODO on the code or you keep track of this elsewhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll be dealing with this in Transforms PR (next one, not yet sent)

@@ -40,12 +40,58 @@ def horizontal_flip_bounding_box(
).view(shape)


def horizontal_flip(inpt: Any) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

Why Any and not Union[Tensor, PIL.Image]?

Copy link
Collaborator Author

@vfdev-5 vfdev-5 Jul 4, 2022

Choose a reason for hiding this comment

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

If we replace Any by union[tensor, pil-image] should we keepelse branch ? In this case, it could be

def horizontal_flip(inpt: Union[torch.Tensor, PIL.Image.Image]) -> Union[torch.Tensor, PIL.Image.Image]:
    if isinstance(inpt, features._Feature):
        return inpt.horizontal_flip()
    elif isinstance(inpt, PIL.Image.Image):
        return horizontal_flip_image_pil(inpt)
    # elif isinstance(inpt, torch.Tensor):
    #     return horizontal_flip_image_tensor(inpt)
    # else:
    #     return inpt
    else:
        return horizontal_flip_image_tensor(inpt)

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed. As discussed earlier, the middle-layer shouldn't return the input as-is on else. I assume this will be removed on a follow up PR? Also we might want to play around with the order of the ifs and the type info, to see if there is any way we can make the middle layer JIT-scriptable.

Comment on lines +320 to +322
perspective_coeffs: List[float],
interpolation: int = _pil_constants.BICUBIC,
fill: Optional[Union[float, List[float], Tuple[float, ...]]] = 0,
fill: Optional[Union[float, List[float], Tuple[float, ...]]] = None,
Copy link
Contributor

@datumbox datumbox Jul 4, 2022

Choose a reason for hiding this comment

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

Here is my understanding for why this is OK. The change on fill doesn't have any effect on the actual operation due to the call on _parse_fill(). Concerning the type of perspective_coeffs I believe that the change actually fixes a bug because PIL breaks if you pass a number:

>>> img.transform(img.size, Image.PERSPECTIVE, 128).show()
TypeError: 'int' object is not subscriptable

@vfdev-5 vfdev-5 force-pushed the proto-f-mid-level-features-ops branch from a81e0a1 to 5bed289 Compare July 5, 2022 16:00
@vfdev-5 vfdev-5 force-pushed the proto-f-mid-level-features-ops branch from 5bed289 to 7b8d79b Compare July 5, 2022 16:00
@vfdev-5 vfdev-5 merged commit bd19fb8 into pytorch:main Jul 6, 2022
@vfdev-5 vfdev-5 deleted the proto-f-mid-level-features-ops branch July 6, 2022 07:40
@github-actions
Copy link

github-actions bot commented Jul 6, 2022

Hey @vfdev-5!

You merged this PR, but no labels were added. The list of valid labels is available at https://github.com/pytorch/vision/blob/main/.github/process_commit.py

facebook-github-bot pushed a commit that referenced this pull request Jul 6, 2022
Summary:
* Added mid-level ops and feature-based ops

* Fixing deadlock in dataloader with circular imports

* Added non-scalar fill support workaround for pad

* Removed comments

* int/float support for fill in pad op

* Updated type hints and removed bypass option from mid-level methods

* Minor nit fixes

Reviewed By: jdsgomes

Differential Revision: D37643902

fbshipit-source-id: e62e7274b3ead0c4e68ec5cf1fc8da7f2c0b72bf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants