{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":31236,"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)\nimport matplotlib.pyplot as plt\nimport tensorflow as tf \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\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\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":"2026-01-02T13:01:16.814976Z","iopub.execute_input":"2026-01-02T13:01:16.815382Z","iopub.status.idle":"2026-01-02T13:01:18.831411Z","shell.execute_reply.started":"2026-01-02T13:01:16.815354Z","shell.execute_reply":"2026-01-02T13:01:18.830417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"monet_path = '/kaggle/input/gan-getting-started/monet_jpg'\nphoto_path = '/kaggle/input/gan-getting-started/photo_jpg'\nmonet_images = [os.path.join(monet_path, f) for f in os.listdir(monet_path) if f.endswith('.jpg')]\nphoto_images = [os.path.join(photo_path,f) for f in os.listdir(photo_path) if f.endswith('.jpg')]\nprint(f'Monet images: {len(monet_images)}')\nprint(f'Photo images: {len(photo_images)}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:18.832976Z","iopub.execute_input":"2026-01-02T13:01:18.833252Z","iopub.status.idle":"2026-01-02T13:01:18.848407Z","shell.execute_reply.started":"2026-01-02T13:01:18.833229Z","shell.execute_reply":"2026-01-02T13:01:18.847678Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# EDA","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\ndef show_images(image_paths, title, n=6, cols=3, figsize=(10, 6)):\n    rows = (n + cols - 1) // cols\n    sampled_paths = random.sample(image_paths, n)\n\n    plt.figure(figsize=figsize)\n    for i, img_path in enumerate(sampled_paths):\n        img = Image.open(img_path)\n        plt.subplot(rows, cols, i + 1)\n        plt.imshow(img)\n        plt.axis(\"off\")\n    plt.suptitle(title, fontsize=16)\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:18.849414Z","iopub.execute_input":"2026-01-02T13:01:18.849839Z","iopub.status.idle":"2026-01-02T13:01:18.855486Z","shell.execute_reply.started":"2026-01-02T13:01:18.849810Z","shell.execute_reply":"2026-01-02T13:01:18.854813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"show_images(monet_images, title=\"Sample Monet Images\", n=6)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:18.856290Z","iopub.execute_input":"2026-01-02T13:01:18.856531Z","iopub.status.idle":"2026-01-02T13:01:19.244039Z","shell.execute_reply.started":"2026-01-02T13:01:18.856499Z","shell.execute_reply":"2026-01-02T13:01:19.243164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"show_images(photo_images, title=\"Sample Photo Images\", n=6)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.245895Z","iopub.execute_input":"2026-01-02T13:01:19.246172Z","iopub.status.idle":"2026-01-02T13:01:19.581802Z","shell.execute_reply.started":"2026-01-02T13:01:19.246148Z","shell.execute_reply":"2026-01-02T13:01:19.580933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_image_sizes(image_paths, n=50):\n    sizes = []\n    for p in random.sample(image_paths, n):\n        with Image.open(p) as img:\n            sizes.append(img.size)\n    return sizes\n\nprint(\"Monet image sizes (sample):\", get_image_sizes(monet_images))\nprint(\"Photo image sizes (sample):\", get_image_sizes(photo_images))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.583226Z","iopub.execute_input":"2026-01-02T13:01:19.583658Z","iopub.status.idle":"2026-01-02T13:01:19.650613Z","shell.execute_reply.started":"2026-01-02T13:01:19.583624Z","shell.execute_reply":"2026-01-02T13:01:19.650047Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compare_domains(monet_paths, photo_paths, n=3):\n    plt.figure(figsize=(8, 4))\n    for i in range(n):\n        monet_img = Image.open(random.choice(monet_paths))\n        photo_img = Image.open(random.choice(photo_paths))\n\n        plt.subplot(2, n, i + 1)\n        plt.imshow(monet_img)\n        plt.axis(\"off\")\n        if i == 0:\n            plt.ylabel(\"Monet\")\n\n        plt.subplot(2, n, i + 1 + n)\n        plt.imshow(photo_img)\n        plt.axis(\"off\")\n        if i == 0:\n            plt.ylabel(\"Photo\")\n\n    plt.tight_layout()\n    plt.show()\n\ncompare_domains(monet_images, photo_images)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.651607Z","iopub.execute_input":"2026-01-02T13:01:19.651910Z","iopub.status.idle":"2026-01-02T13:01:19.852206Z","shell.execute_reply.started":"2026-01-02T13:01:19.651878Z","shell.execute_reply":"2026-01-02T13:01:19.851429Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# DCGAN\n\nDCGAN is single‑domain → we train only on Monet images to generate Monet‑style paintings.","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader\nfrom torchvision import datasets, transforms, utils","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.853117Z","iopub.execute_input":"2026-01-02T13:01:19.853333Z","iopub.status.idle":"2026-01-02T13:01:19.858210Z","shell.execute_reply.started":"2026-01-02T13:01:19.853314Z","shell.execute_reply":"2026-01-02T13:01:19.857361Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Reproducibility\nseed = 42\nrandom.seed(seed)\ntorch.manual_seed(seed)\n\n# Path\nMONET_PATH = \"/kaggle/input/gan-getting-started/monet_jpg\"\n\n# Hyperparameters\nIMAGE_SIZE = 64\nBATCH_SIZE = 128\nNUM_EPOCHS = 100\nLR = 2e-4\nBETA1 = 0.5\nNZ = 100        # Latent vector size\nNGF = 64        # Generator feature maps\nNDF = 64        # Discriminator feature maps\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.859068Z","iopub.execute_input":"2026-01-02T13:01:19.859345Z","iopub.status.idle":"2026-01-02T13:01:19.874054Z","shell.execute_reply.started":"2026-01-02T13:01:19.859308Z","shell.execute_reply":"2026-01-02T13:01:19.873292Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"DCGAN is sensitive to data preprocessing\nImages must be:\n* Resized\n* Normalized to [-1, 1] (because Generator uses Tanh)\n","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import Dataset\nfrom PIL import Image\n\nclass MonetDataset(Dataset):\n    def __init__(self, image_dir, transform=None):\n        self.image_dir = image_dir\n        self.transform = transform\n        self.images = [\n            os.path.join(image_dir, f)\n            for f in os.listdir(image_dir)\n            if f.endswith(\".jpg\")\n        ]\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img = Image.open(self.images[idx]).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.874936Z","iopub.execute_input":"2026-01-02T13:01:19.875215Z","iopub.status.idle":"2026-01-02T13:01:19.887452Z","shell.execute_reply.started":"2026-01-02T13:01:19.875181Z","shell.execute_reply":"2026-01-02T13:01:19.886710Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize(IMAGE_SIZE),\n    transforms.CenterCrop(IMAGE_SIZE),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5, 0.5, 0.5),\n                         (0.5, 0.5, 0.5))\n])\n\ndataset = MonetDataset(\n    image_dir=MONET_PATH,\n    transform=transform\n)\n\ndataloader = DataLoader(\n    dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=2,\n    drop_last=True\n)\n\nprint(f\"Loaded {len(dataset)} Monet images\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.888184Z","iopub.execute_input":"2026-01-02T13:01:19.888368Z","iopub.status.idle":"2026-01-02T13:01:19.909536Z","shell.execute_reply.started":"2026-01-02T13:01:19.888350Z","shell.execute_reply":"2026-01-02T13:01:19.908760Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"GANs are very unstable\nDCGAN paper recommends: Normal init (mean=0, std=0.02)","metadata":{}},{"cell_type":"code","source":"def 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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.910375Z","iopub.execute_input":"2026-01-02T13:01:19.910654Z","iopub.status.idle":"2026-01-02T13:01:19.925523Z","shell.execute_reply.started":"2026-01-02T13:01:19.910627Z","shell.execute_reply":"2026-01-02T13:01:19.924885Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Generator\nMaps random noise → image\nUses:\n* ConvTranspose2d for upsampling\n* BatchNorm for stability\n* ReLU except last layer\n* Tanh → outputs in [-1, 1]","metadata":{}},{"cell_type":"code","source":"class Generator(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.net = nn.Sequential(\n            # (NZ, 1, 1) → (NGF*8, 4, 4)\n            nn.ConvTranspose2d(NZ, NGF*8, 4, 1, 0, bias=False),\n            nn.BatchNorm2d(NGF*8),\n            nn.ReLU(True),\n\n            # 8x8\n            nn.ConvTranspose2d(NGF*8, NGF*4, 4, 2, 1, bias=False),\n            nn.BatchNorm2d(NGF*4),\n            nn.ReLU(True),\n\n            # 16x16\n            nn.ConvTranspose2d(NGF*4, NGF*2, 4, 2, 1, bias=False),\n            nn.BatchNorm2d(NGF*2),\n            nn.ReLU(True),\n\n            # 32x32\n            nn.ConvTranspose2d(NGF*2, NGF, 4, 2, 1, bias=False),\n            nn.BatchNorm2d(NGF),\n            nn.ReLU(True),\n\n            # 64x64\n            nn.ConvTranspose2d(NGF, 3, 4, 2, 1, bias=False),\n            nn.Tanh()\n        )\n\n    def forward(self, x):\n        return self.net(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.926437Z","iopub.execute_input":"2026-01-02T13:01:19.926677Z","iopub.status.idle":"2026-01-02T13:01:19.940672Z","shell.execute_reply.started":"2026-01-02T13:01:19.926646Z","shell.execute_reply":"2026-01-02T13:01:19.940076Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Discriminator\nClassifies real vs fake\nUses:\n* LeakyReLU → prevents dead gradients\n* No BatchNorm on first layer (DCGAN rule)\n* Sigmoid → probability","metadata":{}},{"cell_type":"code","source":"class Discriminator(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.net = nn.Sequential(\n            # 64 → 32\n            nn.Conv2d(3, NDF, 4, 2, 1, bias=False),\n            nn.LeakyReLU(0.2, inplace=True),\n\n            # 32 → 16\n            nn.Conv2d(NDF, NDF*2, 4, 2, 1, bias=False),\n            nn.BatchNorm2d(NDF*2),\n            nn.LeakyReLU(0.2, inplace=True),\n\n            # 16 → 8\n            nn.Conv2d(NDF*2, NDF*4, 4, 2, 1, bias=False),\n            nn.BatchNorm2d(NDF*4),\n            nn.LeakyReLU(0.2, inplace=True),\n\n            # 8 → 4\n            nn.Conv2d(NDF*4, NDF*8, 4, 2, 1, bias=False),\n            nn.BatchNorm2d(NDF*8),\n            nn.LeakyReLU(0.2, inplace=True),\n\n            # 4 → 1\n            nn.Conv2d(NDF*8, 1, 4, 1, 0, bias=False),\n            # nn.Sigmoid()\n        )\n\n    def forward(self, x):\n        return self.net(x).view(-1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.943355Z","iopub.execute_input":"2026-01-02T13:01:19.943766Z","iopub.status.idle":"2026-01-02T13:01:19.961260Z","shell.execute_reply.started":"2026-01-02T13:01:19.943744Z","shell.execute_reply":"2026-01-02T13:01:19.960560Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Model Init, Loss & Optimizers","metadata":{}},{"cell_type":"code","source":"G = Generator().to(DEVICE)\nD = Discriminator().to(DEVICE)\n\nG.apply(weights_init)\nD.apply(weights_init)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:19.962001Z","iopub.execute_input":"2026-01-02T13:01:19.962264Z","iopub.status.idle":"2026-01-02T13:01:20.030677Z","shell.execute_reply.started":"2026-01-02T13:01:19.962242Z","shell.execute_reply":"2026-01-02T13:01:20.029888Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# criterion = nn.BCELoss()\ncriterion = nn.BCEWithLogitsLoss()\n\n# optimizerG = optim.Adam(G.parameters(), lr=LR, betas=(BETA1, 0.999))\n# optimizerD = optim.Adam(D.parameters(), lr=LR, betas=(BETA1, 0.999))\noptimizerD = torch.optim.Adam(D.parameters(), lr=1e-4, betas=(0.5, 0.999))\noptimizerG = torch.optim.Adam(G.parameters(), lr=2e-4, betas=(0.5, 0.999))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:20.031563Z","iopub.execute_input":"2026-01-02T13:01:20.031851Z","iopub.status.idle":"2026-01-02T13:01:20.036723Z","shell.execute_reply.started":"2026-01-02T13:01:20.031818Z","shell.execute_reply":"2026-01-02T13:01:20.036152Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torchvision.utils as vutils\n\nfixed_noise = torch.randn(64, NZ, 1, 1, device=DEVICE)\nprint(fixed_noise.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:20.037522Z","iopub.execute_input":"2026-01-02T13:01:20.037723Z","iopub.status.idle":"2026-01-02T13:01:20.053929Z","shell.execute_reply.started":"2026-01-02T13:01:20.037687Z","shell.execute_reply":"2026-01-02T13:01:20.053387Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(NUM_EPOCHS):\n    for i, real in enumerate(dataloader):\n\n        real = real.to(DEVICE)\n        b_size = real.size(0)\n\n        # ======================\n        # Train Discriminator\n        # ======================\n        D.zero_grad()\n\n        # label_real = torch.ones(b_size, device=DEVICE)\n        label_real = torch.full((b_size,), 0.9, device=DEVICE)\n        output_real = D(real)\n        loss_real = criterion(output_real, label_real)\n\n        noise = torch.randn(b_size, NZ, 1, 1, device=DEVICE)\n        fake = G(noise)\n        label_fake = torch.zeros(b_size, device=DEVICE)\n        output_fake = D(fake.detach())\n        loss_fake = criterion(output_fake, label_fake)\n\n        loss_D = loss_real + loss_fake\n        loss_D.backward()\n        optimizerD.step()\n\n        # ======================\n        # Train Generator\n        # ======================\n        G.zero_grad()\n\n        label_gen = torch.ones(b_size, device=DEVICE)\n        output = D(fake)\n        loss_G = criterion(output, label_gen)\n        loss_G.backward()\n        optimizerG.step()\n\n    print(f\"Epoch [{epoch+1}/{NUM_EPOCHS}]  \"\n          f\"Loss D: {loss_D.item():.4f}  Loss G: {loss_G.item():.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:01:20.054762Z","iopub.execute_input":"2026-01-02T13:01:20.055038Z","iopub.status.idle":"2026-01-02T13:02:26.467454Z","shell.execute_reply.started":"2026-01-02T13:01:20.054989Z","shell.execute_reply":"2026-01-02T13:02:26.466561Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport torchvision.utils as utils\n\nwith torch.no_grad():\n    fake = G(fixed_noise).cpu()\n\nplt.figure(figsize=(8, 8))\nplt.axis(\"off\")\nplt.title(\"Generated Monet Images\")\nplt.imshow(\n    np.transpose(\n        utils.make_grid(fake, padding=2, normalize=True),\n        (1, 2, 0)\n    )\n)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:02:26.468912Z","iopub.execute_input":"2026-01-02T13:02:26.469292Z","iopub.status.idle":"2026-01-02T13:02:26.774466Z","shell.execute_reply.started":"2026-01-02T13:02:26.469262Z","shell.execute_reply":"2026-01-02T13:02:26.773591Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CycleGAN","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torchvision import transforms\nfrom torchvision.datasets import ImageFolder\nfrom torch.utils.data import DataLoader, Dataset\nfrom PIL import Image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:02:26.775563Z","iopub.execute_input":"2026-01-02T13:02:26.775835Z","iopub.status.idle":"2026-01-02T13:02:26.780330Z","shell.execute_reply.started":"2026-01-02T13:02:26.775800Z","shell.execute_reply":"2026-01-02T13:02:26.779657Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMAGE_SIZE = 256\nBATCH_SIZE = 1      \nNUM_WORKERS = 1\nLR = 2e-4\nEPOCHS = 100\nLAMBDA_CYCLE = 10\nLAMBDA_ID = 5\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:09.874075Z","iopub.execute_input":"2026-01-02T13:03:09.874393Z","iopub.status.idle":"2026-01-02T13:03:09.878948Z","shell.execute_reply.started":"2026-01-02T13:03:09.874368Z","shell.execute_reply":"2026-01-02T13:03:09.878079Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MonetPhotoDataset(Dataset):\n    def __init__(self, root, transform=None):\n        self.root = root\n        self.transform = transform\n        self.images = sorted(os.listdir(root))\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(self.root, self.images[idx])\n        image = Image.open(img_path).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:11.620596Z","iopub.execute_input":"2026-01-02T13:03:11.621232Z","iopub.status.idle":"2026-01-02T13:03:11.626043Z","shell.execute_reply.started":"2026-01-02T13:03:11.621202Z","shell.execute_reply":"2026-01-02T13:03:11.625300Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize(286),\n    transforms.RandomCrop(IMAGE_SIZE),\n    transforms.RandomHorizontalFlip(),\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\nmonet_dataset = MonetPhotoDataset(monet_path, transform)\nphoto_dataset = MonetPhotoDataset(photo_path, transform)\n\nmonet_loader = DataLoader(\n    monet_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=NUM_WORKERS,\n    pin_memory=True,\n    persistent_workers=True\n)\n\nphoto_loader = DataLoader(\n    photo_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=NUM_WORKERS,\n    pin_memory=True,\n    persistent_workers=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:13.459502Z","iopub.execute_input":"2026-01-02T13:03:13.460273Z","iopub.status.idle":"2026-01-02T13:03:13.472420Z","shell.execute_reply.started":"2026-01-02T13:03:13.460242Z","shell.execute_reply":"2026-01-02T13:03:13.471764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ResnetBlock(nn.Module):\n    def __init__(self, dim):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(dim, dim, 3),\n            nn.InstanceNorm2d(dim),\n            nn.ReLU(True),\n\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(dim, dim, 3),\n            nn.InstanceNorm2d(dim)\n        )\n\n    def forward(self, x):\n        return x + self.block(x)\n\n\nclass Generator(nn.Module):\n    def __init__(self, in_channels=3, out_channels=3, n_blocks=9):\n        super().__init__()\n\n        layers = [\n            nn.ReflectionPad2d(3),\n            nn.Conv2d(in_channels, 64, 7),\n            nn.InstanceNorm2d(64),\n            nn.ReLU(True)\n        ]\n\n        # Downsampling\n        curr_dim = 64\n        for _ in range(2):\n            layers += [\n                nn.Conv2d(curr_dim, curr_dim * 2, 3, stride=2, padding=1),\n                nn.InstanceNorm2d(curr_dim * 2),\n                nn.ReLU(True)\n            ]\n            curr_dim *= 2\n\n        # ResNet blocks\n        for _ in range(n_blocks):\n            layers.append(ResnetBlock(curr_dim))\n\n        # Upsampling\n        for _ in range(2):\n            layers += [\n                nn.ConvTranspose2d(curr_dim, curr_dim // 2, 3, stride=2, padding=1, output_padding=1),\n                nn.InstanceNorm2d(curr_dim // 2),\n                nn.ReLU(True)\n            ]\n            curr_dim //= 2\n\n        layers += [\n            nn.ReflectionPad2d(3),\n            nn.Conv2d(64, out_channels, 7),\n            nn.Tanh()\n        ]\n\n        self.model = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.model(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:15.848194Z","iopub.execute_input":"2026-01-02T13:03:15.848730Z","iopub.status.idle":"2026-01-02T13:03:15.858248Z","shell.execute_reply.started":"2026-01-02T13:03:15.848700Z","shell.execute_reply":"2026-01-02T13:03:15.857558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Discriminator(nn.Module):\n    def __init__(self, in_channels=3):\n        super().__init__()\n\n        def block(in_c, out_c, normalize=True):\n            layers = [nn.Conv2d(in_c, out_c, 4, stride=2, padding=1)]\n            if normalize:\n                layers.append(nn.InstanceNorm2d(out_c))\n            layers.append(nn.LeakyReLU(0.2, True))\n            return layers\n\n        self.model = nn.Sequential(\n            *block(in_channels, 64, normalize=False),\n            *block(64, 128),\n            *block(128, 256),\n            *block(256, 512),\n            nn.Conv2d(512, 1, 4, padding=1)\n        )\n\n    def forward(self, x):\n        return self.model(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:18.474123Z","iopub.execute_input":"2026-01-02T13:03:18.474425Z","iopub.status.idle":"2026-01-02T13:03:18.480262Z","shell.execute_reply.started":"2026-01-02T13:03:18.474401Z","shell.execute_reply":"2026-01-02T13:03:18.479395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion_gan = nn.MSELoss()\ncriterion_cycle = nn.L1Loss()\ncriterion_identity = nn.L1Loss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:22.154488Z","iopub.execute_input":"2026-01-02T13:03:22.155233Z","iopub.status.idle":"2026-01-02T13:03:22.158959Z","shell.execute_reply.started":"2026-01-02T13:03:22.155200Z","shell.execute_reply":"2026-01-02T13:03:22.158202Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generators\nG_P2M = Generator().to(DEVICE)   # Photo → Monet\nG_M2P = Generator().to(DEVICE)   # Monet → Photo\n\n# Discriminators\nD_M = Discriminator().to(DEVICE)  # Monet discriminator\nD_P = Discriminator().to(DEVICE)  # Photo discriminator","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:23.297446Z","iopub.execute_input":"2026-01-02T13:03:23.298120Z","iopub.status.idle":"2026-01-02T13:03:23.541283Z","shell.execute_reply.started":"2026-01-02T13:03:23.298082Z","shell.execute_reply":"2026-01-02T13:03:23.540677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer_G = optim.Adam(\n    list(G_P2M.parameters()) + list(G_M2P.parameters()),\n    lr=LR, betas=(0.5, 0.999)\n)\n\noptimizer_D_M = optim.Adam(D_M.parameters(), lr=LR, betas=(0.5, 0.999))\noptimizer_D_P = optim.Adam(D_P.parameters(), lr=LR, betas=(0.5, 0.999))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:25.212203Z","iopub.execute_input":"2026-01-02T13:03:25.212995Z","iopub.status.idle":"2026-01-02T13:03:25.218310Z","shell.execute_reply.started":"2026-01-02T13:03:25.212960Z","shell.execute_reply":"2026-01-02T13:03:25.217568Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scaler = torch.amp.GradScaler(\"cuda\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:26.710108Z","iopub.execute_input":"2026-01-02T13:03:26.710839Z","iopub.status.idle":"2026-01-02T13:03:26.714203Z","shell.execute_reply.started":"2026-01-02T13:03:26.710809Z","shell.execute_reply":"2026-01-02T13:03:26.713417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(EPOCHS):\n    for real_monet, real_photo in zip(monet_loader, photo_loader):\n\n        real_monet = real_monet.to(DEVICE)\n        real_photo = real_photo.to(DEVICE)\n\n        # ------------------\n        # PatchGAN labels (shape only, no grads)\n        # ------------------\n        with torch.no_grad():\n            pred_shape_M = D_M(real_monet).shape\n            pred_shape_P = D_P(real_photo).shape\n\n        valid_monet = torch.ones(pred_shape_M, device=DEVICE)\n        fake_monet  = torch.zeros(pred_shape_M, device=DEVICE)\n\n        valid_photo = torch.ones(pred_shape_P, device=DEVICE)\n        fake_photo  = torch.zeros(pred_shape_P, device=DEVICE)\n\n        # ======================\n        #  Train Generators\n        # ======================\n        optimizer_G.zero_grad(set_to_none=True)\n\n        with torch.cuda.amp.autocast():\n            # Identity loss\n            loss_id = (\n                criterion_identity(G_P2M(real_monet), real_monet) +\n                criterion_identity(G_M2P(real_photo), real_photo)\n            ) * LAMBDA_ID\n\n            # GAN loss\n            fake_monet_img = G_P2M(real_photo)\n            fake_photo_img = G_M2P(real_monet)\n\n            loss_GAN = (\n                criterion_gan(D_M(fake_monet_img), valid_monet) +\n                criterion_gan(D_P(fake_photo_img), valid_photo)\n            )\n\n            # Cycle loss\n            rec_monet = G_P2M(fake_photo_img)\n            rec_photo = G_M2P(fake_monet_img)\n\n            loss_cycle = (\n                criterion_cycle(rec_monet, real_monet) +\n                criterion_cycle(rec_photo, real_photo)\n            ) * LAMBDA_CYCLE\n\n            loss_G = loss_GAN + loss_cycle + loss_id\n\n        scaler.scale(loss_G).backward()\n        scaler.step(optimizer_G)\n        scaler.update()\n\n        # ======================\n        #  Train Discriminator M\n        # ======================\n        optimizer_D_M.zero_grad(set_to_none=True)\n\n        with torch.cuda.amp.autocast():\n            loss_D_M = (\n                criterion_gan(D_M(real_monet), valid_monet) +\n                criterion_gan(D_M(fake_monet_img.detach()), fake_monet)\n            ) * 0.5\n\n        scaler.scale(loss_D_M).backward()\n        scaler.step(optimizer_D_M)\n        scaler.update()\n\n        # ======================\n        #  Train Discriminator P\n        # ======================\n        optimizer_D_P.zero_grad(set_to_none=True)\n\n        with torch.cuda.amp.autocast():\n            loss_D_P = (\n                criterion_gan(D_P(real_photo), valid_photo) +\n                criterion_gan(D_P(fake_photo_img.detach()), fake_photo)\n            ) * 0.5\n\n        scaler.scale(loss_D_P).backward()\n        scaler.step(optimizer_D_P)\n        scaler.update()\n\n    print(\n        f\"Epoch [{epoch+1}/{EPOCHS}] \"\n        f\"Loss G: {loss_G.item():.3f} \"\n        f\"Loss D_M: {loss_D_M.item():.3f} \"\n        f\"Loss D_P: {loss_D_P.item():.3f}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-02T13:03:27.916993Z","iopub.execute_input":"2026-01-02T13:03:27.917732Z","execution_failed":"2026-01-02T13:59:28.468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save(G_P2M.state_dict(), \"G_P2M.pth\")","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-02T13:59:28.469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"G_P2M.eval()\nG_M2P.eval()","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-02T13:59:28.469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def denorm(t):\n    return (t + 1) / 2","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-02T13:59:28.469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Get one photo\nreal_photo = next(iter(photo_loader)).to(DEVICE)\n\nwith torch.no_grad():\n    fake_monet = G_P2M(real_photo)\n\n# Convert for display\nreal_photo_img = denorm(real_photo[0]).permute(1, 2, 0).cpu()\nfake_monet_img = denorm(fake_monet[0]).permute(1, 2, 0).cpu()\n\n# Plot\nplt.figure(figsize=(8, 4))\n\nplt.subplot(1, 2, 1)\nplt.imshow(real_photo_img)\nplt.title(\"Real Photo\")\nplt.axis(\"off\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(fake_monet_img)\nplt.title(\"Photo → Monet\")\nplt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-02T13:59:28.469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with torch.no_grad():\n    rec_photo = G_M2P(fake_monet)\n\nrec_photo_img = denorm(rec_photo[0]).permute(1, 2, 0).cpu()\n\nplt.figure(figsize=(12, 4))\n\nplt.subplot(1, 3, 1)\nplt.imshow(real_photo_img)\nplt.title(\"Real Photo\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 2)\nplt.imshow(fake_monet_img)\nplt.title(\"Photo → Monet\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 3)\nplt.imshow(rec_photo_img)\nplt.title(\"Reconstructed Photo\")\nplt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-02T13:59:28.469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"real_monet = next(iter(monet_loader)).to(DEVICE)\n\nwith torch.no_grad():\n    fake_photo = G_M2P(real_monet)\n\nreal_monet_img = denorm(real_monet[0]).permute(1, 2, 0).cpu()\nfake_photo_img = denorm(fake_photo[0]).permute(1, 2, 0).cpu()\n\nplt.figure(figsize=(8, 4))\n\nplt.subplot(1, 2, 1)\nplt.imshow(real_monet_img)\nplt.title(\"Real Monet\")\nplt.axis(\"off\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(fake_photo_img)\nplt.title(\"Monet → Photo\")\nplt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-02T13:59:28.469Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"code","source":"G_P2M.eval()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submit_transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5, 0.5, 0.5),\n                         (0.5, 0.5, 0.5))\n])\n\nphoto_submit_ds = MonetPhotoDataset(photo_path, submit_transform)\nphoto_submit_loader = DataLoader(\n    photo_submit_ds,\n    batch_size=1,\n    shuffle=False,\n    num_workers=1\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, shutil\nimport numpy as np\nfrom PIL import Image\nfrom tqdm import tqdm\n\nos.makedirs(\"../images\", exist_ok=True)\n\nG_P2M.eval()\ntorch.set_grad_enabled(False)\n\ni = 1\n\nfor img in tqdm(photo_submit_loader):\n    img = img.to(DEVICE)\n\n    fake_monet = G_P2M(img)[0]   # (3, H, W)\n    \n    # [-1, 1] → [0, 255]\n    fake_monet = (fake_monet * 0.5 + 0.5).clamp(0, 1)\n    fake_monet = (fake_monet * 255).byte()\n\n    fake_monet = fake_monet.permute(1, 2, 0).cpu().numpy()\n    im = Image.fromarray(fake_monet)\n\n    im.save(f\"../images/{i}.jpg\", quality=95)\n    i += 1\n\n    if i > 7000:   # Kaggle requirement\n        break","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"shutil.make_archive(\n    \"/kaggle/working/images\",\n    \"zip\",\n    \"../images\"\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}