{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":406481,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":332149,"modelId":353069}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport sys\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport pandas as pd\nimport numpy as np\nimport torchaudio\nimport torchaudio.transforms as AT\nimport timm\nimport random\nfrom tqdm import tqdm\nimport concurrent.futures\nimport gc\nfrom typing import Union\nimport time\n\n\ndef apply_power_to_low_ranked_cols(\n    p: np.ndarray,\n    top_k: int = 30,\n    exponent: Union[int, float] = 2,\n    inplace: bool = True\n) -> np.ndarray:\n    \"\"\"\n    Rank columns by their column‑wise maximum and raise every column whose\n    rank falls below `top_k` to a given power.\n    \"\"\"\n    if not inplace:\n        p = p.copy()\n\n    # Identify columns whose max value ranks below `top_k`\n    tail_cols = np.argsort(-p.max(axis=0))[top_k:]\n\n    # Apply the power transformation to those columns\n    p[:, tail_cols] = p[:, tail_cols] ** exponent\n    return p\n\n\ndef normalize_std(spec, eps=1e-6):\n    \"\"\"Normalize spectrogram by standard deviation\"\"\"\n    mean = torch.mean(spec)\n    std = torch.std(spec)\n    return torch.where(std == 0, spec-mean, (spec - mean) / (std+eps))\n\n\nclass BirdCLEFModel(nn.Module):\n    def __init__(self, cfg):\n        super().__init__()\n        self.cfg = cfg\n        \n        taxonomy_df = pd.read_csv(cfg.taxonomy_csv)\n        cfg.num_classes = len(taxonomy_df)\n        \n        # For Kaggle: create model with or without pretrained weights\n        print(f\"Creating model: {cfg.model_name}\")\n        try:\n            self.backbone = timm.create_model(\n                cfg.model_name,\n                pretrained=cfg.pretrained,\n                in_chans=cfg.in_channels,\n                drop_rate=0.2,  # Lower dropout for MobileNetV3\n                drop_path_rate=0.2  # Lower stochastic depth for MobileNetV3\n            )\n            print(f\"Successfully created {cfg.model_name}\")\n            # Print available methods and attributes for debugging\n            print(f\"Model structure: {type(self.backbone)}\")\n            if hasattr(self.backbone, 'classifier'):\n                print(f\"Classifier: {self.backbone.classifier}\")\n        except Exception as e:\n            print(f\"Error creating model: {e}\")\n            # Try alternative model name formats\n            alternative_names = [\n                'mobilenetv3_small.100_in1k',  # Alternative name in newer timm\n                'tf_mobilenetv3_small_100',    # TF variant\n                'mobilenetv3_small'            # Simplified name\n            ]\n            for alt_name in alternative_names:\n                try:\n                    print(f\"Trying alternative model name: {alt_name}\")\n                    self.backbone = timm.create_model(\n                        alt_name,\n                        pretrained=False,\n                        in_chans=cfg.in_channels\n                    )\n                    # Update config to match successful model\n                    cfg.model_name = alt_name\n                    print(f\"Successfully created {alt_name}\")\n                    break\n                except Exception as e2:\n                    print(f\"Error with {alt_name}: {e2}\")\n        \n        # Load pretrained weights from local file if specified\n        if not cfg.pretrained and cfg.pretrained_weights:\n            print(f\"Loading pretrained weights from: {cfg.pretrained_weights}\")\n            try:\n                state_dict = torch.load(cfg.pretrained_weights, map_location='cpu',weights_only=True)\n                # Handle case where state_dict might contain 'model' or 'state_dict' key\n                if 'model' in state_dict:\n                    state_dict = state_dict['model']\n                elif 'state_dict' in state_dict:\n                    state_dict = state_dict['state_dict']\n                \n                # Remove prefix if it exists (like 'backbone.')\n                if all(k.startswith('backbone.') for k in state_dict if k not in ['cls_token', 'pos_embed']):\n                    state_dict = {k.replace('backbone.', ''): v for k, v in state_dict.items()}\n                \n                # Remove classifier weights\n                for k in list(state_dict.keys()):\n                    if 'classifier' in k or 'fc' in k or 'head' in k:\n                        del state_dict[k]\n                \n                self.backbone.load_state_dict(state_dict, strict=False)\n                print(\"Successfully loaded pretrained weights\")\n            except Exception as e:\n                print(f\"Error loading pretrained weights: {e}\")\n        \n        # Debug available classifier structures\n        print(f\"Available attributes: {dir(self.backbone)}\")\n        \n        try:\n            if 'efficientnet' in cfg.model_name:\n                backbone_out = self.backbone.classifier.in_features\n                self.backbone.classifier = nn.Identity()\n                print(f\"Using EfficientNet classifier with {backbone_out} features\")\n            elif 'resnet' in cfg.model_name:\n                backbone_out = self.backbone.fc.in_features\n                self.backbone.fc = nn.Identity()\n                print(f\"Using ResNet classifier with {backbone_out} features\")\n            elif 'mobilenetv3' in cfg.model_name:\n                # MobileNetV3 classifier structure can vary between timm versions\n                if hasattr(self.backbone, 'classifier') and hasattr(self.backbone.classifier, 'in_features'):\n                    backbone_out = self.backbone.classifier.in_features\n                    self.backbone.classifier = nn.Identity()\n                    print(f\"Using MobileNetV3 standard classifier with {backbone_out} features\")\n                elif hasattr(self.backbone, 'classifier') and isinstance(self.backbone.classifier, nn.Sequential):\n                    # For MobileNetV3 with sequential classifier\n                    backbone_out = 0  # Initialize before loop\n                    for module in self.backbone.classifier:\n                        if isinstance(module, nn.Linear):\n                            backbone_out = module.in_features\n                            break\n                    if backbone_out == 0:\n                        backbone_out = 1280  # Default for MobileNetV3 small\n                    self.backbone.classifier = nn.Identity()\n                    print(f\"Using MobileNetV3 sequential classifier with {backbone_out} features\")\n                elif hasattr(self.backbone, 'head') and hasattr(self.backbone.head, 'fc'):\n                    backbone_out = self.backbone.head.fc.in_features\n                    self.backbone.head.fc = nn.Identity()\n                    print(f\"Using MobileNetV3 head.fc with {backbone_out} features\")\n                else:\n                    # Fallback to typical mobilenetv3 small dimension\n                    backbone_out = 1280  # Standard size for MobileNetV3 Small\n                    if hasattr(self.backbone, 'classifier'):\n                        self.backbone.classifier = nn.Identity()\n                    print(f\"Using fallback MobileNetV3 feature dimension: {backbone_out}\")\n            else:\n                # Try to get classifier info for other models\n                print(\"Using generic classifier detection\")\n                if hasattr(self.backbone, 'get_classifier') and callable(getattr(self.backbone, 'get_classifier')):\n                    backbone_out = self.backbone.get_classifier().in_features\n                    self.backbone.reset_classifier(0, '')\n                else:\n                    # Last resort - find any linear layer as a hint\n                    backbone_out = 0\n                    for name, module in self.backbone.named_modules():\n                        if isinstance(module, nn.Linear):\n                            backbone_out = module.in_features\n                            print(f\"Found linear layer with {backbone_out} features: {name}\")\n                            # Don't break, we want the last one\n                    \n                    if backbone_out == 0:\n                        backbone_out = 1280  # Default fallback\n                    print(f\"Using fallback feature dimension: {backbone_out}\")\n        except Exception as e:\n            print(f\"Error setting up classifier: {e}\")\n            # Fallback to a reasonable size for MobileNetV3\n            backbone_out = 1280\n            print(f\"Using emergency fallback feature dimension: {backbone_out}\")\n        \n        self.pooling = nn.AdaptiveAvgPool2d(1)\n        \n        # Add attention mechanism\n        self.attention = nn.Sequential(\n            nn.Conv2d(backbone_out, backbone_out // 16, kernel_size=1),\n            nn.SiLU(),\n            nn.Conv2d(backbone_out // 16, backbone_out, kernel_size=1),\n            nn.Sigmoid()\n        )\n            \n        self.feat_dim = backbone_out\n        \n        # Add multi-sample dropout for better generalization\n        self.dropouts = nn.ModuleList([\n            nn.Dropout(0.3) for _ in range(5)\n        ])\n        \n        self.classifier = nn.Linear(backbone_out, cfg.num_classes)\n        \n        self.mixup_enabled = hasattr(cfg, 'mixup_alpha') and cfg.mixup_alpha > 0\n        self.cutmix_enabled = hasattr(cfg, 'cutmix_alpha') and cfg.cutmix_alpha > 0\n        \n        if self.mixup_enabled:\n            self.mixup_alpha = cfg.mixup_alpha\n        if self.cutmix_enabled:\n            self.cutmix_alpha = cfg.cutmix_alpha\n            \n    def forward(self, x, targets=None):\n        b = x.size(0)\n        \n        # Apply mixup or cutmix during training\n        if self.training and targets is not None:\n            if self.mixup_enabled and self.cutmix_enabled:\n                # Randomly choose between mixup and cutmix\n                if random.random() < 0.5:\n                    x, targets_a, targets_b, lam = self.mixup_data(x, targets)\n                else:\n                    x, targets_a, targets_b, lam = self.cutmix_data(x, targets)\n            elif self.mixup_enabled:\n                x, targets_a, targets_b, lam = self.mixup_data(x, targets)\n            elif self.cutmix_enabled:\n                x, targets_a, targets_b, lam = self.cutmix_data(x, targets)\n            else:\n                targets_a, targets_b, lam = targets, targets, 1.0\n        else:\n            targets_a, targets_b, lam = None, None, None\n        \n        features = self.backbone(x)\n        \n        # Handle different output formats from different backbones\n        if isinstance(features, dict):\n            features = features['features']\n        \n        # For MobileNetV3 and other models, ensure we have 4D tensor for attention\n        # If features is already flattened (2D), reshape it to 4D for attention\n        if len(features.shape) == 2:\n            # Create pseudo spatial dimensions\n            features = features.unsqueeze(-1).unsqueeze(-1)\n            \n        # Now features should be 4D, apply attention mechanism\n        att = self.attention(features)\n        features = features * att\n        \n        # Pool and flatten\n        features = self.pooling(features)\n        features = features.view(b, -1)\n        \n        # Multi-sample dropout for robust training\n        if self.training:\n            logits = torch.zeros(b, self.cfg.num_classes).to(features.device)\n            for dropout in self.dropouts:\n                logits += self.classifier(dropout(features))\n            logits /= len(self.dropouts)\n        else:\n            logits = self.classifier(features)\n        \n        if self.training and (self.mixup_enabled or self.cutmix_enabled) and targets is not None:\n            loss = self.mixup_criterion(F.binary_cross_entropy_with_logits, \n                                       logits, targets_a, targets_b, lam)\n            return logits, loss\n            \n        return logits\n    \n    def mixup_data(self, x, targets):\n        \"\"\"Applies mixup to the data batch\"\"\"\n        batch_size = x.size(0)\n\n        lam = np.random.beta(self.mixup_alpha, self.mixup_alpha)\n\n        indices = torch.randperm(batch_size).to(x.device)\n\n        mixed_x = lam * x + (1 - lam) * x[indices]\n        \n        return mixed_x, targets, targets[indices], lam\n    \n    def cutmix_data(self, x, targets):\n        \"\"\"Applies cutmix to the data batch\"\"\"\n        batch_size = x.size(0)\n        lam = np.random.beta(self.cutmix_alpha, self.cutmix_alpha)\n        \n        # Generate random box\n        W, H = x.size(2), x.size(3)\n        cut_ratio = np.sqrt(1.0 - lam)\n        cut_w = int(W * cut_ratio)\n        cut_h = int(H * cut_ratio)\n        \n        # Uniform\n        cx = np.random.randint(W)\n        cy = np.random.randint(H)\n        \n        # Limit box to image\n        bby1 = np.clip(cy - cut_h // 2, 0, H)\n        bbx1 = np.clip(cx - cut_w // 2, 0, W)\n        bby2 = np.clip(cy + cut_h // 2, 0, H)\n        bbx2 = np.clip(cx + cut_w // 2, 0, W)\n        \n        # Random sample\n        rand_index = torch.randperm(batch_size).to(x.device)\n        \n        # Apply cutmix - first verify the indices are valid\n        x_cut = x.clone()\n        \n        # Only apply if the box has valid dimensions\n        if bbx2 > bbx1 and bby2 > bby1:\n            x_cut[:, :, bbx1:bbx2, bby1:bby2] = x[rand_index, :, bbx1:bbx2, bby1:bby2]\n            \n            # Adjust lambda\n            cut_area = (bbx2 - bbx1) * (bby2 - bby1)\n            lam = 1.0 - (cut_area / (W * H))\n        else:\n            print(f\"Warning: Invalid cutmix box dimensions ({bbx1},{bby1})-({bbx2},{bby2})\")\n        \n        return x_cut, targets, targets[rand_index], lam\n    \n    def mixup_criterion(self, criterion, pred, y_a, y_b, lam):\n        \"\"\"Applies mixup to the loss function\"\"\"\n        return lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)\nclass CFG:\n    \n    seed = 42\n    debug = False  \n    apex = False\n    print_freq = 100\n    num_workers = 4  # Increased from 2\n    \n    # Detect environment\n    # Check if we're in Kaggle\n    if os.path.exists('/kaggle/input'):\n        print(\"Running in Kaggle environment\")\n        is_kaggle = True\n        BASE_PATH = '/kaggle/input/birdclef-2025'\n    else:\n        print(\"Running in local environment\")\n        is_kaggle = False\n        # Look for the data in the current directory or parent directory\n        if os.path.exists('./train.csv'):\n            BASE_PATH = '.'\n        elif os.path.exists('../train.csv'):\n            BASE_PATH = '..'\n        else:\n            BASE_PATH = './data'  # Default fallback\n    \n    OUTPUT_DIR = '/kaggle/working/' if is_kaggle else './outputs'\n    \n    # Create output directory if it doesn't exist\n    os.makedirs(OUTPUT_DIR, exist_ok=True)\n\n    train_datadir = f'{BASE_PATH}/train_audio'\n    train_csv = f'{BASE_PATH}/train.csv'\n    test_soundscapes = f'{BASE_PATH}/test_soundscapes'\n    submission_csv = f'{BASE_PATH}/sample_submission.csv'\n    taxonomy_csv = f'{BASE_PATH}/taxonomy.csv'\n    \n    spectrogram_npy = '/kaggle/input/birdclef25-mel-spectrograms/birdclef2025_melspec_5sec_256_256.npy' if is_kaggle else None\n    \n    model_name = 'mobilenetv3_small_050'  # Changed from mobilenetv3_small_100 to match pretrained weights\n    pretrained = False  # Changed to False for Kaggle (offline usage)\n    pretrained_weights = None  # Path to local weights file, set this if you have downloaded weights\n    in_channels = 1\n    \n    LOAD_DATA = True  \n    USE_AMP = True  # Enable mixed precision\n    PIN_MEMORY = True  # Pin memory for faster data loading\n    \n    FS = 32000\n    TARGET_DURATION = 5.0\n    TARGET_SHAPE = (256, 256)\n    \n    N_FFT = 1024\n    HOP_LENGTH = 512\n    N_MELS = 128\n    FMIN = 50\n    FMAX = 14000\n    \n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    epochs = 15  # Increased from 10\n    batch_size = 64  # Increased for MobileNetV3 which is smaller than EfficientNet\n    gradient_accumulation_steps = 1  # Reduced since MobileNetV3 is more memory efficient\n    criterion = 'BCEWithLogitsLoss'\n\n    n_fold = 5\n    selected_folds = [0, 1, 2, 3, 4]   \n\n    optimizer = 'AdamW'\n    lr = 2e-4  # Slightly higher learning rate for MobileNetV3 which converges faster\n    weight_decay = 5e-5  # Reduced for MobileNetV3 to prevent overfitting\n  \n    scheduler = 'CosineAnnealingWarmRestarts'  # Changed from CosineAnnealingLR\n    min_lr = 1e-6\n    T_0 = 5  # For CosineAnnealingWarmRestarts\n    T_mult = 1  # For CosineAnnealingWarmRestarts\n\n    aug_prob = 0.7  # Increased from 0.5\n    mixup_alpha = 0.4\n    cutmix_alpha = 0.4  # Added cutmix\n    \n    def update_debug_settings(self):\n        if self.debug:\n            self.epochs = 2\n            self.selected_folds = [0]\n\n\n# --- Config ve model yükleme ---\ncfg = CFG()\ncfg.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n# Create mel spectrogram transformer\nmel_spectrogram = AT.MelSpectrogram(\n    sample_rate=cfg.FS,\n    n_fft=cfg.N_FFT,\n    win_length=cfg.N_FFT,\n    hop_length=cfg.HOP_LENGTH,\n    center=True,\n    f_min=cfg.FMIN,\n    f_max=cfg.FMAX,\n    pad_mode=\"reflect\",\n    power=2.0,\n    norm='slaney',\n    n_mels=cfg.N_MELS,\n    mel_scale=\"htk\",\n)\n\n# Improved audio to mel spectrogram conversion\ndef audio_to_mel(filepath):\n    \"\"\"Convert audio file to mel spectrogram tensors for all segments at once\"\"\"\n    waveform, _ = torchaudio.load(filepath, backend=\"soundfile\")\n    len_wav = waveform.shape[1]\n    waveform = waveform[0,:].reshape(1, len_wav)  # stereo->mono or mono->mono\n    \n    # Process all 12 segments at once\n    segments = []\n    for i in range(12):\n        start_idx = i * cfg.FS * 5\n        end_idx = start_idx + cfg.FS * 5\n        \n        # Handle case where audio might be shorter than expected\n        if end_idx > len_wav:\n            if start_idx < len_wav:\n                # Pad the last segment\n                segment = waveform[:, start_idx:len_wav]\n                padding = end_idx - len_wav\n                segment = F.pad(segment, (0, padding))\n            else:\n                # Create zeros if we're completely past the end\n                segment = torch.zeros((1, cfg.FS * 5))\n        else:\n            segment = waveform[:, start_idx:end_idx]\n            \n        # Generate mel spectrogram for this segment\n        melspec = mel_spectrogram(segment)\n        melspec = torch.log(melspec + 1e-6)\n        melspec = normalize_std(melspec)\n        melspec = torch.unsqueeze(melspec, dim=0)  # Add batch dimension\n        \n        segments.append(melspec)\n    \n    # Stack all segments into a single batch tensor\n    return torch.vstack(segments)\n\n# Model checkpoint dosyasını seç (ör: model_fold0.pth)\nMODEL_PATH = '/kaggle/input/gemefficson/pytorch/default/1/tf_efficientnetv2_m.in21k_ft_in1k_fold0_final.pth'  # Gerekirse değiştir\n\n# Taxonomy ve class listesi\nspecies_ids = pd.read_csv(cfg.taxonomy_csv)['primary_label'].tolist()\ncfg.num_classes = len(species_ids)\n\n# Modeli yükle\nmodel = BirdCLEFModel(cfg)\nprint(f\"Loading checkpoint from: {MODEL_PATH}\")\ntry:\n    checkpoint = torch.load(MODEL_PATH, map_location=cfg.device)\n    # Handle different checkpoint formats\n    if isinstance(checkpoint, dict):\n        if 'model_state_dict' in checkpoint:\n            state_dict = checkpoint['model_state_dict']\n        elif 'state_dict' in checkpoint:\n            state_dict = checkpoint['state_dict']\n        else:\n            state_dict = checkpoint\n    else:\n        state_dict = checkpoint\n\n    # Remove any prefix in the state_dict keys\n    new_state_dict = {}\n    for k, v in state_dict.items():\n        # Remove _orig_mod. prefix if present\n        if k.startswith('_orig_mod.'):\n            new_key = k[10:]\n        # Remove module. prefix if present (common in DataParallel models)\n        elif k.startswith('module.'):\n            new_key = k[7:]\n        else:\n            new_key = k\n        new_state_dict[new_key] = v\n    \n    # Load the state dict with strict=False to ignore missing keys\n    missing_keys, unexpected_keys = model.load_state_dict(new_state_dict, strict=False)\n    print(f\"Missing keys: {len(missing_keys)}\")\n    print(f\"Unexpected keys: {len(unexpected_keys)}\")\n    print(\"Model loaded successfully with non-strict loading\")\nexcept Exception as e:\n    print(f\"Error loading checkpoint: {e}\")\n    print(\"Continuing with randomly initialized model\")\n\nmodel.to(cfg.device)\nmodel.eval()\n\n# --- Test dosyalarını bul ---\ntest_audio_dir = '../input/birdclef-2025/test_soundscapes/'\nfile_list = [f for f in sorted(os.listdir(test_audio_dir))] if os.path.exists(test_audio_dir) else []\nfile_list = [file.split('.')[0] for file in file_list if file.endswith('.ogg')]\n\ndebug = False\nif len(file_list) == 0:\n    debug = True\n    debug_st_num = 5\n    debug_num = 100\n    test_audio_dir = '../input/birdclef-2025/train_soundscapes/'\n    file_list = [f for f in sorted(os.listdir(test_audio_dir))] if os.path.exists(test_audio_dir) else []\n    file_list = [file.split('.')[0] for file in file_list if file.endswith('.ogg')]\n    file_list = file_list\n\nprint('Debug mode:', debug)\nprint('Number of test soundscapes:', len(file_list))\n\n# --- Submission formatı ---\nsample_sub = pd.read_csv('/kaggle/input/birdclef-2025/sample_submission.csv')\nclass_labels = list(sample_sub.columns)[1:]\n\n# --- Improved prediction function ---\ndef predict_one_file(afile):\n    path = os.path.join(test_audio_dir, afile + '.ogg')\n    \n    try:\n        # Process all 12 segments at once\n        with torch.inference_mode():  # Faster than no_grad\n            # Convert audio to mel spectrograms (returns tensor of shape [12, 1, n_mels, time])\n            mel_batch = audio_to_mel(path)\n            mel_batch = mel_batch.to(cfg.device)\n            \n            # Get model predictions\n            logits = model(mel_batch)\n            probs = torch.sigmoid(logits).cpu().numpy()\n            \n            # Apply post-processing to suppress low confidence predictions\n            probs = apply_power_to_low_ranked_cols(probs, top_k=30, exponent=2)\n            \n        # Clean up GPU memory\n        torch.cuda.empty_cache()\n        return probs\n    \n    except Exception as e:\n        print(f\"Error processing file {afile}: {e}\")\n        return np.zeros((12, cfg.num_classes))\n\n# --- Parallel prediction for all files ---\ndef process_all_files():\n    all_rows = []\n    \n    # Use ThreadPoolExecutor for parallel processing\n    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:\n        results = list(tqdm(executor.map(predict_one_file, file_list), total=len(file_list), desc=\"Processing files\"))\n    \n    # Create submission rows from results\n    for i, afile in enumerate(file_list):\n        preds = results[i]  # Shape: (12, num_classes)\n        for j in range(preds.shape[0]):\n            row_id = f\"{afile}_{(j+1)*5}\"\n            row = [row_id]\n            \n            # Add predictions for each class in the correct order\n            for col in class_labels:\n                if col in species_ids:\n                    idx = species_ids.index(col)\n                    row.append(preds[j, idx])\n                else:\n                    row.append(0.0)\n            \n            all_rows.append(row)\n    \n    return all_rows\n\n# --- Create and save submission ---\nstart_time = time.time()\nrows = process_all_files()\nend_time = time.time()\n\nprint(f\"Total processing time: {end_time - start_time:.2f} seconds\")\n\n# --- DataFrame and save ---\nsub_df = pd.DataFrame(rows, columns=['row_id'] + class_labels)\nsub_name = 'submission.csv'\nsub_df.to_csv(sub_name, index=False)\nprint(f'Submission file created: {sub_name}') ","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}