{"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"},{"sourceId":417053,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":340243,"modelId":361359},{"sourceId":418937,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":341586,"modelId":362900},{"sourceId":423270,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":344934,"modelId":366226}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport librosa\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\nimport os\n\n# CNN Model (same as training)\nclass AudioCNN(nn.Module):\n    def __init__(self, num_classes):\n        super(AudioCNN, self).__init__()\n        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)\n        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)\n        self.conv3 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)\n        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)\n        self.relu = nn.ReLU()\n        self.dropout = nn.Dropout(0.25)\n        \n        self._to_linear = None\n        self._get_conv_output_size()\n        \n        self.fc1 = nn.Linear(self._to_linear, 128)\n        self.fc2 = nn.Linear(128, num_classes)\n\n    def _get_conv_output_size(self):\n        with torch.no_grad():\n            x = torch.zeros(1, 1, 128, 126)  # Input shape [batch, channels, n_mels, time]\n            x = self.pool(self.relu(self.conv1(x)))\n            x = self.pool(self.relu(self.conv2(x)))\n            x = self.pool(self.relu(self.conv3(x)))\n            self._to_linear = x.numel() // x.shape[0]\n\n    def forward(self, x):\n        x = self.pool(self.relu(self.conv1(x)))\n        x = self.pool(self.relu(self.conv2(x)))\n        x = self.pool(self.relu(self.conv3(x)))\n        x = x.view(x.size(0), -1)  # Flatten\n        x = self.dropout(self.relu(self.fc1(x)))\n        x = self.fc2(x)\n        return x\n\ndef process_audio_segment(audio_data, sample_rate, segment_duration, start_time):\n    \"\"\"Extract a segment from audio and process it into a mel-spectrogram.\"\"\"\n    try:\n        start_samples = int(start_time * sample_rate)\n        end_samples = start_samples + int(segment_duration * sample_rate)\n        \n        if end_samples > len(audio_data):\n            audio_data = np.pad(audio_data, (0, end_samples - len(audio_data)), mode='constant')\n        \n        segment = audio_data[start_samples:end_samples]\n        \n        # Compute mel-spectrogram\n        mel_spec = librosa.feature.melspectrogram(\n            y=segment, sr=sample_rate, n_mels=128, n_fft=2048, hop_length=512\n        )\n        mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n        \n        # Normalize\n        mel_spec_db = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)\n        \n        return mel_spec_db.astype(np.float32)\n    except Exception as e:\n        print(f\"Error processing audio segment: {e}\")\n        return None\n\ndef predict_on_audio(audio_path, model, device, sample_rate, segment_duration, species_ids):\n    \"\"\"Run inference on a single audio file, segmenting into 5-second clips.\"\"\"\n    row_ids = []\n    predictions = []\n    soundscape_id = Path(audio_path).stem\n    \n    try:\n        # Load audio\n        audio_data, sr = librosa.load(audio_path, sr=sample_rate)\n        total_duration = len(audio_data) / sr\n        total_segments = int(np.ceil(total_duration / segment_duration))\n        \n        model.eval()\n        with torch.no_grad():\n            for seg_idx in range(total_segments):\n                start_time = seg_idx * segment_duration\n                row_id = f\"{soundscape_id}_{(seg_idx + 1) * segment_duration}\"\n                row_ids.append(row_id)\n                \n                # Process segment\n                mel_spec = process_audio_segment(audio_data, sample_rate, segment_duration, start_time)\n                if mel_spec is None:\n                    # Fallback: uniform probabilities\n                    predictions.append(np.full(len(species_ids), 1.0 / len(species_ids)))\n                    print(f\"Warning: Using uniform probabilities for {row_id} due to processing error\")\n                    continue\n                \n                mel_spec_tensor = torch.tensor(mel_spec, dtype=torch.float32).unsqueeze(0).unsqueeze(0).to(device)\n                \n                # Predict\n                logits = model(mel_spec_tensor)\n                probs = F.softmax(logits, dim=1).cpu().numpy().squeeze()\n                predictions.append(probs)\n        \n        return row_ids, predictions\n    except Exception as e:\n        print(f\"Error processing {audio_path}: {e}\")\n        # Fallback: generate row_ids with uniform probabilities\n        total_segments = 3  # Assume 3 segments (5s, 10s, 15s) as in sample_submission.csv\n        for seg_idx in range(total_segments):\n            row_id = f\"{soundscape_id}_{(seg_idx + 1) * segment_duration}\"\n            row_ids.append(row_id)\n            predictions.append(np.full(len(species_ids), 1.0 / len(species_ids)))\n        return row_ids, predictions\n\ndef create_submission(row_ids, predictions, species_ids, sample_submission_path, output_path):\n    \"\"\"Create submission DataFrame and save to CSV.\"\"\"\n    print(\"Creating submission dataframe...\")\n    \n    try:\n        # Create submission dictionary\n        submission_dict = {'row_id': row_ids}\n        for i, species in enumerate(species_ids):\n            submission_dict[species] = [pred[i] for pred in predictions]\n        \n        # Create DataFrame\n        submission_df = pd.DataFrame(submission_dict)\n        \n        # Load sample submission to ensure correct column order\n        sample_sub = pd.read_csv(sample_submission_path)\n        expected_cols = sample_sub.columns\n        \n        # Check for missing columns\n        missing_cols = set(expected_cols) - 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                if col != 'row_id':\n                    submission_df[col] = 0.0\n        \n        # Ensure correct column order\n        submission_df = submission_df[expected_cols]\n        \n        # Ensure all expected row_ids are present\n        expected_row_ids = set(sample_sub['row_id'])\n        missing_row_ids = expected_row_ids - set(submission_df['row_id'])\n        if missing_row_ids:\n            print(f\"Warning: Missing {len(missing_row_ids)} row_ids in submission\")\n            for row_id in missing_row_ids:\n                # Append missing row_id with uniform probabilities\n                new_row = {'row_id': row_id}\n                for col in expected_cols[1:]:  # Exclude 'row_id'\n                    new_row[col] = 1.0 / len(species_ids)\n                submission_df = pd.concat([submission_df, pd.DataFrame([new_row])], ignore_index=True)\n        \n        # Save to CSV\n        submission_df.to_csv(output_path, index=False)\n        print(f\"Submission saved to {output_path}\")\n    except Exception as e:\n        print(f\"Error creating submission: {e}\")\n        # Fallback: create a submission with uniform probabilities\n        submission_df = pd.read_csv(sample_submission_path)\n        for col in submission_df.columns[1:]:\n            submission_df[col] = 1.0 / len(species_ids)\n        submission_df.to_csv(output_path, index=False)\n        print(f\"Fallback submission saved to {output_path}\")\n\ndef main():\n    # Parameters\n    test_dir = '/kaggle/input/birdclef-2025/test_soundscapes'  # Kaggle test path\n    model_path = '/kaggle/input/bird-seeker-3/pytorch/default/1/audio_cnn_model_finetuned_10.pth'\n    sample_submission_path = '/kaggle/input/birdclef-2025/sample_submission.csv'\n    output_path = '/kaggle/working/submission.csv'  # Kaggle output path\n    sample_rate = 16000\n    segment_duration = 5  # 5-second segments to match row_id format\n    \n    # Load species from sample_submission.csv\n    try:\n        sample_submission = pd.read_csv(sample_submission_path)\n        species_ids = sample_submission.columns[1:].tolist()  # Exclude 'row_id'\n        num_classes = len(species_ids)\n    except Exception as e:\n        print(f\"Error loading sample_submission.csv: {e}\")\n        return\n    \n    # Device configuration\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    if not torch.cuda.is_available():\n        print(\"Warning: CUDA not available. Running on CPU.\")\n    \n    # Initialize and load model\n    try:\n        model = AudioCNN(num_classes=num_classes).to(device)\n        model.load_state_dict(torch.load(model_path, map_location=device))\n    except Exception as e:\n        print(f\"Error loading model from {model_path}: {e}\")\n        # Fallback: create a submission with uniform probabilities\n        submission_df = pd.read_csv(sample_submission_path)\n        for col in submission_df.columns[1:]:\n            submission_df[col] = 1.0 / len(species_ids)\n        submission_df.to_csv(output_path, index=False)\n        print(f\"Fallback submission saved to {output_path}\")\n        return\n    \n    # Find test audio files\n    try:\n        test_files = list(Path(test_dir).glob('*.ogg'))\n         # Run inference\n        all_row_ids = []\n        all_predictions = []\n        for audio_path in tqdm(test_files, desc=\"Processing audio files\"):\n            row_ids, predictions = predict_on_audio(audio_path, model, device, sample_rate, segment_duration, species_ids)\n            all_row_ids.extend(row_ids)\n            all_predictions.extend(predictions)\n    except Exception as e:\n        print(f\"Error accessing test directory {test_dir}: {e}\")\n        return\n    \n   \n    \n    # Create and save submission\n    create_submission(all_row_ids, all_predictions, species_ids, sample_submission_path, output_path)\n\nif __name__ == \"__main__\":\n    main()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-03T10:19:33.297896Z","iopub.execute_input":"2025-06-03T10:19:33.298235Z","iopub.status.idle":"2025-06-03T10:19:40.770950Z","shell.execute_reply.started":"2025-06-03T10:19:33.298199Z","shell.execute_reply":"2025-06-03T10:19:40.769495Z"}},"outputs":[],"execution_count":null}]}