{"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":243615659,"sourceType":"kernelVersion"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"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\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-05-28T07:49:30.659197Z","iopub.execute_input":"2025-05-28T07:49:30.659489Z","iopub.status.idle":"2025-05-28T07:50:04.189809Z","shell.execute_reply.started":"2025-05-28T07:49:30.659459Z","shell.execute_reply":"2025-05-28T07:50:04.188735Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Pseudo-Labeling Notebook\n\n# 1) Imports & Config\nimport os\nimport ast\nimport torch\nimport librosa\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom torch import nn\nfrom tqdm.notebook import tqdm\nimport timm\n\nclass CFG:\n    sample_rate = 32000\n    duration = 5         # seconds per segment\n    n_mels    = 128\n    n_fft     = 2048\n    hop_length= 512\n    num_classes = 206\n    model_name  = 'efficientnet_b0'\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n\nUNLABELED_DIR = \"/kaggle/input/birdclef-2025/train_soundscapes\"\nOUTPUT_CSV   = \"pseudo_labels.csv\"\nMODEL_PATH   = \"/kaggle/input/efficientnet-v4/efficientnet_b0_frozen_overall_best.pth\"\n\n# 2) Load Pretrained Model\nclass EfficientNetFrozen(nn.Module):\n    \"\"\"\n    EfficientNet‐B0 backbone where:\n      - All layers up through blocks.5 are frozen.\n      - Only backbone.blocks.6 and backbone.bn2 are trainable.\n      - The classifier head is trainable.\n    \"\"\"\n    def __init__(self, model_name=\"efficientnet_b0\", n_classes=206, unfreeze_blocks=[\"blocks.6\"]):\n        super().__init__()\n        # 1) Load pretrained EfficientNet‐B0, no head:\n        self.backbone = timm.create_model(\n            model_name,\n            pretrained=True,\n            in_chans=1,\n            num_classes=0\n        )\n        # 2) First, freeze everything:\n        for param in self.backbone.parameters():\n            param.requires_grad = False\n\n        # 3) Unfreeze only the specified blocks (e.g. \"blocks.6\") and final batchnorm (\"bn2\"):\n        #    If you want to unfreeze more, add them by name in unfreeze_blocks.\n        for name, param in self.backbone.named_parameters():\n            # “blocks.6.” is the final MBConv block in B0; “bn2” is the last batchnorm\n            if any([name.startswith(block_name) for block_name in unfreeze_blocks]) \\\n               or name.startswith(\"bn2\"):\n                param.requires_grad = True\n\n        # 4) Build a new classifier head on top of pooled features:\n        num_features = self.backbone.num_features  # should be 1280 for B0\n        self.classifier = nn.Linear(num_features, n_classes)\n        # Ensure classifier is trainable:\n        for param in self.classifier.parameters():\n            param.requires_grad = True\n\n    def forward(self, x):\n        feats = self.backbone(x)     # → (B, 1280)\n        logits = self.classifier(feats)  # → (B, 206)\n        return logits\nmodel = EfficientNetFrozen(CFG.model_name, CFG.num_classes)\nstate = torch.load(MODEL_PATH, map_location=CFG.device)\nmodel.load_state_dict(state)\nmodel.to(CFG.device)\nmodel.eval()\n\n# 3) Audio → Log-Mel\ndef audio_to_logmel(y):\n    mel = librosa.feature.melspectrogram(\n        y=y, sr=CFG.sample_rate,\n        n_fft=CFG.n_fft, hop_length=CFG.hop_length,\n        n_mels=CFG.n_mels\n    )\n    logmel = librosa.power_to_db(mel)\n    return (logmel - logmel.mean(axis=1, keepdims=True)) / (logmel.std(axis=1, keepdims=True) + 1e-6)\n\n# 4) Pseudo-Labeling Loop\nrows = []\nthreshold = 0.9    # keep only very confident predictions\n\nfor fp in tqdm(sorted(Path(UNLABELED_DIR).glob(\"*.ogg\"))):\n    y, _ = librosa.load(str(fp), sr=CFG.sample_rate)\n    seg_len = CFG.duration * CFG.sample_rate\n    num_segs = len(y) // seg_len\n    for i in range(num_segs):\n        seg = y[i*seg_len:(i+1)*seg_len]\n        if len(seg) < seg_len:\n            seg = np.pad(seg, (0, seg_len-len(seg)))\n        logmel = audio_to_logmel(seg)\n        x = torch.tensor(logmel).unsqueeze(0).unsqueeze(0).float().to(CFG.device)\n        with torch.no_grad():\n            probs = torch.sigmoid(model(x)).cpu().numpy()[0]\n        # pick labels > threshold\n        labels = list(np.where(probs >= threshold)[0])\n        if labels:\n            # store row: filepath, end_time, list of label indices (or map to codes later)\n            rows.append({\n                \"row_id\": f\"{fp.stem}_{(i+1)*CFG.duration}\",\n                \"labels\": labels,\n                **{f\"class_{c}\": float(probs[c]) for c in labels}\n            })\n\n# 5) Save Pseudo-Labels CSV\npseudo_df = pd.DataFrame(rows)\n# fill missing class columns with 0\nall_cls = [f\"class_{i}\" for i in range(CFG.num_classes)]\nfor c in all_cls:\n    if c not in pseudo_df:\n        pseudo_df[c] = 0.0\npseudo_df = pseudo_df[[\"row_id\"] + all_cls]\npseudo_df.to_csv(OUTPUT_CSV, index=False)\nprint(\"✅ Saved pseudo-labels:\", OUTPUT_CSV)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T07:50:04.190934Z","iopub.execute_input":"2025-05-28T07:50:04.191321Z","iopub.status.idle":"2025-05-28T09:31:30.037468Z","shell.execute_reply.started":"2025-05-28T07:50:04.191298Z","shell.execute_reply":"2025-05-28T09:31:30.036610Z"}},"outputs":[],"execution_count":null}]}