{"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'''\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\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\n'''","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:28:26.576745Z","iopub.execute_input":"2025-07-27T05:28:26.577289Z","iopub.status.idle":"2025-07-27T05:28:26.583107Z","shell.execute_reply.started":"2025-07-27T05:28:26.577259Z","shell.execute_reply":"2025-07-27T05:28:26.582299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\n'''\nDiscrinimator architecture for cyclic GAN. \n'''\nclass Discriminator(nn.Module):\n    def __init__(self, in_channels=3):\n        super(Discriminator, self).__init__()\n\n        \n        def conv_block(in_features, out_features, normalize=True):\n            layers = [nn.Conv2d(in_features, out_features, kernel_size=4, stride=2, padding=1)]\n            if normalize:\n                '''\n                Use instance norm over batch norm for generating images. \n                '''\n                layers.append(nn.InstanceNorm2d(out_features))\n            layers.append(nn.LeakyReLU(0.2, inplace=True))\n            return layers\n\n        # 70x70 PatchGAN\n        self.model = nn.Sequential(\n            *conv_block(in_channels, 64, normalize=False),  # 256 → 128\n            *conv_block(64, 128),                           # 128 → 64\n            *conv_block(128, 256),                          # 64 → 32\n            *conv_block(256, 512),                          # 32 → 16\n            nn.Conv2d(512, 1, kernel_size=4, padding=1)     # 16 → ~14 (patch map)\n        )\n\n    def forward(self, x):\n        return self.model(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:28:28.238987Z","iopub.execute_input":"2025-07-27T05:28:28.239554Z","iopub.status.idle":"2025-07-27T05:28:28.245058Z","shell.execute_reply.started":"2025-07-27T05:28:28.239528Z","shell.execute_reply":"2025-07-27T05:28:28.244347Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom PIL import Image\nfrom torch.utils.data import Dataset\nimport torchvision.transforms as transforms\nfrom torch.utils.data import DataLoader\n\nclass MonetDataset(Dataset):\n    def __init__(self, root_dir, transform=None):\n        \"\"\"\n        Args:\n            root_dir (str): Root folder with trainA, trainB, testA, testB subfolders\n            transform (callable, optional): Optional transform to be applied on images.\n            mode (str): One of ['train', 'test']\n        \"\"\"\n        self.root_dir = root_dir\n        self.transform = transform\n\n        self.dir_A = os.path.join(root_dir, f\"monet_jpg\")\n        self.dir_B = os.path.join(root_dir, f\"photo_jpg\")\n\n        self.files_A = sorted(os.listdir(self.dir_A))\n        self.files_B = sorted(os.listdir(self.dir_B))\n\n    def __len__(self):\n        return max(len(self.files_A), len(self.files_B))\n\n    def __getitem__(self, idx):\n        img_A_path = os.path.join(self.dir_A, self.files_A[idx % len(self.files_A)])\n        img_B_path = os.path.join(self.dir_B, self.files_B[idx % len(self.files_B)])\n\n        img_A = Image.open(img_A_path).convert(\"RGB\")\n        img_B = Image.open(img_B_path).convert(\"RGB\")\n\n        if self.transform:\n            img_A = self.transform(img_A)\n            img_B = self.transform(img_B)\n\n        return {\"A\": img_A, \"B\": img_B}\n\n'''\nWrapper class around MonetDataset to load data with custom attributes to Pytorch's Data Loader class.\n'''\nclass MonetDataLoader():\n    '''\n    Constructor to initialize the attributes.\n    '''\n    def __init__(self, folder_path, transform=None, batch_size = 32, shuffle = True):\n        self.folder_path = folder_path\n        self.transform = transform\n        self.batch_size = batch_size\n        self.shuffle = shuffle\n    \n    '''\n    Function to get the dataloader.\n    '''\n    def get_data_loader(self):\n        return DataLoader(MonetDataset(root_dir=self.folder_path, transform=self.transform),\n                          batch_size=self.batch_size,\n                          shuffle=self.shuffle,\n                          num_workers = 4)\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:28:32.233876Z","iopub.execute_input":"2025-07-27T05:28:32.234565Z","iopub.status.idle":"2025-07-27T05:28:32.246859Z","shell.execute_reply.started":"2025-07-27T05:28:32.234529Z","shell.execute_reply":"2025-07-27T05:28:32.245968Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\n'''\nA residual block is a building block used in ResNet which helps neural networks learn. \nBasically it learns the change it wants to apply in the input and then apply the change back in. \nThis helps in gradient flow in backpropagation and reuse of low-level features.\n'''\nclass ResidualBlock(nn.Module):\n    def __init__(self, features):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.ReflectionPad2d(1), # mirrors the edge pixels. \n            nn.Conv2d(features, features, kernel_size=3),\n            nn.InstanceNorm2d(features), # while batch norm normalizes images with mean and std deviation ACROSS ENTIRE BATCH,\n            # instance norm 2d normalizes image across per image, per channel.  \n            nn.ReLU(True),\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(features, features, kernel_size=3),\n            nn.InstanceNorm2d(features),\n        )\n\n    def forward(self, x):\n        return x + self.block(x)\n\nclass Generator(nn.Module):\n    def __init__(self, in_channels=3, out_channels=3, n_residual_blocks=9):\n        super().__init__()\n\n        model = [\n            nn.ReflectionPad2d(3),\n            nn.Conv2d(in_channels, 64, kernel_size=7),\n            nn.InstanceNorm2d(64),\n            nn.ReLU(True),\n        ]\n\n        # Downsampling - we shrink the image to smaller size, but increase the feature channels -\n        # capturing information about more features. \n        in_features = 64\n        for _ in range(2):\n            model += [\n                nn.Conv2d(in_features, in_features*2, 3, stride=2, padding=1),\n                nn.InstanceNorm2d(in_features*2),\n                nn.ReLU(True),\n            ]\n            in_features *= 2\n\n        # Residual blocks\n        for _ in range(n_residual_blocks):\n            model += [ResidualBlock(in_features)]\n\n        # Upsampling\n        for _ in range(2):\n            model += [\n                nn.ConvTranspose2d(in_features, in_features//2, 3, stride=2, padding=1, output_padding=1),\n                nn.InstanceNorm2d(in_features//2),\n                nn.ReLU(True),\n            ]\n            in_features //= 2\n\n        # Output layer\n        model += [\n            nn.ReflectionPad2d(3),\n            nn.Conv2d(64, out_channels, kernel_size=7),\n            nn.Tanh(),\n        ]\n\n        self.model = nn.Sequential(*model)\n\n    def forward(self, x):\n        return self.model(x)\n\nclass UNetDown(nn.Module):\n    def __init__(self, in_channels, out_channels, normalize=True, dropout=0.0):\n        super().__init__()\n        layers = [nn.Conv2d(in_channels, out_channels, 4, 2, 1, bias=False)]\n        if normalize:\n            layers.append(nn.InstanceNorm2d(out_channels))\n        layers.append(nn.LeakyReLU(0.2))\n        if dropout:\n            layers.append(nn.Dropout(dropout))\n        self.model = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.model(x)\n\n\nclass UNetUp(nn.Module):\n    def __init__(self, in_channels, out_channels, dropout=0.0):\n        super().__init__()\n        layers = [\n            nn.ConvTranspose2d(in_channels, out_channels, 4, 2, 1, bias=False),\n            nn.InstanceNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n        ]\n        if dropout:\n            layers.append(nn.Dropout(dropout))\n        self.model = nn.Sequential(*layers)\n\n    def forward(self, x, skip_input):\n        x = self.model(x)\n        return torch.cat((x, skip_input), 1)\n\nclass UNetGenerator(nn.Module):\n    def __init__(self, in_channels=3, out_channels=3):\n        super().__init__()\n        # Downsampling layers\n        self.down1 = UNetDown(in_channels, 64, normalize=False)\n        self.down2 = UNetDown(64, 128)\n        self.down3 = UNetDown(128, 256)\n        self.down4 = UNetDown(256, 512, dropout=0.5)\n        self.down5 = UNetDown(512, 512, dropout=0.5)\n        self.down6 = UNetDown(512, 512, dropout=0.5)\n        self.down7 = UNetDown(512, 512, dropout=0.5)\n        self.down8 = UNetDown(512, 512, normalize=False, dropout=0.5)\n\n        # Upsampling layers\n        self.up1 = UNetUp(512, 512, dropout=0.5)\n        self.up2 = UNetUp(1024, 512, dropout=0.5)\n        self.up3 = UNetUp(1024, 512, dropout=0.5)\n        self.up4 = UNetUp(1024, 512, dropout=0.5)\n        self.up5 = UNetUp(1024, 256)\n        self.up6 = UNetUp(512, 128)\n        self.up7 = UNetUp(256, 64)\n\n        self.final = nn.Sequential(\n            nn.ConvTranspose2d(128, out_channels, 4, 2, 1),\n            nn.Tanh()\n        )\n\n    def forward(self, x):\n        d1 = self.down1(x)\n        d2 = self.down2(d1)\n        d3 = self.down3(d2)\n        d4 = self.down4(d3)\n        d5 = self.down5(d4)\n        d6 = self.down6(d5)\n        d7 = self.down7(d6)\n        d8 = self.down8(d7)\n        u1 = self.up1(d8, d7)\n        u2 = self.up2(u1, d6)\n        u3 = self.up3(u2, d5)\n        u4 = self.up4(u3, d4)\n        u5 = self.up5(u4, d3)\n        u6 = self.up6(u5, d2)\n        u7 = self.up7(u6, d1)\n        return self.final(u7)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:28:13.301899Z","iopub.execute_input":"2025-07-27T05:28:13.302537Z","iopub.status.idle":"2025-07-27T05:28:13.320879Z","shell.execute_reply.started":"2025-07-27T05:28:13.302506Z","shell.execute_reply":"2025-07-27T05:28:13.320258Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\nimport torch\nimport matplotlib.pyplot as plt\n\n'''\nqueue of previously generated fake images. Instead of always feeding new \nfake images to the discriminator, we sometimes reuse older ones.\nThis helps the discriminator: avoid overfitting to the generator's latest outputs and\nsee a more diverse set of \"fake\" examples\n'''\nclass ReplayBuffer():\n    def __init__(self, max_size=50):\n        \"\"\"\n        Stores previously generated images to stabilize GAN training.\n        \"\"\"\n        assert max_size > 0, \"Buffer size must be > 0\"\n        self.max_size = max_size\n        self.data = []\n\n    def push_and_pop(self, new_data):\n        \"\"\"\n        new_data: Tensor of fake images from generator [B, C, H, W]\n        Returns: Tensor of images for discriminator\n        \"\"\"\n        result = []\n        for image in new_data:\n            image = torch.unsqueeze(image.data, 0)  # Add batch dim\n            if len(self.data) < self.max_size:\n                # Fill buffer\n                self.data.append(image)\n                result.append(image)\n            else:\n                if random.random() > 0.5:\n                    # Use a stored image instead\n                    idx = random.randint(0, self.max_size - 1)\n                    old_image = self.data[idx].clone()\n                    self.data[idx] = image  # Replace with new one\n                    result.append(old_image)\n                else:\n                    # Use new image\n                    result.append(image)\n        return torch.cat(result, dim=0)  # Return batch\n\ndef plot_losses(loss_log, title=\"Training Losses\", xlabel=\"Training Steps\", ylabel=\"Loss\", save_path=None):\n    \"\"\"\n    Plots any number of named loss curves over time.\n\n    Parameters:\n    - loss_log: Dictionary with keys as loss names and values as lists of loss values.\n    - title: Title of the plot.\n    - xlabel: Label for x-axis.\n    - ylabel: Label for y-axis.\n    - save_path: If specified, saves the plot to this path.\n    \"\"\"\n    plt.figure(figsize=(12, 6))\n\n    for key, values in loss_log.items():\n        plt.plot(values, label=key)\n\n    plt.xlabel(xlabel)\n    plt.ylabel(ylabel)\n    plt.title(title)\n    plt.legend()\n    plt.grid(True)\n\n    if save_path:\n        plt.savefig(save_path)\n    else:\n        plt.show()\n\nimport torch\nfrom torchvision.utils import save_image\nimport os\n\ndef save_sample_images(tensor_images, epoch, output_dir=\"samples\", num_images=64, filename_prefix=\"sample\"):\n    \"\"\"\n    Saves a grid of generated sample images.\n\n    Args:\n        tensor_images (torch.Tensor): A batch of images, shape (B, C, H, W), range [-1, 1] or [0, 1].\n        epoch (int): The current epoch number (used for filename).\n        output_dir (str): Directory where to save images.\n        num_images (int): Number of images from the batch to save.\n        filename_prefix (str): Prefix for the filename.\n    \"\"\"\n    os.makedirs(output_dir, exist_ok=True)\n\n    # Select only first `num_images`\n    tensor_images = tensor_images[:num_images]\n\n    # Rescale from [-1, 1] to [0, 1] if needed\n    if tensor_images.min() < 0:\n        tensor_images = (tensor_images + 1) / 2\n\n    save_path = os.path.join(output_dir, f\"{filename_prefix}_epoch{epoch}.png\")\n    save_image(tensor_images, save_path, nrow=8, normalize=False)\n    print(f\"✅ Saved sample images to: {save_path}\")\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:28:36.898115Z","iopub.execute_input":"2025-07-27T05:28:36.898415Z","iopub.status.idle":"2025-07-27T05:28:36.907940Z","shell.execute_reply.started":"2025-07-27T05:28:36.898393Z","shell.execute_reply":"2025-07-27T05:28:36.907233Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport os\nimport sys\n# Add the parent directory to sys.path\n#sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), \"..\")))\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader\nimport itertools\nfrom torchvision import transforms\n\nimport glob\nimport re\n\ndef load_latest_checkpoint(model, optimizer, name, checkpoint_dir=\"checkpoints/CYCLEGAN\"):\n    checkpoint_files = glob.glob(f\"{checkpoint_dir}/{name}_epoch_*.pth\")\n    if not checkpoint_files:\n        print(f\"No checkpoint found for {name}. Starting from scratch.\")\n        return model, optimizer, 1  # start at epoch 1\n\n    # Extract epoch numbers\n    def extract_epoch(path):\n        match = re.search(r\"epoch_(\\d+)\", path)\n        return int(match.group(1)) if match else -1\n\n    latest_ckpt = max(checkpoint_files, key=extract_epoch)\n    epoch = extract_epoch(latest_ckpt)\n\n    checkpoint = torch.load(latest_ckpt, map_location=DEVICE)\n    model.load_state_dict(checkpoint[\"model_state\"])\n    optimizer.load_state_dict(checkpoint[\"optimizer_state\"])\n\n    print(f\"✔ Loaded {name} from epoch {epoch}\")\n    return model, optimizer, epoch + 1\n\n\n# === Config ===\nEPOCHS = 50\nBATCH_SIZE = 1\nLR = 0.0002\nIMAGE_SIZE = 256\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n# === Models ===\nG_AB = UNetGenerator().to(DEVICE)  # Photo → Monet\nG_BA = UNetGenerator().to(DEVICE)  # Monet → Photo\nD_A = Discriminator().to(DEVICE)  # Real vs fake photo\nD_B = Discriminator().to(DEVICE)  # Real vs fake Monet\n\n# === Losses ===\ncriterion_GAN = nn.MSELoss()\ncriterion_cycle = nn.L1Loss()\ncriterion_identity = nn.L1Loss()\n\n# === Optimizers ===\noptimizer_G = optim.Adam(itertools.chain(G_AB.parameters(), G_BA.parameters()), lr=LR, betas=(0.5, 0.999))\noptimizer_D_A = optim.Adam(D_A.parameters(), lr=LR, betas=(0.5, 0.999))\noptimizer_D_B = optim.Adam(D_B.parameters(), lr=LR, betas=(0.5, 0.999))\n\nG_AB, optimizer_G, start_epoch = load_latest_checkpoint(G_AB, optimizer_G, \"G_AB\")\nG_BA, optimizer_G, _ = load_latest_checkpoint(G_BA, optimizer_G, \"G_BA\")\nD_A, optimizer_D_A, _ = load_latest_checkpoint(D_A, optimizer_D_A, \"D_A\")\nD_B, optimizer_D_B, _ = load_latest_checkpoint(D_B, optimizer_D_B, \"D_B\")\n\n# =========================================\n# Transforms and Dataloader\n# =========================================\ntransform = transforms.Compose([\n    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)), # ensure all images are of same size as model expects a fixed size input.\n    transforms.ToTensor(), # converts from PIL.Image to torch.tensor\n    transforms.Normalize([0.5], [0.5]) # normalize the images values to be between [-1.0, + 1.0]\n])\n\n# === Data ===\ndataloader = MonetDataLoader(folder_path=\"/kaggle/input/gan-getting-started\", transform=transform, batch_size=BATCH_SIZE).get_data_loader()\n\n# === Buffers for stable training ===\nfake_A_buffer = ReplayBuffer()\nfake_B_buffer = ReplayBuffer()\n\n# === Training Loop ===\nfor epoch in range(start_epoch, EPOCHS+1):\n    \n    for i, batch in enumerate(dataloader):\n        real_A = batch['A'].to(DEVICE)  # Real photo\n        real_B = batch['B'].to(DEVICE)  # Real Monet\n\n        # === Train Generators ===\n        optimizer_G.zero_grad()\n\n        # Identity loss: G_AB(B) ≈ B, G_BA(A) ≈ A\n        idt_A = G_BA(real_A)\n        idt_B = G_AB(real_B)\n        loss_identity = criterion_identity(idt_A, real_A) + criterion_identity(idt_B, real_B)\n\n        # GAN loss: D_B(G_AB(A)) should think it's real Monet\n        fake_B = G_AB(real_A)\n        pred_fake_B = D_B(fake_B)\n        loss_GAN_AB = criterion_GAN(pred_fake_B, torch.ones_like(pred_fake_B))\n\n        # GAN loss: D_A(G_BA(B)) should think it's real photo\n        fake_A = G_BA(real_B)\n        pred_fake_A = D_A(fake_A)\n        loss_GAN_BA = criterion_GAN(pred_fake_A, torch.ones_like(pred_fake_A))\n\n        # Cycle-consistency loss: G_BA(G_AB(A)) ≈ A\n        rec_A = G_BA(fake_B)\n        rec_B = G_AB(fake_A)\n        loss_cycle = criterion_cycle(rec_A, real_A) + criterion_cycle(rec_B, real_B)\n\n        # Total generator loss\n        lambda_cycle = 10.0\n        lambda_idt = 5.0\n        loss_G = loss_GAN_AB + loss_GAN_BA + lambda_cycle * loss_cycle + lambda_idt * loss_identity\n        loss_G.backward()\n        optimizer_G.step()\n\n        # === Train Discriminator A (real photo vs fake photo) ===\n        optimizer_D_A.zero_grad()\n        loss_real_A = criterion_GAN(D_A(real_A), torch.ones_like(D_A(real_A)))\n        fake_A_detached = fake_A_buffer.push_and_pop(fake_A)\n        loss_fake_A = criterion_GAN(D_A(fake_A_detached.detach()), torch.zeros_like(D_A(fake_A_detached)))\n        loss_D_A = 0.5 * (loss_real_A + loss_fake_A)\n        loss_D_A.backward()\n        optimizer_D_A.step()\n\n        # === Train Discriminator B (real Monet vs fake Monet) ===\n        optimizer_D_B.zero_grad()\n        loss_real_B = criterion_GAN(D_B(real_B), torch.ones_like(D_B(real_B)))\n        fake_B_detached = fake_B_buffer.push_and_pop(fake_B)\n        loss_fake_B = criterion_GAN(D_B(fake_B_detached.detach()), torch.zeros_like(D_B(fake_B_detached)))\n        loss_D_B = 0.5 * (loss_real_B + loss_fake_B)\n        loss_D_B.backward()\n        optimizer_D_B.step()\n\n        # === Logging ===\n        if i % 300 == 0 and i > 0:\n            try:\n                print(f\"[Epoch {epoch}/{EPOCHS}] [Batch {i}/{len(dataloader)}] \"\n                      f\"[D_A: {loss_D_A.item():.2f}] [D_B: {loss_D_B.item():.2f}] \"\n                      f\"[G: {loss_G.item():.2f}] [Cycle: {loss_cycle.item():.2f}] [ID: {loss_identity.item():.2f}]\")\n            except:\n                print(\"Checkpointing failed\");\n            break;\n\nloss_log = {'G': [], 'D_A': [], 'D_B': [], 'cycle': [], 'identity': []}\n\n# during training:\nloss_log['G'].append(loss_G.item())\nloss_log['D_A'].append(loss_D_A .item())\nloss_log['D_B'].append(loss_D_B.item())\nloss_log['cycle'].append(loss_cycle.item())\nloss_log['identity'].append(loss_identity.item())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:28:42.035384Z","iopub.execute_input":"2025-07-27T05:28:42.035872Z","iopub.status.idle":"2025-07-27T05:29:44.895442Z","shell.execute_reply.started":"2025-07-27T05:28:42.035845Z","shell.execute_reply":"2025-07-27T05:29:44.894562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torchvision.transforms.v2 as transforms\n\nphoto_dir = \"/kaggle/input/gan-getting-started/photo_jpg\"\nphoto_paths = os.listdir(photo_dir)\n\ntfm = transforms.Compose([\n    transforms.ToImage(),\n    transforms.ToDtype(torch.float32, scale=True),\n    transforms.Normalize([0.5], [0.5])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:32:50.734658Z","iopub.execute_input":"2025-07-27T05:32:50.734958Z","iopub.status.idle":"2025-07-27T05:32:50.868529Z","shell.execute_reply.started":"2025-07-27T05:32:50.734936Z","shell.execute_reply":"2025-07-27T05:32:50.867920Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nos.makedirs(\"/kaggle/images/\", exist_ok=True)\nfor i, path in enumerate(photo_paths):\n    photo_path = os.path.join(photo_dir, path)\n    photo_img = Image.open(photo_path)\n    photo_tsr = tfm(photo_img).unsqueeze(0).to(DEVICE)\n\n    G_AB.eval()\n    with torch.no_grad(): op = G_AB(photo_tsr).squeeze(0).cpu().detach()\n\n    op_unnormalized = op * 0.5 + 0.5\n    op_image = transforms.ToPILImage()(op_unnormalized).convert(\"RGB\")\n    op_image.save(\"/kaggle/images/\" + str(i+1) + \".jpg\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:42:29.726989Z","iopub.execute_input":"2025-07-27T05:42:29.727387Z","iopub.status.idle":"2025-07-27T05:42:34.454241Z","shell.execute_reply.started":"2025-07-27T05:42:29.727360Z","shell.execute_reply":"2025-07-27T05:42:34.453250Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\nshutil.make_archive(\"/kaggle/working/images\", \"zip\", \"/kaggle/images\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T05:48:30.952858Z","iopub.execute_input":"2025-07-27T05:48:30.953142Z","iopub.status.idle":"2025-07-27T05:48:30.965231Z","shell.execute_reply.started":"2025-07-27T05:48:30.953120Z","shell.execute_reply":"2025-07-27T05:48:30.964259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}