{"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":59093,"databundleVersionId":7469972,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# --- Module 1: Environment Setup & Data Loading","metadata":{}},{"cell_type":"code","source":"\"\"\"\nLEVERAGING MACHINE LEARNING TECHNIQUES FOR THE CLASSIFICATION \nAND PREDICTION OF DETRIMENTAL NEURAL PATTERNS\n\nKaggle Persistent Training with Auto-Download Links\n\nHOW IT WORKS:\n1. Trains 2 epochs\n2. Automatically creates download links for checkpoints\n3. Download files to your computer\n4. Next session: Upload files back\n5. Automatically resumes from checkpoint!\n\nINSTRUCTIONS:\n- First run: Just run this cell\n- Later runs: Upload checkpoint files first, then run\n\"\"\"\n\nprint(\"=\" * 80)\nprint(\"LEVERAGING MACHINE LEARNING TECHNIQUES\")\nprint(\"FOR THE CLASSIFICATION AND PREDICTION OF\")\nprint(\"DETRIMENTAL NEURAL PATTERNS\")\nprint(\"=\" * 80)\nprint(\"Kaggle Training with Checkpoint Persistence\")\nprint(\"=\" * 80)\n\n# ============================================================================\n# Setup\n# ============================================================================\nprint(\"\\n[1/11] Importing libraries...\")\n\nimport os\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nimport pickle\nimport gc\nfrom tqdm.auto import tqdm\nimport cv2\nfrom datetime import datetime\nimport shutil\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\n# Install timm if needed\ntry:\n    import timm\nexcept:\n    import subprocess\n    import sys\n    subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"timm==0.9.12\"])\n    import timm\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"✓ Device: {device}\")\n\ndef set_seed(seed=42):\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nset_seed(42)\nprint(\"✓ Libraries imported\")\n\n# ============================================================================\n# Config\n# ============================================================================\nprint(\"\\n[2/11] Setting up configuration...\")\n\nclass Config:\n    BASE_PATH = Path('/kaggle/input/hms-harmful-brain-activity-classification')\n    OUTPUT_DIR = Path('/kaggle/working')\n    OUTPUT_DIR.mkdir(exist_ok=True, parents=True)\n    \n    MODEL_NAME = 'tf_efficientnetv2_m'\n    IMG_SIZE = 512\n    NUM_CLASSES = 6\n    \n    TARGET_COLS = ['seizure_vote', 'lpd_vote', 'gpd_vote', \n                   'lrda_vote', 'grda_vote', 'other_vote']\n    CLASS_NAMES = ['seizure', 'lpd', 'gpd', 'lrda', 'grda', 'other']\n    \n    BATCH_SIZE = 16\n    NUM_WORKERS = 0\n    EPOCHS_PER_RUN = 2\n    TOTAL_EPOCHS = 20\n    LEARNING_RATE = 1e-4\n    MIN_LR = 1e-6\n    WEIGHT_DECAY = 1e-5\n    PATIENCE = 5\n    \n    DEVICE = device\n    USE_AMP = True\n\nconfig = Config()\nprint(f\"✓ Configuration set\")\n\n# ============================================================================\n# Check for Uploaded Checkpoints\n# ============================================================================\nprint(\"\\n[3/11] Checking for uploaded checkpoint files...\")\n\n# Look for checkpoints in /kaggle/input/ (uploaded files)\ninput_dirs = list(Path('/kaggle/input').glob('*checkpoint*'))\nuploaded_checkpoint = None\n\nfor input_dir in input_dirs:\n    checkpoint_files = list(input_dir.glob('checkpoint_epoch_*.pth'))\n    if checkpoint_files:\n        uploaded_checkpoint = max(checkpoint_files, key=lambda x: int(x.stem.split('_')[-1]))\n        print(f\"✓ Found uploaded checkpoint: {uploaded_checkpoint.name}\")\n        # Copy to working directory\n        shutil.copy(uploaded_checkpoint, config.OUTPUT_DIR / uploaded_checkpoint.name)\n        \n        # Also copy history if exists\n        history_file = input_dir / 'training_history.pkl'\n        if history_file.exists():\n            shutil.copy(history_file, config.OUTPUT_DIR / 'training_history.pkl')\n        break\n\nif not uploaded_checkpoint:\n    print(\"  No uploaded checkpoint found - starting fresh training\")\n    print(\"  (This is normal for the first run!)\")\n\n# ============================================================================\n# Load Data\n# ============================================================================\nprint(\"\\n[4/11] Loading dataset...\")\n\ntrain_df = pd.read_csv(config.BASE_PATH / 'train.csv')\n\nvote_sums = train_df[config.TARGET_COLS].sum(axis=1)\nprobabilities = train_df[config.TARGET_COLS].div(vote_sums, axis=0)\nprobabilities = probabilities.fillna(1.0 / config.NUM_CLASSES)\ntrain_df['target_probs'] = list(probabilities.values)\ntrain_df['target_label'] = train_df[config.TARGET_COLS].values.argmax(axis=1)\n\nunique_patients = train_df['patient_id'].unique()\nnp.random.seed(42)\nnp.random.shuffle(unique_patients)\nn_val_patients = int(len(unique_patients) * 0.2)\nval_patients = unique_patients[:n_val_patients]\n\ntrain_df['fold'] = 0\ntrain_df.loc[train_df['patient_id'].isin(val_patients), 'fold'] = 1\n\nprint(f\"✓ Loaded {len(train_df):,} samples\")\n\n# ============================================================================\n# Dataset\n# ============================================================================\nprint(\"\\n[5/11] Creating datasets...\")\n\nclass NeuralPatternsDataset(Dataset):\n    \"\"\"Dataset for Detrimental Neural Patterns Classification\"\"\"\n    def __init__(self, df, mode='train'):\n        self.df = df.reset_index(drop=True)\n        self.mode = mode\n        self.spec_path = config.BASE_PATH / 'train_spectrograms'\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        spec_id = row['spectrogram_id']\n        filepath = self.spec_path / f\"{spec_id}.parquet\"\n        spec_df = pd.read_parquet(filepath)\n        \n        montages = ['LL', 'RL', 'LP', 'RP']\n        channels = []\n        for montage in montages:\n            cols = [c for c in spec_df.columns if c.startswith(montage)]\n            data = spec_df[cols].values\n            channels.append(data)\n        \n        spec = np.stack(channels, axis=-1).astype(np.float32)\n        spec = np.nan_to_num(spec, 0.0)\n        \n        spec_norm = np.zeros_like(spec)\n        for i in range(4):\n            ch = spec[:, :, i]\n            min_val, max_val = ch.min(), ch.max()\n            if max_val > min_val:\n                spec_norm[:, :, i] = (ch - min_val) / (max_val - min_val)\n        \n        if self.mode == 'train' and np.random.rand() < 0.5:\n            spec_norm = np.fliplr(spec_norm)\n        \n        spec_resized = cv2.resize(spec_norm, (config.IMG_SIZE, config.IMG_SIZE))\n        spec_tensor = torch.from_numpy(spec_resized).permute(2, 0, 1)\n        spec_tensor = (spec_tensor - 0.5) / 0.5\n        \n        target = torch.tensor(row['target_probs'], dtype=torch.float32)\n        \n        return spec_tensor, target\n\ntrain_dataset = NeuralPatternsDataset(train_df[train_df['fold']==0], mode='train')\nvalid_dataset = NeuralPatternsDataset(train_df[train_df['fold']==1], mode='valid')\n\ntrain_loader = DataLoader(train_dataset, batch_size=config.BATCH_SIZE,\n                          shuffle=True, num_workers=config.NUM_WORKERS, pin_memory=True)\nvalid_loader = DataLoader(valid_dataset, batch_size=config.BATCH_SIZE,\n                          shuffle=False, num_workers=config.NUM_WORKERS, pin_memory=True)\n\nprint(f\"✓ Datasets created\")\n\n# ============================================================================\n# Model\n# ============================================================================\nprint(\"\\n[6/11] Building model...\")\n\nclass EfficientNetModel(nn.Module):\n    def __init__(self, model_name='tf_efficientnetv2_m', pretrained=True):\n        super().__init__()\n        \n        self.model = timm.create_model(model_name, pretrained=pretrained,\n                                       num_classes=config.NUM_CLASSES, in_chans=3)\n        \n        original_conv = self.model.conv_stem\n        self.model.conv_stem = nn.Conv2d(4, original_conv.out_channels,\n                                         kernel_size=original_conv.kernel_size,\n                                         stride=original_conv.stride,\n                                         padding=original_conv.padding, bias=False)\n        \n        with torch.no_grad():\n            weight = original_conv.weight\n            new_weight = weight.mean(dim=1, keepdim=True).repeat(1, 4, 1, 1)\n            self.model.conv_stem.weight = nn.Parameter(new_weight)\n        \n        num_features = self.model.classifier.in_features\n        self.model.classifier = nn.Sequential(\n            nn.Dropout(0.3), nn.Linear(num_features, 512),\n            nn.BatchNorm1d(512), nn.ReLU(),\n            nn.Dropout(0.3), nn.Linear(512, config.NUM_CLASSES)\n        )\n    \n    def forward(self, x):\n        x = self.model(x)\n        return F.softmax(x, dim=1)\n\n# ============================================================================\n# Load Checkpoint if Available\n# ============================================================================\nprint(\"\\n[7/11] Loading model and checkpoint...\")\n\ncheckpoint_files = list(config.OUTPUT_DIR.glob('checkpoint_epoch_*.pth'))\n\nif checkpoint_files:\n    latest_checkpoint = max(checkpoint_files, key=lambda x: int(x.stem.split('_')[-1]))\n    start_epoch = int(latest_checkpoint.stem.split('_')[-1])\n    \n    print(f\"✓ Resuming from: {latest_checkpoint.name}\")\n    \n    checkpoint = torch.load(latest_checkpoint, map_location=config.DEVICE)\n    \n    model = EfficientNetModel(config.MODEL_NAME, pretrained=False)\n    model.load_state_dict(checkpoint['model_state_dict'])\n    model = model.to(config.DEVICE)\n    \n    class KLDivLoss(nn.Module):\n        def forward(self, pred, target):\n            eps = 1e-7\n            pred = torch.clamp(pred, eps, 1.0)\n            target = torch.clamp(target, eps, 1.0)\n            return torch.mean(torch.sum(target * torch.log(target / pred), dim=1))\n    \n    criterion = KLDivLoss()\n    optimizer = torch.optim.AdamW(model.parameters(), lr=config.LEARNING_RATE,\n                                   weight_decay=config.WEIGHT_DECAY)\n    optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n    \n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, \n                                                             T_max=config.TOTAL_EPOCHS, \n                                                             eta_min=config.MIN_LR)\n    scheduler.load_state_dict(checkpoint['scheduler_state_dict'])\n    \n    best_val_loss = checkpoint['best_val_loss']\n    history = checkpoint['history']\n    \n    print(f\"  Epoch: {start_epoch}/{config.TOTAL_EPOCHS}\")\n    print(f\"  Best accuracy: {max(history['val_acc'])*100:.2f}%\")\n    \nelse:\n    print(\"✓ Starting fresh training\")\n    \n    start_epoch = 0\n    \n    model = EfficientNetModel(config.MODEL_NAME, pretrained=True)\n    model = model.to(config.DEVICE)\n    \n    class KLDivLoss(nn.Module):\n        def forward(self, pred, target):\n            eps = 1e-7\n            pred = torch.clamp(pred, eps, 1.0)\n            target = torch.clamp(target, eps, 1.0)\n            return torch.mean(torch.sum(target * torch.log(target / pred), dim=1))\n    \n    criterion = KLDivLoss()\n    optimizer = torch.optim.AdamW(model.parameters(), lr=config.LEARNING_RATE,\n                                   weight_decay=config.WEIGHT_DECAY)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, \n                                                             T_max=config.TOTAL_EPOCHS,\n                                                             eta_min=config.MIN_LR)\n    \n    best_val_loss = float('inf')\n    history = {'train_loss': [], 'val_loss': [], 'val_acc': [], 'lr': []}\n\nscaler = torch.cuda.amp.GradScaler(enabled=config.USE_AMP)\n\ntotal_params = sum(p.numel() for p in model.parameters())\nprint(f\"✓ Model ready ({total_params/1e6:.1f}M parameters)\")\n\n# ============================================================================\n# Training Functions\n# ============================================================================\nprint(\"\\n[8/11] Preparing training...\")\n\ndef train_epoch(model, loader, criterion, optimizer, scaler, device):\n    model.train()\n    running_loss = 0.0\n    pbar = tqdm(loader, desc='Training')\n    \n    for images, targets in pbar:\n        images, targets = images.to(device), targets.to(device)\n        optimizer.zero_grad()\n        \n        with torch.cuda.amp.autocast(enabled=config.USE_AMP):\n            outputs = model(images)\n            loss = criterion(outputs, targets)\n        \n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        \n        running_loss += loss.item()\n        pbar.set_postfix({'loss': f'{loss.item():.4f}'})\n    \n    return running_loss / len(loader)\n\ndef validate(model, loader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    all_preds, all_targets = [], []\n    \n    with torch.no_grad():\n        for images, targets in tqdm(loader, desc='Validation', leave=False):\n            images, targets = images.to(device), targets.to(device)\n            \n            with torch.cuda.amp.autocast(enabled=config.USE_AMP):\n                outputs = model(images)\n                loss = criterion(outputs, targets)\n            \n            running_loss += loss.item()\n            all_preds.append(outputs.cpu().numpy())\n            all_targets.append(targets.cpu().numpy())\n    \n    loss = running_loss / len(loader)\n    preds = np.concatenate(all_preds)\n    targets = np.concatenate(all_targets)\n    \n    pred_labels = preds.argmax(axis=1)\n    true_labels = targets.argmax(axis=1)\n    accuracy = (pred_labels == true_labels).mean()\n    \n    return loss, accuracy\n\nprint(\"✓ Training ready\")\n\n# ============================================================================\n# Training Loop\n# ============================================================================\nprint(\"\\n[9/11] Starting training...\")\nprint(\"=\" * 80)\nprint(f\"Training epochs {start_epoch + 1} to {min(start_epoch + config.EPOCHS_PER_RUN, config.TOTAL_EPOCHS)}\")\nprint(\"=\" * 80)\n\npatience_counter = 0\nstart_time = datetime.now()\n\nfor epoch in range(start_epoch, min(start_epoch + config.EPOCHS_PER_RUN, config.TOTAL_EPOCHS)):\n    print(f\"\\n{'='*80}\")\n    print(f\"Epoch {epoch+1}/{config.TOTAL_EPOCHS}\")\n    print(f\"Learning Rate: {optimizer.param_groups[0]['lr']:.6f}\")\n    print('='*80)\n    \n    train_loss = train_epoch(model, train_loader, criterion, optimizer, scaler, config.DEVICE)\n    val_loss, val_acc = validate(model, valid_loader, criterion, config.DEVICE)\n    scheduler.step()\n    \n    history['train_loss'].append(train_loss)\n    history['val_loss'].append(val_loss)\n    history['val_acc'].append(val_acc)\n    history['lr'].append(optimizer.param_groups[0]['lr'])\n    \n    print(f\"\\n{'='*80}\")\n    print(f\"Results - Epoch {epoch+1}/{config.TOTAL_EPOCHS}\")\n    print(f\"{'='*80}\")\n    print(f\"  Train Loss:      {train_loss:.4f}\")\n    print(f\"  Val Loss:        {val_loss:.4f}\")\n    print(f\"  Val Accuracy:    {val_acc:.4f} ({val_acc*100:.2f}%)\")\n    print(f\"  Best Val Loss:   {best_val_loss:.4f}\")\n    \n    # Save checkpoint\n    checkpoint = {\n        'epoch': epoch + 1,\n        'model_state_dict': model.state_dict(),\n        'optimizer_state_dict': optimizer.state_dict(),\n        'scheduler_state_dict': scheduler.state_dict(),\n        'best_val_loss': best_val_loss,\n        'history': history,\n    }\n    \n    checkpoint_path = config.OUTPUT_DIR / f'checkpoint_epoch_{epoch+1}.pth'\n    torch.save(checkpoint, checkpoint_path)\n    print(f\"  💾 Checkpoint saved: {checkpoint_path.name}\")\n    \n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        patience_counter = 0\n        best_model_path = config.OUTPUT_DIR / 'best_model.pth'\n        torch.save(model.state_dict(), best_model_path)\n        print(f\"  🎉 Best model saved! (Accuracy: {val_acc*100:.2f}%)\")\n    else:\n        patience_counter += 1\n        print(f\"  No improvement ({patience_counter}/{config.PATIENCE})\")\n    \n    # Save history\n    history_path = config.OUTPUT_DIR / 'training_history.pkl'\n    with open(history_path, 'wb') as f:\n        pickle.dump(history, f)\n\n# ============================================================================\n# Create Download Links\n# ============================================================================\nprint(\"\\n[10/11] Creating download links...\")\n\nfrom IPython.display import FileLink, display, HTML\n\ncompleted_epochs = len(history['train_loss'])\nlatest_checkpoint = config.OUTPUT_DIR / f'checkpoint_epoch_{completed_epochs}.pth'\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"📥 DOWNLOAD THESE FILES (Click the links below):\")\nprint(\"=\" * 80)\n\nif latest_checkpoint.exists():\n    print(f\"\\n1. Latest Checkpoint (Epoch {completed_epochs}):\")\n    display(FileLink(str(latest_checkpoint)))\n\nbest_model_path = config.OUTPUT_DIR / 'best_model.pth'\nif best_model_path.exists():\n    print(f\"\\n2. Best Model:\")\n    display(FileLink(str(best_model_path)))\n\nhistory_path = config.OUTPUT_DIR / 'training_history.pkl'\nif history_path.exists():\n    print(f\"\\n3. Training History:\")\n    display(FileLink(str(history_path)))\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"💡 IMPORTANT: Click and download ALL files above!\")\nprint(\"=\" * 80)\n\n# ============================================================================\n# Summary & Instructions\n# ============================================================================\nprint(\"\\n[11/11] Session summary...\")\nprint(\"=\" * 80)\nprint(\"SESSION COMPLETE!\")\nprint(\"=\" * 80)\n\nremaining_epochs = config.TOTAL_EPOCHS - completed_epochs\nelapsed_time = (datetime.now() - start_time).total_seconds() / 3600\n\nprint(f\"\\n📊 Progress:\")\nprint(f\"  Completed: {completed_epochs}/{config.TOTAL_EPOCHS} epochs\")\nprint(f\"  Best accuracy: {max(history['val_acc'])*100:.2f}%\")\nprint(f\"  Latest accuracy: {history['val_acc'][-1]*100:.2f}%\")\nprint(f\"  Session time: {elapsed_time:.1f}h\")\n\nif remaining_epochs > 0:\n    sessions_needed = (remaining_epochs + config.EPOCHS_PER_RUN - 1) // config.EPOCHS_PER_RUN\n    \n    print(f\"\\n\" + \"=\" * 80)\n    print(\"📋 TO CONTINUE IN NEXT SESSION:\")\n    print(\"=\" * 80)\n    print(f\"\\n1. Download the checkpoint file (link above)\")\n    print(f\"2. Create a NEW Kaggle Dataset:\")\n    print(f\"   - Go to: https://www.kaggle.com/datasets\")\n    print(f\"   - Click 'New Dataset'\")\n    print(f\"   - Upload the checkpoint file\")\n    print(f\"   - Name it: 'neural-patterns-checkpoint-{completed_epochs}'\")\n    print(f\"   - Make it public\")\n    print(f\"\\n3. In your NEXT notebook:\")\n    print(f\"   - Add that dataset as input\")\n    print(f\"   - Run this cell again\")\n    print(f\"   - It will auto-resume from epoch {completed_epochs}!\")\n    print(f\"\\n4. Repeat {sessions_needed} more times to reach 20 epochs\")\nelse:\n    print(f\"\\n🎉 TRAINING COMPLETE!\")\n    print(f\"  Final accuracy: {history['val_acc'][-1]*100:.2f}%\")\n    print(f\"  Best accuracy: {max(history['val_acc'])*100:.2f}%\")\n\n# Plot\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\naxes[0].plot(history['train_loss'], label='Train', marker='o')\naxes[0].plot(history['val_loss'], label='Val', marker='s')\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Loss')\naxes[0].set_title('Detrimental Neural Patterns - Training Progress')\naxes[0].legend()\naxes[0].grid(True, alpha=0.3)\n\naxes[1].plot([x*100 for x in history['val_acc']], marker='o', color='green')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('Accuracy (%)')\naxes[1].set_title(f'Validation Accuracy (Best: {max(history[\"val_acc\"])*100:.1f}%)')\naxes[1].grid(True, alpha=0.3)\n\nplt.tight_layout()\nplot_path = config.OUTPUT_DIR / 'training_progress.png'\nplt.savefig(plot_path, dpi=100)\nplt.show()\n\nprint(f\"\\n4. Training plot:\")\ndisplay(FileLink(str(plot_path)))\n\ngc.collect()\ntorch.cuda.empty_cache()\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"✅ Session Complete!\")\nprint(\"   Detrimental Neural Patterns Classification\")\nprint(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T13:42:58.713043Z","iopub.execute_input":"2025-10-08T13:42:58.713786Z","iopub.status.idle":"2025-10-08T17:05:09.871875Z","shell.execute_reply.started":"2025-10-08T13:42:58.713757Z","shell.execute_reply":"2025-10-08T17:05:09.870669Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Quick download before session ends\nimport os\nfrom pathlib import Path\n\n# List files\nprint(\"Files in /kaggle/working:\")\nfiles = list(Path('/kaggle/working').glob('*.pth')) + list(Path('/kaggle/working').glob('*.pkl'))\nfor f in files:\n    print(f\"  ✓ {f.name} ({f.stat().st_size / 1e6:.1f} MB)\")\n\n# For Kaggle, files auto-save as \"output files\"\n# They'll be in the version's output when you commit\nprint(\"\\n💡 TIP: Files are saved in this session's output\")\nprint(\"   They'll be available after you 'Save Version'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T18:59:18.205488Z","iopub.execute_input":"2025-10-08T18:59:18.206183Z","iopub.status.idle":"2025-10-08T18:59:18.218359Z","shell.execute_reply.started":"2025-10-08T18:59:18.206150Z","shell.execute_reply":"2025-10-08T18:59:18.217217Z"}},"outputs":[],"execution_count":null}]}