{"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"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\nimport zipfile\nimport io\nfrom functools import partial\nfrom PIL import Image\nfrom torchvision import transforms\n\nimport matplotlib.pyplot as plt\nfrom tqdm.auto import tqdm\nfrom torchinfo import summary\nfrom typing import List, Tuple, Any, Callable, Optional\n\nplt.rcParams.update({\"font.family\": \"monospace\"})\ntorch.manual_seed(12)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:33:30.030286Z","iopub.execute_input":"2025-06-08T12:33:30.030442Z","iopub.status.idle":"2025-06-08T12:33:38.656305Z","shell.execute_reply.started":"2025-06-08T12:33:30.030427Z","shell.execute_reply":"2025-06-08T12:33:38.655710Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Monet2Photo(Dataset):\n    def __init__(\n        self,\n        root_monet: str,\n        root_photo: str,\n        transform: Optional[Callable] = None,\n        dataset_size: Optional[int] = None\n    ) -> None:\n        self.root_monet = root_monet\n        self.root_photo = root_photo\n        self.transform = transform\n\n        self.monet_paths = os.listdir(self.root_monet)[:dataset_size]\n        self.photo_paths = os.listdir(self.root_photo)[:dataset_size]\n\n        self.monet_paths = [os.path.join(root_monet, p) for p in self.monet_paths]\n        self.photo_paths = [os.path.join(root_photo, p) for p in self.photo_paths]\n\n        self.dataset_len = max(len(self.monet_paths), len(self.photo_paths))\n\n    def __len__(self) -> int:\n        return self.dataset_len\n\n    def __getitem__(self, idx: int) -> Tuple[Any, Any]:\n        monet_path = self.monet_paths[idx % len(self.monet_paths)]\n        photo_path = self.photo_paths[idx % len(self.photo_paths)]\n\n        monet = Image.open(monet_path)\n        photo = Image.open(photo_path)\n\n        if self.transform:\n            monet = self.transform(monet)\n            photo = self.transform(photo)\n\n        return monet, photo","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:33:44.227311Z","iopub.execute_input":"2025-06-08T12:33:44.227993Z","iopub.status.idle":"2025-06-08T12:33:44.234676Z","shell.execute_reply.started":"2025-06-08T12:33:44.227968Z","shell.execute_reply":"2025-06-08T12:33:44.234108Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n])\ninv_norm = transforms.Normalize((-1, -1, -1), (2, 2, 2))\n\ntrain_dataset = Monet2Photo(\n    root_monet=\"/kaggle/input/gan-getting-started/monet_jpg\",\n    root_photo=\"/kaggle/input/gan-getting-started/photo_jpg\",\n    transform=transform,\n    dataset_size=500\n)\n\nprint(f\"Dataset Size: {len(train_dataset):,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:33:48.137868Z","iopub.execute_input":"2025-06-08T12:33:48.138848Z","iopub.status.idle":"2025-06-08T12:33:48.249891Z","shell.execute_reply.started":"2025-06-08T12:33:48.138818Z","shell.execute_reply":"2025-06-08T12:33:48.249165Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"rows, cols = 4, 5\nindices = torch.randint(0, len(train_dataset)-1, size=(rows*cols,)).numpy()\n\nfig = plt.figure(figsize=(10, 8))\nfig.suptitle(\"Monet Training Images\", fontweight=\"bold\")\nfor i, idx in enumerate(indices, start=1):\n    monet, _ = train_dataset[idx]\n    monet_np = inv_norm(monet).numpy().transpose(1, 2, 0)\n\n    plt.subplot(rows, cols, i)\n    plt.imshow(monet_np)\n    plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:33:55.388270Z","iopub.execute_input":"2025-06-08T12:33:55.388931Z","iopub.status.idle":"2025-06-08T12:33:56.819009Z","shell.execute_reply.started":"2025-06-08T12:33:55.388886Z","shell.execute_reply":"2025-06-08T12:33:56.818213Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig = plt.figure(figsize=(10, 8))\nfig.suptitle(\"Photo Training Images\", fontweight=\"bold\")\nfor i, idx in enumerate(indices, start=1):\n    _, photo = train_dataset[idx]\n    photo_np = inv_norm(photo).numpy().transpose(1, 2, 0)\n\n    plt.subplot(rows, cols, i)\n    plt.imshow(photo_np)\n    plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:33:58.543311Z","iopub.execute_input":"2025-06-08T12:33:58.544059Z","iopub.status.idle":"2025-06-08T12:33:59.586966Z","shell.execute_reply.started":"2025-06-08T12:33:58.544031Z","shell.execute_reply":"2025-06-08T12:33:59.585965Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 4\ntrain_loader = DataLoader(train_dataset, batch_size, shuffle=True)\n\nprint(f\"# Train Batches: {len(train_loader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:25.248562Z","iopub.execute_input":"2025-06-08T12:34:25.248836Z","iopub.status.idle":"2025-06-08T12:34:25.253687Z","shell.execute_reply.started":"2025-06-08T12:34:25.248816Z","shell.execute_reply":"2025-06-08T12:34:25.253010Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"Device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:27.650154Z","iopub.execute_input":"2025-06-08T12:34:27.650852Z","iopub.status.idle":"2025-06-08T12:34:27.696983Z","shell.execute_reply.started":"2025-06-08T12:34:27.650824Z","shell.execute_reply":"2025-06-08T12:34:27.696251Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ConvBlock(nn.Module):\n    def __init__(\n        self,\n        in_channels: int,\n        out_channels: int,\n        kernel_size: int,\n        stride: int = 1,\n        padding: int = 0,\n        act: Optional[str] = \"relu\"\n    ) -> None:\n        super(ConvBlock, self).__init__()\n\n        activation = (\n            {\n                \"relu\": partial(nn.ReLU, inplace=True),\n                \"leaky_relu\": partial(nn.LeakyReLU, 0.2, inplace=True)\n            }.get(act, partial(nn.ReLU, inplace=True))\n            if act else None\n        )\n        modules = [\n            nn.Conv2d(\n                in_channels,\n                out_channels,\n                kernel_size,\n                stride=stride,\n                padding=padding,\n                padding_mode=\"reflect\"\n            ),\n            nn.InstanceNorm2d(out_channels)\n        ]\n        if activation: modules.append(activation())\n\n        self.conv = nn.Sequential(*modules)\n\n    def forward(self, input: torch.Tensor) -> torch.Tensor:\n        return self.conv(input)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:29.293503Z","iopub.execute_input":"2025-06-08T12:34:29.293768Z","iopub.status.idle":"2025-06-08T12:34:29.300046Z","shell.execute_reply.started":"2025-06-08T12:34:29.293746Z","shell.execute_reply":"2025-06-08T12:34:29.299262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class UpsampleBlock(nn.Module):\n    def __init__(\n        self,\n        in_channels: int,\n        out_channels: int,\n        kernel_size: int,\n        stride: int = 1,\n        padding: int = 0\n    ) -> None:\n        super(UpsampleBlock, self).__init__()\n\n        self.upconv = nn.Sequential(\n            nn.ConvTranspose2d(\n                in_channels,\n                out_channels,\n                kernel_size=kernel_size,\n                stride=stride,\n                padding=padding\n            ),\n            nn.InstanceNorm2d(out_channels),\n            nn.ReLU(inplace=True)\n        )\n\n    def forward(self, input: torch.Tensor) -> torch.Tensor:\n        return self.upconv(input)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:31.086255Z","iopub.execute_input":"2025-06-08T12:34:31.086525Z","iopub.status.idle":"2025-06-08T12:34:31.091640Z","shell.execute_reply.started":"2025-06-08T12:34:31.086505Z","shell.execute_reply":"2025-06-08T12:34:31.090793Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ResidualBlock(nn.Module):\n    def __init__(self, channels: int) -> None:\n        super(ResidualBlock, self).__init__()\n\n        self.block = nn.Sequential(\n            ConvBlock(channels, channels, kernel_size=3, padding=1),\n            ConvBlock(channels, channels, kernel_size=3, padding=1, act=None)\n        )\n\n    def forward(self, input: torch.Tensor) -> torch.Tensor:\n        return input + self.block(input)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:32.861135Z","iopub.execute_input":"2025-06-08T12:34:32.861384Z","iopub.status.idle":"2025-06-08T12:34:32.866173Z","shell.execute_reply.started":"2025-06-08T12:34:32.861367Z","shell.execute_reply":"2025-06-08T12:34:32.865483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Generator(nn.Module):\n    def __init__(self, num_features: int, num_blocks: int) -> None:\n        super(Generator, self).__init__()\n\n        self.initial = ConvBlock(3, num_features, kernel_size=7, padding=3)\n        self.down_proj = nn.Sequential(\n            ConvBlock(num_features, num_features*2, kernel_size=4, stride=2, padding=1),\n            ConvBlock(num_features*2, num_features*4, kernel_size=4, stride=2, padding=1)\n        )\n        self.res_blocks = nn.Sequential(*[ResidualBlock(num_features*4) for _ in range(num_blocks)])\n        self.up_proj = nn.Sequential(\n            UpsampleBlock(num_features*4, num_features*2, kernel_size=4, stride=2, padding=1),\n            UpsampleBlock(num_features*2, num_features, kernel_size=4, stride=2, padding=1)\n        )\n        self.final = nn.Sequential(\n            nn.Conv2d(num_features, 3, kernel_size=7, padding=3, padding_mode=\"reflect\"),\n            nn.Tanh()\n        )\n\n    def forward(self, input: torch.Tensor) -> torch.Tensor:\n        output = self.initial(input)\n        output = self.down_proj(output)\n        output = self.res_blocks(output)\n        output = self.up_proj(output)\n        output = self.final(output)\n\n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:34.351054Z","iopub.execute_input":"2025-06-08T12:34:34.351328Z","iopub.status.idle":"2025-06-08T12:34:34.358141Z","shell.execute_reply.started":"2025-06-08T12:34:34.351309Z","shell.execute_reply":"2025-06-08T12:34:34.357510Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Discriminator(nn.Module):\n    def __init__(self, channels: List[int]) -> None:\n        super(Discriminator, self).__init__()\n\n        in_channs, out_channs = channels[:-1], channels[1:]\n        self.model = nn.Sequential(*[\n            ConvBlock(in_chann, out_chann, kernel_size=4, stride=2, padding=1, act=\"leaky_relu\")\n            for in_chann, out_chann in zip(in_channs, out_channs)\n        ])\n        self.final = nn.Sequential(\n            nn.Conv2d(out_channs[-1], 1, kernel_size=4, padding=1, padding_mode=\"reflect\"),\n            nn.Sigmoid()\n        )\n\n    def forward(self, input: torch.Tensor) -> torch.Tensor:\n        output = self.model(input)\n        output = self.final(output)\n\n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:35.748381Z","iopub.execute_input":"2025-06-08T12:34:35.749046Z","iopub.status.idle":"2025-06-08T12:34:35.754238Z","shell.execute_reply.started":"2025-06-08T12:34:35.749021Z","shell.execute_reply":"2025-06-08T12:34:35.753513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gen_m = Generator(num_features=64, num_blocks=6).to(device)\ngen_p = Generator(num_features=64, num_blocks=6).to(device)\n\ndisc_m = Discriminator(channels=[3, 64, 128, 256, 512]).to(device)\ndisc_p = Discriminator(channels=[3, 64, 128, 256, 512]).to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:37.669242Z","iopub.execute_input":"2025-06-08T12:34:37.669530Z","iopub.status.idle":"2025-06-08T12:34:38.043012Z","shell.execute_reply.started":"2025-06-08T12:34:37.669507Z","shell.execute_reply":"2025-06-08T12:34:38.042310Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"summary(gen_m, (batch_size, 3, 256, 256), depth=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:39.972419Z","iopub.execute_input":"2025-06-08T12:34:39.972680Z","iopub.status.idle":"2025-06-08T12:34:40.513467Z","shell.execute_reply.started":"2025-06-08T12:34:39.972661Z","shell.execute_reply":"2025-06-08T12:34:40.512769Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"summary(disc_m, (batch_size, 3, 256, 256), depth=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:44.328299Z","iopub.execute_input":"2025-06-08T12:34:44.328560Z","iopub.status.idle":"2025-06-08T12:34:44.382663Z","shell.execute_reply.started":"2025-06-08T12:34:44.328540Z","shell.execute_reply":"2025-06-08T12:34:44.382104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mse = nn.MSELoss()\nl1 = nn.L1Loss()\n\ndef train_epoch(\n    generators: List[nn.Module],\n    discriminators: List[nn.Module],\n    train_loader: DataLoader[torch.Tensor],\n    optimizers: List[torch.optim.Optimizer],\n    desc: str,\n    lmbd_cycle: float = 10.0,\n    lmbd_identity: float = 5.0\n) -> Tuple[float, float]:\n    gen_m, gen_p = tuple(generators)\n    disc_m, disc_p = tuple(discriminators)\n\n    optim_g, optim_d = tuple(optimizers)\n    \n    gen_m.train()\n    gen_p.train()\n    disc_m.train()\n    disc_p.train()\n\n    gen_loss, disc_loss = 0.0, 0.0\n    num_batches = len(train_loader)\n\n    pbar = tqdm(train_loader, desc=desc)\n    for batch, (Xr_m, Xr_p) in enumerate(pbar, start=1):\n        Xr_m = Xr_m.to(device)\n        Xr_p = Xr_p.to(device)\n\n        Xf_m = gen_m(Xr_p) # monet from photo\n        Xf_p = gen_p(Xr_m) # photo from monet\n\n        # training both discriminators\n        Dr_m, Dr_p = disc_m(Xr_m), disc_p(Xr_p)\n        Df_m, Df_p = disc_m(Xf_m.detach()), disc_p(Xf_p.detach())\n\n        loss_dm = mse(Dr_m, torch.ones_like(Dr_m)) + mse(Df_m, torch.zeros_like(Df_m))\n        loss_dp = mse(Dr_p, torch.ones_like(Dr_p)) + mse(Df_p, torch.zeros_like(Df_p))\n\n        loss_d = (loss_dm + loss_dp)/2\n        optim_d.zero_grad()\n        loss_d.backward()\n        optim_d.step()\n\n        # training both generators\n        ## adversarial loss\n        Df_m, Df_p = disc_m(Xf_m), disc_p(Xf_p)\n        loss_adv = mse(Df_m, torch.ones_like(Df_m)) + mse(Df_p, torch.ones_like(Df_p))\n\n        ## cycle consistency loss\n        C_m, C_p = gen_m(Xf_p), gen_p(Xf_m)\n        loss_cyc = l1(C_m, Xr_m) + l1(C_p, Xr_p)\n\n        ## identity loss\n        loss_iden = 0.0\n        if lmbd_identity >= 0.0:\n            I_m, I_p = gen_m(Xr_m), gen_p(Xr_p)\n            loss_iden = l1(I_m, Xr_m) + l1(I_p, Xr_p)\n\n        loss_g = loss_adv + lmbd_cycle*loss_cyc + lmbd_identity*loss_iden\n        optim_g.zero_grad()\n        loss_g.backward()\n        optim_g.step()\n\n        gen_loss += loss_g.item()\n        disc_loss += loss_d.item()\n\n        pbar.set_postfix({\n            \"Generator Loss\": f\"{gen_loss/batch:.4f}\",\n            \"Discriminator Loss\": f\"{disc_loss/batch:.4f}\"\n        })\n\n    pbar.close()\n    return gen_loss/num_batches, disc_loss/num_batches","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:51.047569Z","iopub.execute_input":"2025-06-08T12:34:51.048078Z","iopub.status.idle":"2025-06-08T12:34:51.057484Z","shell.execute_reply.started":"2025-06-08T12:34:51.048054Z","shell.execute_reply":"2025-06-08T12:34:51.056734Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"epochs, lr = 100, 2e-4\n\noptim_g = torch.optim.Adam(\n    list(gen_m.parameters()) + list(gen_p.parameters()),\n    lr=lr,\n    betas=(0.5, 0.999)\n)\noptim_d = torch.optim.Adam(\n    list(disc_p.parameters()) + list(disc_m.parameters()),\n    lr=lr,\n    betas=(0.5, 0.999)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:34:59.903407Z","iopub.execute_input":"2025-06-08T12:34:59.903987Z","iopub.status.idle":"2025-06-08T12:34:59.908940Z","shell.execute_reply.started":"2025-06-08T12:34:59.903965Z","shell.execute_reply":"2025-06-08T12:34:59.908056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gen_losses, disc_losses = [], []\nfor epoch in range(1, epochs+1):\n    desc = f\"Epoch [{epoch:03d}/{epochs}]\"\n    gen_loss, disc_loss = train_epoch(\n        generators=[gen_m, gen_p],\n        discriminators=[disc_m, disc_p],\n        train_loader=train_loader,\n        optimizers=[optim_g, optim_d],\n        desc=desc,\n        lmbd_identity=0.0\n    )\n\n    gen_losses.append(gen_loss)\n    disc_losses.append(disc_loss)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T12:35:04.506122Z","iopub.execute_input":"2025-06-08T12:35:04.506381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(10, 4))\n\nplt.subplot(1, 2, 1)\nplt.plot(range(1, len(gen_losses)+1), gen_losses)\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Generator Loss-Epoch Distribution\", fontweight=\"bold\")\nplt.grid(True)\n\nplt.subplot(1, 2, 2)\nplt.plot(range(1, len(disc_losses)+1), disc_losses)\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Discriminator Loss-Epoch Distribution\", fontweight=\"bold\")\nplt.grid(True)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T14:41:35.879244Z","iopub.execute_input":"2025-06-04T14:41:35.879524Z","iopub.status.idle":"2025-06-04T14:41:36.364625Z","shell.execute_reply.started":"2025-06-04T14:41:35.879504Z","shell.execute_reply":"2025-06-04T14:41:36.363893Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gen_m.eval()\n\nfig = plt.figure(figsize=(10, 8))\nfig.suptitle(\"Generated Monet Images\", fontweight=\"bold\")\nfor i, idx in enumerate(indices, start=1):\n    _, photo = train_dataset[i]\n    with torch.no_grad():\n        monet = gen_m(photo.to(device)).cpu()\n    monet_np = inv_norm(monet).numpy().transpose(1, 2, 0)\n\n    plt.subplot(rows, cols, i)\n    plt.imshow(monet_np)\n    plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T14:41:38.334670Z","iopub.execute_input":"2025-06-04T14:41:38.334975Z","iopub.status.idle":"2025-06-04T14:41:39.634325Z","shell.execute_reply.started":"2025-06-04T14:41:38.334953Z","shell.execute_reply":"2025-06-04T14:41:39.633208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = Monet2Photo(\n    root_monet=\"/kaggle/input/gan-getting-started/monet_jpg\",\n    root_photo=\"/kaggle/input/gan-getting-started/photo_jpg\",\n    transform=transform,\n)\n\nwith zipfile.ZipFile(\"images.zip\", 'w') as zipf:\n    for i in tqdm(range(len(dataset)), desc=\"Generating Submission\"):\n        _, photo = dataset[i]\n        with torch.no_grad():\n            monet = gen_m(photo.to(device)).cpu()\n        monet_np = inv_norm(monet).numpy().transpose(1, 2, 0)\n        monet_np = (255 * monet_np).astype(np.uint8)\n\n        image = Image.fromarray(monet_np)\n        buffer = io.BytesIO()\n        image.save(buffer, format=\"JPEG\")\n        \n        zipf.writestr(f\"{i+1:04d}.jpg\", buffer.getvalue())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T10:36:55.957011Z","iopub.execute_input":"2025-06-01T10:36:55.957814Z","iopub.status.idle":"2025-06-01T10:40:22.570644Z","shell.execute_reply.started":"2025-06-01T10:36:55.957780Z","shell.execute_reply":"2025-06-01T10:40:22.569787Z"}},"outputs":[],"execution_count":null}]}