{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":"nvidiaTeslaT4","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":12578557,"sourceType":"datasetVersion","datasetId":7596758},{"sourceId":3729,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":2656,"modelId":312}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from enum import StrEnum\nfrom pathlib import Path\nfrom typing import Callable\nimport random\n\nfrom torch.utils.data import DataLoader\nfrom torch.utils.data import Dataset\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchaudio\nimport torchaudio.transforms as tt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:10.493204Z","iopub.execute_input":"2025-07-26T02:06:10.493480Z","iopub.status.idle":"2025-07-26T02:06:12.719060Z","shell.execute_reply.started":"2025-07-26T02:06:10.493439Z","shell.execute_reply":"2025-07-26T02:06:12.718107Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.backends.cudnn.benchmark = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.720393Z","iopub.execute_input":"2025-07-26T02:06:12.720865Z","iopub.status.idle":"2025-07-26T02:06:12.725789Z","shell.execute_reply.started":"2025-07-26T02:06:12.720829Z","shell.execute_reply":"2025-07-26T02:06:12.725069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from dataclasses import dataclass\n\n\n@dataclass(frozen=True, kw_only=True)\nclass Config:\n    num_seconds: int = 5\n    sample_rate: int = 32000\n\n    batch_size: int = 64\n    random_state: int = 42\n    shuffle: bool = True\n    val_size: float = 0.25\n\n    picker_method: str = \"random\"\n    human_voice_segments_file: str = \"../input/birdclef-human-voice/hvs.csv\"\n\n    n_mels: int = 128\n    n_fft: int = 1024\n    f_min: float = 0\n    f_max: float = 16000\n    mel_scale: str = \"slaney\"\n\n    in_chans = 1\n\n    iid_masks: bool = True\n    n_freq: int = n_fft // 2 + 1\n\n    mask_prob: float = 0.5\n    freq_mask_param: int = n_mels // 3\n    time_mask_param: int = 100\n\n    max_noise_factor: float = 0.75\n\n    device: str = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    epochs: int = 10\n    loss: str = \"focal\"\n    model: str = \"tf_efficientnet_b0\"\n    optimizer: str = \"adam\"\n\n    early_stopper_patience: int = 5\n    early_stopper_min_delta: float = 0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.727246Z","iopub.execute_input":"2025-07-26T02:06:12.727550Z","iopub.status.idle":"2025-07-26T02:06:12.798068Z","shell.execute_reply.started":"2025-07-26T02:06:12.727527Z","shell.execute_reply":"2025-07-26T02:06:12.797515Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## DATASET","metadata":{}},{"cell_type":"code","source":"class BirdCLEFDataset(Dataset):\n    def __init__(\n        self,\n        filenames: pd.Series,\n        targets: torch.Tensor,\n        picker: Callable,\n        transformer: Callable,\n    ):\n        self.filenames = filenames\n        self.targets = targets\n\n        self.picker = picker\n        self.transformer = transformer\n\n    def __len__(self) -> int:\n        return len(self.filenames)\n\n    def __getitem__(self, i: int) -> tuple[torch.Tensor, torch.Tensor]:\n        waveform, sample_rate = self.picker(self.filenames.iloc[i])\n        sample = self.transformer(waveform)\n        return sample, self.targets[i]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.800012Z","iopub.execute_input":"2025-07-26T02:06:12.800234Z","iopub.status.idle":"2025-07-26T02:06:12.812873Z","shell.execute_reply.started":"2025-07-26T02:06:12.800204Z","shell.execute_reply":"2025-07-26T02:06:12.812293Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## PICKER","metadata":{}},{"cell_type":"code","source":"def read_segments(segment_file: str | Path) -> dict[str, list[dict]]:\n    return (\n        pd.read_csv(segment_file)\n        .groupby(\"filename\")\n        .apply(lambda x: x[[\"start\", \"end\"]].to_dict(orient=\"records\"), include_groups=False)\n        .to_dict()\n    )\n\n\ndef create_mask(segments: list[dict], size: int) -> np.ndarray:\n    mask = np.ones(size, dtype=bool)\n    for segment in segments:\n        mask[segment[\"start\"] : segment[\"end\"]] = False\n    return mask\n\n\nclass PickerMethod(StrEnum):\n    FIRST = \"first\"\n    RANDOM = \"random\"\n    RMS = \"rms\"\n\n\nclass Picker:\n    def __init__(\n        self,\n        method: PickerMethod,\n        num_seconds: int,\n        sample_rate: int,\n        human_voice_segments: dict[str, list[dict]] = {},\n    ):\n        self.method = method\n        self.num_frames = num_seconds * sample_rate\n        self.sample_rate = sample_rate\n        self.human_voice_segments = human_voice_segments\n\n    def frame_offset(self, total_num_frames: int) -> int:\n        if self.method == PickerMethod.FIRST:\n            return 0\n\n        if self.method == PickerMethod.RANDOM:\n            return random.randint(0, max(0, total_num_frames - self.num_frames))\n\n        raise NotImplementedError(PickerMethod.RMS)\n\n    def __call__(self, filename: Path) -> tuple[torch.Tensor, int]:\n        if filename not in self.human_voice_segments:\n            metadata = torchaudio.info(filename)\n            if metadata.sample_rate != self.sample_rate:\n                raise NotImplementedError(\"Resampling\")\n\n            offset = self.frame_offset(metadata.num_frames)\n            waveform, sample_rate = torchaudio.load(\n                filename,\n                frame_offset=offset,\n                num_frames=self.num_frames,\n            )\n        else:\n            waveform, sample_rate = torchaudio.load(filename)\n            if sample_rate != self.sample_rate:\n                raise NotImplementedError(\"Resampling\")\n\n            total_num_frames = waveform.shape[1]\n            non_voice_mask = create_mask(self.human_voice_segments[str(filename)], total_num_frames)\n            waveform = waveform[non_voice_mask]\n\n            offset = self.frame_offset(total_num_frames)\n            waveform = waveform[:, offset : 1 + min(total_num_frames, offset + self.num_frames)]\n\n        if waveform.shape[1] <= self.num_frames:\n            times = (self.num_frames - 1) // waveform.shape[1] + 1\n            waveform = waveform.repeat(1, times)[:, : self.num_frames]\n\n        assert waveform.shape[1] == self.num_frames\n        return waveform, sample_rate\n\n    @classmethod\n    def from_config(cls, config: Config) -> \"Picker\":\n        return Picker(\n            method=PickerMethod(config.picker_method),\n            num_seconds=config.num_seconds,\n            sample_rate=config.sample_rate,\n            human_voice_segments=read_segments(config.human_voice_segments_file),\n        )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.813556Z","iopub.execute_input":"2025-07-26T02:06:12.813816Z","iopub.status.idle":"2025-07-26T02:06:12.827634Z","shell.execute_reply.started":"2025-07-26T02:06:12.813799Z","shell.execute_reply":"2025-07-26T02:06:12.826948Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## TRANSFORMER","metadata":{}},{"cell_type":"code","source":"class Compose:\n    # https://docs.pytorch.org/vision/stable/generated/torchvision.transforms.Compose.html\n    def __init__(self, transforms: list[Callable]):\n        self.transforms = transforms\n\n    def __call__(self, x: torch.Tensor) -> torch.Tensor:\n        for transform in self.transforms:\n            x = transform(x)\n        return x\n\n\n# class OneOf:\n#     def __init__(self, transforms: list[Callable]):\n#         self.transforms = transforms\n#\n#     def __call__(self, x: torch.Tensor) -> torch.Tensor:\n#         transform = random.choice(self.transforms)\n#         return transform(x)\n\n\nclass RandomTransform:\n    def __init__(self, transform: Callable, p: float = 0.5):\n        self.transform = transform\n        self.p = p\n\n    def __call__(self, x: torch.Tensor) -> torch.Tensor:\n        return self.transform(x) if random.random() < self.p else x\n\n\nclass Scale:\n    def __init__(self, min: float = 0, max: float = 1):\n        self.min = min\n        self.max = max\n\n    def __call__(self, x: torch.Tensor) -> torch.Tensor:\n        eps = 1e-6\n        return self.min + (self.max - self.min) * (x - x.min()) / (x.max() - x.min() + eps)\n\n\ndef create_melspec(config: Config):\n    return Compose(\n        [\n            tt.MelSpectrogram(\n                sample_rate=config.sample_rate,\n                n_fft=config.n_fft,\n                f_min=config.f_min,\n                f_max=config.f_max,\n                n_mels=config.n_mels,\n                mel_scale=config.mel_scale,\n            ),\n            tt.AmplitudeToDB(stype=\"power\", top_db=80),\n            Scale(),\n        ]\n    )\n\n\ndef create_specaug(config: Config):\n    return Compose(\n        [\n            RandomTransform(create_timemask(config), p=config.mask_prob),\n            RandomTransform(create_freqmask(config), p=config.mask_prob),\n        ]\n    )\n\n\ndef create_timemask(config: Config):\n    return tt.TimeMasking(time_mask_param=config.time_mask_param, iid_masks=config.iid_masks)\n\n\ndef create_freqmask(config: Config):\n    return tt.FrequencyMasking(freq_mask_param=config.freq_mask_param, iid_masks=config.iid_masks)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.828221Z","iopub.execute_input":"2025-07-26T02:06:12.828388Z","iopub.status.idle":"2025-07-26T02:06:12.838406Z","shell.execute_reply.started":"2025-07-26T02:06:12.828375Z","shell.execute_reply":"2025-07-26T02:06:12.837883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class NoiseAugment:\n    def __init__(self, config: Config):\n        melspec = create_melspec(config)\n\n        self.max_factor = config.max_noise_factor\n\n        def spec_signal_mask(specgram: torch.Tensor) -> torch.Tensor:\n            mean, std = torch.mean(specgram, dim=1), torch.std(specgram, dim=1)\n            return specgram >= (mean + 1 * std)\n\n        def suppress_signal(waveform: torch.Tensor) -> torch.Tensor:\n            specgram = melspec(waveform)\n            mask = spec_signal_mask(specgram)\n            specgram[mask] = 0\n            mean = torch.mean(specgram, dim=1)\n            specgram[mask] = (torch.ones_like(mask) * mean)[mask]\n            return specgram\n\n        def noise(file: Path) -> torch.Tensor:\n            offset = random.randint(0, (60 - config.num_seconds) * config.sample_rate)\n            waveform, sample_rate = torchaudio.load(\n                file,\n                frame_offset=offset,\n                num_frames=config.num_seconds * config.sample_rate,\n            )\n\n            return suppress_signal(waveform)\n\n        files = Path(\"../input/birdclef-2025/train_soundscapes\").glob(\"*.ogg\")\n        self.noise = [noise(file) for file in random.sample(list(files), 100)]\n\n    def __call__(self, spec: torch.Tensor) -> torch.Tensor:\n        return spec + random.uniform(0, self.max_factor) * random.choice(self.noise)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LOSS","metadata":{}},{"cell_type":"code","source":"class FocalBCEWithLogitsLoss(nn.Module):\n    def __init__(self, device: str, alpha: torch.Tensor, gamma: float = 2, reduction: str = \"mean\"):\n        super().__init__()\n\n        self.alpha = alpha.to(device)\n        self.gamma = gamma\n        self.reduction = reduction\n\n    def forward(self, input: torch.Tensor, target: torch.Tensor) -> torch.Tensor:\n        bce = nn.functional.binary_cross_entropy_with_logits(input, target, reduction=\"none\")\n        p_t = torch.exp(-bce)\n\n        # https://stackoverflow.com/a/78781507\n        focal_loss = self.alpha[target.long()] * ((1 - p_t) ** self.gamma) * bce\n\n        if self.reduction == \"sum\":\n            return focal_loss.sum()\n        if self.reduction == \"mean\":\n            return focal_loss.mean()\n\n        return focal_loss\n\n\ndef create_loss(config: Config, targets: torch.Tensor, **kwargs) -> nn.Module:\n    if config.loss != \"focal\":\n        return nn.BCEWithLogitsLoss(**kwargs)\n\n    alpha = targets.sum() / targets.sum(0)\n    alpha = 100 * (alpha / alpha.sum())\n    return FocalBCEWithLogitsLoss(device=config.device, alpha=alpha, **kwargs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.839481Z","iopub.execute_input":"2025-07-26T02:06:12.839738Z","iopub.status.idle":"2025-07-26T02:06:12.851833Z","shell.execute_reply.started":"2025-07-26T02:06:12.839716Z","shell.execute_reply":"2025-07-26T02:06:12.851082Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## TRAINER","metadata":{}},{"cell_type":"code","source":"from datetime import datetime\n\n\n\nclass EarlyStopperAction(StrEnum):\n    CONTINUE = \"continue\"\n    SAVE = \"save\"\n    STOP = \"stop\"\n\n\nclass EarlyStopper:\n    def __init__(self, patience: int, min_delta: float):\n        self.patience = patience\n        self.min_delta = min_delta\n        self.counter = 0\n        self.best_vloss = float(\"inf\")\n\n    def __call__(self, vloss: float) -> EarlyStopperAction:\n        if vloss >= self.best_vloss:\n            self.counter += 1\n            if self.counter >= self.patience:\n                return EarlyStopperAction.STOP\n\n        elif vloss < self.best_vloss - self.min_delta:\n            self.best_vloss = vloss\n            self.counter = 0\n            return EarlyStopperAction.SAVE\n\n        return EarlyStopperAction.CONTINUE\n\n\nclass Trainer:\n    def __init__(\n        self,\n        model: nn.Module,\n        optimizer: optim.Optimizer,\n        loss: nn.Module,\n        device: str | None = None,\n        early_stopper: Callable | None = None,\n        lr_scheduler: optim.lr_scheduler.LRScheduler | None = None,\n    ):\n        self.epoch = 0\n\n        self.model = model.to(device)\n        self.optimizer = optimizer\n        self.loss = loss\n        self.lr_scheduler = lr_scheduler\n        self.early_stopper = early_stopper\n        self.device = device\n        self.scaler = torch.GradScaler(device)\n\n    def compute_loss(self, X: torch.Tensor, y: torch.Tensor) -> torch.Tensor:\n        y_pred = self.model(X.to(self.device))\n        return self.loss(y_pred, y.to(self.device))\n\n    def train_one_epoch(self, dataloader: DataLoader) -> float:\n        running_loss = 0.0\n\n        for i, (X, y) in enumerate(dataloader):\n            self.optimizer.zero_grad()\n\n            with torch.autocast(self.device):\n                y_pred = self.model(X.to(self.device))\n                loss = self.loss(y_pred, y.to(self.device))\n            self.scaler.scale(loss).backward()\n            self.scaler.step(optimizer)\n            self.scaler.update()\n\n            running_loss += loss\n            avg_loss = running_loss / (i + 1)\n            if i % 50 == 49:\n                print(f\"  batch {i + 1:4}: loss: {avg_loss.item():.6f}\")\n\n        return running_loss.item() / (i + 1)\n\n    def train(self, epochs: int, dataloader: DataLoader, val_dataloader: DataLoader | None = None):\n        for epoch in range(epochs):\n            self.epoch += 1\n\n            self.model.train()\n            tloss = self.train_one_epoch(dataloader)\n\n            vloss = self.validate(val_dataloader) if val_dataloader else 0\n            print(f\"Epoch {self.epoch:2}: train loss: {tloss:.6f}  val loss: {vloss:.6f}\")\n\n            if val_dataloader and self.early_stopper:\n                action = self.early_stopper(vloss)\n                if action == EarlyStopperAction.STOP:\n                    break\n                if action == EarlyStopperAction.SAVE:\n                    self.save()\n\n    def validate(self, dataloader: DataLoader) -> float:\n        self.model.eval()\n        with torch.no_grad():\n            total_loss = 0.0\n            for i, (X, y) in enumerate(dataloader):\n                total_loss += self.compute_loss(X, y)\n            return total_loss.item() / (i + 1)\n\n    def save(self):\n        timestamp = datetime.now().strftime('%Y%m%d_%H%M')\n        path = f\"{self.model.__class__.__name__}_{self.epoch}_{timestamp}.pt\"\n        torch.save(self.model.state_dict(), path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.852391Z","iopub.execute_input":"2025-07-26T02:06:12.852604Z","iopub.status.idle":"2025-07-26T02:06:12.866149Z","shell.execute_reply.started":"2025-07-26T02:06:12.852586Z","shell.execute_reply":"2025-07-26T02:06:12.865492Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## MODELS","metadata":{}},{"cell_type":"code","source":"class BirdSoundCNN(nn.Module):\n    def __init__(self, num_classes: int, in_chans: int):\n        super().__init__()\n\n        self.encoder = nn.Sequential(\n            self.conv_block(in_chans, 32),\n            self.conv_block(32, 64),\n            self.conv_block(64, 128),\n        )\n\n        self.decoder = nn.Sequential(\n            nn.Linear(128 * 14 * 37, 256),\n            nn.ReLU(),\n            nn.Linear(256, num_classes),\n        )\n\n    @staticmethod\n    def conv_block(in_chans: int, out_chans: int, kernel_size: int = 3) -> nn.Sequential:\n        return nn.Sequential(\n            nn.Conv2d(in_chans, out_chans, kernel_size),\n            nn.BatchNorm2d(out_chans),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n        )\n\n    def forward(self, x):\n        x = self.encoder(x)\n        x = x.view(x.size(0), -1)\n        return self.decoder(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.866832Z","iopub.execute_input":"2025-07-26T02:06:12.867085Z","iopub.status.idle":"2025-07-26T02:06:12.881142Z","shell.execute_reply.started":"2025-07-26T02:06:12.867059Z","shell.execute_reply":"2025-07-26T02:06:12.880500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import timm\n\n\nclass PreTrainedModel(nn.Module):\n    def __init__(self, model_name: str, num_classes: int, **kwargs):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=True, num_classes=num_classes, **kwargs)\n\n    def forward(self, x):\n        return self.model(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:12.883070Z","iopub.execute_input":"2025-07-26T02:06:12.883511Z","iopub.status.idle":"2025-07-26T02:06:15.697957Z","shell.execute_reply.started":"2025-07-26T02:06:12.883491Z","shell.execute_reply":"2025-07-26T02:06:15.697365Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## MAIN","metadata":{}},{"cell_type":"code","source":"import math\nimport sys\nimport time\n\nfrom sklearn.model_selection import train_test_split\nfrom torch.nn.functional import one_hot\n\n\ndef create_optimizer(config: Config, model: nn.Module, **kwargs) -> optim.Optimizer:\n    return optim.Adam(model.parameters(), **kwargs)\n\n\ndef create_model(config: Config, num_classes: int, **kwargs) -> nn.Module:\n    if config.model == \"cnn\":\n        return BirdSoundCNN(\n            num_classes=num_classes,\n            in_chans=config.in_chans,\n            **kwargs,\n        )\n\n    return PreTrainedModel(\n        config.model,\n        num_classes=num_classes,\n        in_chans=config.in_chans,\n        **kwargs,\n    )\n\n\ndef one_hot_encode(primary_label: pd.Series, labels: list[str]) -> torch.Tensor:\n    label_to_int = {label: i for i, label in enumerate(labels)}\n    targets = primary_label.map(label_to_int).to_numpy()\n    return one_hot(torch.LongTensor(targets), num_classes=len(labels)).float()\n\n\ndef duplicate_undersampled_classes(df: pd.DataFrame, min_samples: int = 4):\n    samples_per_label = df.primary_label.value_counts()\n    undersampled_labels = samples_per_label[samples_per_label < min_samples].index\n\n    def duplicate_samples(label: str):\n        samples = df[df.primary_label == label]\n        return samples.sample(\n            min_samples - len(samples),\n            replace=True,\n            random_state=config.random_state,\n        )\n\n    return pd.concat([df, *[duplicate_samples(label) for label in undersampled_labels]])\n\n\nif __name__ == \"__main__\":\n    config = Config()\n    print(config)\n\n    root = Path(\"../input/birdclef-2025\")\n    df = pd.read_csv(root / \"train.csv\")\n    df = duplicate_undersampled_classes(df, min_samples=math.ceil(1 / config.val_size))\n\n    filenames = root / \"train_audio\" / df.filename\n\n    label_names = pd.read_csv(root / \"sample_submission.csv\", index_col=0, nrows=0).columns\n    num_classes = len(label_names)\n\n    targets = one_hot_encode(df.primary_label, label_names)\n\n    train_filenames, val_filenames, train_targets, val_targets = train_test_split(\n        filenames,\n        targets,\n        random_state=config.random_state,\n        stratify=df.primary_label,\n    )\n\n    picker  = Picker.from_config(config)\n    melspec = create_melspec(config)\n    specaug = create_specaug(config)\n    transformer = Compose([melspec, specaug])\n\n    train_dataset = BirdCLEFDataset(train_filenames, train_targets, picker, transformer)\n    train_dataloader = DataLoader(train_dataset, batch_size=config.batch_size, shuffle=config.shuffle, pin_memory=True, num_workers=2)\n\n    val_dataset = BirdCLEFDataset(val_filenames, val_targets, picker, melspec)\n    val_dataloader = DataLoader(val_dataset, batch_size=config.batch_size, shuffle=config.shuffle, pin_memory=True, num_workers=2)\n\n    model = create_model(config, num_classes)\n    print(model)\n\n    optimizer = create_optimizer(config, model)\n    loss_fn = create_loss(config, train_targets)\n\n    early_stopper = EarlyStopper(\n        patience=config.early_stopper_patience,\n        min_delta=config.early_stopper_min_delta,\n    )\n    trainer = Trainer(model, optimizer, loss_fn, device=config.device, early_stopper=early_stopper)\n\n    start = time.time()\n    trainer.train(config.epochs, train_dataloader, val_dataloader)\n    train_duration = (time.time() - start) / 60\n    print(f\"Training duration: {train_duration:.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T02:06:15.698567Z","iopub.execute_input":"2025-07-26T02:06:15.698746Z","iopub.status.idle":"2025-07-26T04:21:10.457857Z","shell.execute_reply.started":"2025-07-26T02:06:15.698733Z","shell.execute_reply":"2025-07-26T04:21:10.456684Z"}},"outputs":[],"execution_count":null}]}