{"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":[{"sourceType":"competition","sourceId":91844,"databundleVersionId":11361821},{"sourceType":"datasetVersion","sourceId":11060723,"datasetId":6891568,"databundleVersionId":11447259}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, gc, warnings, logging, time, math, cv2\nfrom pathlib import Path\n\nimport numpy as np, pandas as pd, librosa\nimport torch, torch.nn as nn, torch.nn.functional as F, 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-03-27T17:25:14.920255Z","iopub.execute_input":"2025-03-27T17:25:14.920670Z","iopub.status.idle":"2025-03-27T17:25:28.987099Z","shell.execute_reply.started":"2025-03-27T17:25:14.920636Z","shell.execute_reply":"2025-03-27T17:25:28.985887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    test_soundscapes, submission_csv, taxonomy_csv = (\n        '/kaggle/input/birdclef-2025/test_soundscapes',\n        '/kaggle/input/birdclef-2025/sample_submission.csv',\n        '/kaggle/input/birdclef-2025/taxonomy.csv',\n    )\n    model_path = '/kaggle/input/birdclef25-effnetb0-starter-weight'\n\n    # Audio, Mel spectrogram, Model, Inference parameters\n    FS, WINDOW_SIZE = 32000, 5\n    N_FFT, HOP_LENGTH, N_MELS, FMIN, FMAX = 1024, 512, 128, 50, 14000\n    TARGET_SHAPE = (256, 256)\n    model_name, in_channels, device = 'efficientnet_b0', 1, 'cpu'\n    batch_size, use_tta, tta_count, threshold = 16, False, 3, 0.5\n    use_specific_folds, folds = False, [0, 1]\n    debug, debug_count = False, 3\n\ncfg = CFG()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:25:28.988508Z","iopub.execute_input":"2025-03-27T17:25:28.988852Z","iopub.status.idle":"2025-03-27T17:25:28.994906Z","shell.execute_reply.started":"2025-03-27T17:25:28.988824Z","shell.execute_reply":"2025-03-27T17:25:28.993756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Using device: {cfg.device}\", \n      f\"Loading taxonomy data...\", sep='\\n')  # Print on separate lines\n\ntaxonomy_df = pd.read_csv(cfg.taxonomy_csv)\nspecies_ids = taxonomy_df['primary_label'].tolist()\nnum_classes = len(species_ids)\n\nprint(f\"Number of classes: {num_classes}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:25:28.997569Z","iopub.execute_input":"2025-03-27T17:25:28.997913Z","iopub.status.idle":"2025-03-27T17:25:29.041810Z","shell.execute_reply.started":"2025-03-27T17:25:28.997884Z","shell.execute_reply":"2025-03-27T17:25:29.040707Z"}},"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, self.num_classes = cfg, num_classes \n        self.backbone = timm.create_model(\n            cfg.model_name, pretrained=False, in_chans=cfg.in_channels,\n            drop_rate=0, drop_path_rate=0  # Remove unnecessary decimals\n        )\n\n        # Determine backbone output features based on model architecture\n        if 'efficientnet' in cfg.model_name:\n            self.backbone_out = self.backbone.classifier.in_features\n            self.backbone.classifier = nn.Identity()\n        elif 'resnet' in cfg.model_name:\n            self.backbone_out = self.backbone.fc.in_features\n            self.backbone.fc = nn.Identity()\n        else:\n            self.backbone_out = self.backbone.get_classifier().in_features\n            self.backbone.reset_classifier(0, '')\n\n        self.pooling = nn.AdaptiveAvgPool2d(1)\n        self.classifier = nn.Linear(self.backbone_out, num_classes)\n\n    def forward(self, x):\n        features = self.backbone(x)\n        features = features['features'] if isinstance(features, dict) else features\n        features = self.pooling(features).view(features.size(0), -1) if len(features.shape) == 4 else features\n        return self.classifier(features)  # Directly return logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:25:29.043091Z","iopub.execute_input":"2025-03-27T17:25:29.043428Z","iopub.status.idle":"2025-03-27T17:25:29.051528Z","shell.execute_reply.started":"2025-03-27T17:25:29.043391Z","shell.execute_reply":"2025-03-27T17:25:29.050038Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def audio2melspec(audio_data, cfg):\n    \"\"\"Convert audio data to mel spectrogram.\"\"\"\n    audio_data = np.nan_to_num(audio_data, nan=np.nanmean(audio_data)) if np.isnan(audio_data).any() else audio_data\n\n    mel_spec = librosa.feature.melspectrogram(\n        y=audio_data, sr=cfg.FS, n_fft=cfg.N_FFT, hop_length=cfg.HOP_LENGTH,\n        n_mels=cfg.N_MELS, fmin=cfg.FMIN, fmax=cfg.FMAX, power=2.0\n    )\n    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n    return (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)\n\ndef process_audio_segment(audio_data, cfg):\n    \"\"\"Process audio segment to get mel spectrogram.\"\"\"\n    audio_data = np.pad(audio_data, (0, cfg.FS * cfg.WINDOW_SIZE - len(audio_data)), mode='constant') if len(audio_data) < cfg.FS * cfg.WINDOW_SIZE else audio_data\n    mel_spec = audio2melspec(audio_data, cfg)\n    return cv2.resize(mel_spec, cfg.TARGET_SHAPE, interpolation=cv2.INTER_LINEAR).astype(np.float32) if mel_spec.shape != cfg.TARGET_SHAPE else mel_spec.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:25:29.052779Z","iopub.execute_input":"2025-03-27T17:25:29.053169Z","iopub.status.idle":"2025-03-27T17:25:29.074830Z","shell.execute_reply.started":"2025-03-27T17:25:29.053140Z","shell.execute_reply":"2025-03-27T17:25:29.073410Z"}},"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('**/*.pth'):\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['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\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\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:25:29.076126Z","iopub.execute_input":"2025-03-27T17:25:29.076443Z","iopub.status.idle":"2025-03-27T17:25:29.100559Z","shell.execute_reply.started":"2025-03-27T17:25:29.076416Z","shell.execute_reply":"2025-03-27T17:25:29.099149Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apply_tta(spec, tta_idx):\n    \"\"\"Apply test-time augmentation.\"\"\"\n    return {\n        0: spec,\n        1: np.flip(spec, axis=1),\n        2: np.flip(spec, axis=0)\n    }.get(tta_idx, 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    if cfg.debug:\n        test_files = test_files[:cfg.debug_count]\n        print(f\"Debug mode enabled, using only {cfg.debug_count} files\")\n    print(f\"Found {len(test_files)} test soundscapes\")\n    \n    all_row_ids, all_predictions = [], []\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 create_submission(row_ids, predictions, species_ids, cfg):\n    \"\"\"Create submission dataframe.\"\"\"\n    print(\"Creating submission dataframe...\")\n    submission_dict = {'row_id': row_ids}\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).set_index('row_id')\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        submission_df[list(missing_cols)] = 0.0  # Assigning 0 to missing columns directly\n\n    return submission_df.reindex(columns=sample_sub.columns).reset_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:25:29.101613Z","iopub.execute_input":"2025-03-27T17:25:29.101937Z","iopub.status.idle":"2025-03-27T17:25:29.118932Z","shell.execute_reply.started":"2025-03-27T17:25:29.101910Z","shell.execute_reply":"2025-03-27T17:25:29.117689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    \"\"\"Main function for BirdCLEF-2025 inference.\"\"\"\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    if not models:\n        print(\"No models found! Please check model paths.\")\n        return\n\n    print(f\"Model usage: {'Single' if len(models) == 1 else 'Ensemble'} model{'s' if len(models) > 1 else ''}\")\n    \n    row_ids, predictions = run_inference(cfg, models, species_ids)\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    print(f\"Inference completed in {(time.time() - start_time) / 60:.2f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:25:29.121146Z","iopub.execute_input":"2025-03-27T17:25:29.121444Z","iopub.status.idle":"2025-03-27T17:25:29.142754Z","shell.execute_reply.started":"2025-03-27T17:25:29.121420Z","shell.execute_reply":"2025-03-27T17:25:29.141615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if __name__ == \"__main__\":\n    try:\n        main()\n    except Exception as e:\n        print(f\"An error occurred during inference: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:25:29.143947Z","iopub.execute_input":"2025-03-27T17:25:29.144305Z","iopub.status.idle":"2025-03-27T17:25:30.382657Z","shell.execute_reply.started":"2025-03-27T17:25:29.144276Z","shell.execute_reply":"2025-03-27T17:25:30.381628Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}