{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"},{"sourceId":11858784,"sourceType":"datasetVersion","datasetId":7451557}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, accuracy_score, f1_score\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nimport os\nimport glob\nimport joblib\nimport time\nimport sys\nfrom datetime import datetime\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential, load_model, Model\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization, Add, Activation, Input\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint\n\ndef print_step(message):\n    current_time = datetime.now().strftime(\"%H:%M:%S\")\n    print(f\"\\n{'='*50}\")\n    print(f\"[{current_time}] {message}\")\n    print(f\"{'='*50}\")\n    sys.stdout.flush()\n\ndef print_progress(current, total, prefix=''):\n    bar_length = 50\n    filled_length = int(round(bar_length * current / float(total)))\n    bar = '█' * filled_length + '-' * (bar_length - filled_length)\n    current_time = datetime.now().strftime(\"%H:%M:%S\")\n    print(f'\\r[{current_time}] {prefix} |{bar}| {current}/{total} ({(current/total)*100:.1f}%)', end='')\n    sys.stdout.flush()\n    if current == total:\n        print()\n\nprint_step(\"Starting quick test run...\")\n\n# Path to the combined dataset\noutput_combined_npy_path = \"/kaggle/input/128128-birdclef-2025-dataset/data/combined_melspectrograms_dataset_fixed_shape.npy\"\nif not os.path.exists(output_combined_npy_path):\n    raise FileNotFoundError(f\"Combined dataset not found at: {output_combined_npy_path}\")\n\nprint_step(\"Loading combined dataset...\")\ntry:\n    combined_dataset = np.load(output_combined_npy_path)\n    print(f\"Successfully loaded dataset with shape: {combined_dataset.shape}\")\nexcept Exception as e:\n    print(f\"Error loading combined dataset: {str(e)}\")\n    sys.exit(1)\n\n# Directory containing individual mel spectrograms\nnpy_dir = \"/kaggle/input/128128-birdclef-2025-dataset/train_melspectrograms_npy_fixed_shape_128\"\nif not os.path.exists(npy_dir):\n    print(f\"Warning: Individual spectrograms directory not found at: {npy_dir}\")\n    # Since we have combined dataset, we'll need to get labels differently\n    npy_files = []\nelse:\n    print_step(\"Gathering .npy files...\")\n    npy_files = sorted(glob.glob(os.path.join(npy_dir, \"*.npy\")))\n    print(f\"Found {len(npy_files)} .npy files\")\n\n# Original training audio directory\naudio_dir = \"/kaggle/input/birdclef-2025/train_audio\"\nif not os.path.exists(audio_dir):\n    raise FileNotFoundError(f\"Audio directory not found at: {audio_dir}\")\n\nprint_step(\"Scanning audio files...\")\nfile_list = []\nspecies_labels = []\nspecies_count = 0\ntotal_dirs = len([d for d in os.listdir(audio_dir) if os.path.isdir(os.path.join(audio_dir, d))])\n\nfor root, _, files in os.walk(audio_dir):\n    if os.path.basename(root) != \"train_audio\":  # Just compare the basename\n        species_count += 1\n        print_progress(species_count, total_dirs, \"Scanning species directories\")\n        for fname in files:\n            if fname.lower().endswith(\".ogg\"):\n                path = os.path.join(root, fname)\n                file_list.append(path)\n                species_labels.append(os.path.basename(os.path.dirname(path)))\n\nprint(f\"\\nFound {len(file_list)} audio files across {total_dirs} species\")\n\n# Extract labels from filenames\ndef extract_labels(npy_files):\n    print_step(\"Extracting labels...\")\n    labels = []\n    total_files = len(npy_files)\n\n    if total_files == 0:\n        raise ValueError(\"No files provided for label extraction\")\n    \n    for i, npy_file in enumerate(npy_files):\n        if i % 10 == 0:\n            print_progress(i, total_files, \"Processing files\")\n        \n        base_name = os.path.basename(npy_file)\n        audio_name = base_name.split('_')[0] + '.ogg'\n        label_found = False\n        for j, file_path in enumerate(file_list):\n            if os.path.basename(file_path) == audio_name:\n                labels.append(species_labels[j])\n                label_found = True\n                break\n        if not label_found:\n            print(f\"\\nWarning: No label found for {audio_name}\")\n            continue\n    \n    print_progress(total_files, total_files, \"Processing files\")\n\n    if len(labels) == 0:\n        raise ValueError(\"No labels were extracted from the provided files\")\n\n    return labels\n\nlabels = extract_labels(npy_files)\n\nif len(labels) != len(combined_dataset):\n    raise ValueError(f\"Number of labels ({len(labels)}) does not match number of spectrograms ({len(combined_dataset)})\")\n\nprint_step(\"Converting labels to numerical values...\")\nlabel_encoder = LabelEncoder()\ny = label_encoder.fit_transform(labels)\nprint(f\"Converted {len(np.unique(y))} unique labels\")\n\nprint_step(\"Reshaping and splitting data...\")\n# Reshape data for CNN (samples, height, width, channels)\nX = combined_dataset.reshape(combined_dataset.shape[0], 128, 128, 1)\nprint(\"Reshaped spectrograms for CNN training\")\n\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\nprint(f\"Training set: {X_train.shape}\")\nprint(f\"Test set: {X_test.shape}\")\nprint(f\"Number of classes: {len(label_encoder.classes_)}\")\n\n# Optionally, use a subset of the data for faster experimentation\n# Uncomment the following lines to use only a portion of the data (e.g., 20%)\n# subset_size = int(0.2 * X.shape[0])\n# X = X[:subset_size]\n# y = y[:subset_size]\n\ndef residual_block(x, filters, kernel_size=3, stride=1, use_bias=True, name=None):\n    \"\"\"A residual block with two 3x3 convolutions and a skip connection.\"\"\"\n    shortcut = x\n    \n    # First convolution\n    x = Conv2D(filters, kernel_size, strides=stride, padding='same', use_bias=use_bias, name=f'{name}_conv1')(x)\n    x = BatchNormalization(name=f'{name}_bn1')(x)\n    x = Activation('relu', name=f'{name}_relu1')(x)\n    \n    # Second convolution\n    x = Conv2D(filters, kernel_size, strides=1, padding='same', use_bias=use_bias, name=f'{name}_conv2')(x)\n    x = BatchNormalization(name=f'{name}_bn2')(x)\n    \n    # Skip connection\n    if stride != 1 or shortcut.shape[-1] != filters:\n        shortcut = Conv2D(filters, 1, strides=stride, padding='same', use_bias=use_bias, name=f'{name}_shortcut')(shortcut)\n        shortcut = BatchNormalization(name=f'{name}_shortcut_bn')(shortcut)\n    \n    # Add skip connection\n    x = Add(name=f'{name}_add')([x, shortcut])\n    x = Activation('relu', name=f'{name}_relu2')(x)\n    \n    return x\n\ndef create_resnet_model(input_shape, num_classes):\n    \"\"\"Create a ResNet model for bird sound classification.\"\"\"\n    inputs = Input(shape=input_shape)\n    \n    # Initial convolution\n    x = Conv2D(64, 7, strides=2, padding='same', use_bias=True, name='conv1')(inputs)\n    x = BatchNormalization(name='bn1')(x)\n    x = Activation('relu', name='relu1')(x)\n    x = MaxPooling2D(3, strides=2, padding='same', name='pool1')(x)\n    \n    # Residual blocks\n    x = residual_block(x, 64, name='block1_1')\n    x = residual_block(x, 64, name='block1_2')\n    \n    x = residual_block(x, 128, stride=2, name='block2_1')\n    x = residual_block(x, 128, name='block2_2')\n    \n    x = residual_block(x, 256, stride=2, name='block3_1')\n    x = residual_block(x, 256, name='block3_2')\n    \n    x = residual_block(x, 512, stride=2, name='block4_1')\n    x = residual_block(x, 512, name='block4_2')\n    \n    # Global average pooling and dense layers\n    x = tf.keras.layers.GlobalAveragePooling2D(name='avg_pool')(x)\n    x = Dense(512, activation='relu', name='fc1')(x)\n    x = Dropout(0.5, name='dropout1')(x)\n    x = Dense(256, activation='relu', name='fc2')(x)\n    x = Dropout(0.3, name='dropout2')(x)\n    outputs = Dense(num_classes, activation='softmax', name='fc3')(x)\n    \n    model = Model(inputs, outputs, name='resnet')\n    \n    model.compile(\n        optimizer='adam',\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    return model\n\n# Define models and their parameters\nmodels = {\n    'ResNet': {\n        'model': create_resnet_model((128, 128, 1), len(label_encoder.classes_)),\n        'params': {\n            'batch_size': [32],\n            'epochs': [30],\n            'patience': [5]\n        }\n    }\n}\n\ndef train_and_evaluate(model_name, model_dict, X_train, y_train, X_test, y_test):\n    print(f\"\\n{'='*50}\")\n    print(f\"Training {model_name}\")\n    print(f\"{'='*50}\")\n\n    model = model_dict['model']\n    params = model_dict['params']\n    \n    # Create callbacks\n    callbacks = [\n        EarlyStopping(\n            monitor='val_loss',\n            patience=params['patience'][0],\n            restore_best_weights=True\n        ),\n        ModelCheckpoint(\n            'best_model.h5',\n            monitor='val_accuracy',\n            save_best_only=True\n        )\n    ]\n    \n    start_time = time.time()\n    \n    # Train the model\n    history = model.fit(\n        X_train, y_train,\n        batch_size=params['batch_size'][0],\n        epochs=params['epochs'][0],\n        validation_split=0.2,\n        callbacks=callbacks,\n        verbose=1\n    )\n    \n    training_time = time.time() - start_time\n    \n    # Evaluate the model\n    y_pred = np.argmax(model.predict(X_test), axis=1)\n    accuracy = accuracy_score(y_test, y_pred)\n    f1 = f1_score(y_test, y_pred, average='macro')\n    \n    print(f\"\\nTraining completed in {training_time:.2f} seconds\")\n    print(f\"Test Accuracy: {accuracy:.3f}\")\n    print(f\"Test F1-score: {f1:.3f}\")\n    \n    return {\n        'model_name': model_name,\n        'best_params': params,\n        'best_score': max(history.history['val_accuracy']),\n        'test_accuracy': accuracy,\n        'test_f1': f1,\n        'training_time': training_time,\n        'best_model': model\n    }\n\nresults = []\ntotal_start_time = time.time()\n\nfor model_name, model_dict in models.items():\n    result = train_and_evaluate(model_name, model_dict, X_train, y_train, X_test, y_test)\n    results.append(result)\n\ntotal_time = time.time() - total_start_time\n\nprint(\"\\n\\nModel Comparison Summary:\")\nprint(\"=\"*100)\nprint(f\"{'Model':<25} {'Best Params':<35} {'CV Score':<12} {'Test Acc':<10} {'Test F1':<10} {'Time (s)':<10}\")\nprint(\"-\"*100)\nfor result in results:\n    print(f\"{result['model_name']:<25} {str(result['best_params'])[:35]:<35} \"\n          f\"{result['best_score']:.3f}        {result['test_accuracy']:.3f}     \"\n          f\"{result['test_f1']:.3f}     {result['training_time']:.1f}\")\nprint(f\"\\nTotal training time for all models: {total_time:.2f} seconds\")\n\nbest_model_idx = np.argmax([r['test_f1'] for r in results])\nbest_model = results[best_model_idx]\nprint(f\"\\nBest performing model: {best_model['model_name']}\")\nprint(f\"Best F1-score: {best_model['test_f1']:.3f}\")\n\n# Save the best model\nmodel_path = '/kaggle/working/best_bird_classifier_model.h5'\nos.makedirs(os.path.dirname(model_path), exist_ok=True)\nbest_model['best_model'].save(model_path)\n\nprint(f\"\\nBest model saved to: {model_path}\")\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n####Submission Part\nimport tensorflow as tf\nimport pandas as pd\nimport numpy as np\nimport os\nimport librosa\nfrom tensorflow.keras.models import load_model\n\n# --- Load best model and label encoder ---\nmodel_path = '/kaggle/working/best_bird_classifier_model.h5'\nbest_model = load_model(model_path)\nclass_labels = list(label_encoder.classes_)\n\n# --- Define test audio directory and file list ---\ntest_audio_dir = '/kaggle/input/birdclef-2025/test_soundscapes'\ndebug = False\n\n# Check if the test directory exists\nif not os.path.exists(test_audio_dir):\n    test_audio_dir = '/kaggle/input/birdclef-2025/test_soundscapes'  # Try local path\n\n# Get file list and check if we need debug mode\nfile_list = []\nif os.path.exists(test_audio_dir):\n    file_list = [f for f in sorted(os.listdir(test_audio_dir)) if f.lower().endswith('.ogg')]\n    # Filter out non-audio files (like readme.txt)\n    file_list = [file.split('.')[0] for file in file_list]\n\n# If no OGG files or only readme.txt, switch to debug mode\nif len(file_list) == 0:\n    debug = True\n    print(\"No test files found. Switching to debug mode with train_soundscapes.\")\n    test_audio_dir = '/kaggle/input/birdclef-2025/train_soundscapes'\n    if not os.path.exists(test_audio_dir):\n        test_audio_dir = '/kaggle/input/birdclef-2025/train_soundscapes'\n    \n    file_list = [f for f in sorted(os.listdir(test_audio_dir)) if f.lower().endswith('.ogg')]\n    file_list = [file.split('.')[0] for file in file_list]\n    # Limit files in debug mode to make testing faster\n    file_list = file_list[:5]  # Use only 5 files for debugging\n\nprint('Debug mode:', debug)\nprint('Test directory:', test_audio_dir)\nprint('Number of test soundscapes:', len(file_list))\n\n# --- Helper: Convert audio to mel spectrogram ---\ndef audio_to_melspectrogram(file_path, offset=0, duration=5.0):\n    y, sr = librosa.load(file_path, sr=32000, offset=offset, duration=duration)\n    S = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=1024, hop_length=512, \n                                     n_mels=128, fmin=50, fmax=14000)\n    S_DB = librosa.power_to_db(S, ref=np.max)\n    # Crop/pad to (128, 128)\n    current_frames = S_DB.shape[1]\n    target_frames = 128\n    if current_frames > target_frames:\n        start_frame = (current_frames - target_frames) // 2\n        S_DB = S_DB[:, start_frame:start_frame+target_frames]\n    elif current_frames < target_frames:\n        S_DB = np.pad(S_DB, ((0, 0), (0, target_frames - current_frames)), mode='constant')\n    return S_DB\n\n# --- Prediction loop ---\npred = {'row_id': []}\nfor species_code in class_labels:\n    pred[species_code] = []\n\nfor afile in file_list:\n    path = os.path.join(test_audio_dir, afile + '.ogg')\n    print(f\"Processing {afile}...\")  # Add progress indicator\n    \n    # For each 5s chunk (assuming 60s audio, 12 chunks)\n    for i in range(12):\n        # Get mel spectrogram for the chunk\n        S_DB = audio_to_melspectrogram(path, offset=i*5, duration=5.0)\n        \n        # Reshape for CNN input (add batch and channel dimensions)\n        X = S_DB.reshape(1, 128, 128, 1)\n        \n        # Predict probabilities\n        probs = best_model.predict(X, verbose=0)[0]\n        \n        row_id = f\"{afile}_{(i+1)*5}\"\n        pred['row_id'].append(row_id)\n        for idx, species_code in enumerate(class_labels):\n            pred[species_code].append(probs[idx])\n\n# --- Create DataFrame and save ---\nresults = pd.DataFrame(pred, columns=['row_id'] + class_labels)\nresults.to_csv(\"submission.csv\", index=False)\nprint(f\"Created initial submission.csv with {len(results)} entries\")\n\n# --- Smoothing (post-processing) ---\nsub = pd.read_csv('submission.csv')\ncols = sub.columns[1:]\ngroups = sub['row_id'].str.rsplit('_', n=1).str[0]\ngroups = groups.values\nfor group in np.unique(groups):\n    sub_group = sub[group == groups]\n    predictions = sub_group[cols].values\n    new_predictions = predictions.copy()\n    for i in range(1, predictions.shape[0]-1):\n        new_predictions[i] = (predictions[i-1] * 0.2) + (predictions[i] * 0.6) + (predictions[i+1] * 0.2)\n    new_predictions[0] = (predictions[0] * 0.8) + (predictions[1] * 0.2)\n    new_predictions[-1] = (predictions[-1] * 0.8) + (predictions[-2] * 0.2)\n    sub_group[cols] = new_predictions\n    sub[group == groups] = sub_group\nsub.to_csv(\"submission.csv\", index=False)\nprint(\"Applied smoothing and saved final submission.csv\")\n\n# --- Display results if in debug mode ---\nif debug:\n    print(\"\\nFirst 5 rows of results:\")\n    print(results.head())  ","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}