diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 7f64d09da50..00000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: "\U0001F41B Bug Report" -about: Create a report to help us improve torchvision -title: '' -labels: '' -assignees: '' - ---- - -## πŸ› Bug - - - -## To Reproduce - -Steps to reproduce the behavior: - -1. -1. -1. - - - -## Expected behavior - - - -## Environment - -Please copy and paste the output from our -[environment collection script](https://github.com/raw/pytorch/pytorch/master/torch/utils/collect_env.py) -(or fill out the checklist below manually). - -You can get the script and run it with: -``` -wget https://github.com/raw/pytorch/pytorch/master/torch/utils/collect_env.py -# For security purposes, please check the contents of collect_env.py before running it. -python collect_env.py -``` - - - PyTorch / torchvision Version (e.g., 1.0 / 0.4.0): - - OS (e.g., Linux): - - How you installed PyTorch / torchvision (`conda`, `pip`, source): - - Build command you used (if compiling from source): - - Python version: - - CUDA/cuDNN version: - - GPU models and configuration: - - Any other relevant information: - -## Additional context - - diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000000..50907a60989 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,60 @@ +name: πŸ› Bug Report +description: Create a report to help us reproduce and fix the bug + +body: +- type: markdown + attributes: + value: > + #### Before submitting a bug, please make sure the issue hasn't been already addressed by searching through [the existing and past issues](https://github.com/pytorch/vision/issues?q=is%3Aissue+sort%3Acreated-desc+). +- type: textarea + attributes: + label: πŸ› Describe the bug + description: | + Please provide a clear and concise description of what the bug is. + + If relevant, add a minimal example so that we can reproduce the error by running the code. It is very important for he snippet to be as succinct (minimal) as possible, so please take time to trim down any irrelevant code to help us debug efficiently. We are going to copy-paste your code and we expect to get the same result as you did: avoid any external data, and include the relevant imports, etc. For example: + + ```python + # All necessary imports at the beginning + import torch + import torchvision + from torchvision.ops import nms + + # A succinct reproducing example trimmed down to the essential parts: + N = 5 + boxes = torch.rand(N, 4) # Note: the bug is here, we should enforce that x1 < x2 and y1 < y2! + scores = torch.rand(N) + nms(boxes, scores, iou_threshold=.9) + ``` + + If the code is too long (hopefully, it isn't), feel free to put it in a public gist and link it in the issue: https://gist.github.com. + + Please also paste or describe the results you observe instead of the expected results. If you observe an error, please paste the error message including the **full** traceback of the exception. It may be relevant to wrap error messages in ```` ```triple quotes blocks``` ````. + placeholder: | + A clear and concise description of what the bug is. + + ```python + Sample code to reproduce the problem + ``` + + ``` + The error message you got, with the full traceback. + ```` + validations: + required: true +- type: textarea + attributes: + label: Versions + description: | + Please run the following and paste the output below. + ```sh + wget https://github.com/raw/pytorch/pytorch/master/torch/utils/collect_env.py + # For security purposes, please check the contents of collect_env.py before running it. + python collect_env.py + ``` + validations: + required: true +- type: markdown + attributes: + value: > + Thanks for contributing πŸŽ‰! diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..bdb6d3614f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Usage questions + url: https://discuss.pytorch.org/ + about: Ask questions and discuss with other torchvision community members diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md deleted file mode 100644 index a3618080a87..00000000000 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: "\U0001F4DA Documentation" -about: Report an issue related to https://pytorch.org/docs -title: '' -labels: '' -assignees: '' - ---- - -## πŸ“š Documentation - - diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 00000000000..a7fb6c04c63 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,20 @@ +name: πŸ“š Documentation +description: Report an issue related to https://pytorch.org/vision/stable/index.html + +body: +- type: textarea + attributes: + label: πŸ“š The doc issue + description: > + A clear and concise description of what content in https://pytorch.org/vision/stable/index.html is an issue. If this has to do with the general https://pytorch.org website, please file an issue at https://github.com/pytorch/pytorch.github.io/issues/new/choose instead. If this has to do with https://pytorch.org/tutorials, please file an issue at https://github.com/pytorch/tutorials/issues/new. + validations: + required: true +- type: textarea + attributes: + label: Suggest a potential alternative/fix + description: > + Tell us how we could improve the documentation in this regard. +- type: markdown + attributes: + value: > + Thanks for contributing πŸŽ‰! diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md deleted file mode 100644 index 2187981e6d4..00000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: "\U0001F680Feature Request" -about: Submit a proposal/request for a new torchvision feature -title: '' -labels: '' -assignees: '' - ---- - -## πŸš€ Feature - - -## Motivation - - - -## Pitch - - - -## Alternatives - - - -## Additional context - - diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000000..85c727dbcf5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,32 @@ +name: πŸš€ Feature request +description: Submit a proposal/request for a new torchvision feature + +body: +- type: textarea + attributes: + label: πŸš€ The feature + description: > + A clear and concise description of the feature proposal + validations: + required: true +- type: textarea + attributes: + label: Motivation, pitch + description: > + Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., *"I'm working on X and would like Y to be possible"*. If this is related to another GitHub issue, please link here too. + validations: + required: true +- type: textarea + attributes: + label: Alternatives + description: > + A description of any alternative solutions or features you've considered, if any. +- type: textarea + attributes: + label: Additional context + description: > + Add any other context or screenshots about the feature request. +- type: markdown + attributes: + value: > + Thanks for contributing πŸŽ‰! diff --git a/.github/ISSUE_TEMPLATE/questions-help-support.md b/.github/ISSUE_TEMPLATE/questions-help-support.md deleted file mode 100644 index fb59e084128..00000000000 --- a/.github/ISSUE_TEMPLATE/questions-help-support.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: "❓Questions/Help/Support" -about: Do you need support? We have resources. -title: '' -labels: '' -assignees: '' - ---- - -## ❓ Questions and Help - -### Please note that this issue tracker is not a help form and this issue will be closed. - -We have a set of [listed resources available on the website](https://pytorch.org/resources). Our primary means of support is our discussion forum: - -- [Discussion Forum](https://discuss.pytorch.org/) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index 59479f23899..854c869a0de 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -24,7 +24,7 @@ number of channels, ``H`` and ``W`` are image height and width. A batch of Tensor Images is a tensor of ``(B, C, H, W)`` shape, where ``B`` is a number of images in the batch. -The expected range of the values of a tensor image is implicitely defined by +The expected range of the values of a tensor image is implicitly defined by the tensor dtype. Tensor images with a float dtype are expected to have values in ``[0, 1)``. Tensor images with an integer dtype are expected to have values in ``[0, MAX_DTYPE]`` where ``MAX_DTYPE`` is the largest value @@ -35,7 +35,7 @@ images of a given batch, but they will produce different transformations across calls. For reproducible transformations across calls, you may use :ref:`functional transforms `. -The following examples illustate the use of the available transforms: +The following examples illustrate the use of the available transforms: * :ref:`sphx_glr_auto_examples_plot_transforms.py` diff --git a/mypy.ini b/mypy.ini index 52ddce8ec51..de04d6d173e 100644 --- a/mypy.ini +++ b/mypy.ini @@ -20,10 +20,6 @@ ignore_errors=True ignore_errors = True -[mypy-torchvision.models.quantization.*] - -ignore_errors = True - [mypy-torchvision.ops.*] ignore_errors = True diff --git a/torchvision/datasets/ucf101.py b/torchvision/datasets/ucf101.py index 2c2952fab1a..ca8963efd75 100644 --- a/torchvision/datasets/ucf101.py +++ b/torchvision/datasets/ucf101.py @@ -14,7 +14,9 @@ class UCF101(VisionDataset): UCF101 is an action recognition video dataset. This dataset consider every video as a collection of video clips of fixed size, specified by ``frames_per_clip``, where the step in frames between each clip is given by - ``step_between_clips``. + ``step_between_clips``. The dataset itself can be downloaded from the dataset website; + annotations that ``annotation_path`` should be pointing to can be downloaded from `here + `. To give an example, for 2 videos with 10 and 15 frames respectively, if ``frames_per_clip=5`` and ``step_between_clips=5``, the dataset size will be (2 + 3) = 5, where the first two @@ -26,7 +28,8 @@ class UCF101(VisionDataset): Args: root (string): Root directory of the UCF101 Dataset. - annotation_path (str): path to the folder containing the split files + annotation_path (str): path to the folder containing the split files; + see docstring above for download instructions of these files frames_per_clip (int): number of frames in a clip. step_between_clips (int, optional): number of frames between each clip. fold (int, optional): which fold to use. Should be between 1 and 3. diff --git a/torchvision/io/__init__.py b/torchvision/io/__init__.py index 742344e6b0f..d0ec1b406f3 100644 --- a/torchvision/io/__init__.py +++ b/torchvision/io/__init__.py @@ -1,4 +1,5 @@ import torch +from typing import Any, Dict, Iterator from ._video_opt import ( Timebase, @@ -33,13 +34,13 @@ if _HAS_VIDEO_OPT: - def _has_video_opt(): + def _has_video_opt() -> bool: return True else: - def _has_video_opt(): + def _has_video_opt() -> bool: return False @@ -99,7 +100,7 @@ class VideoReader: Currently available options include ``['video', 'audio']`` """ - def __init__(self, path, stream="video"): + def __init__(self, path: str, stream: str = "video") -> None: if not _has_video_opt(): raise RuntimeError( "Not compiled with video_reader support, " @@ -109,7 +110,7 @@ def __init__(self, path, stream="video"): ) self._c = torch.classes.torchvision.Video(path, stream) - def __next__(self): + def __next__(self) -> Dict[str, Any]: """Decodes and returns the next frame of the current stream. Frames are encoded as a dict with mandatory data and pts fields, where data is a tensor, and pts is a @@ -126,10 +127,10 @@ def __next__(self): raise StopIteration return {"data": frame, "pts": pts} - def __iter__(self): + def __iter__(self) -> Iterator['VideoReader']: return self - def seek(self, time_s: float): + def seek(self, time_s: float) -> 'VideoReader': """Seek within current stream. Args: @@ -144,7 +145,7 @@ def seek(self, time_s: float): self._c.seek(time_s) return self - def get_metadata(self): + def get_metadata(self) -> Dict[str, Any]: """Returns video metadata Returns: @@ -152,7 +153,7 @@ def get_metadata(self): """ return self._c.get_metadata() - def set_current_stream(self, stream: str): + def set_current_stream(self, stream: str) -> bool: """Set current stream. Explicitly define the stream we are operating on. diff --git a/torchvision/models/quantization/googlenet.py b/torchvision/models/quantization/googlenet.py index bc1477d8f65..685815ac676 100644 --- a/torchvision/models/quantization/googlenet.py +++ b/torchvision/models/quantization/googlenet.py @@ -2,6 +2,8 @@ import torch import torch.nn as nn from torch.nn import functional as F +from typing import Any +from torch import Tensor from ..._internally_replaced_utils import load_state_dict_from_url from torchvision.models.googlenet import ( @@ -18,7 +20,13 @@ } -def googlenet(pretrained=False, progress=True, quantize=False, **kwargs): +def googlenet( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> "QuantizableGoogLeNet": + r"""GoogLeNet (Inception v1) model architecture from `"Going Deeper with Convolutions" `_. @@ -70,48 +78,51 @@ def googlenet(pretrained=False, progress=True, quantize=False, **kwargs): if not original_aux_logits: model.aux_logits = False - model.aux1 = None - model.aux2 = None + model.aux1 = None # type: ignore[assignment] + model.aux2 = None # type: ignore[assignment] return model class QuantizableBasicConv2d(BasicConv2d): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(QuantizableBasicConv2d, self).__init__(*args, **kwargs) self.relu = nn.ReLU() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: x = self.conv(x) x = self.bn(x) x = self.relu(x) return x - def fuse_model(self): + def fuse_model(self) -> None: torch.quantization.fuse_modules(self, ["conv", "bn", "relu"], inplace=True) class QuantizableInception(Inception): - def __init__(self, *args, **kwargs): - super(QuantizableInception, self).__init__( + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableInception, self).__init__( # type: ignore[misc] conv_block=QuantizableBasicConv2d, *args, **kwargs) self.cat = nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: outputs = self._forward(x) return self.cat.cat(outputs, 1) class QuantizableInceptionAux(InceptionAux): - - def __init__(self, *args, **kwargs): - super(QuantizableInceptionAux, self).__init__( - conv_block=QuantizableBasicConv2d, *args, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableInceptionAux, self).__init__( # type: ignore[misc] + conv_block=QuantizableBasicConv2d, + *args, + **kwargs + ) self.relu = nn.ReLU() self.dropout = nn.Dropout(0.7) - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: # aux1: N x 512 x 14 x 14, aux2: N x 528 x 14 x 14 x = F.adaptive_avg_pool2d(x, (4, 4)) # aux1: N x 512 x 4 x 4, aux2: N x 528 x 4 x 4 @@ -130,9 +141,9 @@ def forward(self, x): class QuantizableGoogLeNet(GoogLeNet): - - def __init__(self, *args, **kwargs): - super(QuantizableGoogLeNet, self).__init__( + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableGoogLeNet, self).__init__( # type: ignore[misc] blocks=[QuantizableBasicConv2d, QuantizableInception, QuantizableInceptionAux], *args, **kwargs @@ -140,7 +151,7 @@ def __init__(self, *args, **kwargs): self.quant = torch.quantization.QuantStub() self.dequant = torch.quantization.DeQuantStub() - def forward(self, x): + def forward(self, x: Tensor) -> GoogLeNetOutputs: x = self._transform_input(x) x = self.quant(x) x, aux1, aux2 = self._forward(x) @@ -153,7 +164,7 @@ def forward(self, x): else: return self.eager_outputs(x, aux2, aux1) - def fuse_model(self): + def fuse_model(self) -> None: r"""Fuse conv/bn/relu modules in googlenet model Fuse conv+bn+relu/ conv+relu/conv+bn modules to prepare for quantization. diff --git a/torchvision/models/quantization/inception.py b/torchvision/models/quantization/inception.py index 833d8fb8b75..6c6384c295a 100644 --- a/torchvision/models/quantization/inception.py +++ b/torchvision/models/quantization/inception.py @@ -3,6 +3,9 @@ import torch import torch.nn as nn import torch.nn.functional as F +from torch import Tensor +from typing import Any, List + from torchvision.models import inception as inception_module from torchvision.models.inception import InceptionOutputs from ..._internally_replaced_utils import load_state_dict_from_url @@ -22,7 +25,13 @@ } -def inception_v3(pretrained=False, progress=True, quantize=False, **kwargs): +def inception_v3( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> "QuantizableInception3": + r"""Inception v3 model architecture from `"Rethinking the Inception Architecture for Computer Vision" `_. @@ -84,68 +93,93 @@ def inception_v3(pretrained=False, progress=True, quantize=False, **kwargs): class QuantizableBasicConv2d(inception_module.BasicConv2d): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(QuantizableBasicConv2d, self).__init__(*args, **kwargs) self.relu = nn.ReLU() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: x = self.conv(x) x = self.bn(x) x = self.relu(x) return x - def fuse_model(self): + def fuse_model(self) -> None: torch.quantization.fuse_modules(self, ["conv", "bn", "relu"], inplace=True) class QuantizableInceptionA(inception_module.InceptionA): - def __init__(self, *args, **kwargs): - super(QuantizableInceptionA, self).__init__(conv_block=QuantizableBasicConv2d, *args, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableInceptionA, self).__init__( # type: ignore[misc] + conv_block=QuantizableBasicConv2d, + *args, + **kwargs + ) self.myop = nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: outputs = self._forward(x) return self.myop.cat(outputs, 1) class QuantizableInceptionB(inception_module.InceptionB): - def __init__(self, *args, **kwargs): - super(QuantizableInceptionB, self).__init__(conv_block=QuantizableBasicConv2d, *args, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableInceptionB, self).__init__( # type: ignore[misc] + conv_block=QuantizableBasicConv2d, + *args, + **kwargs + ) self.myop = nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: outputs = self._forward(x) return self.myop.cat(outputs, 1) class QuantizableInceptionC(inception_module.InceptionC): - def __init__(self, *args, **kwargs): - super(QuantizableInceptionC, self).__init__(conv_block=QuantizableBasicConv2d, *args, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableInceptionC, self).__init__( # type: ignore[misc] + conv_block=QuantizableBasicConv2d, + *args, + **kwargs + ) self.myop = nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: outputs = self._forward(x) return self.myop.cat(outputs, 1) class QuantizableInceptionD(inception_module.InceptionD): - def __init__(self, *args, **kwargs): - super(QuantizableInceptionD, self).__init__(conv_block=QuantizableBasicConv2d, *args, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableInceptionD, self).__init__( # type: ignore[misc] + conv_block=QuantizableBasicConv2d, + *args, + **kwargs + ) self.myop = nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: outputs = self._forward(x) return self.myop.cat(outputs, 1) class QuantizableInceptionE(inception_module.InceptionE): - def __init__(self, *args, **kwargs): - super(QuantizableInceptionE, self).__init__(conv_block=QuantizableBasicConv2d, *args, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableInceptionE, self).__init__( # type: ignore[misc] + conv_block=QuantizableBasicConv2d, + *args, + **kwargs + ) self.myop1 = nn.quantized.FloatFunctional() self.myop2 = nn.quantized.FloatFunctional() self.myop3 = nn.quantized.FloatFunctional() - def _forward(self, x): + def _forward(self, x: Tensor) -> List[Tensor]: branch1x1 = self.branch1x1(x) branch3x3 = self.branch3x3_1(x) @@ -166,18 +200,28 @@ def _forward(self, x): outputs = [branch1x1, branch3x3, branch3x3dbl, branch_pool] return outputs - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: outputs = self._forward(x) return self.myop3.cat(outputs, 1) class QuantizableInceptionAux(inception_module.InceptionAux): - def __init__(self, *args, **kwargs): - super(QuantizableInceptionAux, self).__init__(conv_block=QuantizableBasicConv2d, *args, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableInceptionAux, self).__init__( # type: ignore[misc] + conv_block=QuantizableBasicConv2d, + *args, + **kwargs + ) class QuantizableInception3(inception_module.Inception3): - def __init__(self, num_classes=1000, aux_logits=True, transform_input=False): + def __init__( + self, + num_classes: int = 1000, + aux_logits: bool = True, + transform_input: bool = False, + ) -> None: super(QuantizableInception3, self).__init__( num_classes=num_classes, aux_logits=aux_logits, @@ -195,7 +239,7 @@ def __init__(self, num_classes=1000, aux_logits=True, transform_input=False): self.quant = torch.quantization.QuantStub() self.dequant = torch.quantization.DeQuantStub() - def forward(self, x): + def forward(self, x: Tensor) -> InceptionOutputs: x = self._transform_input(x) x = self.quant(x) x, aux = self._forward(x) @@ -208,7 +252,7 @@ def forward(self, x): else: return self.eager_outputs(x, aux) - def fuse_model(self): + def fuse_model(self) -> None: r"""Fuse conv/bn/relu modules in inception model Fuse conv+bn+relu/ conv+relu/conv+bn modules to prepare for quantization. diff --git a/torchvision/models/quantization/mobilenetv2.py b/torchvision/models/quantization/mobilenetv2.py index 857d919b1fa..f914fdc7815 100644 --- a/torchvision/models/quantization/mobilenetv2.py +++ b/torchvision/models/quantization/mobilenetv2.py @@ -1,5 +1,10 @@ from torch import nn +from torch import Tensor + from ..._internally_replaced_utils import load_state_dict_from_url + +from typing import Any + from torchvision.models.mobilenetv2 import InvertedResidual, ConvBNReLU, MobileNetV2, model_urls from torch.quantization import QuantStub, DeQuantStub, fuse_modules from .utils import _replace_relu, quantize_model @@ -14,24 +19,24 @@ class QuantizableInvertedResidual(InvertedResidual): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(QuantizableInvertedResidual, self).__init__(*args, **kwargs) self.skip_add = nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: if self.use_res_connect: return self.skip_add.add(x, self.conv(x)) else: return self.conv(x) - def fuse_model(self): + def fuse_model(self) -> None: for idx in range(len(self.conv)): if type(self.conv[idx]) == nn.Conv2d: fuse_modules(self.conv, [str(idx), str(idx + 1)], inplace=True) class QuantizableMobileNetV2(MobileNetV2): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: """ MobileNet V2 main class @@ -42,13 +47,13 @@ def __init__(self, *args, **kwargs): self.quant = QuantStub() self.dequant = DeQuantStub() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: x = self.quant(x) x = self._forward_impl(x) x = self.dequant(x) return x - def fuse_model(self): + def fuse_model(self) -> None: for m in self.modules(): if type(m) == ConvBNReLU: fuse_modules(m, ['0', '1', '2'], inplace=True) @@ -56,7 +61,12 @@ def fuse_model(self): m.fuse_model() -def mobilenet_v2(pretrained=False, progress=True, quantize=False, **kwargs): +def mobilenet_v2( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableMobileNetV2: """ Constructs a MobileNetV2 architecture from `"MobileNetV2: Inverted Residuals and Linear Bottlenecks" diff --git a/torchvision/models/quantization/mobilenetv3.py b/torchvision/models/quantization/mobilenetv3.py index 5462af89127..a1aa9d7d4bd 100644 --- a/torchvision/models/quantization/mobilenetv3.py +++ b/torchvision/models/quantization/mobilenetv3.py @@ -17,23 +17,28 @@ class QuantizableSqueezeExcitation(SqueezeExcitation): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.skip_mul = nn.quantized.FloatFunctional() def forward(self, input: Tensor) -> Tensor: return self.skip_mul.mul(self._scale(input, False), input) - def fuse_model(self): + def fuse_model(self) -> None: fuse_modules(self, ['fc1', 'relu'], inplace=True) class QuantizableInvertedResidual(InvertedResidual): - def __init__(self, *args, **kwargs): - super().__init__(*args, se_layer=QuantizableSqueezeExcitation, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__( # type: ignore[misc] + se_layer=QuantizableSqueezeExcitation, + *args, + **kwargs + ) self.skip_add = nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: if self.use_res_connect: return self.skip_add.add(x, self.block(x)) else: @@ -41,7 +46,7 @@ def forward(self, x): class QuantizableMobileNetV3(MobileNetV3): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: """ MobileNet V3 main class @@ -52,13 +57,13 @@ def __init__(self, *args, **kwargs): self.quant = QuantStub() self.dequant = DeQuantStub() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: x = self.quant(x) x = self._forward_impl(x) x = self.dequant(x) return x - def fuse_model(self): + def fuse_model(self) -> None: for m in self.modules(): if type(m) == ConvBNActivation: modules_to_fuse = ['0', '1'] @@ -74,7 +79,7 @@ def _load_weights( model: QuantizableMobileNetV3, model_url: Optional[str], progress: bool, -): +) -> None: if model_url is None: raise ValueError("No checkpoint is available for {}".format(arch)) state_dict = load_state_dict_from_url(model_url, progress=progress) @@ -88,8 +93,9 @@ def _mobilenet_v3_model( pretrained: bool, progress: bool, quantize: bool, - **kwargs: Any -): + **kwargs: Any, +) -> QuantizableMobileNetV3: + model = QuantizableMobileNetV3(inverted_residual_setting, last_channel, block=QuantizableInvertedResidual, **kwargs) _replace_relu(model) @@ -112,7 +118,12 @@ def _mobilenet_v3_model( return model -def mobilenet_v3_large(pretrained=False, progress=True, quantize=False, **kwargs): +def mobilenet_v3_large( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableMobileNetV3: """ Constructs a MobileNetV3 Large architecture from `"Searching for MobileNetV3" `_. diff --git a/torchvision/models/quantization/resnet.py b/torchvision/models/quantization/resnet.py index 2f3f50e8013..8f87e40ec3d 100644 --- a/torchvision/models/quantization/resnet.py +++ b/torchvision/models/quantization/resnet.py @@ -1,6 +1,9 @@ import torch from torchvision.models.resnet import Bottleneck, BasicBlock, ResNet, model_urls import torch.nn as nn +from torch import Tensor +from typing import Any, Type, Union, List + from ..._internally_replaced_utils import load_state_dict_from_url from torch.quantization import fuse_modules from .utils import _replace_relu, quantize_model @@ -20,11 +23,11 @@ class QuantizableBasicBlock(BasicBlock): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(QuantizableBasicBlock, self).__init__(*args, **kwargs) self.add_relu = torch.nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: identity = x out = self.conv1(x) @@ -41,7 +44,7 @@ def forward(self, x): return out - def fuse_model(self): + def fuse_model(self) -> None: torch.quantization.fuse_modules(self, [['conv1', 'bn1', 'relu'], ['conv2', 'bn2']], inplace=True) if self.downsample: @@ -49,13 +52,13 @@ def fuse_model(self): class QuantizableBottleneck(Bottleneck): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(QuantizableBottleneck, self).__init__(*args, **kwargs) self.skip_add_relu = nn.quantized.FloatFunctional() self.relu1 = nn.ReLU(inplace=False) self.relu2 = nn.ReLU(inplace=False) - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: identity = x out = self.conv1(x) out = self.bn1(out) @@ -73,7 +76,7 @@ def forward(self, x): return out - def fuse_model(self): + def fuse_model(self) -> None: fuse_modules(self, [['conv1', 'bn1', 'relu1'], ['conv2', 'bn2', 'relu2'], ['conv3', 'bn3']], inplace=True) @@ -83,13 +86,13 @@ def fuse_model(self): class QuantizableResNet(ResNet): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(QuantizableResNet, self).__init__(*args, **kwargs) self.quant = torch.quantization.QuantStub() self.dequant = torch.quantization.DeQuantStub() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: x = self.quant(x) # Ensure scriptability # super(QuantizableResNet,self).forward(x) @@ -98,7 +101,7 @@ def forward(self, x): x = self.dequant(x) return x - def fuse_model(self): + def fuse_model(self) -> None: r"""Fuse conv/bn/relu modules in resnet models Fuse conv+bn+relu/ Conv+relu/conv+Bn modules to prepare for quantization. @@ -112,7 +115,16 @@ def fuse_model(self): m.fuse_model() -def _resnet(arch, block, layers, pretrained, progress, quantize, **kwargs): +def _resnet( + arch: str, + block: Type[Union[BasicBlock, Bottleneck]], + layers: List[int], + pretrained: bool, + progress: bool, + quantize: bool, + **kwargs: Any, +) -> QuantizableResNet: + model = QuantizableResNet(block, layers, **kwargs) _replace_relu(model) if quantize: @@ -135,7 +147,12 @@ def _resnet(arch, block, layers, pretrained, progress, quantize, **kwargs): return model -def resnet18(pretrained=False, progress=True, quantize=False, **kwargs): +def resnet18( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableResNet: r"""ResNet-18 model from `"Deep Residual Learning for Image Recognition" `_ @@ -148,7 +165,13 @@ def resnet18(pretrained=False, progress=True, quantize=False, **kwargs): quantize, **kwargs) -def resnet50(pretrained=False, progress=True, quantize=False, **kwargs): +def resnet50( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableResNet: + r"""ResNet-50 model from `"Deep Residual Learning for Image Recognition" `_ @@ -161,7 +184,12 @@ def resnet50(pretrained=False, progress=True, quantize=False, **kwargs): quantize, **kwargs) -def resnext101_32x8d(pretrained=False, progress=True, quantize=False, **kwargs): +def resnext101_32x8d( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableResNet: r"""ResNeXt-101 32x8d model from `"Aggregated Residual Transformation for Deep Neural Networks" `_ diff --git a/torchvision/models/quantization/shufflenetv2.py b/torchvision/models/quantization/shufflenetv2.py index 17885015772..4f0861dcb30 100644 --- a/torchvision/models/quantization/shufflenetv2.py +++ b/torchvision/models/quantization/shufflenetv2.py @@ -1,12 +1,12 @@ import torch import torch.nn as nn +from torch import Tensor +from typing import Any + from ..._internally_replaced_utils import load_state_dict_from_url -import torchvision.models.shufflenetv2 -import sys +from torchvision.models import shufflenetv2 from .utils import _replace_relu, quantize_model -shufflenetv2 = sys.modules['torchvision.models.shufflenetv2'] - __all__ = [ 'QuantizableShuffleNetV2', 'shufflenet_v2_x0_5', 'shufflenet_v2_x1_0', 'shufflenet_v2_x1_5', 'shufflenet_v2_x2_0' @@ -22,16 +22,16 @@ class QuantizableInvertedResidual(shufflenetv2.InvertedResidual): - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(QuantizableInvertedResidual, self).__init__(*args, **kwargs) self.cat = nn.quantized.FloatFunctional() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: if self.stride == 1: x1, x2 = x.chunk(2, dim=1) - out = self.cat.cat((x1, self.branch2(x2)), dim=1) + out = self.cat.cat([x1, self.branch2(x2)], dim=1) else: - out = self.cat.cat((self.branch1(x), self.branch2(x)), dim=1) + out = self.cat.cat([self.branch1(x), self.branch2(x)], dim=1) out = shufflenetv2.channel_shuffle(out, 2) @@ -39,18 +39,23 @@ def forward(self, x): class QuantizableShuffleNetV2(shufflenetv2.ShuffleNetV2): - def __init__(self, *args, **kwargs): - super(QuantizableShuffleNetV2, self).__init__(*args, inverted_residual=QuantizableInvertedResidual, **kwargs) + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + def __init__(self, *args: Any, **kwargs: Any) -> None: + super(QuantizableShuffleNetV2, self).__init__( # type: ignore[misc] + *args, + inverted_residual=QuantizableInvertedResidual, + **kwargs + ) self.quant = torch.quantization.QuantStub() self.dequant = torch.quantization.DeQuantStub() - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: x = self.quant(x) x = self._forward_impl(x) x = self.dequant(x) return x - def fuse_model(self): + def fuse_model(self) -> None: r"""Fuse conv/bn/relu modules in shufflenetv2 model Fuse conv+bn+relu/ conv+relu/conv+bn modules to prepare for quantization. @@ -74,7 +79,15 @@ def fuse_model(self): ) -def _shufflenetv2(arch, pretrained, progress, quantize, *args, **kwargs): +def _shufflenetv2( + arch: str, + pretrained: bool, + progress: bool, + quantize: bool, + *args: Any, + **kwargs: Any, +) -> QuantizableShuffleNetV2: + model = QuantizableShuffleNetV2(*args, **kwargs) _replace_relu(model) @@ -98,7 +111,12 @@ def _shufflenetv2(arch, pretrained, progress, quantize, *args, **kwargs): return model -def shufflenet_v2_x0_5(pretrained=False, progress=True, quantize=False, **kwargs): +def shufflenet_v2_x0_5( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableShuffleNetV2: """ Constructs a ShuffleNetV2 with 0.5x output channels, as described in `"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" @@ -113,7 +131,12 @@ def shufflenet_v2_x0_5(pretrained=False, progress=True, quantize=False, **kwargs [4, 8, 4], [24, 48, 96, 192, 1024], **kwargs) -def shufflenet_v2_x1_0(pretrained=False, progress=True, quantize=False, **kwargs): +def shufflenet_v2_x1_0( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableShuffleNetV2: """ Constructs a ShuffleNetV2 with 1.0x output channels, as described in `"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" @@ -128,7 +151,12 @@ def shufflenet_v2_x1_0(pretrained=False, progress=True, quantize=False, **kwargs [4, 8, 4], [24, 116, 232, 464, 1024], **kwargs) -def shufflenet_v2_x1_5(pretrained=False, progress=True, quantize=False, **kwargs): +def shufflenet_v2_x1_5( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableShuffleNetV2: """ Constructs a ShuffleNetV2 with 1.5x output channels, as described in `"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" @@ -143,7 +171,12 @@ def shufflenet_v2_x1_5(pretrained=False, progress=True, quantize=False, **kwargs [4, 8, 4], [24, 176, 352, 704, 1024], **kwargs) -def shufflenet_v2_x2_0(pretrained=False, progress=True, quantize=False, **kwargs): +def shufflenet_v2_x2_0( + pretrained: bool = False, + progress: bool = True, + quantize: bool = False, + **kwargs: Any, +) -> QuantizableShuffleNetV2: """ Constructs a ShuffleNetV2 with 2.0x output channels, as described in `"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" diff --git a/torchvision/models/quantization/utils.py b/torchvision/models/quantization/utils.py index bf23c9a9332..c195d162482 100644 --- a/torchvision/models/quantization/utils.py +++ b/torchvision/models/quantization/utils.py @@ -2,7 +2,7 @@ from torch import nn -def _replace_relu(module): +def _replace_relu(module: nn.Module) -> None: reassign = {} for name, mod in module.named_children(): _replace_relu(mod) @@ -16,7 +16,7 @@ def _replace_relu(module): module._modules[key] = value -def quantize_model(model, backend): +def quantize_model(model: nn.Module, backend: str) -> None: _dummy_input_data = torch.rand(1, 3, 299, 299) if backend not in torch.backends.quantized.supported_engines: raise RuntimeError("Quantized backend not supported ") @@ -24,15 +24,16 @@ def quantize_model(model, backend): model.eval() # Make sure that weight qconfig matches that of the serialized models if backend == 'fbgemm': - model.qconfig = torch.quantization.QConfig( + model.qconfig = torch.quantization.QConfig( # type: ignore[assignment] activation=torch.quantization.default_observer, weight=torch.quantization.default_per_channel_weight_observer) elif backend == 'qnnpack': - model.qconfig = torch.quantization.QConfig( + model.qconfig = torch.quantization.QConfig( # type: ignore[assignment] activation=torch.quantization.default_observer, weight=torch.quantization.default_weight_observer) - model.fuse_model() + # TODO https://github.com/pytorch/vision/pull/4232#pullrequestreview-730461659 + model.fuse_model() # type: ignore[operator] torch.quantization.prepare(model, inplace=True) model(_dummy_input_data) torch.quantization.convert(model, inplace=True) diff --git a/torchvision/transforms/autoaugment.py b/torchvision/transforms/autoaugment.py index 4db0f9fea35..8013d637ae1 100644 --- a/torchvision/transforms/autoaugment.py +++ b/torchvision/transforms/autoaugment.py @@ -192,9 +192,9 @@ def _get_magnitudes(self, num_bins: int, image_size: List[int]) -> Dict[str, Tup "Sharpness": (torch.linspace(0.0, 0.9, num_bins), True), "Posterize": (8 - (torch.arange(num_bins) / ((num_bins - 1) / 4)).round().int(), False), "Solarize": (torch.linspace(256.0, 0.0, num_bins), False), - "AutoContrast": (torch.tensor(float('nan')), False), - "Equalize": (torch.tensor(float('nan')), False), - "Invert": (torch.tensor(float('nan')), False), + "AutoContrast": (torch.tensor(0.0), False), + "Equalize": (torch.tensor(0.0), False), + "Invert": (torch.tensor(0.0), False), } @staticmethod @@ -230,8 +230,7 @@ def forward(self, img: Tensor) -> Tensor: if probs[i] <= p: op_meta = self._get_magnitudes(10, F.get_image_size(img)) magnitudes, signed = op_meta[op_name] - magnitude = float(magnitudes[magnitude_id].item()) \ - if not magnitudes.isnan().all() and magnitude_id is not None else 0.0 + magnitude = float(magnitudes[magnitude_id].item()) if magnitude_id is not None else 0.0 if signed and signs[i] == 0: magnitude *= -1.0 img = _apply_op(img, op_name, magnitude, interpolation=self.interpolation, fill=fill) @@ -279,9 +278,9 @@ def _get_magnitudes(self, num_bins: int) -> Dict[str, Tuple[Tensor, bool]]: "Sharpness": (torch.linspace(0.0, 0.99, num_bins), True), "Posterize": (8 - (torch.arange(num_bins) / ((num_bins - 1) / 6)).round().int(), False), "Solarize": (torch.linspace(256.0, 0.0, num_bins), False), - "AutoContrast": (torch.tensor(float('nan')), False), - "Equalize": (torch.tensor(float('nan')), False), - "Invert": (torch.tensor(float('nan')), False), + "AutoContrast": (torch.tensor(0.0), False), + "Equalize": (torch.tensor(0.0), False), + "Invert": (torch.tensor(0.0), False), } def forward(self, img: Tensor): @@ -303,7 +302,7 @@ def forward(self, img: Tensor): op_name = list(op_meta.keys())[op_index] magnitudes, signed = op_meta[op_name] magnitude = float(magnitudes[torch.randint(len(magnitudes), (1,), dtype=torch.long)].item()) \ - if not magnitudes.isnan().all() else 0.0 + if magnitudes.ndim > 0 else 0.0 if signed and torch.randint(2, (1,)): magnitude *= -1.0