Why there is a difference between batch and individual transforms?

Marcus_Wong: <p>pytorch                   2.1.2<br>
torchvision               0.16.2</p>
<p>I try use v2 transforms by individual with for loop:<br>
<code>pp_img1 = [preprocess(image) for image in orignal_images]</code><br>
and by<br>
batch :<br>
<code>pp_img2 = preprocess(orignal_images)</code></p>
<p>but i found the output is different after preprocess.<br>
Actually: <code>pp_img1[0]</code> and <code>pp_img2 [0]</code> are the same,<br>
but <code>pp_img1[1]</code> and <code>pp_img2[1]</code> … and so on are different</p>
<p>Should they be the same result? Or I misunderstand some concept?<br>
Why there is a difference between batch and individual transforms?</p>
<p>The full code:</p>
<pre><code class="lang-auto">import torch
from torchvision.transforms import v2
preprocess = v2.Compose([
    v2.ToTensor(),               # Convert to tensor (0, 1)
    v2.Normalize([0.5], [0.5]),  # Map to (-1, 1)
])
#individual 
pp_img1 = [preprocess(image) for image in orignal_images]
# batch
pp_img2 = preprocess(orignal_images)

Z = pp_img1 + pp_img2
show_images(torch.stack(Z))
</code></pre>
<p>pp_img1  + pp_img2  image:<br>
<div class="lightbox-wrapper"><a class="lightbox" href="https://discuss.pytorch.org/uploads/default/original/3X/b/e/be18ff84605a1dc50698e1c77c638290d72bb07f.png" data-download-href="https://discuss.pytorch.org/uploads/default/be18ff84605a1dc50698e1c77c638290d72bb07f" title="螢幕擷取畫面 2024-01-04 150906"><img src="https://discuss.pytorch.org/uploads/default/original/3X/b/e/be18ff84605a1dc50698e1c77c638290d72bb07f.png" alt="螢幕擷取畫面 2024-01-04 150906" data-base62-sha1="r7Ghd55NwcaO0WXGHBjalvmxbHN" width="690" height="124" data-dominant-color="C4C4C4"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">螢幕擷取畫面 2024-01-04 150906</span><span class="informations">729×132 3.15 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>

ptrblck: <p>It seems only the first sample is transformed in <code>v2.Normalize</code> as seen here:</p>
<pre data-code-wrap="python"><code class="lang-python">ref = [torch.randn(3, 4, 4) for _ in range(2)]

norm = v2.Normalize([0.5], [0.5])
out1 = [norm(r) for r in ref]
out2 = norm(ref)

print(torch.stack(out1) - torch.stack(out2))
# tensor([[[[ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000]],

#          [[ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000]],

#          [[ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000],
#           [ 0.0000,  0.0000,  0.0000,  0.0000]]],


#         [[[-0.2077, -2.1606, -3.0535,  0.2506],
#           [-0.1663,  0.7301, -3.8487, -0.4478],
#           [-1.8119, -0.1568, -0.2179, -2.6124],
#           [-0.6122, -1.1466, -1.0353, -0.2566]],

#          [[-0.8328, -2.4928, -1.3262, -0.8081],
#           [-0.3667, -1.4218, -1.9000, -1.0971],
#           [-1.5551,  0.6238, -1.2795, -1.7695],
#           [-0.1538, -1.3588, -1.2829, -1.9727]],

#          [[-0.6973, -0.9461, -2.5446,  1.0588],
#           [ 1.0722, -0.1794, -0.7449, -2.8416],
#           [-0.4963, -1.0180, -1.4314, -0.1204],
#           [ 0.4762, -0.5133, -0.5140, -3.4192]]]])

print(out2[1] - ref[1])
# tensor([[[0., 0., 0., 0.],
#          [0., 0., 0., 0.],
#          [0., 0., 0., 0.],
#          [0., 0., 0., 0.]],

#         [[0., 0., 0., 0.],
#          [0., 0., 0., 0.],
#          [0., 0., 0., 0.],
#          [0., 0., 0., 0.]],

#         [[0., 0., 0., 0.],
#          [0., 0., 0., 0.],
#          [0., 0., 0., 0.],
#          [0., 0., 0., 0.]]])
</code></pre>
<p><a class="mention" href="/u/pmeier">@pmeier</a> do you know if this is expected and an unsupported use case?</p>

pmeier: <p>The issue is that you are using plain tensor as images. In such a scenario, <a class="mention" href="/u/ptrblck">@ptrblck</a> is right in that only the first item is transformed for BC with v1. See the <a href="https://pytorch.org/vision/stable/auto_examples/transforms/plot_transforms_getting_started.html#what-do-i-pass-as-input" rel="noopener nofollow ugc">note in this section of the documentation</a>.</p>
<p>What you want to do is to wrap your images in our new tensor subclasses, e.g.</p>
<pre data-code-wrap="python"><code class="lang-python">from torchvision import tv_tensors

orignal_images = [tv_tensors.Image(image) for image in orignal_images]
</code></pre>
<p>After that there should be no difference between the two methods. Note that this step is mandatory for all inputs besides images.</p>
<p>Furthermore, <code>ToTensor</code> is deprecated. Use <code>v2.ToImage()</code> followed by a <code>v2.ToDtype(dtype=torch.float32, scale=True)</code> instead. The former will also handle the wrapping into <code>tv_tensors.Image</code> for you.</p>
<p>So basically your example will be solved by using</p>
<pre><code class="lang-auto">preprocess = v2.Compose([
    v2.ToImage(),
    v2.ToDtype(torch.float32, scale=True),
    v2.Normalize([0.5], [0.5]),
])
</code></pre>