{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":25954,"databundleVersionId":2091745,"sourceType":"competition"},{"sourceId":1297722,"sourceType":"datasetVersion","datasetId":750498},{"sourceId":2130303,"sourceType":"datasetVersion","datasetId":1278322}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# BirdCLEF 2021 - Inference Notebook\n\nThis notebook performs inference on BirdCLEF 2021 soundscape data using pre-trained ResNeSt50 models.","metadata":{}},{"cell_type":"markdown","source":"## Imports and Configuration","metadata":{}},{"cell_type":"code","source":"import shutil\nimport os\nshutil.copytree('../input/resnest50-fast-package/resnest-0.0.6b20200701/resnest', 'resnet', dirs_exist_ok=True)\nos.system('pip install \"./resnet\" --no-deps')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:09:30.884797Z","iopub.execute_input":"2025-11-16T19:09:30.885532Z","iopub.status.idle":"2025-11-16T19:09:35.249747Z","shell.execute_reply.started":"2025-11-16T19:09:30.885489Z","shell.execute_reply":"2025-11-16T19:09:35.249043Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport librosa as lb\nimport soundfile as sf\nimport pandas as pd\nimport cv2\nfrom pathlib import Path\nimport re\n\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom resnest.torch import resnest50\n\nfrom tqdm.notebook import tqdm\nimport time\n# Constants\nNUM_CLASSES = 397\nSR = 32_000  # Sample rate\nDURATION = 5  # Audio duration in seconds\nTHRESH = 0.25  # Prediction threshold\n\n# Device configuration\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"DEVICE: {DEVICE}\")\n\n# Data paths\nTEST_AUDIO_ROOT = Path(\"../input/birdclef-2021/test_soundscapes\")\nSAMPLE_SUB_PATH = \"../input/birdclef-2021/sample_submission.csv\"\nTARGET_PATH = None\n\n# Fallback to training soundscapes if test files not found\nif not list(TEST_AUDIO_ROOT.glob(\"*.ogg\")):\n    TEST_AUDIO_ROOT = Path(\"../input/birdclef-2021/train_soundscapes\")\n    SAMPLE_SUB_PATH = None\n    TARGET_PATH = Path(\"../input/birdclef-2021/train_soundscape_labels.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:00.199760Z","iopub.execute_input":"2025-11-16T19:10:00.200045Z","iopub.status.idle":"2025-11-16T19:10:04.148058Z","shell.execute_reply.started":"2025-11-16T19:10:00.200024Z","shell.execute_reply":"2025-11-16T19:10:04.147318Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Processing Classes","metadata":{}},{"cell_type":"code","source":"class MelSpecComputer:\n    \"\"\"Computes mel-spectrograms from audio signals.\"\"\"\n    \n    def __init__(self, sr, n_mels, fmin, fmax, **kwargs):\n        self.sr = sr\n        self.n_mels = n_mels\n        self.fmin = fmin\n        self.fmax = fmax\n        kwargs[\"n_fft\"] = kwargs.get(\"n_fft\", self.sr // 10)\n        kwargs[\"hop_length\"] = kwargs.get(\"hop_length\", self.sr // (10 * 4))\n        self.kwargs = kwargs\n\n    def __call__(self, y):\n        melspec = lb.feature.melspectrogram(\n            y=y, \n            sr=self.sr, \n            n_mels=self.n_mels, \n            fmin=self.fmin, \n            fmax=self.fmax, \n            **self.kwargs\n        )\n        melspec = lb.power_to_db(melspec).astype(np.float32)\n        return melspec","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:04.148970Z","iopub.execute_input":"2025-11-16T19:10:04.149333Z","iopub.status.idle":"2025-11-16T19:10:04.154413Z","shell.execute_reply.started":"2025-11-16T19:10:04.149315Z","shell.execute_reply":"2025-11-16T19:10:04.153813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def mono_to_color(X, eps=1e-6, mean=None, std=None):\n    \"\"\"Convert mono spectrogram to color image.\"\"\"\n    mean = mean or X.mean()\n    std = std or X.std()\n    X = (X - mean) / (std + eps)\n    \n    _min, _max = X.min(), X.max()\n\n    if (_max - _min) > eps:\n        V = np.clip(X, _min, _max)\n        V = 255 * (V - _min) / (_max - _min)\n        V = V.astype(np.uint8)\n    else:\n        V = np.zeros_like(X, dtype=np.uint8)\n\n    return V\n\n\ndef crop_or_pad(y, length):\n    \"\"\"Crop or pad audio to specified length.\"\"\"\n    if len(y) < length:\n        y = np.concatenate([y, np.zeros(length - len(y))])\n    elif len(y) > length:\n        y = y[:length]\n    return y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:04.254505Z","iopub.execute_input":"2025-11-16T19:10:04.254731Z","iopub.status.idle":"2025-11-16T19:10:04.261237Z","shell.execute_reply.started":"2025-11-16T19:10:04.254713Z","shell.execute_reply":"2025-11-16T19:10:04.260619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BirdCLEFDataset(Dataset):\n    \"\"\"Dataset class for BirdCLEF audio data.\"\"\"\n    \n    def __init__(self, data, sr=SR, n_mels=128, fmin=0, fmax=None, \n                 duration=DURATION, step=None, res_type=\"kaiser_fast\", resample=True):\n        self.data = data\n        self.sr = sr\n        self.n_mels = n_mels\n        self.fmin = fmin\n        self.fmax = fmax or self.sr // 2\n        self.duration = duration\n        self.audio_length = self.duration * self.sr\n        self.step = step or self.audio_length\n        self.res_type = res_type\n        self.resample = resample\n\n        self.mel_spec_computer = MelSpecComputer(\n            sr=self.sr, \n            n_mels=self.n_mels, \n            fmin=self.fmin, \n            fmax=self.fmax\n        )\n\n    def __len__(self):\n        return len(self.data)\n    \n    @staticmethod\n    def normalize(image):\n        \"\"\"Normalize image to [0, 1] and convert to 3-channel.\"\"\"\n        image = image.astype(\"float32\", copy=False) / 255.0\n        image = np.stack([image, image, image])\n        return image\n    \n    def audio_to_image(self, audio):\n        \"\"\"Convert audio to mel-spectrogram image.\"\"\"\n        melspec = self.mel_spec_computer(audio)\n        image = mono_to_color(melspec)\n        image = self.normalize(image)\n        return image\n\n    def read_file(self, filepath):\n        \"\"\"Read and process audio file into spectrogram images.\"\"\"\n        audio, orig_sr = sf.read(filepath, dtype=\"float32\")\n\n        if self.resample and orig_sr != self.sr:\n            audio = lb.resample(audio, orig_sr, self.sr, res_type=self.res_type)\n          \n        # Split audio into segments\n        audios = []\n        for i in range(self.audio_length, len(audio) + self.step, self.step):\n            start = max(0, i - self.audio_length)\n            end = start + self.audio_length\n            audios.append(audio[start:end])\n            \n        # Remove last segment if too short\n        if len(audios[-1]) < self.audio_length:\n            audios = audios[:-1]\n            \n        # Convert segments to images\n        images = [self.audio_to_image(audio) for audio in audios]\n        images = np.stack(images)\n        \n        return images\n    \n    def __getitem__(self, idx):\n        return self.read_file(self.data.loc[idx, \"filepath\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:16.762807Z","iopub.execute_input":"2025-11-16T19:10:16.763330Z","iopub.status.idle":"2025-11-16T19:10:16.772196Z","shell.execute_reply.started":"2025-11-16T19:10:16.763303Z","shell.execute_reply":"2025-11-16T19:10:16.771455Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Preparation","metadata":{}},{"cell_type":"code","source":"# Load test data\ndata = pd.DataFrame(\n    [(path.stem, *path.stem.split(\"_\"), path) for path in Path(TEST_AUDIO_ROOT).glob(\"*.ogg\")],\n    columns=[\"filename\", \"id\", \"site\", \"date\", \"filepath\"]\n)\nprint(f\"Test data shape: {data.shape}\")\ndata.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:19.689387Z","iopub.execute_input":"2025-11-16T19:10:19.690008Z","iopub.status.idle":"2025-11-16T19:10:19.721479Z","shell.execute_reply.started":"2025-11-16T19:10:19.689983Z","shell.execute_reply":"2025-11-16T19:10:19.720865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create label mappings\ndf_train = pd.read_csv(\"../input/birdclef-2021/train_metadata.csv\")\nLABEL_IDS = {label: label_id for label_id, label in enumerate(sorted(df_train[\"primary_label\"].unique()))}\nINV_LABEL_IDS = {val: key for key, val in LABEL_IDS.items()}\n\nprint(f\"Number of classes: {len(LABEL_IDS)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:21.292249Z","iopub.execute_input":"2025-11-16T19:10:21.292856Z","iopub.status.idle":"2025-11-16T19:10:21.708874Z","shell.execute_reply.started":"2025-11-16T19:10:21.292815Z","shell.execute_reply":"2025-11-16T19:10:21.708097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create test dataset\ntest_data = BirdCLEFDataset(data=data)\nprint(f\"Test dataset size: {len(test_data)}\")\nprint(f\"First batch shape: {test_data[0].shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:23.043234Z","iopub.execute_input":"2025-11-16T19:10:23.043504Z","iopub.status.idle":"2025-11-16T19:10:38.490555Z","shell.execute_reply.started":"2025-11-16T19:10:23.043484Z","shell.execute_reply":"2025-11-16T19:10:38.489720Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Loading","metadata":{}},{"cell_type":"code","source":"def load_net(checkpoint_path, num_classes=NUM_CLASSES):\n    \"\"\"Load pre-trained ResNeSt50 model.\"\"\"\n    net = resnest50(pretrained=False)\n    net.fc = nn.Linear(net.fc.in_features, num_classes)\n    \n    # Load checkpoint\n    dummy_device = torch.device(\"cpu\")\n    checkpoint = torch.load(checkpoint_path, map_location=dummy_device)\n    \n    # Remove 'model.' prefix from state dict keys if present\n    state_dict = {}\n    for key in list(checkpoint.keys()):\n        new_key = key.replace(\"model.\", \"\")\n        state_dict[new_key] = checkpoint[key]\n    \n    net.load_state_dict(state_dict)\n    net = net.to(DEVICE)\n    net.eval()\n    return net","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:42.455046Z","iopub.execute_input":"2025-11-16T19:10:42.455744Z","iopub.status.idle":"2025-11-16T19:10:42.460453Z","shell.execute_reply.started":"2025-11-16T19:10:42.455721Z","shell.execute_reply":"2025-11-16T19:10:42.459655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load ensemble of models\ncheckpoint_paths = [\n    Path(\"../input/kkiller-birdclef-models-public/birdclef_resnest50_fold0_epoch_10_f1_val_06471_20210417161101.pth\"),\n]\n\nnets = [load_net(checkpoint_path) for checkpoint_path in checkpoint_paths]\nprint(f\"Loaded {len(nets)} model(s)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:45.059458Z","iopub.execute_input":"2025-11-16T19:10:45.059729Z","iopub.status.idle":"2025-11-16T19:10:47.062401Z","shell.execute_reply.started":"2025-11-16T19:10:45.059711Z","shell.execute_reply":"2025-11-16T19:10:47.061784Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference Functions","metadata":{}},{"cell_type":"code","source":"@torch.no_grad()\ndef get_thresh_preds(out, thresh=None):\n    \"\"\"Get predictions using thresholding.\"\"\"\n    thresh = thresh or THRESH\n    o = (-out).argsort(1)\n    npreds = (out > thresh).sum(1)\n    preds = []\n    for oo, npred in zip(o, npreds):\n        preds.append(oo[:npred].cpu().numpy().tolist())\n    return preds\n\n\ndef get_bird_names(preds):\n    \"\"\"Convert prediction indices to bird names.\"\"\"\n    bird_names = []\n    for pred in preds:\n        if not pred:\n            bird_names.append(\"nocall\")\n        else:\n            bird_names.append(\" \".join([INV_LABEL_IDS[bird_id] for bird_id in pred]))\n    return bird_names\n\n\n@torch.no_grad()\ndef predict(nets, test_data, names=True):\n    \"\"\"Run inference on test data.\"\"\"\n    preds = []\n    \n    for idx in tqdm(range(len(test_data))):\n        xb = torch.from_numpy(test_data[idx]).to(DEVICE)\n        pred = 0.0\n        \n        # Ensemble predictions\n        for net in nets:\n            o = net(xb)\n            o = torch.sigmoid(o)\n            pred += o\n\n        pred /= len(nets)\n        \n        if names:\n            pred = get_bird_names(get_thresh_preds(pred))\n\n        preds.append(pred)\n    \n    return preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:52.073325Z","iopub.execute_input":"2025-11-16T19:10:52.073945Z","iopub.status.idle":"2025-11-16T19:10:52.080392Z","shell.execute_reply.started":"2025-11-16T19:10:52.073923Z","shell.execute_reply":"2025-11-16T19:10:52.079713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Run inference\nprint(\"Running inference...\")\npred_probas = predict(nets, test_data, names=False)\nprint(f\"Generated {len(pred_probas)} predictions\")\n\n# Apply threshold to get final predictions\npreds = [get_bird_names(get_thresh_preds(pred, thresh=THRESH)) for pred in pred_probas]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:10:54.478771Z","iopub.execute_input":"2025-11-16T19:10:54.479046Z","iopub.status.idle":"2025-11-16T19:11:41.145147Z","shell.execute_reply.started":"2025-11-16T19:10:54.479029Z","shell.execute_reply":"2025-11-16T19:11:41.144306Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Create Submission","metadata":{}},{"cell_type":"code","source":"def preds_as_df(data, preds):\n    \"\"\"Convert predictions to submission format.\"\"\"\n    sub = {\"row_id\": [], \"birds\": []}\n    \n    for row, pred in zip(data.itertuples(False), preds):\n        # Create row_ids for each 5-second segment\n        row_id = [f\"{row.id}_{row.site}_{5 * i}\" for i in range(1, len(pred) + 1)]\n        sub[\"birds\"] += pred\n        sub[\"row_id\"] += row_id\n    \n    sub = pd.DataFrame(sub)\n    \n# Merge with sample submission if available\n    if SAMPLE_SUB_PATH:\n        sample_sub = pd.read_csv(SAMPLE_SUB_PATH, usecols=[\"row_id\"])\n        sub = sample_sub.merge(sub, on=\"row_id\", how=\"left\")\n        sub[\"birds\"] = sub[\"birds\"].fillna(\"nocall\")\n    \n    return sub\n\n\n# Create submission dataframe\nsub = preds_as_df(data, preds)\nprint(f\"Submission shape: {sub.shape}\")\nsub.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:12:10.877179Z","iopub.execute_input":"2025-11-16T19:12:10.877467Z","iopub.status.idle":"2025-11-16T19:12:10.890058Z","shell.execute_reply.started":"2025-11-16T19:12:10.877446Z","shell.execute_reply":"2025-11-16T19:12:10.889437Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save submission\nsub.to_csv(\"submission.csv\", index=False)\nprint(\"Submission saved as 'submission.csv'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-16T19:12:14.754337Z","iopub.execute_input":"2025-11-16T19:12:14.754808Z","iopub.status.idle":"2025-11-16T19:12:14.767022Z","shell.execute_reply.started":"2025-11-16T19:12:14.754785Z","shell.execute_reply":"2025-11-16T19:12:14.766256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}