{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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"},{"sourceId":15853,"sourceType":"modelInstanceVersion","modelInstanceId":2739,"modelId":319},{"sourceId":425213,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":346579,"modelId":367853}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### Section 0: Notebook configuration\n\nThis notebook section is used to configure the notebook environment.","metadata":{}},{"cell_type":"code","source":"# Imports\nimport os\nimport gc\nfrom typing import Union\nimport math\nimport itertools\nimport keras\nimport torch\nimport torchaudio\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport tensorflow as tf\nfrom PIL import Image\nimport matplotlib.pyplot as plt","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:12.754796Z","iopub.execute_input":"2025-06-07T02:09:12.755303Z","iopub.status.idle":"2025-06-07T02:09:18.687417Z","shell.execute_reply.started":"2025-06-07T02:09:12.755279Z","shell.execute_reply":"2025-06-07T02:09:18.686541Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Config\nDEBUG = True\nUSE_GBV_CLASSIFIER = True\n\nBC_TRAINING_DATA_METADATA_FILE = \"/kaggle/input/birdclef-2025/train.csv\"\nGBV_CLASSIFIER_LABELS_FILE = \"/kaggle/input/bird-vocalization-classifier/tensorflow2/bird-vocalization-classifier/8/assets/label.csv\"\n\nTEST_SOUNDSCAPES_DATA_PATH = \"/kaggle/input/birdclef-2025/test_soundscapes\"\nTRAIN_SOUNDSCAPES_DATA_PATH = \"/kaggle/input/birdclef-2025/train_soundscapes\"\nEXT = \".ogg\"\nMAX_FILES = 10\n\n# Sampling rate\nSR = 32000\n\nUSE_SLICE = False\nLOAD_SLICE = SR * 10","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:18.688615Z","iopub.execute_input":"2025-06-07T02:09:18.68989Z","iopub.status.idle":"2025-06-07T02:09:18.696913Z","shell.execute_reply.started":"2025-06-07T02:09:18.689852Z","shell.execute_reply":"2025-06-07T02:09:18.695163Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Section 1: Load labels\n\nThis notebook section is used to load unique labels from the BirdCLEF+ 2025 competition dataset and the Google Bird Vocalization classifier.","metadata":{}},{"cell_type":"code","source":"def load_training_metadata(training_metadata_file: str) -> pd.DataFrame:\n    \"\"\"\n    This function loads the training data index file.\n\n    @param {str} training_metadata_file: The full path expression for the BirdCLEF+ 2025 training data metadata file.\n    @return {pd.DataFrame} _: The training data index file data as a Pandas DataFrame.\n    @raise {Exception} e\n    \"\"\"\n    try:\n        return pd.read_csv(training_metadata_file)\n    except Exception as e:\n        print(str(e))","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:18.697649Z","iopub.execute_input":"2025-06-07T02:09:18.697993Z","iopub.status.idle":"2025-06-07T02:09:18.715695Z","shell.execute_reply.started":"2025-06-07T02:09:18.697969Z","shell.execute_reply":"2025-06-07T02:09:18.714748Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def read_gbv_classifier_labels(labels_file: str) -> list:\n    \"\"\"\n    This function returns the Google Bird Vocalization classifier labels as a list.\n\n    @param {str} labels_file: The Google Bird Vocalization classifier labels file.\n    @return {list} gbv_classifier_labels\n    \"\"\"\n    gbv_classifier_labels = pd.read_csv(labels_file).iloc[:, 0].to_list()\n    return gbv_classifier_labels","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:18.717971Z","iopub.execute_input":"2025-06-07T02:09:18.718439Z","iopub.status.idle":"2025-06-07T02:09:18.729507Z","shell.execute_reply.started":"2025-06-07T02:09:18.718414Z","shell.execute_reply":"2025-06-07T02:09:18.728699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Read BirdCLEF+ 2025 training metadata file and get unique labels\nbc_metadata = load_training_metadata(BC_TRAINING_DATA_METADATA_FILE)\nbc_labels = bc_metadata[\"primary_label\"].unique()","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:18.730186Z","iopub.execute_input":"2025-06-07T02:09:18.730367Z","iopub.status.idle":"2025-06-07T02:09:18.850474Z","shell.execute_reply.started":"2025-06-07T02:09:18.730352Z","shell.execute_reply":"2025-06-07T02:09:18.849964Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Read GBV classifier labels file and get unique labels\ngbv_classifier_labels = read_gbv_classifier_labels(GBV_CLASSIFIER_LABELS_FILE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:18.851128Z","iopub.execute_input":"2025-06-07T02:09:18.85135Z","iopub.status.idle":"2025-06-07T02:09:18.860994Z","shell.execute_reply.started":"2025-06-07T02:09:18.851326Z","shell.execute_reply":"2025-06-07T02:09:18.860408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Get BirdCLEF+ 2025 classes not in GBV classifier classes\ndef extract_classes_not_in_gbvc(bc_labels: list, gbvc_labels: str) -> list:\n    \"\"\"\n    Returns the list of BirdCLEF+ 2025 classes that are not part of the Google Bird Vocalization (GBV) classifier.\n\n    @param {list} bc_labels: List of unique labels in the BirdCLEF+ 2025 dataset.\n    @param {str} gbvc_labels: GBV classifier labels.\n    @return {list} _: The BirdCLEF+ 2025 classes not covered by the GBV classifier.\n    \"\"\"\n    return [bc_label for bc_label in bc_labels if bc_label not in gbvc_labels]\n\nbc_non_gbv_labels = extract_classes_not_in_gbvc(bc_labels, gbv_classifier_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:18.861593Z","iopub.execute_input":"2025-06-07T02:09:18.861818Z","iopub.status.idle":"2025-06-07T02:09:18.892322Z","shell.execute_reply.started":"2025-06-07T02:09:18.861801Z","shell.execute_reply":"2025-06-07T02:09:18.891631Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"### DEBUG/OUTPUT CELL ###\nassert 63 == len(bc_non_gbv_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:18.892988Z","iopub.execute_input":"2025-06-07T02:09:18.893212Z","iopub.status.idle":"2025-06-07T02:09:18.901523Z","shell.execute_reply.started":"2025-06-07T02:09:18.893195Z","shell.execute_reply":"2025-06-07T02:09:18.900934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create indices map from BC labels to non-GBV BC labels\nmap_bc_labels_to_bc_non_gbv_labels = [-1] * len(bc_labels)\nfor l in range(len(bc_labels)):\n    bc_label = bc_labels[l]\n    if bc_label in bc_non_gbv_labels:\n        idx = bc_non_gbv_labels.index(bc_label)\n        map_bc_labels_to_bc_non_gbv_labels[l] = idx\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:18.902205Z","iopub.execute_input":"2025-06-07T02:09:18.902418Z","iopub.status.idle":"2025-06-07T02:09:18.914505Z","shell.execute_reply.started":"2025-06-07T02:09:18.902393Z","shell.execute_reply":"2025-06-07T02:09:18.913967Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"### DEBUG/OUTPUT CELL ###\nprint(map_bc_labels_to_bc_non_gbv_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:18.916549Z","iopub.execute_input":"2025-06-07T02:09:18.91674Z","iopub.status.idle":"2025-06-07T02:09:18.927447Z","shell.execute_reply.started":"2025-06-07T02:09:18.916726Z","shell.execute_reply":"2025-06-07T02:09:18.926869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# If using GBV classifier predictions, create indices map from BC labels to GBV classifier labels\nif USE_GBV_CLASSIFIER:\n    map_bc_labels_to_gbv_classifier_labels = [-1] * len(bc_labels)\n    for l in range(len(bc_labels)):\n        bc_label = bc_labels[l]\n        if bc_label in gbv_classifier_labels:\n            idx = gbv_classifier_labels.index(bc_label)\n            map_bc_labels_to_gbv_classifier_labels[l] = idx\nelse:\n    map_bc_labels_to_gbv_classifier_labels = None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:18.928143Z","iopub.execute_input":"2025-06-07T02:09:18.928336Z","iopub.status.idle":"2025-06-07T02:09:18.941415Z","shell.execute_reply.started":"2025-06-07T02:09:18.928321Z","shell.execute_reply":"2025-06-07T02:09:18.940809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"### DEBUG/OUTPUT CELL ###\n# if USE_GBV_CLASSIFIER:\n#     print(\"First 10 BirdCLEF+ 2025 labels:\")\n#     print(bc_labels[:10])\n#     print(\"Length of BirdCLEF+ 2025 labels:\")\n#     print(len(bc_labels)) \n#     print(\"First 10 GBV classifier labels:\")\n#     print(gbv_classifier_labels[:10])\n#     print(\"Length of GBV classifier labels:\")\n#     print(len(gbv_classifier_labels))\n#     print(\"map_bc_labels_to_gbv_classifier_labels:\")\n#     print(map_bc_labels_to_gbv_classifier_labels)\n# else:\n#     print(\"First 10 BirdCLEF+ 2025 labels:\")\n#     print(bc_labels[:10])\n#     print(\"Length of BirdCLEF+ 2025 labels:\")\n#     print(len(bc_labels)) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:18.94202Z","iopub.execute_input":"2025-06-07T02:09:18.942256Z","iopub.status.idle":"2025-06-07T02:09:18.955196Z","shell.execute_reply.started":"2025-06-07T02:09:18.942238Z","shell.execute_reply":"2025-06-07T02:09:18.954539Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Section 2: Soundscapes Loading\n\nThis section is used to load raw soundscapes data.","metadata":{}},{"cell_type":"code","source":"def load_soundscapes_for_prediction(debug: bool = True, use_slice: bool = False) -> dict:\n    \"\"\"\n    Loads soundscapes audio data for generating predictions.\n\n    @param {bool} debug: If `True`, soundscapes audio is loaded from training data; otherwise, test soundscapes audio is loaded.\n    @param {bool} use_slice: Optional; if `True`, audio is sliced using LOAD_SLICE constant.\n    @return {dict} _audio\n    @raise {Exception} e\n    \"\"\"\n    try:\n        _audio = {}\n        \n        if debug:\n            _path = TRAIN_SOUNDSCAPES_DATA_PATH\n            _files = os.listdir(_path)\n        else:\n            _path = TEST_SOUNDSCAPES_DATA_PATH\n            _files = os.listdir(_path)\n\n        # Get `.ogg` soundscape files\n        _files = [_file for _file in _files if _file.endswith(EXT)]\n        _file_count = 0\n\n        for _file in _files:            \n            if _file_count < MAX_FILES:\n                _audio_tensor, _ = read_audio_data(_path + \"/\" + _file)\n                if use_slice:\n                    if len(_audio_tensor[0]) > LOAD_SLICE:\n                        _audio_tensor = _audio_tensor[:,:LOAD_SLICE] # Return all rows of tensor, but only columns up to SLICE length\n                _audio[_file] =  _audio_tensor\n                _file_count += 1\n            else:\n                break\n        return _audio\n    except Exception as e:\n        print(str(e))\n\ndef read_audio_data(file: str) -> torch.Tensor:\n    _audio_tensor, _sampling_rate = torchaudio.load(file, normalize=True)\n    return _audio_tensor, _sampling_rate","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:18.955815Z","iopub.execute_input":"2025-06-07T02:09:18.956071Z","iopub.status.idle":"2025-06-07T02:09:18.97382Z","shell.execute_reply.started":"2025-06-07T02:09:18.956055Z","shell.execute_reply":"2025-06-07T02:09:18.973237Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load soundscapes\nsoundscapes_audio = load_soundscapes_for_prediction(DEBUG, False)","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:18.974404Z","iopub.execute_input":"2025-06-07T02:09:18.974589Z","iopub.status.idle":"2025-06-07T02:09:19.468632Z","shell.execute_reply.started":"2025-06-07T02:09:18.974576Z","shell.execute_reply":"2025-06-07T02:09:19.468099Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"### DEBUG/OUTPUT CELL ###\nprint(dict(itertools.islice(soundscapes_audio.items(), 2)))","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:19.469369Z","iopub.execute_input":"2025-06-07T02:09:19.469616Z","iopub.status.idle":"2025-06-07T02:09:19.472803Z","shell.execute_reply.started":"2025-06-07T02:09:19.469591Z","shell.execute_reply":"2025-06-07T02:09:19.472104Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Section 3: Audio Processing Pipeline Functions\n\nThis notebook section defines the constants and functions used to process audio data prior to running predictions.","metadata":{}},{"cell_type":"code","source":"# Config\nFRAME_LENGTH = 5\nFRAME_STEP = 5\n\n# Mel spectrogram parameters\nN_FFT = 1024  # FFT size\nHOP_SIZE = 256\nN_MELS = 256\nFMIN = 50  # minimum frequency\nFMAX = 14000 # maximum frequency\n\n# Other\nIMG_SIZE = 224  # target image shape","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:19.473566Z","iopub.execute_input":"2025-06-07T02:09:19.473828Z","iopub.status.idle":"2025-06-07T02:09:19.486536Z","shell.execute_reply.started":"2025-06-07T02:09:19.473806Z","shell.execute_reply":"2025-06-07T02:09:19.485863Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def frame_audio(audio_data: np.ndarray, frame_length: int = FRAME_LENGTH, frame_step: int = FRAME_STEP, sample_rate = SR) -> list[tf.Tensor]:\n    \"\"\"\n    Returns an audio signal as a set of frames based on supplied frame length and frame step.\n\n    @param {np.ndarray} audio_data: The raw audio signal data.\n    @param {float} frame_length: The frame length expressed in seconds.\n    @param {float} hop_size: The frame step expressed in seconds.\n    @param {int} sample_rate: The sampling rate expressed in Hz.\n    @return {tf.Tensor} framed_audio: Set of audio frames derived from original audio signal data.\n    \"\"\"\n    if frame_length is None or frame_length < 0:\n        return audio_data[np.newaxis, :]\n    length = int(frame_length * sample_rate)\n    step = int(frame_step * sample_rate)\n    frames = tf.signal.frame(audio_data, length, step, pad_end=True, pad_value=0, axis=-1)\n    return frames","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:19.4872Z","iopub.execute_input":"2025-06-07T02:09:19.487476Z","iopub.status.idle":"2025-06-07T02:09:19.502961Z","shell.execute_reply.started":"2025-06-07T02:09:19.48746Z","shell.execute_reply":"2025-06-07T02:09:19.5023Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generate audio frames\naudio_frames = []\nrow_ids = []\n\nfor key in soundscapes_audio.keys():\n    frames = frame_audio(soundscapes_audio[key], FRAME_LENGTH, FRAME_STEP, SR)\n    np_frames = [frame.numpy() for frame in frames[0]]\n    audio_frames = audio_frames + np_frames\n    names = []\n    for f in range(0, len(np_frames)):\n        names.append(key.replace(\".ogg\", \"\") + \"_\" + str(f * FRAME_STEP))\n    row_ids = row_ids + names","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:19.503733Z","iopub.execute_input":"2025-06-07T02:09:19.503978Z","iopub.status.idle":"2025-06-07T02:09:20.119466Z","shell.execute_reply.started":"2025-06-07T02:09:19.503959Z","shell.execute_reply":"2025-06-07T02:09:20.118663Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"### DEBUG/OUTPUT CELL ###\nprint(audio_frames[:3])\nprint(row_ids[:3])\nprint(len(audio_frames))\nprint(len(row_ids))","metadata":{"execution":{"iopub.status.busy":"2025-06-07T02:09:20.120312Z","iopub.execute_input":"2025-06-07T02:09:20.120634Z","iopub.status.idle":"2025-06-07T02:09:20.124333Z","shell.execute_reply.started":"2025-06-07T02:09:20.120609Z","shell.execute_reply":"2025-06-07T02:09:20.123692Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def audio2melspec(audio_data):\n    \"\"\"\n    Convert the raw audio data into a normalized mel spectrogram.\n    \n    @param {torch.tensor} audio_data: The audio samples as a Tensor object.\n    @return {np.ndarray} mel_spec_norm: Normalized mel spectrogram.\n    \"\"\"\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=SR,\n        n_fft=N_FFT,\n        hop_length=HOP_SIZE,\n        n_mels=N_MELS,\n        fmin=FMIN,\n        fmax=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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:20.124939Z","iopub.execute_input":"2025-06-07T02:09:20.125167Z","iopub.status.idle":"2025-06-07T02:09:20.13959Z","shell.execute_reply.started":"2025-06-07T02:09:20.125151Z","shell.execute_reply":"2025-06-07T02:09:20.139082Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def to_melspectrogram_image(mel_spectrogram_norm, target_size=(IMG_SIZE, IMG_SIZE)):\n    \"\"\"\n    Converts an audio file to a mel spectrogram image.\n\n    @param {np.ndarray} mel_spectrogram_norm: Normalized mel spectrogram data.\n    @param {tuple(int, int)} target_size: The target size for the resized image.\n    @return {np.ndarray} mel_spectrogram_resized: A 224x224 NumPy array representing the mel spectrogram image (or None if there's an error).\n    @raise {Exception} e: Outputs exception and returns `None`.\n    \"\"\"\n    try:\n        img = Image.fromarray(mel_spectrogram_norm * 255).convert(\"RGB\")\n        img = img.resize(target_size, Image.Resampling.LANCZOS)\n        mel_spectrogram_resized = np.array(img) / 1.0 # No normalization\n        return mel_spectrogram_resized\n    except Exception as e:\n        print(str(e))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:20.14028Z","iopub.execute_input":"2025-06-07T02:09:20.140979Z","iopub.status.idle":"2025-06-07T02:09:20.159176Z","shell.execute_reply.started":"2025-06-07T02:09:20.140955Z","shell.execute_reply":"2025-06-07T02:09:20.158438Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Section 4: Load models\n\nThis notebook section loads the classifier model(s).","metadata":{}},{"cell_type":"code","source":"model_bc = keras.saving.load_model(\"[INSERT_PATH_TO_YOUR_FINETUNED_MODEL_HERE]\")\nif USE_GBV_CLASSIFIER:\n    model_gbv = tf.saved_model.load(\"/kaggle/input/bird-vocalization-classifier/tensorflow2/bird-vocalization-classifier/8\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:20.159933Z","iopub.execute_input":"2025-06-07T02:09:20.160209Z","iopub.status.idle":"2025-06-07T02:09:22.485204Z","shell.execute_reply.started":"2025-06-07T02:09:20.160193Z","shell.execute_reply":"2025-06-07T02:09:22.484386Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Section 5: Running Predictions\n\nThis notebook section runs predictions.","metadata":{}},{"cell_type":"code","source":"# Config\nGBV_CLASSIFIER_THRESHOLD = 10.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:22.486113Z","iopub.execute_input":"2025-06-07T02:09:22.486383Z","iopub.status.idle":"2025-06-07T02:09:22.489973Z","shell.execute_reply.started":"2025-06-07T02:09:22.48636Z","shell.execute_reply":"2025-06-07T02:09:22.48923Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_preds(audio_frame: tf.Tensor, bc_labels: list, map_bc_labels_to_gbv_classifier_labels: list, map_bc_labels_to_bc_non_gbv_labels: list, use_gbv_classifier: bool, gbv_classifier_threshold: float = 0.75) -> list:\n    \"\"\"\n    This function generates prediction(s) on audio frame data. If `use_gbv_classifier` is set to \n    `True`, the function first generates predictions using the GBV classifier. The predictions for the\n    143 BirdCLEF+ 2025 competition dataset classes known to the GBV classifier are isolated. If the\n    maximum probability among the 143 \"known\" classes is above or equal to `gbv_classifier_threshold`, \n    then the GBV predicted class is selected as the true class. If the maximum probability among the 143 \n    \"known\" classes  is below `gbv_classifier_threshold`, it is assumed that the true class is among the\n    63 classes \"unknown\" to the GBV classifier. The logic then runs predictions using the finetuned `bc_model`. \n    The predicted class from that prediction set is selected as the true class.\n\n    @param {tf.Tensor} audio_frame: The audio frame data that prediction(s) will be generated for.\n    @param {list} bc_labels: List of unique BirdCLEF+ 2025 competition dataset labels.\n    @param {list} map_bc_labels_to_gbv_classifier_labels: Mapping of unique BirdCLEF+ 2025 labels to their indices in `gbv_classifier_labels`.\n    @param {list} map_bc_labels_to_bc_non_gbv_labels: Mapping of unique BirdCLEF+ 2025 labels to their indices in `bc_non_gbv_labels`.\n    @param {bool} use_gbv_classifier: If `True`, the GBV classifier is also used to generate a prediction on the audio frame.\n    @param {float} gbv_classifier_threshold: The threshold used to decide if a GBV classifier prediction is kept.\n    @return {np.ndarray} _: The classifier model predictions.\n    \"\"\"\n    if use_gbv_classifier:\n        logits = model_gbv.infer_tf(tf.expand_dims(audio_frame, 0))[\"label\"]\n        probs = tf.math.softmax(logits, axis=-1)\n        probs = probs.numpy()\n        \n        gbv_row = [0] * len(bc_labels)\n        for r in range(len(map_bc_labels_to_gbv_classifier_labels)):\n            gbv_row[r] = probs[0][map_bc_labels_to_gbv_classifier_labels[r]] if map_bc_labels_to_gbv_classifier_labels[r] != -1 else 0\n\n        gbv_row = list(map(float, gbv_row))\n        \n        gbv_pred_prob = max(gbv_row)\n        gbv_pred_idx = gbv_row.index(max(gbv_row))\n        \n        if gbv_pred_prob >= gbv_classifier_threshold:\n            return gbv_row, gbv_pred_prob, bc_labels[gbv_pred_idx]\n        else:\n            # Prediction by GBV classifier does not meet or exceed threshold\n            use_gbv_classifier = False\n\n    if not use_gbv_classifier:\n        # Run predictions using BC model        \n        mel_spec = audio2melspec(audio_frame)\n        mel_spec_resized = to_melspectrogram_image(mel_spec, (IMG_SIZE, IMG_SIZE))\n        dataset = tf.data.Dataset.from_tensors([mel_spec_resized])\n        logits = model_bc.predict(dataset)\n        probs = tf.math.softmax(logits, axis=-1)\n        probs = probs.numpy()\n        \n        bc_row = [0] * len(bc_labels)\n        for r in range(len(map_bc_labels_to_bc_non_gbv_labels)):\n            bc_row[r] = probs[0][map_bc_labels_to_bc_non_gbv_labels[r]] if map_bc_labels_to_bc_non_gbv_labels[r] != -1 else 0\n        \n        bc_row = list(map(float, bc_row))\n        \n        bc_pred_prob = max(bc_row)\n        bc_pred_idx = bc_row.index(max(bc_row))      \n        return bc_row, bc_pred_prob, bc_labels[bc_pred_idx]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:22.491096Z","iopub.execute_input":"2025-06-07T02:09:22.491347Z","iopub.status.idle":"2025-06-07T02:09:22.50592Z","shell.execute_reply.started":"2025-06-07T02:09:22.491325Z","shell.execute_reply":"2025-06-07T02:09:22.505256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"rows = []\npreds = []\nfor i in range(len(audio_frames)): # Uncomment to run preds on all audio frames\n# for i in range(20): # Uncomment to run preds on subset of audio frames\n    row, pred_prob, pred_class = generate_preds(audio_frames[i], bc_labels, map_bc_labels_to_gbv_classifier_labels, map_bc_labels_to_bc_non_gbv_labels, USE_GBV_CLASSIFIER, GBV_CLASSIFIER_THRESHOLD)\n    rows.append(row)\n    preds.append((pred_class, pred_prob))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:22.506581Z","iopub.execute_input":"2025-06-07T02:09:22.506853Z","iopub.status.idle":"2025-06-07T02:09:39.144518Z","shell.execute_reply.started":"2025-06-07T02:09:22.506837Z","shell.execute_reply":"2025-06-07T02:09:39.143972Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"### DEBUG/OUTPUT CELL ###\nprint(preds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:09:39.145267Z","iopub.execute_input":"2025-06-07T02:09:39.145737Z","iopub.status.idle":"2025-06-07T02:09:39.150383Z","shell.execute_reply.started":"2025-06-07T02:09:39.145717Z","shell.execute_reply":"2025-06-07T02:09:39.14961Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Section 6: Save predictions to submission file\n\nThis notebook section saves classifier predictions and probabilities to CSV files. Probabilities are saved using the submission file format for the BirdCLEF+ 2025 competition.","metadata":{}},{"cell_type":"code","source":"SAVE_PATH = \"/kaggle/working/\"\n\n# Export probabilities to BirdCLEF+ 2025 competition submission file format\ndef format_prob(p):\n    return f\"{p:.10f}\"\n\nwith open(SAVE_PATH + \"/\" + \"submission.csv\", \"w\", encoding=\"utf8\") as f:\n    # Write header\n    header = \"row_id,\" + \",\".join(bc_labels) + \"\\n\"\n    f.write(header)\n\n    # Write probabilities\n    for i in range(len(rows)):\n        data = row_ids[i] + \",\" + \",\".join(map(format_prob, rows[i])) + \"\\n\"\n        f.write(data)\n\n# Export predictions\nwith open(SAVE_PATH + \"/\" + \"preds.csv\", \"w\", encoding=\"utf8\") as p:\n    # Write header\n    header = \"row_id,predicted_class,predicted_prob\" + \"\\n\"\n    p.write(header)\n\n    # Write predictions\n    for i in range(len(rows)):\n        data = row_ids[i] + \",\" + str(preds[i][0]) + \",\" + str(preds[i][1]) + \"\\n\"\n        p.write(data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T02:10:54.193092Z","iopub.execute_input":"2025-06-07T02:10:54.193669Z","iopub.status.idle":"2025-06-07T02:10:54.210757Z","shell.execute_reply.started":"2025-06-07T02:10:54.193645Z","shell.execute_reply":"2025-06-07T02:10:54.210127Z"}},"outputs":[],"execution_count":null}]}