{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":367145,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":304337,"modelId":324817}],"dockerImageVersionId":31012,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"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\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:27.973973Z","iopub.execute_input":"2025-05-01T02:18:27.974460Z","iopub.status.idle":"2025-05-01T02:18:27.980794Z","shell.execute_reply.started":"2025-05-01T02:18:27.974428Z","shell.execute_reply":"2025-05-01T02:18:27.979720Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apply_power_to_low_ranked_cols(\n    p: np.ndarray,\n    top_k: int = 30,\n    exponent: float = 2.0,\n    inplace: bool = True\n) -> np.ndarray:\n    if not inplace:\n        p = p.copy()\n    tail_cols = np.argsort(-p.max(axis=0))[top_k:]\n    p[:, tail_cols] = p[:, tail_cols] ** exponent\n    return p","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"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/efficientnetb0_birdclef_trained/pytorch/default/1'  \n    \n    FS = 32000  \n    WINDOW_SIZE = 5  \n    \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    batch_size = 16\n    threshold = 0.5\n    \ncfg = CFG()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:27.982596Z","iopub.execute_input":"2025-05-01T02:18:27.982874Z","iopub.status.idle":"2025-05-01T02:18:28.001720Z","shell.execute_reply.started":"2025-05-01T02:18:27.982843Z","shell.execute_reply":"2025-05-01T02:18:28.000477Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Using device: {cfg.device}\")\ntaxonomy_df = pd.read_csv(cfg.taxonomy_csv)\nspecies_ids = taxonomy_df['primary_label'].tolist()\nnum_classes = len(species_ids)\nprint(f\"Number of classes: {num_classes}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:28.004176Z","iopub.execute_input":"2025-05-01T02:18:28.004566Z","iopub.status.idle":"2025-05-01T02:18:28.027362Z","shell.execute_reply.started":"2025-05-01T02:18:28.004541Z","shell.execute_reply":"2025-05-01T02:18:28.026286Z"}},"outputs":[],"execution_count":null},{"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        backbone_out = self.backbone.classifier.in_features\n        self.backbone.classifier = nn.Identity()\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:28.028419Z","iopub.execute_input":"2025-05-01T02:18:28.028756Z","iopub.status.idle":"2025-05-01T02:18:28.038585Z","shell.execute_reply.started":"2025-05-01T02:18:28.028713Z","shell.execute_reply":"2025-05-01T02:18:28.037746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def audio2melspec(audio_data, cfg):\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    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-05-01T02:18:28.040922Z","iopub.execute_input":"2025-05-01T02:18:28.041229Z","iopub.status.idle":"2025-05-01T02:18:28.063313Z","shell.execute_reply.started":"2025-05-01T02:18:28.041188Z","shell.execute_reply":"2025-05-01T02:18:28.062417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def find_model_files(cfg):\n\n    model_files = []\n    \n    model_dir = Path(cfg.model_path)\n    \n    for path in model_dir.glob('**/*.pth'):\n        model_files.append(str(path))\n    \n    return model_files","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:28.064434Z","iopub.execute_input":"2025-05-01T02:18:28.064689Z","iopub.status.idle":"2025-05-01T02:18:28.084233Z","shell.execute_reply.started":"2025-05-01T02:18:28.064662Z","shell.execute_reply":"2025-05-01T02:18:28.083267Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_models(cfg, num_classes):\n\n    models = []\n    \n    model_files = find_model_files(cfg)\n    \n    print(f\"Found a total of {len(model_files)} model files.\")\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['model_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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:28.085344Z","iopub.execute_input":"2025-05-01T02:18:28.086059Z","iopub.status.idle":"2025-05-01T02:18:28.100777Z","shell.execute_reply.started":"2025-05-01T02:18:28.086007Z","shell.execute_reply":"2025-05-01T02:18:28.099753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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            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            with torch.no_grad():\n                outputs = models[0](mel_spec)\n                final_preds = torch.sigmoid(outputs).cpu().numpy().squeeze()\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-05-01T02:18:28.101838Z","iopub.execute_input":"2025-05-01T02:18:28.102131Z","iopub.status.idle":"2025-05-01T02:18:28.123080Z","shell.execute_reply.started":"2025-05-01T02:18:28.102104Z","shell.execute_reply":"2025-05-01T02:18:28.122092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def run_inference(cfg, models, species_ids):\n#     test_files = list(Path(cfg.test_soundscapes).glob('*.ogg'))\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#         all_row_ids.extend(row_ids)\n#         all_predictions.extend(predictions)\n    \n#     return all_row_ids, all_predictions\n\ndef run_inference(cfg, models, species_ids):\n    test_files = list(Path(cfg.test_soundscapes).glob('*.ogg'))\n    \n    print(f\"Found {len(test_files)} test soundscapes\")\n\n    all_row_ids = []\n    all_predictions = []\n    grouped_preds = {}\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        predictions = np.array(predictions)\n        predictions = apply_power_to_low_ranked_cols(predictions, top_k=30, exponent=2.0)\n        smoothed_preds = predictions.copy()\n        for i in range(1, len(predictions) - 1):\n            smoothed_preds[i] = (\n                0.2 * predictions[i - 1] +\n                0.6 * predictions[i] +\n                0.2 * predictions[i + 1]\n            )\n        smoothed_preds[0] = 0.8 * predictions[0] + 0.2 * predictions[1]\n        smoothed_preds[-1] = 0.8 * predictions[-1] + 0.2 * predictions[-2]\n\n        all_row_ids.extend(row_ids)\n        all_predictions.extend(smoothed_preds)\n    \n    return all_row_ids, all_predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:28.124127Z","iopub.execute_input":"2025-05-01T02:18:28.124483Z","iopub.status.idle":"2025-05-01T02:18:28.146205Z","shell.execute_reply.started":"2025-05-01T02:18:28.124440Z","shell.execute_reply":"2025-05-01T02:18:28.144978Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_submission(row_ids, predictions, species_ids, cfg):\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-05-01T02:18:28.147501Z","iopub.execute_input":"2025-05-01T02:18:28.147855Z","iopub.status.idle":"2025-05-01T02:18:28.164238Z","shell.execute_reply.started":"2025-05-01T02:18:28.147827Z","shell.execute_reply":"2025-05-01T02:18:28.163180Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    start_time = time.time()\n    print(\"Starting BirdCLEF-2025 inference...\")\n\n    models = load_models(cfg, num_classes)\n    \n    if not models:\n        print(\"No models found! Please check model paths.\")\n        return\n    \n    print(f\"Model usage: {'Single model' if len(models) == 1 else f'Ensemble of {len(models)} models'}\")\n\n    row_ids, predictions = run_inference(cfg, models, species_ids)\n\n    submission_df = create_submission(row_ids, predictions, species_ids, cfg)\n\n    submission_path = 'submission.csv'\n    submission_df.to_csv(submission_path, index=False)\n    print(f\"Submission saved to {submission_path}\")\n    \n    end_time = time.time()\n    print(f\"Inference completed in {(end_time - start_time)/60:.2f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:28.166989Z","iopub.execute_input":"2025-05-01T02:18:28.167366Z","iopub.status.idle":"2025-05-01T02:18:28.189103Z","shell.execute_reply.started":"2025-05-01T02:18:28.167341Z","shell.execute_reply":"2025-05-01T02:18:28.188072Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T02:18:28.190261Z","iopub.execute_input":"2025-05-01T02:18:28.190534Z","iopub.status.idle":"2025-05-01T02:18:28.511584Z","shell.execute_reply.started":"2025-05-01T02:18:28.190514Z","shell.execute_reply":"2025-05-01T02:18:28.510562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}