{"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,"sourceType":"competition"}],"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\nimport os\nimport librosa\nimport numpy as np\nimport glob\nfrom joblib import Parallel, delayed\nfrom tqdm import tqdm\nfrom sklearn.decomposition import PCA\nimport librosa.display\nimport pandas as pd\nfrom tqdm.auto import tqdm\nfrom functools import lru_cache\nimport concurrent.futures\nimport warnings\nimport lightgbm as lgb\nfrom xgboost import XGBClassifier\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\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder\nfrom sklearn.metrics import classification_report\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense, Dropout, BatchNormalization\nfrom tensorflow.keras.optimizers import Adam\nfrom sklearn.metrics import classification_report, accuracy_score","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_audio_dir = '/kaggle/input/birdclef-2025/train_audio/'\ntrain, taxonomy = pd.read_csv('/kaggle/input/birdclef-2025/train.csv'), pd.read_csv('/kaggle/input/birdclef-2025/taxonomy.csv')\nsubmission =  pd.read_csv('/kaggle/input/birdclef-2025/sample_submission.csv')\nsubmission.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"selected_features = ['primary_label','filename','latitude','longitude','scientific_name','common_name']\ntrain = train[selected_features]\n# Define your unique labels\nlabels = {'brtpar1', '1139490', 'compau', 'chbant1', 'yehcar1', 'yecspi2', 'watjac1', 'grasal4', 'grbhaw1', \n          'yebfly1', 'neocor', '81930', 'spbwoo1', '64862', 'grepot1', 'ruther1', 'banana', 'whttro1', \n          '1462711', '42087', '66531', 'soulap1', 'amakin1', '41970', '65373', '714022', 'bafibi1', 'blcant4', \n          'rutjac1', 'plbwoo1', 'anhing', 'yehbla2', '21211', 'recwoo1', 'blbgra1', 'creoro1', 'shtfly1', \n          'amekes', 'blchaw1', '21116', '566513', 'bugtan', 'strcuc1', '1564122', '1462737', 'purgal2', \n          'socfly1', 'gohman1', 'gycwor1', 'bubwre1', 'blhpar1', '65336', 'solsan', '134933', '24292', \n          '42113', 'plukit1', 'savhaw1', 'sobtyr1', 'chfmac1', 'yebsee1', '66016', 'blbwre1', 'mastit1', \n          'smbani', 'whfant1', 'strfly1', 'roahaw', 'rumfly1', '476537', 'butsal1', 'bucmot3', 'colcha1', \n          'bobfly1', '67082', 'rebbla1', 'pavpig2', '1192948', 'whbman1', 'verfly', 'eardov1', 'norscr1', \n          'rinkin1', '67252', 'greibi1', 'greegr', 'cattyr', 'laufal1', 'trokin', 'grekis', 'crebob1', \n          'bubcur1', 'fotfly', 'palhor2', '476538', '24322', 'tropar', 'whwswa1', 'yercac1', '517119', \n          '24272', 'cocher1', 'labter1', 'bicwre1', 'compot1', 'olipic1', 'blcjay1', 'colara1', 'spepar1', \n          'cregua1', 'cargra1', '22976', 'plctan1', '715170', 'leagre', '22973', 'bkmtou1', 'yelori1', \n          'trsowl', 'strher', 'ragmac1', 'yeofly1', '548639', 'tbsfin1', '135045', '65344', 'bkcdon', \n          'stbwoo2', 'piepuf1', '868458', '963335', 'blctit1', 'saffin', 'rtlhum', 'royfly1', '66893', \n          'rutpuf1', 'linwoo1', 'wbwwre1', 'srwswa1', '126247', 'gretin1', 'grnkin', 'littin1', 'secfly1', \n          '41778', '528041', 'bbwduc', 'greani1', 'rubsee1', 'orcpar', 'rosspo1', 'yebela1', '47067', \n          'crcwoo1', '65349', 'snoegr', 'gybmar', 'thbeup1', '66578', 'turvul', 'rugdov', 'baymac', \n          'speowl1', 'cocwoo1', 'cotfly1', 'y00678', '65419', 'bobher1', '52884', '41663', '22333', \n          'piwtyr1', '21038', '787625', 'rufmot1', '65962', 'paltan1', '48124', '555142', '65547', \n          'crbtan1', '1194042', 'ywcpar', 'shghum1', 'cinbec1', 'thlsch3', '1346504', '555086', 'sahpar1', \n          'grysee1', 'blkvul', '523060', 'strowl1', 'whbant1', 'whmtyr1', '65448', 'ampkin1', 'whtdov', \n          'yectyr1', '42007', '46010', 'pirfly1', 'woosto', 'babwar', '50186'}\n\n# Create a dictionary to store DataFrames\ndfs = {label: train[train['primary_label'] == label] for label in labels}","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\n# Suppress librosa warnings that waste time printing\nwarnings.filterwarnings('ignore')\n\ndef extract_low_amplitude_regions(stft, threshold_db=-40):\n    # Take the absolute value of the spectrogram\n    magnitude = np.abs(stft)\n    \n    # Convert to power (energy) spectrogram\n    power_spectrogram = np.abs(magnitude) ** 2\n    \n    # Convert to decibels\n    db_magnitude = librosa.power_to_db(power_spectrogram)\n    \n    # Create mask for low amplitude regions\n    mask = db_magnitude > threshold_db\n    \n    # Replace low amplitude regions with a very low value\n    low_amp_spectrogram = np.where(mask, db_magnitude, -np.inf)\n    \n    return mask, power_spectrogram  # Return power_spectrogram directly for efficiency\n\n# Add caching to expensive function calls\n@lru_cache(maxsize=128)\ndef cached_load(filename_key, sr=32000, duration=5):\n    \"\"\"Cache-friendly loader that uses a string key\"\"\"\n    return librosa.load(filename_key, sr=sr, duration=duration)\n\ndef extract_features_fast(y, sr):\n    \"\"\"Feature extraction with low amplitude region detection\"\"\"\n    # Skip HPSS which is very expensive (still using raw signal)\n    y_percussive = y\n    \n    # Compute spectrogram (using faster n_fft if possible)\n    n_fft = min(2048, len(y)//4)  # Smaller FFT is faster\n    stft = librosa.stft(y_percussive, n_fft=n_fft, hop_length=n_fft//4)\n    \n    # Apply low amplitude region extraction (integrated into pipeline)\n    mask, power_spectrogram = extract_low_amplitude_regions(stft)\n    \n    # Extract features using the power spectrogram from low amplitude extraction\n    spectral_centroid = np.mean(librosa.feature.spectral_centroid(\n        S=power_spectrogram, sr=sr), axis=1)\n    \n    spectral_rolloff = np.mean(librosa.feature.spectral_rolloff(\n        S=power_spectrogram, sr=sr), axis=1)\n    \n    zero_crossing_rate = np.mean(librosa.feature.zero_crossing_rate(\n        y_percussive), axis=1)\n\n    # More efficient concatenation (still using the three features requested)\n    return np.concatenate([spectral_centroid, spectral_rolloff, zero_crossing_rate])\n\ndef process_file(args):\n    \"\"\"Standalone function for parallel processing\"\"\"\n    filename, label = args\n    try:\n        y, sr = cached_load(filename, sr=32000, duration=5)\n        features = extract_features_fast(y, sr)\n        return (features, label)\n    except Exception as e:\n        # Print error for debugging (needed in Kaggle)\n        print(f\"Error processing {filename}: {str(e)}\")\n        return None\n\ndef prepare_data_optimized_parallel(dfs, labels, train_audio_dir, max_files_per_directory=50, n_components=64):\n    # Prepare arguments for parallel processing\n    all_args = []\n    for label in labels:\n        df = dfs[label]\n        n_files = min(max_files_per_directory, len(df))\n        filenames = [os.path.join(train_audio_dir, df.iloc[i]['filename']) for i in range(n_files)]\n        all_args.extend([(filename, label) for filename in filenames])\n    \n    # For Kaggle - don't use too many workers\n    # Kaggle typically has 2-4 CPU cores available\n    num_workers = min(2, os.cpu_count() or 1)\n    \n    # Display total number of files\n    print(f\"Processing {len(all_args)} files with {num_workers} workers\")\n    \n    # Process in parallel - but with a smaller chunksize for Kaggle\n    x_p = []\n    y = []\n    \n    # Use ThreadPoolExecutor instead of ProcessPoolExecutor for Kaggle compatibility\n    with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor:\n        results = list(tqdm(\n            executor.map(process_file, all_args, chunksize=5), \n            total=len(all_args),\n            desc=\"Extracting features\",\n            unit=\"file\"\n        ))\n    \n    # Filter out None results (errors) and unpack features and labels\n    results = [r for r in results if r is not None]\n    if results:\n        x_p, y = zip(*results)\n    \n    # Convert to numpy arrays\n    if not x_p:\n        print(\"\\nWARNING: No audio files were successfully processed\")\n        return np.array([]), np.array([]), None, None\n    \n    x_p = np.array(x_p)\n    y = np.array(y)\n    \n    print(f\"\\nTotal processed data shape: x_p={x_p.shape}, y={y.shape}\")\n    \n    # Fit and transform with scaler\n    scaler_p = StandardScaler()\n    x_p_scaled = scaler_p.fit_transform(x_p)\n    \n    # Fit and transform with PCA (with minimal valid components)\n    valid_n_components = min(n_components, *x_p_scaled.shape)\n    if valid_n_components <= 0:\n        return x_p_scaled, y, scaler_p, None\n    \n    pca_p = PCA(n_components=valid_n_components)\n    x_p_embedded = pca_p.fit_transform(x_p_scaled)\n    \n    return x_p_embedded, y, scaler_p, pca_p","metadata":{"trusted":true,"_kg_hide-output":true,"_kg_hide-input":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Example usage in Kaggle\nfrom sklearn.model_selection import train_test_split\n\n# Assuming dfs, labels, train_audio_dir are already defined\nX, y, scaler, pca = prepare_data_optimized_parallel(\n    dfs, \n    labels, \n    train_audio_dir, \n    max_files_per_directory=40, \n    n_components=20\n)\n\n# Split and model training as usual\nX_train_val, test_data_features, y_train_val, y_test_final = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) # Added stratify for balanced classes","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\ntest_soundscapes = glob.glob('/kaggle/input/birdclef-2025/train_soundscapes/*')\n\n# Define efficient processing function with caching\n@lru_cache(maxsize=128)\ndef cached_load(filename_key, sr=32000, duration=5):\n    \"\"\"Cache-friendly loader that uses a string key\"\"\"\n    return librosa.load(filename_key, sr=sr, duration=duration)\n\ndef process_file(filename):\n    \"\"\"Process a single file efficiently\"\"\"\n    try:\n        # Use cached loading\n        y, sr = cached_load(filename, sr=32000, duration=5)\n        \n        # Use our optimized feature extraction\n        y_percussive = y  # Skip HPSS\n        \n        # Compute spectrogram efficiently\n        n_fft = min(2048, len(y)//4)\n        stft = librosa.stft(y_percussive, n_fft=n_fft, hop_length=n_fft//4)\n        \n        # Apply low amplitude region extraction\n        magnitude = np.abs(stft)\n        power_spectrogram = np.abs(magnitude) ** 2\n        db_magnitude = librosa.power_to_db(power_spectrogram)\n        mask = db_magnitude > -40  # threshold_db\n        \n        # Extract features\n        spectral_centroid = np.mean(librosa.feature.spectral_centroid(\n            S=power_spectrogram, sr=sr), axis=1)\n        spectral_rolloff = np.mean(librosa.feature.spectral_rolloff(\n            S=power_spectrogram, sr=sr), axis=1)\n        zero_crossing_rate = np.mean(librosa.feature.zero_crossing_rate(\n            y_percussive), axis=1)\n        \n        # Create feature vector\n        features = np.concatenate([\n            spectral_centroid, spectral_rolloff, zero_crossing_rate\n        ])\n        \n        return filename, features\n    except Exception as e:\n        print(f\"Error processing file {filename}: {str(e)}\")\n        return None\n\ndef process_files_parallel(files, max_workers=2):\n    \"\"\"Process files in parallel with better error handling\"\"\"\n    data_features = []\n    data_filename = []\n    \n    # Process in parallel - optimized for Kaggle\n    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:\n        results = list(tqdm(\n            executor.map(process_file, files),\n            total=len(files),\n            desc='Processing test files'\n        ))\n    \n    # Filter out None results from failures\n    results = [r for r in results if r is not None]\n    \n    # Unpack results\n    if results:\n        filenames, features = zip(*results)\n        data_filename.extend(filenames)\n        data_features.extend(features)\n    \n    return np.array(data_filename), np.array(data_features)\n\n# Process test files in chunks\nmax_workers = min(2, os.cpu_count() or 1)\nprint(f\"Processing test files with {max_workers} workers\")\n\nn = len(test_soundscapes)\nfiles1 = test_soundscapes[:n//3]\nfiles2 = test_soundscapes[n//3:2*n//3]\nfiles3 = test_soundscapes[2*n//3:]\n\n# Process each chunk in parallel\ntest_data_filename1, test_data_features1 = process_files_parallel(files1, max_workers=max_workers)\ntest_data_filename2, test_data_features2 = process_files_parallel(files2, max_workers=max_workers)\ntest_data_filename3, test_data_features3 = process_files_parallel(files3, max_workers=max_workers)\n\n# Combine results\ntest_data_filename = np.concatenate([test_data_filename1, test_data_filename2, test_data_filename3])\ntest_data_features = np.concatenate([test_data_features1, test_data_features2, test_data_features3])\n\nprint(f\"Combined test data: {len(test_data_filename)} files processed\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n# Encode labels (ensure LabelEncoder is imported)\nfrom sklearn.metrics import log_loss\n\n\n# Split the data into training and validation sets\nX_train, X_val, y_train, y_val = train_test_split(X_train_val, y_train_val, test_size=0.25, random_state=42, stratify=y_train_val)\n\nprint(f\"Shapes after split: X_train={X_train.shape}, X_val={X_val.shape}, test_data_features={test_data_features.shape}\")\nprint(f\"Shapes after split: y_train={y_train.shape}, y_val={y_val.shape}, y_test_final={y_test_final.shape}\")\n\n# Encode labels\nlabel_encoder = LabelEncoder()\ny_train_encoded = label_encoder.fit_transform(y_train)\nbird_labels = list(label_encoder.classes_)\n\n# Define the full set of encoded labels for log_loss\nnum_classes = len(bird_labels)\nfull_labels = list(range(num_classes))  # Corresponds to encoded labels [0, 1, ..., num_classes-1]\nprint(f\"Number of classes in bird_labels: {len(bird_labels)}\")\n\n# Encode validation labels\ny_val_encoded = label_encoder.transform(y_val)\n\n# Apply the same transformations to test data that were used on training data\n# Check if test_data_features is empty before transformation\ntest_vectors = None\nif test_data_features.shape[0] > 0:\n    print(\"Processing test data features...\")\n    if pca is not None:\n        # Apply the same scaling (scaler should be fitted on X_train or X_train_val)\n        test_data_scaled = scaler.transform(test_data_features)\n        # Apply the same PCA transformation (pca should be fitted on scaled X_train or X_train_val)\n        test_vectors = pca.transform(test_data_scaled)\n    else:\n        # Only scale without PCA (scaler should be fitted on X_train or X_train_val)\n        test_vectors = scaler.transform(test_data_features)\nelse:\n    print(\"WARNING: test_data_features is empty (shape: {}). Skipping test data transformation and predictions.\".format(test_data_features.shape))\n\n# Train LightGBM model\nprint(\"\\nTraining LightGBM model...\")\nlgb_params = {\n    'objective': 'multiclass',\n    'num_class': len(bird_labels),\n    'metric': 'multi_logloss',\n    'learning_rate': 0.05,\n    'num_leaves': 31,\n    'feature_fraction': 0.8,\n    'bagging_fraction': 0.8,\n    'bagging_freq': 1,\n    'verbose': -1,\n    'n_jobs': -1,  # Use all available cores\n    'seed': 42  # for reproducibility\n}\n\nlgb_train = lgb.Dataset(X_train, y_train_encoded)\nlgb_val = lgb.Dataset(X_val, y_val_encoded, reference=lgb_train)\n\nlgb_model = lgb.train(\n    lgb_params,\n    lgb_train,\n    num_boost_round=5000,  # Increased rounds, relying on early stopping\n    valid_sets=[lgb_train, lgb_val],\n    callbacks=[lgb.early_stopping(stopping_rounds=100, verbose=False)]  # Increased stopping rounds, make verbose=False cleaner\n)\n\nprint(f\"LightGBM best iteration: {lgb_model.best_iteration}\")\n\n# Train XGBoost model\nprint(\"\\nTraining XGBoost model...\")\nxgb_model = XGBClassifier(\n    objective='multi:softprob',\n    eval_metric='mlogloss',\n    learning_rate=0.05,\n    max_depth=6,\n    subsample=0.8,\n    colsample_bytree=0.8,\n    n_estimators=5000,  # Increased estimators, relying on early stopping\n    use_label_encoder=False,  # Still include for potential older versions\n    verbosity=1,\n    n_jobs=-1,  # Use all available cores\n    random_state=42  # for reproducibility\n)\n\n# Using y_val_encoded for evaluation set\nxgb_model.fit(\n    X_train, y_train_encoded,\n    eval_set=[(X_val, y_val_encoded)],\n    early_stopping_rounds=100,  # Increased early stopping rounds\n    verbose=False  # Make verbose=False for cleaner output during fit\n)\n\nprint(f\"XGBoost best iteration: {xgb_model.best_iteration}\")\n\n# Get predictions on validation set\nlgb_val_pred = lgb_model.predict(X_val, num_iteration=lgb_model.best_iteration)\nxgb_val_pred = xgb_model.predict_proba(X_val)\n\n# Debugging shapes and unique classes before log_loss\nprint(f\"Number of unique classes in y_train_encoded: {len(np.unique(y_train_encoded))}\")\nprint(f\"Number of unique classes in y_val_encoded: {len(np.unique(y_val_encoded))}\")\nprint(f\"Shape of lgb_val_pred: {lgb_val_pred.shape}\")\nprint(f\"Shape of xgb_val_pred: {xgb_val_pred.shape}\")\nprint(f\"Length of full_labels: {len(full_labels)}\")\n\n# Calculate and print validation metrics with labels parameter\nprint(f\"\\nLightGBM validation log loss: {log_loss(y_val_encoded, lgb_val_pred, labels=full_labels):.4f}\")\nprint(f\"XGBoost validation log loss: {log_loss(y_val_encoded, xgb_val_pred, labels=full_labels):.4f}\")\n\n# Get predictions on test set only if test_vectors is not None (i.e., test_data_features was not empty)\nlgb_probabilities = None\nxgb_probabilities = None\nblended_probabilities = None\nif test_vectors is not None and test_vectors.shape[0] > 0:\n    print(\"Generating predictions for test set...\")\n    lgb_probabilities = lgb_model.predict(test_vectors, num_iteration=lgb_model.best_iteration)\n    xgb_probabilities = xgb_model.predict_proba(test_vectors)  # predict_proba automatically uses best iteration with early stopping\n    # Blend predictions (simple average)\n    blended_probabilities = (lgb_probabilities + xgb_probabilities) / 2\nelse:\n    print(\"WARNING: No test data available for predictions. Skipping test set predictions and submission creation.\")\n\n# --------------------- STEP 4: CREATE SUBMISSION ---------------------\n# Only proceed with submission if test data is available\nif test_vectors is not None and test_vectors.shape[0] > 0:\n    # Function to create row_id from filenames (ensure test_data_filename is defined)\n    def create_row_id(filename):\n        parts = filename.split('/')[-1].split('.')[0].split('_')\n        # Handle potential variations or ensure consistent format\n        if len(parts) >= 2:\n            return f\"{parts[0]}_{parts[1]}\"\n        return filename  # Fallback (less likely needed for competition data)\n\n    # Create row_ids from the test filenames\n    # Assuming test_data_filename is a list or pandas Series matching test_data_features rows\n    test_df = pd.DataFrame({'filename': test_data_filename})\n    test_df['row_id'] = test_df['filename'].apply(create_row_id)\n\n    # Create a new DataFrame with just row_ids\n    submission = pd.DataFrame({'row_id': test_df['row_id']})\n\n    # Add probability columns for each bird class\n    for i, bird_label in enumerate(bird_labels):\n        submission[bird_label] = blended_probabilities[:, i]\n\n    # Display the head of the submission DataFrame for verification\n    print(\"\\nBlended Submission Head:\")\n    print(submission.head())\n\n    # Save the submission to a CSV file\n    submission.to_csv('submission.csv', index=False)\n    print(\"\\nSubmission file 'blended_submission.csv' has been created.\")\n\n    # Also save individual model predictions (optional)\n    lgb_submission = pd.DataFrame({'row_id': test_df['row_id']})\n    xgb_submission = pd.DataFrame({'row_id': test_df['row_id']})\n\n    for i, bird_label in enumerate(bird_labels):\n        lgb_submission[bird_label] = lgb_probabilities[:, i]\n        xgb_submission[bird_label] = xgb_probabilities[:, i]\n\n    #lgb_submission.to_csv('submission.csv', index=False)\n    #xgb_submission.to_csv('submission.csv', index=False)\n    print(\"Individual model submissions also saved.\")\nelse:\n    print(\"WARNING: Submission files not created due to empty test data.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}