{"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,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport torch\nimport dataclasses\nfrom typing import Optional, Callable, Tuple, List\n# Import data from files\ntrain_csv = '/kaggle/input/birdclef-2025/train.csv'\ntest_data = '/kaggle/input/birdclef-2025/test_soundscapes'\nsubmission = '/kaggle/input/birdclef-2025/sample_submission.csv'\ntaxonomy_csv = '/kaggle/input/birdclef-2025/taxonomy.csv'\n\n# Define audio parameter class\n@dataclasses.dataclass\nclass AudioParam:\n    SR: int = 32_000  # Sample rate\n    NFFT: int = 2048  # Number of FFT points\n    NMEL: int = 128   # Number of Mel bands\n    FMAX: int = 16_000 # Maximum frequency\n    FMIN: int = 20   # Minimum frequency\n    HOP_LENGTH: int = NFFT // 4  # Hop length\n\naudio_param = AudioParam()\n\n# Load sample_submission CSV to get bird class names\nsubmission_csv = pd.read_csv(submission)\nbird_classes = submission_csv.columns.drop('row_id').tolist()  # List of bird classes\n\nfile_names = [os.path.join(test_data, filepath) for filepath in os.listdir(test_data) if filepath.endswith(\".ogg\")]\n\n# Use a single file for debugging.  This makes the matrix dimension calculations easier.\nif len(file_names) == 0:\n    file_names = [\n        '/kaggle/input/birdclef-2025/train_soundscapes/H03_20230505_190000.ogg',\n    ]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-16T00:11:15.365275Z","iopub.execute_input":"2025-05-16T00:11:15.366466Z","iopub.status.idle":"2025-05-16T00:11:15.403850Z","shell.execute_reply.started":"2025-05-16T00:11:15.366419Z","shell.execute_reply":"2025-05-16T00:11:15.403063Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\ndefine an audio processing pipeline to prepare the input data for the CNN model\n\"\"\"\n\ndef pipeline(x: np.ndarray) -> np.ndarray:\n    \"\"\"\n    Converts audio data to a mel spectrogram and then to a dB scale.\n    \"\"\"\n    mels = librosa.feature.melspectrogram(\n        y=x,\n        sr=audio_param.SR,\n        n_fft=audio_param.NFFT,\n        n_mels=audio_param.NMEL,\n        fmax=audio_param.FMAX,\n        fmin=audio_param.FMIN,\n        hop_length=audio_param.HOP_LENGTH,\n    )\n    db_map = librosa.power_to_db(mels, ref=np.max)\n    db_map = (db_map + 80) / (80 + 1e-6)  # Normalize to [0, 1] - Added small constant\n    if np.isnan(db_map).any():\n        print('Warning: NaN values detected in db_map!')\n        db_map = np.nan_to_num(db_map) #Replace with 0\n\n    return db_map[None, :, :]  # Add a channel dimension (1, height, width)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-16T00:11:15.405940Z","iopub.execute_input":"2025-05-16T00:11:15.406598Z","iopub.status.idle":"2025-05-16T00:11:15.413774Z","shell.execute_reply.started":"2025-05-16T00:11:15.406558Z","shell.execute_reply":"2025-05-16T00:11:15.412789Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_audio_segments(filepath: str) -> Tuple[List[torch.Tensor], List[str]]:\n    \"\"\"\n    Load audio, split into 5-second segments, and apply preprocessing.\n\n    Returns:\n        List of tensors ready for model input and corresponding row_ids.\n    \"\"\"\n\n    x, _ = librosa.load(filepath, sr=audio_param.SR)\n    if x.size == 0:\n        print(f\"Warning: Audio file {filepath} is empty!\")\n        return [], []\n\n    num_segments = int(np.floor(len(x) / audio_param.SR / 5))\n    processed_segments = []\n    row_ids = []\n\n    for i in range(num_segments):\n        start = i * audio_param.SR * 5\n        end = (i + 1) * audio_param.SR * 5\n        segment = x[start:end]\n\n        # Convert to mel spectrogram\n        segment = pipeline(segment)\n\n        # Convert to tensor\n        segment_tensor = torch.from_numpy(segment).float().unsqueeze(0)\n        processed_segments.append(segment_tensor)\n\n        filepath_name = os.path.basename(filepath).split(\".\")[0]\n        row_id = f\"{filepath_name}_{(i + 1) * 5}\"\n        row_ids.append(row_id)\n\n    return processed_segments, row_ids\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-16T00:11:15.414793Z","iopub.execute_input":"2025-05-16T00:11:15.415059Z","iopub.status.idle":"2025-05-16T00:11:15.444684Z","shell.execute_reply.started":"2025-05-16T00:11:15.415039Z","shell.execute_reply":"2025-05-16T00:11:15.443465Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-16T00:11:15.445676Z","iopub.execute_input":"2025-05-16T00:11:15.445940Z","iopub.status.idle":"2025-05-16T00:11:15.463236Z","shell.execute_reply.started":"2025-05-16T00:11:15.445919Z","shell.execute_reply":"2025-05-16T00:11:15.462195Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  a simpler, randomly initialized CNN model\nclass SimpleCNN(nn.Module):\n    def __init__(self, num_classes: int = 1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1)\n        self.relu1 = nn.ReLU()\n        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)\n        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)\n        self.relu2 = nn.ReLU()\n        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)\n        self.flatten = nn.Flatten()\n        self._to_linear = None\n        self.fc1 = nn.Linear(1, num_classes)\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        x = self.pool1(self.relu1(self.conv1(x)))\n        x = self.pool2(self.relu2(self.conv2(x)))\n        x = self.flatten(x)\n        \n        if self._to_linear is None:\n            self._to_linear = x.shape[1]\n            if self._to_linear == 0:\n               return torch.zeros((1, len(bird_classes)))\n            self.fc1 = nn.Linear(self._to_linear, len(bird_classes))\n        x = self.fc1(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-16T00:11:15.465033Z","iopub.execute_input":"2025-05-16T00:11:15.465440Z","iopub.status.idle":"2025-05-16T00:11:15.481346Z","shell.execute_reply.started":"2025-05-16T00:11:15.465405Z","shell.execute_reply":"2025-05-16T00:11:15.480501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = SimpleCNN(num_classes=len(bird_classes)) # initialize the model\nmodel.eval() # Set the model to evaluation mode.\n\n# prediction fuction\n@torch.no_grad()\ndef predict(filepath: str) -> Tuple[np.ndarray, List[str]]:\n\n    segments, row_ids = process_audio_segments(filepath)\n    if not segments:\n        return np.array([]), []\n\n    outputs = []\n    \n    for seg in segments:\n        out = model(seg).sigmoid().cpu().numpy()\n        outputs.append(out[0])\n\n    return np.array(outputs), row_ids","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-16T00:11:15.482257Z","iopub.execute_input":"2025-05-16T00:11:15.482651Z","iopub.status.idle":"2025-05-16T00:11:15.539254Z","shell.execute_reply.started":"2025-05-16T00:11:15.482618Z","shell.execute_reply":"2025-05-16T00:11:15.538247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gc\nimport torch.nn.functional as F\nfrom concurrent.futures import ThreadPoolExecutor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-16T00:11:15.540014Z","iopub.execute_input":"2025-05-16T00:11:15.540341Z","iopub.status.idle":"2025-05-16T00:11:15.545662Z","shell.execute_reply.started":"2025-05-16T00:11:15.540313Z","shell.execute_reply":"2025-05-16T00:11:15.544246Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"row_id = []\nmatrix = []\n\n#Using a ThreadPoolExecutor to parallelize the predictions\nwith ThreadPoolExecutor(max_workers=6) as executor:\n    for filepath_idx, (filepath) in enumerate(file_names):\n        out, rid = predict(filepath)\n        if len(rid) > 0:\n            row_id.extend(rid)\n            matrix.extend(out)\n        else:\n            print(f\"Warning: No predictions generated for file: {filepath}\")\n        gc.collect()\n\n    matrix = np.array(matrix).reshape(-1, len(bird_classes))\n    row_id = np.array(row_id).reshape(-1, 1)\n    matrix = np.hstack([row_id, matrix])\n\n    # Create a Pandas DataFrame from the results.\n    sub = pd.DataFrame(matrix, columns=[\"row_id\", *bird_classes])\n    sub.to_csv('submission.csv', index=False)\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-16T00:11:15.546590Z","iopub.execute_input":"2025-05-16T00:11:15.546856Z","iopub.status.idle":"2025-05-16T00:11:34.620232Z","shell.execute_reply.started":"2025-05-16T00:11:15.546825Z","shell.execute_reply":"2025-05-16T00:11:34.619330Z"}},"outputs":[],"execution_count":null}]}