The usual approach is to just implement the code to load and process one single sample, yes.
That makes it quite easy to write your own code as you don’t have to take care of the batching.
The DataLoader will take care of it even using multiprocessing.
If you want to apply multiple transformations on your data, you could just compose them:
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])
    ])

dataset = MyDataset(image_paths, transforms=data_transform)

The transformations won’t be randomly selected, but applied in the order you’ve created them.
If you want to pick a transformation randomly, you can use RandomChoice 8.
Otherwise the transformation will be applied in order as you pass them (or apply them in your Dataset).
If you would like to rotate your images before flipping them (for whatever reason), just change the order of your transforms.



 emcenrue:

does that mean that the original sample could potentially never be used for training? In keras, the augmentation produces additional samples.


I think you are also wrong on this point.

Generate batches of tensor image data with real-time data augmentation.

This does not sound as if the original samples are created before the augmented ones.
As I’m not that familiar with Keras, feel free to correct me, but using this code I cannot get the original sample from the DataGenerator:
data_dir = './dummy_image/'
image = Image.open(data_dir + 'class0/dummy_image.jpg')
im_arr = np.array(image)

datagen = ImageDataGenerator(
        rescale=None,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

train_generator = datagen.flow_from_directory(
        data_dir,
        target_size=im_arr.shape[:-1],
        batch_size=1,
        class_mode='binary')

x_1, _ = train_generator.next()
f, axarr = plt.subplots(1, 2)
axarr[0].imshow(x_1[0].astype(np.uint8))
axarr[1].imshow(im_arr)
plt.show()

for idx, (x, y) in enumerate(train_generator):
    x = x.astype(np.uint8).squeeze()
    print('Iter {}, Abs error {}, x.min {}, x.max{}, im.min {}, im.max {}'.format(
        idx, np.mean(np.abs(x-im_arr)), x.min(), x.max(), im_arr.min(), im_arr.max()))
    if np.allclose(x, im_arr):
        break
    plt.imshow(np.abs(x-im_arr))
    plt.show()

Note that I’ve created two folders (class0, class1) with the same single image inside both of them.