{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","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"},{"sourceId":12403896,"sourceType":"datasetVersion","datasetId":7822321}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as FUNC\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import transforms, datasets, utils\nfrom PIL import Image\nimport numpy as np\nfrom tqdm import tqdm\nimport os\nfrom torchvision.models import inception_v3\nfrom scipy import linalg\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:01.892098Z","iopub.execute_input":"2025-07-08T01:47:01.892285Z","iopub.status.idle":"2025-07-08T01:47:09.235662Z","shell.execute_reply.started":"2025-07-08T01:47:01.892268Z","shell.execute_reply":"2025-07-08T01:47:09.235018Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ResnetBlock(nn.Module):\n    def __init__(self, dim):\n        super().__init__()\n        self.conv_block = nn.Sequential(\n            nn.Conv2d(dim, dim, 3, 1, 1, bias=False),\n            nn.InstanceNorm2d(dim),\n            nn.ReLU(True),\n            nn.Conv2d(dim, dim, 3, 1, 1, bias=False),\n            nn.InstanceNorm2d(dim),\n        )\n\n    def forward(self, x):\n        return x + self.conv_block(x)\n\nclass ResnetGenerator(nn.Module):\n    def __init__(self, input_nc, output_nc, n_blocks=9):\n        super().__init__()\n        model = [\n            nn.Conv2d(input_nc, 64, 7, 1, 3, bias=False),\n            nn.InstanceNorm2d(64),\n            nn.ReLU(True),\n        ]\n        # Downsampling\n        in_features = 64\n        out_features = in_features * 2\n        for _ in range(2):\n            model += [\n                nn.Conv2d(in_features, out_features, 3, 2, 1, bias=False),\n                nn.InstanceNorm2d(out_features),\n                nn.ReLU(True)\n            ]\n            in_features = out_features\n            out_features = in_features * 2\n        # ResNet blocks\n        for _ in range(n_blocks):\n            model += [ResnetBlock(in_features)]\n        # Upsampling\n        out_features = in_features // 2\n        for _ in range(2):\n            model += [\n                nn.ConvTranspose2d(in_features, out_features, 3, 2, 1, 1, bias=False),\n                nn.InstanceNorm2d(out_features),\n                nn.ReLU(True)\n            ]\n            in_features = out_features\n            out_features = in_features // 2\n        # Output layer\n        model += [\n            nn.Conv2d(64, output_nc, 7, 1, 3),\n            nn.Tanh()\n        ]\n        self.model = nn.Sequential(*model)\n\n    def forward(self, x):\n        return self.model(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:09.236371Z","iopub.execute_input":"2025-07-08T01:47:09.236713Z","iopub.status.idle":"2025-07-08T01:47:09.244936Z","shell.execute_reply.started":"2025-07-08T01:47:09.236696Z","shell.execute_reply":"2025-07-08T01:47:09.244074Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class PatchDiscriminator(nn.Module):\n    def __init__(self, input_nc):\n        super().__init__()\n        model = [\n            nn.Conv2d(input_nc, 64, 4, 2, 1),\n            nn.LeakyReLU(0.2, True),\n            nn.Conv2d(64, 128, 4, 2, 1, bias=False),\n            nn.InstanceNorm2d(128),\n            nn.LeakyReLU(0.2, True),\n            nn.Conv2d(128, 256, 4, 2, 1, bias=False),\n            nn.InstanceNorm2d(256),\n            nn.LeakyReLU(0.2, True),\n            nn.Conv2d(256, 512, 4, 1, 1, bias=False),\n            nn.InstanceNorm2d(512),\n            nn.LeakyReLU(0.2, True),\n            nn.Conv2d(512, 1, 4, 1, 1)\n        ]\n        self.model = nn.Sequential(*model)\n\n    def forward(self, x):\n        return self.model(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:09.246992Z","iopub.execute_input":"2025-07-08T01:47:09.247235Z","iopub.status.idle":"2025-07-08T01:47:09.268256Z","shell.execute_reply.started":"2025-07-08T01:47:09.247218Z","shell.execute_reply":"2025-07-08T01:47:09.267678Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def gan_loss(pred, target_is_real):\n    target = torch.ones_like(pred) if target_is_real else torch.zeros_like(pred)\n    return FUNC.mse_loss(pred, target)\n\ndef cycle_loss(recon, real):\n    return FUNC.l1_loss(recon, real)\n\ndef identity_loss(idt, real):\n    return FUNC.l1_loss(idt, real)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:09.268848Z","iopub.execute_input":"2025-07-08T01:47:09.269043Z","iopub.status.idle":"2025-07-08T01:47:09.286853Z","shell.execute_reply.started":"2025-07-08T01:47:09.269027Z","shell.execute_reply":"2025-07-08T01:47:09.286054Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3 canals (RGB)\nG = ResnetGenerator(3, 3).to(device)  # X->Y\nF = ResnetGenerator(3, 3).to(device)  # Y->X\nD_X = PatchDiscriminator(3).to(device)\nD_Y = PatchDiscriminator(3).to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:09.287514Z","iopub.execute_input":"2025-07-08T01:47:09.287724Z","iopub.status.idle":"2025-07-08T01:47:09.733846Z","shell.execute_reply.started":"2025-07-08T01:47:09.287701Z","shell.execute_reply":"2025-07-08T01:47:09.733217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MonetDataset(Dataset):\n    def __init__(self, root_photo, root_monet, transform, max_photos=None, max_monets=None):\n        self.photo_files = sorted(os.listdir(root_photo))\n        self.monet_files = sorted(os.listdir(root_monet))\n        if max_photos is not None:\n            self.photo_files = self.photo_files[:max_photos]\n        if max_monets is not None:\n            self.monet_files = self.monet_files[:max_monets]\n        self.photo_root = root_photo\n        self.monet_root = root_monet\n        self.transform = transform\n\n    def __len__(self):\n        return max(len(self.photo_files), len(self.monet_files))\n\n    def __getitem__(self, idx):\n        photo_path = os.path.join(self.photo_root, self.photo_files[idx % len(self.photo_files)])\n        monet_path = os.path.join(self.monet_root, self.monet_files[idx % len(self.monet_files)])\n        photo = self.transform(Image.open(photo_path).convert('RGB'))\n        monet = self.transform(Image.open(monet_path).convert('RGB'))\n        return photo, monet\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:09.734524Z","iopub.execute_input":"2025-07-08T01:47:09.734754Z","iopub.status.idle":"2025-07-08T01:47:09.740921Z","shell.execute_reply.started":"2025-07-08T01:47:09.734738Z","shell.execute_reply":"2025-07-08T01:47:09.740035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.5]*3, [0.5]*3)\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:09.741703Z","iopub.execute_input":"2025-07-08T01:47:09.741892Z","iopub.status.idle":"2025-07-08T01:47:09.758164Z","shell.execute_reply.started":"2025-07-08T01:47:09.741877Z","shell.execute_reply":"2025-07-08T01:47:09.757391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = MonetDataset(\n    root_photo=\"/kaggle/input/gan-getting-started/photo_jpg\",\n    root_monet=\"/kaggle/input/gan-getting-started/monet_jpg\",\n    transform=transform,\n    max_photos=300,   \n    max_monets=300 \n)\nloader = DataLoader(dataset, batch_size=8, shuffle=True, num_workers=4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:09.758829Z","iopub.execute_input":"2025-07-08T01:47:09.759066Z","iopub.status.idle":"2025-07-08T01:47:09.843462Z","shell.execute_reply.started":"2025-07-08T01:47:09.759041Z","shell.execute_reply":"2025-07-08T01:47:09.842942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torch.optim import Adam\nfrom tqdm import tqdm\nimport os\n\ntorch.cuda.empty_cache()  # clear GPU cache\n\n# (1) Training parameters\nlr = 2e-4\nbeta1 = 0.5\nnum_epochs = 100\nsave_every = 10  # save checkpoints every 10 epochs\nout_dir = \"/kaggle/working/checkpoints\"\nos.makedirs(out_dir, exist_ok=True)\n\n# (2) Optimizers\noptimizer_G = Adam(list(G.parameters()) + list(F.parameters()), lr=lr, betas=(beta1, 0.999))\noptimizer_D_X = Adam(D_X.parameters(), lr=lr, betas=(beta1, 0.999))\noptimizer_D_Y = Adam(D_Y.parameters(), lr=lr, betas=(beta1, 0.999))\n\nlambda_cyc = 10\nlambda_id = 5\n\n# (3) Checkpoint saving\ndef save_checkpoint(G, F, D_X, D_Y, epoch, out_dir=out_dir):\n    torch.save(G.state_dict(), f\"{out_dir}/G_epoch{epoch}.pth\")\n    torch.save(F.state_dict(), f\"{out_dir}/F_epoch{epoch}.pth\")\n    torch.save(D_X.state_dict(), f\"{out_dir}/D_X_epoch{epoch}.pth\")\n    torch.save(D_Y.state_dict(), f\"{out_dir}/D_Y_epoch{epoch}.pth\")\n    print(f\"[✓] Saved at epoch {epoch}\")\n\n# (4) Loss logs\nfrom collections import defaultdict\nlosses = defaultdict(list)\n\n# (5) Training loop\nfor epoch in range(1, num_epochs + 1):\n    G.train(); F.train(); D_X.train(); D_Y.train()\n    tq = tqdm(loader, desc=f\"Epoch {epoch}/{num_epochs}\")\n    for real_x, real_y in tq:\n        real_x, real_y = real_x.to(device), real_y.to(device)\n\n        # Generators\n        optimizer_G.zero_grad()\n        idt_y = G(real_y)\n        idt_x = F(real_x)\n        loss_idt_y = identity_loss(idt_y, real_y) * lambda_id\n        loss_idt_x = identity_loss(idt_x, real_x) * lambda_id\n        fake_y = G(real_x)\n        fake_x = F(real_y)\n        pred_fake_y = D_Y(fake_y)\n        pred_fake_x = D_X(fake_x)\n        loss_GAN_G = gan_loss(pred_fake_y, True)\n        loss_GAN_F = gan_loss(pred_fake_x, True)\n        rec_x = F(fake_y)\n        rec_y = G(fake_x)\n        loss_cycle_x = cycle_loss(rec_x, real_x) * lambda_cyc\n        loss_cycle_y = cycle_loss(rec_y, real_y) * lambda_cyc\n        loss_G = loss_GAN_G + loss_GAN_F + loss_cycle_x + loss_cycle_y + loss_idt_x + loss_idt_y\n        loss_G.backward()\n        optimizer_G.step()\n\n        # Discriminator D_Y\n        optimizer_D_Y.zero_grad()\n        pred_real_y = D_Y(real_y)\n        pred_fake_y = D_Y(fake_y.detach())\n        loss_D_Y = (gan_loss(pred_real_y, True) + gan_loss(pred_fake_y, False)) * 0.5\n        loss_D_Y.backward()\n        optimizer_D_Y.step()\n\n        # Discriminator D_X\n        optimizer_D_X.zero_grad()\n        pred_real_x = D_X(real_x)\n        pred_fake_x = D_X(fake_x.detach())\n        loss_D_X = (gan_loss(pred_real_x, True) + gan_loss(pred_fake_x, False)) * 0.5\n        loss_D_X.backward()\n        optimizer_D_X.step()\n\n        # Track losses\n        losses['G'].append(loss_G.item())\n        losses['D_X'].append(loss_D_X.item())\n        losses['D_Y'].append(loss_D_Y.item())\n        losses['loss_idt_x'].append(loss_idt_x.item())\n        losses['loss_idt_y'].append(loss_idt_y.item())\n        losses['loss_cycle_x'].append(loss_cycle_x.item())\n        losses['loss_cycle_y'].append(loss_cycle_y.item())\n\n        # Show losses in progress bar\n        tq.set_postfix({\n            'G': f'{loss_G.item():.3f}',\n            'D_X': f'{loss_D_X.item():.3f}',\n            'D_Y': f'{loss_D_Y.item():.3f}'\n        })\n\n    # Save checkpoints\n    if epoch % save_every == 0 or epoch == num_epochs:\n        save_checkpoint(G, F, D_X, D_Y, epoch, out_dir=out_dir)\n\n    # Clear GPU cache\n    torch.cuda.empty_cache()\n\nprint(\"Training finished!\")\n\n# Save losses for plotting later\nimport pickle\nwith open(f\"{out_dir}/losses.pkl\", \"wb\") as f:\n    pickle.dump(dict(losses), f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T01:47:09.845363Z","iopub.execute_input":"2025-07-08T01:47:09.845629Z","iopub.status.idle":"2025-07-08T03:52:53.339244Z","shell.execute_reply.started":"2025-07-08T01:47:09.845610Z","shell.execute_reply":"2025-07-08T03:52:53.338301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# MiFID\nfrom torchvision.models import inception_v3\nimport torch.nn as nn\n\ndef get_inception_features(images, model, batch_size=8):\n    features = []\n    model.eval()\n    with torch.no_grad():\n        for i in range(0, images.shape[0], batch_size):\n            batch = images[i:i+batch_size].to(device)\n            batch = FUNC.interpolate(batch, size=(299, 299), mode='bilinear', align_corners=False)\n            preds = model(batch)\n            if isinstance(preds, tuple):  # for older torchvision versions\n                preds = preds[0]\n            preds = preds.detach().cpu().numpy()\n            features.append(preds)\n    features = np.concatenate(features, axis=0)\n    return features\n\ndef calculate_fid(mu1, sigma1, mu2, sigma2, eps=1e-6):\n    diff = mu1 - mu2\n    covmean, _ = linalg.sqrtm(sigma1 @ sigma2, disp=False)\n    if np.iscomplexobj(covmean):\n        covmean = covmean.real\n    fid = diff.dot(diff) + np.trace(sigma1 + sigma2 - 2 * covmean)\n    return fid\n\ndef compute_mifid(genA, realA, genB, realB, batch_size=8):\n    # genA: G(X) — photos translated into Monet style\n    # realA: real Monet paintings\n    # genB: F(Y) — paintings translated into photo style\n    # realB: real photos\n\n    # load Inception only once\n    model = inception_v3(pretrained=False, transform_input=False).to(device)\n    model.load_state_dict(torch.load(\"/kaggle/input/inception-v3-google-0cc3c7bd-pth/inception_v3_google-0cc3c7bd.pth\"))\n    model.fc = nn.Identity()\n    model.eval()\n\n    # FID(G(X), Y)\n    feats1 = get_inception_features(genA, model, batch_size)\n    feats2 = get_inception_features(realA, model, batch_size)\n    mu1, sigma1 = feats1.mean(axis=0), np.cov(feats1, rowvar=False)\n    mu2, sigma2 = feats2.mean(axis=0), np.cov(feats2, rowvar=False)\n    fid_A = calculate_fid(mu1, sigma1, mu2, sigma2)\n\n    # FID(F(Y), X)\n    feats1 = get_inception_features(genB, model, batch_size)\n    feats2 = get_inception_features(realB, model, batch_size)\n    mu1, sigma1 = feats1.mean(axis=0), np.cov(feats1, rowvar=False)\n    mu2, sigma2 = feats2.mean(axis=0), np.cov(feats2, rowvar=False)\n    fid_B = calculate_fid(mu1, sigma1, mu2, sigma2)\n\n    # MiFID = sum of both directions\n    mifid = fid_A + fid_B\n    return mifid, fid_A, fid_B","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T03:52:53.340641Z","iopub.execute_input":"2025-07-08T03:52:53.340937Z","iopub.status.idle":"2025-07-08T03:52:53.350458Z","shell.execute_reply.started":"2025-07-08T03:52:53.340911Z","shell.execute_reply":"2025-07-08T03:52:53.349767Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"N = 300\nphotos, monets = [], []\nfor i in range(N):\n    photo, monet = dataset[i]\n    photos.append(photo)\n    monets.append(monet)\n\ntest_photos = torch.stack(photos)\ntest_monet = torch.stack(monets)\n\nwith torch.no_grad():\n    fake_monet = G(test_photos.to(device)).cpu()   # G(X): photo → Monet\n    rec_photos = F(test_monet.to(device)).cpu()    # F(Y): Monet → photo\n\n# MiFID evaluation\nmifid, fid_xy, fid_yx = compute_mifid(\n    fake_monet, test_monet,      # G(X), Y\n    rec_photos, test_photos,     # F(Y), X\n    batch_size=32\n)\nprint(f\"MiFID: {mifid:.4f} (FID_XY={fid_xy:.4f}, FID_YX={fid_yx:.4f})\")\n\n# Visualization of the first N samples\nN_vis = 8\n\ndef denorm(t):\n    t = (t * 0.5) + 0.5\n    return t.clamp(0, 1).permute(1, 2, 0).numpy()\n\nimport matplotlib.pyplot as plt\nfig, axs = plt.subplots(N_vis, 4, figsize=(12, 3 * N_vis))\nif N_vis == 1:\n    axs = axs.reshape(1, 4)\n\nfor i in range(N_vis):\n    axs[i, 0].imshow(denorm(test_photos[i]))\n    axs[i, 0].set_title('Photo')\n    axs[i, 0].axis('off')\n\n    axs[i, 1].imshow(denorm(fake_monet[i]))\n    axs[i, 1].set_title('Photo→Monet')\n    axs[i, 1].axis('off')\n\n    axs[i, 2].imshow(denorm(test_monet[i]))\n    axs[i, 2].set_title('Monet')\n    axs[i, 2].axis('off')\n\n    axs[i, 3].imshow(denorm(rec_photos[i]))\n    axs[i, 3].set_title('Monet→Photo')\n    axs[i, 3].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T03:52:53.351189Z","iopub.execute_input":"2025-07-08T03:52:53.351386Z","iopub.status.idle":"2025-07-08T03:53:24.757526Z","shell.execute_reply.started":"2025-07-08T03:52:53.351371Z","shell.execute_reply":"2025-07-08T03:53:24.756322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = MonetDataset(\n    root_photo=\"/kaggle/input/gan-getting-started/photo_jpg\",\n    root_monet=\"/kaggle/input/gan-getting-started/monet_jpg\",\n    transform=transform,\n    max_photos=7000,   \n    max_monets=300 \n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T04:02:54.850714Z","iopub.execute_input":"2025-07-08T04:02:54.851337Z","iopub.status.idle":"2025-07-08T04:02:54.862190Z","shell.execute_reply.started":"2025-07-08T04:02:54.851312Z","shell.execute_reply":"2025-07-08T04:02:54.861515Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Create and load the generator\nG = ResnetGenerator(3, 3).to(device)\nstate = torch.load(\"/kaggle/working/checkpoints/G_epoch100.pth\", map_location=device)\ntry:\n    G.load_state_dict(state)\nexcept RuntimeError:\n    from collections import OrderedDict\n    new_state = OrderedDict()\n    for k, v in state.items():\n        name = k.replace('module.', '')\n        new_state[name] = v\n    G.load_state_dict(new_state)\nG.eval()\n\n# 2. Generation\nmax_gen = 7000\nphoto_files = dataset.photo_files[:max_gen]\noutput_dir = \"/kaggle/working/gen_images\"\nos.makedirs(output_dir, exist_ok=True)\n\nphoto_files = dataset.photo_files[:max_gen]\nfor i, photo_name in enumerate(tqdm(photo_files)):\n    img_path = os.path.join(dataset.photo_root, photo_name)\n    img = Image.open(img_path).convert('RGB')\n    tensor = transform(img).unsqueeze(0).to(device)\n    with torch.no_grad():\n        fake_monet = G(tensor).cpu().squeeze(0)\n    img_np = (fake_monet * 0.5 + 0.5).clamp(0, 1).numpy().transpose(1, 2, 0)\n    img_np = (img_np * 255).astype(np.uint8)\n    Image.fromarray(img_np).save(f\"{output_dir}/{i:05d}.jpg\", \"JPEG\")\n\n# 3. Archive results\nimport shutil\nshutil.make_archive(\"/kaggle/working/images\", 'zip', output_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-08T04:02:55.843096Z","iopub.execute_input":"2025-07-08T04:02:55.843741Z","iopub.status.idle":"2025-07-08T04:05:38.111634Z","shell.execute_reply.started":"2025-07-08T04:02:55.843722Z","shell.execute_reply":"2025-07-08T04:05:38.110719Z"}},"outputs":[],"execution_count":null}]}