{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","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":11256170,"sourceType":"datasetVersion","datasetId":7034633},{"sourceId":11355273,"sourceType":"datasetVersion","datasetId":7106224},{"sourceId":11472893,"sourceType":"datasetVersion","datasetId":7190148},{"sourceId":11476975,"sourceType":"datasetVersion","datasetId":7193130},{"sourceId":11572881,"sourceType":"datasetVersion","datasetId":7255641},{"sourceId":11590028,"sourceType":"datasetVersion","datasetId":7267540},{"sourceId":11604811,"sourceType":"datasetVersion","datasetId":7278679},{"sourceId":11622769,"sourceType":"datasetVersion","datasetId":7273136},{"sourceId":11623745,"sourceType":"datasetVersion","datasetId":7291837},{"sourceId":11626405,"sourceType":"datasetVersion","datasetId":7294126},{"sourceId":11636468,"sourceType":"datasetVersion","datasetId":7283520},{"sourceId":11641758,"sourceType":"datasetVersion","datasetId":7305069},{"sourceId":11660969,"sourceType":"datasetVersion","datasetId":7317963},{"sourceId":11664660,"sourceType":"datasetVersion","datasetId":7319232},{"sourceId":11690323,"sourceType":"datasetVersion","datasetId":7328138},{"sourceId":11701048,"sourceType":"datasetVersion","datasetId":7338081},{"sourceId":11723222,"sourceType":"datasetVersion","datasetId":7359227},{"sourceId":11723474,"sourceType":"datasetVersion","datasetId":7359370},{"sourceId":11723484,"sourceType":"datasetVersion","datasetId":7359376},{"sourceId":11723775,"sourceType":"datasetVersion","datasetId":7359523},{"sourceId":11723785,"sourceType":"datasetVersion","datasetId":7359528},{"sourceId":11729883,"sourceType":"datasetVersion","datasetId":7363201},{"sourceId":11732372,"sourceType":"datasetVersion","datasetId":7365049},{"sourceId":11733542,"sourceType":"datasetVersion","datasetId":7365975},{"sourceId":11738639,"sourceType":"datasetVersion","datasetId":7369259},{"sourceId":11751077,"sourceType":"datasetVersion","datasetId":7377216},{"sourceId":11763856,"sourceType":"datasetVersion","datasetId":7385143},{"sourceId":11770347,"sourceType":"datasetVersion","datasetId":7389377},{"sourceId":11771666,"sourceType":"datasetVersion","datasetId":7390456},{"sourceId":11773097,"sourceType":"datasetVersion","datasetId":7391484},{"sourceId":11773715,"sourceType":"datasetVersion","datasetId":7391884},{"sourceId":11785929,"sourceType":"datasetVersion","datasetId":7400005},{"sourceId":11787643,"sourceType":"datasetVersion","datasetId":7401257},{"sourceId":11837638,"sourceType":"datasetVersion","datasetId":7437249},{"sourceId":11860040,"sourceType":"datasetVersion","datasetId":7450913},{"sourceId":11868730,"sourceType":"datasetVersion","datasetId":7458462},{"sourceId":11893948,"sourceType":"datasetVersion","datasetId":7476132},{"sourceId":11948821,"sourceType":"datasetVersion","datasetId":7511989},{"sourceId":11949995,"sourceType":"datasetVersion","datasetId":7512798},{"sourceId":11950638,"sourceType":"datasetVersion","datasetId":7513257},{"sourceId":11960439,"sourceType":"datasetVersion","datasetId":7520561},{"sourceId":11961888,"sourceType":"datasetVersion","datasetId":7521638},{"sourceId":11972468,"sourceType":"datasetVersion","datasetId":7528842},{"sourceId":11982788,"sourceType":"datasetVersion","datasetId":7536329},{"sourceId":11992817,"sourceType":"datasetVersion","datasetId":7536764},{"sourceId":11992926,"sourceType":"datasetVersion","datasetId":7543403},{"sourceId":12004886,"sourceType":"datasetVersion","datasetId":7543374},{"sourceId":12005350,"sourceType":"datasetVersion","datasetId":7552369},{"sourceId":12018517,"sourceType":"datasetVersion","datasetId":7561397},{"sourceId":12020261,"sourceType":"datasetVersion","datasetId":7562519},{"sourceId":12025647,"sourceType":"datasetVersion","datasetId":7566074},{"sourceId":12026647,"sourceType":"datasetVersion","datasetId":7566796},{"sourceId":12027699,"sourceType":"datasetVersion","datasetId":7567508},{"sourceId":12028691,"sourceType":"datasetVersion","datasetId":7568189},{"sourceId":12034167,"sourceType":"datasetVersion","datasetId":7571998},{"sourceId":12036073,"sourceType":"datasetVersion","datasetId":7573461},{"sourceId":12037631,"sourceType":"datasetVersion","datasetId":7574593},{"sourceId":12038847,"sourceType":"datasetVersion","datasetId":7575469},{"sourceId":12044855,"sourceType":"datasetVersion","datasetId":7579658},{"sourceId":12044973,"sourceType":"datasetVersion","datasetId":7579734},{"sourceId":12049724,"sourceType":"datasetVersion","datasetId":7583282},{"sourceId":12051680,"sourceType":"datasetVersion","datasetId":7584656},{"sourceId":12057049,"sourceType":"datasetVersion","datasetId":7588466},{"sourceId":12058270,"sourceType":"datasetVersion","datasetId":7589390},{"sourceId":12060157,"sourceType":"datasetVersion","datasetId":7590777},{"sourceId":12061265,"sourceType":"datasetVersion","datasetId":7591580},{"sourceId":230837629,"sourceType":"kernelVersion"},{"sourceId":242708059,"sourceType":"kernelVersion"}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **BirdCLEF 2025 Inference Notebook**\nThis notebook runs inference on BirdCLEF 2025 test soundscapes and generates a submission file. It supports both single model inference and ensemble inference with multiple models. You can find the pre-processing and training processes in the following notebooks:\n\n- [Transforming Audio-to-Mel Spec. | BirdCLEF'25](https://www.kaggle.com/code/kadircandrisolu/transforming-audio-to-mel-spec-birdclef-25)  \n- [EfficientNet B0 Pytorch [Train] | BirdCLEF'25](https://www.kaggle.com/code/kadircandrisolu/efficientnet-b0-pytorch-train-birdclef-25)\n\n**Features**\n- Audio Preprocessing\n- Test-Time Augmentation (TTA)","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport warnings\nimport logging\nimport time\nimport math\nimport cv2\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport timm\nfrom tqdm.auto import tqdm\n\nimport concurrent.futures\nimport itertools\n\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:21.731261Z","iopub.execute_input":"2025-06-03T09:16:21.731612Z","iopub.status.idle":"2025-06-03T09:16:35.260687Z","shell.execute_reply.started":"2025-06-03T09:16:21.731555Z","shell.execute_reply":"2025-06-03T09:16:35.259436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    # Paths\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    \n    # Model configurations\n    models_config = [\n        # {\n        #     'name': 'tf_efficientnetv2_b3',  #878\n        #     'path': '/kaggle/input/pseudo-level2-v2b3-40e-mixp02-04/model_sed_pseudo_epoch40.pth',\n        #     'in_channels': 3,\n        #     'folds': None,\n        #     'weight': 0.2,\n        #     'pooling': 'sed',\n        #     'use_normalized_input': True,\n        #     'use_specific_file': True,\n        #     'use_attention': False,\n        #     'mel_params': {\n        #         'n_fft': 2048,\n        #         'hop_length': 1024,\n        #         'n_mels': 128,\n        #         'fmin': 20,\n        #         'fmax': 16000\n        #     }\n        # },\n        \n        # {\n        #     'name': 'tf_efficientnetv2_s.in21k',  #875\n        #     'path': '/kaggle/input/pseudo-level2-v2s/model_sed_pseudo_epoch35.pth',\n        #     'in_channels': 3,\n        #     'folds': None,\n        #     'weight': 0.2,\n        #     'pooling': 'sed',\n        #     'use_normalized_input': True,\n        #     'use_specific_file': True,\n        #     'use_attention': False,\n        #     'mel_params': {\n        #         'n_fft': 2048,\n        #         'hop_length': 512,\n        #         'n_mels': 128,\n        #         'fmin': 20,\n        #         'fmax': 16000\n        #     }\n        # },\n        {\n            'name': 'eca_nfnet_l0',  #865\n            'path': '/kaggle/input/pseudo-level2-eca/model_sed_pseudo_epoch35.pth',\n            'in_channels': 3,\n            'folds': None,  # Use specified folds\n            'weight': 1,\n            'pooling': 'sed',\n            'use_normalized_input': True,\n            'use_specific_file': True,\n            'use_attention': False,\n            'mel_params': {\n                'n_fft': 2048,\n                'hop_length': 512,\n                'n_mels': 128,\n                'fmin': 20,\n                'fmax': 16000\n            }\n        },\n        # {\n        #     'name': 'efficientnet_b0',  #868\n        #     'path': '/kaggle/input/pseudo-level2-b0-dropout05-45e/model_sed_pseudo_epoch45.pth',\n        #     'in_channels': 3,\n        #     'folds': None,\n        #     'weight': 0.1,\n        #     'pooling': 'sed',  # Use SED-specific architecture\n        #     'use_normalized_input': True,\n        #     'use_specific_file': True,\n        #     'use_attention': False,\n        #     'mel_params': {\n        #         'n_fft': 2048,\n        #         'hop_length': 512,\n        #         'n_mels': 128,\n        #         'fmin': 20,\n        #         'fmax': 16000\n        #     }\n        # },\n        # {\n        #     'name': 'seresnext26t_32x4d',  #885\n        #     'path': '/kaggle/input/pseudo-level2-seresnext-dropout05-45e/model_sed_pseudo_epoch45.pth',\n        #     'in_channels': 3,\n        #     'folds': None,\n        #     'weight': 0.4,\n        #     'pooling': 'sed',  # Use SED-specific architecture\n        #     'use_normalized_input': True,\n        #     'use_specific_file': True,\n        #     'use_attention': False,\n        #     'mel_params': {\n        #         'n_fft': 2048,\n        #         'hop_length': 512,\n        #         'n_mels': 128,\n        #         'fmin': 20,\n        #         'fmax': 16000\n        #     }\n        # }\n    ]\n    \n    # Audio parameters\n    FS = 32000\n    WINDOW_SIZE = 5\n    TARGET_SHAPE = (256, 256)\n\n    # SED模型推理内部 TTA 参数\n    DURATION = 5.0  # 音频总时长(秒)\n    \n    # Device setting\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    \n    # Inference parameters\n    use_specific_folds = True    # Use only specific folds for each model\n    \n    # Post-processing\n    apply_temporal_smoothing = True  # Apply temporal smoothing to the predictions\n    \n    # Debug mode settings\n    debug = False\n    debug_count = 10\n\n# Initialize configuration\ncfg = CFG()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.262114Z","iopub.execute_input":"2025-06-03T09:16:35.262416Z","iopub.status.idle":"2025-06-03T09:16:35.278986Z","shell.execute_reply.started":"2025-06-03T09:16:35.262391Z","shell.execute_reply":"2025-06-03T09:16:35.277716Z"}},"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-03T09:16:35.281995Z","iopub.execute_input":"2025-06-03T09:16:35.282426Z","iopub.status.idle":"2025-06-03T09:16:35.334547Z","shell.execute_reply.started":"2025-06-03T09:16:35.282388Z","shell.execute_reply":"2025-06-03T09:16:35.333332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalize_imagenet(tensor):\n    \"\"\"应用ImageNet归一化到张量\"\"\"\n    mean = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1)\n    std = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1)\n    \n    return (tensor - mean) / std","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.336201Z","iopub.execute_input":"2025-06-03T09:16:35.336624Z","iopub.status.idle":"2025-06-03T09:16:35.343812Z","shell.execute_reply.started":"2025-06-03T09:16:35.336544Z","shell.execute_reply":"2025-06-03T09:16:35.342383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Helper functions for SED model\ndef init_layer(layer):\n    nn.init.xavier_uniform_(layer.weight)\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)\n\ndef interpolate(x: torch.Tensor, ratio: int):\n    \"\"\"Interpolate data in time domain. This is used to compensate the\n    resolution reduction in downsampling of a CNN.\n    Args:\n      x: (batch_size, time_steps, classes_num)\n      ratio: int, ratio to interpolate\n    Returns:\n      upsampled: (batch_size, time_steps * ratio, classes_num)\n    \"\"\"\n    (batch_size, time_steps, classes_num) = x.shape\n    upsampled = x[:, :, None, :].repeat(1, 1, ratio, 1)\n    upsampled = upsampled.reshape(batch_size, time_steps * ratio, classes_num)\n    return upsampled\n\ndef pad_framewise_output(framewise_output: torch.Tensor, frames_num: int):\n    \"\"\"Pad framewise_output to the same length as input frames. The pad value\n    is the same as the value of the last frame.\n    Args:\n      framewise_output: (batch_size, frames_num, classes_num)\n      frames_num: int, number of frames to pad\n    Outputs:\n      output: (batch_size, frames_num, classes_num)\n    \"\"\"\n    output = F.interpolate(\n        framewise_output.unsqueeze(1),\n        size=(frames_num, framewise_output.size(2)),\n        align_corners=True,\n        mode=\"bilinear\",\n    ).squeeze(1)\n    return output\n\n# AttBlockV2 for SED model\nclass 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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.345163Z","iopub.execute_input":"2025-06-03T09:16:35.345602Z","iopub.status.idle":"2025-06-03T09:16:35.366815Z","shell.execute_reply.started":"2025-06-03T09:16:35.345537Z","shell.execute_reply":"2025-06-03T09:16:35.365368Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Multi-Head Self Attention implementation\nclass MultiHeadSelfAttention(nn.Module):\n    def __init__(self, embed_dim, num_heads=4):\n        super().__init__()\n        self.embed_dim = embed_dim\n        self.num_heads = num_heads\n        self.head_dim = embed_dim // num_heads\n        \n        # QKV projection matrices\n        self.q_proj = nn.Linear(embed_dim, embed_dim)\n        self.k_proj = nn.Linear(embed_dim, embed_dim)\n        self.v_proj = nn.Linear(embed_dim, embed_dim)\n        self.out_proj = nn.Linear(embed_dim, embed_dim)\n        \n    def forward(self, x):\n        # x: [batch_size, seq_len, embed_dim]\n        batch_size, seq_len, _ = x.shape\n        \n        # Project query, key, value\n        q = self.q_proj(x)  # [batch_size, seq_len, embed_dim]\n        k = self.k_proj(x)  # [batch_size, seq_len, embed_dim]\n        v = self.v_proj(x)  # [batch_size, seq_len, embed_dim]\n        \n        # Reshape to multi-head form\n        q = q.reshape(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)\n        k = k.reshape(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)\n        v = v.reshape(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)\n        \n        # Calculate attention scores\n        scores = torch.matmul(q, k.transpose(-1, -2)) / math.sqrt(self.head_dim)\n        attn_weights = F.softmax(scores, dim=-1)  # [batch_size, num_heads, seq_len, seq_len]\n        \n        # Weight and aggregate value vectors\n        context = torch.matmul(attn_weights, v)  # [batch_size, num_heads, seq_len, head_dim]\n        \n        # Reshape and project output\n        context = context.permute(0, 2, 1, 3).reshape(batch_size, seq_len, self.embed_dim)\n        output = self.out_proj(context)  # [batch_size, seq_len, embed_dim]\n        \n        # Weight aggregate over time dimension to get sequence representation\n        final_weights = F.softmax(output.mean(-1), dim=-1).unsqueeze(-1)  # [batch_size, seq_len, 1]\n        final_context = torch.sum(output * final_weights, dim=1)  # [batch_size, embed_dim]\n        \n        return final_context, attn_weights, final_weights","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.368016Z","iopub.execute_input":"2025-06-03T09:16:35.368433Z","iopub.status.idle":"2025-06-03T09:16:35.392527Z","shell.execute_reply.started":"2025-06-03T09:16:35.368390Z","shell.execute_reply":"2025-06-03T09:16:35.391249Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# GeM Pooling implementation\nclass GeM(nn.Module):\n    \"\"\"\n    Generalized Mean Pooling\n    \"\"\"\n    def __init__(self, p=3.0, eps=1e-6, requires_grad=False):\n        super(GeM, self).__init__()\n        self.p = nn.Parameter(torch.ones(1) * p, requires_grad=requires_grad)\n        self.eps = eps\n\n    def forward(self, x):\n        return self._gem(x, p=self.p, eps=self.eps)\n    \n    def _gem(self, x, p, eps):\n        x = x.clamp(min=eps).pow(p)\n        x = F.avg_pool2d(x, x.size()[2:])\n        x = x.pow(1./p)\n        return x\n    \n    def __repr__(self):\n        return f\"GeM(p={self.p.data.item():.4f}, eps={self.eps})\"\n\n# Unified model class that can handle different pooling strategies\nclass BirdCLEFModel(nn.Module):\n    def __init__(self, model_name, in_channels, num_classes, pooling_type='avg', use_attention=False, cfg=None):\n        super().__init__()\n        \n        self.pooling_type = pooling_type\n        self.use_attention = use_attention\n        self.cfg = cfg\n        # Create backbone\n        self.backbone = timm.create_model(\n            model_name,\n            pretrained=False,\n            in_chans=in_channels,\n            drop_rate=0.0,\n            drop_path_rate=0.0\n        )\n        \n        # Get backbone output features\n        if 'efficientnet' in model_name:\n            backbone_out = self.backbone.classifier.in_features\n            self.backbone.classifier = nn.Identity()\n            if use_attention or pooling_type.lower() == 'sed':\n                self.backbone.global_pool = nn.Identity()  # Remove global pooling for attention\n        # elif \"eca\" in model_name:\n        #     backbone_out = self.backbone.head.fc.in_features\n        elif 'resnet' in model_name:\n            backbone_out = self.backbone.fc.in_features\n            self.backbone.fc = nn.Identity()\n            if use_attention or pooling_type.lower() == 'sed':\n                self.backbone.global_pool = nn.Identity()\n        else:\n            backbone_out = self.backbone.get_classifier().in_features\n            self.backbone.reset_classifier(0, '')\n        \n        # SED-specific components\n        if pooling_type.lower() == 'sed':\n            self.bn0 = nn.BatchNorm2d(256)  # Target shape height\n            # Get encoder layers only\n            layers = list(self.backbone.children())[:-2]\n            self.encoder = nn.Sequential(*layers)\n            self.fc1 = nn.Linear(backbone_out, backbone_out, bias=True)\n            self.att_block = AttBlockV2(backbone_out, num_classes, activation=\"sigmoid\")\n            \n            # Initialize SED-specific components\n            init_layer(self.fc1)\n            init_bn(self.bn0)\n        else:\n            # Choose pooling based on configuration\n            if pooling_type.lower() == 'gem':\n                self.pooling = GeM(p=3.0, requires_grad=False)\n            elif pooling_type.lower() == 'attention':\n                self.attention = MultiHeadSelfAttention(\n                    embed_dim=backbone_out,\n                    num_heads=4\n                )\n            else:\n                self.pooling = nn.AdaptiveAvgPool2d(1)\n        \n        self.feat_dim = backbone_out\n        \n        # For regular models only\n        if pooling_type.lower() != 'sed':\n            self.classifier = nn.Linear(backbone_out, num_classes)\n\n    def attention_infer(self, start, end, x, time_att):\n        \"\"\"使用注意力机制进行推理\"\"\"\n        # 确保边界有效\n        start = max(0, start)\n        end = min(x.size(2), end)\n        \n        # 如果窗口无效，返回零预测\n        if start >= end or start >= x.size(2) or end <= 0:\n            return torch.zeros((x.size(0), self.att_block.cla.out_channels), device=x.device)\n        \n        feat = x[:, :, start:end]\n        \n        # 计算时间注意力\n        att = torch.softmax(time_att[:, :, start:end], dim=-1)\n        \n        # 获取帧级预测\n        framewise_logits = self.att_block.cla(feat)\n        \n        # 使用注意力机制融合各帧的预测\n        clipwise_logits = torch.sum(framewise_logits * att, dim=2)\n        \n        # 注意这里返回的是logits而不是sigmoid后的结果\n        return clipwise_logits\n        \n    def forward(self, x, return_attention=False):\n        # Handle SED-specific forward pass\n        if self.pooling_type.lower() == 'sed':\n            # x shape: [batch_size, channels, time, freq]\n            x = x.permute((0, 1, 3, 2))\n            frames_num = x.shape[2]\n            \n            # Apply batch normalization\n            x = x.transpose(1, 3)\n            x = self.bn0(x)\n            x = x.transpose(1, 3)\n            \n            # (batch_size, channels, time, freq) -> (batch_size, channels, freq, time)\n            x = x.transpose(2, 3)\n            \n            # Through encoder network\n            x = self.encoder(x)\n            \n            # Average over frequency dimension\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            \n            # 获取特征时间维度\n            feat_time = x.size(2)\n            \n            time_att = torch.tanh(self.att_block.att(x))\n            \n            feat_time = x.size(-1)\n            start = 0\n            end = feat_time\n            pred = self.attention_infer(start,end,x,time_att)\n\n            tta_delta = 2\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        elif self.use_attention:\n            # Standard attention-based forward pass\n            features = self.backbone.forward_features(x)\n            \n            batch_size, c, h, w = features.shape\n            features = features.permute(0, 2, 3, 1)\n            features = features.reshape(batch_size, h * w, c)\n            \n            if return_attention:\n                context_vector, attn_weights, time_weights = self.attention(features)\n            else:\n                context_vector, _, _ = self.attention(features)\n            \n            logits = self.classifier(context_vector)\n            \n            if return_attention:\n                return logits, attn_weights, time_weights\n            return logits\n        else:\n            # Standard forward pass\n            features = self.backbone(x)\n            \n            if isinstance(features, dict):\n                features = features['features']\n                \n            if len(features.shape) == 4:\n                features = self.pooling(features)\n                features = features.view(features.size(0), -1)\n            \n            logits = self.classifier(features)\n            return logits\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\n            ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.393794Z","iopub.execute_input":"2025-06-03T09:16:35.394150Z","iopub.status.idle":"2025-06-03T09:16:35.421713Z","shell.execute_reply.started":"2025-06-03T09:16:35.394120Z","shell.execute_reply":"2025-06-03T09:16:35.420289Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def audio2melspec(audio_data, mel_params, fs=32000):\n    \"\"\"Convert audio data to mel spectrogram with specified parameters\"\"\"\n    if np.isnan(audio_data).any():\n        mean_signal = np.nanmean(audio_data)\n        audio_data = np.nan_to_num(audio_data, nan=mean_signal)\n\n    mel_spec = librosa.feature.melspectrogram(\n        y=audio_data,\n        sr=fs,\n        n_fft=mel_params['n_fft'],\n        hop_length=mel_params['hop_length'],\n        n_mels=mel_params['n_mels'],\n        fmin=mel_params['fmin'],\n        fmax=mel_params['fmax'],\n        power=2.0\n    )\n\n    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n    mel_spec_norm = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)\n    \n    return mel_spec_norm\n\n\ndef process_audio_segment(audio_data, mel_params, target_shape, channels=1, use_normalized_input=False, fs=32000, window_size=5):\n    \"\"\"Process audio segment to get mel spectrogram with the specified parameters\"\"\"\n    if len(audio_data) < fs * window_size:\n        audio_data = np.pad(audio_data, \n                          (0, fs * window_size - len(audio_data)), \n                          mode='constant')\n    \n    mel_spec = audio2melspec(audio_data, mel_params, fs)\n    \n    # Resize if needed\n    if mel_spec.shape != target_shape:\n        mel_spec = cv2.resize(mel_spec, target_shape, interpolation=cv2.INTER_LINEAR)\n    \n    mel_spec = mel_spec.astype(np.float32)\n    \n    # Convert to tensor based on required channels and normalization\n    mel_spec_tensor = torch.tensor(mel_spec, dtype=torch.float32).unsqueeze(0)\n    \n    if channels == 3:\n        mel_spec_tensor = mel_spec_tensor.repeat(3, 1, 1)  # Create 3 channels\n        if use_normalized_input:\n            mel_spec_tensor = normalize_imagenet(mel_spec_tensor)\n    \n    return mel_spec_tensor\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.422918Z","iopub.execute_input":"2025-06-03T09:16:35.423249Z","iopub.status.idle":"2025-06-03T09:16:35.441327Z","shell.execute_reply.started":"2025-06-03T09:16:35.423221Z","shell.execute_reply":"2025-06-03T09:16:35.440005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def find_model_files(model_path, folds=None):\n    \"\"\"\n    Find model files in the specified model directory\n    If folds is provided, only return models for those folds\n    \"\"\"\n    model_files = []\n    \n    # If model_path is directly a .pth file, return it\n    if model_path.endswith('.pth'):\n        return [model_path]\n    \n    model_dir = Path(model_path)\n    \n    for path in model_dir.glob('**/*.pth'):\n        model_files.append(str(path))\n    \n    if folds is not None:\n        filtered_files = []\n        for fold in folds:\n            fold_files = [f for f in model_files if f\"fold{fold}\" in f]\n            filtered_files.extend(fold_files)\n        model_files = filtered_files\n    \n    return model_files\n\ndef load_models(cfg, num_classes):\n    \"\"\"\n    Load all models based on configuration\n    \"\"\"\n    loaded_models = []\n    \n    for model_config in cfg.models_config:\n        model_name = model_config['name']\n        model_path = model_config['path']\n        in_channels = model_config['in_channels']\n        pooling_type = model_config.get('pooling', 'avg')\n        use_attention = model_config.get('use_attention', False)\n        use_specific_file = model_config.get('use_specific_file', False)\n        \n        if use_specific_file:\n            # For models with specific file path\n            model_files = [model_path]\n        else:\n            # For models with folder path that need fold filtering\n            folds = model_config['folds'] if cfg.use_specific_folds else None\n            model_files = find_model_files(model_path, folds)\n        \n        if not model_files:\n            print(f\"Warning: No model files found for {model_name} under {model_path}!\")\n            continue\n        \n        print(f\"Found {len(model_files)} model files for {model_name} with pooling type '{pooling_type}'\")\n        \n        model_group = []\n        for model_file in model_files:\n            try:\n                print(f\"Loading model: {model_file}\")\n                checkpoint = torch.load(model_file, map_location=torch.device(cfg.device))\n                \n                model = BirdCLEFModel(\n                    model_name, \n                    in_channels, \n                    num_classes, \n                    pooling_type,\n                    use_attention,\n                    cfg\n                )\n                \n                # Handle different checkpoint formats\n                if 'model_state_dict' in checkpoint:\n                    model.load_state_dict(checkpoint['model_state_dict'])\n                elif 'state_dict' in checkpoint:\n                    # Some models might use 'state_dict' key instead\n                    model.load_state_dict(checkpoint['state_dict'])\n                else:\n                    # Direct state dict\n                    model.load_state_dict(checkpoint)\n                \n                model = model.to(cfg.device)\n                model.eval()\n                \n                # Add model to group with its configuration\n                model_group.append({\n                    'model': model,\n                    'config': model_config\n                })\n                \n                print(f\"Successfully loaded model with pooling type: {pooling_type}\")\n                \n            except Exception as e:\n                print(f\"Error loading model {model_file}: {e}\")\n                import traceback\n                traceback.print_exc()\n        \n        if model_group:\n            loaded_models.extend(model_group)\n    \n    if not loaded_models:\n        print(\"Error: No models loaded successfully!\")\n    else:\n        print(f\"Successfully loaded {len(loaded_models)} models in total\")\n    \n    return loaded_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    predictions = []\n    row_ids = []\n    soundscape_id = Path(audio_path).stem  # audio_path可能是Path对象，需要确保兼容\n    \n    try:\n        print(f\"Processing {soundscape_id}\")\n        audio_data, _ = librosa.load(str(audio_path), sr=cfg.FS)  # 确保转换为字符串\n        \n        total_segments = int(len(audio_data) / (cfg.FS * cfg.WINDOW_SIZE))\n        \n        for segment_idx in range(total_segments):\n            start_sample = segment_idx * cfg.FS * cfg.WINDOW_SIZE\n            end_sample = start_sample + cfg.FS * cfg.WINDOW_SIZE\n            segment_audio = audio_data[start_sample:end_sample]\n            \n            end_time_sec = (segment_idx + 1) * cfg.WINDOW_SIZE\n            row_id = f\"{soundscape_id}_{end_time_sec}\"\n            row_ids.append(row_id)\n            \n            # Group models by mel spectrogram parameters\n            model_groups = {}\n            for model_data in models:\n                model_config = model_data['config']\n                \n                # Create a key based on mel parameters and normalization\n                mel_params = frozenset(model_config['mel_params'].items())\n                in_channels = model_config['in_channels']\n                use_normalized = model_config.get('use_normalized_input', False)\n                \n                param_key = (mel_params, in_channels, use_normalized)\n                \n                if param_key not in model_groups:\n                    model_groups[param_key] = []\n                \n                model_groups[param_key].append(model_data)\n            \n            # Store predictions and weights for this segment\n            all_model_predictions = []\n            all_model_weights = []\n            \n            # Process each parameter group once to avoid redundant mel spec extraction\n            for param_key, group_models in model_groups.items():\n                mel_params_dict = dict(param_key[0])\n                in_channels = param_key[1]\n                use_normalized = param_key[2]\n                \n                # Process audio once for this parameter group\n                mel_spec = process_audio_segment(\n                    segment_audio, \n                    mel_params_dict,\n                    cfg.TARGET_SHAPE,\n                    channels=in_channels,\n                    use_normalized_input=use_normalized,\n                    fs=cfg.FS,\n                    window_size=cfg.WINDOW_SIZE\n                )\n                \n                # Add batch dimension and move to device\n                mel_spec = mel_spec.unsqueeze(0).to(cfg.device)\n                \n                # Get predictions from all models in this group\n                for model_data in group_models:\n                    model = model_data['model']\n                    weight = model_data['config'].get('weight', 1.0)\n                    pooling_type = model_data['config'].get('pooling', 'avg').lower()\n                    \n                    # Get prediction\n                    with torch.no_grad():\n                        outputs = model(mel_spec)\n                        # Skip sigmoid for SED models since they already return probabilities\n                        if pooling_type != 'sed':\n                            outputs = torch.sigmoid(outputs)\n                        probs = outputs.cpu().numpy().squeeze()\n                    \n                    all_model_predictions.append(probs)\n                    all_model_weights.append(weight)\n            \n            # Calculate weighted average of model predictions\n            if all_model_predictions:\n                # Normalize weights\n                sum_weights = sum(all_model_weights)\n                if sum_weights > 0:\n                    normalized_weights = [w / sum_weights for w in all_model_weights]\n                    \n                    # Weighted average\n                    weighted_pred = np.zeros_like(all_model_predictions[0])\n                    for pred, weight in zip(all_model_predictions, normalized_weights):\n                        weighted_pred += pred * weight\n                    \n                    predictions.append(weighted_pred)\n            \n    except Exception as e:\n        print(f\"Error processing {audio_path}: {e}\")\n    \n    return row_ids, predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.444499Z","iopub.execute_input":"2025-06-03T09:16:35.444857Z","iopub.status.idle":"2025-06-03T09:16:35.469916Z","shell.execute_reply.started":"2025-06-03T09:16:35.444828Z","shell.execute_reply":"2025-06-03T09:16:35.468618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apply_tta(spec, tta_idx):\n    \"\"\"Apply test-time augmentation\"\"\"\n    if tta_idx == 0:\n        # Original spectrogram\n        return spec\n    elif tta_idx == 1:\n        # Time shift (horizontal flip)\n        return np.flip(spec, axis=1)\n    elif tta_idx == 2:\n        # Frequency shift (vertical flip)\n        return np.flip(spec, axis=0)\n    else:\n        return spec\n\ndef run_inference(cfg, models, species_ids):\n    \"\"\"Run inference on all test soundscapes\"\"\"\n    test_files = list(Path(cfg.test_soundscapes).glob('*.ogg'))\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    # 使用多线程加速推理\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\n\ndef create_submission(row_ids, predictions, species_ids, cfg, apply_temporal_smoothing=True):\n    \"\"\"Create submission dataframe with optional temporal smoothing\"\"\"\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    # Apply temporal smoothing if enabled\n    if apply_temporal_smoothing:\n        print(\"Applying temporal smoothing...\")\n        cols = submission_df.columns[1:]  # All columns except row_id\n        groups = submission_df['row_id'].str.rsplit('_', n=1).str[0]\n        groups = groups.values\n        \n        for group in np.unique(groups):\n            sub_group = submission_df[group == groups]\n            predictions = sub_group[cols].values\n            new_predictions = predictions.copy()\n            \n            # Apply smoothing for middle frames\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            \n            # Special handling for first and last frame\n            if predictions.shape[0] > 1:\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            \n            sub_group[cols] = new_predictions\n            submission_df.loc[group == groups, cols] = new_predictions\n\n    # Ensure all columns from sample submission are present\n    submission_df.set_index('row_id', inplace=True)\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    # Ensure columns are in the same order as sample submission\n    submission_df = submission_df[sample_sub.columns]\n    submission_df = submission_df.reset_index()\n    \n    return submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.471412Z","iopub.execute_input":"2025-06-03T09:16:35.471830Z","iopub.status.idle":"2025-06-03T09:16:35.496681Z","shell.execute_reply.started":"2025-06-03T09:16:35.471771Z","shell.execute_reply":"2025-06-03T09:16:35.495366Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    start_time = time.time()\n    print(\"\\n\" + \"=\"*50)\n    print(\"Starting BirdCLEF-2025 Multi-Model Ensemble Inference\")\n    print(\"=\"*50)\n    \n    # Print configuration\n    print(\"\\nModel Configurations:\")\n    for i, model_config in enumerate(cfg.models_config):\n        print(f\"  Model {i+1}: {model_config['name']}\")\n        print(f\"    - Input Channels: {model_config['in_channels']}\")\n        print(f\"    - Weight: {model_config['weight']}\")\n        print(f\"    - Pooling: {model_config.get('pooling', 'avg')}\")\n    \n    # Load all models\n    models = load_models(cfg, num_classes)\n    \n    if not models:\n        print(\"No models loaded! Please check model paths.\")\n        return\n    \n    # Run inference\n    row_ids, predictions = run_inference(cfg, models, species_ids)\n\n    # Create submission without temporal smoothing first\n    submission_df = create_submission(\n        row_ids, \n        predictions, \n        species_ids, \n        cfg, \n        apply_temporal_smoothing=False  # Don't apply smoothing yet\n    )\n\n    # Save initial submission\n    initial_submission_path = 'submission.csv'\n    submission_df.to_csv(initial_submission_path, index=False)\n    print(f\"Initial submission saved to {initial_submission_path}\")\n    \n    end_time = time.time()\n    print(f\"Inference completed in {(end_time - start_time)/60:.2f} minutes\")\n    \n    # Note: Temporal smoothing will be applied in a separate code block","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.497864Z","iopub.execute_input":"2025-06-03T09:16:35.498241Z","iopub.status.idle":"2025-06-03T09:16:35.520848Z","shell.execute_reply.started":"2025-06-03T09:16:35.498202Z","shell.execute_reply":"2025-06-03T09:16:35.519490Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:16:35.522091Z","iopub.execute_input":"2025-06-03T09:16:35.522601Z","iopub.status.idle":"2025-06-03T09:18:12.732915Z","shell.execute_reply.started":"2025-06-03T09:16:35.522522Z","shell.execute_reply":"2025-06-03T09:18:12.731667Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\n# 第一步：读取提交文件\nsub = pd.read_csv('submission.csv')\ncols = sub.columns[1:]  # 获取除了row_id之外的所有物种列\n\n# 复制数据以进行处理\npred_df = sub.copy()\n\n# 应用时间平滑 (Temporal Smoothing)\n# 根据音频文件分组\ngroups = pred_df['row_id'].str.rsplit('_', n=1).str[0]\ngroups = groups.values\n\n# 对每个音频文件进行时间平滑处理\nfor group in np.unique(groups):\n    # 获取当前音频文件的所有行\n    sub_group = pred_df[group == groups]\n    predictions = sub_group[cols].values\n    new_predictions = predictions.copy()\n    \n    # 对中间的片段应用前中后加权平均\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    \n    # 对第一个和最后一个片段进行特殊处理\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    \n    # 更新数据\n    sub_group[cols] = new_predictions\n    pred_df[group == groups] = sub_group\n\n# 第四步：保存最终结果\npred_df.to_csv(\"submission.csv\", index=False)\nprint(\"处理完成！最终提交文件已保存为 'submission.csv'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-03T09:18:12.734332Z","iopub.execute_input":"2025-06-03T09:18:12.734725Z","iopub.status.idle":"2025-06-03T09:18:13.597288Z","shell.execute_reply.started":"2025-06-03T09:18:12.734683Z","shell.execute_reply":"2025-06-03T09:18:13.595897Z"}},"outputs":[],"execution_count":null}]}