{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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\n# Suppress warnings and limit logging output\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)\n\nclass CFG:\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/birdclef-2025-efficientnet-b0'\n    \n    FS = 32000\n    WINDOW_SIZE = 5\n    N_FFT = 1034\n    HOP_LENGTH = 64\n    N_MELS = 136\n    FMIN = 20\n    FMAX = 16000\n    TARGET_SHAPE = (256, 256)\n    \n    model_name = 'efficientnet_b0'\n    in_channels = 1\n    device = 'cpu'\n    \n    batch_size = 16\n    use_tta = False\n    tta_count = 3\n    threshold = 0.7\n    \n    use_specific_folds = False\n    folds = [0, 1]\n    \n    debug = False\n    debug_count = 3\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T10:42:56.450555Z","iopub.execute_input":"2025-04-07T10:42:56.450857Z","iopub.status.idle":"2025-04-07T10:43:06.718589Z","shell.execute_reply.started":"2025-04-07T10:42:56.450826Z","shell.execute_reply":"2025-04-07T10:43:06.717702Z"}},"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        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        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        if isinstance(features, dict):\n            features = features['features']\n        if len(features.shape) == 4:\n            features = self.pooling(features)\n            features = features.view(features.size(0), -1)\n        logits = self.classifier(features)\n        return logits\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T10:43:30.401889Z","iopub.execute_input":"2025-04-07T10:43:30.402191Z","iopub.status.idle":"2025-04-07T10:43:30.408394Z","shell.execute_reply.started":"2025-04-07T10:43:30.402167Z","shell.execute_reply":"2025-04-07T10:43:30.407742Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BirdCLEF2025Pipeline:\n    def __init__(self, cfg):\n        self.cfg = cfg\n        self.taxonomy_df = None\n        self.species_ids = []\n        self.models = []\n        self._load_taxonomy()\n\n    def _load_taxonomy(self):\n        print(\"Loading taxonomy data...\")\n        self.taxonomy_df = pd.read_csv(self.cfg.taxonomy_csv)\n        self.species_ids = self.taxonomy_df['primary_label'].tolist()\n        print(f\"Number of classes: {len(self.species_ids)}\")\n\n    def audio2melspec(self, audio_data):\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        mel_spec = librosa.feature.melspectrogram(\n            y=audio_data,\n            sr=self.cfg.FS,\n            n_fft=self.cfg.N_FFT,\n            hop_length=self.cfg.HOP_LENGTH,\n            n_mels=self.cfg.N_MELS,\n            fmin=self.cfg.FMIN,\n            fmax=self.cfg.FMAX,\n            power=2.0\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        return mel_spec_norm\n\n    def process_audio_segment(self, audio_data):\n        if len(audio_data) < self.cfg.FS * self.cfg.WINDOW_SIZE:\n            audio_data = np.pad(\n                audio_data,\n                (0, self.cfg.FS * self.cfg.WINDOW_SIZE - len(audio_data)),\n                mode='constant'\n            )\n        mel_spec = self.audio2melspec(audio_data)\n        if mel_spec.shape != self.cfg.TARGET_SHAPE:\n            mel_spec = cv2.resize(mel_spec, self.cfg.TARGET_SHAPE, interpolation=cv2.INTER_LINEAR)\n        return mel_spec.astype(np.float32)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T10:43:35.874473Z","iopub.execute_input":"2025-04-07T10:43:35.874778Z","iopub.status.idle":"2025-04-07T10:43:35.882169Z","shell.execute_reply.started":"2025-04-07T10:43:35.874756Z","shell.execute_reply":"2025-04-07T10:43:35.881236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"    def find_model_files(self):\n        model_files = []\n        model_dir = Path(self.cfg.model_path)\n        for path in model_dir.glob('**/*.pth'):\n            model_files.append(str(path))\n        return model_files\n\n    def load_models(self):\n        self.models = []\n        model_files = self.find_model_files()\n        if not model_files:\n            print(f\"Warning: No model files found under {self.cfg.model_path}!\")\n            return self.models\n\n        print(f\"Found a total of {len(model_files)} model files.\")\n        \n        if self.cfg.use_specific_folds:\n            filtered_files = []\n            for fold in self.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 ({self.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(self.cfg.device))\n                model = BirdCLEFModel(self.cfg, len(self.species_ids))\n                model.load_state_dict(checkpoint['model_state_dict'])\n                model = model.to(self.cfg.device)\n                model.eval()\n                self.models.append(model)\n            except Exception as e:\n                print(f\"Error loading model {model_path}: {e}\")\n        \n        return self.models\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T10:43:39.961806Z","iopub.execute_input":"2025-04-07T10:43:39.962111Z","iopub.status.idle":"2025-04-07T10:43:39.969558Z","shell.execute_reply.started":"2025-04-07T10:43:39.962088Z","shell.execute_reply":"2025-04-07T10:43:39.968740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"    def predict(self, input_tensor):\n        preds = []\n        input_tensor = input_tensor.to(self.cfg.device)\n        for model in self.models:\n            with torch.no_grad():\n                output = model(input_tensor)\n                output = torch.sigmoid(output)\n                preds.append(output.cpu().numpy())\n        preds = np.mean(preds, axis=0)\n        return preds\n\n    def segment_audio(self, audio_data):\n        segment_length = self.cfg.FS * self.cfg.WINDOW_SIZE\n        segments = []\n        num_segments = math.ceil(len(audio_data) / segment_length)\n        for i in range(num_segments):\n            start = i * segment_length\n            end = min((i + 1) * segment_length, len(audio_data))\n            segment = audio_data[start:end]\n            if len(segment) < segment_length:\n                segment = np.pad(segment, (0, segment_length - len(segment)), mode='constant')\n            segments.append(segment)\n        return segments\n\n    def predict_soundscape(self, file_path):\n        y, sr = librosa.load(file_path, sr=self.cfg.FS, mono=True)\n        segments = self.segment_audio(y)\n        predictions = []\n\n        for segment in segments:\n            mel = self.process_audio_segment(segment)\n            mel_tensor = torch.from_numpy(mel).unsqueeze(0).unsqueeze(0)  # (1, 1, H, W)\n            pred = self.predict(mel_tensor)[0]\n            predictions.append(pred)\n\n        predictions = np.array(predictions)\n        averaged_preds = predictions.mean(axis=0)\n        return averaged_preds\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T10:43:48.821456Z","iopub.execute_input":"2025-04-07T10:43:48.821769Z","iopub.status.idle":"2025-04-07T10:43:48.829217Z","shell.execute_reply.started":"2025-04-07T10:43:48.821744Z","shell.execute_reply":"2025-04-07T10:43:48.828271Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_submission(predictor, test_files, output_csv='submission.csv'):\n    id_list = []\n    prediction_list = []\n\n    for file_path in tqdm(test_files, desc=\"Predicting\"):\n        preds = predictor.predict_soundscape(file_path)\n        file_id = os.path.splitext(os.path.basename(file_path))[0]\n        id_list.append(file_id)\n        prediction_list.append(preds)\n\n    prediction_array = np.array(prediction_list)\n    df = pd.DataFrame(prediction_array, columns=predictor.cfg.LABELS)\n    df.insert(0, 'filename', id_list)\n    df.to_csv(output_csv, index=False)\n    print(f\"Submission file saved to {output_csv}\")\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T10:44:16.967291Z","iopub.execute_input":"2025-04-07T10:44:16.967596Z","iopub.status.idle":"2025-04-07T10:44:16.973044Z","shell.execute_reply.started":"2025-04-07T10:44:16.967573Z","shell.execute_reply":"2025-04-07T10:44:16.972237Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run(self):\n    print(\"[INFO] Starting BirdCLEF2025 pipeline...\")\n\n    # Step 1: Load test files\n    test_files = glob.glob(os.path.join(self.cfg.test_dir, \"*.ogg\"))\n    print(f\"[INFO] Found {len(test_files)} test files.\")\n\n    # Step 2: Initialize predictor\n    predictor = SoundscapePredictor(self.cfg)\n\n    # Step 3: Create submission\n    submission_df = create_submission(predictor, test_files)\n\n    # Step 4: Save submission\n    submission_df.to_csv(\"submission.csv\", index=False)\n    print(\"[INFO] Submission file saved as submission.csv.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-07T10:44:39.528339Z","iopub.execute_input":"2025-04-07T10:44:39.528651Z","iopub.status.idle":"2025-04-07T10:44:39.533136Z","shell.execute_reply.started":"2025-04-07T10:44:39.528623Z","shell.execute_reply":"2025-04-07T10:44:39.532426Z"}},"outputs":[],"execution_count":null}]}