{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"accelerator":"GPU","colab":{"collapsed_sections":[],"provenance":[]},"coursera":{"schema_names":["GANSC1-2A"]},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Deep Convolutional GAN (DCGAN)","metadata":{"id":"1czVdIlqnImH"}},{"cell_type":"code","source":"import torch\nfrom torchvision import transforms\nfrom torchvision.utils import make_grid\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nimport os\nimport matplotlib.pyplot as plt\n\n# Define the path to Monet images\nmonet_path = '/kaggle/input/gan-getting-started/monet_jpg'\n\n# Dataset class to load Monet images\nclass MonetDataset(Dataset):\n    def __init__(self, image_dir, transform=None):\n        self.image_paths = [os.path.join(image_dir, img) for img in os.listdir(image_dir) if img.endswith('.jpg')]\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        image = Image.open(self.image_paths[idx]).convert(\"RGB\")\n        return self.transform(image) if self.transform else image\n\n# Transform and DataLoader setup\ntransform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n])\nmonet_loader = DataLoader(MonetDataset(monet_path, transform=transform), batch_size=32, shuffle=True)\n\n# Display function for a batch\n# Display function for a batch\ndef show_tensor_images(image_tensor, num_images=25):\n    image_tensor = (image_tensor + 1) / 2  # Normalize back to [0,1]\n    grid_img = make_grid(image_tensor[:num_images], nrow=5)\n    plt.figure(figsize=(4, 4))\n    plt.imshow(grid_img.permute(1, 2, 0).cpu().numpy())\n    plt.axis(\"off\")\n    plt.show()\n\n\n# Display a batch of Monet images\nfor batch in monet_loader:\n    show_tensor_images(batch, num_images=16)\n    break\n","metadata":{"id":"JfkorNJrnmNO","trusted":true,"execution":{"iopub.status.busy":"2024-11-09T10:10:53.082304Z","iopub.execute_input":"2024-11-09T10:10:53.082677Z","iopub.status.idle":"2024-11-09T10:10:58.331188Z","shell.execute_reply.started":"2024-11-09T10:10:53.082642Z","shell.execute_reply":"2024-11-09T10:10:58.330235Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch import nn  # This imports nn so it can be used directly","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-09T10:10:58.333179Z","iopub.execute_input":"2024-11-09T10:10:58.333970Z","iopub.status.idle":"2024-11-09T10:10:58.338272Z","shell.execute_reply.started":"2024-11-09T10:10:58.333925Z","shell.execute_reply":"2024-11-09T10:10:58.337132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nclass Generator(nn.Module):\n\n    '''\n\n    Generator Class\n\n    Values:\n\n        z_dim: the dimension of the noise vector, a scalar\n\n        im_chan: the number of channels of the output image, a scalar\n\n              (MNIST is black-and-white, so 1 channel is your default)\n\n        hidden_dim: the inner dimension, a scalar\n\n    '''\n\n    def __init__(self, z_dim=100, im_chan=3, hidden_dim=64):\n\n        super(Generator, self).__init__()\n\n        self.z_dim = z_dim\n\n        # Build the neural network\n\n        self.gen = nn.Sequential(\n\n            self.make_gen_block(z_dim, hidden_dim * 8, kernel_size=4, stride=1),   # 1x1 -> 4x4\n            self.make_gen_block(hidden_dim * 8, hidden_dim * 4),                   # 4x4 -> 8x8\n            self.make_gen_block(hidden_dim * 4, hidden_dim * 2),                   # 8x8 -> 16x16\n            self.make_gen_block(hidden_dim * 2, hidden_dim),                       # 16x16 -> 32x32\n            self.make_gen_block(hidden_dim, hidden_dim // 2),                      # 32x32 -> 64x64\n            self.make_gen_block(hidden_dim // 2, hidden_dim // 4),                 # 64x64 -> 128x128\n            self.make_gen_block(hidden_dim // 4, im_chan, kernel_size=4, final_layer=True),  # 128x128 -> 256x256\n\n        )\n\n\n\n    def make_gen_block(self, input_channels, output_channels, kernel_size=3, stride=2, final_layer=False):\n\n        '''\n\n        Function to return a sequence of operations corresponding to a generator block of DCGAN, \n\n        corresponding to a transposed convolution, a batchnorm (except for in the last layer), and an activation.\n\n        Parameters:\n\n            input_channels: how many channels the input feature representation has\n\n            output_channels: how many channels the output feature representation should have\n\n            kernel_size: the size of each convolutional filter, equivalent to (kernel_size, kernel_size)\n\n            stride: the stride of the convolution\n\n            final_layer: a boolean, true if it is the final layer and false otherwise \n\n                      (affects activation and batchnorm)\n\n        '''\n\n\n\n        # Build the neural block\n\n        if not final_layer:\n\n            return nn.Sequential(\n\n                \n\n                nn.ConvTranspose2d(input_channels, output_channels, kernel_size, stride),\n\n                nn.BatchNorm2d(output_channels),\n\n                nn.ReLU(inplace=True)\n\n                \n\n            )\n\n        else: # Final Layer\n\n            return nn.Sequential(\n\n                \n\n                nn.ConvTranspose2d(input_channels, output_channels, kernel_size, stride),\n\n                nn.Tanh(),\n\n                \n\n            )\n\n\n\n    def unsqueeze_noise(self, noise):\n\n        '''\n\n        Function for completing a forward pass of the generator: Given a noise tensor, \n\n        returns a copy of that noise with width and height = 1 and channels = z_dim.\n\n        Parameters:\n\n            noise: a noise tensor with dimensions (n_samples, z_dim)\n\n        '''\n\n        return noise.view(len(noise), self.z_dim, 1, 1)\n\n\n\n    def forward(self, noise):\n\n        '''\n\n        Function for completing a forward pass of the generator: Given a noise tensor, \n\n        returns generated images.\n\n        Parameters:\n\n            noise: a noise tensor with dimensions (n_samples, z_dim)\n\n        '''\n\n        x = self.unsqueeze_noise(noise)\n\n        return self.gen(x)\n\n\n\ndef get_noise(n_samples, z_dim, device='cpu'):\n\n    '''\n\n    Function for creating noise vectors: Given the dimensions (n_samples, z_dim)\n\n    creates a tensor of that shape filled with random numbers from the normal distribution.\n\n    Parameters:\n\n        n_samples: the number of samples to generate, a scalar\n\n        z_dim: the dimension of the noise vector, a scalar\n\n        device: the device type\n\n    '''\n\n    return torch.randn(n_samples, z_dim, device=device)","metadata":{"id":"EvO7h0LYnEJZ","trusted":true,"execution":{"iopub.status.busy":"2024-11-09T10:10:58.339682Z","iopub.execute_input":"2024-11-09T10:10:58.340033Z","iopub.status.idle":"2024-11-09T10:10:58.364660Z","shell.execute_reply.started":"2024-11-09T10:10:58.339992Z","shell.execute_reply":"2024-11-09T10:10:58.363766Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Discriminator\n\nThe second component you need to create is the discriminator.\n\n\n\nYou will use 3 layers in your discriminator's neural network. Like with the generator, you will need create the function to create a single neural network block for the discriminator.\n","metadata":{"id":"r9fScH98nkYH"}},{"cell_type":"code","source":"class Discriminator(nn.Module):\n\n    '''\n\n    Discriminator Class\n\n    Values:\n\n        im_chan: the number of channels of the output image, a scalar\n\n              (MNIST is black-and-white, so 1 channel is your default)\n\n    hidden_dim: the inner dimension, a scalar\n\n    '''\n\n    def __init__(self, im_chan=3, hidden_dim=64):\n\n        super(Discriminator, self).__init__()\n\n        self.disc = nn.Sequential(\n\n            self.make_disc_block(im_chan, hidden_dim),\n            self.make_disc_block(hidden_dim, hidden_dim * 2),\n            self.make_disc_block(hidden_dim * 2, hidden_dim * 4),\n            self.make_disc_block(hidden_dim * 4, hidden_dim * 8),\n            self.make_disc_block(hidden_dim * 8, 1, final_layer=True),\n\n        )\n\n\n\n    def make_disc_block(self, input_channels, output_channels, kernel_size=4, stride=2, final_layer=False):\n\n        '''\n\n        Function to return a sequence of operations corresponding to a discriminator block of DCGAN, \n\n        corresponding to a convolution, a batchnorm (except for in the last layer), and an activation.\n\n        Parameters:\n\n            input_channels: how many channels the input feature representation has\n\n            output_channels: how many channels the output feature representation should have\n\n            kernel_size: the size of each convolutional filter, equivalent to (kernel_size, kernel_size)\n\n            stride: the stride of the convolution\n\n            final_layer: a boolean, true if it is the final layer and false otherwise \n\n                      (affects activation and batchnorm)\n\n        '''\n\n        \n\n        # Build the neural block\n\n        if not final_layer:\n\n            return nn.Sequential(\n\n                #### START CODE HERE ####\n\n                nn.Conv2d(input_channels, output_channels, kernel_size, stride),\n\n                nn.BatchNorm2d(output_channels),\n\n                nn.LeakyReLU(0.2, inplace=True)\n\n                #### END CODE HERE ####\n\n            )\n\n        else: # Final Layer\n\n            return nn.Sequential(\n\n                #### START CODE HERE ####\n\n                nn.Conv2d(input_channels, output_channels, kernel_size, stride)\n\n                #### END CODE HERE ####\n\n            )\n\n\n\n    '''\n\n    Function for completing a forward pass of the discriminator: Given an image tensor, \n\n    returns a 1-dimension tensor representing fake/real.\n\n    Parameters:\n\n        image: a flattened image tensor with dimension (im_dim)\n\n    '''\n\n    def forward(self, image):\n\n        disc_pred = self.disc(image)\n\n        return disc_pred.view(len(disc_pred), -1)","metadata":{"id":"aA4AxGnmpuPq","trusted":true,"execution":{"iopub.status.busy":"2024-11-09T10:10:58.366729Z","iopub.execute_input":"2024-11-09T10:10:58.367004Z","iopub.status.idle":"2024-11-09T10:10:58.379684Z","shell.execute_reply.started":"2024-11-09T10:10:58.366974Z","shell.execute_reply":"2024-11-09T10:10:58.378932Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training\n\nNow you can put it all together!\n\nRemember that these are your parameters:\n\n  *   criterion: the loss function\n\n  *   n_epochs: the number of times you iterate through the entire dataset when training\n\n  *   z_dim: the dimension of the noise vector\n\n  *   display_step: how often to display/visualize the images\n\n  *   batch_size: the number of images per forward/backward pass\n\n  *   lr: the learning rate\n\n  *   beta_1, beta_2: the momentum term\n\n  *   device: the device type\n\n\n\n<!-- In addition, be warned that **this runs very slowly on the default CPU**. One way to run this more quickly is to download the .ipynb and upload it to Google Drive, then open it with Google Colab, click on `Runtime -> Change runtime type` and set hardware accelerator to GPU and replace\n\n`device = \"cpu\"`\n\nwith\n\n`device = \"cuda\"`. The code should then run without any more changes, over 1,000 times faster.  -->\n","metadata":{"id":"qRk_8azSq3tF"}},{"cell_type":"code","source":"criterion = nn.BCEWithLogitsLoss()\n\nz_dim = 64\n\ndisplay_step = 500\n\nbatch_size = 128\n\n# A learning rate of 0.0002 works well on DCGAN\nlr = 0.002\n\nbeta_1 = 0.5\n\nbeta_2 = 0.999\n\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n# Transformation to rescale images between -1 and 1 (the range of the tanh activation)\ntransform = transforms.Compose([\n    transforms.Resize((256, 256)),  # Resize images to 256x256 for GAN\n    transforms.ToTensor(),          # Convert images to tensor\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # Normalize to range [-1, 1]\n])\n\n# Instantiate DataLoader for Monet images\nmonet_path = '/kaggle/input/gan-getting-started/monet_jpg'\nmonet_dataloader = DataLoader(\n    MonetDataset(monet_path, transform=transform),\n    batch_size=batch_size,\n    shuffle=True\n)","metadata":{"id":"IFLQ039u-qdu","trusted":true,"execution":{"iopub.status.busy":"2024-11-09T10:10:58.380608Z","iopub.execute_input":"2024-11-09T10:10:58.380943Z","iopub.status.idle":"2024-11-09T10:10:58.418036Z","shell.execute_reply.started":"2024-11-09T10:10:58.380906Z","shell.execute_reply":"2024-11-09T10:10:58.417020Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Then, you can initialize your generator, discriminator, and optimizers.","metadata":{"id":"24Var22i_Ccs"}},{"cell_type":"code","source":"gen = Generator(z_dim).to(device)\n\ngen_opt = torch.optim.Adam(gen.parameters(), lr=lr, betas=(beta_1, beta_2))\n\ndisc = Discriminator().to(device) \n\ndisc_opt = torch.optim.Adam(disc.parameters(), lr=lr, betas=(beta_1, beta_2))\n\n\n\n# You initialize the weights to the normal distribution\n\n# with mean 0 and standard deviation 0.02\n\ndef weights_init(m):\n\n    if isinstance(m, nn.Conv2d) or isinstance(m, nn.ConvTranspose2d):\n\n        torch.nn.init.normal_(m.weight, 0.0, 0.02)\n\n    if isinstance(m, nn.BatchNorm2d):\n\n        torch.nn.init.normal_(m.weight, 0.0, 0.02)\n\n        torch.nn.init.constant_(m.bias, 0)\n\ngen = gen.apply(weights_init)\n\ndisc = disc.apply(weights_init)","metadata":{"id":"sDFRZ8tg_Y57","trusted":true,"execution":{"iopub.status.busy":"2024-11-09T10:10:58.419351Z","iopub.execute_input":"2024-11-09T10:10:58.420063Z","iopub.status.idle":"2024-11-09T10:10:58.643819Z","shell.execute_reply.started":"2024-11-09T10:10:58.420020Z","shell.execute_reply":"2024-11-09T10:10:58.642864Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Finally, you can train your GAN!\n\nFor each epoch, you will process the entire dataset in batches. For every batch, you will update the discriminator and generator. Then, you can see DCGAN's results!","metadata":{"id":"7iCTg3w4_Zw6"}},{"cell_type":"code","source":"from tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-09T10:10:58.644997Z","iopub.execute_input":"2024-11-09T10:10:58.645285Z","iopub.status.idle":"2024-11-09T10:10:58.649551Z","shell.execute_reply.started":"2024-11-09T10:10:58.645255Z","shell.execute_reply":"2024-11-09T10:10:58.648573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"n_epochs = 200\n\ndisplay_step = 100  \n\ncur_step = 0\n\nmean_generator_loss = 0\n\nmean_discriminator_loss = 0\n\nfor epoch in range(n_epochs):\n\n    # Dataloader returns the batches\n\n    for real in tqdm(monet_loader):  # Expecting only the image tensor here\n\n        cur_batch_size = len(real)\n\n        real = real.to(device)\n\n\n\n        ## Update discriminator ##\n\n        disc_opt.zero_grad()\n\n        fake_noise = get_noise(cur_batch_size, z_dim, device=device)\n\n        fake = gen(fake_noise)\n\n        disc_fake_pred = disc(fake.detach())\n\n        disc_fake_loss = criterion(disc_fake_pred, torch.zeros_like(disc_fake_pred))\n\n        disc_real_pred = disc(real)\n\n        disc_real_loss = criterion(disc_real_pred, torch.ones_like(disc_real_pred))\n\n        disc_loss = (disc_fake_loss + disc_real_loss) / 2\n\n\n\n        # Keep track of the average discriminator loss\n\n        mean_discriminator_loss += disc_loss.item() / display_step\n\n        # Update gradients\n\n        disc_loss.backward(retain_graph=True)\n\n        # Update optimizer\n\n        disc_opt.step()\n\n\n\n        ## Update generator ##\n\n        gen_opt.zero_grad()\n\n        fake_noise_2 = get_noise(cur_batch_size, z_dim, device=device)\n\n        fake_2 = gen(fake_noise_2)\n\n        disc_fake_pred = disc(fake_2)\n\n        gen_loss = criterion(disc_fake_pred, torch.ones_like(disc_fake_pred))\n\n        gen_loss.backward()\n\n        gen_opt.step()\n\n\n\n        # Keep track of the average generator loss\n\n        mean_generator_loss += gen_loss.item() / display_step\n\n\n\n        ## Visualization code ##\n\n        if cur_step % display_step == 0 and cur_step > 0:\n\n            print(f\"Step {cur_step}: Generator loss: {mean_generator_loss}, discriminator loss: {mean_discriminator_loss}\")\n\n            show_tensor_images(fake)\n\n            show_tensor_images(real)\n\n            mean_generator_loss = 0\n\n            mean_discriminator_loss = 0\n\n        cur_step += 1\n","metadata":{"id":"UXptQZcwrBrq","trusted":true,"execution":{"iopub.status.busy":"2024-11-09T10:10:58.651101Z","iopub.execute_input":"2024-11-09T10:10:58.651535Z","iopub.status.idle":"2024-11-09T10:21:50.979799Z","shell.execute_reply.started":"2024-11-09T10:10:58.651490Z","shell.execute_reply":"2024-11-09T10:21:50.978756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"id":"Am3QlaFzJv7P","trusted":true},"outputs":[],"execution_count":null}]}