{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":11948763,"sourceType":"datasetVersion","datasetId":7130272},{"sourceId":12079109,"sourceType":"datasetVersion","datasetId":7603755}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -qqq /kaggle/input/onnxruntime/humanfriendly-10.0-py2.py3-none-any.whl --no-index --find-links /kaggle/input/onnxruntime\n!pip install -qqq /kaggle/input/onnxruntime/coloredlogs-15.0.1-py2.py3-none-any.whl --no-index --find-links /kaggle/input/onnxruntime\n!pip install -qqq /kaggle/input/onnxruntime/onnxruntime-1.21.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl --no-index --find-links /kaggle/input/onnxruntime","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:35:22.773496Z","iopub.execute_input":"2025-06-06T10:35:22.773835Z","iopub.status.idle":"2025-06-06T10:35:37.151768Z","shell.execute_reply.started":"2025-06-06T10:35:22.773808Z","shell.execute_reply":"2025-06-06T10:35:37.150548Z"}},"outputs":[],"execution_count":null},{"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 as lb\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\nimport matplotlib.pyplot as plt\nfrom collections import OrderedDict\n\nimport concurrent.futures\nimport onnx\nimport onnxruntime as ort\n\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:35:37.153074Z","iopub.execute_input":"2025-06-06T10:35:37.153454Z","iopub.status.idle":"2025-06-06T10:35:54.389515Z","shell.execute_reply.started":"2025-06-06T10:35:37.153408Z","shell.execute_reply":"2025-06-06T10:35:54.388554Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    \n    seed = 42\n    debug = False  \n    apex = True\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 = [\n                    '/kaggle/input/bird2025-infer-model/sed_v2b3P_30_v3.pth',\n                    '/kaggle/input/bird2025-infer-model/sed_v2b3P_30_v3_seed3407.pth',\n                    '/kaggle/input/bird2025-infer-model/sed_serP_30_v4.pth',\n                  ]\n \n    # model_name = 'tf_efficientnetv2_s_in21k'\n    # model_name = 'rexnet_150'\n    model_name = 'seresnext26t_32x4d'  \n    pretrained = False\n    in_channels = 1\n\n    img_size = 384\n    \n    SR = 32000\n    target_duration = 5\n    train_duration = 10\n    \n    \n    device = 'cpu'\n\n    \n    debug = False\n    debug_count = 3\n\n    ensemble_weights = [0.4, 0.4, 0.2]\n\ncfg = CFG()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:35:54.391491Z","iopub.execute_input":"2025-06-06T10:35:54.391963Z","iopub.status.idle":"2025-06-06T10:35:54.398696Z","shell.execute_reply.started":"2025-06-06T10:35:54.391938Z","shell.execute_reply":"2025-06-06T10:35:54.397569Z"}},"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-06-06T10:35:54.399505Z","iopub.execute_input":"2025-06-06T10:35:54.399866Z","iopub.status.idle":"2025-06-06T10:35:54.449621Z","shell.execute_reply.started":"2025-06-06T10:35:54.399845Z","shell.execute_reply":"2025-06-06T10:35:54.448713Z"}},"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\ndef normalize_batch_per_sample_concise(mel_spec_db_batch):\n    mel_spec_db_batch = mel_spec_db_batch.squeeze(1)\n    if not mel_spec_db_batch.is_floating_point():\n        mel_spec_db_batch = mel_spec_db_batch.to(torch.float32)\n\n    batch_size = mel_spec_db_batch.shape[0]\n    \n    flattened_specs = mel_spec_db_batch.reshape(batch_size, -1)\n    \n    min_vals = torch.amin(flattened_specs, dim=1, keepdim=True)[0]\n    max_vals = torch.amax(flattened_specs, dim=1, keepdim=True)[0]\n\n    min_vals_expanded = min_vals.unsqueeze(-1)\n    max_vals_expanded = max_vals.unsqueeze(-1)\n    \n    epsilon = 1e-8\n    denominator = max_vals_expanded - min_vals_expanded + epsilon\n    \n    normalized_batch = (mel_spec_db_batch - min_vals_expanded) / denominator\n\n    normalized_batch = normalized_batch.unsqueeze(1)\n    \n    return normalized_batch\n\ndef zscore_normalize(mel_spec_db_batch, epsilon=1e-6):\n\n    mel_spec_db_batch = mel_spec_db_batch.squeeze(1)\n\n    mean_vals = torch.mean(mel_spec_db_batch, dim=(1, 2), keepdim=True)\n    std_vals = torch.std(mel_spec_db_batch, dim=(1, 2), keepdim=True)\n\n    numerator = mel_spec_db_batch - mean_vals\n    denominator = std_vals + epsilon\n    \n    mel_spec_norm_batch = numerator / denominator\n\n    mel_spec_norm_batch_clipped = torch.clamp(mel_spec_norm_batch, min=-3, max=3)\n\n    mel_spec_norm_batch_clipped = mel_spec_norm_batch_clipped.unsqueeze(1)\n    \n    return mel_spec_norm_batch_clipped","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:35:54.450811Z","iopub.execute_input":"2025-06-06T10:35:54.451171Z","iopub.status.idle":"2025-06-06T10:35:54.473835Z","shell.execute_reply.started":"2025-06-06T10:35:54.451141Z","shell.execute_reply":"2025-06-06T10:35:54.472825Z"}},"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-06-06T10:35:54.474954Z","iopub.execute_input":"2025-06-06T10:35:54.475310Z","iopub.status.idle":"2025-06-06T10:35:54.490661Z","shell.execute_reply.started":"2025-06-06T10:35:54.475273Z","shell.execute_reply":"2025-06-06T10:35:54.489402Z"}},"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\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.tta_delta = 2\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 forward(self, x):\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-self.tta_delta)\n        end_minus=end-self.tta_delta\n        pred_minus = self.attention_infer(start_minus,end_minus,x,time_att)\n\n        start_plus = start+self.tta_delta\n        end_plus=min(feat_time, end+self.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-06-06T10:35:54.492211Z","iopub.execute_input":"2025-06-06T10:35:54.492685Z","iopub.status.idle":"2025-06-06T10:35:54.518869Z","shell.execute_reply.started":"2025-06-06T10:35:54.492629Z","shell.execute_reply":"2025-06-06T10:35:54.517715Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"checkpoints_cfg = torch.load(cfg.model_files[0], map_location=torch.device(cfg.device), weights_only=False)['cfg']\n\nmelspec_transform = torchaudio.transforms.MelSpectrogram(\n    sample_rate=checkpoints_cfg['SR'],\n    hop_length=checkpoints_cfg['hop_length'],\n    n_mels=checkpoints_cfg['n_mels'],\n    f_min=checkpoints_cfg['f_min'],\n    f_max=checkpoints_cfg['f_max']-2000,\n    n_fft=checkpoints_cfg['n_fft'],\n    pad_mode=\"constant\",\n    norm=\"slaney\",\n    onesided=True,\n    mel_scale=\"htk\",\n)\ndb_transform = torchaudio.transforms.AmplitudeToDB(stype=\"power\", top_db=80)\n\ndef transform_to_spec(audio, model_cfg):\n    spec = melspec_transform(audio)\n    spec = db_transform(spec)\n\n    if model_cfg['normal'] == 80:\n        spec = (spec + 80) / 80\n    elif model_cfg['normal'] == 255:\n        spec = spec / 255\n    elif model_cfg['normal'] == 'min_max':\n        spec = normalize_batch_per_sample_concise(spec)\n    elif model_cfg['normal'] == 'zscore':\n        spec = zscore_normalize(spec)\n    else:\n        raise NotImplementedError\n\n    if model_cfg['in_channels'] == 3:\n        spec = image_delta(spec)\n\n    return spec\n\ndef 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\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:35:54.520245Z","iopub.execute_input":"2025-06-06T10:35:54.520559Z","iopub.status.idle":"2025-06-06T10:35:57.251753Z","shell.execute_reply.started":"2025-06-06T10:35:54.520530Z","shell.execute_reply":"2025-06-06T10:35:57.250911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_models(cfg, num_classes):\n    \"\"\"\n    Load all found model files and prepare them for ensemble\n    \"\"\"\n    models = []\n    model_cfgs = []\n    input_names = ['x']\n    output_names = ['output']\n    \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\n    state_dict_list = []\n    for i, model_path in enumerate(model_files):\n\n        print(f\"Loading model: {model_path}\")\n        checkpoint = torch.load(model_path, map_location=torch.device(cfg.device), weights_only=False)\n\n        \n        cfg_temp = checkpoint['cfg']\n        cfg_temp['device'] = cfg.device\n        \n        model = BirdCLEFModel(cfg_temp)\n        state_dict = checkpoint['model_state_dict']\n        state_dict = {k: v for k, v in state_dict.items() if not k.startswith(\"melspec_transform.\")}\n\n        state_dict_list.append(state_dict)\n\n    \n        model.load_state_dict(state_dict)\n        model = model.to(cfg.device)\n        \n        model.eval()\n        \n        input_tensor = torch.randn(\n            12,\n            cfg_temp['in_channels'],\n            cfg_temp['n_mels'],\n            768,\n        )\n    \n        name = f\"model_{i}.onnx\"\n    \n        torch.onnx.export(\n            model,\n            input_tensor,\n            name,\n            verbose=False,\n            input_names=input_names,\n            output_names=output_names,\n            dynamic_axes={\n                \"x\": {0: \"batch_size\"},\n                \"output\": {0: \"batch_size\"}\n            }\n        )\n        \n        models.append(ort.InferenceSession(f\"model_{i}.onnx\"))\n        model_cfgs.append(cfg_temp)\n    \n    return models, model_cfgs\n\ndef predict_on_spectrogram(audio_path, models, model_cfgs, 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    audio_list = []\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        audio_list.append(audio_input)\n\n    audio_input = np.stack(audio_list, axis=0)\n    audio_input = torch.tensor(audio_input, dtype=torch.float32).unsqueeze(1).to(cfg.device)\n        \n    if len(models) == 1:\n        mel_spec = transform_to_spec(audio_input, model_cfgs[0])\n        outputs = models[0].run(['output'], {'x': mel_spec.numpy()})[0]\n        final_preds = outputs.squeeze()\n\n    else:\n        segment_preds = []\n        for model_cfg, model in zip(model_cfgs, models):\n            mel_spec = transform_to_spec(audio_input, model_cfg)\n            outputs = model.run(['output'], {'x': mel_spec.numpy()})[0]\n            probs = outputs.squeeze()\n            segment_preds.append(smooth_submission_single(probs))\n            \n        \n        final_preds = np.average(segment_preds, axis=0, weights=cfg.ensemble_weights)\n            \n    predictions = final_preds\n    \n    return row_ids, predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:35:57.254208Z","iopub.execute_input":"2025-06-06T10:35:57.254505Z","iopub.status.idle":"2025-06-06T10:35:57.269334Z","shell.execute_reply.started":"2025-06-06T10:35:57.254483Z","shell.execute_reply":"2025-06-06T10:35:57.268283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_inference(cfg, models, model_cfgs, 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    if cfg.debug:\n        print(f\"Debug mode enabled, using only {cfg.debug_count} files\")\n        test_files = test_files[:cfg.debug_count]\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    #         tqdm(\n    #             executor.map(\n    #                 predict_on_spectrogram,\n    #                 test_files,\n    #                 itertools.repeat(models),\n    #                 itertools.repeat(model_cfgs),\n    #                 itertools.repeat(cfg),\n    #                 itertools.repeat(species_ids)\n    #             ),\n    #             total=len(test_files),\n    #             desc=\"Processing audio files\"\n    #         )\n    #     )\n\n    # for rids, preds in results:\n    #     all_row_ids.extend(rids)\n    #     all_predictions.extend(preds)\n    for audio_path in tqdm(test_files):\n        row_ids, predictions = predict_on_spectrogram(str(audio_path), models, model_cfgs, cfg, species_ids)\n        all_row_ids.extend(row_ids)\n        all_predictions.extend(predictions)\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}\")\n\ndef smooth_submission_single(pred_data):\n    new_pred_data = np.copy(pred_data)\n    new_pred_data[0] = (pred_data[0] * 0.8) + (pred_data[1] * 0.2)\n    new_pred_data[-1] = (pred_data[-1] * 0.8) + (pred_data[-2] * 0.2)\n    for i in range(1, pred_data.shape[0]-1):\n        new_pred_data[i] = (pred_data[i-1] * 0.2) + (pred_data[i] * 0.6) + (pred_data[i+1] * 0.2)\n\n    return new_pred_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:35:57.270083Z","iopub.execute_input":"2025-06-06T10:35:57.270412Z","iopub.status.idle":"2025-06-06T10:35:57.291438Z","shell.execute_reply.started":"2025-06-06T10:35:57.270391Z","shell.execute_reply":"2025-06-06T10:35:57.290573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    print(\"Starting BirdCLEF-2025 inference...\")\n\n    models, model_cfgs= 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    start_time = time.time()\n    row_ids, predictions = run_inference(cfg, models, model_cfgs, 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    if len(models) == 1:\n        smooth_submission(submission_pat)\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-06-06T10:35:57.292909Z","iopub.execute_input":"2025-06-06T10:35:57.293764Z","iopub.status.idle":"2025-06-06T10:35:57.323810Z","shell.execute_reply.started":"2025-06-06T10:35:57.293728Z","shell.execute_reply":"2025-06-06T10:35:57.322617Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:35:57.324825Z","iopub.execute_input":"2025-06-06T10:35:57.325104Z","iopub.status.idle":"2025-06-06T10:38:14.423007Z","shell.execute_reply.started":"2025-06-06T10:35:57.325082Z","shell.execute_reply":"2025-06-06T10:38:14.422194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"result = pd.read_csv(\"submission.csv\")\nresult","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T10:39:23.459204Z","iopub.execute_input":"2025-06-06T10:39:23.459529Z","iopub.status.idle":"2025-06-06T10:39:23.495083Z","shell.execute_reply.started":"2025-06-06T10:39:23.459508Z","shell.execute_reply":"2025-06-06T10:39:23.494123Z"}},"outputs":[],"execution_count":null}]}