{"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,"sourceType":"competition"},{"sourceId":11125971,"sourceType":"datasetVersion","datasetId":6938568}],"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.","metadata":{}},{"cell_type":"code","source":"import argparse\nimport logging\nimport os\nfrom typing import Any, Dict, List, Optional, Tuple, Union\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader\nfrom tqdm import tqdm\n\nimport transformers\nfrom transformers import AutoModelForSequenceClassification, AutoTokenizer\nimport pytorch_lightning as pl\n\nimport albumentations as A\n\nimport torchaudio","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:46:47.426377Z","iopub.execute_input":"2025-03-22T12:46:47.426663Z","iopub.status.idle":"2025-03-22T12:47:31.360225Z","shell.execute_reply.started":"2025-03-22T12:46:47.426631Z","shell.execute_reply":"2025-03-22T12:47:31.359574Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DEBUG = False\nN_DEBUG = 5\n\nclass CFG:\n    paths = {\n        \"data_dir\": \"/kaggle/input/birdclef-2025\",\n        \"model_dir\": \"/kaggle/input/birdclef-2025-baseline\"\n    }\n    data = {\n        \"sample_submission_file\": \"/kaggle/input/birdclef-2025/sample_submission.csv\",\n        \"taxonomy_csv\": \"/kaggle/input/birdclef-2025/taxonomy.csv\",\n        \"image_size\": 256,\n        \"mel_spec_params\": {\n            \"sample_rate\": 32000,\n            \"n_mels\": 128,\n            \"f_min\": 20,\n            \"f_max\": 16000,\n            \"n_fft\": 2048,\n            \"hop_length\": 512,\n            \"normalized\": True,\n            \"center\": True,\n            \"pad_mode\": \"constant\",\n            \"norm\": \"slaney\",\n            \"onesided\": True,\n            \"mel_scale\": \"slaney\"\n        },\n        \"train_duration\": 160000,\n        \"top_db\": 80,\n        \"fold\": 0,\n        \"num_folds\": 5\n    }\n\n    inference = {\n        \"ensemble\": False,\n        \"checkpoint\": \"best\",\n        \"batch_size\": 64,\n        \"tta\": False\n    }\n\n    model = {\n        \"name\": \"birdclef\",\n        \"backbone\": \"eca_nfnet_l0\",\n        \"pretrained\": False,\n        \"dropout\": 0.5,\n        \"hidden_dim\": 256,\n        \"num_classes\": 206\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:31.361013Z","iopub.execute_input":"2025-03-22T12:47:31.361397Z","iopub.status.idle":"2025-03-22T12:47:31.366601Z","shell.execute_reply.started":"2025-03-22T12:47:31.361375Z","shell.execute_reply":"2025-03-22T12:47:31.365787Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Models","metadata":{}},{"cell_type":"code","source":"\"\"\"\nPyTorch model definitions.\n\"\"\"\nfrom typing import Dict, List, Optional, Tuple, Union\n\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision.models as models\nimport timm\nfrom transformers import AutoModel\n\n\nclass TabularMLP(nn.Module):\n    \"\"\"\n    Multi-layer perceptron for tabular data.\n    \n    Attributes:\n        input_dim: Input dimension\n        hidden_dims: List of hidden dimensions\n        output_dim: Output dimension\n        dropout: Dropout rate\n        batch_norm: Whether to use batch normalization\n        activation: Activation function\n    \"\"\"\n    \n    def __init__(self, \n                input_dim: int, \n                hidden_dims: List[int] = [256, 128, 64], \n                output_dim: int = 1, \n                dropout: float = 0.5,\n                batch_norm: bool = True,\n                activation: str = 'relu'):\n        \"\"\"\n        Initialize TabularMLP.\n        \n        Args:\n            input_dim: Input dimension\n            hidden_dims: List of hidden dimensions\n            output_dim: Output dimension\n            dropout: Dropout rate\n            batch_norm: Whether to use batch normalization\n            activation: Activation function ('relu', 'leaky_relu', 'elu', 'gelu')\n        \"\"\"\n        super().__init__()\n        \n        self.input_dim = input_dim\n        self.hidden_dims = hidden_dims\n        self.output_dim = output_dim\n        self.dropout = dropout\n        self.batch_norm = batch_norm\n        \n        # Create activation function\n        if activation == 'relu':\n            self.activation = nn.ReLU()\n        elif activation == 'leaky_relu':\n            self.activation = nn.LeakyReLU(0.1)\n        elif activation == 'elu':\n            self.activation = nn.ELU()\n        elif activation == 'gelu':\n            self.activation = nn.GELU()\n        else:\n            raise ValueError(f\"Activation {activation} not supported\")\n        \n        # Create layers\n        layers = []\n        prev_dim = input_dim\n        \n        for i, hidden_dim in enumerate(hidden_dims):\n            layers.append(nn.Linear(prev_dim, hidden_dim))\n            \n            if batch_norm:\n                layers.append(nn.BatchNorm1d(hidden_dim))\n                \n            layers.append(self.activation)\n            layers.append(nn.Dropout(dropout))\n            \n            prev_dim = hidden_dim\n        \n        # Add output layer\n        layers.append(nn.Linear(prev_dim, output_dim))\n        \n        # Create sequential model\n        self.model = nn.Sequential(*layers)\n    \n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        \"\"\"\n        Forward pass.\n        \n        Args:\n            x: Input tensor\n            \n        Returns:\n            Output tensor\n        \"\"\"\n        return self.model(x)\n\n\nclass ResNetClassifier(nn.Module):\n    \"\"\"\n    ResNet-based classifier for image data.\n    \n    Attributes:\n        backbone: ResNet backbone\n        fc: Fully connected head\n        dropout: Dropout layer\n    \"\"\"\n    \n    def __init__(self, \n                num_classes: int = 1, \n                backbone: str = 'resnet50', \n                pretrained: bool = True,\n                dropout: float = 0.5):\n        \"\"\"\n        Initialize ResNetClassifier.\n        \n        Args:\n            num_classes: Number of output classes\n            backbone: ResNet backbone ('resnet18', 'resnet34', 'resnet50', 'resnet101', 'resnet152')\n            pretrained: Whether to use pretrained weights\n            dropout: Dropout rate\n        \"\"\"\n        super().__init__()\n        \n        # Create backbone\n        if backbone == 'resnet18':\n            self.backbone = models.resnet18(pretrained=pretrained)\n        elif backbone == 'resnet34':\n            self.backbone = models.resnet34(pretrained=pretrained)\n        elif backbone == 'resnet50':\n            self.backbone = models.resnet50(pretrained=pretrained)\n        elif backbone == 'resnet101':\n            self.backbone = models.resnet101(pretrained=pretrained)\n        elif backbone == 'resnet152':\n            self.backbone = models.resnet152(pretrained=pretrained)\n        else:\n            raise ValueError(f\"Backbone {backbone} not supported\")\n        \n        # Get feature dimension\n        in_features = self.backbone.fc.in_features\n        \n        # Replace fully connected layer\n        self.backbone.fc = nn.Identity()\n        \n        # Create new head\n        self.dropout = nn.Dropout(dropout)\n        \n        # For binary classification with sigmoid, use 1 output unit\n        if num_classes == 1:\n            self.fc = nn.Linear(in_features, 1)\n        else:\n            self.fc = nn.Linear(in_features, num_classes)\n        \n        self.num_classes = num_classes\n    \n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        \"\"\"\n        Forward pass.\n        \n        Args:\n            x: Input tensor\n            \n        Returns:\n            Output tensor\n        \"\"\"\n        features = self.backbone(x)\n        features = self.dropout(features)\n        logits = self.fc(features)\n        \n        return logits\n\n\nclass TransformerTextClassifier(nn.Module):\n    \"\"\"\n    Transformer-based text classifier.\n    \n    Attributes:\n        transformer: Transformer model\n        dropout: Dropout layer\n        fc: Fully connected head\n    \"\"\"\n    \n    def __init__(self, \n                model_name: str = 'bert-base-uncased', \n                num_classes: int = 1,\n                dropout: float = 0.3,\n                freeze_base: bool = False):\n        \"\"\"\n        Initialize TransformerTextClassifier.\n        \n        Args:\n            model_name: Name of the pretrained transformer model\n            num_classes: Number of output classes\n            dropout: Dropout rate\n            freeze_base: Whether to freeze the transformer base\n        \"\"\"\n        super().__init__()\n        \n        # Load transformer model\n        self.transformer = AutoModel.from_pretrained(model_name)\n        \n        # Freeze transformer if specified\n        if freeze_base:\n            for param in self.transformer.parameters():\n                param.requires_grad = False\n        \n        # Get hidden size of the model\n        hidden_size = self.transformer.config.hidden_size\n        \n        # Create classification head\n        self.dropout = nn.Dropout(dropout)\n        \n        # For binary classification with sigmoid, use 1 output unit\n        if num_classes == 1:\n            self.fc = nn.Linear(hidden_size, 1)\n        else:\n            self.fc = nn.Linear(hidden_size, num_classes)\n        \n        self.num_classes = num_classes\n    \n    def forward(self, \n               input_ids: torch.Tensor, \n               attention_mask: torch.Tensor,\n               token_type_ids: Optional[torch.Tensor] = None) -> torch.Tensor:\n        \"\"\"\n        Forward pass.\n        \n        Args:\n            input_ids: Input token ids\n            attention_mask: Attention mask\n            token_type_ids: Token type ids\n            \n        Returns:\n            Output tensor\n        \"\"\"\n        # Get transformer outputs\n        if token_type_ids is not None:\n            outputs = self.transformer(\n                input_ids=input_ids,\n                attention_mask=attention_mask,\n                token_type_ids=token_type_ids\n            )\n        else:\n            outputs = self.transformer(\n                input_ids=input_ids,\n                attention_mask=attention_mask\n            )\n        \n        # Get pooled output (CLS token)\n        pooled_output = outputs.pooler_output\n        \n        # Apply dropout and classification head\n        pooled_output = self.dropout(pooled_output)\n        logits = self.fc(pooled_output)\n        \n        return logits\n\n\nclass TabNetModel(nn.Module):\n    \"\"\"\n    Simplified TabNet for tabular data.\n    \n    This is a simplified implementation for demonstration.\n    For production, consider using the official implementation or PyTorch Tabular.\n    \n    Attributes:\n        input_dim: Input dimension\n        output_dim: Output dimension\n        n_d: Feature dimension in decision step\n        n_a: Feature dimension in attention step\n        n_steps: Number of decision steps\n        gamma: Information routing parameter\n        cat_idxs: List of categorical feature indices\n        cat_dims: List of categorical feature dimensions\n        n_independent: Number of independent feature transformers\n        n_shared: Number of shared feature transformers\n    \"\"\"\n    \n    def __init__(self, \n                input_dim: int,\n                output_dim: int = 1,\n                n_d: int = 64,\n                n_a: int = 64,\n                n_steps: int = 3,\n                gamma: float = 1.3,\n                cat_idxs: List[int] = None,\n                cat_dims: List[int] = None,\n                n_independent: int = 2,\n                n_shared: int = 2):\n        \"\"\"\n        Initialize TabNetModel.\n        \n        Args:\n            input_dim: Input dimension\n            output_dim: Output dimension\n            n_d: Feature dimension in decision step\n            n_a: Feature dimension in attention step\n            n_steps: Number of decision steps\n            gamma: Information routing parameter\n            cat_idxs: List of categorical feature indices\n            cat_dims: List of categorical feature dimensions\n            n_independent: Number of independent feature transformers\n            n_shared: Number of shared feature transformers\n        \"\"\"\n        super().__init__()\n        \n        self.input_dim = input_dim\n        self.output_dim = output_dim\n        self.n_d = n_d\n        self.n_a = n_a\n        self.n_steps = n_steps\n        self.gamma = gamma\n        \n        # Initialize feature preprocessing (simplified)\n        self.initial_fc = nn.Linear(input_dim, n_d + n_a)\n        self.initial_bn = nn.BatchNorm1d(n_d + n_a)\n        \n        # Initialize feature transformers\n        self.independent_fcs = nn.ModuleList([\n            nn.Sequential(\n                nn.Linear(n_d + n_a, n_d + n_a),\n                nn.BatchNorm1d(n_d + n_a),\n                nn.ReLU()\n            ) for _ in range(n_independent)\n        ])\n        \n        self.shared_fcs = nn.ModuleList([\n            nn.Sequential(\n                nn.Linear(n_d + n_a, n_d + n_a),\n                nn.BatchNorm1d(n_d + n_a),\n                nn.ReLU()\n            ) for _ in range(n_shared)\n        ])\n        \n        # Initialize attentive transformer and feature transformer (simplified)\n        self.attentive_transformer = nn.ModuleList([\n            nn.Sequential(\n                nn.Linear(n_a, input_dim),\n                nn.BatchNorm1d(input_dim),\n                nn.Sigmoid()\n            ) for _ in range(n_steps)\n        ])\n        \n        # Output layer for final prediction\n        self.final_fc = nn.Linear(n_d * n_steps, output_dim)\n    \n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        \"\"\"\n        Forward pass.\n        \n        Args:\n            x: Input tensor\n            \n        Returns:\n            Output tensor\n        \"\"\"\n        batch_size = x.size(0)\n        \n        # Initial feature transformation\n        x_processed = self.initial_fc(x)\n        x_processed = self.initial_bn(x_processed)\n        x_processed = F.relu(x_processed)\n        \n        # Apply feature transformations\n        for independent, shared in zip(self.independent_fcs, self.shared_fcs):\n            x_processed = independent(x_processed) + shared(x_processed)\n        \n        # TabNet decision step\n        steps_output = []\n        prior = torch.ones(batch_size, self.input_dim).to(x.device)\n        \n        for step in range(self.n_steps):\n            # Split features for decision and attention\n            d = x_processed[:, :self.n_d]\n            a = x_processed[:, self.n_d:]\n            \n            # Calculate attention mask\n            mask = self.attentive_transformer[step](a)\n            mask = mask * prior\n            prior = prior * (self.gamma - mask)\n            \n            # Apply mask to input\n            masked_x = x * mask\n            \n            # Process masked input (simplified)\n            masked_x_processed = self.initial_fc(masked_x)\n            masked_x_processed = self.initial_bn(masked_x_processed)\n            masked_x_processed = F.relu(masked_x_processed)\n            \n            for independent, shared in zip(self.independent_fcs, self.shared_fcs):\n                masked_x_processed = independent(masked_x_processed) + shared(masked_x_processed)\n            \n            # Get decision output\n            d_masked = masked_x_processed[:, :self.n_d]\n            steps_output.append(d_masked)\n        \n        # Concatenate step outputs\n        out = torch.cat(steps_output, dim=1)\n        \n        # Final output layer\n        out = self.final_fc(out)\n        \n        return out\n\n\nclass GeM(nn.Module):\n    \"\"\"\n    Generalized Mean Pooling layer.\n    \n    Attributes:\n        p: Pooling parameter (learnable)\n        eps: Small value to prevent numerical issues\n    \"\"\"\n    def __init__(self, p=3, eps=1e-6):\n        super(GeM, self).__init__()\n        self.p = nn.Parameter(torch.ones(1) * p)\n        self.eps = eps\n\n    def forward(self, x):\n        bs, ch, h, w = x.shape\n        x = F.avg_pool2d(x.clamp(min=self.eps).pow(self.p), (x.size(-2), x.size(-1))).pow(\n            1.0 / self.p)\n        x = x.view(bs, ch)\n        return x\n\n\nclass BirdCLEFModel(nn.Module):\n    \"\"\"\n    CNN model with backbone from timm and GeM pooling for BirdCLEF competition.\n    \n    Attributes:\n        backbone: CNN backbone from timm\n        global_pools: Pooling layers\n        neck: Batch normalization layer\n        head: Fully connected head\n    \"\"\"\n    def __init__(self, \n                 num_classes: int, \n                 backbone: str = 'eca_nfnet_l0', \n                 pretrained: bool = True):\n        \"\"\"\n        Initialize BirdCLEFModel.\n        \n        Args:\n            num_classes: Number of output classes\n            backbone: Backbone model name from timm\n            pretrained: Whether to use pretrained weights\n        \"\"\"\n        super().__init__()\n\n        out_indices = (3, 4)\n        self.backbone = timm.create_model(\n            backbone,\n            features_only=True,\n            pretrained=pretrained,\n            in_chans=3,\n            num_classes=num_classes,\n            out_indices=out_indices,\n        )\n        feature_dims = self.backbone.feature_info.channels()\n\n        self.global_pools = nn.ModuleList([GeM() for _ in out_indices])\n        self.mid_features = sum(feature_dims)\n        self.neck = nn.BatchNorm1d(self.mid_features)\n        self.head = nn.Linear(self.mid_features, num_classes)\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        \"\"\"\n        Forward pass.\n        \n        Args:\n            x: Input tensor (spectrograms)\n            \n        Returns:\n            Output logits\n        \"\"\"\n        ms = self.backbone(x)\n        h = torch.cat([global_pool(m) for m, global_pool in zip(ms, self.global_pools)], dim=1)\n        x = self.neck(h)\n        x = self.head(x)\n        return x\n\n\ndef get_model(cfg) -> nn.Module:\n    \"\"\"\n    Get model based on configuration.\n    \n    Args:\n        cfg: Configuration\n        \n    Returns:\n        PyTorch model\n    \"\"\"\n    model_name = cfg.model[\"name\"].lower()\n    \n    taxonomy_df = pd.read_csv(cfg.data[\"taxonomy_csv\"])\n    species_ids = taxonomy_df['primary_label'].tolist()\n    num_classes = len(species_ids)\n\n    if model_name == 'birdclef':\n        return BirdCLEFModel(\n            num_classes=num_classes,\n            backbone=cfg.model[\"backbone\"],\n            pretrained=cfg.model[\"pretrained\"]\n        )\n    else:\n        raise ValueError(f\"Model {model_name} not supported\") ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:31.368218Z","iopub.execute_input":"2025-03-22T12:47:31.368423Z","iopub.status.idle":"2025-03-22T12:47:34.556554Z","shell.execute_reply.started":"2025-03-22T12:47:31.368405Z","shell.execute_reply":"2025-03-22T12:47:34.555906Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Utils","metadata":{}},{"cell_type":"code","source":"def read_wav(path, sample_rate=32000) -> torch.Tensor:\n    \"\"\"\n    Read and normalize a wav file.\n    \n    Args:\n        path: Path to wav file\n        sample_rate: Target sample rate\n        \n    Returns:\n        Normalized audio tensor\n    \"\"\"\n    wav, org_sr = torchaudio.load(path, normalize=True)\n    wav = torchaudio.functional.resample(wav, orig_freq=org_sr, new_freq=sample_rate)\n    return wav","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.557650Z","iopub.execute_input":"2025-03-22T12:47:34.557900Z","iopub.status.idle":"2025-03-22T12:47:34.561599Z","shell.execute_reply.started":"2025-03-22T12:47:34.557879Z","shell.execute_reply":"2025-03-22T12:47:34.560806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_device() -> torch.device:\n    \"\"\"\n    Get device for PyTorch.\n    \n    Returns:\n        PyTorch device\n    \"\"\"\n    if torch.cuda.is_available():\n        return torch.device(\"cuda\")\n    else:\n        return torch.device(\"cpu\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.562327Z","iopub.execute_input":"2025-03-22T12:47:34.562531Z","iopub.status.idle":"2025-03-22T12:47:34.607592Z","shell.execute_reply.started":"2025-03-22T12:47:34.562509Z","shell.execute_reply":"2025-03-22T12:47:34.606772Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_checkpoint(checkpoint_path: str, \n                   model: torch.nn.Module, \n                   optimizer: Optional[torch.optim.Optimizer] = None) -> Tuple[torch.nn.Module, Dict[str, Any]]:\n    \"\"\"\n    Load model checkpoint.\n    \n    Args:\n        checkpoint_path: Path to checkpoint file\n        model: PyTorch model\n        optimizer: PyTorch optimizer (optional)\n        \n    Returns:\n        Tuple of model and checkpoint info\n    \"\"\"\n    # Load checkpoint\n    checkpoint = torch.load(checkpoint_path, map_location=get_device())\n    \n    # Load model state\n    model.load_state_dict(checkpoint[\"model_state_dict\"])\n    \n    # Load optimizer state if provided\n    if optimizer is not None:\n        optimizer.load_state_dict(checkpoint[\"optimizer_state_dict\"])\n    \n    # Extract checkpoint info\n    checkpoint_info = {\n        \"epoch\": checkpoint[\"epoch\"],\n        \"loss\": checkpoint[\"loss\"]\n    }\n    \n    # Add any additional metrics\n    for key, value in checkpoint.items():\n        if key not in [\"epoch\", \"model_state_dict\", \"optimizer_state_dict\", \"loss\", \"config\"]:\n            checkpoint_info[key] = value\n    \n    return model, checkpoint_info","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.608375Z","iopub.execute_input":"2025-03-22T12:47:34.608628Z","iopub.status.idle":"2025-03-22T12:47:34.626509Z","shell.execute_reply.started":"2025-03-22T12:47:34.608608Z","shell.execute_reply":"2025-03-22T12:47:34.625855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def crop_or_pad_wav(wav, duration):\n    \"\"\"\n    Crop or pad wav to desired duration.\n    \n    Args:\n        wav: Audio tensor\n        duration: Target duration in samples\n        \n    Returns:\n        Audio tensor of length duration\n    \"\"\"\n    while wav.size(-1) < duration:\n        wav = torch.cat([wav, wav], dim=1)\n    wav = wav[:, :duration]\n    return wav","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.627245Z","iopub.execute_input":"2025-03-22T12:47:34.627490Z","iopub.status.idle":"2025-03-22T12:47:34.642115Z","shell.execute_reply.started":"2025-03-22T12:47:34.627470Z","shell.execute_reply":"2025-03-22T12:47:34.641303Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalize_melspec(X, eps=1e-6):\n    \"\"\"\n    Normalize mel spectrogram.\n    \n    Args:\n        X: Mel spectrogram tensor\n        eps: Small value to prevent division by zero\n        \n    Returns:\n        Normalized mel spectrogram\n    \"\"\"\n    mean = X.mean((1, 2), keepdim=True)\n    std = X.std((1, 2), keepdim=True)\n    Xstd = (X - mean) / (std + eps)\n\n    norm_min, norm_max = (\n        Xstd.min(-1)[0].min(-1)[0],\n        Xstd.max(-1)[0].max(-1)[0],\n    )\n    fix_ind = (norm_max - norm_min) > eps * torch.ones_like(\n        (norm_max - norm_min)\n    )\n    V = torch.zeros_like(Xstd)\n    if fix_ind.sum():\n        V_fix = Xstd[fix_ind]\n        norm_max_fix = norm_max[fix_ind, None, None]\n        norm_min_fix = norm_min[fix_ind, None, None]\n        V_fix = torch.max(\n            torch.min(V_fix, norm_max_fix),\n            norm_min_fix,\n        )\n        V_fix = (V_fix - norm_min_fix) / (norm_max_fix - norm_min_fix)\n        V[fix_ind] = V_fix\n    return V","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.643948Z","iopub.execute_input":"2025-03-22T12:47:34.644141Z","iopub.status.idle":"2025-03-22T12:47:34.657167Z","shell.execute_reply.started":"2025-03-22T12:47:34.644124Z","shell.execute_reply":"2025-03-22T12:47:34.656562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_submission_df(\n    row_ids: List[str],\n    predictions: np.ndarray,\n    sample_submission_df: pd.DataFrame\n) -> pd.DataFrame:\n    \"\"\"\n    Create submission DataFrame.\n    \n    Args:\n        row_ids: List of row IDs\n        predictions: Model predictions\n        sample_submission_df: Sample submission dataframe\n        \n    Returns:\n        Submission DataFrame\n    \"\"\"\n    # Get columns from sample submission\n    columns = sample_submission_df.columns\n    \n    # Create DataFrame with predictions\n    submission_df = pd.DataFrame(predictions, columns=columns[1:])\n    \n    # Add row_id column\n    submission_df[\"row_id\"] = row_ids\n    \n    # Reorder columns to match sample submission\n    submission_df = submission_df[columns]\n    \n    # Ensure all required row_ids are present\n    sample_ids = set(sample_submission_df[\"row_id\"])\n    submission_ids = set(submission_df[\"row_id\"])\n    \n    if sample_ids != submission_ids:\n        missing_ids = sample_ids - submission_ids\n        if missing_ids:\n            print(f\"Warning: {len(missing_ids)} row_ids from sample submission are missing in predictions.\")\n    \n    return submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.658174Z","iopub.execute_input":"2025-03-22T12:47:34.658379Z","iopub.status.idle":"2025-03-22T12:47:34.675947Z","shell.execute_reply.started":"2025-03-22T12:47:34.658361Z","shell.execute_reply":"2025-03-22T12:47:34.675143Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def save_submission(submission_df: pd.DataFrame) -> str:\n    \"\"\"\n    Save submission dataframe to file.\n    \n    Args:\n        submission_df: Submission dataframe\n        cfg: Configuration\n        \n    Returns:\n        Path to saved submission file\n    \"\"\"\n    submission_filepath = \"submission.csv\"\n    # Save submission\n    submission_df.to_csv(submission_filepath, index=False)\n\n    return submission_filepath","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.676584Z","iopub.execute_input":"2025-03-22T12:47:34.676812Z","iopub.status.idle":"2025-03-22T12:47:34.693927Z","shell.execute_reply.started":"2025-03-22T12:47:34.676793Z","shell.execute_reply":"2025-03-22T12:47:34.693095Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"def generate_predictions(cfg) -> pd.DataFrame:\n    \"\"\"\n    Generate predictions using a trained model.\n    \n    Args:\n        cfg: Configuration\n        \n    Returns:\n        DataFrame with predictions\n    \"\"\"\n    # Get device\n    device = get_device()\n    print(f\"Using device: {device}\")\n    \n    # BirdCLEF specific data loading\n    print(\"Loading test data...\")\n    \n    # Load test data\n    if DEBUG:\n        test_path = os.path.join(cfg.paths[\"data_dir\"], \"train_soundscapes/\")\n        test_files = list(Path(test_path).glob('*.ogg'))[:N_DEBUG]\n    else:\n        test_path = os.path.join(cfg.paths[\"data_dir\"], \"test_soundscapes/\")\n        test_files = list(Path(test_path).glob('*.ogg'))\n    \n    # Get sample submission for target columns\n    sample_submission_df = pd.read_csv(cfg.data[\"sample_submission_file\"])\n    target_columns = sample_submission_df.columns.tolist()[1:]\n    bird2id = {b: i for i, b in enumerate(target_columns)}\n    \n    # Set up for processing audio into segments\n    print(\"Processing audio files into segments...\")\n    \n    transforms_test = A.Compose([\n        A.Resize(cfg.data[\"image_size\"], cfg.data[\"image_size\"]),\n        A.Normalize()\n    ])\n    \n    # Transforms\n    mel_transform = torchaudio.transforms.MelSpectrogram(**cfg.data[\"mel_spec_params\"])\n    db_transform = torchaudio.transforms.AmplitudeToDB(stype='power', top_db=cfg.data[\"top_db\"])\n    \n    # Process each soundscape file\n    all_row_ids = []\n    all_predictions = []\n    \n    # Load model(s)\n    if cfg.inference[\"ensemble\"]:\n        print(\"Creating ensemble of models...\")\n        models = []\n        \n        for fold in range(cfg.data[\"num_folds\"]):\n            # Load model for this fold\n            checkpoint_path = os.path.join(cfg.paths[\"model_dir\"], f\"fold_{fold}.bin\")\n            if not os.path.exists(checkpoint_path):\n                print(f\"Model file not found at {checkpoint_path}, skipping fold {fold}\")\n                continue\n                \n            print(f\"Loading model from {checkpoint_path}...\")\n            model = get_model(cfg)\n            model, _ = load_checkpoint(checkpoint_path, model)\n            model = model.to(device)\n            models.append(model)\n    else:\n        fold = cfg.data[\"fold\"]\n        # Determine checkpoint path for a single model\n        if cfg.inference[\"checkpoint\"] == 'best':\n            checkpoint_path = os.path.join(cfg.paths[\"model_dir\"], f\"fold_{fold}.bin\")\n        else:\n            checkpoint_path = os.path.join(cfg.paths[\"model_dir\"], f\"fold_{fold}_final.bin\")\n        \n        # Load model\n        print(f\"Loading model from {checkpoint_path}...\")\n        model = get_model(cfg)\n        model, _ = load_checkpoint(checkpoint_path, model)\n        model = model.to(device)\n        models = [model]\n    \n    # Process each soundscape\n    for audio_path in tqdm(test_files, desc=\"Processing soundscapes\"):\n        soundscape_id = audio_path.stem\n        print(f\"Processing {soundscape_id}\")\n        \n        try:\n            # Load audio read_wav(path)\n            wav = read_wav(audio_path)\n            \n            # get length of audio\n            channels, n_samples = wav.shape\n            \n            print(f\"Channels: {channels}, Samples: {n_samples}\")\n            \n            # Determine total segments\n            window_size = cfg.data[\"train_duration\"]  # duration sample size = 5 * sample_rates\n            total_segments = int(n_samples / window_size)\n            \n            segment_specs = []\n            segment_row_ids = []\n            \n            print(f\"Total segments: {total_segments}\")\n            \n            # Process each 5-second segment\n            for segment_idx in range(total_segments):\n                start_sample = segment_idx * window_size\n                end_sample = start_sample + window_size\n                segment_audio = wav[:, start_sample:end_sample]\n                \n                # Crop or pad audio\n                segment_audio = crop_or_pad_wav(segment_audio, cfg.data[\"train_duration\"])\n                \n                # Create mel spectrogram\n                mel_spectrogram = normalize_melspec(db_transform(mel_transform(segment_audio)))\n                mel_spectrogram = mel_spectrogram * 255\n                mel_spectrogram = mel_spectrogram.expand(3, -1, -1).permute(1, 2, 0).numpy()\n                \n                end_time_sec = int((segment_idx + 1) * window_size / cfg.data[\"mel_spec_params\"][\"sample_rate\"])\n                row_id = f\"soundscape_{soundscape_id}_{end_time_sec}\"\n                segment_row_ids.append(row_id)\n                \n                # Apply transforms\n                if transforms_test is not None:\n                    res = transforms_test(image=mel_spectrogram)\n                    mel_spectrogram = res['image'].astype(np.float32)\n                else:\n                    mel_spectrogram = mel_spectrogram.astype(np.float32)\n\n                mel_spectrogram = mel_spectrogram.transpose(2, 0, 1)\n                \n                segment_specs.append(mel_spectrogram)\n            \n            # Batch processing\n            batch_size = cfg.inference[\"batch_size\"]\n            for i in range(0, len(segment_specs), batch_size):\n                batch_specs = segment_specs[i:i+batch_size]\n                batch_row_ids = segment_row_ids[i:i+batch_size]\n                \n                # Create tensor batch\n                batch_tensor = torch.tensor(np.array(batch_specs), dtype=torch.float32).to(device)\n                \n                # Run inference for each model\n                batch_predictions = []\n                for model in models:\n                    model.eval()\n                    with torch.no_grad():\n                        outputs = model(batch_tensor)\n                        preds = outputs.sigmoid().cpu().detach().numpy()\n                        batch_predictions.append(preds)\n                \n                # Average predictions if ensemble\n                if len(models) > 1:\n                    batch_preds = np.mean(batch_predictions, axis=0)\n                else:\n                    batch_preds = batch_predictions[0]\n                \n                # Add to results\n                all_predictions.extend(batch_preds)\n                all_row_ids.extend(batch_row_ids)\n                \n        except Exception as e:\n            print(f\"Error processing {audio_path}: {e}\")\n    \n    # Create submission DataFrame\n    print(\"Creating submission dataframe...\")\n    print(f\"Predictions count: {len(all_predictions)}\")\n    submission_df = create_submission_df(all_row_ids, all_predictions, sample_submission_df)\n    \n    return submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.694634Z","iopub.execute_input":"2025-03-22T12:47:34.694864Z","iopub.status.idle":"2025-03-22T12:47:34.710580Z","shell.execute_reply.started":"2025-03-22T12:47:34.694845Z","shell.execute_reply":"2025-03-22T12:47:34.709648Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Main","metadata":{}},{"cell_type":"code","source":"def main():\n    # Load configuration\n    cfg = CFG()\n    \n    # Generate predictions\n    submission_df = generate_predictions(cfg)\n    \n    # Save submission\n    submission_filepath = save_submission(submission_df)\n    print(f\"Submission saved to {submission_filepath}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.711319Z","iopub.execute_input":"2025-03-22T12:47:34.711509Z","iopub.status.idle":"2025-03-22T12:47:34.730039Z","shell.execute_reply.started":"2025-03-22T12:47:34.711492Z","shell.execute_reply":"2025-03-22T12:47:34.729246Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T12:47:34.730888Z","iopub.execute_input":"2025-03-22T12:47:34.731125Z","iopub.status.idle":"2025-03-22T12:47:41.563637Z","shell.execute_reply.started":"2025-03-22T12:47:34.731097Z","shell.execute_reply":"2025-03-22T12:47:41.562993Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}