From 933d9eb55db9ad97369c5c9f713c38e9251d7ae5 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Tue, 14 Sep 2021 05:36:40 -0500 Subject: [PATCH 1/2] Fixes #62396 Fixed output size to match opencv, scikit-image, scipy if scale factor is specified --- aten/src/ATen/native/UpSample.cpp | 3 ++- test/test_nn.py | 40 ++++++++++++++++++++++--------- torch/nn/functional.py | 6 +++-- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/aten/src/ATen/native/UpSample.cpp b/aten/src/ATen/native/UpSample.cpp index f703f8a4f5664..8ff4303485058 100644 --- a/aten/src/ATen/native/UpSample.cpp +++ b/aten/src/ATen/native/UpSample.cpp @@ -21,7 +21,8 @@ TORCH_API c10::SmallVector compute_output_size( TORCH_CHECK(static_cast(scale_factors->size()) == spatial_dimensions); c10::SmallVector ret; for (int i = 0; i < spatial_dimensions; ++i) { - ret.push_back(static_cast(input_size[i+2]) * scale_factors.value()[i]); + // we perform round (i.e. int(0.5 + x)) to match opencv, scipy, scikit-image output size + ret.push_back(0.5 + static_cast(input_size[i+2]) * scale_factors.value()[i]); } return ret; } diff --git a/test/test_nn.py b/test/test_nn.py index f5b435f0d0d24..fabdc0309d097 100644 --- a/test/test_nn.py +++ b/test/test_nn.py @@ -10444,6 +10444,18 @@ def test_upsamplingNearest1d(self): input = torch.randn(1, 1, 2, requires_grad=True) gradcheck(lambda x: F.interpolate(x, 4, mode='nearest'), [input]) + # Check https://github.com/pytorch/pytorch/issues/62396 + test_scales = [0.1234, 0.9999, 1.8] + isize = 32 + expected_out_sizes = [int(0.5 + s * isize) for s in test_scales] + t_in = torch.randint(0, 256, size=(1, 1, isize), dtype=torch.float) + for r in [True, False]: + for s, expected_osize in zip(test_scales, expected_out_sizes): + t_out = F.interpolate( + t_in, scale_factor=s, recompute_scale_factor=r, mode="nearest" + ) + self.assertEqual(t_out.shape[-1], expected_osize) + def test_upsamplingLinear1d(self): for align_corners in [True, False]: kwargs = dict(mode='linear', align_corners=align_corners) @@ -10507,17 +10519,20 @@ def test_upsamplingBicubic2d(self): def test_upsampling_not_recompute_scale_factor(self): # test output against known input: result must match opencv + # opencv gives output of shape (5, 5, 2) in_t = torch.arange(8.).view(1, 2, 2, 2) expected_out_t = torch.tensor( - [[[[-0.32725, -0.08843, 0.37933, 0.79744], - [0.15039, 0.38921, 0.85697, 1.27508], - [1.08591, 1.32473, 1.79249, 2.21060], - [1.92213, 2.16095, 2.62871, 3.04682]], - - [[3.67275, 3.91157, 4.37933, 4.79744], - [4.15039, 4.38921, 4.85697, 5.27508], - [5.08591, 5.32473, 5.79249, 6.21060], - [5.92213, 6.16095, 6.62871, 7.04682]]]]) + [[[[-0.32725, -0.08843, 0.37933, 0.79744, 0.88296], + [0.15039, 0.38921, 0.85697, 1.27508, 1.3606], + [1.08591, 1.32473, 1.79249, 2.21060, 2.29613], + [1.92213, 2.16095, 2.62871, 3.04682, 3.13234], + [2.09318, 2.33200, 2.79976, 3.21787, 3.30340]], + + [[3.67275, 3.91157, 4.37933, 4.79744, 4.88296], + [4.15039, 4.38921, 4.85697, 5.27508, 5.36060], + [5.08591, 5.32473, 5.79249, 6.21060, 6.29613], + [5.92213, 6.16095, 6.62871, 7.04682, 7.13234], + [6.09318, 6.33200, 6.79976, 7.21787, 7.30340]]]]) if IS_PPC: # Both OpenCV and PyTorch give a slightly different result on PPC expected_out_t = torch.tensor( @@ -10532,7 +10547,10 @@ def test_upsampling_not_recompute_scale_factor(self): [5.92212, 6.16094, 6.62870, 7.04680]]]]) out_t = F.interpolate(in_t, scale_factor=2.3, mode='bicubic', align_corners=False, recompute_scale_factor=False) torch.set_printoptions(precision=5) - self.assertEqual(out_t, expected_out_t, atol=1e-4, rtol=0) + if IS_PPC: + self.assertEqual(out_t[..., :3, :3], expected_out_t[..., :3, :3], atol=1e-4, rtol=0) + else: + self.assertEqual(out_t, expected_out_t, atol=1e-4, rtol=0) device_list = ['cpu'] if TEST_CUDA: @@ -10545,7 +10563,7 @@ def test_upsampling_not_recompute_scale_factor(self): for scale_factor in [0.6, 1.6, 2.3]: in_t = torch.ones(2, 2, 2, 2).to(device) out_t = F.interpolate(in_t, scale_factor=scale_factor, **kwargs) - out_size = int(math.floor(in_t.shape[-1] * scale_factor)) + out_size = int(math.floor(0.5 + in_t.shape[-1] * scale_factor)) self.assertEqual(torch.ones(2, 2, out_size, out_size), out_t.data, atol=1e-5, rtol=0) input = torch.randn(2, 2, 2, 2, requires_grad=True) diff --git a/torch/nn/functional.py b/torch/nn/functional.py index 4b0449c8f5672..d597e3b22c361 100644 --- a/torch/nn/functional.py +++ b/torch/nn/functional.py @@ -3697,13 +3697,15 @@ def interpolate(input: Tensor, size: Optional[int] = None, scale_factor: Optiona # The C++ code will recompute it based on the (integer) output size. if not torch.jit.is_scripting() and torch._C._get_tracing_state(): # make scale_factor a tensor in tracing so constant doesn't get baked in + # we perform round (i.e. floor(0.5 + x)) to match opencv, scipy, scikit-image output size output_size = [ - (torch.floor((input.size(i + 2).float() * torch.tensor(scale_factors[i], dtype=torch.float32)).float())) + (torch.floor(0.5 + (input.size(i + 2).float() * torch.tensor(scale_factors[i], dtype=torch.float32)).float())) for i in range(dim) ] else: assert scale_factors is not None - output_size = [int(math.floor(float(input.size(i + 2)) * scale_factors[i])) for i in range(dim)] + # we perform round (i.e. floor(0.5 + x)) to match opencv, scipy, scikit-image output size + output_size = [int(math.floor(0.5 + float(input.size(i + 2)) * scale_factors[i])) for i in range(dim)] scale_factors = None if input.dim() == 3 and mode == "nearest": From bf368474d51a8422ae04c9d4fd8aa1c84853f406 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Wed, 17 Nov 2021 10:45:48 -0600 Subject: [PATCH 2/2] Enabled commented tests --- test/test_nn.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/test_nn.py b/test/test_nn.py index 5486cc753c215..7aba328befb39 100644 --- a/test/test_nn.py +++ b/test/test_nn.py @@ -14837,9 +14837,7 @@ def test_upsamplingNearestExact1d_rescale(self, device): # Checks https://github.com/pytorch/pytorch/issues/62237 isize = 20 in_t = torch.arange(isize, dtype=torch.float, device=device).unsqueeze(0).unsqueeze(0) - # for s in [1.00001, 0.99999]: # 0.9999 case is broken - # See issue: https://github.com/pytorch/pytorch/issues/62396 - for s in [1.00001, ]: + for s in [1.00001, 0.99999]: out_t = F.interpolate( in_t, scale_factor=s, recompute_scale_factor=False, mode="nearest-exact" ) @@ -14847,9 +14845,7 @@ def test_upsamplingNearestExact1d_rescale(self, device): self.assertEqual(out_t, expected_out, msg=f"scale: {s}") # checks data duplication if output_size == 2 * input_size - # for s in [2.00001, 1.99999]: # 1.99999 case is broken - # See issue: https://github.com/pytorch/pytorch/issues/62396 - for s in [2.00001, ]: + for s in [2.00001, 1.99999]: out_t = F.interpolate( in_t, scale_factor=s, recompute_scale_factor=False, mode="nearest-exact" )