{"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,"sourceType":"competition"},{"sourceId":11711640,"sourceType":"datasetVersion","datasetId":7351512},{"sourceId":390483,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":321617,"modelId":342223},{"sourceId":391679,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":322502,"modelId":343194}],"dockerImageVersionId":31012,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport torch\nimport torchaudio\nimport pandas as pd\nimport torchaudio.transforms as T\nfrom tqdm import tqdm\nimport warnings\nwarnings.filterwarnings(\"ignore\")\nimport torchvision.models as models\n\n# --- Class Labels ---\nclass_names = [  # Full 206-class list\n    \"1139490\", \"1192948\", \"1194042\", \"126247\", \"1346504\", \"134933\", \"135045\", \"1462711\", \"1462737\", \"1564122\",\n    \"21038\", \"21116\", \"21211\", \"22333\", \"22973\", \"22976\", \"24272\", \"24292\", \"24322\", \"41663\", \"41778\", \"41970\", \"42007\",\n    \"42087\", \"42113\", \"46010\", \"47067\", \"476537\", \"476538\", \"48124\", \"50186\", \"517119\", \"523060\", \"528041\", \"52884\",\n    \"548639\", \"555086\", \"555142\", \"566513\", \"64862\", \"65336\", \"65344\", \"65349\", \"65373\", \"65419\", \"65448\", \"65547\",\n    \"65962\", \"66016\", \"66531\", \"66578\", \"66893\", \"67082\", \"67252\", \"714022\", \"715170\", \"787625\", \"81930\", \"868458\",\n    \"963335\", \"amakin1\", \"amekes\", \"ampkin1\", \"anhing\", \"babwar\", \"bafibi1\", \"banana\", \"baymac\", \"bbwduc\", \"bicwre1\",\n    \"bkcdon\", \"bkmtou1\", \"blbgra1\", \"blbwre1\", \"blcant4\", \"blchaw1\", \"blcjay1\", \"blctit1\", \"blhpar1\", \"blkvul\",\n    \"bobfly1\", \"bobher1\", \"brtpar1\", \"bubcur1\", \"bubwre1\", \"bucmot3\", \"bugtan\", \"butsal1\", \"cargra1\", \"cattyr\",\n    \"chbant1\", \"chfmac1\", \"cinbec1\", \"cocher1\", \"cocwoo1\", \"colara1\", \"colcha1\", \"compau\", \"compot1\", \"cotfly1\",\n    \"crbtan1\", \"crcwoo1\", \"crebob1\", \"cregua1\", \"creoro1\", \"eardov1\", \"fotfly\", \"gohman1\", \"grasal4\", \"grbhaw1\",\n    \"greani1\", \"greegr\", \"greibi1\", \"grekis\", \"grepot1\", \"gretin1\", \"grnkin\", \"grysee1\", \"gybmar\", \"gycwor1\",\n    \"labter1\", \"laufal1\", \"leagre\", \"linwoo1\", \"littin1\", \"mastit1\", \"neocor\", \"norscr1\", \"olipic1\", \"orcpar\",\n    \"palhor2\", \"paltan1\", \"pavpig2\", \"piepuf1\", \"pirfly1\", \"piwtyr1\", \"plbwoo1\", \"plctan1\", \"plukit1\", \"purgal2\",\n    \"ragmac1\", \"rebbla1\", \"recwoo1\", \"rinkin1\", \"roahaw\", \"rosspo1\", \"royfly1\", \"rtlhum\", \"rubsee1\", \"rufmot1\",\n    \"rugdov\", \"rumfly1\", \"ruther1\", \"rutjac1\", \"rutpuf1\", \"saffin\", \"sahpar1\", \"savhaw1\", \"secfly1\", \"shghum1\",\n    \"shtfly1\", \"smbani\", \"snoegr\", \"sobtyr1\", \"socfly1\", \"solsan\", \"soulap1\", \"spbwoo1\", \"speowl1\", \"spepar1\",\n    \"srwswa1\", \"stbwoo2\", \"strcuc1\", \"strfly1\", \"strher\", \"strowl1\", \"tbsfin1\", \"thbeup1\", \"thlsch3\", \"trokin\",\n    \"tropar\", \"trsowl\", \"turvul\", \"verfly\", \"watjac1\", \"wbwwre1\", \"whbant1\", \"whbman1\", \"whfant1\", \"whmtyr1\",\n    \"whtdov\", \"whttro1\", \"whwswa1\", \"woosto\", \"y00678\", \"yebela1\", \"yebfly1\", \"yebsee1\", \"yecspi2\", \"yectyr1\",\n    \"yehbla2\", \"yehcar1\", \"yelori1\", \"yeofly1\", \"yercac1\", \"ywcpar\"]\n\n# --- Metadata Tensor ---\nmeta_cols = ['latitude', 'longitude', 'rating']\nmeta_df = pd.read_csv(\"/kaggle/input/fullmeta/subset_df_full_meta.csv\")\nmeta_tensor = torch.tensor(meta_df[meta_cols].mean().values, dtype=torch.float32).unsqueeze(0)\n\n# --- Model Definition (EffNetB3 + Metadata) ---\nimport torch.nn as nn\nfrom timm import create_model\n\nclass EffB3ResNetEnsemble(nn.Module):\n    def __init__(self, num_classes, metadata_dim):\n        super().__init__()\n\n        # EfficientNet-B3 backbone\n        self.effb3 = create_model(\"efficientnet_b3\", pretrained=False, in_chans=1, num_classes=0)\n\n        # ResNet-18 backbone with single-channel input and no classification head\n        self.resnet18 = models.resnet18(weights=None)\n        self.resnet18.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)\n        self.resnet18.fc = nn.Identity()\n\n        # Metadata branch\n        self.metadata_head = nn.Sequential(\n            nn.Linear(metadata_dim, 128),\n            nn.ReLU(),\n            nn.BatchNorm1d(128)\n        )\n\n        # Classifier head with smaller hidden layer and stronger dropout\n        self.classifier = nn.Sequential(\n            nn.Linear(1536 + 512 + 128, 256),   # 🔽 smaller head for regularization\n            nn.ReLU(),\n            nn.Dropout(0.5),                    # 🔼 stronger dropout\n            nn.Linear(256, num_classes)        # BCEWithLogits → no sigmoid here\n        )\n\n    def forward(self, x, meta):\n        feat_effb3 = self.effb3(x)\n        feat_resnet = self.resnet18(x)\n        feat_meta = self.metadata_head(meta)\n        combined = torch.cat([feat_effb3, feat_resnet, feat_meta], dim=1)\n        return self.classifier(combined)\n\n# --- Load Model (CPU only) ---\ndevice = torch.device(\"cpu\")\nmodel = EffB3ResNetEnsemble(num_classes=206, metadata_dim=3)\nmodel.load_state_dict(torch.load(\"/kaggle/input/effb3resnet18/pytorch/default/1/best_model_ensemble_effb3_resnet.pth\", map_location=device))\nmodel.eval()\n\n# --- Mel Spectrogram Settings ---\nmel_transform = T.MelSpectrogram(\n    sample_rate=32000,\n    n_fft=1024,\n    hop_length=320,\n    n_mels=128,\n    f_min=20,\n    f_max=16000\n)\ndb_transform = T.AmplitudeToDB()\n\n# --- Load Test Soundscapes ---\ntest_path = \"/kaggle/input/birdclef-2025/test_soundscapes\"\nsoundscape_files = sorted([f for f in os.listdir(test_path) if f.endswith('.ogg')])\nif not soundscape_files:\n    test_path = \"/kaggle/input/birdclef-2025/train_soundscapes\"\n    soundscape_files = sorted([f for f in os.listdir(test_path) if f.endswith('.ogg')])[:3]\n\n# --- Inference ---\nsubmission_rows = []\n\nfor fname in tqdm(soundscape_files):\n    waveform, sr = torchaudio.load(os.path.join(test_path, fname))\n    assert sr == 32000\n\n    for start_sec in range(0, 60, 5):\n        start_sample = start_sec * sr\n        end_sample = (start_sec + 5) * sr\n        if end_sample > waveform.shape[1]:\n            continue\n\n        clip = waveform[:, start_sample:end_sample]\n        mel = mel_transform(clip)\n        mel_db = db_transform(mel)\n        x_img = mel_db.unsqueeze(0)  # [1, 1, 128, T]\n        x_meta = meta_tensor\n\n        with torch.no_grad():\n            logits = model(x_img, x_meta)\n            probs = torch.sigmoid(logits)[0].numpy()\n\n        row_id = f\"{fname.replace('.ogg', '')}_{start_sec + 5}\"\n        row = {\"row_id\": row_id}\n        row.update({class_names[i]: probs[i] for i in range(206)})\n        submission_rows.append(row)\n\n# --- Save Submission ---\nsubmission_df = pd.DataFrame(submission_rows)\nsubmission_df = submission_df[[\"row_id\"] + class_names]\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(\"✅ submission.csv saved with\", len(submission_df), \"rows\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T08:06:29.075519Z","iopub.execute_input":"2025-05-14T08:06:29.075935Z","iopub.status.idle":"2025-05-14T08:06:37.784114Z","shell.execute_reply.started":"2025-05-14T08:06:29.075911Z","shell.execute_reply":"2025-05-14T08:06:37.783026Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}