Jovian
⭐️
Sign In
In [ ]:
!pip install jovian --upgrade --quite

Creating new images from a GAN using Pytorch

The objective of this assignment is to create new images that don't exist within the dataset the model is trained on.

I have chosen a Generative Adversarial Network as the means to achieve this objective.

The dataset I've chosen for this project is the Intel Image Classification dataset found in Kaggle at https://www.kaggle.com/ikarus777/best-artworks-of-all-time?select=resized

Download the dataset

Using 'opendatasets' and download the dataset.

In [2]:
!pip install opendatasets --upgrade --quiet
In [3]:
import opendatasets as od
In [7]:
dataset_url = 'https://www.kaggle.com/ikarus777/best-artworks-of-all-time'
In [8]:
od.download(dataset_url)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-8-59ad568d7907> in <module>() ----> 1 od.download(dataset_url) /usr/local/lib/python3.6/dist-packages/opendatasets/__init__.py in download(dataset_id_or_url, data_dir, force, dry_run, **kwargs) 14 if not os.path.exists(data_dir): 15 os.makedirs(data_dir) ---> 16 dataset = importlib.import_module('opendatasets.datasets.' + dataset_id) 17 if dry_run: 18 print('This is a dry run. URLs will be displayed but the files will not be downloaded.') /usr/lib/python3.6/importlib/__init__.py in import_module(name, package) 124 break 125 level += 1 --> 126 return _bootstrap._gcd_import(name[level:], package, level) 127 128 /usr/lib/python3.6/importlib/_bootstrap.py in _gcd_import(name, package, level) /usr/lib/python3.6/importlib/_bootstrap.py in _find_and_load(name, import_) /usr/lib/python3.6/importlib/_bootstrap.py in _find_and_load_unlocked(name, import_) ModuleNotFoundError: No module named 'opendatasets.datasets./content/drive/MyDrive/deep learning/datasets/artists/artists/' --------------------------------------------------------------------------- NOTE: If your import is failing due to a missing package, you can manually install dependencies using either !pip or !apt. To view examples of installing some common dependencies, click the "Open Examples" button below. ---------------------------------------------------------------------------

Import the dataset into PyTorch

This dataset is comprised of 2 folders:

  • images: This folder contains all image files within folders of artists.
  • resized: This folder contains all image files within one folder.

As a GAN classifies the data as real or fake, the artist categories are not needed so I will only use the files in the resized folder.

Use the ImageFolder class from torchvision.

In [12]:
data_dir = '/content/best-artworks-of-all-time/resized/'
In [10]:
import os
In [13]:
for cls in os.listdir(data_dir):
  print(cls, ':', len(os.listdir(data_dir + '/' + cls)))
resized : 8683
In [14]:
from torchvision.datasets import ImageFolder
In [15]:
dataset = ImageFolder(data_dir)
In [16]:
len(dataset)
Out[16]:
8683
In [17]:
dataset[2007]
Out[17]:
(<PIL.Image.Image image mode=RGB size=613x478 at 0x7F6DE9877630>, 0)
In [18]:
dataset.classes
Out[18]:
['resized']
In [19]:
import matplotlib.pyplot as plt
%matplotlib inline
In [20]:
img, label = dataset[0]
plt.imshow(img)
Out[20]:
<matplotlib.image.AxesImage at 0x7f6de8aa5978>
Notebook Image

#Data preparation and data loading.

First I'll resize and centre crop all images to ensure they are all the same shape and size.

Then convert them to tensors and normalize them.

Then create the data loader.

Then look at some samples.

In [21]:
import torchvision.transforms as tt
from torch.utils.data import DataLoader
In [22]:
image_size = 64
batch_size = 128
stats = (0.5, 0.5, 0.5), (0.5, 0.5, 0.5)
In [23]:
train_ds = ImageFolder(data_dir, transform=tt.Compose([
    tt.Resize(image_size),
    tt.CenterCrop(image_size),
    tt.ToTensor(),
    tt.Normalize(*stats)]))

train_dl = DataLoader(train_ds, batch_size, shuffle=True, num_workers=3, pin_memory=True)
In [24]:
from torchvision.utils import make_grid
In [25]:
def denorm(img_tensors):
    return img_tensors * stats[1][0] + stats[0][0]
