{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":44224,"databundleVersionId":5188730,"sourceType":"competition"},{"sourceId":3836,"sourceType":"modelInstanceVersion","modelInstanceId":2739,"modelId":319}],"dockerImageVersionId":30407,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"In this notebook, we'll use a pre-trained machine learning model to generate a submission to the [BirdClef2023 competition](https://www.kaggle.com/c/birdclef-2023).  The goal of the competition is to identify Eastern African bird species by sound.","metadata":{}},{"cell_type":"markdown","source":"## Step 1: Imports","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport tensorflow_hub as hub\nimport tensorflow_io as tfio\n\nimport pandas as pd\nimport numpy as np\nimport librosa\nimport glob\n\nimport csv\nimport io\n\nfrom IPython.display import Audio","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2025-06-22T09:50:57.254887Z","iopub.execute_input":"2025-06-22T09:50:57.255304Z","iopub.status.idle":"2025-06-22T09:50:57.261384Z","shell.execute_reply.started":"2025-06-22T09:50:57.255266Z","shell.execute_reply":"2025-06-22T09:50:57.259930Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 2: Explore the training data\n\nWe'll start by loading a couple of training examples and using the IPython.display.Audio module to play them!","metadata":{}},{"cell_type":"code","source":"# Load a sample audio files from two different species\naudio_abe, sr_abe = librosa.load(\"/kaggle/input/birdclef-2023/train_audio/abethr1/XC128013.ogg\")\naudio_abh, sr_abh = librosa.load(\"/kaggle/input/birdclef-2023/train_audio/abhori1/XC127317.ogg\")","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:50:57.263780Z","iopub.execute_input":"2025-06-22T09:50:57.264169Z","iopub.status.idle":"2025-06-22T09:50:57.421332Z","shell.execute_reply.started":"2025-06-22T09:50:57.264105Z","shell.execute_reply":"2025-06-22T09:50:57.420098Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Play the audio\nAudio(data=audio_abe, rate=sr_abe)","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:50:57.422598Z","iopub.execute_input":"2025-06-22T09:50:57.422929Z","iopub.status.idle":"2025-06-22T09:50:57.479701Z","shell.execute_reply.started":"2025-06-22T09:50:57.422895Z","shell.execute_reply":"2025-06-22T09:50:57.478188Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Play the audio\nAudio(data=audio_abh, rate=sr_abh)","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:50:57.481935Z","iopub.execute_input":"2025-06-22T09:50:57.482383Z","iopub.status.idle":"2025-06-22T09:50:57.535947Z","shell.execute_reply.started":"2025-06-22T09:50:57.482344Z","shell.execute_reply":"2025-06-22T09:50:57.534489Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 3: Match the model's output with the bird species in the competition\n\nThe competition includes 264 classes of birds, 261 of which exist in this model. We'll set up a way to map the model's output logits to our competition.","metadata":{}},{"cell_type":"code","source":"model = hub.load('https://kaggle.com/models/google/bird-vocalization-classifier/frameworks/tensorFlow2/variations/bird-vocalization-classifier/versions/1')\nlabels_path = hub.resolve('https://kaggle.com/models/google/bird-vocalization-classifier/frameworks/tensorFlow2/variations/bird-vocalization-classifier/versions/1') + \"/assets/label.csv\"","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:50:57.537465Z","iopub.execute_input":"2025-06-22T09:50:57.537823Z","iopub.status.idle":"2025-06-22T09:51:03.801479Z","shell.execute_reply.started":"2025-06-22T09:50:57.537789Z","shell.execute_reply":"2025-06-22T09:51:03.800416Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find the name of the class with the top score when mean-aggregated across frames.\ndef class_names_from_csv(class_map_csv_text):\n    \"\"\"Returns list of class names corresponding to score vector.\"\"\"\n    with open(labels_path) as csv_file:\n        csv_reader = csv.reader(csv_file, delimiter=',')\n        class_names = [mid for mid, desc in csv_reader]\n        return class_names[1:]\n\n## note that the bird classifier classifies a much larger set of birds than the\n## competition, so we need to load the model's set of class names or else our \n## indices will be off.\nclasses = class_names_from_csv(labels_path)","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:03.802700Z","iopub.execute_input":"2025-06-22T09:51:03.803022Z","iopub.status.idle":"2025-06-22T09:51:03.814022Z","shell.execute_reply.started":"2025-06-22T09:51:03.802992Z","shell.execute_reply":"2025-06-22T09:51:03.812535Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_metadata = pd.read_csv(\"/kaggle/input/birdclef-2023/train_metadata.csv\")\ntrain_metadata.head()\ncompetition_classes = sorted(train_metadata.primary_label.unique())\n\nforced_defaults = 0\ncompetition_class_map = []\nfor c in competition_classes:\n    try:\n        i = classes.index(c)\n        competition_class_map.append(i)\n    except:\n        competition_class_map.append(0)\n        forced_defaults += 1\n        \n## this is the count of classes not supported by our pretrained model\n## you could choose to simply not predict these, set a default as above,\n## or create your own model using the pretrained model as a base.\nforced_defaults","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:03.815543Z","iopub.execute_input":"2025-06-22T09:51:03.815877Z","iopub.status.idle":"2025-06-22T09:51:03.928856Z","shell.execute_reply.started":"2025-06-22T09:51:03.815844Z","shell.execute_reply":"2025-06-22T09:51:03.927605Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 4: Preprocess the data\n\nThe following functions are one way to load the audio provided and break it up into the five-second samples with a sample rate of 32,000 required by the competition.","metadata":{}},{"cell_type":"code","source":"def frame_audio(\n      audio_array: np.ndarray,\n      window_size_s: float = 5.0,\n      hop_size_s: float = 5.0,\n      sample_rate = 32000,\n      ) -> np.ndarray:\n    \n    \"\"\"Helper function for framing audio for inference.\"\"\"\n    \"\"\" using tf.signal \"\"\"\n    if window_size_s is None or window_size_s < 0:\n        return audio_array[np.newaxis, :]\n    frame_length = int(window_size_s * sample_rate)\n    hop_length = int(hop_size_s * sample_rate)\n    framed_audio = tf.signal.frame(audio_array, frame_length, hop_length, pad_end=True)\n    return framed_audio\n\ndef ensure_sample_rate(waveform, original_sample_rate,\n                       desired_sample_rate=32000):\n    \"\"\"Resample waveform if required.\"\"\"\n    if original_sample_rate != desired_sample_rate:\n        waveform = tfio.audio.resample(waveform, original_sample_rate, desired_sample_rate)\n    return desired_sample_rate, waveform","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:03.930335Z","iopub.execute_input":"2025-06-22T09:51:03.930775Z","iopub.status.idle":"2025-06-22T09:51:03.939836Z","shell.execute_reply.started":"2025-06-22T09:51:03.930728Z","shell.execute_reply":"2025-06-22T09:51:03.938342Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Below we load one training sample - use the Audio function to listen to the samples inside the notebook!","metadata":{}},{"cell_type":"code","source":"audio, sample_rate = librosa.load(\"/kaggle/input/birdclef-2023/train_audio/afghor1/XC156639.ogg\")\nsample_rate, wav_data = ensure_sample_rate(audio, sample_rate)\nAudio(wav_data, rate=sample_rate)","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:03.943182Z","iopub.execute_input":"2025-06-22T09:51:03.943530Z","iopub.status.idle":"2025-06-22T09:51:04.228679Z","shell.execute_reply.started":"2025-06-22T09:51:03.943497Z","shell.execute_reply":"2025-06-22T09:51:04.226383Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 5: Make predictions\n\nEach test sample is cut into 5-second chunks. We use the pretrained model to return probabilities for all 10k birds included in the model, then pull out the classes used in this competition to create a final submission row. Note that we are NOT doing anything special to handle the 3 missing classes; those will need fine-tuning / transfer learning, which will be handled in a separate notebook.","metadata":{}},{"cell_type":"code","source":"fixed_tm = frame_audio(wav_data)\nlogits, embeddings = model.infer_tf(fixed_tm[:1])\nprobabilities = tf.nn.softmax(logits)\nargmax = np.argmax(probabilities)\nprint(f\"The audio is from the class {classes[argmax]} (element:{argmax} in the label.csv file), with probability of {probabilities[0][argmax]}\")","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:04.230432Z","iopub.execute_input":"2025-06-22T09:51:04.230810Z","iopub.status.idle":"2025-06-22T09:51:13.784209Z","shell.execute_reply.started":"2025-06-22T09:51:04.230773Z","shell.execute_reply":"2025-06-22T09:51:13.783046Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# NEW CELL - Add after Cell 10\ndef load_ebird_taxonomy():\n    \"\"\"Load eBird taxonomy and create species code to common name mapping\"\"\"\n    try:\n        taxonomy = pd.read_csv(\"/kaggle/input/birdclef-2023/eBird_Taxonomy_v2021.csv\")\n        \n        # Create dictionary mapping species codes to names\n        code_to_common = dict(zip(taxonomy['SPECIES_CODE'], taxonomy['PRIMARY_COM_NAME']))\n        code_to_scientific = dict(zip(taxonomy['SPECIES_CODE'], taxonomy['SCI_NAME']))\n        \n        return code_to_common, code_to_scientific\n    except Exception as e:\n        print(f\"Error loading taxonomy: {e}\")\n        # Fallback: create empty dictionaries\n        return {}, {}\n\ndef get_bird_name(species_code, code_to_common_dict):\n    \"\"\"Convert species code to common name\"\"\"\n    return code_to_common_dict.get(species_code, f\"Unknown species: {species_code}\")\n\n# Load the taxonomy mappings\ncode_to_common, code_to_scientific = load_ebird_taxonomy()\nprint(f\"Loaded {len(code_to_common)} species mappings\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:51:13.785667Z","iopub.execute_input":"2025-06-22T09:51:13.786086Z","iopub.status.idle":"2025-06-22T09:51:13.850942Z","shell.execute_reply.started":"2025-06-22T09:51:13.786043Z","shell.execute_reply":"2025-06-22T09:51:13.849690Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_for_sample(filename, sample_submission, frame_limit_secs=None):\n    file_id = filename.split(\".ogg\")[0].split(\"/\")[-1]\n    \n    audio, sample_rate = librosa.load(filename)\n    sample_rate, wav_data = ensure_sample_rate(audio, sample_rate)\n    \n    fixed_tm = frame_audio(wav_data)\n    \n    frame = 5\n    all_logits, all_embeddings = model.infer_tf(fixed_tm[:1])\n    for window in fixed_tm[1:]:\n        if frame_limit_secs and frame > frame_limit_secs:\n            continue\n        \n        logits, embeddings = model.infer_tf(window[np.newaxis, :])\n        all_logits = np.concatenate([all_logits, logits], axis=0)\n        frame += 5\n    \n    frame = 5\n    all_probabilities = []\n    for frame_logits in all_logits:\n        probabilities = tf.nn.softmax(frame_logits).numpy()\n        \n        ## set the appropriate row in the sample submission\n        sample_submission.loc[sample_submission.row_id == file_id + \"_\" + str(frame), competition_classes] = probabilities[competition_class_map]\n        frame += 5","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:13.852216Z","iopub.execute_input":"2025-06-22T09:51:13.852527Z","iopub.status.idle":"2025-06-22T09:51:13.861197Z","shell.execute_reply.started":"2025-06-22T09:51:13.852496Z","shell.execute_reply":"2025-06-22T09:51:13.860024Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 6: Generate a submission\n\nNow we process all of the test samples as discussed above, creating output rows, and saving them in the provided `sample_submission.csv`. Finally, we save these rows to our final output file: `submission.csv`. This is the file that gets submitted and scored when you submit the notebook.","metadata":{}},{"cell_type":"code","source":"test_samples = list(glob.glob(\"/kaggle/input/birdclef-2023/test_soundscapes/*.ogg\"))\ntest_samples","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:13.862567Z","iopub.execute_input":"2025-06-22T09:51:13.862854Z","iopub.status.idle":"2025-06-22T09:51:13.876882Z","shell.execute_reply.started":"2025-06-22T09:51:13.862828Z","shell.execute_reply":"2025-06-22T09:51:13.875613Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_sub = pd.read_csv(\"/kaggle/input/birdclef-2023/sample_submission.csv\")\nsample_sub[competition_classes] = sample_sub[competition_classes].astype(np.float32)\nsample_sub.head()","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:13.878225Z","iopub.execute_input":"2025-06-22T09:51:13.878564Z","iopub.status.idle":"2025-06-22T09:51:13.966302Z","shell.execute_reply.started":"2025-06-22T09:51:13.878533Z","shell.execute_reply":"2025-06-22T09:51:13.964930Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"frame_limit_secs = 15 if sample_sub.shape[0] == 3 else None\nfor sample_filename in test_samples:\n    predict_for_sample(sample_filename, sample_sub, frame_limit_secs=15)","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:13.967950Z","iopub.execute_input":"2025-06-22T09:51:13.968306Z","iopub.status.idle":"2025-06-22T09:51:25.078497Z","shell.execute_reply.started":"2025-06-22T09:51:13.968275Z","shell.execute_reply":"2025-06-22T09:51:25.077161Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_sub","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:25.079915Z","iopub.execute_input":"2025-06-22T09:51:25.080366Z","iopub.status.idle":"2025-06-22T09:51:25.110199Z","shell.execute_reply.started":"2025-06-22T09:51:25.080320Z","shell.execute_reply":"2025-06-22T09:51:25.108917Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_sub.to_csv(\"submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2025-06-22T09:51:25.111594Z","iopub.execute_input":"2025-06-22T09:51:25.111910Z","iopub.status.idle":"2025-06-22T09:51:25.127345Z","shell.execute_reply.started":"2025-06-22T09:51:25.111879Z","shell.execute_reply":"2025-06-22T09:51:25.126158Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# NEW CELL - Add after Cell 16\ndef predict_single_audio_file(audio_file_path, top_n=5):\n    \"\"\"\n    Predict bird species for a single audio file and return common names\n    \n    Args:\n        audio_file_path: Path to the audio file (.ogg, .wav, .mp3, etc.)\n        top_n: Number of top predictions to return (default: 5)\n    \n    Returns:\n        List of dictionaries with species predictions and common names\n    \"\"\"\n    try:\n        # Load and preprocess the audio\n        audio, sample_rate = librosa.load(audio_file_path)\n        sample_rate, wav_data = ensure_sample_rate(audio, sample_rate)\n        \n        # Frame the audio into 5-second segments\n        fixed_tm = frame_audio(wav_data)\n        \n        print(f\"Processing {len(fixed_tm)} segments from audio file...\")\n        \n        # Get predictions for all segments\n        all_probabilities = []\n        \n        for i, segment in enumerate(fixed_tm):\n            logits, embeddings = model.infer_tf(segment[np.newaxis, :])\n            probabilities = tf.nn.softmax(logits[0]).numpy()\n            \n            # Map to competition classes\n            competition_probabilities = probabilities[competition_class_map]\n            all_probabilities.append(competition_probabilities)\n            \n            if (i + 1) % 10 == 0:\n                print(f\"Processed {i + 1}/{len(fixed_tm)} segments\")\n        \n        # Average probabilities across all segments\n        avg_probabilities = np.mean(all_probabilities, axis=0)\n        \n        # Get top predictions\n        top_indices = np.argsort(avg_probabilities)[-top_n:][::-1]\n        \n        results = []\n        for idx in top_indices:\n            species_code = competition_classes[idx]\n            common_name = get_bird_name(species_code, code_to_common)\n            scientific_name = code_to_scientific.get(species_code, \"Unknown\")\n            probability = float(avg_probabilities[idx])\n            \n            results.append({\n                'rank': len(results) + 1,\n                'species_code': species_code,\n                'common_name': common_name,\n                'scientific_name': scientific_name,\n                'probability': probability,\n                'confidence_percentage': probability * 100\n            })\n        \n        return results\n        \n    except Exception as e:\n        print(f\"Error processing audio file: {e}\")\n        return []\n\ndef display_predictions(predictions):\n    \"\"\"Display predictions in a formatted way\"\"\"\n    if not predictions:\n        print(\"No predictions available.\")\n        return\n    \n    print(f\"\\n{'='*60}\")\n    print(\"BIRD SPECIES PREDICTIONS\")\n    print(f\"{'='*60}\")\n    \n    for pred in predictions:\n        print(f\"{pred['rank']}. {pred['common_name']}\")\n        print(f\"   Species Code: {pred['species_code']}\")\n        print(f\"   Scientific Name: {pred['scientific_name']}\")\n        print(f\"   Confidence: {pred['confidence_percentage']:.2f}%\")\n        print(f\"   Probability: {pred['probability']:.6f}\")\n        print(\"-\" * 40)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:51:25.129015Z","iopub.execute_input":"2025-06-22T09:51:25.129457Z","iopub.status.idle":"2025-06-22T09:51:25.145814Z","shell.execute_reply.started":"2025-06-22T09:51:25.129409Z","shell.execute_reply":"2025-06-22T09:51:25.144546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Example: Predict for a training audio file\nexample_audio_path = \"/kaggle/input/birdclef-2023/train_audio/grewoo2/XC347855.ogg\"\n\nprint(\"Making predictions for example audio file...\")\npredictions = predict_single_audio_file(example_audio_path, top_n=5)\ndisplay_predictions(predictions)\n\n# You can also listen to the audio\naudio_data, sr = librosa.load(example_audio_path)\nAudio(audio_data, rate=sr)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:54:11.090457Z","iopub.execute_input":"2025-06-22T09:54:11.090845Z"}},"outputs":[],"execution_count":null}]}