{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n\nfiles = []\nfor dirname, _, filenames in os.walk('/kaggle/input/gan-getting-started/monet_jpg'):\n    for filename in filenames:\n        files.append(os.path.join(dirname, filename))\n\nprint(files[0])\nprint(len(files))\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-12T19:51:38.411958Z","iopub.execute_input":"2025-09-12T19:51:38.412627Z","iopub.status.idle":"2025-09-12T19:51:38.943974Z","shell.execute_reply.started":"2025-09-12T19:51:38.412600Z","shell.execute_reply":"2025-09-12T19:51:38.943081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset\nfrom PIL import Image\nimport os # Make sure os is imported\n\n# PART 1: Make sure this custom Dataset class is defined in your script.\n# This class is designed for folders that contain only images and no sub-folders.\nclass ImageDataset(Dataset):\n    def __init__(self, root_dir, transform=None):\n        self.root_dir = root_dir\n        self.transform = transform\n        self.image_files = [f for f in os.listdir(root_dir) if os.path.isfile(os.path.join(root_dir, f))]\n\n    def __len__(self):\n        return len(self.image_files)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(self.root_dir, self.image_files[idx])\n        image = Image.open(img_path).convert(\"RGB\")\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        # We return a dummy label (0) because the DataLoader expects a tuple,\n        # but we don't need labels for training a GAN.\n        return image, 0\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-12T19:59:23.138683Z","iopub.execute_input":"2025-09-12T19:59:23.139485Z","iopub.status.idle":"2025-09-12T19:59:23.145150Z","shell.execute_reply.started":"2025-09-12T19:59:23.139450Z","shell.execute_reply":"2025-09-12T19:59:23.144338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision\nimport torchvision.datasets as datasets\nimport torchvision.transforms as transforms\nfrom torch.utils.data import DataLoader\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport os\n\n# Ensure the output directory exists\nos.makedirs(\"/kaggle/working/generated_images\", exist_ok=True)\n\n#---------------------------------------------------\n# 1. CONFIGURATION AND HYPERPARAMETERS\n#---------------------------------------------------\nclass Config:\n    DATASET_PATH = \"/kaggle/input/gan-getting-started/monet_jpg\" # IMPORTANT: Change this to your dataset path\n    DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    BATCH_SIZE = 64\n    IMAGE_SIZE = 64  # Using 64x64 for faster training on Kaggle. Change to 256 if you have time.\n    CHANNELS_IMG = 3\n    NOISE_DIM = 100\n    NUM_EPOCHS = 30\n    LR = 0.0002\n    BETA1 = 0.5  # As recommended in the DCGAN paper\n    \n    # Determined by IMAGE_SIZE. For 64x64, we need 4 upsampling layers.\n    # For 256x256, you had 6 layers. We adjust this for IMAGE_SIZE.\n    FEATURE_MAPS = 64\n\nprint(f\"Using device: {Config.DEVICE}\")\n\n\n#---------------------------------------------------\n# 2. MODEL DEFINITIONS (Generator and Discriminator)\n#---------------------------------------------------\n\n# Your Generator, slightly adjusted to be configurable for 64x64 images\nclass Generator(nn.Module):\n    def __init__(self, noise_dim, image_channels, feature_maps):\n        super(Generator, self).__init__()\n        self.net = nn.Sequential(\n            # Input: (N, noise_dim, 1, 1)\n            self._make_block(noise_dim, feature_maps * 8, kernel_size=4, stride=1, padding=0), # -> 4x4\n            self._make_block(feature_maps * 8, feature_maps * 4, kernel_size=4, stride=2, padding=1), # -> 8x8\n            self._make_block(feature_maps * 4, feature_maps * 2, kernel_size=4, stride=2, padding=1), # -> 16x16\n            self._make_block(feature_maps * 2, feature_maps, kernel_size=4, stride=2, padding=1), # -> 32x32\n            nn.ConvTranspose2d(feature_maps, image_channels, kernel_size=4, stride=2, padding=1), # -> 64x64\n            nn.Tanh()\n        )\n\n    def _make_block(self, in_channels, out_channels, kernel_size, stride, padding):\n        return nn.Sequential(\n            nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, padding, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(True),\n        )\n\n    def forward(self, x):\n        return self.net(x)\n\n# Discriminator for a 64x64 image\nclass Discriminator(nn.Module):\n    def __init__(self, image_channels, feature_maps):\n        super(Discriminator, self).__init__()\n        self.net = nn.Sequential(\n            # Input: (N, 3, 64, 64)\n            nn.Conv2d(image_channels, feature_maps, kernel_size=4, stride=2, padding=1), # -> 32x32\n            nn.LeakyReLU(0.2),\n            self._make_block(feature_maps, feature_maps * 2, kernel_size=4, stride=2, padding=1), # -> 16x16\n            self._make_block(feature_maps * 2, feature_maps * 4, kernel_size=4, stride=2, padding=1), # -> 8x8\n            self._make_block(feature_maps * 4, feature_maps * 8, kernel_size=4, stride=2, padding=1), # -> 4x4\n            nn.Conv2d(feature_maps * 8, 1, kernel_size=4, stride=1, padding=0), # -> 1x1\n            nn.Sigmoid()\n        )\n\n    def _make_block(self, in_channels, out_channels, kernel_size, stride, padding):\n        return nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.LeakyReLU(0.2),\n        )\n\n    def forward(self, x):\n        return self.net(x).view(-1, 1) # Flatten to (batch_size, 1)\n\n# Function to initialize weights as recommended by the DCGAN paper\ndef weights_init(m):\n    classname = m.__class__.__name__\n    if classname.find('Conv') != -1:\n        nn.init.normal_(m.weight.data, 0.0, 0.02)\n    elif classname.find('BatchNorm') != -1:\n        nn.init.normal_(m.weight.data, 1.0, 0.02)\n        nn.init.constant_(m.bias.data, 0)\n\n\n#---------------------------------------------------\n# 3. DATASET AND DATALOADER SETUP\n#---------------------------------------------------\n\n# Data augmentation and normalization\n# We resize images to IMAGE_SIZE and normalize to [-1, 1] as required by the Tanh activation\ntransform = transforms.Compose([\n    transforms.Resize(Config.IMAGE_SIZE),\n    transforms.CenterCrop(Config.IMAGE_SIZE),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.5, 0.5, 0.5],\n        std=[0.5, 0.5, 0.5]\n    )\n])\n\n# ImageFolder is great for datasets where images are in a single folder\n# IMPORTANT: Make sure your DATASET_PATH points to the folder containing the images\n# dataset = datasets.ImageFolder(root=Config.DATASET_PATH, transform=transform)\ndataset = ImageDataset(root_dir=Config.DATASET_PATH, transform=transform)\ndataloader = DataLoader(dataset, batch_size=Config.BATCH_SIZE, shuffle=True, num_workers=2)\n\n\n#---------------------------------------------------\n# 4. INITIALIZE MODELS, OPTIMIZERS, LOSS\n#---------------------------------------------------\n\n# Instantiate models\ngen = Generator(Config.NOISE_DIM, Config.CHANNELS_IMG, Config.FEATURE_MAPS).to(Config.DEVICE)\ndisc = Discriminator(Config.CHANNELS_IMG, Config.FEATURE_MAPS).to(Config.DEVICE)\n\n# Apply weight initialization\ngen.apply(weights_init)\ndisc.apply(weights_init)\n\n# Setup Optimizers\noptimizerG = optim.Adam(gen.parameters(), lr=Config.LR, betas=(Config.BETA1, 0.999))\noptimizerD = optim.Adam(disc.parameters(), lr=Config.LR, betas=(Config.BETA1, 0.999))\n\n# Loss function\ncriterion = nn.BCELoss()\n\n# Fixed noise for consistent visualization throughout training\nfixed_noise = torch.randn(Config.BATCH_SIZE, Config.NOISE_DIM, 1, 1).to(Config.DEVICE)\n\n# Lists to track progress\nG_losses = []\nD_losses = []\n\n\n#---------------------------------------------------\n# 5. THE TRAINING LOOP\n#---------------------------------------------------\n\nprint(\"Starting Training...\")\ngen.train()\ndisc.train()\n\nfor epoch in range(Config.NUM_EPOCHS):\n    for i, (real_imgs, _) in enumerate(tqdm(dataloader, desc=f\"Epoch {epoch+1}/{Config.NUM_EPOCHS}\")):\n        real_imgs = real_imgs.to(Config.DEVICE)\n        batch_size = real_imgs.shape[0]\n\n        ## ---------------------------------\n        ## Train Discriminator: max log(D(x)) + log(1 - D(G(z)))\n        ## ---------------------------------\n\n        disc.zero_grad()\n\n        # --- Train with REAL images ---\n        # Using soft labels (e.g., 0.9 instead of 1.0) for stability\n        label_real = torch.full((batch_size, 1), 0.9, device=Config.DEVICE)\n        output_real = disc(real_imgs)\n        lossD_real = criterion(output_real, label_real)\n        lossD_real.backward()\n\n        # --- Train with FAKE images ---\n        noise = torch.randn(batch_size, Config.NOISE_DIM, 1, 1, device=Config.DEVICE)\n        fake_imgs = gen(noise)\n        label_fake = torch.full((batch_size, 1), 0.1, device=Config.DEVICE)\n        \n        # We use .detach() to avoid backpropagating through the Generator\n        # We only want to update the Discriminator at this step\n        output_fake = disc(fake_imgs.detach())\n        lossD_fake = criterion(output_fake, label_fake)\n        lossD_fake.backward()\n        \n        # Total discriminator loss and optimizer step\n        lossD = lossD_real + lossD_fake\n        optimizerD.step()\n\n        ## ---------------------------------\n        ## Train Generator: max log(D(G(z)))\n        ## ---------------------------------\n        \n        gen.zero_grad()\n        \n        # We want the generator to fool the discriminator, so we use REAL labels (1.0)\n        # for the generator's loss calculation.\n        label_gen = torch.full((batch_size, 1), 1.0, device=Config.DEVICE)\n        \n        # Re-run the discriminator on the fake images (no detach this time)\n        output_gen = disc(fake_imgs)\n        lossG = criterion(output_gen, label_gen)\n        lossG.backward()\n        optimizerG.step()\n        \n        # Save losses for plotting\n        G_losses.append(lossG.item())\n        D_losses.append(lossD.item())\n\n    # At the end of each epoch, save and visualize results\n    print(f\"Epoch [{epoch+1}/{Config.NUM_EPOCHS}] Loss D: {lossD.item():.4f}, Loss G: {lossG.item():.4f}\")\n\n    with torch.no_grad():\n        # Generate images from the fixed noise vector for consistent comparison\n        gen.eval() # Set to evaluation mode\n        fake_samples = gen(fixed_noise).detach().cpu()\n        gen.train() # Set back to training mode\n        \n        # Denormalize images from [-1, 1] to [0, 1] for saving\n        fake_samples = (fake_samples + 1) / 2\n        \n        # Save a grid of generated images\n        grid = torchvision.utils.make_grid(fake_samples, nrow=8)\n        torchvision.utils.save_image(grid, f\"/kaggle/working/generated_images/epoch_{epoch+1}.png\")\n\n#---------------------------------------------------\n# 6. SAVE MODELS AND PLOT RESULTS\n#---------------------------------------------------\n\n# Save the trained models\ntorch.save(gen.state_dict(), '/kaggle/working/generator.pth')\ntorch.save(disc.state_dict(), '/kaggle/working/discriminator.pth')\nprint(\"Models saved to /kaggle/working/\")\n\n# Plot the training losses\nplt.figure(figsize=(10,5))\nplt.title(\"Generator and Discriminator Loss During Training\")\nplt.plot(G_losses, label=\"G\")\nplt.plot(D_losses, label=\"D\")\nplt.xlabel(\"iterations\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.savefig(\"/kaggle/working/loss_curve.png\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-12T20:02:46.182248Z","iopub.execute_input":"2025-09-12T20:02:46.182861Z","iopub.status.idle":"2025-09-12T20:03:05.167610Z","shell.execute_reply.started":"2025-09-12T20:02:46.182800Z","shell.execute_reply":"2025-09-12T20:03:05.166873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}