{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"},{"sourceId":12432848,"sourceType":"datasetVersion","datasetId":7822744}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#pip install opencv-python\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T16:37:25.034568Z","iopub.execute_input":"2025-07-10T16:37:25.034961Z","iopub.status.idle":"2025-07-10T16:37:25.040171Z","shell.execute_reply.started":"2025-07-10T16:37:25.034935Z","shell.execute_reply":"2025-07-10T16:37:25.039053Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#pip install geopandas","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T16:37:25.041954Z","iopub.execute_input":"2025-07-10T16:37:25.042306Z","iopub.status.idle":"2025-07-10T16:37:25.060891Z","shell.execute_reply.started":"2025-07-10T16:37:25.042260Z","shell.execute_reply":"2025-07-10T16:37:25.059829Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#pip install folium","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T16:37:25.061871Z","iopub.execute_input":"2025-07-10T16:37:25.062145Z","iopub.status.idle":"2025-07-10T16:37:25.078502Z","shell.execute_reply.started":"2025-07-10T16:37:25.062125Z","shell.execute_reply":"2025-07-10T16:37:25.077275Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport seaborn as sns\n\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport tensorflow_hub as hub\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.utils import Sequence\nfrom tensorflow.keras.applications import EfficientNetB2, EfficientNetB3, ResNet50\nfrom tensorflow.keras.layers import Input, GlobalAveragePooling2D, Dense, Dropout\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.preprocessing.image  import ImageDataGenerator\nfrom sklearn.metrics import classification_report, accuracy_score, f1_score, precision_score, recall_score,confusion_matrix\nimport librosa\nimport random\nimport time\nfrom tensorflow.keras.losses import BinaryFocalCrossentropy\n\nimport cv2\nimport matplotlib.pyplot as plt\nfrom glob import glob\nimport geopandas as gpd\nfrom shapely.geometry import Point\nimport folium\nfrom pathlib import Path\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau, TensorBoard\n","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2025-07-10T16:37:25.081087Z","iopub.execute_input":"2025-07-10T16:37:25.081904Z","iopub.status.idle":"2025-07-10T16:37:25.101308Z","shell.execute_reply.started":"2025-07-10T16:37:25.081871Z","shell.execute_reply":"2025-07-10T16:37:25.100278Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === PATHS & DATA ===\nDATA_DIR = Path(\"/kaggle/input/birdclef-2025\")\n#train_audio = DATA_DIR / \"train_audio\"\n#train_soundscapes = DATA_DIR / \"train_soundscapes\"\ntest_soundscapes = DATA_DIR / \"test_soundscapes\"\n#train_df= pd.read_csv(DATA_DIR / \"train.csv\")\ntaxonomy_df = pd.read_csv(DATA_DIR / \"taxonomy.csv\")\nSAMPLE_SUB = pd.read_csv(DATA_DIR / \"sample_submission.csv\")","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.102383Z","iopub.execute_input":"2025-07-10T16:37:25.102712Z","iopub.status.idle":"2025-07-10T16:37:25.140622Z","shell.execute_reply.started":"2025-07-10T16:37:25.102689Z","shell.execute_reply":"2025-07-10T16:37:25.139745Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\n# Map all available files with full paths\ndef index_all_audio_files(train_audio_path):\n    audio_index = {}\n    for f in Path(train_audio_path).rglob(\"*.ogg\"):\n        audio_index[f.name] = f\n    return audio_index\n","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.141494Z","iopub.execute_input":"2025-07-10T16:37:25.141730Z","iopub.status.idle":"2025-07-10T16:37:25.147044Z","shell.execute_reply.started":"2025-07-10T16:37:25.141711Z","shell.execute_reply":"2025-07-10T16:37:25.145878Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\n'''train_audio_dir = Path(\"/kaggle/input/birdclef-2025/train_audio\")\nogg_files = list(train_audio_dir.rglob(\"*.ogg\"))\n\nprint(f\"Total .ogg files found: {len(ogg_files)}\")'''\n","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.148045Z","iopub.execute_input":"2025-07-10T16:37:25.148842Z","iopub.status.idle":"2025-07-10T16:37:25.167848Z","shell.execute_reply.started":"2025-07-10T16:37:25.148816Z","shell.execute_reply":"2025-07-10T16:37:25.166877Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. Constants\nSAMPLE_RATE = 32000\nDURATION = 5  # in seconds\nNUM_CLASSES = len(taxonomy_df)  # total classes in BirdCLEF+ 2025\nINPUT_SHAPE = (300, 300, 3)\nCONFIDENCE_THRESHOLD = 0.5\n#  Advanced switches\nUSE_PSEUDO_LABELS = True\nUSE_FOCAL_LOSS = True\nTHRESHOLD = 0.1\n\n\nprint(f\" USE_PSEUDO_LABELS: {USE_PSEUDO_LABELS}\")\nprint(f\" USE_FOCAL_LOSS: {USE_FOCAL_LOSS}\")\nprint(f\" EVAL THRESHOLD: {THRESHOLD}\")\nprint(f\" PSEUDO-LABEL THRESHOLD: {CONFIDENCE_THRESHOLD}\")","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.168680Z","iopub.execute_input":"2025-07-10T16:37:25.168966Z","iopub.status.idle":"2025-07-10T16:37:25.189859Z","shell.execute_reply.started":"2025-07-10T16:37:25.168947Z","shell.execute_reply":"2025-07-10T16:37:25.188832Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def audio_to_spectrogram(file_path):\n    file_path = Path(file_path)\n    \n    if not file_path.exists():\n        raise FileNotFoundError(f\"File not found: {file_path}\")\n    if file_path.suffix.lower() != '.ogg':\n        raise ValueError(f\"Unsupported file type (not .ogg): {file_path}\")\n    \n    # Load audio\n    y, sr = librosa.load(file_path, sr=SAMPLE_RATE, duration=DURATION)\n    \n    # Audio integrity checks\n    if y is None or len(y) < sr * 1:\n        raise ValueError(f\"Audio too short or empty in {file_path}\")\n    if np.max(np.abs(y)) < 0.001:\n        raise ValueError(f\"Audio is mostly silent in {file_path}\")\n\n    # Generate spectrogram\n    melspec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)\n    logmel = librosa.power_to_db(melspec)\n    img = cv2.resize(logmel, (300, 300))\n    img = np.stack([img, img, img], axis=-1)\n    return img / 255.0\n","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.190817Z","iopub.execute_input":"2025-07-10T16:37:25.191082Z","iopub.status.idle":"2025-07-10T16:37:25.215524Z","shell.execute_reply.started":"2025-07-10T16:37:25.191060Z","shell.execute_reply":"2025-07-10T16:37:25.214449Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === FILE INDEXING ===\ndef index_all_audio_files(audio_root):\n    audio_index = {}\n    for f in Path(audio_root).rglob(\"*.ogg\"):\n        audio_index[f.name.lower()] = f\n    print(f\"Indexed {len(audio_index)} audio files.\")\n    return audio_index","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.218232Z","iopub.execute_input":"2025-07-10T16:37:25.218532Z","iopub.status.idle":"2025-07-10T16:37:25.233980Z","shell.execute_reply.started":"2025-07-10T16:37:25.218510Z","shell.execute_reply":"2025-07-10T16:37:25.232875Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''class AudioDataGenerator(Sequence):\n    def __init__(self, df, audio_index, taxonomy_df, batch_size=32, augment=False):\n        self.df = df.reset_index(drop=True)\n        self.audio_index = audio_index\n        self.taxonomy_df = taxonomy_df\n        self.batch_size = batch_size\n        self.augment = augment\n        print(f\"[✓] Initialized AudioDataGenerator with {len(self.df)} samples.\")\n\n    def __len__(self):\n        return int(np.ceil(len(self.df) / self.batch_size))\n\n    def __getitem__(self, idx):\n        batch = self.df.iloc[idx*self.batch_size:(idx+1)*self.batch_size]\n        X, y = [], []\n        for _, row in batch.iterrows():\n            try:\n                file_path = str(self.audio_index[row['filename']])\n                spec = audio_to_spectrogram(file_path)\n                if spec.shape != INPUT_SHAPE:\n                    raise ValueError(\"Incorrect spectrogram shape\")\n                if self.augment:\n                    spec = self.augment_spectrogram(spec)\n                X.append(spec)\n                label = np.zeros(NUM_CLASSES)\n                idx_tax = self.taxonomy_df[self.taxonomy_df['primary_label'] == row['primary_label']].index[0]\n                label[idx_tax] = 1\n                y.append(label)\n            except Exception as e:\n                print(f\"Error loading {file_path}: {e}\")\n                continue\n        print(f\"[✓] Generated batch {idx + 1}/{self.__len__()} with {len(X)} samples.\")\n        return np.array(X), np.array(y)\n\n\n    #  Advanced augmentation\n    def frequency_mask(self, spec, F=20):\n        f = random.randint(0, F)\n        f0 = random.randint(0, spec.shape[0] - f)\n        spec[f0:f0+f, :, :] = 0\n        return spec\n\n    def time_mask(self, spec, T=20):\n        t = random.randint(0, T)\n        t0 = random.randint(0, spec.shape[1] - t)\n        spec[:, t0:t0+t, :] = 0\n        return spec\n    \n    \n    def augment_spectrogram(self, spec):\n        if random.random() < 0.5:\n            spec += np.random.normal(0, 0.01, spec.shape)\n        if random.random() < 0.5:\n            t0 = random.randint(0, spec.shape[1] - 10)\n            spec[:, t0:t0 + 10, :] = 0\n        if random.random() < 0.5:\n            f0 = random.randint(0, spec.shape[0] - 10)\n            spec[f0:f0 + 10, :, :] = 0\n        if random.random() < 0.5:\n            spec = np.roll(spec, random.randint(-20, 20), axis=1)\n        if random.random() < 0.5:\n            spec = np.roll(spec, random.randint(-5, 5), axis=0)\n        if random.random() < 0.3:\n            spec += np.random.normal(0, 0.03, spec.shape)  # Stronger noise\n\n        return np.clip(spec, 0, 1)'''","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.235615Z","iopub.execute_input":"2025-07-10T16:37:25.235939Z","iopub.status.idle":"2025-07-10T16:37:25.254843Z","shell.execute_reply.started":"2025-07-10T16:37:25.235909Z","shell.execute_reply":"2025-07-10T16:37:25.253733Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''# === LOAD TRAINING + VALIDATION GENERATORS ===\ndef load_training_generator(split=0.1):\n    train_df['filename'] = train_df['filename'].apply(lambda x: Path(x).name.lower().strip())\n    audio_index = {f.name.lower(): f for f in Path(train_audio).rglob(\"*.ogg\")}\n    filtered_df = train_df[train_df['filename'].isin(audio_index)].copy()\n    train_df_split, val_df_split = train_test_split(filtered_df, test_size=split, stratify=filtered_df['primary_label'], random_state=42)\n\n    return (\n        AudioDataGenerator(train_df_split, audio_index, taxonomy_df, augment=True),\n        AudioDataGenerator(val_df_split, audio_index, taxonomy_df, augment=False)\n    )'''","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.255698Z","iopub.execute_input":"2025-07-10T16:37:25.255956Z","iopub.status.idle":"2025-07-10T16:37:25.280610Z","shell.execute_reply.started":"2025-07-10T16:37:25.255936Z","shell.execute_reply":"2025-07-10T16:37:25.279707Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''HYPERPARAMS_GRID = [\n    {\n        \"arch\": \"EfficientNetB2\",\n        \"dropout_rate\": 0.4,\n        \"dense_units\": 512,\n        \"learning_rate\": 1e-4,\n        \"finetune_depth\": 80,\n        \"finetune_lr\": 1e-6\n    },\n    {\n        \"arch\": \"EfficientNetB3\",\n        \"dropout_rate\": 0.4,\n        \"dense_units\": 512,\n        \"learning_rate\": 1e-4,\n        \"finetune_depth\": 80,\n        \"finetune_lr\": 1e-6\n    },\n    {\n        \"arch\": \"ResNet50\",\n        \"dropout_rate\": 0.3,\n        \"dense_units\": 512,\n        \"learning_rate\": 1e-4,\n        \"finetune_depth\": 80,\n        \"finetune_lr\": 1e-6\n    }\n]\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T16:37:25.281795Z","iopub.execute_input":"2025-07-10T16:37:25.282109Z","iopub.status.idle":"2025-07-10T16:37:25.301650Z","shell.execute_reply.started":"2025-07-10T16:37:25.282079Z","shell.execute_reply":"2025-07-10T16:37:25.300819Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''def build_model(config):\n    if config['arch'] == 'EfficientNetB2':\n        base = EfficientNetB2(include_top=False, weights='imagenet', input_shape=INPUT_SHAPE)\n    elif config['arch'] == 'EfficientNetB3':\n        base = EfficientNetB3(include_top=False, weights='imagenet', input_shape=INPUT_SHAPE)\n    elif config['arch'] == 'ResNet50':\n        base = ResNet50(include_top=False, weights='imagenet', input_shape=INPUT_SHAPE)\n    else:\n        raise ValueError(\"Unsupported architecture\")\n\n    inputs = Input(shape=INPUT_SHAPE)\n    x = base(inputs, training=False)\n    x = GlobalAveragePooling2D()(x)\n    x = Dropout(config['dropout_rate'])(x)\n    x = Dense(config['dense_units'], activation='relu')(x)\n    outputs = Dense(NUM_CLASSES, activation='sigmoid')(x)\n    model = Model(inputs, outputs)\n    return model'''","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.302561Z","iopub.execute_input":"2025-07-10T16:37:25.302808Z","iopub.status.idle":"2025-07-10T16:37:25.324578Z","shell.execute_reply.started":"2025-07-10T16:37:25.302787Z","shell.execute_reply":"2025-07-10T16:37:25.323509Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''def train_model(model, train_gen, val_gen, X_pseudo, y_pseudo, config):\n    X_val, y_val = val_gen[0]\n    X_train, y_train = train_gen[0]\n    if USE_PSEUDO_LABELS and len(X_pseudo) > 0:\n        print(f\"Adding {len(X_pseudo)} pseudo-labeled samples to training data\")\n        X_train = np.concatenate([X_train, X_pseudo])\n        y_train = np.concatenate([y_train, y_pseudo])\n\n    callbacks = [\n        EarlyStopping(patience=15, restore_best_weights=True),\n        ModelCheckpoint(f\"best_{config['arch'].lower()}.h5\", save_best_only=True),\n        ReduceLROnPlateau(factor=0.5, patience=3, min_lr=1e-7, verbose=1)\n    ]\n\n    history = {}\n\n    print(\"Feature extraction phase...\")\n    loss_fn = BinaryFocalCrossentropy(gamma=2.0,  label_smoothing=0.05) if USE_FOCAL_LOSS else 'binary_crossentropy'\n    model.compile(optimizer=tf.keras.optimizers.Adam(config['learning_rate']),loss=loss_fn, metrics=['accuracy'])\n    hist1 = model.fit(\n        X_train, y_train,\n        validation_data=(X_val, y_val),\n        epochs=30,\n        batch_size=32,\n        callbacks=callbacks\n    )\n    history['pretrain'] = hist1.history\n\n    print(\"\\n Starting fine-tuning...\")\n    model.trainable = True\n    for layer in model.layers[:-config['finetune_depth']]:\n        layer.trainable = False\n\n    model.compile(optimizer=tf.keras.optimizers.Adam(config['finetune_lr']),loss=loss_fn, metrics=['accuracy'])\n    hist2 = model.fit(\n        X_train, y_train,\n        validation_data=(X_val, y_val),\n        epochs=30,\n        batch_size=32,\n        callbacks=callbacks\n    )\n    history['finetune'] = hist2.history\n\n    # === Predict on validation set\n    print(\"\\n Predicting on validation set...\")\n    y_val_pred = model.predict(X_val)\n\n    # --- Plot probability histogram\n    plt.figure(figsize=(8, 5))\n    plt.hist(y_val_pred.flatten(), bins=50, color='skyblue')\n    plt.title('Predicted Probabilities Distribution')\n    plt.xlabel('Probability')\n    plt.ylabel('Frequency')\n    plt.show()\n\n    # === Threshold sweep\n    thresholds = [0.05, 0.1, 0.12, 0.15, 0.18, 0.2, 0.25, 0.3]\n    best_thresh = 0.0\n    best_f1_macro = 0.0\n\n    print(\"\\n Threshold Tuning Results on Validation Set:\")\n    for thresh in thresholds:\n        y_val_bin = (y_val_pred > thresh).astype(int)\n        val_acc = accuracy_score(y_val, y_val_bin)\n        f1_macro = f1_score(y_val, y_val_bin, average='macro', zero_division=0)\n        f1_micro = f1_score(y_val, y_val_bin, average='micro', zero_division=0)\n        precision_macro = precision_score(y_val, y_val_bin, average='macro', zero_division=0)\n        recall_macro = recall_score(y_val, y_val_bin, average='macro', zero_division=0)\n\n        print(f\"\\nThreshold: {thresh:.2f}\")\n        print(f\"  Validation Accuracy = {val_acc:.4f}\")\n        print(f\"  F1 (macro) = {f1_macro:.4f}\")\n        print(f\"  F1 (micro) = {f1_micro:.4f}\")\n        print(f\"  Precision (macro) = {precision_macro:.4f}\")\n        print(f\"  Recall (macro) = {recall_macro:.4f}\")\n\n        if f1_macro > best_f1_macro:\n            best_f1_macro = f1_macro\n            best_thresh = thresh\n\n    print(\"\\n Best Threshold on Validation Set:\")\n    print(f\"  Best Threshold = {best_thresh}\")\n    print(f\"  Best F1 (macro) = {best_f1_macro:.4f}\")\n\n    # === Confusion matrix at best threshold\n    y_val_bin_best = (y_val_pred > best_thresh).astype(int)\n    cm = confusion_matrix(np.argmax(y_val, axis=1), np.argmax(y_val_bin_best, axis=1))\n    plt.figure(figsize=(10, 8))\n    sns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\")\n    plt.title(f\"Validation Confusion Matrix (Threshold={best_thresh:.2f})\")\n    plt.xlabel(\"Predicted\")\n    plt.ylabel(\"True\")\n    plt.show()\n\n    # === Save best threshold for submission use\n    with open(f\"best_threshold_{config['arch'].lower()}.txt\", \"w\") as f:\n        f.write(str(best_thresh))\n\n    return model, history\n'''","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.325465Z","iopub.execute_input":"2025-07-10T16:37:25.325738Z","iopub.status.idle":"2025-07-10T16:37:25.350215Z","shell.execute_reply.started":"2025-07-10T16:37:25.325711Z","shell.execute_reply":"2025-07-10T16:37:25.349432Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_pseudo_labels(audio_dir, model, taxonomy_df, threshold=CONFIDENCE_THRESHOLD):\n    start_time = time.time()\n    pseudo_X, pseudo_y = [], []\n    audio_files = list(Path(audio_dir).rglob(\"*.ogg\"))\n    print(f\"[✓] Found {len(audio_files)} audio files for pseudo-labeling.\")\n\n    for file in audio_files:\n        try:\n            spec = audio_to_spectrogram(str(file))\n            if spec.shape != INPUT_SHAPE:\n                print(f\"[✗] Skipping {file.name}, invalid spectrogram shape: {spec.shape}\")\n                continue\n            prob = model.predict(np.expand_dims(spec, axis=0), verbose=0)[0]\n            if np.max(prob) > threshold:\n                pseudo_X.append(spec)\n                pseudo_y.append(prob)\n                print(f\"[✓] Pseudo-label from {file.name} with max prob {np.max(prob):.2f}\")\n            else:\n                print(f\"[✗] Low confidence ({np.max(prob):.2f}) on {file.name}, skipped.\")\n        except Exception as e:\n            print(f\"[!] Pseudo-labeling error for {file.name}: {e}\")\n\n    print(f\"[✓] Total pseudo-labeled samples: {len(pseudo_X)} in {time.time() - start_time:.2f} seconds.\")\n    return np.array(pseudo_X), np.array(pseudo_y)\n","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.351072Z","iopub.execute_input":"2025-07-10T16:37:25.351374Z","iopub.status.idle":"2025-07-10T16:37:25.374180Z","shell.execute_reply.started":"2025-07-10T16:37:25.351349Z","shell.execute_reply":"2025-07-10T16:37:25.373348Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''import folium\n\n# === TRAINING HISTORY ===\ndef plot_training_history(history):\n    for phase in history:\n        plt.figure(figsize=(12, 4))\n        plt.subplot(1, 2, 1)\n        plt.plot(history[phase]['accuracy'], label='train')\n        plt.plot(history[phase]['val_accuracy'], label='val')\n        plt.title(f'{phase} accuracy')\n        plt.legend()\n        plt.subplot(1, 2, 2)\n        plt.plot(history[phase]['loss'], label='train')\n        plt.plot(history[phase]['val_loss'], label='val')\n        plt.title(f'{phase} loss')\n        plt.legend()\n        plt.tight_layout()\n        plt.savefig(f\"training_curve_{phase}.png\")'''","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.374984Z","iopub.execute_input":"2025-07-10T16:37:25.375210Z","iopub.status.idle":"2025-07-10T16:37:25.399549Z","shell.execute_reply.started":"2025-07-10T16:37:25.375193Z","shell.execute_reply":"2025-07-10T16:37:25.398650Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === EVALUATION ===\ndef evaluate_predictions(y_true, y_pred):\n    y_pred_bin = (y_pred > 0.5).astype(int)\n    print(\"Accuracy:\", accuracy_score(y_true, y_pred_bin))\n    print(\"F1 (macro):\", f1_score(y_true, y_pred_bin, average='macro'))\n    print(\"F1 (micro):\", f1_score(y_true, y_pred_bin, average='micro'))\n    print(\"Precision:\", precision_score(y_true, y_pred_bin, average='macro'))\n    print(\"Recall:\", recall_score(y_true, y_pred_bin, average='macro'))","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.400588Z","iopub.execute_input":"2025-07-10T16:37:25.400860Z","iopub.status.idle":"2025-07-10T16:37:25.420864Z","shell.execute_reply.started":"2025-07-10T16:37:25.400834Z","shell.execute_reply":"2025-07-10T16:37:25.419704Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_submission(preds, row_ids):\n    df = pd.DataFrame(preds, columns=[f\"c{i}\" for i in range(preds.shape[1])])\n    df.insert(0, \"row_id\", row_ids)\n    df.to_csv(\"submission.csv\", index=False)\n    print(f\" Saved submission.csv\")","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.422011Z","iopub.execute_input":"2025-07-10T16:37:25.422392Z","iopub.status.idle":"2025-07-10T16:37:25.444395Z","shell.execute_reply.started":"2025-07-10T16:37:25.422361Z","shell.execute_reply":"2025-07-10T16:37:25.443306Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === TEST TIME AUGMENTATION (TTA) ===\ndef predict_with_tta(model, X, n=3):\n    print(f\"Running TTA with {n} augmentations...\")\n    preds = np.zeros((len(X), NUM_CLASSES))\n    for i in range(n):\n        X_aug = np.array([np.clip(x + np.random.normal(0, 0.01, x.shape), 0, 1) for x in X])\n        preds += model.predict(X_aug, verbose=0)\n        print(f\"TTA round {i + 1}/{n} completed.\")\n    return preds / n","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.445358Z","iopub.execute_input":"2025-07-10T16:37:25.445661Z","iopub.status.idle":"2025-07-10T16:37:25.465557Z","shell.execute_reply.started":"2025-07-10T16:37:25.445631Z","shell.execute_reply":"2025-07-10T16:37:25.464677Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === MAP ===\ndef plot_predictions_on_map(preds, metadata, threshold=0.5):\n    fmap = folium.Map(location=[0, -60], zoom_start=3)\n    for i, row in metadata.iterrows():\n        lat, lon = row['latitude'], row['longitude']\n        if pd.notnull(lat) and pd.notnull(lon):\n            ids = np.where(preds[i] > threshold)[0]\n            if len(ids):\n                label = \", \".join([f\"Species {sid}\" for sid in ids])\n                folium.Marker(location=[lat, lon], popup=label).add_to(fmap)\n    fmap.save(\"prediction_map.html\")","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.466446Z","iopub.execute_input":"2025-07-10T16:37:25.466748Z","iopub.status.idle":"2025-07-10T16:37:25.486083Z","shell.execute_reply.started":"2025-07-10T16:37:25.466725Z","shell.execute_reply":"2025-07-10T16:37:25.485005Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''def run_pipeline():\n    print(\"\\n Starting BirdCLEF training pipeline...\")\n    \n    # === Load training & validation generators\n    train_gen, val_gen = load_training_generator()\n    \n    for config in HYPERPARAMS_GRID:\n        print(\"\\n====================================\")\n        print(f\" Starting training for architecture: {config['arch']}\")\n        print(\"====================================\\n\")\n        \n        # === Build model\n        model = build_model(config)\n        print(f\" Built model: {config['arch']}\")\n        \n        # === Pseudo-labeling\n        if USE_PSEUDO_LABELS:\n            print(\" Generating pseudo-labels...\")\n            X_pseudo, y_pseudo = generate_pseudo_labels(\n                audio_dir=DATA_DIR / \"train_soundscapes\",\n                model=model,\n                taxonomy_df=taxonomy_df,\n                threshold=CONFIDENCE_THRESHOLD\n            )\n        else:\n            print(\" Pseudo-labeling is OFF.\")\n            X_pseudo = np.zeros((0, *INPUT_SHAPE))\n            y_pseudo = np.zeros((0, NUM_CLASSES))\n        \n        print(f\" Pseudo-labeled samples: {len(X_pseudo)}\")\n        \n        # === Train model (with threshold tuning)\n        model, history = train_model(\n            model,\n            train_gen,\n            val_gen,\n            X_pseudo,\n            y_pseudo,\n            config\n        )\n        \n        # === Save final model with clear name\n        model_filename = f\"{config['arch'].lower()}_finetuned.h5\"\n        model.save(model_filename)\n        print(f\" Model saved as {model_filename}\")\n        \n        # === Plot training curves\n        plot_training_history(history)\n        \n        # === Final Validation Predictions and Metrics\n        X_val, y_val = val_gen[0]\n        y_val_pred = model.predict(X_val)\n        \n        print(\"\\n Final Evaluation on Validation Set:\")\n        for thresh in [0.1, 0.15, 0.2, 0.25, 0.3]:\n            y_val_bin = (y_val_pred > thresh).astype(int)\n            val_acc = accuracy_score(y_val, y_val_bin)\n            f1_macro = f1_score(y_val, y_val_bin, average='macro', zero_division=0)\n            f1_micro = f1_score(y_val, y_val_bin, average='micro', zero_division=0)\n            precision_macro = precision_score(y_val, y_val_bin, average='macro', zero_division=0)\n            recall_macro = recall_score(y_val, y_val_bin, average='macro', zero_division=0)\n            print(f\"\\nThreshold: {thresh:.2f}\")\n            print(f\"  Validation Accuracy = {val_acc:.4f}\")\n            print(f\"  F1 (macro) = {f1_macro:.4f}\")\n            print(f\"  F1 (micro) = {f1_micro:.4f}\")\n            print(f\"  Precision (macro) = {precision_macro:.4f}\")\n            print(f\"  Recall (macro) = {recall_macro:.4f}\")\n        \n        print(\"\\n Completed training for architecture:\", config['arch'])\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T16:37:25.487114Z","iopub.execute_input":"2025-07-10T16:37:25.487458Z","iopub.status.idle":"2025-07-10T16:37:25.509648Z","shell.execute_reply.started":"2025-07-10T16:37:25.487428Z","shell.execute_reply":"2025-07-10T16:37:25.508756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Run\n'''if __name__ == \"__main__\":\n    run_pipeline() '''","metadata":{"scrolled":true,"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T16:37:25.510484Z","iopub.execute_input":"2025-07-10T16:37:25.510742Z","iopub.status.idle":"2025-07-10T16:37:25.533169Z","shell.execute_reply.started":"2025-07-10T16:37:25.510716Z","shell.execute_reply":"2025-07-10T16:37:25.532359Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom pathlib import Path\nfrom tensorflow.keras.models import load_model\n\ndef submission_pipeline_tta(model_name, model_dir, tta_rounds=3, batch_size=16, features_dir=None, input_shape=INPUT_SHAPE):\n    \"\"\"\n    BirdCLEF2025 Kaggle Submission Pipeline, robust to missing test audio and exceptions.\n    - Loads a Keras model.\n    - If test audio exists, runs inference with TTA (for local debug/validation).\n    - If not, builds submission.csv with zeros or custom logic using sample_submission.csv (for competition submission).\n    - Always creates a valid submission.csv, even if exceptions occur.\n    - Uses precomputed features if features_dir is given.\n    \"\"\"\n    try:\n        print(\"\\nSUBMISSION PIPELINE (Batchwise, TTA-ready)\")\n        print(f\"Model: {model_name}\")\n        print(f\"TTA rounds: {tta_rounds}\")\n\n        # Load model\n        model_path = Path(model_dir) / f\"{model_name}.h5\"\n        model = load_model(model_path)\n        print(f\"Loaded model from {model_path}\")\n\n        # Load threshold\n        try:\n            threshold_path = Path(model_dir) / f\"best_threshold_{model_name.lower()}.txt\"\n            with open(threshold_path, \"r\") as f:\n                THRESHOLD = float(f.read())\n            print(f\"Loaded best threshold: {THRESHOLD}\")\n        except Exception:\n            THRESHOLD = 0.05\n            print(f\"Threshold file not found. Using fallback THRESHOLD={THRESHOLD}\")\n\n        # Try to find test audio files (for local debug)\n        print(\"Searching for test_soundscapes...\")\n        test_soundscapes_dir = Path(\"/kaggle/input/birdclef-2025/test_soundscapes\")\n        test_files = list(test_soundscapes_dir.rglob(\"*.ogg\")) if test_soundscapes_dir.exists() else []\n\n        if len(test_files) > 0:\n            # LOCAL DEBUG PIPELINE: process test audio with TTA\n            print(f\"Found {len(test_files)} test .ogg files\")\n            # (Implement your audio_to_spectrogram and create_submission as needed.)\n            all_preds = []\n            all_row_ids = []\n\n            def yield_batches(files, batch_size):\n                batch = []\n                meta = []\n                for f in files:\n                    try:\n                        spec = audio_to_spectrogram(f)  # You must implement this function!\n                        if spec.shape != input_shape:\n                            print(f\"[✗] Invalid spectrogram for {f.name}\")\n                            continue\n                        batch.append(spec)\n                        meta.append(f)\n                        if len(batch) == batch_size:\n                            yield batch, meta\n                            batch = []\n                            meta = []\n                    except Exception as e:\n                        print(f\"[✗] Error processing {f.name}: {e}\")\n                if batch:\n                    yield batch, meta\n\n            print(\"Starting batch processing + TTA...\")\n            for batch, metas in yield_batches(test_files, batch_size):\n                batch = np.array(batch)\n                preds = np.zeros((len(batch), model.output_shape[1]))\n                for i in range(tta_rounds):\n                    X_aug = np.clip(batch + np.random.normal(0, 0.01, batch.shape), 0, 1)\n                    preds += model.predict(X_aug, verbose=0)\n                preds /= tta_rounds\n                preds_bin = (preds > THRESHOLD).astype(int)\n                all_preds.extend(preds_bin)\n                for f in metas:\n                    all_row_ids.extend([f\"soundscape_{f.stem}_{i*5}\" for i in range(12)])\n\n            print(f\"Processed all batches. Total predictions: {len(all_preds)}\")\n            all_preds = np.array(all_preds)\n            create_submission(all_preds, all_row_ids)  # You must implement this function!\n            print(f\"\\nSubmission CSV saved with threshold={THRESHOLD}, TTA={tta_rounds}, batch_size={batch_size}\")\n\n        else:\n            # KAGGLE SUBMISSION PIPELINE: generate submission.csv from sample_submission.csv only\n            print(\"No test .ogg files found! Generating predictions from sample_submission.csv only.\")\n            sample_sub_path = \"/kaggle/input/birdclef-2025/sample_submission.csv\"\n            sample_submission = pd.read_csv(sample_sub_path)\n            row_ids = sample_submission['row_id'].values\n            num_rows, num_classes = sample_submission.shape[0], sample_submission.shape[1] - 1\n\n            # DUMMY: fill with zeros (replace with your logic if you have precomputed features/metadata)\n            preds = np.zeros((num_rows, num_classes))\n\n            # If features_dir is provided, use precomputed features for prediction\n            if features_dir is not None:\n                features_path = Path(features_dir)\n                for i, row in sample_submission.iterrows():\n                    feature_path = features_path / f\"{row['row_id']}.npy\"\n                    if feature_path.exists():\n                        try:\n                            spec = np.load(feature_path)\n                            if spec.shape != input_shape:\n                                print(f\"[✗] Invalid shape for {row['row_id']}: {spec.shape}\")\n                                continue\n                            tta_preds = np.zeros(num_classes)\n                            for t in range(tta_rounds):\n                                aug_spec = np.clip(spec + np.random.normal(0, 0.01, spec.shape), 0, 1)\n                                tta_preds += model.predict(aug_spec[None, ...], verbose=0)[0]\n                            preds[i, :] = tta_preds / tta_rounds\n                        except Exception as e:\n                            print(f\"[✗] Feature error for {row['row_id']}: {e}\")\n                            preds[i, :] = 0\n\n            result = pd.DataFrame(preds, columns=sample_submission.columns[1:])\n            result.insert(0, 'row_id', row_ids)\n            result.to_csv('submission.csv', index=False)\n            print(\"Submission file generated successfully!\")\n\n    except Exception as e:\n        # Always create a fallback submission file on error\n        print(f\"Exception occurred: {e}\\nGenerating fallback submission.csv with zeros.\")\n        sample_sub_path = \"/kaggle/input/birdclef-2025/sample_submission.csv\"\n        sample_submission = pd.read_csv(sample_sub_path)\n        row_ids = sample_submission['row_id'].values\n        num_rows, num_classes = sample_submission.shape[0], sample_submission.shape[1] - 1\n        preds = np.zeros((num_rows, num_classes))\n        result = pd.DataFrame(preds, columns=sample_submission.columns[1:])\n        result.insert(0, 'row_id', row_ids)\n        result.to_csv('submission.csv', index=False)\n        print(\"Fallback submission file generated.\")","metadata":{"execution":{"iopub.status.busy":"2025-07-10T16:37:25.534231Z","iopub.execute_input":"2025-07-10T16:37:25.534585Z","iopub.status.idle":"2025-07-10T16:37:25.556612Z","shell.execute_reply.started":"2025-07-10T16:37:25.534557Z","shell.execute_reply":"2025-07-10T16:37:25.555577Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Run for submission\n#submission_pipeline_tta(\"efficientnetb2_finetuned\", tta_rounds=3)\n#submission_pipeline_tta(\"efficientnetb3_finetuned\", tta_rounds=3)\nsubmission_pipeline_tta(\n    model_name=\"resnet50_finetuned\",\n    model_dir=Path(\"/kaggle/input/resnet50finetuned\"),\n    tta_rounds=3,\n    batch_size=16,\n    features_dir=\"/kaggle/input/my-precomputed-test-features\",  \n    input_shape=INPUT_SHAPE\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T16:37:25.557612Z","iopub.execute_input":"2025-07-10T16:37:25.557917Z","iopub.status.idle":"2025-07-10T16:37:27.979499Z","shell.execute_reply.started":"2025-07-10T16:37:25.557891Z","shell.execute_reply":"2025-07-10T16:37:27.978570Z"}},"outputs":[],"execution_count":null}]}