Changing transformation applied to data during training

username: mmackay
cooked: <p>I would like to change the transformation I am applying to data during training. For example, I might want to change the size of the random crop I am taking of images from 32 to 28 or change the amount of jitter applied to an image. Is there a way of doing this that works with the DataLoader class when num_workers &gt; 0?</p>
<p>Thanks for the help!</p>

username: devansh20la
cooked: <p>You can apply tranformations to the data loader class.</p>
<pre><code class="lang-auto">data_transform = transforms.Compose([
        transforms.RandomSizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406],
                             std=[0.229, 0.224, 0.225])])

data_loader = datasets.ImageFolder(root='data/train',
                                           transform=data_transform)

train_loader = torch.utils.data.DataLoader(data_loader,
                                             batch_size=4, shuffle=True,
                                             num_workers=4)
</code></pre>
<p>Additonal resources on writing custom data loader class can be found here: <a href="http://pytorch.org/tutorials/beginner/data_loading_tutorial.html#" rel="nofollow noopener">http://pytorch.org/tutorials/beginner/data_loading_tutorial.html#</a></p>

username: mmackay
cooked: <p>I’m aware of this. I want to change the transformation that the dataloader is using during training.</p>

username: ptrblck
cooked: <p>You could write your own <code>Dataset</code> and add some logic into the different transformations.<br>
I created a small code snippet using different “stages” for the transformations.<br>
You should apply your logic there, since I’m not sure, when to switch the transforms.<br>
Take care of the different sizes, since your model might complain about a size mismatching! <img src="https://discuss.pytorch.org/images/emoji/apple/wink.png?v=5" title=":wink:" class="emoji" alt=":wink:"></p>
<pre><code class="lang-auto">import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
import torchvision.transforms.functional as TF


class MyData(Dataset):
    def __init__(self):
        self.images = [TF.to_pil_image(x) for x in torch.ByteTensor(10, 3, 48, 48)]
        self.set_stage(0) # initial stage
        
    def __getitem__(self, index):
        image = self.images[index]
        
        # Just apply your transformations here
        image = self.crop(image)
        x = TF.to_tensor(image)
        return x
        
    def set_stage(self, stage):
        if stage == 0:
            print('Using (32, 32) crops')
            self.crop = transforms.RandomCrop((32, 32))
        elif stage == 1:
            print('Using (28, 28) crops')
            self.crop = transforms.RandomCrop((28, 28))
        
    def __len__(self):
        return len(self.images)


dataset = MyData()
loader = DataLoader(dataset,
                    batch_size=2,
                    num_workers=2,
                    shuffle=True)

for batch_idx, data in enumerate(loader):
    print('Batch idx {}, data shape {}'.format(
        batch_idx, data.shape))
    
loader.dataset.set_stage(1)

for batch_idx, data in enumerate(loader):
    print('Batch idx {}, data shape {}'.format(
        batch_idx, data.shape))
</code></pre>

username: mmackay
cooked: <p>Thank you, this is exactly what I need!</p>

username: Brando_Miranda
cooked: <p>I know that the solution you provided works, but what I am confused is why if I actually have a float Tensor then it doesn’t work:</p>
<pre><code>inputs = transform(inputs)
</code></pre>
<p>Error:</p>
<pre><code>TypeError: pic should be PIL Image or ndarray. Got &lt;class 'torch.FloatTensor'&gt;
</code></pre>
<p>I see what the error says but why wouldn’t transforms work for FloatTensors? It seems so counter intuitive to me.</p>

username: ptrblck
cooked: <p>Currently only PIL Images are supported, so you would have to transform it to an Image before calling <code>transform</code>.<br>
I think the reason is, that the transformations rely heavily on PIL functions and thus cannot be applied on <code>Tensor</code>s so easily.</p>

username: Shyam_Shankar_H.R
cooked: <p>put “transforms.ToTensor(),” after you do other transforms.</p>

username: TanmDL
cooked: <aside class="quote no-group" data-username="ptrblck" data-post="8" data-topic="15671">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/ptrblck/48/1823_2.png" class="avatar"> ptrblck:</div>
<blockquote>
<p>transformations rely heavily on PIL functions and thus cannot be applied on <code>Tensor</code> s so easily.</p>
</blockquote>
</aside>
<p>Exactly. If I tranform the tensor to pil and viceversa then the final result is not accurate. How do I solve the problem. I will be highly obliged if you create for mnist dataset. For my test cases, the data loader gives three outputs data, transformation(data) and target.</p>

username: ptrblck
cooked: <p>Could you check the data type of your input?<br>
We have a similar issue recently <a href="https://discuss.pytorch.org/t/pixel-values-changes-when-apply-transform-totensor/78504/6">here</a>, where the inputs were <code>FloatTensors</code> and thus an overflow occurred.</p>

username: Brando_Miranda
cooked: <p>what if I already have a dataset object? would:</p>
<pre><code class="lang-auto">valset.transform = new_transform
</code></pre>
<p>work?</p>

username: ptrblck
cooked: <p>It depends on your workflow.<br>
The manipulation itself would work and <code>valset</code> would use the <code>new_transform</code> when <code>self.transform</code> is called. However, if you are wrapping <code>valset</code> into a <code>DataLoader</code> using multiple workers, you have to be careful when (and if) this change will be visible.<br>
When you start iterating the <code>DataLoader</code>, each worker will create a copy of the <code>Dataset</code> until the loop finishes. Changing the <code>valset</code> via <code>loader.dataset.transform = new_transform</code> would then be visible in the next epoch (or when you restart the <code>DataLoader</code> loop). Also, if you are using <code>persistent_workers=True</code>, the workers would never restart (and thus also never create a new copy of the dataset) and thus the change won’t be used.</p>

username: Brando_Miranda
cooked: <p>Thanks!</p>
<p>I think a good solution can be found here: <a href="https://discuss.pytorch.org/t/changing-transforms-after-creating-a-dataset/64929/7" class="inline-onebox">Changing transforms after creating a dataset - #7 by Brando_Miranda</a></p>
<pre><code class="lang-auto">train_dataset = MyDataset(train_transform)
val_dataset = MyDataset(val_transform)
train_indices, val_indices = sklearn.model_selection.train_test_split(indices)
train_dataset = torch.utils.data.Subset(train_dataset, train_indices)
val_dataset = torch.utils.data.Subset(val_dataset, val_indices)
</code></pre>

username: Rizhao_Cai
cooked: <p>When using torch.utils.data.ConcatDataset to concat datasets,it seems not able to access the set_stage function. How to solve it?</p>

username: ptrblck
cooked: <p><code>Concatdataset</code> stores the passed datasets in its internal <code>self.datasets</code> attribute, so you could access your custom attribute through it.</p>