{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"celltoolbar":"Raw Cell Format","kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":30587,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import transforms\nimport matplotlib.pyplot as plt\nfrom itertools import chain\nimport numpy as np\nimport shutil\nfrom torchvision.utils import make_grid","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:38.819068Z","iopub.execute_input":"2024-11-17T12:18:38.819389Z","iopub.status.idle":"2024-11-17T12:18:42.650084Z","shell.execute_reply.started":"2024-11-17T12:18:38.819362Z","shell.execute_reply":"2024-11-17T12:18:42.649215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 5\nMONET_DIR = \"/kaggle/input/gan-getting-started/monet_jpg\"\nPHOTO_DIR = \"/kaggle/input/gan-getting-started/photo_jpg\"\nLEARNING_RATE = 1e-4\nBETA_VALUES = (0.5, 0.996)\nTOTAL_EPOCHS = 135\nDECAY_START = 52\nSHOW_EVERY = 20","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:42.651874Z","iopub.execute_input":"2024-11-17T12:18:42.652312Z","iopub.status.idle":"2024-11-17T12:18:42.657735Z","shell.execute_reply.started":"2024-11-17T12:18:42.652282Z","shell.execute_reply":"2024-11-17T12:18:42.656646Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_transforms = transforms.Compose([\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:42.660885Z","iopub.execute_input":"2024-11-17T12:18:42.661169Z","iopub.status.idle":"2024-11-17T12:18:42.672211Z","shell.execute_reply.started":"2024-11-17T12:18:42.661145Z","shell.execute_reply":"2024-11-17T12:18:42.671441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class PaintingDataset(Dataset):\n    def __init__(self, domain_a_path, domain_b_path, is_test=False, img_transforms=None):\n        self.img_transforms = img_transforms\n\n        if is_test:\n            self.domain_a_images = [os.path.join(domain_a_path, fname) for fname in sorted(os.listdir(domain_a_path))[250:]]\n            self.domain_b_images = [os.path.join(domain_b_path, fname) for fname in sorted(os.listdir(domain_b_path))[250:301]]\n        else:\n            self.domain_a_images = [os.path.join(domain_a_path, fname) for fname in sorted(os.listdir(domain_a_path))[:250]]\n            self.domain_b_images = [os.path.join(domain_b_path, fname) for fname in sorted(os.listdir(domain_b_path))[:250]]\n\n    def __len__(self):\n        return len(self.domain_a_images)\n\n    def __getitem__(self, idx):\n        img_a = Image.open(self.domain_a_images[idx])\n        img_b = Image.open(self.domain_b_images[idx])\n\n        if self.img_transforms:\n            img_a = self.img_transforms(img_a)\n            img_b = self.img_transforms(img_b)\n\n        return img_a, img_b\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:42.673399Z","iopub.execute_input":"2024-11-17T12:18:42.673671Z","iopub.status.idle":"2024-11-17T12:18:42.683612Z","shell.execute_reply.started":"2024-11-17T12:18:42.673648Z","shell.execute_reply":"2024-11-17T12:18:42.682807Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data = PaintingDataset(MONET_DIR, PHOTO_DIR, is_test=False, img_transforms=image_transforms)\ntest_data = PaintingDataset(MONET_DIR, PHOTO_DIR, is_test=True, img_transforms=image_transforms)\n\ntrain_dataloader = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\ntest_dataloader = DataLoader(test_data, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:42.686509Z","iopub.execute_input":"2024-11-17T12:18:42.686769Z","iopub.status.idle":"2024-11-17T12:18:42.935782Z","shell.execute_reply.started":"2024-11-17T12:18:42.686737Z","shell.execute_reply":"2024-11-17T12:18:42.934977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class PatchDiscriminator(nn.Module):\n    def __init__(self, channels_in):\n        super().__init__()\n        self.layers = nn.Sequential(\n            nn.Conv2d(channels_in, 64, kernel_size=4, stride=2, padding=1),\n            nn.LeakyReLU(0.2, inplace=True),\n            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),\n            nn.InstanceNorm2d(128),\n            nn.LeakyReLU(0.2, inplace=True),\n            nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1),\n            nn.InstanceNorm2d(256),\n            nn.LeakyReLU(0.2, inplace=True),\n            nn.Conv2d(256, 512, kernel_size=4, stride=2, padding=1),\n            nn.InstanceNorm2d(512),\n            nn.LeakyReLU(0.2, inplace=True),\n            nn.ZeroPad2d((1, 0, 1, 0)),\n            nn.Conv2d(512, 1, kernel_size=4, padding=1)\n        )\n\n    def forward(self, x):\n        return self.layers(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:42.937117Z","iopub.execute_input":"2024-11-17T12:18:42.937476Z","iopub.status.idle":"2024-11-17T12:18:42.945970Z","shell.execute_reply.started":"2024-11-17T12:18:42.937436Z","shell.execute_reply":"2024-11-17T12:18:42.945068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ResidualBlock(nn.Module):\n    def __init__(self, num_channels):\n        super().__init__()\n        self.res_block = nn.Sequential(\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(num_channels, num_channels, kernel_size=3),\n            nn.InstanceNorm2d(num_channels),\n            nn.ReLU(inplace=True),\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(num_channels, num_channels, kernel_size=3),\n            nn.InstanceNorm2d(num_channels)\n        )\n\n    def forward(self, x):\n        return x + self.res_block(x)\n\nclass ResNetGenerator(nn.Module):\n    def __init__(self, input_channels, res_blocks=9):\n        super().__init__()\n\n        # Initial convolution\n        self.init_layer = nn.Sequential(\n            nn.ReflectionPad2d(input_channels),\n            nn.Conv2d(input_channels, 64, kernel_size=7),\n            nn.InstanceNorm2d(64),\n            nn.ReLU(inplace=True)\n        )\n\n        # Downsampling layers\n        self.down_layers = nn.Sequential(\n            nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),\n            nn.InstanceNorm2d(128),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1),\n            nn.InstanceNorm2d(256),\n            nn.ReLU(inplace=True)\n        )\n\n        # Residual blocks\n        self.residuals = nn.Sequential(*[ResidualBlock(256) for _ in range(res_blocks)])\n\n        # Upsampling layers\n        self.up_layers = nn.Sequential(\n            nn.Upsample(scale_factor=2),\n            nn.Conv2d(256, 128, kernel_size=3, padding=1),\n            nn.InstanceNorm2d(128),\n            nn.ReLU(inplace=True),\n            nn.Upsample(scale_factor=2),\n            nn.Conv2d(128, 64, kernel_size=3, padding=1),\n            nn.InstanceNorm2d(64),\n            nn.ReLU(inplace=True)\n        )\n\n        # Output layer\n        self.output_layer = nn.Sequential(\n            nn.ReflectionPad2d(input_channels),\n            nn.Conv2d(64, input_channels, kernel_size=7),\n            nn.Tanh()\n        )\n\n    def forward(self, x):\n        x = self.init_layer(x)\n        x = self.down_layers(x)\n        x = self.residuals(x)\n        x = self.up_layers(x)\n        return self.output_layer(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:42.947392Z","iopub.execute_input":"2024-11-17T12:18:42.947695Z","iopub.status.idle":"2024-11-17T12:18:42.961160Z","shell.execute_reply.started":"2024-11-17T12:18:42.947671Z","shell.execute_reply":"2024-11-17T12:18:42.960294Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gen_ab = ResNetGenerator(3, res_blocks=9)\ngen_ba = ResNetGenerator(3, res_blocks=9)\ndisc_a = PatchDiscriminator(3)\ndisc_b = PatchDiscriminator(3)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodels = [gen_ab, gen_ba, disc_a, disc_b]\nfor model in models:\n    model.to(device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:42.962226Z","iopub.execute_input":"2024-11-17T12:18:42.962504Z","iopub.status.idle":"2024-11-17T12:18:43.452518Z","shell.execute_reply.started":"2024-11-17T12:18:42.962481Z","shell.execute_reply":"2024-11-17T12:18:43.451722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"loss_gan = nn.MSELoss().to(device)\nloss_cycle = nn.L1Loss().to(device)\nloss_identity = nn.L1Loss().to(device)\n\nopt_g = torch.optim.Adam(chain(gen_ab.parameters(), gen_ba.parameters()), lr=LEARNING_RATE, betas=BETA_VALUES)\nopt_d_a = torch.optim.Adam(disc_a.parameters(), lr=LEARNING_RATE, betas=BETA_VALUES)\nopt_d_b = torch.optim.Adam(disc_b.parameters(), lr=LEARNING_RATE, betas=BETA_VALUES)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:43.453615Z","iopub.execute_input":"2024-11-17T12:18:43.453898Z","iopub.status.idle":"2024-11-17T12:18:43.461170Z","shell.execute_reply.started":"2024-11-17T12:18:43.453873Z","shell.execute_reply":"2024-11-17T12:18:43.460273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.optim.lr_scheduler import LambdaLR\n\n# Learning rate decay scheduler\ndef decay_scheduler(current_epoch, start_decay_epoch, total_epochs):\n    \"\"\"Decay learning rate linearly starting from `start_decay_epoch`.\"\"\"\n    if current_epoch < start_decay_epoch:\n        return 1.0\n    else:\n        decay_factor = 1 - (current_epoch - start_decay_epoch) / (total_epochs - start_decay_epoch)\n        return max(0, decay_factor)\n\n# Initialize schedulers for optimizers\nscheduler_gen = LambdaLR(opt_g, lr_lambda=lambda epoch: decay_scheduler(epoch, DECAY_START, TOTAL_EPOCHS))\nscheduler_disc_a = LambdaLR(opt_d_a, lr_lambda=lambda epoch: decay_scheduler(epoch, DECAY_START, TOTAL_EPOCHS))\nscheduler_disc_b = LambdaLR(opt_d_b, lr_lambda=lambda epoch: decay_scheduler(epoch, DECAY_START, TOTAL_EPOCHS))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:43.463002Z","iopub.execute_input":"2024-11-17T12:18:43.463366Z","iopub.status.idle":"2024-11-17T12:18:43.474463Z","shell.execute_reply.started":"2024-11-17T12:18:43.463333Z","shell.execute_reply":"2024-11-17T12:18:43.473517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize_results(real_monet, real_photo):\n    \"\"\"Visualizes real and generated images between two domains.\"\"\"\n    # Switch generators to evaluation mode\n    gen_ab.eval()\n    gen_ba.eval()\n\n    # Convert inputs to tensors\n    real_monet = real_monet.type(torch.FloatTensor).to(device)\n    real_photo = real_photo.type(torch.FloatTensor).to(device)\n\n    # Generate fake images\n    with torch.no_grad():\n        fake_photo = gen_ab(real_monet)\n        fake_monet = gen_ba(real_photo)\n\n    # Prepare image grids\n    num_images = real_monet.size(0)  # Number of samples in batch\n    real_monet_grid = make_grid(real_monet, nrow=num_images, normalize=True)\n    fake_photo_grid = make_grid(fake_photo, nrow=num_images, normalize=True)\n    real_photo_grid = make_grid(real_photo, nrow=num_images, normalize=True)\n    fake_monet_grid = make_grid(fake_monet, nrow=num_images, normalize=True)\n\n    # Plot images\n    fig, axes = plt.subplots(2, 2, figsize=(10, 10))\n\n    axes[0, 0].imshow(real_monet_grid.permute(1, 2, 0).cpu())\n    axes[0, 0].set_title(\"Real Images from Monet Domain (X)\")\n    axes[0, 0].axis('off')\n\n    axes[0, 1].imshow(fake_photo_grid.permute(1, 2, 0).cpu())\n    axes[0, 1].set_title(\"Generated Images to Photo Domain (Y)\")\n    axes[0, 1].axis('off')\n\n    axes[1, 0].imshow(real_photo_grid.permute(1, 2, 0).cpu())\n    axes[1, 0].set_title(\"Real Images from Photo Domain (Y)\")\n    axes[1, 0].axis('off')\n\n    axes[1, 1].imshow(fake_monet_grid.permute(1, 2, 0).cpu())\n    axes[1, 1].set_title(\"Generated Images to Monet Domain (X)\")\n    axes[1, 1].axis('off')\n\n    plt.tight_layout()\n    plt.show()\n\n# Example usage with test data\nreal_monet_batch, real_photo_batch = next(iter(test_dataloader))\nvisualize_results(real_monet_batch, real_photo_batch)\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:43.475686Z","iopub.execute_input":"2024-11-17T12:18:43.476393Z","iopub.status.idle":"2024-11-17T12:18:45.139947Z","shell.execute_reply.started":"2024-11-17T12:18:43.476358Z","shell.execute_reply":"2024-11-17T12:18:45.138940Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize another batch of test data\nreal_monet_batch, real_photo_batch = next(iter(test_dataloader))\nvisualize_results(real_monet_batch, real_photo_batch)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:45.141426Z","iopub.execute_input":"2024-11-17T12:18:45.141803Z","iopub.status.idle":"2024-11-17T12:18:46.054512Z","shell.execute_reply.started":"2024-11-17T12:18:45.141767Z","shell.execute_reply":"2024-11-17T12:18:46.053462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(1, TOTAL_EPOCHS + 1):\n    for i, (monet_batch, photo_batch) in enumerate(train_dataloader):\n        monet_batch, photo_batch = monet_batch.to(device), photo_batch.to(device)\n\n        # Train Generators (Gen_A2B and Gen_B2A)\n        opt_g.zero_grad()\n\n        # Identity loss\n        id_monet = gen_ba(monet_batch)\n        id_photo = gen_ab(photo_batch)\n        loss_id = loss_identity(id_monet, monet_batch) + loss_identity(id_photo, photo_batch)\n\n        # GAN loss\n        fake_photo = gen_ab(monet_batch)\n        fake_monet = gen_ba(photo_batch)\n        pred_fake_photo = disc_b(fake_photo)\n        pred_fake_monet = disc_a(fake_monet)\n        loss_gan_ab = loss_gan(pred_fake_photo, torch.ones_like(pred_fake_photo))\n        loss_gan_ba = loss_gan(pred_fake_monet, torch.ones_like(pred_fake_monet))\n\n        # Cycle consistency loss\n        rec_monet = gen_ba(fake_photo)\n        rec_photo = gen_ab(fake_monet)\n        loss_cycle_consistency = loss_cycle(rec_monet, monet_batch) + loss_cycle(rec_photo, photo_batch)\n\n        # Total generator loss\n        loss_gen_total = loss_id * 5.0 + loss_gan_ab + loss_gan_ba + loss_cycle_consistency * 10.0\n        loss_gen_total.backward()\n        opt_g.step()\n\n        # Train Discriminators (Disc_A and Disc_B)\n        # Update Discriminator A\n        opt_d_a.zero_grad()\n        pred_real_monet = disc_a(monet_batch)\n        pred_fake_monet_detached = disc_a(fake_monet.detach())\n        loss_d_a = (loss_gan(pred_real_monet, torch.ones_like(pred_real_monet)) +\n                    loss_gan(pred_fake_monet_detached, torch.zeros_like(pred_fake_monet_detached))) * 0.5\n        loss_d_a.backward()\n        opt_d_a.step()\n\n        # Update Discriminator B\n        opt_d_b.zero_grad()\n        pred_real_photo = disc_b(photo_batch)\n        pred_fake_photo_detached = disc_b(fake_photo.detach())\n        loss_d_b = (loss_gan(pred_real_photo, torch.ones_like(pred_real_photo)) +\n                    loss_gan(pred_fake_photo_detached, torch.zeros_like(pred_fake_photo_detached))) * 0.5\n        loss_d_b.backward()\n        opt_d_b.step()\n\n    # Step schedulers after each epoch\n    scheduler_gen.step()\n    scheduler_disc_a.step()\n    scheduler_disc_b.step()\n\n    # Logging and checkpointing\n    if epoch % SHOW_EVERY == 0 or epoch == TOTAL_EPOCHS:\n        print(f\"Epoch [{epoch}/{TOTAL_EPOCHS}] | \"\n              f\"Loss Gen: {loss_gen_total.item():.4f} | \"\n              f\"Loss Disc A: {loss_d_a.item():.4f} | \"\n              f\"Loss Disc B: {loss_d_b.item():.4f}\")\n\n        # Example: Save model checkpoints\n        torch.save(gen_ab.state_dict(), f\"gen_ab_epoch_{epoch}.pth\")\n        torch.save(gen_ba.state_dict(), f\"gen_ba_epoch_{epoch}.pth\")\n        torch.save(disc_a.state_dict(), f\"disc_a_epoch_{epoch}.pth\")\n        torch.save(disc_b.state_dict(), f\"disc_b_epoch_{epoch}.pth\")","metadata":{"scrolled":true,"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T12:18:46.056110Z","iopub.execute_input":"2024-11-17T12:18:46.056502Z","iopub.status.idle":"2024-11-17T14:42:19.390874Z","shell.execute_reply.started":"2024-11-17T12:18:46.056464Z","shell.execute_reply":"2024-11-17T14:42:19.389178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"files = [PHOTO_DIR + \"/\" + name for name in os.listdir(PHOTO_DIR)]\nlen(files)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T14:42:19.396330Z","iopub.execute_input":"2024-11-17T14:42:19.396924Z","iopub.status.idle":"2024-11-17T14:42:19.410420Z","shell.execute_reply.started":"2024-11-17T14:42:19.396880Z","shell.execute_reply":"2024-11-17T14:42:19.409706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"save_dir = '/kaggle/working/images'\nif not os.path.exists(save_dir):\n    os.makedirs(save_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T15:49:04.309224Z","iopub.execute_input":"2024-11-17T15:49:04.309925Z","iopub.status.idle":"2024-11-17T15:49:04.314874Z","shell.execute_reply.started":"2024-11-17T15:49:04.309869Z","shell.execute_reply":"2024-11-17T15:49:04.314017Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_transforms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])\n])\n\n# Convert tensor to PIL image\ntensor_to_pil = transforms.ToPILImage()\n\n# Ensure the generator is in evaluation mode\ngen_ba.eval()\n\n# Process and generate images in batches\nfor start_idx in range(0, len(files), BATCH_SIZE):\n    # Prepare batch files\n    current_batch_files = files[start_idx:start_idx + BATCH_SIZE]\n    batch_images = [image_transforms(Image.open(img_path)) for img_path in current_batch_files]\n    \n    # Stack images into a tensor batch\n    batch_tensor = torch.stack(batch_images).to(device).float()\n\n    # Generate fake images\n    with torch.no_grad():\n        generated_batch = gen_ba(batch_tensor).cpu()\n    \n    # Save each generated image\n    for img_idx, generated_image in enumerate(generated_batch):\n        # Normalize tensor to [0, 255] and convert to numpy\n        img_np = generated_image.permute(1, 2, 0).numpy()\n        img_np = ((img_np - img_np.min()) * 255 / (img_np.max() - img_np.min())).astype(np.uint8)\n        \n        # Convert numpy image to PIL format and save\n        pil_image = tensor_to_pil(img_np)\n        _, image_name = os.path.split(current_batch_files[img_idx])\n        pil_image.save(os.path.join(save_dir, image_name))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T15:49:21.136084Z","iopub.execute_input":"2024-11-17T15:49:21.136974Z","iopub.status.idle":"2024-11-17T15:51:37.336524Z","shell.execute_reply.started":"2024-11-17T15:49:21.136936Z","shell.execute_reply":"2024-11-17T15:51:37.335701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!zip -r file.zip /kaggle/working","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T15:51:42.645758Z","iopub.execute_input":"2024-11-17T15:51:42.646475Z","iopub.status.idle":"2024-11-17T15:51:53.358320Z","shell.execute_reply.started":"2024-11-17T15:51:42.646441Z","shell.execute_reply":"2024-11-17T15:51:53.357325Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import FileLink\nFileLink(r'file.zip')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-17T15:51:55.583962Z","iopub.execute_input":"2024-11-17T15:51:55.584610Z","iopub.status.idle":"2024-11-17T15:51:55.591910Z","shell.execute_reply.started":"2024-11-17T15:51:55.584572Z","shell.execute_reply":"2024-11-17T15:51:55.591115Z"}},"outputs":[],"execution_count":null}]}