{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-24T04:48:46.271263Z","iopub.execute_input":"2025-04-24T04:48:46.271537Z","execution_failed":"2025-04-24T04:50:22.809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# PANNs-Based Species Classifier for Eco-Acoustic Detection (Kaggle Starter Notebook)\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.preprocessing import MultiLabelBinarizer\nfrom tqdm import tqdm\nimport joblib\n\n\nAUDIO_DIR = '/kaggle/input/birdclef-2025/train_audio'\nTEST_DIR = '/kaggle/input/birdclef-2025/test_soundscapes'\nTRAIN_CSV = '/kaggle/input/birdclef-2025/train.csv'\nSAMPLE_SUB = '/kaggle/input/birdclef-2025/sample_submission.csv'\nSAMPLE_RATE = 32000\nEMBED_DIM = 2048\nNUM_CLASSES = 206\nBATCH_SIZE = 32\n\n# ==== Load PANNs Model ====\nclass Cnn14(nn.Module):\n    def __init__(self, sample_rate, window_size, hop_size, mel_bins, fmin, fmax, classes_num):\n        super().__init__()\n        self.embedding_layer = nn.Identity()  # Replace with pretrained model\n\n    def forward(self, x):\n        return {'embedding': torch.randn(x.size(0), EMBED_DIM)}\n\npanns_model = Cnn14(\n    sample_rate=SAMPLE_RATE,\n    window_size=1024,\n    hop_size=320,\n    mel_bins=64,\n    fmin=50,\n    fmax=14000,\n    classes_num=527\n)\npanns_model.eval()\n\n# ==== Dataset ====\nclass BirdSoundDataset(Dataset):\n    def __init__(self, df, audio_dir, label_binarizer):\n        self.df = df\n        self.audio_dir = audio_dir\n        self.label_binarizer = label_binarizer\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        filepath = os.path.join(self.audio_dir, row['filename'])\n        audio, _ = librosa.load(filepath, sr=SAMPLE_RATE)\n        if len(audio) > SAMPLE_RATE * 5:\n            audio = audio[:SAMPLE_RATE * 5]\n        elif len(audio) < SAMPLE_RATE * 5:\n            audio = np.pad(audio, (0, SAMPLE_RATE * 5 - len(audio)))\n\n        waveform = torch.tensor(audio).unsqueeze(0)\n        with torch.no_grad():\n            embed = panns_model(waveform)['embedding'].squeeze()\n\n        labels = self.label_binarizer.transform([[row['primary_label']]])[0]\n        return embed, torch.tensor(labels, dtype=torch.float32)\n\n# ==== Data Preprocessing ====\ndf = pd.read_csv(TRAIN_CSV)\nmlb = MultiLabelBinarizer()\nmlb.fit([[label] for label in df['primary_label'].unique()])\n\ntrain_dataset = BirdSoundDataset(df, AUDIO_DIR, mlb)\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)\n\n# ==== Classifier ====\nclass SpeciesClassifier(nn.Module):\n    def __init__(self, in_dim=EMBED_DIM, out_dim=NUM_CLASSES):\n        super().__init__()\n        self.fc = nn.Sequential(\n            nn.Linear(in_dim, 1024),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(1024, out_dim),\n            nn.Sigmoid()\n        )\n\n    def forward(self, x):\n        return self.fc(x)\n\nmodel = SpeciesClassifier()\ncriterion = nn.BCELoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n\n# ==== Training Loop ====\nepochs = 5\nfor epoch in range(epochs):\n    model.train()\n    total_loss = 0\n    for x_batch, y_batch in tqdm(train_loader):\n        optimizer.zero_grad()\n        outputs = model(x_batch)\n        loss = criterion(outputs, y_batch)\n        loss.backward()\n        optimizer.step()\n        total_loss += loss.item()\n\n    print(f\"Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}\")\n\n# ==== Save Model and Binarizer ====\ntorch.save(model.state_dict(), 'species_classifier.pth')\njoblib.dump(mlb, 'label_binarizer.pkl')\n\n# ==== Inference & Sample Submission Generation ====\nsample_sub = pd.read_csv(SAMPLE_SUB)\nspecies_ids = sample_sub.columns.tolist()[1:]  # Exclude 'row_id' column\n\npredictions = []\nrow_ids = []\n\nfor filename in sorted(os.listdir(TEST_DIR)):\n    if not filename.endswith('.ogg'):\n        continue\n\n    path = os.path.join(TEST_DIR, filename)\n    audio, _ = librosa.load(path, sr=SAMPLE_RATE)\n    duration = librosa.get_duration(y=audio, sr=SAMPLE_RATE)\n\n    # Loop through the audio in 5-second segments\n    for start in range(0, int(duration), 5):\n        end = start + 5\n        if end > duration:\n            break\n\n        segment = audio[start * SAMPLE_RATE:end * SAMPLE_RATE]\n        \n        # If the segment is smaller than 5 seconds, pad it\n        if len(segment) < 5 * SAMPLE_RATE:\n            segment = np.pad(segment, (0, 5 * SAMPLE_RATE - len(segment)))\n\n        # Create a tensor for the segment and generate embedding\n        waveform = torch.tensor(segment).unsqueeze(0)\n        with torch.no_grad():\n            embed = panns_model(waveform)['embedding']\n            pred = model(embed).squeeze().numpy()  # Predicted probabilities for the species\n\n        # Create row_id based on filename and end time\n        row_id = f\"soundscape_{filename[:-4]}_{end}\"  # e.g., soundscape_12345_20\n        \n        # Append the prediction and row_id\n        predictions.append(pred)\n        row_ids.append(row_id)\n\n# Create the final submission DataFrame\nsubmission_df = pd.DataFrame(predictions, columns=species_ids)\nsubmission_df.insert(0, 'row_id', row_ids)  # Insert row_id at the beginning\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"✅ Sample submission generated: submission.csv\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"> > ","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}