diff --git a/notebooks/load_birdclallclassifier.ipynb b/notebooks/load_birdclallclassifier.ipynb
index dccdefc..fb1888a 100644
--- a/notebooks/load_birdclallclassifier.ipynb
+++ b/notebooks/load_birdclallclassifier.ipynb
@@ -2,31 +2,33 @@
"cells": [
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"id": "148505fc",
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from nightingale.model.bird_call_classifier import BirdCallClassifier\n",
- "from nightingale.data_pipeline.wav_loader import load_wav_16k_mono"
+ "from nightingale.data_pipeline.audio_preprocessor import AudioPreprocessor\n",
+ "# convert data if necessary\n"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 4,
"id": "594d6080",
"metadata": {},
"outputs": [],
"source": [
- "wav1 = load_wav_16k_mono(\"../data/birdclef-2024/train_audio_16/cohcuc1/XC19645.wav\")\n",
- "wav2 = load_wav_16k_mono(\"../data/birdclef-2024/train_audio_16/integr/XC810654.wav\")\n",
- "wav3 = load_wav_16k_mono(\"../data/birdclef-2024/train_audio_16/tilwar1/XC191454.wav\")"
+ "preprocessor = AudioPreprocessor()\n",
+ "wav1,sr1 = preprocessor.load_audio(\"../data/birdclef-2024/train_audio_16/cohcuc1/XC19645.wav\")\n",
+ "wav2,sr2 = preprocessor.load_audio(\"../data/birdclef-2024/train_audio_16/integr/XC810654.wav\")\n",
+ "wav3,sr3 = preprocessor.load_audio(\"../data/birdclef-2024/train_audio_16/tilwar1/XC191454.wav\")"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 5,
"id": "0855a68f",
"metadata": {},
"outputs": [],
@@ -36,10 +38,31 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 6,
"id": "047543cf",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025-11-25 14:19:39.536124: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 22413312 exceeds 10% of free system memory.\n",
+ "2025-11-25 14:19:39.539009: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 22413312 exceeds 10% of free system memory.\n",
+ "2025-11-25 14:19:39.615155: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 21037056 exceeds 10% of free system memory.\n",
+ "2025-11-25 14:19:39.618458: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 21037056 exceeds 10% of free system memory.\n",
+ "2025-11-25 14:19:39.623203: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 21037056 exceeds 10% of free system memory.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Common Hawk-Cuckoo\n",
+ "Common Hawk-Cuckoo\n",
+ "Common Hawk-Cuckoo\n"
+ ]
+ }
+ ],
"source": [
"print(model.predict(wav1))\n",
"print(model.predict(wav2))\n",
@@ -49,7 +72,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "nightingale",
"language": "python",
"name": "python3"
},
@@ -63,7 +86,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.12"
+ "version": "3.11.14"
}
},
"nbformat": 4,
diff --git a/notebooks/train_nightingale.ipynb b/notebooks/train_nightingale.ipynb
index 4948fc0..abfa933 100644
--- a/notebooks/train_nightingale.ipynb
+++ b/notebooks/train_nightingale.ipynb
@@ -10,14 +10,32 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"id": "ac694b25",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TensorFlow version: 2.20.0\n",
+ "tf.keras version: 3.12.0\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/workspaces/nightingale/.venv/lib/python3.11/site-packages/tensorflow_hub/__init__.py:61: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.\n",
+ " from pkg_resources import parse_version\n"
+ ]
+ }
+ ],
"source": [
"import pandas as pd\n",
"from nightingale.model.classifier_head import ClassifierHead\n",
- "from nightingale.data_pipeline.wav_loader import load_wav_16k_mono\n",
+ "\n",
+ "from nightingale.data_pipeline.audio_preprocessor import AudioPreprocessor\n",
"import os\n",
"import glob\n",
"from sklearn.model_selection import train_test_split\n",
@@ -39,10 +57,318 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 3,
"id": "1137cde9",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.microsoft.datawrangler.viewer.v0+json": {
+ "columns": [
+ {
+ "name": "index",
+ "rawType": "int64",
+ "type": "integer"
+ },
+ {
+ "name": "primary_label",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "secondary_labels",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "type",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "latitude",
+ "rawType": "float64",
+ "type": "float"
+ },
+ {
+ "name": "longitude",
+ "rawType": "float64",
+ "type": "float"
+ },
+ {
+ "name": "scientific_name",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "common_name",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "author",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "license",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "rating",
+ "rawType": "float64",
+ "type": "float"
+ },
+ {
+ "name": "url",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "filename",
+ "rawType": "object",
+ "type": "string"
+ }
+ ],
+ "ref": "4ff9dbb9-c493-4ca1-aab2-bbc2744d8fe7",
+ "rows": [
+ [
+ "0",
+ "asbfly",
+ "[]",
+ "['call']",
+ "39.2297",
+ "118.1987",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "Matt Slaymaker",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 3.0",
+ "5.0",
+ "https://www.xeno-canto.org/134896",
+ "asbfly/XC134896.ogg"
+ ],
+ [
+ "1",
+ "asbfly",
+ "[]",
+ "['song']",
+ "51.403",
+ "104.6401",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "Magnus Hellström",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 3.0",
+ "2.5",
+ "https://www.xeno-canto.org/164848",
+ "asbfly/XC164848.ogg"
+ ],
+ [
+ "2",
+ "asbfly",
+ "[]",
+ "['song']",
+ "36.3319",
+ "127.3555",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "Stuart Fisher",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 4.0",
+ "2.5",
+ "https://www.xeno-canto.org/175797",
+ "asbfly/XC175797.ogg"
+ ],
+ [
+ "3",
+ "asbfly",
+ "[]",
+ "['call']",
+ "21.1697",
+ "70.6005",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "vir joshi",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 4.0",
+ "4.0",
+ "https://www.xeno-canto.org/207738",
+ "asbfly/XC207738.ogg"
+ ],
+ [
+ "4",
+ "asbfly",
+ "[]",
+ "['call']",
+ "15.5442",
+ "73.7733",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "Albert Lastukhin & Sergei Karpeev",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 4.0",
+ "4.0",
+ "https://www.xeno-canto.org/209218",
+ "asbfly/XC209218.ogg"
+ ]
+ ],
+ "shape": {
+ "columns": 12,
+ "rows": 5
+ }
+ },
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " primary_label | \n",
+ " secondary_labels | \n",
+ " type | \n",
+ " latitude | \n",
+ " longitude | \n",
+ " scientific_name | \n",
+ " common_name | \n",
+ " author | \n",
+ " license | \n",
+ " rating | \n",
+ " url | \n",
+ " filename | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['call'] | \n",
+ " 39.2297 | \n",
+ " 118.1987 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " Matt Slaymaker | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 5.0 | \n",
+ " https://www.xeno-canto.org/134896 | \n",
+ " asbfly/XC134896.ogg | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['song'] | \n",
+ " 51.4030 | \n",
+ " 104.6401 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " Magnus Hellström | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 2.5 | \n",
+ " https://www.xeno-canto.org/164848 | \n",
+ " asbfly/XC164848.ogg | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['song'] | \n",
+ " 36.3319 | \n",
+ " 127.3555 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " Stuart Fisher | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 2.5 | \n",
+ " https://www.xeno-canto.org/175797 | \n",
+ " asbfly/XC175797.ogg | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['call'] | \n",
+ " 21.1697 | \n",
+ " 70.6005 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " vir joshi | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 4.0 | \n",
+ " https://www.xeno-canto.org/207738 | \n",
+ " asbfly/XC207738.ogg | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['call'] | \n",
+ " 15.5442 | \n",
+ " 73.7733 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " Albert Lastukhin & Sergei Karpeev | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 4.0 | \n",
+ " https://www.xeno-canto.org/209218 | \n",
+ " asbfly/XC209218.ogg | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " primary_label secondary_labels type latitude longitude \\\n",
+ "0 asbfly [] ['call'] 39.2297 118.1987 \n",
+ "1 asbfly [] ['song'] 51.4030 104.6401 \n",
+ "2 asbfly [] ['song'] 36.3319 127.3555 \n",
+ "3 asbfly [] ['call'] 21.1697 70.6005 \n",
+ "4 asbfly [] ['call'] 15.5442 73.7733 \n",
+ "\n",
+ " scientific_name common_name \\\n",
+ "0 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "1 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "2 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "3 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "4 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "\n",
+ " author \\\n",
+ "0 Matt Slaymaker \n",
+ "1 Magnus Hellström \n",
+ "2 Stuart Fisher \n",
+ "3 vir joshi \n",
+ "4 Albert Lastukhin & Sergei Karpeev \n",
+ "\n",
+ " license rating \\\n",
+ "0 Creative Commons Attribution-NonCommercial-Sha... 5.0 \n",
+ "1 Creative Commons Attribution-NonCommercial-Sha... 2.5 \n",
+ "2 Creative Commons Attribution-NonCommercial-Sha... 2.5 \n",
+ "3 Creative Commons Attribution-NonCommercial-Sha... 4.0 \n",
+ "4 Creative Commons Attribution-NonCommercial-Sha... 4.0 \n",
+ "\n",
+ " url filename \n",
+ "0 https://www.xeno-canto.org/134896 asbfly/XC134896.ogg \n",
+ "1 https://www.xeno-canto.org/164848 asbfly/XC164848.ogg \n",
+ "2 https://www.xeno-canto.org/175797 asbfly/XC175797.ogg \n",
+ "3 https://www.xeno-canto.org/207738 asbfly/XC207738.ogg \n",
+ "4 https://www.xeno-canto.org/209218 asbfly/XC209218.ogg "
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"# Read train meta data\n",
"train_metadata_path = \"../data/birdclef-2024/train_metadata.csv\"\n",
@@ -70,7 +396,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 4,
"id": "c0ba8473",
"metadata": {},
"outputs": [],
@@ -110,34 +436,6 @@
"### Split data: Training, Validation and Test"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "ade4a54a",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Step 1: Split the data into training (60%), validation (20%) and test (20%) sets\n",
- "train_df_idx, temp_df_idx = train_test_split(filtered_bird_df.index, test_size=0.4, random_state=42, stratify=filtered_bird_df['target'])\n",
- "val_df_idx, test_df_idx = train_test_split(temp_df_idx, test_size=0.5, random_state=42, stratify=filtered_bird_df.loc[temp_df_idx, 'target'])\n",
- "\n",
- "# Step 2: Create 'fold' column in original filtered_bird_df\n",
- "filtered_bird_df['fold'] = '' # initialize empty\n",
- "filtered_bird_df.loc[train_df_idx, 'fold'] = 1\n",
- "filtered_bird_df.loc[val_df_idx, 'fold'] = 2\n",
- "filtered_bird_df.loc[test_df_idx, 'fold'] = 3\n",
- "\n",
- "# filtered_bird_df.head(10)\n",
- "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 1]['target'], bins=len(bird_classes), alpha=0.7, label='Train')\n",
- "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 2]['target'], bins=len(bird_classes), alpha=0.7, label='Val')\n",
- "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 3]['target'], bins=len(bird_classes), alpha=0.7, label='Test')\n",
- "# plt.xlabel('Bird Classes')\n",
- "# plt.ylabel('Count')\n",
- "# plt.title('Distribution of Bird Classes in Train, Val, and Test Sets')\n",
- "# plt.legend()\n",
- "# plt.show()"
- ]
- },
{
"cell_type": "markdown",
"id": "9721db5e",
@@ -161,7 +459,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 6,
"id": "ba052c95",
"metadata": {},
"outputs": [],
@@ -185,6 +483,26 @@
"metadata": {},
"outputs": [],
"source": [
+ "# Step 1: Split the data into training (60%), validation (20%) and test (20%) sets\n",
+ "train_df_idx, temp_df_idx = train_test_split(filtered_bird_df.index, test_size=0.4, random_state=42, stratify=filtered_bird_df['target'])\n",
+ "val_df_idx, test_df_idx = train_test_split(temp_df_idx, test_size=0.5, random_state=42, stratify=filtered_bird_df.loc[temp_df_idx, 'target'])\n",
+ "\n",
+ "# Step 2: Create 'fold' column in original filtered_bird_df\n",
+ "filtered_bird_df['fold'] = '' # initialize empty\n",
+ "filtered_bird_df.loc[train_df_idx, 'fold'] = 1\n",
+ "filtered_bird_df.loc[val_df_idx, 'fold'] = 2\n",
+ "filtered_bird_df.loc[test_df_idx, 'fold'] = 3\n",
+ "\n",
+ "# filtered_bird_df.head(10)\n",
+ "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 1]['target'], bins=len(bird_classes), alpha=0.7, label='Train')\n",
+ "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 2]['target'], bins=len(bird_classes), alpha=0.7, label='Val')\n",
+ "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 3]['target'], bins=len(bird_classes), alpha=0.7, label='Test')\n",
+ "# plt.xlabel('Bird Classes')\n",
+ "# plt.ylabel('Count')\n",
+ "# plt.title('Distribution of Bird Classes in Train, Val, and Test Sets')\n",
+ "# plt.legend()\n",
+ "# plt.show()\n",
+ "\n",
"filenames_train = filtered_bird_df[filtered_bird_df['fold'] == 1]['filename']\n",
"targets_train = filtered_bird_df[filtered_bird_df['fold'] == 1]['target']\n",
"\n",
@@ -200,7 +518,24 @@
"test_ds = tf.data.Dataset.from_tensor_slices((filenames_test, targets_test))\n",
"\n",
"def load_wav_for_map(filename, label):\n",
- " return load_wav_16k_mono(filename), label\n",
+ " \"\"\"\n",
+ " Read and decode a WAV file into a mono waveform tensor.\n",
+ "\n",
+ " Uses tf.io.read_file + tf.audio.decode_wav to load the file, squeezes the\n",
+ " channel dimension so the returned tensor has shape [num_samples].\n",
+ "\n",
+ " Args:\n",
+ " filename: path to the audio file (string tensor).\n",
+ " label: associated label.\n",
+ "\n",
+ " Returns:\n",
+ " (wav, label): tuple where wav is a 1-D float32 tensor and label is unchanged.\n",
+ " \"\"\"\n",
+ " audio = tf.io.read_file(filename)\n",
+ " wav, sr = tf.audio.decode_wav(audio, desired_channels=1)\n",
+ " # Remove the trailing channel dimension -> shape [num_samples]\n",
+ " wav = tf.squeeze(wav, axis=-1)\n",
+ " return wav, label\n",
"\n",
"train_ds = train_ds.map(load_wav_for_map)\n",
"val_ds = val_ds.map(load_wav_for_map)\n",
@@ -209,11 +544,12 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 9,
"id": "41c181bb",
"metadata": {},
"outputs": [],
"source": [
+ "model = hub.load('https://tfhub.dev/google/yamnet/1')\n",
"def extract_embedding(wav_data, label):\n",
" ''' run YAMNet to extract embedding from the wav data '''\n",
" scores, embeddings, spectrogram = model(wav_data)\n",
@@ -241,10 +577,88 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 10,
"id": "83eb0ddf",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Model: \"classifier_head\"\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1mModel: \"classifier_head\"\u001b[0m\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+ "┃ Layer (type) ┃ Output Shape ┃ Param # ┃\n",
+ "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+ "│ dense (Dense) │ ? │ 0 (unbuilt) │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dense_1 (Dense) │ ? │ 0 (unbuilt) │\n",
+ "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+ "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n",
+ "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+ "│ dense (\u001b[38;5;33mDense\u001b[0m) │ ? │ \u001b[38;5;34m0\u001b[0m (unbuilt) │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dense_1 (\u001b[38;5;33mDense\u001b[0m) │ ? │ \u001b[38;5;34m0\u001b[0m (unbuilt) │\n",
+ "└─────────────────────────────────┴────────────────────────┴───────────────┘\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Total params: 0 (0.00 B)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Trainable params: 0 (0.00 B)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Non-trainable params: 0 (0.00 B)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
"num_bird_classes = len(bird_classes)\n",
"bird_class_model = ClassifierHead(num_classes=num_bird_classes)\n",
@@ -254,7 +668,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 11,
"id": "09c44c15",
"metadata": {},
"outputs": [],
@@ -280,10 +694,21 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 12,
"id": "3d70881d",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"# import mlflow\n",
"# from mlflow import MlflowClient\n",
@@ -304,10 +729,19 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 13,
"id": "ea50b3d5",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Env: None\n",
+ "From MLflow: file:///workspaces/nightingale/notebooks/mlruns\n"
+ ]
+ }
+ ],
"source": [
"import os\n",
"print(\"Env:\", os.getenv(\"MLFLOW_TRACKING_URI\"))\n",
@@ -325,7 +759,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 14,
"id": "1dcec8ff",
"metadata": {},
"outputs": [],
@@ -357,10 +791,155 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 15,
"id": "80f0dd2a",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Epoch 1/20\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/workspaces/nightingale/.venv/lib/python3.11/site-packages/keras/src/backend/tensorflow/nn.py:717: UserWarning: \"`sparse_categorical_crossentropy` received `from_logits=True`, but the `output` argument was produced by a Softmax activation and thus does not represent logits. Was this intended?\n",
+ " output, from_logits = _get_logits(\n",
+ "2025-11-24 13:20:15.356312: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 24870912 exceeds 10% of free system memory.\n",
+ "2025-11-24 13:20:15.362225: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 24968064 exceeds 10% of free system memory.\n",
+ "2025-11-24 13:20:15.374365: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 49545216 exceeds 10% of free system memory.\n",
+ "2025-11-24 13:20:15.421970: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 49545216 exceeds 10% of free system memory.\n",
+ "2025-11-24 13:20:15.450114: W external/local_xla/xla/tsl/framework/cpu_allocator_impl.cc:84] Allocation of 49545216 exceeds 10% of free system memory.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " 24/Unknown \u001b[1m3s\u001b[0m 2ms/step - accuracy: 0.8219 - loss: 0.4678"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025-11-24 13:20:17.297713: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n",
+ "/workspaces/nightingale/.venv/lib/python3.11/site-packages/keras/src/trainers/epoch_iterator.py:164: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.\n",
+ " self._interrupted_warning()\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 50ms/step - accuracy: 0.9218 - loss: 0.2627 - val_accuracy: 0.7048 - val_loss: 1.3252\n",
+ "Epoch 2/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 0.9769 - loss: 0.0706 - val_accuracy: 0.7004 - val_loss: 1.6772\n",
+ "Epoch 3/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 2ms/step - accuracy: 0.9901 - loss: 0.0360 - val_accuracy: 0.7026 - val_loss: 1.9530\n",
+ "Epoch 4/20\n",
+ "\u001b[1m 1/29\u001b[0m \u001b[37m━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m0s\u001b[0m 4ms/step - accuracy: 0.9688 - loss: 0.0634"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025-11-24 13:20:18.624689: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n",
+ "2025-11-24 13:20:18.710861: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 4ms/step - accuracy: 0.9923 - loss: 0.0257 - val_accuracy: 0.7004 - val_loss: 1.8766\n",
+ "Epoch 5/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 0.9967 - loss: 0.0157 - val_accuracy: 0.6982 - val_loss: 2.0125\n",
+ "Epoch 6/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 2ms/step - accuracy: 1.0000 - loss: 0.0097 - val_accuracy: 0.7048 - val_loss: 2.1867\n",
+ "Epoch 7/20\n",
+ "\u001b[1m 1/29\u001b[0m \u001b[37m━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m0s\u001b[0m 5ms/step - accuracy: 1.0000 - loss: 0.0033"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025-11-24 13:20:18.889192: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 0.9989 - loss: 0.0076 - val_accuracy: 0.6960 - val_loss: 2.2336\n",
+ "Epoch 8/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 0.9989 - loss: 0.0098 - val_accuracy: 0.6960 - val_loss: 2.3949\n",
+ "Epoch 9/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 1.0000 - loss: 0.0054 - val_accuracy: 0.7070 - val_loss: 2.4787\n",
+ "Epoch 10/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 1.0000 - loss: 0.0036 - val_accuracy: 0.7048 - val_loss: 2.4777\n",
+ "Epoch 11/20\n",
+ "\u001b[1m 1/29\u001b[0m \u001b[37m━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m0s\u001b[0m 4ms/step - accuracy: 1.0000 - loss: 0.0018"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025-11-24 13:20:19.203583: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 1.0000 - loss: 0.0027 - val_accuracy: 0.7048 - val_loss: 2.5876\n",
+ "Epoch 12/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 1.0000 - loss: 0.0022 - val_accuracy: 0.7070 - val_loss: 2.6581\n",
+ "Epoch 13/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 2ms/step - accuracy: 1.0000 - loss: 0.0019 - val_accuracy: 0.6982 - val_loss: 2.7069\n",
+ "Epoch 14/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 1.0000 - loss: 0.0017 - val_accuracy: 0.7048 - val_loss: 2.7296\n",
+ "Epoch 15/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 1.0000 - loss: 0.0014 - val_accuracy: 0.7026 - val_loss: 2.7604\n",
+ "Epoch 16/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 1.0000 - loss: 0.0012 - val_accuracy: 0.7048 - val_loss: 2.8253\n",
+ "Epoch 17/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 2ms/step - accuracy: 1.0000 - loss: 0.0011 - val_accuracy: 0.7048 - val_loss: 2.8632\n",
+ "Epoch 18/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 2ms/step - accuracy: 1.0000 - loss: 9.3358e-04 - val_accuracy: 0.7048 - val_loss: 2.9056\n",
+ "Epoch 19/20\n",
+ "\u001b[1m 1/29\u001b[0m \u001b[37m━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m0s\u001b[0m 5ms/step - accuracy: 1.0000 - loss: 0.0016"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025-11-24 13:20:19.832534: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 2ms/step - accuracy: 1.0000 - loss: 8.9333e-04 - val_accuracy: 0.6982 - val_loss: 2.9335\n",
+ "Epoch 20/20\n",
+ "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 1.0000 - loss: 8.0682e-04 - val_accuracy: 0.7048 - val_loss: 2.9797\n"
+ ]
+ }
+ ],
"source": [
"history = bird_class_model.fit(train_ds,\n",
" epochs=20,\n",
@@ -378,10 +957,20 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 16,
"id": "0dfe1437",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m13/13\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 56ms/step - accuracy: 0.8396 - loss: 0.7635\n",
+ "Loss: 0.7634812593460083\n",
+ "Accuracy: 0.8395990133285522\n"
+ ]
+ }
+ ],
"source": [
"loss, accuracy = bird_class_model.evaluate(test_ds)\n",
"\n",
@@ -391,10 +980,32 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 17,
"id": "8bd2dcaf",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "ename": "MissingConfigException",
+ "evalue": "Yaml file '/workspaces/nightingale/notebooks/mlruns/0/meta.yaml' does not exist.",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
+ "\u001b[31mMissingConfigException\u001b[39m Traceback (most recent call last)",
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[17]\u001b[39m\u001b[32m, line 15\u001b[39m\n\u001b[32m 3\u001b[39m params = {\n\u001b[32m 4\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mnum_bird_classes\u001b[39m\u001b[33m\"\u001b[39m: num_bird_classes,\n\u001b[32m 5\u001b[39m \u001b[33m\"\u001b[39m\u001b[33moptimizer\u001b[39m\u001b[33m\"\u001b[39m: \u001b[33m\"\u001b[39m\u001b[33madam\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 11\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mearly_stopping_patience\u001b[39m\u001b[33m\"\u001b[39m: \u001b[32m3\u001b[39m,\n\u001b[32m 12\u001b[39m }\n\u001b[32m 14\u001b[39m \u001b[38;5;66;03m# Initiate the MLflow run context\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m15\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[43mmlflow\u001b[49m\u001b[43m.\u001b[49m\u001b[43mstart_run\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m run:\n\u001b[32m 16\u001b[39m \u001b[38;5;66;03m# Log the parameters used for the model fit\u001b[39;00m\n\u001b[32m 17\u001b[39m mlflow.log_params(params)\n\u001b[32m 19\u001b[39m \u001b[38;5;66;03m# Log the error metrics that were calculated during validation\u001b[39;00m\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/tracking/fluent.py:475\u001b[39m, in \u001b[36mstart_run\u001b[39m\u001b[34m(run_id, experiment_id, run_name, nested, parent_run_id, tags, description, log_system_metrics)\u001b[39m\n\u001b[32m 471\u001b[39m user_specified_tags[MLFLOW_RUN_NAME] = run_name\n\u001b[32m 473\u001b[39m resolved_tags = context_registry.resolve_tags(user_specified_tags)\n\u001b[32m--> \u001b[39m\u001b[32m475\u001b[39m active_run_obj = \u001b[43mclient\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcreate_run\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 476\u001b[39m \u001b[43m \u001b[49m\u001b[43mexperiment_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43mexp_id_for_run\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 477\u001b[39m \u001b[43m \u001b[49m\u001b[43mtags\u001b[49m\u001b[43m=\u001b[49m\u001b[43mresolved_tags\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 478\u001b[39m \u001b[43m \u001b[49m\u001b[43mrun_name\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrun_name\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 479\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 481\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m log_system_metrics \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 482\u001b[39m \u001b[38;5;66;03m# If `log_system_metrics` is not specified, we will check environment variable.\u001b[39;00m\n\u001b[32m 483\u001b[39m log_system_metrics = MLFLOW_ENABLE_SYSTEM_METRICS_LOGGING.get()\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/tracking/client.py:479\u001b[39m, in \u001b[36mMlflowClient.create_run\u001b[39m\u001b[34m(self, experiment_id, start_time, tags, run_name)\u001b[39m\n\u001b[32m 425\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mcreate_run\u001b[39m(\n\u001b[32m 426\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 427\u001b[39m experiment_id: \u001b[38;5;28mstr\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 430\u001b[39m run_name: \u001b[38;5;28mstr\u001b[39m | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 431\u001b[39m ) -> Run:\n\u001b[32m 432\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 433\u001b[39m \u001b[33;03m Create a :py:class:`mlflow.entities.Run` object that can be associated with\u001b[39;00m\n\u001b[32m 434\u001b[39m \u001b[33;03m metrics, parameters, artifacts, etc.\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 477\u001b[39m \u001b[33;03m status: RUNNING\u001b[39;00m\n\u001b[32m 478\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m479\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_tracking_client\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcreate_run\u001b[49m\u001b[43m(\u001b[49m\u001b[43mexperiment_id\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstart_time\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtags\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrun_name\u001b[49m\u001b[43m)\u001b[49m\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/telemetry/track.py:30\u001b[39m, in \u001b[36mrecord_usage_event..decorator..wrapper\u001b[39m\u001b[34m(*args, **kwargs)\u001b[39m\n\u001b[32m 28\u001b[39m start_time = time.time()\n\u001b[32m 29\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m30\u001b[39m result = \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 31\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m result \u001b[38;5;66;03m# noqa: RET504\u001b[39;00m\n\u001b[32m 32\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m:\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/tracking/_tracking_service/client.py:183\u001b[39m, in \u001b[36mTrackingServiceClient.create_run\u001b[39m\u001b[34m(self, experiment_id, start_time, tags, run_name)\u001b[39m\n\u001b[32m 178\u001b[39m \u001b[38;5;66;03m# Extract user from tags\u001b[39;00m\n\u001b[32m 179\u001b[39m \u001b[38;5;66;03m# This logic is temporary; the user_id attribute of runs is deprecated and will be removed\u001b[39;00m\n\u001b[32m 180\u001b[39m \u001b[38;5;66;03m# in a later release.\u001b[39;00m\n\u001b[32m 181\u001b[39m user_id = tags.get(MLFLOW_USER, \u001b[33m\"\u001b[39m\u001b[33munknown\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m183\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mstore\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcreate_run\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 184\u001b[39m \u001b[43m \u001b[49m\u001b[43mexperiment_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43mexperiment_id\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 185\u001b[39m \u001b[43m \u001b[49m\u001b[43muser_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43muser_id\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 186\u001b[39m \u001b[43m \u001b[49m\u001b[43mstart_time\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstart_time\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mget_current_time_millis\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 187\u001b[39m \u001b[43m \u001b[49m\u001b[43mtags\u001b[49m\u001b[43m=\u001b[49m\u001b[43m[\u001b[49m\u001b[43mRunTag\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mtags\u001b[49m\u001b[43m.\u001b[49m\u001b[43mitems\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 188\u001b[39m \u001b[43m \u001b[49m\u001b[43mrun_name\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrun_name\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 189\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/store/tracking/file_store.py:689\u001b[39m, in \u001b[36mFileStore.create_run\u001b[39m\u001b[34m(self, experiment_id, user_id, start_time, tags, run_name)\u001b[39m\n\u001b[32m 685\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 686\u001b[39m \u001b[33;03mCreates a run with the specified attributes.\u001b[39;00m\n\u001b[32m 687\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 688\u001b[39m experiment_id = FileStore.DEFAULT_EXPERIMENT_ID \u001b[38;5;28;01mif\u001b[39;00m experiment_id \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m experiment_id\n\u001b[32m--> \u001b[39m\u001b[32m689\u001b[39m experiment = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mget_experiment\u001b[49m\u001b[43m(\u001b[49m\u001b[43mexperiment_id\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 690\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m experiment \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 691\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m MlflowException(\n\u001b[32m 692\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mCould not create run under experiment with ID \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mexperiment_id\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m - no such \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 693\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mexperiment exists.\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 694\u001b[39m databricks_pb2.RESOURCE_DOES_NOT_EXIST,\n\u001b[32m 695\u001b[39m )\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/store/tracking/file_store.py:498\u001b[39m, in \u001b[36mFileStore.get_experiment\u001b[39m\u001b[34m(self, experiment_id)\u001b[39m\n\u001b[32m 487\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 488\u001b[39m \u001b[33;03mFetch the experiment.\u001b[39;00m\n\u001b[32m 489\u001b[39m \u001b[33;03mNote: This API will search for active as well as deleted experiments.\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 495\u001b[39m \u001b[33;03m A single Experiment object if it exists, otherwise raises an Exception.\u001b[39;00m\n\u001b[32m 496\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 497\u001b[39m experiment_id = FileStore.DEFAULT_EXPERIMENT_ID \u001b[38;5;28;01mif\u001b[39;00m experiment_id \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m experiment_id\n\u001b[32m--> \u001b[39m\u001b[32m498\u001b[39m experiment = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_get_experiment\u001b[49m\u001b[43m(\u001b[49m\u001b[43mexperiment_id\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 499\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m experiment \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 500\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m MlflowException(\n\u001b[32m 501\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mExperiment \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mexperiment_id\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m does not exist.\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 502\u001b[39m databricks_pb2.RESOURCE_DOES_NOT_EXIST,\n\u001b[32m 503\u001b[39m )\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/store/tracking/file_store.py:466\u001b[39m, in \u001b[36mFileStore._get_experiment\u001b[39m\u001b[34m(self, experiment_id, view_type)\u001b[39m\n\u001b[32m 461\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m experiment_dir \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 462\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m MlflowException(\n\u001b[32m 463\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mCould not find experiment with ID \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mexperiment_id\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m,\n\u001b[32m 464\u001b[39m databricks_pb2.RESOURCE_DOES_NOT_EXIST,\n\u001b[32m 465\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m466\u001b[39m meta = \u001b[43mFileStore\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_read_yaml\u001b[49m\u001b[43m(\u001b[49m\u001b[43mexperiment_dir\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mFileStore\u001b[49m\u001b[43m.\u001b[49m\u001b[43mMETA_DATA_FILE_NAME\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 467\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m meta \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 468\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m MissingConfigException(\n\u001b[32m 469\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mExperiment \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mexperiment_id\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m is invalid with empty \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 470\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mFileStore.META_DATA_FILE_NAME\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m in directory \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mexperiment_dir\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 471\u001b[39m )\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/store/tracking/file_store.py:1636\u001b[39m, in \u001b[36mFileStore._read_yaml\u001b[39m\u001b[34m(root, file_name, retries)\u001b[39m\n\u001b[32m 1633\u001b[39m time.sleep(\u001b[32m0.1\u001b[39m * (\u001b[32m3\u001b[39m - attempts_remaining))\n\u001b[32m 1634\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m _read_helper(root, file_name, attempts_remaining - \u001b[32m1\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m1636\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_read_helper\u001b[49m\u001b[43m(\u001b[49m\u001b[43mroot\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfile_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mattempts_remaining\u001b[49m\u001b[43m=\u001b[49m\u001b[43mretries\u001b[49m\u001b[43m)\u001b[49m\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/store/tracking/file_store.py:1629\u001b[39m, in \u001b[36mFileStore._read_yaml.._read_helper\u001b[39m\u001b[34m(root, file_name, attempts_remaining)\u001b[39m\n\u001b[32m 1628\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_read_helper\u001b[39m(root, file_name, attempts_remaining=\u001b[32m2\u001b[39m):\n\u001b[32m-> \u001b[39m\u001b[32m1629\u001b[39m result = \u001b[43mread_yaml\u001b[49m\u001b[43m(\u001b[49m\u001b[43mroot\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfile_name\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1630\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m result \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mor\u001b[39;00m attempts_remaining == \u001b[32m0\u001b[39m:\n\u001b[32m 1631\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m result\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/workspaces/nightingale/.venv/lib/python3.11/site-packages/mlflow/utils/yaml_utils.py:107\u001b[39m, in \u001b[36mread_yaml\u001b[39m\u001b[34m(root, file_name)\u001b[39m\n\u001b[32m 105\u001b[39m file_path = os.path.join(root, file_name)\n\u001b[32m 106\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m exists(file_path):\n\u001b[32m--> \u001b[39m\u001b[32m107\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m MissingConfigException(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mYaml file \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfile_path\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m does not exist.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 108\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m codecs.open(file_path, mode=\u001b[33m\"\u001b[39m\u001b[33mr\u001b[39m\u001b[33m\"\u001b[39m, encoding=ENCODING) \u001b[38;5;28;01mas\u001b[39;00m yaml_file:\n\u001b[32m 109\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m yaml.load(yaml_file, Loader=YamlSafeLoader)\n",
+ "\u001b[31mMissingConfigException\u001b[39m: Yaml file '/workspaces/nightingale/notebooks/mlruns/0/meta.yaml' does not exist."
+ ]
+ }
+ ],
"source": [
"# Assemble the metrics we're going to write into a collection\n",
"metrics = {\"Loss\": loss, \"Accuracy\": accuracy}\n",
@@ -470,7 +1081,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "nightingale",
"language": "python",
"name": "python3"
},
@@ -484,7 +1095,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.12"
+ "version": "3.11.14"
}
},
"nbformat": 4,
diff --git a/notebooks/train_nightingale_2.ipynb b/notebooks/train_nightingale_2.ipynb
new file mode 100644
index 0000000..c709dd2
--- /dev/null
+++ b/notebooks/train_nightingale_2.ipynb
@@ -0,0 +1,1209 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "id": "a9cd6428",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import os\n",
+ "import tensorflow as tf\n",
+ "from sklearn.model_selection import train_test_split\n",
+ "import tensorflow_hub as hub\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "18953a0d",
+ "metadata": {},
+ "source": [
+ "### Load and Explore birdclef-2024 data (pre conversion)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "id": "d504fa73",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.microsoft.datawrangler.viewer.v0+json": {
+ "columns": [
+ {
+ "name": "index",
+ "rawType": "int64",
+ "type": "integer"
+ },
+ {
+ "name": "primary_label",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "secondary_labels",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "type",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "latitude",
+ "rawType": "float64",
+ "type": "float"
+ },
+ {
+ "name": "longitude",
+ "rawType": "float64",
+ "type": "float"
+ },
+ {
+ "name": "scientific_name",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "common_name",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "author",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "license",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "rating",
+ "rawType": "float64",
+ "type": "float"
+ },
+ {
+ "name": "url",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "filename",
+ "rawType": "object",
+ "type": "string"
+ }
+ ],
+ "ref": "8b388208-6c6d-4e82-bf7e-042781df15b5",
+ "rows": [
+ [
+ "0",
+ "asbfly",
+ "[]",
+ "['call']",
+ "39.2297",
+ "118.1987",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "Matt Slaymaker",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 3.0",
+ "5.0",
+ "https://www.xeno-canto.org/134896",
+ "asbfly/XC134896.ogg"
+ ],
+ [
+ "1",
+ "asbfly",
+ "[]",
+ "['song']",
+ "51.403",
+ "104.6401",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "Magnus Hellström",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 3.0",
+ "2.5",
+ "https://www.xeno-canto.org/164848",
+ "asbfly/XC164848.ogg"
+ ],
+ [
+ "2",
+ "asbfly",
+ "[]",
+ "['song']",
+ "36.3319",
+ "127.3555",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "Stuart Fisher",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 4.0",
+ "2.5",
+ "https://www.xeno-canto.org/175797",
+ "asbfly/XC175797.ogg"
+ ],
+ [
+ "3",
+ "asbfly",
+ "[]",
+ "['call']",
+ "21.1697",
+ "70.6005",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "vir joshi",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 4.0",
+ "4.0",
+ "https://www.xeno-canto.org/207738",
+ "asbfly/XC207738.ogg"
+ ],
+ [
+ "4",
+ "asbfly",
+ "[]",
+ "['call']",
+ "15.5442",
+ "73.7733",
+ "Muscicapa dauurica",
+ "Asian Brown Flycatcher",
+ "Albert Lastukhin & Sergei Karpeev",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 4.0",
+ "4.0",
+ "https://www.xeno-canto.org/209218",
+ "asbfly/XC209218.ogg"
+ ]
+ ],
+ "shape": {
+ "columns": 12,
+ "rows": 5
+ }
+ },
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " primary_label | \n",
+ " secondary_labels | \n",
+ " type | \n",
+ " latitude | \n",
+ " longitude | \n",
+ " scientific_name | \n",
+ " common_name | \n",
+ " author | \n",
+ " license | \n",
+ " rating | \n",
+ " url | \n",
+ " filename | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['call'] | \n",
+ " 39.2297 | \n",
+ " 118.1987 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " Matt Slaymaker | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 5.0 | \n",
+ " https://www.xeno-canto.org/134896 | \n",
+ " asbfly/XC134896.ogg | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['song'] | \n",
+ " 51.4030 | \n",
+ " 104.6401 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " Magnus Hellström | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 2.5 | \n",
+ " https://www.xeno-canto.org/164848 | \n",
+ " asbfly/XC164848.ogg | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['song'] | \n",
+ " 36.3319 | \n",
+ " 127.3555 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " Stuart Fisher | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 2.5 | \n",
+ " https://www.xeno-canto.org/175797 | \n",
+ " asbfly/XC175797.ogg | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['call'] | \n",
+ " 21.1697 | \n",
+ " 70.6005 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " vir joshi | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 4.0 | \n",
+ " https://www.xeno-canto.org/207738 | \n",
+ " asbfly/XC207738.ogg | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " asbfly | \n",
+ " [] | \n",
+ " ['call'] | \n",
+ " 15.5442 | \n",
+ " 73.7733 | \n",
+ " Muscicapa dauurica | \n",
+ " Asian Brown Flycatcher | \n",
+ " Albert Lastukhin & Sergei Karpeev | \n",
+ " Creative Commons Attribution-NonCommercial-Sha... | \n",
+ " 4.0 | \n",
+ " https://www.xeno-canto.org/209218 | \n",
+ " asbfly/XC209218.ogg | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " primary_label secondary_labels type latitude longitude \\\n",
+ "0 asbfly [] ['call'] 39.2297 118.1987 \n",
+ "1 asbfly [] ['song'] 51.4030 104.6401 \n",
+ "2 asbfly [] ['song'] 36.3319 127.3555 \n",
+ "3 asbfly [] ['call'] 21.1697 70.6005 \n",
+ "4 asbfly [] ['call'] 15.5442 73.7733 \n",
+ "\n",
+ " scientific_name common_name \\\n",
+ "0 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "1 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "2 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "3 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "4 Muscicapa dauurica Asian Brown Flycatcher \n",
+ "\n",
+ " author \\\n",
+ "0 Matt Slaymaker \n",
+ "1 Magnus Hellström \n",
+ "2 Stuart Fisher \n",
+ "3 vir joshi \n",
+ "4 Albert Lastukhin & Sergei Karpeev \n",
+ "\n",
+ " license rating \\\n",
+ "0 Creative Commons Attribution-NonCommercial-Sha... 5.0 \n",
+ "1 Creative Commons Attribution-NonCommercial-Sha... 2.5 \n",
+ "2 Creative Commons Attribution-NonCommercial-Sha... 2.5 \n",
+ "3 Creative Commons Attribution-NonCommercial-Sha... 4.0 \n",
+ "4 Creative Commons Attribution-NonCommercial-Sha... 4.0 \n",
+ "\n",
+ " url filename \n",
+ "0 https://www.xeno-canto.org/134896 asbfly/XC134896.ogg \n",
+ "1 https://www.xeno-canto.org/164848 asbfly/XC164848.ogg \n",
+ "2 https://www.xeno-canto.org/175797 asbfly/XC175797.ogg \n",
+ "3 https://www.xeno-canto.org/207738 asbfly/XC207738.ogg \n",
+ "4 https://www.xeno-canto.org/209218 asbfly/XC209218.ogg "
+ ]
+ },
+ "execution_count": 39,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Read train meta data\n",
+ "train_metadata_path = \"../data/birdclef-2024/train_metadata.csv\"\n",
+ "\n",
+ "train_df = pd.read_csv(train_metadata_path)\n",
+ "train_df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "id": "71495869",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.microsoft.datawrangler.viewer.v0+json": {
+ "columns": [
+ {
+ "name": "index",
+ "rawType": "object",
+ "type": "string"
+ },
+ {
+ "name": "latitude",
+ "rawType": "float64",
+ "type": "float"
+ },
+ {
+ "name": "longitude",
+ "rawType": "float64",
+ "type": "float"
+ },
+ {
+ "name": "rating",
+ "rawType": "float64",
+ "type": "float"
+ }
+ ],
+ "ref": "c1d21358-84af-4dfd-9d74-caa6c4d616a1",
+ "rows": [
+ [
+ "count",
+ "24081.0",
+ "24081.0",
+ "24459.0"
+ ],
+ [
+ "mean",
+ "32.53703967894161",
+ "43.64069944245256",
+ "3.8434931926898073"
+ ],
+ [
+ "std",
+ "19.440382482421175",
+ "50.19135150350931",
+ "1.1008399312899093"
+ ],
+ [
+ "min",
+ "-43.524",
+ "-171.7654",
+ "0.0"
+ ],
+ [
+ "25%",
+ "17.1601",
+ "2.5457",
+ "3.0"
+ ],
+ [
+ "50%",
+ "37.1551",
+ "26.6876",
+ "4.0"
+ ],
+ [
+ "75%",
+ "49.1144",
+ "85.3193",
+ "5.0"
+ ],
+ [
+ "max",
+ "71.964",
+ "177.4478",
+ "5.0"
+ ]
+ ],
+ "shape": {
+ "columns": 3,
+ "rows": 8
+ }
+ },
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " latitude | \n",
+ " longitude | \n",
+ " rating | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | count | \n",
+ " 24081.000000 | \n",
+ " 24081.000000 | \n",
+ " 24459.000000 | \n",
+ "
\n",
+ " \n",
+ " | mean | \n",
+ " 32.537040 | \n",
+ " 43.640699 | \n",
+ " 3.843493 | \n",
+ "
\n",
+ " \n",
+ " | std | \n",
+ " 19.440382 | \n",
+ " 50.191352 | \n",
+ " 1.100840 | \n",
+ "
\n",
+ " \n",
+ " | min | \n",
+ " -43.524000 | \n",
+ " -171.765400 | \n",
+ " 0.000000 | \n",
+ "
\n",
+ " \n",
+ " | 25% | \n",
+ " 17.160100 | \n",
+ " 2.545700 | \n",
+ " 3.000000 | \n",
+ "
\n",
+ " \n",
+ " | 50% | \n",
+ " 37.155100 | \n",
+ " 26.687600 | \n",
+ " 4.000000 | \n",
+ "
\n",
+ " \n",
+ " | 75% | \n",
+ " 49.114400 | \n",
+ " 85.319300 | \n",
+ " 5.000000 | \n",
+ "
\n",
+ " \n",
+ " | max | \n",
+ " 71.964000 | \n",
+ " 177.447800 | \n",
+ " 5.000000 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " latitude longitude rating\n",
+ "count 24081.000000 24081.000000 24459.000000\n",
+ "mean 32.537040 43.640699 3.843493\n",
+ "std 19.440382 50.191352 1.100840\n",
+ "min -43.524000 -171.765400 0.000000\n",
+ "25% 17.160100 2.545700 3.000000\n",
+ "50% 37.155100 26.687600 4.000000\n",
+ "75% 49.114400 85.319300 5.000000\n",
+ "max 71.964000 177.447800 5.000000"
+ ]
+ },
+ "execution_count": 40,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "train_df.describe()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "34f8f2e3",
+ "metadata": {},
+ "source": [
+ "### Prepare dataframe pointing to bird call audio data in wav format"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "id": "b9c56857",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Processing 4 folders\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/cohcuc1/XC214005.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/cohcuc1/XC214006.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/cohcuc1/XC19645.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/cohcuc1/XC174932.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/cohcuc1/XC177780.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/cohcuc1/XC140254.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/cohcuc1/XC191169.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/cohcuc1/XC136837.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/integr/XC842970.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/integr/XC401712.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/integr/XC397702.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/integr/XC840667.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/integr/XC810654.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/tilwar1/XC42645.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/tilwar1/XC42646.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/tilwar1/XC133404.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/tilwar1/XC189206.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/tilwar1/XC191453.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/tilwar1/XC176794.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/tilwar1/XC191454.wav\n",
+ "Skipped (already exists): ../data/birdclef-2024/train_audio_16/tilwar1/XC191455.wav\n"
+ ]
+ }
+ ],
+ "source": [
+ "data_input_root = \"../data/birdclef-2024/train_audio\"\n",
+ "data_output_root = \"../data/birdclef-2024/train_audio_16\"\n",
+ "\n",
+ "from nightingale.data_pipeline.audio_preprocessor import AudioPreprocessor\n",
+ "# convert data if necessary\n",
+ "preprocessor = AudioPreprocessor()\n",
+ "\n",
+ "## Batch convert a complete dataset and store\n",
+ "# Read train meta data. Convert to yamnet compatible format. Skip if file already exists. Reproduce folder structure.\n",
+ "preprocessor.process_folder(\n",
+ " input_root= data_input_root,\n",
+ " output_root= data_output_root,\n",
+ ")\n",
+ "\n",
+ "## Load single file\n",
+ "# input_audio_path = \"../data/birdclef-2024/train_audio/cohcuc1/XC19645.wav\"\n",
+ "# output_audio_path = \"../XC19645.wav\"\n",
+ "# audio_tensor = preprocessor.process_file(input_audio_path, output_audio_path)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8000c8b8",
+ "metadata": {},
+ "source": [
+ "### Filter the Metadata for the folder that are actually existing"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "id": "6ef7c047",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Number of bird classes in dataset: 3\n"
+ ]
+ }
+ ],
+ "source": [
+ "from nightingale.data_pipeline.filter_birdclef_data import load_birdclef_metadata\n",
+ "\n",
+ "# filter the metadata csv file for th data that is actually in the data folder\n",
+ "filtered_bird_df, num_of_bird_classes_in_dataset = load_birdclef_metadata(metadata_path=train_metadata_path, \n",
+ " audio_root=\"../data/birdclef-2024/train_audio_16\")\n",
+ "\n",
+ "print(f\"Number of bird classes in dataset: {num_of_bird_classes_in_dataset}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5e287445",
+ "metadata": {},
+ "source": [
+ "### Split data: Training, Validation and Test"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "id": "0fb6207d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Step 1: Split the data into training (60%), validation (20%) and test (20%) sets\n",
+ "train_df_idx, temp_df_idx = train_test_split(filtered_bird_df.index, test_size=0.4, random_state=42, stratify=filtered_bird_df['target'])\n",
+ "val_df_idx, test_df_idx = train_test_split(temp_df_idx, test_size=0.5, random_state=42, stratify=filtered_bird_df.loc[temp_df_idx, 'target'])\n",
+ "\n",
+ "# Step 2: Create 'fold' column in original filtered_bird_df\n",
+ "filtered_bird_df['fold'] = '' # initialize empty\n",
+ "filtered_bird_df.loc[train_df_idx, 'fold'] = 1\n",
+ "filtered_bird_df.loc[val_df_idx, 'fold'] = 2\n",
+ "filtered_bird_df.loc[test_df_idx, 'fold'] = 3\n",
+ "\n",
+ "# filtered_bird_df.head(10)\n",
+ "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 1]['target'], bins=len(bird_classes), alpha=0.7, label='Train')\n",
+ "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 2]['target'], bins=len(bird_classes), alpha=0.7, label='Val')\n",
+ "# plt.hist(filtered_bird_df[filtered_bird_df['fold'] == 3]['target'], bins=len(bird_classes), alpha=0.7, label='Test')\n",
+ "# plt.xlabel('Bird Classes')\n",
+ "# plt.ylabel('Count')\n",
+ "# plt.title('Distribution of Bird Classes in Train, Val, and Test Sets')\n",
+ "# plt.legend()\n",
+ "# plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "id": "8433d3f5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "filenames_train = filtered_bird_df[filtered_bird_df['fold'] == 1]['filename']\n",
+ "targets_train = filtered_bird_df[filtered_bird_df['fold'] == 1]['target']\n",
+ "\n",
+ "filenames_val = filtered_bird_df[filtered_bird_df['fold'] == 2]['filename']\n",
+ "targets_val = filtered_bird_df[filtered_bird_df['fold'] == 2]['target']\n",
+ "\n",
+ "filenames_test = filtered_bird_df[filtered_bird_df['fold'] == 3]['filename']\n",
+ "targets_test = filtered_bird_df[filtered_bird_df['fold'] == 3]['target']\n",
+ "\n",
+ "\n",
+ "train_ds = tf.data.Dataset.from_tensor_slices((filenames_train, targets_train))\n",
+ "val_ds = tf.data.Dataset.from_tensor_slices((filenames_val, targets_val))\n",
+ "test_ds = tf.data.Dataset.from_tensor_slices((filenames_test, targets_test))\n",
+ "\n",
+ "def load_wav_for_map(filename, label):\n",
+ " \"\"\"\n",
+ " Read and decode a WAV file into a mono waveform tensor.\n",
+ "\n",
+ " Uses tf.io.read_file + tf.audio.decode_wav to load the file, squeezes the\n",
+ " channel dimension so the returned tensor has shape [num_samples].\n",
+ "\n",
+ " Args:\n",
+ " filename: path to the audio file (string tensor).\n",
+ " label: associated label.\n",
+ "\n",
+ " Returns:\n",
+ " (wav, label): tuple where wav is a 1-D float32 tensor and label is unchanged.\n",
+ " \"\"\"\n",
+ " audio = tf.io.read_file(filename)\n",
+ " wav, sr = tf.audio.decode_wav(audio, desired_channels=1)\n",
+ " # Remove the trailing channel dimension -> shape [num_samples]\n",
+ " wav = tf.squeeze(wav, axis=-1)\n",
+ " return wav, label\n",
+ "\n",
+ "train_ds = train_ds.map(load_wav_for_map)\n",
+ "val_ds = val_ds.map(load_wav_for_map)\n",
+ "test_ds = test_ds.map(load_wav_for_map)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "03c82358",
+ "metadata": {},
+ "source": [
+ "# Load the Yamnet Model and extract Embeddings"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "id": "5aca4873",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "model = hub.load('https://tfhub.dev/google/yamnet/1')\n",
+ "def extract_embedding(wav_data, label):\n",
+ " ''' run YAMNet to extract embedding from the wav data '''\n",
+ " scores, embeddings, spectrogram = model(wav_data)\n",
+ " num_embeddings = tf.shape(embeddings)[0]\n",
+ " return (embeddings,\n",
+ " tf.repeat(label, num_embeddings))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "id": "1852e16e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "train_ds = train_ds.map(extract_embedding).unbatch()\n",
+ "val_ds = val_ds.map(extract_embedding).unbatch()\n",
+ "test_ds = test_ds.map(extract_embedding).unbatch()\n",
+ "train_ds.element_spec\n",
+ "\n",
+ "train_ds = train_ds.cache().shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)\n",
+ "val_ds = val_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)\n",
+ "test_ds = test_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e22d4b33",
+ "metadata": {},
+ "source": [
+ "## Create Model for Classifier"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "id": "c1b433b4",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Model: \"classifier_head\"\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1mModel: \"classifier_head\"\u001b[0m\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+ "┃ Layer (type) ┃ Output Shape ┃ Param # ┃\n",
+ "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+ "│ dense (Dense) │ ? │ 0 (unbuilt) │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dense_1 (Dense) │ ? │ 0 (unbuilt) │\n",
+ "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+ "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n",
+ "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+ "│ dense (\u001b[38;5;33mDense\u001b[0m) │ ? │ \u001b[38;5;34m0\u001b[0m (unbuilt) │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dense_1 (\u001b[38;5;33mDense\u001b[0m) │ ? │ \u001b[38;5;34m0\u001b[0m (unbuilt) │\n",
+ "└─────────────────────────────────┴────────────────────────┴───────────────┘\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Total params: 0 (0.00 B)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Trainable params: 0 (0.00 B)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Non-trainable params: 0 (0.00 B)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from nightingale.model.classifier_head import ClassifierHead\n",
+ "\n",
+ "bird_class_model = ClassifierHead(num_classes=num_of_bird_classes_in_dataset)\n",
+ "\n",
+ "bird_class_model.summary()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "id": "3c380b83",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bird_class_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
+ " optimizer=\"adam\",\n",
+ " metrics=['accuracy'])\n",
+ "\n",
+ "callback = tf.keras.callbacks.EarlyStopping(monitor='loss',\n",
+ " patience=3,\n",
+ " restore_best_weights=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36763755",
+ "metadata": {},
+ "source": [
+ "# Train the model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7aa56ec4",
+ "metadata": {},
+ "source": [
+ "#### Train classifier"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "id": "5256d5fc",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Epoch 1/5\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/workspaces/nightingale/.venv/lib/python3.11/site-packages/keras/src/backend/tensorflow/nn.py:717: UserWarning: \"`sparse_categorical_crossentropy` received `from_logits=True`, but the `output` argument was produced by a Softmax activation and thus does not represent logits. Was this intended?\n",
+ " output, from_logits = _get_logits(\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " 14/Unknown \u001b[1m2s\u001b[0m 4ms/step - accuracy: 0.6383 - loss: 0.7548"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025-11-25 12:36:22.157308: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n",
+ "/workspaces/nightingale/.venv/lib/python3.11/site-packages/keras/src/trainers/epoch_iterator.py:164: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.\n",
+ " self._interrupted_warning()\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m25/25\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 61ms/step - accuracy: 0.8582 - loss: 0.3638 - val_accuracy: 0.9267 - val_loss: 0.2084\n",
+ "Epoch 2/5\n",
+ "\u001b[1m25/25\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 3ms/step - accuracy: 0.9699 - loss: 0.1017 - val_accuracy: 0.9400 - val_loss: 0.1792\n",
+ "Epoch 3/5\n",
+ "\u001b[1m25/25\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 2ms/step - accuracy: 0.9812 - loss: 0.0634 - val_accuracy: 0.9444 - val_loss: 0.1663\n",
+ "Epoch 4/5\n",
+ "\u001b[1m25/25\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 2ms/step - accuracy: 0.9862 - loss: 0.0449 - val_accuracy: 0.9222 - val_loss: 0.1945\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025-11-25 12:36:23.534349: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n",
+ "2025-11-25 12:36:23.617490: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n",
+ "2025-11-25 12:36:23.734711: I tensorflow/core/framework/local_rendezvous.cc:407] Local rendezvous is aborting with status: OUT_OF_RANGE: End of sequence\n",
+ "\t [[{{node IteratorGetNext}}]]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Epoch 5/5\n",
+ "\u001b[1m25/25\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 4ms/step - accuracy: 0.9912 - loss: 0.0343 - val_accuracy: 0.9378 - val_loss: 0.1777\n"
+ ]
+ }
+ ],
+ "source": [
+ "history = bird_class_model.fit(train_ds,\n",
+ " epochs=5,\n",
+ " validation_data=val_ds,\n",
+ " callbacks=callback)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ea8359a6",
+ "metadata": {},
+ "source": [
+ "#### Evaluate classifier"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "id": "bd931dfe",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m17/17\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 63ms/step - accuracy: 0.8969 - loss: 0.3787\n",
+ "Loss: 0.3786546289920807\n",
+ "Accuracy: 0.8968871831893921\n"
+ ]
+ }
+ ],
+ "source": [
+ "loss, accuracy = bird_class_model.evaluate(test_ds)\n",
+ "\n",
+ "print(\"Loss: \", loss)\n",
+ "print(\"Accuracy: \", accuracy)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "af8b5ef8",
+ "metadata": {},
+ "source": [
+ "# Create the MLFlow Server"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "id": "ba9a394a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import mlflow\n",
+ "\n",
+ "mlflow.set_tracking_uri(\"http://0.0.0.0:5000\")\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "5c8283f2",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Env: http://0.0.0.0:5000\n",
+ "From MLflow: http://0.0.0.0:5000\n"
+ ]
+ }
+ ],
+ "source": [
+ "import os\n",
+ "print(\"Env:\", os.getenv(\"MLFLOW_TRACKING_URI\"))\n",
+ "print(\"From MLflow:\", mlflow.get_tracking_uri())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19dc625e",
+ "metadata": {},
+ "source": [
+ "### Create experiment \n",
+ "RUN THE FOLLOWING CODE BLOCK ONLY ONCE FOR INITIAL EXPERIMENT SETUP!!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "1c1d179a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "experiment_description = (\n",
+ " \"Nightingale is a bird call classification project.\"\n",
+ ")\n",
+ "\n",
+ "experiment_tags = {\n",
+ " \"project_name\": \"nightingale\",\n",
+ " \"mlflow.note.content\": experiment_description,\n",
+ "}\n",
+ "\n",
+ "# only run following command once to create the experiment after the server has been started for the first time\n",
+ "# client.create_experiment(name=\"Nightingale Bird Call Classification\", tags=experiment_tags)\n",
+ "mlflow.set_experiment(\n",
+ " experiment_name=\"nightingale\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "f29e4b9e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 27ms/step\n",
+ "Shape of input_example: (32, 1024)\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2025/11/25 08:38:33 WARNING mlflow.utils.requirements_utils: Detected one or more mismatches between the model's dependencies and the current Python environment:\n",
+ " - keras (current: 3.12.0, required: keras==3.10.0)\n",
+ "To fix the mismatches, call `mlflow.pyfunc.get_model_dependencies(model_uri)` to fetch the model's environment and install dependencies using the resulting environment file.\n",
+ "2025/11/25 08:38:33 WARNING mlflow.utils.environment: Failed to resolve installed pip version. ``pip`` will be added to conda.yaml environment spec without a version specifier.\n",
+ "Registered model 'nightingale-dev.default.Reg-Bird-Call-Classifier-Head' already exists. Creating a new version of this model...\n",
+ "2025/11/25 08:38:33 INFO mlflow.store.model_registry.abstract_store: Waiting up to 300 seconds for model version to finish creation. Model name: nightingale-dev.default.Reg-Bird-Call-Classifier-Head, version 2\n",
+ "Created version '2' of model 'nightingale-dev.default.Reg-Bird-Call-Classifier-Head'.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "🏃 View run rambunctious-owl-571 at: http://0.0.0.0:5000/#/experiments/960222781255945012/runs/66a682e224794c09a32d93acf6d6e51a\n",
+ "🧪 View experiment at: http://0.0.0.0:5000/#/experiments/960222781255945012\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Assemble the metrics we're going to write into a collection\n",
+ "metrics = {\"Loss\": loss, \"Accuracy\": accuracy}\n",
+ "params = {\n",
+ " \"num_bird_classes\": num_of_bird_classes_in_dataset,\n",
+ " \"optimizer\": \"adam\",\n",
+ " \"loss_function\": \"SparseCategoricalCrossentropy\",\n",
+ " \"loss_from_logits\": True,\n",
+ " \"epochs\": len(history.epoch),\n",
+ " \"batch_size\": 32,\n",
+ " \"early_stopping_monitor\": \"loss\",\n",
+ " \"early_stopping_patience\": 3,\n",
+ "}\n",
+ "\n",
+ "# Initiate the MLflow run context\n",
+ "with mlflow.start_run() as run:\n",
+ " # Log the parameters used for the model fit\n",
+ " mlflow.log_params(params)\n",
+ "\n",
+ " # Log the error metrics that were calculated during validation\n",
+ " mlflow.log_metrics(metrics)\n",
+ "\n",
+ " # Take one batch from the dataset\n",
+ " x_batch, y_batch = next(iter(train_ds))\n",
+ "\n",
+ " # Convert to numpy (MLflow expects numpy or tensor-like input, not a tf.data.Dataset)\n",
+ " sample_input = x_batch.numpy()\n",
+ " sample_output = bird_class_model.predict(sample_input)\n",
+ "\n",
+ " # Infer signature from data\n",
+ " signature = mlflow.models.infer_signature(sample_input, sample_output)\n",
+ "\n",
+ " print(\"Shape of input_example:\", sample_input.shape)\n",
+ " # Log an instance of the trained model for later use\n",
+ " model_info = mlflow.keras.log_model(model=bird_class_model, name = \"Bird-Call-Classifier-Head\", signature=signature, pip_requirements=['keras==3.10.0'], registered_model_name=\"nightingale-dev.default.Reg-Bird-Call-Classifier-Head\")\n",
+ "# # mlflow.sklearn.log_model(sk_model=rf, input_example=X_val, name=artifact_path)\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "52cc0920",
+ "metadata": {},
+ "source": [
+ "#### Run inference on a bird call audio sample (YAMNet + classifier head)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1c293abf",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# wav = load_wav_16k_mono(filtered_bird_df[filtered_bird_df['fold'] == 3]['filename'].values[1])\n",
+ "# scores, embeddings, spectrogram = model(wav)\n",
+ "# result = bird_class_model(embeddings).numpy()\n",
+ "\n",
+ "# inferred_class = bird_classes[result.mean(axis=0).argmax()]\n",
+ "# print(f'The main sound is: {inferred_class}')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "76a0540f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# bird_class_model.save('bird_classifier_head.keras')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "nightingale",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.14"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/pyproject.toml b/pyproject.toml
index c980c18..a10027a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,34 +10,35 @@ readme = "README.md"
license-files = [ "LICENSE" ]
requires-python = ">=3.11,<3.12"
dependencies = [
- "tensorflow (==2.20.0)",
- "tensorflow-hub (==0.16.1)",
- "fastapi (==0.116.2)",
- "mlflow[databricks] (>=3.4.0,<4.0.0)",
- "uvicorn (==0.37.0)",
- "python-multipart (==0.0.20)",
- "python-dotenv (>=1.1.1,<2.0.0)"
+ "tensorflow==2.20.0",
+ "tensorflow-hub==0.16.1",
+ "fastapi==0.116.2",
+ "mlflow[databricks]>=3.4.0,<4.0.0",
+ "uvicorn==0.37.0",
+ "python-multipart==0.0.20",
+ "python-dotenv>=1.1.1,<2.0.0"
]
[dependency-groups]
doc = [
- "sphinx (==8.2.3)",
- "sphinx-rtd-theme (==3.0.2)"
+ "sphinx==8.2.3",
+ "sphinx-rtd-theme==3.0.2"
]
dev = [
- "numpy (==2.3.3)",
- "scikit-learn (==1.7.2)",
- "matplotlib (==3.10.6)",
- "seaborn (==0.13.2)",
- "ipykernel (==6.30.1)",
- "ipywidgets (==8.1.7)",
- "tensorflow (==2.20.0)",
- "tensorflow-io (==0.37.1)",
- "tensorflow-hub (==0.16.1)",
- "pydub (==0.25.1)"
+ "numpy==2.3.3",
+ "scikit-learn==1.7.2",
+ "matplotlib==3.10.6",
+ "seaborn==0.13.2",
+ "ipykernel==6.30.1",
+ "ipywidgets==8.1.7",
+ "tensorflow==2.20.0",
+ "tensorflow-io==0.37.1",
+ "tensorflow-hub==0.16.1",
+ "pydub==0.25.1",
+ "librosa==0.11.0"
]
test = [
- "pytest (==8.4.2)"
+ "pytest==8.4.2"
]
[project.urls]
diff --git a/src/nightingale/data_pipeline/audio_preprocessor.py b/src/nightingale/data_pipeline/audio_preprocessor.py
new file mode 100644
index 0000000..87dc93d
--- /dev/null
+++ b/src/nightingale/data_pipeline/audio_preprocessor.py
@@ -0,0 +1,139 @@
+import os
+import librosa
+import soundfile as sf
+import numpy as np
+
+class AudioPreprocessor:
+ """
+ Helper for loading, converting, resampling and saving audio files to a
+ format suitable for YAMNet (16 kHz mono WAV by default).
+
+ Responsibilities:
+ - Check whether a file already matches the target format (is_yamnet_ready)
+ - Load an audio file, resampling/mono-converting when necessary (load_audio)
+ - Write a waveform to disk as WAV, creating parent folders if needed (save_wav)
+ - Process a single file, skipping if already processed (process_file)
+ - Walk an input folder tree and process all supported audio files while
+ preserving folder structure under an output root (process_folder)
+
+ The class intentionally does not alter the audio data beyond resampling
+ and channel conversion. It returns/accepts numpy float32 waveforms and
+ uses soundfile for saving to ensure stable WAV output.
+ """
+
+ def __init__(self, target_sr=16000, mono=True):
+ # Target sample rate for YAMNet and whether to force mono output.
+ self.target_sr = target_sr
+ self.mono = mono
+
+ def is_yamnet_ready(self, file_path):
+ """
+ Quick check whether the given file is already a WAV with the target
+ sample rate and (optionally) a single channel.
+
+ Returns:
+ True when the file is a .wav with sample rate == target_sr and,
+ if mono==True, has 1 channel. False for any error or mismatch.
+ """
+ try:
+ # Use soundfile to inspect headers without decoding entire file.
+ with sf.SoundFile(file_path) as f:
+ sr = f.samplerate
+ channels = f.channels
+ ext = os.path.splitext(file_path)[1].lower()
+ # Match extension, sample rate and channel count (if required).
+ return ext == ".wav" and sr == self.target_sr and (channels == 1 if self.mono else True)
+ except:
+ # If file can't be opened or inspected, treat as not-ready.
+ return False
+
+ def load_audio(self, file_path):
+ """
+ Load audio into a numpy float32 waveform.
+
+ If the file is already YAMNet-ready (checked by is_yamnet_ready) the
+ function loads it without resampling to keep original samples when
+ possible. Otherwise it forces resampling to target_sr and mono according
+ to the object configuration.
+
+ Returns:
+ (wav, sr) where wav is a 1-D numpy.float32 array and sr == target_sr.
+ """
+ # If file already matches the desired format, load raw (no resample).
+ if self.is_yamnet_ready(file_path):
+ wav, sr = librosa.load(file_path, sr=None, mono=self.mono)
+ else:
+ # Force conversion/resampling to target sample rate and channel config.
+ wav, sr = librosa.load(file_path, sr=self.target_sr, mono=self.mono)
+ # Ensure dtype is float32 which downstream models expect.
+ return wav.astype(np.float32), self.target_sr
+
+ def save_wav(self, waveform, sr, output_path):
+ """
+ Save a numpy waveform to disk as a WAV file.
+
+ Ensures parent directories exist before writing.
+ """
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
+ sf.write(output_path, waveform, sr)
+
+ def process_file(self, input_file, output_file):
+ """
+ Convert and save a single input file to the output path.
+
+ Behavior:
+ - If output_file already exists, skip processing.
+ - Otherwise load (and convert/resample if needed) and save as WAV.
+ - Prints simple status messages; exceptions are caught and printed.
+ """
+ if os.path.exists(output_file):
+ print(f"Skipped (already exists): {output_file}")
+ return
+
+ try:
+ wav, sr = self.load_audio(input_file)
+ self.save_wav(wav, sr, output_file)
+ print(f"Processed: {output_file}")
+ except Exception as e:
+ # Do not raise here; just report the problem and continue processing other files.
+ print(f"Error processing {input_file}: {e}")
+
+ def process_folder(self, input_root, output_root, max_folders=None):
+ """
+ Recursively process supported audio files under input_root and write
+ converted files under output_root while preserving relative paths.
+
+ Args:
+ input_root: top-level folder to traverse.
+ output_root: destination root where converted files will be placed.
+ max_folders: optional int to limit number of top-level subfolders processed.
+
+ Notes:
+ - Only files with extensions (ogg, wav, mp3, flac) are processed.
+ - The folder traversal preserves the input folder structure relative to input_root.
+ """
+ # Collect immediate subfolders under the input root (sorted for determinism).
+ subfolders = sorted(
+ [os.path.join(input_root, d) for d in os.listdir(input_root)
+ if os.path.isdir(os.path.join(input_root, d))]
+ )
+
+ # Optionally limit the number of subfolders to process.
+ if max_folders is not None:
+ subfolders = subfolders[:max_folders]
+
+ print(f"Processing {len(subfolders)} folders")
+
+ # Walk each selected subfolder recursively and process supported files.
+ for folder in subfolders:
+ for dirpath, _, filenames in os.walk(folder):
+ for filename in filenames:
+ if filename.lower().endswith((".ogg", ".wav", ".mp3", ".flac")):
+ in_path = os.path.join(dirpath, filename)
+
+ # Build output path that mirrors the input structure and ensures .wav extension.
+ rel_path = os.path.relpath(in_path, input_root)
+ rel_path = os.path.splitext(rel_path)[0] + ".wav"
+ out_path = os.path.join(output_root, rel_path)
+
+ self.process_file(in_path, out_path)
diff --git a/src/nightingale/data_pipeline/convert_audio.py b/src/nightingale/data_pipeline/convert_audio.py
deleted file mode 100644
index 2ffb022..0000000
--- a/src/nightingale/data_pipeline/convert_audio.py
+++ /dev/null
@@ -1,69 +0,0 @@
-import os
-from pydub import AudioSegment
-
-def convert_and_resample(input_file: str, output_file: str):
- """
- Converts an audio file to WAV format and resamples it to 16 kHz.
- Skips conversion if the output file already exists.
-
- Args:
- input_file (str): Path to the input audio file (e.g., .ogg, .mp3).
- output_file (str): Path to save the converted and resampled .wav file.
- """
- print("start conversion")
- if os.path.exists(output_file):
- print(f"Skipped (already exists): {output_file}")
- return
-
- try:
- # Load the input audio file
- audio = AudioSegment.from_file(input_file)
- #set quantisation
- audio_quant16 = audio.set_sample_width(2) # 2 bytes = 16 bit
- # Resample to 16 kHz
- audio_16k = audio_quant16.set_frame_rate(16000)
- # Export as .wav
- audio_16k.export(output_file, format="wav")
- print(f"Conversion and resampling successful: {output_file}")
- except Exception as e:
- print(f"Error during conversion and resampling: {e}")
-
-
-def batch_convert_and_resample(input_root, output_root, convert_and_resample, max_folders=None):
- """
- Walk through input_root, find all .ogg files, and convert them to .wav
- in output_root with the same folder structure.
-
- Parameters:
- input_root (str): Path to the root folder containing .ogg files.
- output_root (str): Path where converted .wav files will be saved.
- convert_and_resample (func): Function that takes (in_path, out_path).
- max_folders (int, optional): If set, only process the first N subfolders.
- """
- # List top-level subfolders in input_root
- subfolders = sorted(
- [os.path.join(input_root, d) for d in os.listdir(input_root)
- if os.path.isdir(os.path.join(input_root, d))]
- )
- print("start conversion")
- # Limit to first N folders if requested
- if max_folders is not None:
- subfolders = subfolders[:max_folders]
-
- for folder in subfolders:
- for dirpath, _, filenames in os.walk(folder):
- for filename in filenames:
- if filename.lower().endswith(".ogg") or filename.lower().endswith(".wav"):
- in_path = os.path.join(dirpath, filename)
-
- # Build matching output path
- rel_path = os.path.relpath(in_path, input_root)
- rel_path_no_ext = os.path.splitext(rel_path)[0] + ".wav"
- out_path = os.path.join(output_root, rel_path_no_ext)
-
- # Ensure output directory exists
- os.makedirs(os.path.dirname(out_path), exist_ok=True)
-
- # Convert
- convert_and_resample(in_path, out_path)
- print(f"Converted: {in_path} -> {out_path}")
\ No newline at end of file
diff --git a/src/nightingale/data_pipeline/filter_birdclef_data.py b/src/nightingale/data_pipeline/filter_birdclef_data.py
new file mode 100644
index 0000000..e05835c
--- /dev/null
+++ b/src/nightingale/data_pipeline/filter_birdclef_data.py
@@ -0,0 +1,43 @@
+import glob
+import os
+import pandas as pd
+
+def load_birdclef_metadata(metadata_path="../data/birdclef-2024/train_metadata.csv",
+ audio_root="../data/birdclef-2024/train_audio_16"):
+ """
+ Load and filter BirdClef metadata to include only wav files present under audio_root.
+
+ Args:
+ metadata_path: Path to the CSV metadata file (e.g. ../data/birdclef-2024/train_metadata.csv).
+ audio_root: Root folder containing audio files (e.g. ../data/birdclef-2024/train_audio_16).
+
+ Returns:
+ Tuple (filtered_bird_df, num_of_bird_classes_in_dataset)
+ - filtered_bird_df: DataFrame with absolute paths in the 'filename' column and a 'target' column.
+ - num_of_bird_classes_in_dataset: int count of unique bird classes present in the filtered set.
+ """
+
+ # Read metadata CSV
+ bird_df = pd.read_csv(metadata_path)
+
+ # Change the filename endings from .ogg to .wav in the filename column
+ bird_df['filename'] = bird_df['filename'].str.replace('.ogg', '.wav', regex=False)
+
+ # Find all wav files under audio_root and convert to relative paths for matching against metadata
+ wav_paths = glob.glob(os.path.join(audio_root, "**", "*.wav"), recursive=True)
+ wav_rel = [os.path.relpath(p, audio_root) for p in wav_paths]
+
+ filtered_bird_df = bird_df[bird_df['filename'].isin(wav_rel)]
+
+ bird_classes = list(set(filtered_bird_df['common_name']))
+ num_of_bird_classes_in_dataset = len(bird_classes)
+
+ map_class_to_id = {name: idx for idx, name in enumerate(bird_classes)}
+ class_id = filtered_bird_df['common_name'].apply(lambda name: map_class_to_id[name])
+ filtered_bird_df = filtered_bird_df.assign(target=class_id)
+
+ # Convert filenames to absolute paths under audio_root
+ full_path = filtered_bird_df['filename'].apply(lambda row: os.path.join(audio_root, row))
+ filtered_bird_df = filtered_bird_df.assign(filename=full_path)
+
+ return filtered_bird_df, num_of_bird_classes_in_dataset
diff --git a/src/nightingale/data_pipeline/wav_loader.py b/src/nightingale/data_pipeline/wav_loader.py
deleted file mode 100644
index 6f5b984..0000000
--- a/src/nightingale/data_pipeline/wav_loader.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from pydub import AudioSegment
-import tensorflow as tf
-
-def load_wav_16k_mono(filename):
- """ Load a WAV file, convert it to a float tensor, resample to 16 kHz single-channel audio. """
- file_contents = tf.io.read_file(filename)
- wav, sample_rate = tf.audio.decode_wav(
- file_contents,
- desired_channels=1)
- wav = tf.squeeze(wav, axis=-1)
-
- #sample_rate = tf.cast(sample_rate, dtype=tf.int64)
- #wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000)
- return wav
\ No newline at end of file
diff --git a/uv.lock b/uv.lock
index c596699..3536712 100644
--- a/uv.lock
+++ b/uv.lock
@@ -153,6 +153,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
]
+[[package]]
+name = "audioread"
+version = "3.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/4a/874ecf9b472f998130c2b5e145dcdb9f6131e84786111489103b66772143/audioread-3.1.0.tar.gz", hash = "sha256:1c4ab2f2972764c896a8ac61ac53e261c8d29f0c6ccd652f84e18f08a4cab190", size = 20082, upload-time = "2025-10-26T19:44:13.484Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/16/fbe8e1e185a45042f7cd3a282def5bb8d95bb69ab9e9ef6a5368aa17e426/audioread-3.1.0-py3-none-any.whl", hash = "sha256:b30d1df6c5d3de5dcef0fb0e256f6ea17bdcf5f979408df0297d8a408e2971b4", size = 23143, upload-time = "2025-10-26T19:44:12.016Z" },
+]
+
[[package]]
name = "azure-core"
version = "1.36.0"
@@ -879,6 +888,8 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" },
{ url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" },
{ url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" },
+ { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" },
{ url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" },
]
@@ -1400,6 +1411,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/2b/7e0248f65e35800ea8e4e3dbb3bcc36c61b81f5b8abeddaceec8320ab491/langsmith-0.4.38-py3-none-any.whl", hash = "sha256:326232a24b1c6dd308a3188557cc023adf8fb14144263b2982c115a6be5141e7", size = 397341, upload-time = "2025-10-23T22:28:18.333Z" },
]
+[[package]]
+name = "lazy-loader"
+version = "0.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" },
+]
+
[[package]]
name = "libclang"
version = "18.1.1"
@@ -1417,6 +1440,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/71/cf/e01dc4cc79779cd82d77888a88ae2fa424d93b445ad4f6c02bfc18335b70/libclang-18.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:3f0e1f49f04d3cd198985fea0511576b0aee16f9ff0e0f0cad7f9c57ec3c20e8", size = 22361112, upload-time = "2024-03-17T16:42:59.565Z" },
]
+[[package]]
+name = "librosa"
+version = "0.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "audioread" },
+ { name = "decorator" },
+ { name = "joblib" },
+ { name = "lazy-loader" },
+ { name = "msgpack" },
+ { name = "numba" },
+ { name = "numpy" },
+ { name = "pooch" },
+ { name = "scikit-learn" },
+ { name = "scipy" },
+ { name = "soundfile" },
+ { name = "soxr" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/64/36/360b5aafa0238e29758729e9486c6ed92a6f37fa403b7875e06c115cdf4a/librosa-0.11.0.tar.gz", hash = "sha256:f5ed951ca189b375bbe2e33b2abd7e040ceeee302b9bbaeeffdfddb8d0ace908", size = 327001, upload-time = "2025-03-11T15:09:54.884Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b5/ba/c63c5786dfee4c3417094c4b00966e61e4a63efecee22cb7b4c0387dda83/librosa-0.11.0-py3-none-any.whl", hash = "sha256:0b6415c4fd68bff4c29288abe67c6d80b587e0e1e2cfb0aad23e4559504a7fa1", size = 260749, upload-time = "2025-03-11T15:09:52.982Z" },
+]
+
[[package]]
name = "litellm"
version = "1.75.9"
@@ -1439,6 +1486,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/b2/f21db9636d9fcd67b2c557c58aafb8a6e9b53864f7a40d645e09c2e3ab98/litellm-1.75.9-py3-none-any.whl", hash = "sha256:a72c3e05bcb0e50ac1804f0df09d0d7bf5cb41e84351e1609a960033b0ef01c1", size = 8920144, upload-time = "2025-08-20T17:43:48.327Z" },
]
+[[package]]
+name = "llvmlite"
+version = "0.45.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/99/8d/5baf1cef7f9c084fb35a8afbde88074f0d6a727bc63ef764fe0e7543ba40/llvmlite-0.45.1.tar.gz", hash = "sha256:09430bb9d0bb58fc45a45a57c7eae912850bedc095cd0810a57de109c69e1c32", size = 185600, upload-time = "2025-10-01T17:59:52.046Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/ad/9bdc87b2eb34642c1cfe6bcb4f5db64c21f91f26b010f263e7467e7536a3/llvmlite-0.45.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:60f92868d5d3af30b4239b50e1717cb4e4e54f6ac1c361a27903b318d0f07f42", size = 43043526, upload-time = "2025-10-01T18:03:15.051Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/ea/c25c6382f452a943b4082da5e8c1665ce29a62884e2ec80608533e8e82d5/llvmlite-0.45.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98baab513e19beb210f1ef39066288784839a44cd504e24fff5d17f1b3cf0860", size = 37253118, upload-time = "2025-10-01T18:04:06.783Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/af/85fc237de98b181dbbe8647324331238d6c52a3554327ccdc83ced28efba/llvmlite-0.45.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3adc2355694d6a6fbcc024d59bb756677e7de506037c878022d7b877e7613a36", size = 56288209, upload-time = "2025-10-01T18:01:00.168Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/df/3daf95302ff49beff4230065e3178cd40e71294968e8d55baf4a9e560814/llvmlite-0.45.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f3377a6db40f563058c9515dedcc8a3e562d8693a106a28f2ddccf2c8fcf6ca", size = 55140958, upload-time = "2025-10-01T18:02:11.199Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/56/4c0d503fe03bac820ecdeb14590cf9a248e120f483bcd5c009f2534f23f0/llvmlite-0.45.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9c272682d91e0d57f2a76c6d9ebdfccc603a01828cdbe3d15273bdca0c3363a", size = 38132232, upload-time = "2025-10-01T18:04:52.181Z" },
+]
+
[[package]]
name = "mako"
version = "1.3.10"
@@ -1655,6 +1715,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/29/7f/99006f6c261ef694363e8599ad858c223aa9918231e8bd7a1569041967ac/mlflow_tracing-3.5.1-py3-none-any.whl", hash = "sha256:4fd685347158e0d2c48f5bec3d15ecfc6fadc1dbb48073cb220ded438408fa65", size = 1273904, upload-time = "2025-10-22T17:56:10.748Z" },
]
+[[package]]
+name = "msgpack"
+version = "1.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" },
+ { url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" },
+ { url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" },
+ { url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" },
+ { url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" },
+]
+
[[package]]
name = "multidict"
version = "6.7.0"
@@ -1727,6 +1804,7 @@ dependencies = [
dev = [
{ name = "ipykernel" },
{ name = "ipywidgets" },
+ { name = "librosa" },
{ name = "matplotlib" },
{ name = "numpy" },
{ name = "pydub" },
@@ -1759,6 +1837,7 @@ requires-dist = [
dev = [
{ name = "ipykernel", specifier = "==6.30.1" },
{ name = "ipywidgets", specifier = "==8.1.7" },
+ { name = "librosa", specifier = "==0.11.0" },
{ name = "matplotlib", specifier = "==3.10.6" },
{ name = "numpy", specifier = "==2.3.3" },
{ name = "pydub", specifier = "==0.25.1" },
@@ -1774,6 +1853,23 @@ doc = [
]
test = [{ name = "pytest", specifier = "==8.4.2" }]
+[[package]]
+name = "numba"
+version = "0.62.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "llvmlite" },
+ { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/20/33dbdbfe60e5fd8e3dbfde299d106279a33d9f8308346022316781368591/numba-0.62.1.tar.gz", hash = "sha256:7b774242aa890e34c21200a1fc62e5b5757d5286267e71103257f4e2af0d5161", size = 2749817, upload-time = "2025-09-29T10:46:31.551Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dd/5f/8b3491dd849474f55e33c16ef55678ace1455c490555337899c35826836c/numba-0.62.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:f43e24b057714e480fe44bc6031de499e7cf8150c63eb461192caa6cc8530bc8", size = 2684279, upload-time = "2025-09-29T10:43:37.213Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/18/71969149bfeb65a629e652b752b80167fe8a6a6f6e084f1f2060801f7f31/numba-0.62.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57cbddc53b9ee02830b828a8428757f5c218831ccc96490a314ef569d8342b7b", size = 2687330, upload-time = "2025-09-29T10:43:59.601Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/7d/403be3fecae33088027bc8a95dc80a2fda1e3beff3e0e5fc4374ada3afbe/numba-0.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:604059730c637c7885386521bb1b0ddcbc91fd56131a6dcc54163d6f1804c872", size = 3739727, upload-time = "2025-09-29T10:42:45.922Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/c3/3d910d08b659a6d4c62ab3cd8cd93c4d8b7709f55afa0d79a87413027ff6/numba-0.62.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6c540880170bee817011757dc9049dba5a29db0c09b4d2349295991fe3ee55f", size = 3445490, upload-time = "2025-09-29T10:43:12.692Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/82/9d425c2f20d9f0a37f7cb955945a553a00fa06a2b025856c3550227c5543/numba-0.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:03de6d691d6b6e2b76660ba0f38f37b81ece8b2cc524a62f2a0cfae2bfb6f9da", size = 2745550, upload-time = "2025-09-29T10:44:20.571Z" },
+]
+
[[package]]
name = "numpy"
version = "2.3.3"
@@ -2023,6 +2119,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
+[[package]]
+name = "pooch"
+version = "1.8.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+ { name = "platformdirs" },
+ { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c6/77/b3d3e00c696c16cf99af81ef7b1f5fe73bd2a307abca41bd7605429fe6e5/pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10", size = 59353, upload-time = "2024-06-06T16:53:46.224Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47", size = 64574, upload-time = "2024-06-06T16:53:44.343Z" },
+]
+
[[package]]
name = "prompt-toolkit"
version = "3.0.52"
@@ -2621,6 +2731,46 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" },
]
+[[package]]
+name = "soundfile"
+version = "0.13.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi" },
+ { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" },
+ { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" },
+ { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" },
+ { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" },
+]
+
+[[package]]
+name = "soxr"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/7e/f4b461944662ad75036df65277d6130f9411002bfb79e9df7dff40a31db9/soxr-1.0.0.tar.gz", hash = "sha256:e07ee6c1d659bc6957034f4800c60cb8b98de798823e34d2a2bba1caa85a4509", size = 171415, upload-time = "2025-09-07T13:22:21.317Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/65/ce/a3262bc8733d3a4ce5f660ed88c3d97f4b12658b0909e71334cba1721dcb/soxr-1.0.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:28e19d74a5ef45c0d7000f3c70ec1719e89077379df2a1215058914d9603d2d8", size = 206739, upload-time = "2025-09-07T13:21:54.572Z" },
+ { url = "https://files.pythonhosted.org/packages/64/dc/e8cbd100b652697cc9865dbed08832e7e135ff533f453eb6db9e6168d153/soxr-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8dc69fc18884e53b72f6141fdf9d80997edbb4fec9dc2942edcb63abbe0d023", size = 165233, upload-time = "2025-09-07T13:21:55.887Z" },
+ { url = "https://files.pythonhosted.org/packages/75/12/4b49611c9ba5e9fe6f807d0a83352516808e8e573f8b4e712fc0c17f3363/soxr-1.0.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f15450e6f65f22f02fcd4c5a9219c873b1e583a73e232805ff160c759a6b586", size = 208867, upload-time = "2025-09-07T13:21:57.076Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/70/92146ab970a3ef8c43ac160035b1e52fde5417f89adb10572f7e788d9596/soxr-1.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f73f57452f9df37b4de7a4052789fcbd474a5b28f38bba43278ae4b489d4384", size = 242633, upload-time = "2025-09-07T13:21:58.621Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/a7/628479336206959463d08260bffed87905e7ba9e3bd83ca6b405a0736e94/soxr-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:9f417c3d69236051cf5a1a7bad7c4bff04eb3d8fcaa24ac1cb06e26c8d48d8dc", size = 173814, upload-time = "2025-09-07T13:21:59.798Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/c7/f92b81f1a151c13afb114f57799b86da9330bec844ea5a0d3fe6a8732678/soxr-1.0.0-cp312-abi3-macosx_10_14_x86_64.whl", hash = "sha256:abecf4e39017f3fadb5e051637c272ae5778d838e5c3926a35db36a53e3a607f", size = 205508, upload-time = "2025-09-07T13:22:01.252Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/1d/c945fea9d83ea1f2be9d116b3674dbaef26ed090374a77c394b31e3b083b/soxr-1.0.0-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:e973d487ee46aa8023ca00a139db6e09af053a37a032fe22f9ff0cc2e19c94b4", size = 163568, upload-time = "2025-09-07T13:22:03.558Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/80/10640970998a1d2199bef6c4d92205f36968cddaf3e4d0e9fe35ddd405bd/soxr-1.0.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8ce273cca101aff3d8c387db5a5a41001ba76ef1837883438d3c652507a9ccc", size = 204707, upload-time = "2025-09-07T13:22:05.125Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/87/2726603c13c2126cb8ded9e57381b7377f4f0df6ba4408e1af5ddbfdc3dd/soxr-1.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8f2a69686f2856d37823bbb7b78c3d44904f311fe70ba49b893af11d6b6047b", size = 238032, upload-time = "2025-09-07T13:22:06.428Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/04/530252227f4d0721a5524a936336485dfb429bb206a66baf8e470384f4a2/soxr-1.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:2a3b77b115ae7c478eecdbd060ed4f61beda542dfb70639177ac263aceda42a2", size = 172070, upload-time = "2025-09-07T13:22:07.62Z" },
+]
+
[[package]]
name = "sphinx"
version = "8.2.3"