In [26]:
def show_images(images, nmax=64):
    fig, ax = plt.subplots(figsize=(8, 8))
    ax.set_xticks([]); ax.set_yticks([])
    ax.imshow(make_grid(denorm(images.detach()[:nmax]), nrow=8).permute(1, 2, 0))

def show_batch(dl, nmax=64):
    for images, _ in dl:
        show_images(images, nmax)
        break
In [27]:
show_batch(train_dl)
Notebook Image

Using a GPU

To use a GPU and check if one is available, we define a couple of helper functions (get_default_device & to_device) and a helper class DeviceDataLoader to move our model & data to the GPU as required.

In [28]:
def get_default_device():
    """Pick GPU if available, else CPU"""
    if torch.cuda.is_available():
        return torch.device('cuda')
    else:
        return torch.device('cpu')
    
def to_device(data, device):
    """Move tensor(s) to chosen device"""
    if isinstance(data, (list,tuple)):
        return [to_device(x, device) for x in data]
    return data.to(device, non_blocking=True)

class DeviceDataLoader():
    """Wrap a dataloader to move data to a device"""
    def __init__(self, dl, device):
        self.dl = dl
        self.device = device
        
    def __iter__(self):
        """Yield a batch of data after moving it to device"""
        for b in self.dl: 
            yield to_device(b, self.device)

    def __len__(self):
        """Number of batches"""
        return len(self.dl)
In [29]:
import torch
In [30]:
device = get_default_device()
device
Out[30]:
device(type='cuda')
In [31]:
train_dl = DeviceDataLoader(train_dl, device)
In [32]:
import torch.nn as nn

Creating the model

First I'll define the discriminator and then the generator and move both to the GPU.

Then view some fake images from the generator.

In [33]:
discriminator = nn.Sequential(
    # in: 3 x 64 x 64

    nn.Conv2d(3, 64, kernel_size=4, stride=2, padding=1, bias=False),
    nn.BatchNorm2d(64),
    nn.LeakyReLU(0.2, inplace=True),
    # out: 64 x 32 x 32

    nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1, bias=False),
    nn.BatchNorm2d(128),
    nn.LeakyReLU(0.2, inplace=True),
    # out: 128 x 16 x 16

    nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1, bias=False),
    nn.BatchNorm2d(256),
    nn.LeakyReLU(0.2, inplace=True),
    # out: 256 x 8 x 8

    nn.Conv2d(256, 512, kernel_size=4, stride=2, padding=1, bias=False),
    nn.BatchNorm2d(512),
    nn.LeakyReLU(0.2, inplace=True),
    # out: 512 x 4 x 4

    nn.Conv2d(512, 1, kernel_size=4, stride=1, padding=0, bias=False),
    # out: 1 x 1 x 1

    nn.Flatten(),
    nn.Sigmoid())
In [34]:
discriminator = to_device(discriminator, device)
In [35]:
latent_size = 128
In [36]:
generator = nn.Sequential(
    # in: latent_size x 1 x 1

    nn.ConvTranspose2d(latent_size, 512, kernel_size=4, stride=1, padding=0, bias=False),
    nn.BatchNorm2d(512),
    nn.ReLU(True),
    # out: 512 x 4 x 4

    nn.ConvTranspose2d(512, 256, kernel_size=4, stride=2, padding=1, bias=False),
    nn.BatchNorm2d(256),
    nn.ReLU(True),
    # out: 256 x 8 x 8

    nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=False),
    nn.BatchNorm2d(128),
    nn.ReLU(True),
    # out: 128 x 16 x 16

    nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1, bias=False),
    nn.BatchNorm2d(64),
    nn.ReLU(True),
    # out: 64 x 32 x 32

    nn.ConvTranspose2d(64, 3, kernel_size=4, stride=2, padding=1, bias=False),
    nn.Tanh()
    # out: 3 x 64 x 64
)
In [37]:
xb = torch.randn(batch_size, latent_size, 1, 1) # random latent tensors
fake_images = generator(xb)
print(fake_images.shape)
show_images(fake_images)
torch.Size([128, 3, 64, 64])