from nbdev.showdoc import *
Unpaired data loading
DataLoaders
class.
Example Dataset - Horse to Zebra conversion
Here, we are going to use the horse2zebra
dataset provided by UC Berkeley. I have already downloaded it, You can download it at the URL https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/horse2zebra.zip with the fastai untar_data
function. Additionally, we can view the directory with Path.ls()
(added by fastai).
= untar_data('https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/horse2zebra.zip') horse2zebra
= horse2zebra.ls().sorted()
folders print(folders)
[Path('/home/tmabraham/.fastai/data/horse2zebra/testA'), Path('/home/tmabraham/.fastai/data/horse2zebra/testB'), Path('/home/tmabraham/.fastai/data/horse2zebra/trainA'), Path('/home/tmabraham/.fastai/data/horse2zebra/trainB')]
We can see that we have four directories, a train and test directory for both domains.
Create DataLoaders object:
We can treat the image in Domain A as the input and the image in Domain B as the target. We want to be able to index the dataset for a fixed image in domain A but a random image in domain B, in order to avoid fixed pairs.
A brief summary of how fastai Datasets works: > “A Datasets creates a tuple from items (typically input,target) by applying to them each list of Transform (or Pipeline) in tfms.”
(from docs)
So for transforms we will have a list of list of transforms. Each list of transforms are used to obtain, process, and return the inputs (in this case Domain A) and the targets (Domain B) as a tuple.
Let’s first get our image paths:
= folders[2]
trainA_path = folders[3]
trainB_path = folders[0]
testA_path = folders[1] testB_path
We can use get_image_files
to get the image files from the directories:
= get_image_files(trainA_path)
filesA = get_image_files(trainB_path) filesB
filesA
(#1067) [Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_6873.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_6335.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_4123.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_4785.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_1798.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_691.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_2048.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_3329.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_835.jpg'),Path('/home/tmabraham/.fastai/data/horse2zebra/trainA/n02381460_993.jpg')...]
Now, we can have a Transform that randomly selects an image in domain B for the current pair:
RandPair
RandPair (itemsB)
Returns a random image from domain B, resulting in a random pair of images from domain A and B.
0),RandPair(filesB)(0))
test_ne(RandPair(filesB)(type(RandPair(filesB)(0)),type(Path('.'))) test_eq(
Now let’s make our Datasets
(assume no split for now). We load as a PILImage
, convert to a Tensor
, and resize:
=128
size= Datasets(filesA, tfms=[[PILImage.create, ToTensor, Resize(size)],
dsets =None) [RandPair(filesB),PILImage.create, ToTensor, Resize(size)]],splits
Now we can create a DataLoader
. Note that fastai allows for batch-level transforms that can be performed on an accelerator like a GPU. Let’s normalize the dataset:
= [IntToFloatTensor, Normalize.from_stats(mean=0.5, std=0.5)]
batch_tfms = dsets.dataloaders(bs=4, num_workers=2, after_batch=batch_tfms) dls
We can also show the batch:
dls.show_batch()
= dls.one_batch()
xb,yb xb.shape
torch.Size([4, 3, 128, 128])
0].cpu().permute(1,2,0).numpy()) plt.imshow(dls.after_batch.decode(xb)[
<matplotlib.image.AxesImage>
Some hacks for custom normalization for each of the inputs.
TensorImageB
TensorImageB (x, **kwargs)
A Tensor
which support subclass pickling, and maintains metadata when casting or after methods
TensorImageA
TensorImageA (x, **kwargs)
A Tensor
which support subclass pickling, and maintains metadata when casting or after methods
PILImageB
PILImageB ()
A RGB Pillow Image
that can show itself and converts to TensorImage
PILImageA
PILImageA ()
A RGB Pillow Image
that can show itself and converts to TensorImage
ToTensorB
ToTensorB (enc=None, dec=None, split_idx=None, order=None)
Convert item to TensorImageB
ToTensorA
ToTensorA (enc=None, dec=None, split_idx=None, order=None)
Convert item to TensorImageA
change_type_of_tfm
change_type_of_tfm (tfm, old_type, new_type)
= np.ones((128,128,3)) dummy_data
= Normalize.from_stats(1,1) normalize_tfm
0],TensorImage(np.zeros((128,128,3))).cuda()) test_eq(normalize_tfm(TensorImage(dummy_data).cuda())[
= change_type_of_tfm(normalize_tfm, TensorImage, TensorImageA) new_normalize_tfm
0],TensorImageA(np.zeros((128,128,3))).cuda()) test_eq(new_normalize_tfm(TensorImageA(dummy_data).cuda())[
Let’s add a data loading function to our library. Note that we don’t have a validation set (not necessary for CycleGAN training). Also note that we load the images with size load_size
and take a random crop of the image with size crop_size
(default of 256x256) to load into the model. We can also specify a subset of the data if we want (num_A
and num_B
). Finally, we have provided an optional argument to add your own transforms if you need.
get_dls
get_dls (pathA, pathB, num_A=None, num_B=None, load_size=512, crop_size=256, item_tfms=None, batch_tfms=None, bs=4, num_workers=2, normalize=False)
Given image files from two domains (pathA
, pathB
), create DataLoaders
object. Loading and randomly cropped sizes of load_size
and crop_size
are set to defaults of 512 and 256. Batch size is specified by bs
(default=4).
Quick tests:
=512
load_size=256
crop_size=4
bs= get_dls(trainA_path, trainB_path,load_size=load_size,crop_size=crop_size,bs=bs) dls
type(dls[0]),TfmdDL)
test_eq(len(dls[0]),int(len(trainA_path.ls())/bs))
test_eq(len(dls[1]),0) test_eq(
= next(iter(dls[0]))
xb,yb
test_eq(xb.shape,yb.shape)3, crop_size, crop_size])) test_eq(xb.shape,torch.Size([bs,
dls.show_batch()
= 100
num_A = 150
num_B = get_dls(trainA_path, trainB_path,num_A=num_A,num_B=num_B,load_size=load_size,crop_size=crop_size,bs=bs) dls
len(dls[0]),int(min(num_A,num_B)/bs)) test_eq(
= get_dls(trainA_path, trainB_path,num_A=num_A,num_B=num_B,load_size=load_size,crop_size=crop_size,
dls =[*aug_transforms(size=224), Normalize.from_stats(mean=0.5, std=0.5)], bs=bs) batch_tfms
dls.show_batch()
class MakeAll(Transform):
=5
orderdef __init__(self, value): self.value = value
def encodes(self, x:TensorImage): return TensorImage(torch.ones(*x.shape))*self.value
def decodes(self, x:TensorImage): return x
= get_dls(trainA_path, trainB_path, load_size=load_size,crop_size=crop_size,item_tfms={TensorImageA: MakeAll(255.)}, bs=bs)
dls = next(iter(dls[0]))
xb, yb *xb.shape)).cuda()) test_eq(xb, TensorImage(torch.ones(
= get_dls(trainA_path, trainB_path, load_size=load_size,crop_size=crop_size,item_tfms={TensorImageB: MakeAll(255.)}, bs=bs)
dls = next(iter(dls[0]))
xb, yb *yb.shape)).cuda()) test_eq(yb, TensorImage(torch.ones(
= get_dls(trainA_path, trainB_path, load_size=load_size,crop_size=crop_size,item_tfms={TensorImageA: MakeAll(0.), TensorImageB: MakeAll(255.)}, bs=bs)
dls = next(iter(dls[0]))
xb, yb -1*torch.ones(*xb.shape)).cuda())
test_eq(xb, TensorImage(*yb.shape)).cuda()) test_eq(yb, TensorImage(torch.ones(
dls.show_batch()
from albumentations import RandomContrast
class AlbumentationsTfm(Transform):
=5
orderdef __init__(self, aug): self.aug = aug
def encodes(self, img: TensorImage):
= self.aug(image=np.array(img))['image']
aug_img return type(img)(aug_img)
= get_dls(trainA_path, trainB_path, load_size=load_size,crop_size=crop_size,item_tfms={TensorImageA: AlbumentationsTfm(aug=RandomContrast((0.75,0.99),p=1)), \
dls =RandomContrast((-0.75,-0.99),p=1))}, bs=bs) TensorImageB: AlbumentationsTfm(aug
/home/tmabraham/anaconda3/envs/UPIT/lib/python3.9/site-packages/albumentations/augmentations/transforms.py:1826: FutureWarning: This class has been deprecated. Please use RandomBrightnessContrast
warnings.warn(
dls.show_batch()
= get_dls(trainA_path, trainB_path, load_size=load_size,crop_size=crop_size,batch_tfms={TensorImageA: MakeAll(255.)}, bs=bs)
dls = next(iter(dls[0]))
xb, yb *xb.shape))) test_eq(xb, TensorImage(torch.ones(
= get_dls(trainA_path, trainB_path, load_size=load_size,crop_size=crop_size,batch_tfms={TensorImageB: MakeAll(255.)}, bs=bs)
dls = next(iter(dls[0]))
xb, yb *yb.shape))) test_eq(yb, TensorImage(torch.ones(
= get_dls(trainA_path, trainB_path, load_size=load_size,crop_size=crop_size,batch_tfms={TensorImageA: MakeAll(0.), TensorImageB: MakeAll(255.)}, bs=bs)
dls = next(iter(dls[0]))
xb, yb *xb.shape)))
test_eq(xb, TensorImage(torch.zeros(*yb.shape))) test_eq(yb, TensorImage(torch.ones(
dls.show_batch()
= get_dls(trainA_path, trainB_path,num_A=num_A,num_B=num_B,load_size=load_size,crop_size=crop_size, normalize=False, bs=bs) dls
= DataLoader(Datasets([(TensorImageA(torch.ones(3,256,256)),TensorImageB(torch.ones(3,256,256))) for i in range(100)]))
dummy_dl 'cuda')
dummy_dl.to(=dls.after_batch(next(iter(dummy_dl)))[0]
xb,yb test_eq(TensorImage(xb),TensorImage(yb))
= get_dls(trainA_path, trainB_path,num_A=num_A,num_B=num_B,load_size=load_size,crop_size=crop_size, normalize=True, bs=bs) dls
= DataLoader(Datasets([(TensorImageA(torch.ones(3,256,256)),TensorImageB(torch.ones(3,256,256))) for i in range(100)]))
dummy_dl 'cuda')
dummy_dl.to(=dls.after_batch(next(iter(dummy_dl)))[0]
xb,yb test_ne(TensorImage(xb),TensorImage(yb))
HuggingFace Datasets Loader
HuggingFace has a Datasets package that allows us to access the hundreds of datasets available on the Hub. This includes our Horse-to-Zebra dataset (over here). Here is another helper function specific for creating dataloaders for datasets available on the HuggingFace Hub:
get_dls_from_hf
get_dls_from_hf (dataset_name, fieldA='imageA', fieldB='imageB', num_A=None, num_B=None, load_size=512, crop_size=256, item_tfms=None, batch_tfms=None, bs=4, num_workers=2, normalize=False)
Given a name of a dataset available on the HuggingFace Hub, create DataLoaders
object. Field names given in fieldA
and fieldB
arguments. Loading and randomly cropped sizes of load_size
and crop_size
are set to defaults of 512 and 256. Batch size is specified by bs
(default=4).
create_image
create_image (x, image_type)
convert_func
convert_func (x)
Quick tests
=512
load_size=256
crop_size=4
bs= get_dls_from_hf('huggan/horse2zebra',load_size=load_size,crop_size=crop_size,bs=bs) dls
Using custom data configuration huggan--horse2zebra-aligned-424fab4179d04c8e
Reusing dataset parquet (/home/tmabraham/.cache/huggingface/datasets/parquet/huggan--horse2zebra-aligned-424fab4179d04c8e/0.0.0/0b6d5799bb726b24ad7fc7be720c170d8e497f575d02d47537de9a5bac074901)
type(dls[0]),TfmdDL) test_eq(
= next(iter(dls[0]))
xb,yb
test_eq(xb.shape,yb.shape)3, crop_size, crop_size])) test_eq(xb.shape,torch.Size([bs,
dls.show_batch()
for i in dls[0]:
pass
= get_dls_from_hf('huggan/horse2zebra',fieldA='imageB',fieldB='imageA',load_size=load_size,crop_size=crop_size,bs=bs) dls
Using custom data configuration huggan--horse2zebra-aligned-424fab4179d04c8e
Reusing dataset parquet (/home/tmabraham/.cache/huggingface/datasets/parquet/huggan--horse2zebra-aligned-424fab4179d04c8e/0.0.0/0b6d5799bb726b24ad7fc7be720c170d8e497f575d02d47537de9a5bac074901)
dls.show_batch()