Data Loading & Transformations: transforms.RandomApply() alternative transformation

Wilan: <p>I’m pre-processing my data to feed into a CNN and am applying <code>RandomApply</code> with a probability of 0.3.</p>
<p>Is there a way to apply a transformation if the transformation in <code>RandomApply</code> didn’t get selected?<br>
In other words, how do I apply another transformation if the other <code>70%</code> was selected?<br>
(Kind of like if-else)</p>
<pre><code class="lang-auto">transforms.Compose([transforms.RandomApply(
                        [transforms.Resize((720+50, 720+50))], p=0.3),
                    transforms.ToTensor(),
                    transforms.Normalize(0.5, 0.5)])
</code></pre>

ptrblck: <p>I think the cleanest approach would be to either define a custom transformation and add the logic to the <code>__call__</code> method in your object or to use e.g. <code>transforms.Lambda</code> and pick the transformation based on the random number.</p>

Wilan: <p><a class="mention" href="/u/ptrblck">@ptrblck</a> Thank you for the suggestions! I ended up creating a custom transformation and added the logic in the <code>__call__</code> method.</p>

Wilan: <p>I’m getting an error in my implementation:</p>
<pre><code class="lang-auto">class RandomStretch(object):
    '''
    Stretches an image's height or width based on some probability and scale
    
    Params:
        p_h:      Probability to stretch height
        p_w:      Probability to stretch width
        h_scale:  Tuple of (height_lower_boundary, height_upper_boundary)
        w_scale:  Tuple of (width_lower_boundary, width_upper_boundary)
    '''
    
    def __init__(self, p_h, p_w, h_scale, w_scale):
        assert p_h + p_w &lt; 1.0
        self.p_h = p_h
        self.p_w = p_w
        self.h_low, self.h_high = h_scale
        self.w_low, self.w_high = w_scale
    
    def __call__(self, sample):
        
        image, label = sample
        
        # Random float in [0, 1)
        prob = np.random.random()
        
        # Height stretch
        if 0 &lt; prob and prob &lt; self.p_h:
            h_stretch = np.random.uniform(self.h_low, self.h_high)
            image = transforms.Resize(size=(720*h_stretch, 720))(image)
        
        # Width stretch
        elif self.p_h &lt; prob and prob &lt; self.p_w:
            w_stretch = np.random.uniform(self.w_low, self.w_high)
            image = transforms.Resize(size=(720, 720*w_stretch))(image)
        
        return {'image': image, 'label:': label}


train_transforms = transforms.Compose([transforms.Resize(size=(720, 720)),
                                       RandomStretch(p_h=0.25, p_w=0.25, h_scale=(0.9, 1.1), w_scale=(0.9, 1.1)),
                                       transforms.RandomHorizontalFlip(p=0.5),
                                       transforms.RandomAffine(degrees=0, scale=(0.9, 1.1)),
                                       transforms.Grayscale(1),
                                       transforms.ToTensor(),
                                       transforms.Normalize(0.5, 0.5)]) 

train_data = ImageFolder(train_dir, transform=train_transforms, is_valid_file=check_valid)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True, pin_memory=True, num_workers=4)

train_images, train_labels = next(iter(train_loader))
</code></pre>
<pre><code class="lang-auto">---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
&lt;ipython-input-95-eda6aa29a3f9&gt; in &lt;module&gt;
      1 train_classes = dict(zip((0, 1, 2, 3, 4), train_data.classes))
----&gt; 2 train_images, train_labels = next(iter(train_loader))
      3 
      4 print('[Train Loader]\n')
      5 print('images.shape: {} \ttype(images): {}'.format(train_images.shape, type(train_images)))

~/anaconda3/envs/cv/lib/python3.6/site-packages/torch/utils/data/dataloader.py in __next__(self)
    361 
    362     def __next__(self):
--&gt; 363         data = self._next_data()
    364         self._num_yielded += 1
    365         if self._dataset_kind == _DatasetKind.Iterable and \

~/anaconda3/envs/cv/lib/python3.6/site-packages/torch/utils/data/dataloader.py in _next_data(self)
    987             else:
    988                 del self._task_info[idx]
--&gt; 989                 return self._process_data(data)
    990 
    991     def _try_put_index(self):

~/anaconda3/envs/cv/lib/python3.6/site-packages/torch/utils/data/dataloader.py in _process_data(self, data)
   1012         self._try_put_index()
   1013         if isinstance(data, ExceptionWrapper):
-&gt; 1014             data.reraise()
   1015         return data
   1016 

~/anaconda3/envs/cv/lib/python3.6/site-packages/torch/_utils.py in reraise(self)
    393             # (https://bugs.python.org/issue2651), so we work around it.
    394             msg = KeyErrorMessage(msg)
--&gt; 395         raise self.exc_type(msg)

TypeError: Caught TypeError in DataLoader worker process 0.
Original Traceback (most recent call last):
  File "/home/wilson/anaconda3/envs/cv/lib/python3.6/site-packages/torch/utils/data/_utils/worker.py", line 185, in _worker_loop
    data = fetcher.fetch(index)
  File "/home/wilson/anaconda3/envs/cv/lib/python3.6/site-packages/torch/utils/data/_utils/fetch.py", line 44, in fetch
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/home/wilson/anaconda3/envs/cv/lib/python3.6/site-packages/torch/utils/data/_utils/fetch.py", line 44, in &lt;listcomp&gt;
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/home/wilson/anaconda3/envs/cv/lib/python3.6/site-packages/torchvision/datasets/folder.py", line 139, in __getitem__
    sample = self.transform(sample)
  File "/home/wilson/anaconda3/envs/cv/lib/python3.6/site-packages/torchvision/transforms/transforms.py", line 61, in __call__
    img = t(img)
  File "&lt;ipython-input-93-0b840063ddb7&gt;", line 39, in __call__
    image, label = sample
TypeError: 'Image' object is not iterable
</code></pre>

ptrblck: <p><code>torchvision.transforms</code> expect the <code>PIL.Image</code> as an input and also return the image, while your custom transformation returns a <code>dict</code> and tries to unwrap the incoming image so you won’t be able to use this transformation directly in <code>transforms.Compose</code>.<br>
You could apply the <code>torchvision.transformations</code> on the image and target separately and use <code>RandomStretch</code> on both of them in another custom class.</p>