{"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":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"},{"sourceId":11719803,"sourceType":"datasetVersion","datasetId":7130272},{"sourceId":11870659,"sourceType":"datasetVersion","datasetId":7459867}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### Some useful references:\n1. **[Training]**: https://github.com/LIHANG-HONG/birdclef2023-2nd-place-solution\n2. **[Inference]**: https://www.kaggle.com/code/kadircandrisolu/efficientnet-b0-pytorch-inference-birdclef-25\n\nThis model backbone is seresnext26t_32x4d","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport warnings\nimport logging\nimport time\nimport math\nimport cv2\nfrom pathlib import Path\nimport joblib\n\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport soundfile as sf\nfrom soundfile import SoundFile \nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast, GradScaler\nimport timm\nfrom tqdm.auto import tqdm\nfrom glob import glob\nimport torchaudio\nimport random\nimport itertools\nfrom typing import Union\n\nimport concurrent.futures\n\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:19:15.331758Z","iopub.execute_input":"2025-05-20T09:19:15.332248Z","iopub.status.idle":"2025-05-20T09:19:15.339980Z","shell.execute_reply.started":"2025-05-20T09:19:15.332219Z","shell.execute_reply":"2025-05-20T09:19:15.338942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    \n    seed = 42\n    print_freq = 100\n    num_workers = 4\n\n    stage = 'train_bce'\n\n    train_datadir = '/kaggle/input/birdclef-2025/train_audio'\n    train_csv = '/kaggle/input/birdclef-2025/train.csv'\n    test_soundscapes = '/kaggle/input/birdclef-2025/test_soundscapes'\n    submission_csv = '/kaggle/input/birdclef-2025/sample_submission.csv'\n    taxonomy_csv = '/kaggle/input/birdclef-2025/taxonomy.csv'\n    model_files = ['/kaggle/input/bird2025-sed-ckpt/sedmodel.pth'\n                  ]\n \n    model_name = 'seresnext26t_32x4d'  \n    pretrained = False\n    in_channels = 1\n\n    \n    SR = 32000\n    target_duration = 5\n    train_duration = 10\n    \n    \n    device = 'cpu'\n\ncfg = CFG()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:19:15.341561Z","iopub.execute_input":"2025-05-20T09:19:15.341974Z","iopub.status.idle":"2025-05-20T09:19:15.368498Z","shell.execute_reply.started":"2025-05-20T09:19:15.341941Z","shell.execute_reply":"2025-05-20T09:19:15.367326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Using device: {cfg.device}\")\nprint(f\"Loading taxonomy data...\")\ntaxonomy_df = pd.read_csv(cfg.taxonomy_csv)\nspecies_ids = taxonomy_df['primary_label'].tolist()\nnum_classes = len(species_ids)\nprint(f\"Number of classes: {num_classes}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:19:15.370295Z","iopub.execute_input":"2025-05-20T09:19:15.370656Z","iopub.status.idle":"2025-05-20T09:19:15.400642Z","shell.execute_reply.started":"2025-05-20T09:19:15.370631Z","shell.execute_reply":"2025-05-20T09:19:15.399103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def set_seed(seed=42):\n    \"\"\"\n    Set seed for reproducibility\n    \"\"\"\n    random.seed(seed)\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.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(cfg.seed)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:19:15.401921Z","iopub.execute_input":"2025-05-20T09:19:15.402198Z","iopub.status.idle":"2025-05-20T09:19:15.419501Z","shell.execute_reply.started":"2025-05-20T09:19:15.402177Z","shell.execute_reply":"2025-05-20T09:19:15.418313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AttBlockV2(nn.Module):\n    def __init__(self, in_features: int, out_features: int, activation=\"linear\"):\n        super().__init__()\n\n        self.activation = activation\n        self.att = nn.Conv1d(\n            in_channels=in_features,\n            out_channels=out_features,\n            kernel_size=1,\n            stride=1,\n            padding=0,\n            bias=True,\n        )\n        self.cla = nn.Conv1d(\n            in_channels=in_features,\n            out_channels=out_features,\n            kernel_size=1,\n            stride=1,\n            padding=0,\n            bias=True,\n        )\n\n        self.init_weights()\n\n    def init_weights(self):\n        init_layer(self.att)\n        init_layer(self.cla)\n\n    def forward(self, x):\n        # x: (n_samples, n_in, n_time)\n        norm_att = torch.softmax(torch.tanh(self.att(x)), dim=-1)\n        cla = self.nonlinear_transform(self.cla(x))\n        x = torch.sum(norm_att * cla, dim=2)\n        return x, norm_att, cla\n\n    def nonlinear_transform(self, x):\n        if self.activation == \"linear\":\n            return x\n        elif self.activation == \"sigmoid\":\n            return torch.sigmoid(x)\n\n\ndef init_layer(layer):\n    nn.init.xavier_uniform_(layer.weight)\n\n    if hasattr(layer, \"bias\"):\n        if layer.bias is not None:\n            layer.bias.data.fill_(0.0)\n\ndef init_bn(bn):\n    bn.bias.data.fill_(0.0)\n    bn.weight.data.fill_(1.0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:19:15.421241Z","iopub.execute_input":"2025-05-20T09:19:15.421549Z","iopub.status.idle":"2025-05-20T09:19:15.437643Z","shell.execute_reply.started":"2025-05-20T09:19:15.421525Z","shell.execute_reply":"2025-05-20T09:19:15.435848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nclass BirdCLEFModel(nn.Module):\n    def __init__(self, cfg):\n        super().__init__()\n        self.cfg = cfg\n        \n        taxonomy_df = pd.read_csv('/kaggle/input/birdclef-2025/taxonomy.csv')\n        self.num_classes = len(taxonomy_df)\n\n        self.bn0 = nn.BatchNorm2d(cfg['n_mels'])\n        \n        self.backbone = timm.create_model(\n            cfg['model_name'],\n            pretrained=False,\n            in_chans=cfg['in_channels'],\n            drop_rate=0.2,\n            drop_path_rate=0.2,\n        )\n\n        layers = list(self.backbone.children())[:-2]\n        self.encoder = nn.Sequential(*layers)\n        \n        if \"efficientnet\" in self.cfg['model_name']:\n            backbone_out = self.backbone.classifier.in_features\n        elif \"eca\" in self.cfg['model_name']:\n            backbone_out = self.backbone.head.fc.in_features\n        elif \"res\" in self.cfg['model_name']:\n            backbone_out = self.backbone.fc.in_features\n        else:\n            backbone_out = self.backbone.num_features\n            \n        \n        self.fc1 = nn.Linear(backbone_out, backbone_out, bias=True)\n        self.att_block = AttBlockV2(backbone_out, self.num_classes, activation=\"sigmoid\")\n\n        self.melspec_transform = torchaudio.transforms.MelSpectrogram(\n            sample_rate=self.cfg['SR'],\n            hop_length=self.cfg['hop_length'],\n            n_mels=self.cfg['n_mels'],\n            f_min=self.cfg['f_min'],\n            f_max=self.cfg['f_max'],\n            n_fft=self.cfg['n_fft'],\n            pad_mode=\"constant\",\n            norm=\"slaney\",\n            onesided=True,\n            mel_scale=\"htk\",\n        )\n        if self.cfg['device'] == \"cuda\":\n            self.melspec_transform = self.melspec_transform.cuda()\n        else:\n            self.melspec_transform = self.melspec_transform.cpu()\n\n        self.db_transform = torchaudio.transforms.AmplitudeToDB(\n            stype=\"power\", top_db=80\n        )\n\n\n    def extract_feature(self,x):\n        x = x.permute((0, 1, 3, 2))\n        frames_num = x.shape[2]\n        \n        x = x.transpose(1, 3)\n        x = self.bn0(x)\n        x = x.transpose(1, 3)\n        \n        # if self.training:\n        #    x = self.spec_augmenter(x)\n        \n        x = x.transpose(2, 3)\n        # (batch_size, channels, freq, frames)\n        x = self.encoder(x)\n        \n        # (batch_size, channels, frames)\n        x = torch.mean(x, dim=2)\n        \n        # channel smoothing\n        x1 = F.max_pool1d(x, kernel_size=3, stride=1, padding=1)\n        x2 = F.avg_pool1d(x, kernel_size=3, stride=1, padding=1)\n        x = x1 + x2\n        \n        x = F.dropout(x, p=0.5, training=self.training)\n        x = x.transpose(1, 2)\n        x = F.relu_(self.fc1(x))\n        x = x.transpose(1, 2)\n        x = F.dropout(x, p=0.5, training=self.training)\n        return x, frames_num\n        \n    @torch.cuda.amp.autocast(enabled=False)\n    def transform_to_spec(self, audio):\n\n        audio = audio.float()\n        \n        spec = self.melspec_transform(audio)\n        spec = self.db_transform(spec)\n\n        if self.cfg['normal'] == 80:\n            spec = (spec + 80) / 80\n        elif self.cfg['normal'] == 255:\n            spec = spec / 255\n        else:\n            raise NotImplementedError\n                \n        if self.cfg['in_channels'] == 3:\n            spec = image_delta(spec)\n        \n        return spec\n\n    def forward(self, x):\n\n        with torch.no_grad():\n            x = self.transform_to_spec(x)\n\n        x, frames_num = self.extract_feature(x)\n        \n        (clipwise_output, norm_att, segmentwise_output) = self.att_block(x)\n        logit = torch.sum(norm_att * self.att_block.cla(x), dim=2)\n        segmentwise_logit = self.att_block.cla(x).transpose(1, 2)\n        segmentwise_output = segmentwise_output.transpose(1, 2)\n\n        return torch.logit(clipwise_output)\n\n    def infer(self, x, tta_delta=2):\n        with torch.no_grad():\n            x = self.transform_to_spec(x)\n        x,_ = self.extract_feature(x)\n        time_att = torch.tanh(self.att_block.att(x))\n        feat_time = x.size(-1)\n        start = (\n            feat_time / 2 - feat_time * (self.cfg['infer_duration'] / self.cfg['duration_train']) / 2\n        )\n        end = start + feat_time * (self.cfg['infer_duration'] / self.cfg['duration_train'])\n        start = int(start)\n        end = int(end)\n        pred = self.attention_infer(start,end,x,time_att)\n\n        start_minus = max(0, start-tta_delta)\n        end_minus=end-tta_delta\n        pred_minus = self.attention_infer(start_minus,end_minus,x,time_att)\n\n        start_plus = start+tta_delta\n        end_plus=min(feat_time, end+tta_delta)\n        pred_plus = self.attention_infer(start_plus,end_plus,x,time_att)\n\n        pred = 0.5*pred + 0.25*pred_minus + 0.25*pred_plus\n        return pred\n        \n    def attention_infer(self,start,end,x,time_att):\n        feat = x[:, :, start:end]\n        # att = torch.softmax(time_att[:, :, start:end], dim=-1)\n        #             print(feat_time, start, end)\n        #             print(att_a.sum(), att.sum(), time_att.shape)\n        framewise_pred = torch.sigmoid(self.att_block.cla(feat))\n        framewise_pred_max = framewise_pred.max(dim=2)[0]\n        # clipwise_output = torch.sum(framewise_pred * att, dim=-1)\n        #logits = torch.sum(\n        #    self.att_block.cla(feat) * att,\n        #    dim=-1,\n        #)\n\n        # return clipwise_output\n        return framewise_pred_max","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:21:36.675772Z","iopub.execute_input":"2025-05-20T09:21:36.676252Z","iopub.status.idle":"2025-05-20T09:21:36.698237Z","shell.execute_reply.started":"2025-05-20T09:21:36.676225Z","shell.execute_reply":"2025-05-20T09:21:36.697297Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_sample(path, cfg):\n    audio, orig_sr = sf.read(path, dtype=\"float32\")\n    seconds = []\n    audio_length = cfg.SR * cfg.target_duration\n    step = audio_length\n    for i in range(audio_length, len(audio) + step, step):\n        start = max(0, i - audio_length)\n        end = start + audio_length\n        if end > len(audio):\n            pass\n        else:\n            seconds.append(int(end/cfg.SR))\n\n    audio = np.concatenate([audio,audio,audio])\n    audios = []\n    for i,second in enumerate(seconds):\n        end_seconds = int(second)\n        start_seconds = int(end_seconds - cfg.target_duration)\n    \n        end_index = int(cfg.SR * (end_seconds + (cfg.train_duration - cfg.target_duration) / 2) ) + len(audio) // 3\n        start_index = int(cfg.SR * (start_seconds - (cfg.train_duration - cfg.target_duration) / 2) ) + len(audio) // 3\n        end_pad = int(cfg.SR * (cfg.train_duration - cfg.target_duration) / 2) \n        start_pad = int(cfg.SR * (cfg.train_duration - cfg.target_duration) / 2) \n        y = audio[start_index:end_index].astype(np.float32)\n        if i==0:\n            y[:start_pad] = 0\n        elif i==(len(seconds)-1):\n            y[-end_pad:] = 0\n        audios.append(y)\n\n    return audios\n\ndef sigmoid(x):\n    s = 1 / (1 + np.exp(-x))\n    return s","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:19:15.475974Z","iopub.execute_input":"2025-05-20T09:19:15.476261Z","iopub.status.idle":"2025-05-20T09:19:15.498863Z","shell.execute_reply.started":"2025-05-20T09:19:15.476235Z","shell.execute_reply":"2025-05-20T09:19:15.497961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def find_model_files(cfg):\n    \"\"\"\n    Find all .pth model files in the specified model directory\n    \"\"\"\n    model_files = []\n    \n    model_dir = Path(cfg.model_path)\n    \n    for path in model_dir.glob('**/*.pth'):\n        model_files.append(str(path))\n    \n    return model_files\n\ndef load_models(cfg, num_classes):\n    \"\"\"\n    Load all found model files and prepare them for ensemble\n    \"\"\"\n    models = []\n    \n    # model_files = find_model_files(cfg)\n    model_files = cfg.model_files\n    \n    if not model_files:\n        print(f\"Warning: No model files found under {cfg.model_path}!\")\n        return models\n    \n    print(f\"Found a total of {len(model_files)} model files.\")\n    \n    for i, model_path in enumerate(model_files):\n        try:\n            print(f\"Loading model: {model_path}\")\n            checkpoint = torch.load(model_path, map_location=torch.device(cfg.device), weights_only=False)\n            cfg_temp = checkpoint['cfg']\n            cfg_temp['device'] = cfg.device\n            \n            model = BirdCLEFModel(cfg_temp)\n            model.load_state_dict(checkpoint['model_state_dict'])\n            model = model.to(cfg.device)\n            model.eval()\n            model.zero_grad()\n            model.half().float()\n            \n            models.append(model)\n        except Exception as e:\n            print(f\"Error loading model {model_path}: {e}\")\n    \n    return models\n\ndef predict_on_spectrogram(audio_path, models, cfg, species_ids):\n    \"\"\"Process a single audio file and predict species presence for each 5-second segment\"\"\"\n    audio_path = str(audio_path)\n    predictions = []\n    row_ids = []\n    soundscape_id = Path(audio_path).stem\n\n    print(f\"Processing {soundscape_id}\")\n    audio_data = load_sample(audio_path, cfg)\n    for segment_idx, audio_input in enumerate(audio_data):\n        \n        end_time_sec = (segment_idx + 1) * cfg.target_duration\n        row_id = f\"{soundscape_id}_{end_time_sec}\"\n        row_ids.append(row_id)\n        \n        mel_spec = torch.tensor(audio_input, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n        mel_spec = mel_spec.to(cfg.device)\n        \n        if len(models) == 1:\n            with torch.no_grad():\n                outputs = models[0].infer(mel_spec)\n                final_preds = outputs.squeeze()\n                # final_preds = torch.sigmoid(outputs).cpu().numpy().squeeze()\n\n        else:\n            segment_preds = []\n            for model in models:\n                with torch.no_grad():\n                    outputs = model.infer(mel_spec)\n                    probs = outputs.squeeze()\n                    # probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                    segment_preds.append(probs)\n\n            \n            final_preds = np.mean(segment_preds, axis=0)\n                \n        predictions.append(final_preds)\n\n    predictions = np.stack(predictions,axis=0)\n    \n    return row_ids, predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:21:50.123932Z","iopub.execute_input":"2025-05-20T09:21:50.125211Z","iopub.status.idle":"2025-05-20T09:21:50.137398Z","shell.execute_reply.started":"2025-05-20T09:21:50.125177Z","shell.execute_reply":"2025-05-20T09:21:50.136470Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_inference(cfg, models, species_ids):\n    \"\"\"Run inference on all test soundscapes\"\"\"\n    test_files = list(Path(cfg.test_soundscapes).glob('*.ogg'))\n    if len(test_files) == 0:\n        test_files = sorted(glob(str(Path('/kaggle/input/birdclef-2025/train_soundscapes') / '*.ogg')))[:10]\n    \n    print(f\"Found {len(test_files)} test soundscapes\")\n\n    all_row_ids = []\n    all_predictions = []\n\n    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:\n        results = list(\n        executor.map(\n            predict_on_spectrogram,\n            test_files,\n            itertools.repeat(models),\n            itertools.repeat(cfg),\n            itertools.repeat(species_ids)\n        )\n    )\n\n    for rids, preds in results:\n        all_row_ids.extend(rids)\n        all_predictions.extend(preds)\n    \n    return all_row_ids, all_predictions\n\ndef create_submission(row_ids, predictions, species_ids, cfg):\n    \"\"\"Create submission dataframe\"\"\"\n    print(\"Creating submission dataframe...\")\n\n    submission_dict = {'row_id': row_ids}\n    \n    for i, species in enumerate(species_ids):\n        submission_dict[species] = [pred[i] for pred in predictions]\n\n    submission_df = pd.DataFrame(submission_dict)\n\n    submission_df.set_index('row_id', inplace=True)\n\n    sample_sub = pd.read_csv(cfg.submission_csv, index_col='row_id')\n\n    missing_cols = set(sample_sub.columns) - set(submission_df.columns)\n    if missing_cols:\n        print(f\"Warning: Missing {len(missing_cols)} species columns in submission\")\n        for col in missing_cols:\n            submission_df[col] = 0.0\n\n    submission_df = submission_df[sample_sub.columns]\n\n    submission_df = submission_df.reset_index()\n    \n    return submission_df\n\n\ndef smooth_submission(submission_path):\n        \"\"\"\n        Post-process the submission CSV by smoothing predictions to enforce temporal consistency.\n        \n        For each soundscape (grouped by the file name part of 'row_id'), each row's predictions\n        are averaged with those of its neighbors using defined weights.\n        \n        :param submission_path: Path to the submission CSV file.\n        \"\"\"\n        print(\"Smoothing submission predictions...\")\n        sub = pd.read_csv(submission_path)\n        cols = sub.columns[1:]\n        # Extract group names by splitting row_id on the last underscore\n        groups = sub['row_id'].str.rsplit('_', n=1).str[0].values\n        unique_groups = np.unique(groups)\n        \n        for group in unique_groups:\n            # Get indices for the current group\n            idx = np.where(groups == group)[0]\n            sub_group = sub.iloc[idx].copy()\n            predictions = sub_group[cols].values\n            new_predictions = predictions.copy()\n            \n            if predictions.shape[0] > 1:\n                # Smooth the predictions using neighboring segments\n                new_predictions[0] = (predictions[0] * 0.8) + (predictions[1] * 0.2)\n                new_predictions[-1] = (predictions[-1] * 0.8) + (predictions[-2] * 0.2)\n                for i in range(1, predictions.shape[0]-1):\n                    new_predictions[i] = (predictions[i-1] * 0.2) + (predictions[i] * 0.6) + (predictions[i+1] * 0.2)\n            # Replace the smoothed values in the submission dataframe\n            sub.iloc[idx, 1:] = new_predictions\n        \n        sub.to_csv(submission_path, index=False)\n        print(f\"Smoothed submission saved to {submission_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:19:15.535448Z","iopub.execute_input":"2025-05-20T09:19:15.535755Z","iopub.status.idle":"2025-05-20T09:19:15.563314Z","shell.execute_reply.started":"2025-05-20T09:19:15.535721Z","shell.execute_reply":"2025-05-20T09:19:15.562383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    start_time = time.time()\n    print(\"Starting BirdCLEF-2025 inference...\")\n\n    models = load_models(cfg, num_classes)\n    \n    if not models:\n        print(\"No models found! Please check model paths.\")\n        return\n    \n    print(f\"Model usage: {'Single model' if len(models) == 1 else f'Ensemble of {len(models)} models'}\")\n\n    row_ids, predictions = run_inference(cfg, models, species_ids)\n\n    submission_df = create_submission(row_ids, predictions, species_ids, cfg)\n\n    submission_path = 'submission.csv'\n    submission_df.to_csv(submission_path, index=False)\n    print(f\"Submission saved to {submission_path}\")\n\n    smooth_submission(submission_path)\n    \n    end_time = time.time()\n    print(f\"Inference completed in {(end_time - start_time)/60:.2f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:19:15.564420Z","iopub.execute_input":"2025-05-20T09:19:15.564693Z","iopub.status.idle":"2025-05-20T09:19:15.589022Z","shell.execute_reply.started":"2025-05-20T09:19:15.564673Z","shell.execute_reply":"2025-05-20T09:19:15.587706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:21:52.521512Z","iopub.execute_input":"2025-05-20T09:21:52.522255Z","iopub.status.idle":"2025-05-20T09:22:26.746979Z","shell.execute_reply.started":"2025-05-20T09:21:52.522222Z","shell.execute_reply":"2025-05-20T09:22:26.745886Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.read_csv(\"submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T09:22:26.749000Z","iopub.execute_input":"2025-05-20T09:22:26.749346Z","iopub.status.idle":"2025-05-20T09:22:26.785801Z","shell.execute_reply.started":"2025-05-20T09:22:26.749317Z","shell.execute_reply":"2025-05-20T09:22:26.784912Z"}},"outputs":[],"execution_count":null}]}