{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":11828260,"sourceType":"datasetVersion","datasetId":7430593},{"sourceId":407521,"sourceType":"modelInstanceVersion","modelInstanceId":316555,"modelId":337067},{"sourceId":426275,"sourceType":"modelInstanceVersion","modelInstanceId":347494,"modelId":368752}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false},"papermill":{"default_parameters":{},"duration":27.447062,"end_time":"2025-03-12T14:13:11.647927","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-03-12T14:12:44.200865","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Libraries #","metadata":{}},{"cell_type":"code","source":"import os\nimport warnings\nimport logging\nimport time\nimport math\nimport cv2\nfrom pathlib import Path\nimport gc\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport timm\nimport torch.nn.functional as F\nimport torchaudio\nimport torchaudio.transforms as AT\nfrom contextlib import contextmanager\nimport concurrent.futures\nfrom typing import Union\nimport librosa\nimport timm\nfrom tqdm.auto import tqdm\n\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.279502Z","iopub.execute_input":"2025-06-05T23:57:25.280026Z","iopub.status.idle":"2025-06-05T23:57:25.286429Z","shell.execute_reply.started":"2025-06-05T23:57:25.279989Z","shell.execute_reply":"2025-06-05T23:57:25.285138Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Post-Processing Function","metadata":{}},{"cell_type":"code","source":"def apply_power_to_low_ranked_cols(\n    p: np.ndarray,\n    top_k: int = 30,\n    exponent: float = 2,\n    inplace: bool = True\n) -> np.ndarray:\n    \"\"\"\n    Apply a power transformation to low-ranked class columns in the prediction matrix.\n    \"\"\"\n    if not inplace:\n        p = p.copy()\n\n    tail_cols = np.argsort(-p.max(axis=0))[top_k:]\n    p[:, tail_cols] = p[:, tail_cols] ** exponent\n    return p\n\ndef smooth_chunk_predictions(predictions: np.ndarray) -> np.ndarray:\n    \"\"\"\n    Apply temporal smoothing to a (chunks x classes) prediction array.\n    \"\"\"\n    new_predictions = predictions.copy()\n    for i in range(1, predictions.shape[0] - 1):\n        new_predictions[i] = (\n            0.2 * predictions[i - 1] +\n            0.6 * predictions[i] +\n            0.2 * predictions[i + 1]\n        )\n    new_predictions[0] = 0.8 * predictions[0] + 0.2 * predictions[1]\n    new_predictions[-1] = 0.8 * predictions[-1] + 0.2 * predictions[-2]\n    return new_predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.287851Z","iopub.execute_input":"2025-06-05T23:57:25.288147Z","iopub.status.idle":"2025-06-05T23:57:25.317129Z","shell.execute_reply.started":"2025-06-05T23:57:25.288121Z","shell.execute_reply":"2025-06-05T23:57:25.315862Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Configuration settings","metadata":{}},{"cell_type":"code","source":"class CFG:\n \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_path = '/kaggle/input/eafewaf/gemmacpp/default/1/Pseudo Labeled EfficientNet with Power Adjustment'  \n    \n    # Audio parameters\n    FS = 32000  \n    WINDOW_SIZE = 5  \n    \n    # Mel spectrogram parameters\n    N_FFT = 1024\n    HOP_LENGTH = 512\n    N_MELS = 128\n    FMIN = 50\n    FMAX = 14000\n    TARGET_SHAPE = (256, 256)\n    \n    model_name = 'efficientnet_b0'\n    in_channels = 1\n    device = 'cpu'  \n    \n    # Inference parameters\n    batch_size = 16\n    use_tta = False  \n    tta_count = 3   \n    threshold = 0.5\n    \n    use_specific_folds = False  # If False, use all found models\n    folds = [0, 1]  # Used only if use_specific_folds is True\n    \n    debug = False\n    debug_count = 3\n\ncfg = CFG()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.319986Z","iopub.execute_input":"2025-06-05T23:57:25.320325Z","iopub.status.idle":"2025-06-05T23:57:25.342966Z","shell.execute_reply.started":"2025-06-05T23:57:25.320299Z","shell.execute_reply":"2025-06-05T23:57:25.341533Z"}},"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-05T23:57:25.344514Z","iopub.execute_input":"2025-06-05T23:57:25.344954Z","iopub.status.idle":"2025-06-05T23:57:25.385836Z","shell.execute_reply.started":"2025-06-05T23:57:25.344918Z","shell.execute_reply":"2025-06-05T23:57:25.384277Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"class _BirdCLEFModel(nn.Module):\n    def __init__(self, cfg, num_classes):\n        super().__init__()\n        self.cfg = cfg\n        \n        self.backbone = timm.create_model(\n            cfg.model_name,\n            pretrained=False,  \n            in_chans=cfg.in_channels,\n            drop_rate=0.0,    \n            drop_path_rate=0.0\n        )\n        \n        if 'efficientnet' in cfg.model_name:\n            backbone_out = self.backbone.classifier.in_features\n            self.backbone.classifier = nn.Identity()\n        elif 'resnet' in cfg.model_name:\n            backbone_out = self.backbone.fc.in_features\n            self.backbone.fc = nn.Identity()\n        else:\n            backbone_out = self.backbone.get_classifier().in_features\n            self.backbone.reset_classifier(0, '')\n        \n        self.pooling = nn.AdaptiveAvgPool2d(1)\n        self.feat_dim = backbone_out\n        self.classifier = nn.Linear(backbone_out, num_classes)\n        \n    def forward(self, x):\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        \nclass BirdCLEFModel(nn.Module):\n    def __init__(self, cfg, num_classes):\n        super().__init__()\n        self.model = _BirdCLEFModel(cfg, num_classes)\n\n    def forward(self, x):\n        return self.model(x)","metadata":{"papermill":{"duration":2.175154,"end_time":"2025-03-12T14:13:02.578522","exception":false,"start_time":"2025-03-12T14:13:00.403368","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.387163Z","iopub.execute_input":"2025-06-05T23:57:25.387470Z","iopub.status.idle":"2025-06-05T23:57:25.397924Z","shell.execute_reply.started":"2025-06-05T23:57:25.387444Z","shell.execute_reply":"2025-06-05T23:57:25.396633Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Utility Functions","metadata":{}},{"cell_type":"code","source":"def audio2melspec(audio_data, cfg):\n    \"\"\"Convert audio data to mel spectrogram\"\"\"\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=cfg.FS,\n        n_fft=cfg.N_FFT,\n        hop_length=cfg.HOP_LENGTH,\n        n_mels=cfg.N_MELS,\n        fmin=cfg.FMIN,\n        fmax=cfg.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\ndef process_audio_segment(audio_data, cfg):\n    \"\"\"Process audio segment to get mel spectrogram\"\"\"\n    if len(audio_data) < cfg.FS * cfg.WINDOW_SIZE:\n        audio_data = np.pad(audio_data, \n                          (0, cfg.FS * cfg.WINDOW_SIZE - len(audio_data)), \n                          mode='constant')\n    \n    mel_spec = audio2melspec(audio_data, cfg)\n    \n    # Resize if needed\n    if mel_spec.shape != cfg.TARGET_SHAPE:\n        mel_spec = cv2.resize(mel_spec, cfg.TARGET_SHAPE, interpolation=cv2.INTER_LINEAR)\n        \n    return mel_spec.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.399014Z","iopub.execute_input":"2025-06-05T23:57:25.399399Z","iopub.status.idle":"2025-06-05T23:57:25.426597Z","shell.execute_reply.started":"2025-06-05T23:57:25.399372Z","shell.execute_reply":"2025-06-05T23:57:25.425397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def find_model_files(cfg):\n    \"\"\"\n    Find all .pth model files in the specified model directory\n    \"\"\"\n    model_files = []\n    \n    model_dir = Path(cfg.model_path)\n    \n    for path in model_dir.glob('**/*.ckpt'):\n        model_files.append(str(path))\n    \n    return model_files\n\ndef load_models(cfg, num_classes):\n    \"\"\"\n    Load all found model files and prepare them for ensemble\n    \"\"\"\n    models = []\n    \n    model_files = find_model_files(cfg)\n    \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    if cfg.use_specific_folds:\n        filtered_files = []\n        for fold in cfg.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        print(f\"Using {len(model_files)} model files for the specified folds ({cfg.folds}).\")\n    \n    for model_path in model_files:\n        try:\n            print(f\"Loading model: {model_path}\")\n            checkpoint = torch.load(model_path, map_location=torch.device(cfg.device))\n            \n            model = BirdCLEFModel(cfg, num_classes)\n            model.load_state_dict(checkpoint['state_dict'])\n            model = model.to(cfg.device)\n            model.eval()\n            \n            models.append(model)\n        except Exception as e:\n            print(f\"Error loading model {model_path}: {e}\")\n    \n    return models\n\ndef predict_on_spectrogram(audio_path, models, cfg, species_ids):\n    \"\"\"Process a single audio file and predict species presence for each 5-second segment\"\"\"\n    predictions = []\n    row_ids = []\n    soundscape_id = Path(audio_path).stem\n    \n    try:\n        print(f\"Processing {soundscape_id}\")\n        audio_data, _ = librosa.load(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            if cfg.use_tta:\n                all_preds = []\n                \n                for tta_idx in range(cfg.tta_count):\n                    mel_spec = process_audio_segment(segment_audio, cfg)\n                    mel_spec = apply_tta(mel_spec, tta_idx)\n\n                    mel_spec = torch.tensor(mel_spec, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n                    mel_spec = mel_spec.to(cfg.device)\n\n                    if len(models) == 1:\n                        with torch.no_grad():\n                            outputs = models[0](mel_spec)\n                            probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                            all_preds.append(probs)\n                    else:\n                        segment_preds = []\n                        for model in models:\n                            with torch.no_grad():\n                                outputs = model(mel_spec)\n                                probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                                segment_preds.append(probs)\n                        \n                        avg_preds = np.mean(segment_preds, axis=0)\n                        all_preds.append(avg_preds)\n\n                final_preds = np.mean(all_preds, axis=0)\n            else:\n                mel_spec = process_audio_segment(segment_audio, cfg)\n                \n                mel_spec = torch.tensor(mel_spec, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n                mel_spec = mel_spec.to(cfg.device)\n                \n                if len(models) == 1:\n                    with torch.no_grad():\n                        outputs = models[0](mel_spec)\n                        final_preds = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                else:\n                    segment_preds = []\n                    for model in models:\n                        with torch.no_grad():\n                            outputs = model(mel_spec)\n                            probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                            segment_preds.append(probs)\n\n                    final_preds = np.mean(segment_preds, axis=0)\n                    \n            predictions.append(final_preds)\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-05T23:57:25.428845Z","iopub.execute_input":"2025-06-05T23:57:25.429239Z","iopub.status.idle":"2025-06-05T23:57:25.456575Z","shell.execute_reply.started":"2025-06-05T23:57:25.429199Z","shell.execute_reply":"2025-06-05T23:57:25.455470Z"}},"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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.457987Z","iopub.execute_input":"2025-06-05T23:57:25.458346Z","iopub.status.idle":"2025-06-05T23:57:25.483869Z","shell.execute_reply.started":"2025-06-05T23:57:25.458316Z","shell.execute_reply":"2025-06-05T23:57:25.482205Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Predict","metadata":{}},{"cell_type":"code","source":"def run_inference(cfg, models, species_ids):\n    \"\"\"Run inference on all test soundscapes\"\"\"\n    test_files = list(Path(cfg.test_soundscapes).glob('*.ogg'))\n\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    for audio_path in tqdm(test_files):\n        row_ids, predictions = predict_on_spectrogram(str(audio_path), models, cfg, species_ids)\n\n        # --- ADD THIS BLOCK BELOW ---\n        preds_array = np.stack(predictions)  # shape (n_chunks, n_classes)\n        preds_array = apply_power_to_low_ranked_cols(preds_array, top_k=30, exponent=2)\n        preds_array = smooth_chunk_predictions(preds_array)\n        predictions = [row for row in preds_array]  # convert back to list of arrays\n        # --- END BLOCK ---\n\n        all_row_ids.extend(row_ids)\n        all_predictions.extend(predictions)\n\n    return all_row_ids, all_predictions\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.485088Z","iopub.execute_input":"2025-06-05T23:57:25.485480Z","iopub.status.idle":"2025-06-05T23:57:25.513121Z","shell.execute_reply.started":"2025-06-05T23:57:25.485440Z","shell.execute_reply":"2025-06-05T23:57:25.511905Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.514482Z","iopub.execute_input":"2025-06-05T23:57:25.514986Z","iopub.status.idle":"2025-06-05T23:57:25.541301Z","shell.execute_reply.started":"2025-06-05T23:57:25.514943Z","shell.execute_reply":"2025-06-05T23:57:25.539965Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    start_time = time.time()\n    print(\"Starting BirdCLEF-2025 inference...\")\n    print(f\"TTA enabled: {cfg.use_tta} (variations: {cfg.tta_count if cfg.use_tta else 0})\")\n\n    models = load_models(cfg, num_classes)\n    \n    if not models:\n        print(\"No models found! Please check model paths.\")\n        return\n    \n    print(f\"Model usage: {'Single model' if len(models) == 1 else f'Ensemble of {len(models)} models'}\")\n\n    row_ids, predictions = run_inference(cfg, models, species_ids)\n\n    submission_df = create_submission(row_ids, predictions, species_ids, cfg)\n\n    submission_path = 'submission.csv'\n    submission_df.to_csv(submission_path, index=False)\n    print(f\"Submission saved to {submission_path}\")\n    \n    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-05T23:57:25.542368Z","iopub.execute_input":"2025-06-05T23:57:25.542711Z","iopub.status.idle":"2025-06-05T23:57:25.572698Z","shell.execute_reply.started":"2025-06-05T23:57:25.542674Z","shell.execute_reply":"2025-06-05T23:57:25.571718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-05T23:57:25.573829Z","iopub.execute_input":"2025-06-05T23:57:25.574131Z","iopub.status.idle":"2025-06-05T23:57:27.658782Z","shell.execute_reply.started":"2025-06-05T23:57:25.574100Z","shell.execute_reply":"2025-06-05T23:57:27.657632Z"}},"outputs":[],"execution_count":null}]}