{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"isSourceIdPinned":false,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ===============================\n# 📦 Imports\n# ===============================\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport soundfile as sf\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.metrics import f1_score, classification_report, confusion_matrix\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-28T01:02:54.330394Z","iopub.execute_input":"2025-04-28T01:02:54.331268Z","iopub.status.idle":"2025-04-28T01:02:55.986731Z","shell.execute_reply.started":"2025-04-28T01:02:54.331230Z","shell.execute_reply":"2025-04-28T01:02:55.985704Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 📚 Settings\n# ===============================\n\nAUDIO_BASE_TRAIN = '/kaggle/input/birdclef-2025/train_audio/'\nAUDIO_BASE_TEST = '/kaggle/input/birdclef-2025/test_soundscapes/'\n\n# Top 10 birds used\ntop10 = ['grekis', 'compau', 'trokin', 'roahaw', 'banana', 'whtdov', 'socfly1', 'yeofly1', 'bobfly1', 'wbwwre1']\n\n# Full list of class labels (all 206 birds for submission)\nclass_labels = sorted(os.listdir(AUDIO_BASE_TRAIN))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T01:02:55.988218Z","iopub.execute_input":"2025-04-28T01:02:55.989101Z","iopub.status.idle":"2025-04-28T01:02:56.009810Z","shell.execute_reply.started":"2025-04-28T01:02:55.989071Z","shell.execute_reply":"2025-04-28T01:02:56.008874Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 🔥 Feature Extraction Function\n# ===============================\n\ndef extract_binary_features_from_chunk(chunk, percentile=76.7):\n    sr = 32000\n    S = librosa.stft(chunk, n_fft=1024, hop_length=512)\n    S_power = np.abs(S) ** 2\n    S_db = librosa.power_to_db(S_power, ref=np.max)\n    freqs = librosa.fft_frequencies(sr=sr, n_fft=1024)\n    bin_edges = np.linspace(0, 16000, 3201)\n    binary_vector = np.zeros(3200, dtype=int)\n    energy_per_freq = S_db.max(axis=1)\n    adaptive_threshold = np.percentile(energy_per_freq, percentile)\n\n    for j in range(3200):\n        f_start = bin_edges[j]\n        f_end = bin_edges[j+1]\n        bin_mask = (freqs >= f_start) & (freqs < f_end)\n        if np.any(bin_mask):\n            max_energy = np.max(energy_per_freq[bin_mask])\n            if max_energy > adaptive_threshold:\n                binary_vector[j] = 1\n\n    return binary_vector","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T01:02:56.011024Z","iopub.execute_input":"2025-04-28T01:02:56.011315Z","iopub.status.idle":"2025-04-28T01:02:56.018327Z","shell.execute_reply.started":"2025-04-28T01:02:56.011292Z","shell.execute_reply":"2025-04-28T01:02:56.017390Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 🏗️ Build Training Dataset\n# ===============================\n\nprint(\"🔵 Building training dataset...\")\n\ntrain_meta = pd.read_csv('/kaggle/input/birdclef-2025/train.csv')\n# DO NOT TOUCH train_meta['filename']\n\n\n# Create Positive and Negative Samples\npos_df = train_meta[train_meta['primary_label'] == 'grekis'].copy()\npos_df['label'] = 1\n\nneg_df = train_meta[(train_meta['primary_label'].isin(top10)) & (train_meta['primary_label'] != 'grekis')].copy()\nneg_df['label'] = 0\n\n# Balance the classes\nfrom sklearn.utils import resample\nneg_df_balanced = resample(neg_df, replace=False, n_samples=len(pos_df), random_state=42)\n\ncombined_df = pd.concat([pos_df, neg_df_balanced]).sample(frac=1, random_state=42).reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T01:02:56.020207Z","iopub.execute_input":"2025-04-28T01:02:56.020477Z","iopub.status.idle":"2025-04-28T01:02:56.262121Z","shell.execute_reply.started":"2025-04-28T01:02:56.020456Z","shell.execute_reply":"2025-04-28T01:02:56.261245Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 🔥 Extract Features for Training\n# ===============================\n\nX = []\ny = []\n\nprint(\"🔵 Extracting training features...\")\n\nfor _, row in tqdm(combined_df.iterrows(), total=len(combined_df)):\n    path = os.path.join(AUDIO_BASE_TRAIN, row['filename'])\n\n    try:\n        y_raw, sr = sf.read(path)\n        samples_per_chunk = sr * 5\n        num_chunks = len(y_raw) // samples_per_chunk\n\n        if num_chunks == 0:\n            continue\n\n        chunk_features = []\n        for i in range(num_chunks):\n            start = i * samples_per_chunk\n            end = start + samples_per_chunk\n            chunk = y_raw[start:end]\n\n            vec = extract_binary_features_from_chunk(chunk, percentile=76.7)\n            chunk_features.append(vec)\n\n        chunk_features = np.array(chunk_features)\n        final_vector = np.median(chunk_features, axis=0)\n        final_vector = (final_vector > 0.5).astype(int)\n\n        X.append(final_vector)\n        y.append(row['label'])\n\n    except Exception as e:\n        print(f\"❌ Failed for {row['filename']}: {e}\")\n\nX = np.stack(X)\ny = np.array(y)\n\nprint(f\"✅ Final Training Feature Shape: {X.shape}\")\nprint(f\"✅ Final Training Labels Shape: {y.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T01:02:56.263070Z","iopub.execute_input":"2025-04-28T01:02:56.263328Z","iopub.status.idle":"2025-04-28T01:13:38.736461Z","shell.execute_reply.started":"2025-04-28T01:02:56.263308Z","shell.execute_reply":"2025-04-28T01:13:38.735359Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 📈 Train Random Forest with Cross Validation\n# ===============================\n\nprint(\"\\n🔵 Training Random Forest...\")\n\nn_splits = 5\nskf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)\n\nf1_scores_rf = []\n\nfor fold, (train_idx, val_idx) in enumerate(skf.split(X, y)):\n    print(f\"\\n🔵 Fold {fold+1}/{n_splits}\")\n\n    X_train, X_val = X[train_idx], X[val_idx]\n    y_train, y_val = y[train_idx], y[val_idx]\n\n    clf_rf = RandomForestClassifier(\n        n_estimators=500,\n        class_weight='balanced',\n        random_state=42,\n        n_jobs=-1\n    )\n\n    clf_rf.fit(X_train, y_train)\n    y_pred_rf = clf_rf.predict(X_val)\n\n    f1 = f1_score(y_val, y_pred_rf)\n    print(f\"✅ Fold {fold+1} F1-Score: {f1:.4f}\")\n\n    f1_scores_rf.append(f1)\n\navg_f1_rf = np.mean(f1_scores_rf)\nprint(f\"\\n📊 Average F1-Score across {n_splits} folds: {avg_f1_rf:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T01:13:38.737582Z","iopub.execute_input":"2025-04-28T01:13:38.738117Z","iopub.status.idle":"2025-04-28T01:13:52.714172Z","shell.execute_reply.started":"2025-04-28T01:13:38.738090Z","shell.execute_reply":"2025-04-28T01:13:52.713025Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 🧪 Predict on Test Soundscapes\n# ===============================\n\nprint(\"\\n🔵 Predicting on test soundscapes...\")\n\nsubmission_rows = []\n\ntest_soundscapes = [os.path.join(AUDIO_BASE_TEST, f) for f in sorted(os.listdir(AUDIO_BASE_TEST)) if f.endswith('.ogg')]\n\nfor soundscape_path in tqdm(test_soundscapes, desc=\"Processing Test Soundscapes\"):\n    try:\n        sig, sr = librosa.load(soundscape_path, sr=32000)\n    except Exception as e:\n        print(f\"❌ Error reading {soundscape_path}: {e}\")\n        continue\n\n    samples_per_chunk = sr * 5\n\n    for i in range(0, len(sig), samples_per_chunk):\n        chunk = sig[i:i+samples_per_chunk]\n        if len(chunk) < samples_per_chunk:\n            continue\n\n        try:\n            vec = extract_binary_features_from_chunk(chunk, percentile=76.7)\n            vec = vec.reshape(1, -1)\n\n            prob = clf_rf.predict_proba(vec)[0][1]  # Probability for grekis\n\n            row_id = os.path.basename(soundscape_path).split('.')[0] + f'_{(i//samples_per_chunk+1)*5}'\n            row = [row_id]\n\n            for bird in class_labels:\n                if bird == 'grekis':\n                    row.append(prob)\n                else:\n                    row.append(0.001)  # Small probability for other birds\n\n            submission_rows.append(row)\n\n        except Exception as e:\n            print(f\"❌ Error processing chunk {i}: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T01:13:52.715428Z","iopub.execute_input":"2025-04-28T01:13:52.715708Z","iopub.status.idle":"2025-04-28T01:13:52.735893Z","shell.execute_reply.started":"2025-04-28T01:13:52.715685Z","shell.execute_reply":"2025-04-28T01:13:52.734272Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 💾 Create Submission File\n# ===============================\n\nsubmission_df = pd.DataFrame(submission_rows, columns=['row_id'] + class_labels)\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"✅ Final submission.csv created!\")\n\nsubmission_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T01:13:52.736903Z","iopub.execute_input":"2025-04-28T01:13:52.737228Z","iopub.status.idle":"2025-04-28T01:13:52.820532Z","shell.execute_reply.started":"2025-04-28T01:13:52.737198Z","shell.execute_reply":"2025-04-28T01:13:52.819439Z"}},"outputs":[],"execution_count":null}]}