{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":11075449,"sourceType":"datasetVersion","datasetId":6902504}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"https://www.kaggle.com/code/docxian/birdclef-2025-eda-geography\n\nhttps://www.kaggle.com/code/xiaoazuzong/lb-0-778-efficientnet-b0-pytorch-inference/notebook\n","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport warnings\nimport logging\nimport time\nimport math\nimport cv2\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport timm\nfrom tqdm.auto import tqdm\n\n\nimport numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport folium\n\nimport librosa\nimport librosa.display\nfrom IPython.display import Audio\n\n\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:47:33.928264Z","iopub.execute_input":"2025-03-18T22:47:33.928588Z","iopub.status.idle":"2025-03-18T22:47:34.823284Z","shell.execute_reply.started":"2025-03-18T22:47:33.928564Z","shell.execute_reply":"2025-03-18T22:47:34.821836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n \n    test_soundscapes = '/kaggle/input/birdclef-2025/test_soundscapes'\n    submission_csv = '/kaggle/input/birdclef-2025/sample_submission.csv'\n    taxonomy_csv = '/kaggle/input/birdclef-2025/taxonomy.csv'\n    model_path = '/kaggle/input/birdclef-2025-efficientnet-b0'  \n    \n    # Audio parameters\n    FS = 32000  \n    WINDOW_SIZE = 5  \n    \n    # Mel spectrogram parameters\n    N_FFT = 1024\n    HOP_LENGTH = 512\n    N_MELS = 128\n    FMIN = 50\n    FMAX = 14000\n    TARGET_SHAPE = (256, 256)\n    \n    model_name = 'efficientnet_b0'\n    in_channels = 1\n    device = 'cpu'  \n    \n    # Inference parameters\n    batch_size = 16\n    use_tta = False  \n    tta_count = 3   \n    threshold = 0.5\n    \n    use_specific_folds = False  # If False, use all found models\n    folds = [0, 1]  # Used only if use_specific_folds is True\n    \n    debug = False\n    debug_count = 3\n\ncfg = CFG()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:40:03.697675Z","iopub.execute_input":"2025-03-18T22:40:03.698008Z","iopub.status.idle":"2025-03-18T22:40:03.703668Z","shell.execute_reply.started":"2025-03-18T22:40:03.697985Z","shell.execute_reply":"2025-03-18T22:40:03.702698Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# read train data file\ndf = pd.read_csv('../input/birdclef-2025/train.csv')\n\n# read taxonomy file\ndf_taxo = pd.read_csv('../input/birdclef-2025/taxonomy.csv')\n\ndf = pd.merge(left=df, right=df_taxo[['primary_label', 'inat_taxon_id', 'class_name']], how='left', on='primary_label')\n\n\nprint(df.head())\nprint(df.info())\nprint(df.collection.value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:51:45.006552Z","iopub.execute_input":"2025-03-18T22:51:45.007087Z","iopub.status.idle":"2025-03-18T22:51:45.231116Z","shell.execute_reply.started":"2025-03-18T22:51:45.007059Z","shell.execute_reply":"2025-03-18T22:51:45.230059Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# first simple plot of locations\nplt.figure(figsize=(12,6))\nsns.scatterplot(data=df, x='longitude', y='latitude', \n                color='darkblue')\nplt.grid()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:51:49.560343Z","iopub.execute_input":"2025-03-18T22:51:49.560689Z","iopub.status.idle":"2025-03-18T22:51:50.200242Z","shell.execute_reply.started":"2025-03-18T22:51:49.560661Z","shell.execute_reply":"2025-03-18T22:51:50.198957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nfilename='XC112602.ogg'\ny, sr = librosa.load('../input/birdclef-2025/train_audio/banana/' + filename)\n# play sound\nAudio(y, rate=sr)\n# load audio file\n\n\n# show wave data\nplt.figure(figsize=(14,5))\nplt.plot(y, color='darkblue')\nplt.grid()\nplt.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:52:42.037874Z","iopub.execute_input":"2025-03-18T22:52:42.038444Z","iopub.status.idle":"2025-03-18T22:52:42.419502Z","shell.execute_reply.started":"2025-03-18T22:52:42.038415Z","shell.execute_reply":"2025-03-18T22:52:42.418562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# play sound\nAudio(y, rate=sr)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:53:04.019770Z","iopub.execute_input":"2025-03-18T22:53:04.020122Z","iopub.status.idle":"2025-03-18T22:53:04.088090Z","shell.execute_reply.started":"2025-03-18T22:53:04.020094Z","shell.execute_reply":"2025-03-18T22:53:04.086839Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# audio_path = \"../input/birdclef-2025/train_audio/XC12345.ogg\"  # Example audio path\n# y, sr = librosa.load(audio_path, sr=None)\n# duration = librosa.get_duration(y=y, sr=sr)\n# print(f\"Duration: {duration:.2f} seconds\")\n\n# # Plot waveform\n# plt.figure(figsize=(10, 4))\n# librosa.display.waveshow(y, sr=sr)\n# plt.title('Waveform of Sample Audio')\n# plt.xlabel('Time (s)')\n# plt.ylabel('Amplitude')\n# plt.show()\n\n\n# def plot_spectrogram(audio_path):\n#     y, sr = librosa.load(audio_path, sr=None)\n#     S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)\n#     S_dB = librosa.power_to_db(S, ref=np.max)\n\n#     plt.figure(figsize=(10, 4))\n#     librosa.display.specshow(S_dB, sr=sr, x_axis='time', y_axis='mel')\n#     plt.colorbar(format='%+2.0f dB')\n#     plt.title('Mel Spectrogram')\n#     plt.show()\n\n# # Example usage\n# plot_spectrogram(audio_path)\n\n\nimport librosa\n\n# Correct file path from the list\naudio_path = \"../input/birdclef-2025/train_audio/greani1/XC132190.ogg\"\n\n# Load audio\ny, sr = librosa.load(audio_path, sr=None)\nprint(f\"Audio Loaded! ✅ Duration: {librosa.get_duration(y=y, sr=sr):.2f} seconds\")\nimport glob\n\n# Get all OGG files from the subdirectories\nall_audio_files = glob.glob(\"../input/birdclef-2025/train_audio/**/*.ogg\", recursive=True)\n\n# Load and analyze first 3 files\nfor audio_path in all_audio_files[:3]:\n    y, sr = librosa.load(audio_path, sr=None)\n    duration = librosa.get_duration(y=y, sr=sr)\n    print(f\"Loaded: {audio_path.split('/')[-1]} | Duration: {duration:.2f} seconds\")\n\nimport librosa.display\nimport matplotlib.pyplot as plt\n\n# Plot waveform\nplt.figure(figsize=(10, 4))\nlibrosa.display.waveshow(y, sr=sr)\nplt.title('Waveform of Sample Audio')\nplt.xlabel('Time (s)')\nplt.ylabel('Amplitude')\nplt.show()\n\n# Plot spectrogram\nS = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)\nS_dB = librosa.power_to_db(S, ref=np.max)\n\nplt.figure(figsize=(10, 4))\nlibrosa.display.specshow(S_dB, sr=sr, x_axis='time', y_axis='mel')\nplt.colorbar(format='%+2.0f dB')\nplt.title('Mel Spectrogram')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T23:07:29.518105Z","iopub.execute_input":"2025-03-18T23:07:29.518453Z","iopub.status.idle":"2025-03-18T23:07:29.568710Z","shell.execute_reply.started":"2025-03-18T23:07:29.518426Z","shell.execute_reply":"2025-03-18T23:07:29.566750Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_features(audio_path, max_pad_len=128):\n    y, sr = librosa.load(audio_path, sr=32000)  # Use 32 kHz as specified\n    S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)\n    S_db = librosa.power_to_db(S, ref=np.max)\n\n    # Pad or trim spectrogram\n    if S_db.shape[1] < max_pad_len:\n        pad_width = max_pad_len - S_db.shape[1]\n        S_db = np.pad(S_db, ((0, 0), (0, pad_width)), mode='constant')\n    else:\n        S_db = S_db[:, :max_pad_len]\n\n    return S_db\n\n# Example feature extraction\naudio_path = \"../input/birdclef-2025/train_audio/greani1/XC132190.ogg\"\nfeatures = extract_features(audio_path)\nprint(f\"Extracted Features Shape: {features.shape}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T23:12:45.361459Z","iopub.execute_input":"2025-03-18T23:12:45.361811Z","iopub.status.idle":"2025-03-18T23:12:45.429827Z","shell.execute_reply.started":"2025-03-18T23:12:45.361783Z","shell.execute_reply":"2025-03-18T23:12:45.428972Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv('../input/birdclef-2025/train.csv')\ntaxonomy_df = pd.read_csv('../input/birdclef-2025/taxonomy.csv')\ntrain_df = pd.merge(train_df, taxonomy_df[['primary_label', 'class_name']], how='left', on='primary_label')\n\nprint(train_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T23:17:55.461658Z","iopub.execute_input":"2025-03-18T23:17:55.462007Z","iopub.status.idle":"2025-03-18T23:17:55.553337Z","shell.execute_reply.started":"2025-03-18T23:17:55.461980Z","shell.execute_reply":"2025-03-18T23:17:55.552273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport cv2\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.utils import to_categorical\n\n# Pad or truncate to fixed shape\ndef pad_or_truncate(S, max_len=128):\n    \"\"\"Pad or truncate the spectrogram to a fixed size of 128x128.\"\"\"\n    if S.shape[1] < max_len:\n        # Pad with zeros if shorter\n        pad_width = max_len - S.shape[1]\n        S = np.pad(S, ((0, 0), (0, pad_width)), mode='constant')\n    else:\n        # Truncate if longer\n        S = S[:, :max_len]\n\n    return S\n\n# Extract features with padding/truncation\ndef extract_features(audio_path):\n    try:\n        y, sr = librosa.load(audio_path, sr=None)\n        S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)\n        S_dB = librosa.power_to_db(S, ref=np.max)\n\n        # Pad or truncate the spectrogram to 128x128\n        S_fixed = pad_or_truncate(S_dB, max_len=128)\n        \n        return S_fixed\n    except Exception as e:\n        print(f\"❌ Error processing {audio_path}: {e}\")\n        return None\n\n# Load a small portion of the dataset\nsample_df = train_df.sample(100, random_state=42)  # Use 100 samples to keep it small\nX, y = [], []\n\n# Process files and extract features\nfor i, row in sample_df.iterrows():\n    audio_file = f\"../input/birdclef-2025/train_audio/{row['filename']}\"\n    if os.path.exists(audio_file):\n        feature = extract_features(audio_file)\n        \n        # Check for valid feature shape\n        if feature is not None and feature.shape == (128, 128):\n            X.append(feature)\n            y.append(row['primary_label'])\n        else:\n            print(f\"⚠️ Skipping {row['filename']} due to invalid feature shape.\")\n\n# Convert to numpy arrays and reshape for CNN input\nif len(X) == 0 or len(y) == 0:\n    raise ValueError(\"❌ No valid audio files were processed. Check file paths and feature extraction!\")\n\nX = np.array(X)\nX = X.reshape(X.shape[0], 128, 128, 1)  # Reshape for CNN input\ny_encoded, y_labels = pd.factorize(y)  # Encode text labels as integers\ny = to_categorical(y_encoded)\n\n# Split data into training and validation\nX_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)\n\nprint(f\"✅ Training data shape: {X_train.shape}, Validation data shape: {X_val.shape}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T23:24:16.027668Z","iopub.execute_input":"2025-03-18T23:24:16.028022Z","iopub.status.idle":"2025-03-18T23:24:24.347858Z","shell.execute_reply.started":"2025-03-18T23:24:16.028000Z","shell.execute_reply":"2025-03-18T23:24:24.346980Z"},"_kg_hide-output":true,"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":" ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T23:21:50.035478Z","iopub.execute_input":"2025-03-18T23:21:50.035816Z","iopub.status.idle":"2025-03-18T23:21:50.041842Z","shell.execute_reply.started":"2025-03-18T23:21:50.035790Z","shell.execute_reply":"2025-03-18T23:21:50.040408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"✅ Files successfully processed: {len(X)}\")\nprint(f\"✅ Unique classes: {len(np.unique(y_encoded))}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import (\n    Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization\n)\nfrom tensorflow.keras.optimizers import Adam\n\n\n# Define the optimized CNN model\ndef build_cnn_model(input_shape=(128, 128, 1), num_classes=100):\n    model = Sequential()\n\n    # Convolutional Block 1\n    model.add(Conv2D(16, (3, 3), activation=\"relu\", input_shape=input_shape, padding=\"same\"))\n    model.add(MaxPooling2D((2, 2)))\n\n    # Convolutional Block 2\n    model.add(Conv2D(32, (3, 3), activation=\"relu\", padding=\"same\"))\n    model.add(MaxPooling2D((2, 2)))\n\n    # Convolutional Block 3\n    model.add(Conv2D(64, (3, 3), activation=\"relu\", padding=\"same\"))\n    model.add(MaxPooling2D((2, 2)))\n\n    # Flatten and Dense Layers\n    model.add(Flatten())\n    model.add(Dense(128, activation=\"relu\"))\n    model.add(Dropout(0.3))  # Reduced dropout to retain useful features\n\n    # Output Layer\n    model.add(Dense(num_classes, activation=\"softmax\"))\n\n    # Compile with lower learning rate\n    optimizer = Adam(learning_rate=1e-4)  # Slower learning to stabilize training\n    model.compile(\n        optimizer=optimizer,\n        loss=\"categorical_crossentropy\",\n        metrics=[\"accuracy\"]\n    )\n\n    return model\n\n\n# Build and compile the improved model\nmodel = build_cnn_model(input_shape=(128, 128, 1), num_classes=len(y_labels))\n\n# Print model summary\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T23:28:22.151028Z","iopub.execute_input":"2025-03-18T23:28:22.151345Z","iopub.status.idle":"2025-03-18T23:28:22.270588Z","shell.execute_reply.started":"2025-03-18T23:28:22.151320Z","shell.execute_reply":"2025-03-18T23:28:22.269554Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import (Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization)\nfrom tensorflow.keras.optimizers import Adam\n\n# 📚 Dummy Dataset (Replace with real data)\nX_train = np.random.rand(300, 64, 64, 3)\ny_train = np.random.randint(0, 5, 300)\nX_val = np.random.rand(50, 64, 64, 3)\ny_val = np.random.randint(0, 5, 50)\n\n# 🧠 One-Hot Encoding\ny_train_onehot = tf.keras.utils.to_categorical(y_train, 5)\ny_val_onehot = tf.keras.utils.to_categorical(y_val, 5)\n\n# 🎨 Model (Back to Basics)\nmodel = Sequential()\n\n# 🔥 Conv Layer 1\nmodel.add(Conv2D(64, (3, 3), activation='relu', input_shape=(64, 64, 3)))\nmodel.add(BatchNormalization())\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\n\n# 🔥 Conv Layer 2\nmodel.add(Conv2D(128, (3, 3), activation='relu'))\nmodel.add(BatchNormalization())\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\n\n# 🔥 Conv Layer 3\nmodel.add(Conv2D(256, (3, 3), activation='relu'))\nmodel.add(BatchNormalization())\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\n\n# 🧠 Flatten and Dense\nmodel.add(Flatten())\nmodel.add(Dense(512, activation='relu'))\nmodel.add(Dropout(0.5))\nmodel.add(Dense(5, activation='softmax'))\n\n# ⚡️ Compile with a higher LR\n# model.compile(optimizer=Adam(learning_rate=5e-4), loss='categorical_crossentropy', metrics=['accuracy'])\nmodel.compile(\n    optimizer='adam',\n    loss='categorical_crossentropy',  # Correct loss for one-hot labels\n    metrics=['accuracy']\n)\n\n# 🏋️‍♂️ Training (No Aug, No Class Weights)\nhistory = model.fit(\n    X_train / 255.0, y_train_onehot,\n    validation_data=(X_val / 255.0, y_val_onehot),\n    epochs=5,\n    batch_size=32,\n    callbacks=[\n        tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2, min_lr=1e-6),\n        tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=4, restore_best_weights=True)\n    ]\n)\n\n# 📈 Evaluate\nval_loss, val_acc = model.evaluate(X_val / 255.0, y_val_onehot)\nprint(f\"✅ Final Validation Accuracy: {val_acc:.4f}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# print(y_train[:5])  # If this looks like [0, 1, 2, 3, 4] — use sparse_categorical_crossentropy\n# print(y_train_onehot[:5])  # If this looks like one-hot — use categorical_crossentropy\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T23:43:11.587485Z","iopub.execute_input":"2025-03-18T23:43:11.587774Z","iopub.status.idle":"2025-03-18T23:43:11.593943Z","shell.execute_reply.started":"2025-03-18T23:43:11.587752Z","shell.execute_reply":"2025-03-18T23:43:11.592676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import numpy as np\n# unique, counts = np.unique(y_train, return_counts=True)\n# print(dict(zip(unique, counts)))\n# print(X_val.shape, y_val.shape)\n# print(np.argmax(y_val[:5], axis=1))  # Check first few labels\n# preds = model.predict(X_val / 255.0)\n# print(np.argmax(preds[:5], axis=1))  # Check predictions\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T23:40:06.724574Z","iopub.execute_input":"2025-03-18T23:40:06.724917Z","iopub.status.idle":"2025-03-18T23:40:06.756315Z","shell.execute_reply.started":"2025-03-18T23:40:06.724867Z","shell.execute_reply":"2025-03-18T23:40:06.755140Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from tensorflow.keras.applications import MobileNetV2\n# from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout\n# from tensorflow.keras.models import Model\n# from tensorflow.keras.optimizers import Adam\n# from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping\n\n# # 📸 Load MobileNetV2 with pre-trained ImageNet weights, excluding the top layer\n# base_model = MobileNetV2(weights='imagenet', include_top=False, input_shape=(64, 64, 3))\n\n# # 🛠️ Add custom classification head\n# x = base_model.output\n# x = GlobalAveragePooling2D()(x)\n# x = Dense(512, activation='relu')(x)\n# x = Dropout(0.5)(x)  # Add dropout to reduce overfitting\n# predictions = Dense(5, activation='softmax')(x)\n\n# # 🧠 Create final model\n# model = Model(inputs=base_model.input, outputs=predictions)\n\n# # 🧊 Freeze base model layers initially\n# for layer in base_model.layers:\n#     layer.trainable = False\n\n# # 🚀 Compile the model with a small learning rate for initial training\n# model.compile(\n#     optimizer=Adam(learning_rate=1e-4),\n#     loss='categorical_crossentropy',\n#     metrics=['accuracy']\n# )\n\n# # 📉 Callbacks for better training\n# reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=3, min_lr=1e-6)\n# early_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)\n\n# # 🏋️‍♂️ Train the model (initial training with frozen base layers)\n# history = model.fit(\n#     X_train / 255.0, y_train_onehot,\n#     validation_data=(X_val / 255.0, y_val_onehot),\n#     epochs=10,\n#     batch_size=32,\n#     callbacks=[reduce_lr, early_stop]\n# )\n\n# # 🔓 Unfreeze some of the top layers in base_model for fine-tuning\n# for layer in base_model.layers[-20:]:\n#     layer.trainable = True\n\n# # 🆙 Recompile with a smaller learning rate for fine-tuning\n# model.compile(\n#     optimizer=Adam(learning_rate=1e-5),  # Smaller LR for fine-tuning\n#     loss='categorical_crossentropy',\n#     metrics=['accuracy']\n# )\n\n# # 🎯 Fine-tuning the model with more epochs\n# fine_tune_history = model.fit(\n#     X_train / 255.0, y_train_onehot,\n#     validation_data=(X_val / 255.0, y_val_onehot),\n#     epochs=10,\n#     batch_size=32,\n#     callbacks=[reduce_lr, early_stop]\n# )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T01:11:01.568143Z","iopub.execute_input":"2025-03-22T01:11:01.568509Z","iopub.status.idle":"2025-03-22T01:11:01.573092Z","shell.execute_reply.started":"2025-03-22T01:11:01.568481Z","shell.execute_reply":"2025-03-22T01:11:01.572019Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"taxonomy_df = pd.read_csv(cfg.taxonomy_csv)\nspecies_ids = taxonomy_df['primary_label'].tolist()\nnum_classes = len(species_ids)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:40:19.317539Z","iopub.execute_input":"2025-03-18T22:40:19.317834Z","iopub.status.idle":"2025-03-18T22:40:19.345694Z","shell.execute_reply.started":"2025-03-18T22:40:19.317809Z","shell.execute_reply":"2025-03-18T22:40:19.344444Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BirdCLEFModel(nn.Module):\n    def __init__(self, cfg, num_classes):\n        super().__init__()\n        self.cfg = cfg\n        \n        self.backbone = timm.create_model(\n            cfg.model_name,\n            pretrained=False,  \n            in_chans=cfg.in_channels,\n            drop_rate=0.0,    \n            drop_path_rate=0.0\n        )\n        \n        if 'efficientnet' in cfg.model_name:\n            backbone_out = self.backbone.classifier.in_features\n            self.backbone.classifier = nn.Identity()\n        elif 'resnet' in cfg.model_name:\n            backbone_out = self.backbone.fc.in_features\n            self.backbone.fc = nn.Identity()\n        else:\n            backbone_out = self.backbone.get_classifier().in_features\n            self.backbone.reset_classifier(0, '')\n        \n        self.pooling = nn.AdaptiveAvgPool2d(1)\n        self.feat_dim = backbone_out\n        self.classifier = nn.Linear(backbone_out, num_classes)\n        \n    def forward(self, x):\n        features = self.backbone(x)\n        \n        if isinstance(features, dict):\n            features = features['features']\n            \n        if len(features.shape) == 4:\n            features = self.pooling(features)\n            features = features.view(features.size(0), -1)\n        \n        logits = self.classifier(features)\n        return logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:43:38.521742Z","iopub.execute_input":"2025-03-18T22:43:38.522099Z","iopub.status.idle":"2025-03-18T22:43:38.528532Z","shell.execute_reply.started":"2025-03-18T22:43:38.522071Z","shell.execute_reply":"2025-03-18T22:43:38.527546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def audio2melspec(audio_data, cfg):\n    \"\"\"Convert audio data to mel spectrogram\"\"\"\n    if np.isnan(audio_data).any():\n        mean_signal = np.nanmean(audio_data)\n        audio_data = np.nan_to_num(audio_data, nan=mean_signal)\n\n    mel_spec = librosa.feature.melspectrogram(\n        y=audio_data,\n        sr=cfg.FS,\n        n_fft=cfg.N_FFT,\n        hop_length=cfg.HOP_LENGTH,\n        n_mels=cfg.N_MELS,\n        fmin=cfg.FMIN,\n        fmax=cfg.FMAX,\n        power=2.0\n    )\n\n    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n    mel_spec_norm = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)\n    \n    return mel_spec_norm\n\ndef process_audio_segment(audio_data, cfg):\n    \"\"\"Process audio segment to get mel spectrogram\"\"\"\n    if len(audio_data) < cfg.FS * cfg.WINDOW_SIZE:\n        audio_data = np.pad(audio_data, \n                          (0, cfg.FS * cfg.WINDOW_SIZE - len(audio_data)), \n                          mode='constant')\n    \n    mel_spec = audio2melspec(audio_data, cfg)\n    \n    # Resize if needed\n    if mel_spec.shape != cfg.TARGET_SHAPE:\n        mel_spec = cv2.resize(mel_spec, cfg.TARGET_SHAPE, interpolation=cv2.INTER_LINEAR)\n        \n    return mel_spec.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:43:55.537783Z","iopub.execute_input":"2025-03-18T22:43:55.538157Z","iopub.status.idle":"2025-03-18T22:43:55.544946Z","shell.execute_reply.started":"2025-03-18T22:43:55.538125Z","shell.execute_reply":"2025-03-18T22:43:55.543861Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def find_model_files(cfg):\n    \"\"\"\n    Find all .pth model files in the specified model directory\n    \"\"\"\n    model_files = []\n    \n    model_dir = Path(cfg.model_path)\n    \n    for path in model_dir.glob('**/*.pth'):\n        model_files.append(str(path))\n    \n    return model_files\n\ndef load_models(cfg, num_classes):\n    \"\"\"\n    Load all found model files and prepare them for ensemble\n    \"\"\"\n    models = []\n    \n    model_files = find_model_files(cfg)\n    \n    if not model_files:\n        print(f\"Warning: No model files found under {cfg.model_path}!\")\n        return models\n    \n    print(f\"Found a total of {len(model_files)} model files.\")\n    \n    if cfg.use_specific_folds:\n        filtered_files = []\n        for fold in cfg.folds:\n            fold_files = [f for f in model_files if f\"fold{fold}\" in f]\n            filtered_files.extend(fold_files)\n        model_files = filtered_files\n        print(f\"Using {len(model_files)} model files for the specified folds ({cfg.folds}).\")\n    \n    for model_path in model_files:\n        try:\n            print(f\"Loading model: {model_path}\")\n            checkpoint = torch.load(model_path, map_location=torch.device(cfg.device))\n            \n            model = BirdCLEFModel(cfg, num_classes)\n            model.load_state_dict(checkpoint['model_state_dict'])\n            model = model.to(cfg.device)\n            model.eval()\n            \n            models.append(model)\n        except Exception as e:\n            print(f\"Error loading model {model_path}: {e}\")\n    \n    return models\n\ndef predict_on_spectrogram(audio_path, models, cfg, species_ids):\n    \"\"\"Process a single audio file and predict species presence for each 5-second segment\"\"\"\n    predictions = []\n    row_ids = []\n    soundscape_id = Path(audio_path).stem\n    \n    try:\n        print(f\"Processing {soundscape_id}\")\n        audio_data, _ = librosa.load(audio_path, sr=cfg.FS)\n        \n        total_segments = int(len(audio_data) / (cfg.FS * cfg.WINDOW_SIZE))\n        \n        for segment_idx in range(total_segments):\n            start_sample = segment_idx * cfg.FS * cfg.WINDOW_SIZE\n            end_sample = start_sample + cfg.FS * cfg.WINDOW_SIZE\n            segment_audio = audio_data[start_sample:end_sample]\n            \n            end_time_sec = (segment_idx + 1) * cfg.WINDOW_SIZE\n            row_id = f\"{soundscape_id}_{end_time_sec}\"\n            row_ids.append(row_id)\n\n            if cfg.use_tta:\n                all_preds = []\n                \n                for tta_idx in range(cfg.tta_count):\n                    mel_spec = process_audio_segment(segment_audio, cfg)\n                    mel_spec = apply_tta(mel_spec, tta_idx)\n\n                    mel_spec = torch.tensor(mel_spec, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n                    mel_spec = mel_spec.to(cfg.device)\n\n                    if len(models) == 1:\n                        with torch.no_grad():\n                            outputs = models[0](mel_spec)\n                            probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                            all_preds.append(probs)\n                    else:\n                        segment_preds = []\n                        for model in models:\n                            with torch.no_grad():\n                                outputs = model(mel_spec)\n                                probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                                segment_preds.append(probs)\n                        \n                        avg_preds = np.mean(segment_preds, axis=0)\n                        all_preds.append(avg_preds)\n\n                final_preds = np.mean(all_preds, axis=0)\n            else:\n                mel_spec = process_audio_segment(segment_audio, cfg)\n                \n                mel_spec = torch.tensor(mel_spec, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n                mel_spec = mel_spec.to(cfg.device)\n                \n                if len(models) == 1:\n                    with torch.no_grad():\n                        outputs = models[0](mel_spec)\n                        final_preds = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                else:\n                    segment_preds = []\n                    for model in models:\n                        with torch.no_grad():\n                            outputs = model(mel_spec)\n                            probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                            segment_preds.append(probs)\n\n                    final_preds = np.mean(segment_preds, axis=0)\n                    \n            predictions.append(final_preds)\n            \n    except Exception as e:\n        print(f\"Error processing {audio_path}: {e}\")\n    \n    return row_ids, predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:44:13.318753Z","iopub.execute_input":"2025-03-18T22:44:13.319193Z","iopub.status.idle":"2025-03-18T22:44:13.339545Z","shell.execute_reply.started":"2025-03-18T22:44:13.319155Z","shell.execute_reply":"2025-03-18T22:44:13.338651Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apply_tta(spec, tta_idx):\n    \"\"\"Apply test-time augmentation\"\"\"\n    if tta_idx == 0:\n        # Original spectrogram\n        return spec\n    elif tta_idx == 1:\n        # Time shift (horizontal flip)\n        return np.flip(spec, axis=1)\n    elif tta_idx == 2:\n        # Frequency shift (vertical flip)\n        return np.flip(spec, axis=0)\n    else:\n        return spec\n\ndef run_inference(cfg, models, species_ids):\n    \"\"\"Run inference on all test soundscapes\"\"\"\n    test_files = list(Path(cfg.test_soundscapes).glob('*.ogg'))\n    \n    if cfg.debug:\n        print(f\"Debug mode enabled, using only {cfg.debug_count} files\")\n        test_files = test_files[:cfg.debug_count]\n    \n    print(f\"Found {len(test_files)} test soundscapes\")\n\n    all_row_ids = []\n    all_predictions = []\n\n    for audio_path in tqdm(test_files):\n        row_ids, predictions = predict_on_spectrogram(str(audio_path), models, cfg, species_ids)\n        all_row_ids.extend(row_ids)\n        all_predictions.extend(predictions)\n    \n    return all_row_ids, all_predictions\n\ndef create_submission(row_ids, predictions, species_ids, cfg):\n    \"\"\"Create submission dataframe\"\"\"\n    print(\"Creating submission dataframe...\")\n\n    submission_dict = {'row_id': row_ids}\n    \n    for i, species in enumerate(species_ids):\n        submission_dict[species] = [pred[i] for pred in predictions]\n\n    submission_df = pd.DataFrame(submission_dict)\n\n    submission_df.set_index('row_id', inplace=True)\n\n    sample_sub = pd.read_csv(cfg.submission_csv, index_col='row_id')\n\n    missing_cols = set(sample_sub.columns) - set(submission_df.columns)\n    if missing_cols:\n        print(f\"Warning: Missing {len(missing_cols)} species columns in submission\")\n        for col in missing_cols:\n            submission_df[col] = 0.0\n\n    submission_df = submission_df[sample_sub.columns]\n\n    submission_df = submission_df.reset_index()\n    \n    return submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:44:25.748367Z","iopub.execute_input":"2025-03-18T22:44:25.748702Z","iopub.status.idle":"2025-03-18T22:44:25.756928Z","shell.execute_reply.started":"2025-03-18T22:44:25.748677Z","shell.execute_reply":"2025-03-18T22:44:25.755662Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    start_time = time.time()\n    print(\"Starting BirdCLEF-2025 inference...\")\n    print(f\"TTA enabled: {cfg.use_tta} (variations: {cfg.tta_count if cfg.use_tta else 0})\")\n\n    models = load_models(cfg, num_classes)\n    \n    if not models:\n        print(\"No models found! Please check model paths.\")\n        return\n    \n    print(f\"Model usage: {'Single model' if len(models) == 1 else f'Ensemble of {len(models)} models'}\")\n\n    row_ids, predictions = run_inference(cfg, models, species_ids)\n\n    submission_df = create_submission(row_ids, predictions, species_ids, cfg)\n\n    submission_path = 'submission.csv'\n    submission_df.to_csv(submission_path, index=False)\n    print(f\"Submission saved to {submission_path}\")\n    \n    end_time = time.time()\n    print(f\"Inference completed in {(end_time - start_time)/60:.2f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:44:38.435763Z","iopub.execute_input":"2025-03-18T22:44:38.436106Z","iopub.status.idle":"2025-03-18T22:44:38.442772Z","shell.execute_reply.started":"2025-03-18T22:44:38.436081Z","shell.execute_reply":"2025-03-18T22:44:38.441412Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nif __name__ == \"__main__\":\n    main()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T22:44:46.583160Z","iopub.execute_input":"2025-03-18T22:44:46.583477Z","iopub.status.idle":"2025-03-18T22:44:50.935476Z","shell.execute_reply.started":"2025-03-18T22:44:46.583452Z","shell.execute_reply":"2025-03-18T22:44:50.934134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}