{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":11060723,"sourceType":"datasetVersion","datasetId":6891568},{"sourceId":333392,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":279246,"modelId":300162}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Pseudo-Label Generation\n\n\n**Goal:** For each 5-second segment of each soundscape, if the model predicts a bird species with confidence ≥ 0.9, we save it as a pseudo-label.\n","metadata":{}},{"cell_type":"code","source":"import 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 pathlib import Path\n\nclass CFG:\n    test_soundscapes = \"/kaggle/input/birdclef-2025/train_soundscapes\"\n    taxonomy_csv     = \"/kaggle/input/birdclef-2025/taxonomy.csv\"\n    model_dir        = \"/kaggle/input/regnrty008/pytorch/default/1\"\n    SR = 32000\n    WINDOW_SIZE = 5\n    N_FFT = 1024\n    HOP_LENGTH = 512\n    N_MELS = 128\n    FMIN = 50\n    FMAX = 14000\n    TARGET_SHAPE = (256, 256)\n    model_name = \"regnety_008\"\n    in_channels = 1\n    device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    pseudo_label_threshold = 0.93\n\ncfg = CFG()\n\ntaxonomy_df = pd.read_csv(cfg.taxonomy_csv)\nspecies_ids = taxonomy_df['primary_label'].tolist()\nnum_classes = len(species_ids)\nprint(f\"Loaded taxonomy with {num_classes} species classes.\")\nprint(f\"Using device: {cfg.device}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-22T06:25:53.059620Z","iopub.execute_input":"2025-05-22T06:25:53.059903Z","iopub.status.idle":"2025-05-22T06:26:00.606576Z","shell.execute_reply.started":"2025-05-22T06:25:53.059866Z","shell.execute_reply":"2025-05-22T06:26:00.605494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import timm\n\nimport timm\n\nclass BirdCLEFModel(nn.Module):\n    def __init__(self, cfg, num_classes):\n        super().__init__()\n        self.backbone = timm.create_model(\n            cfg.model_name,\n            pretrained=False,\n            in_chans=cfg.in_channels,\n            drop_rate=0.0,\n            drop_path_rate=0.0\n        )\n        \n        if 'efficientnet' in cfg.model_name:\n            backbone_out = self.backbone.classifier.in_features\n            self.backbone.classifier = nn.Identity()\n        elif 'resnet' in cfg.model_name:\n            backbone_out = self.backbone.fc.in_features\n            self.backbone.fc = nn.Identity()\n        else:\n            backbone_out = self.backbone.get_classifier().in_features\n            self.backbone.reset_classifier(0, '')  # use '' for newer timm models\n            \n        self.pooling = nn.AdaptiveAvgPool2d(1)\n        self.classifier = nn.Linear(backbone_out, num_classes)\n    \n    def forward(self, x):\n        features = self.backbone(x)\n        if isinstance(features, dict):\n            features = features.get('features', features.get('out', features))\n        if features.dim() == 4:\n            features = self.pooling(features)\n            features = features.view(features.size(0), -1)\n        logits = self.classifier(features)\n        return logits\n\n\nmodel_files = list(Path(cfg.model_dir).rglob(\"*.pth\")) + list(Path(cfg.model_dir).rglob(\"*.pt\"))\nassert len(model_files) > 0, \"No model files found.\"\n\nmodels = []\nfor mfile in model_files:\n    print(f\"Loading: {mfile.name}\")\n    model = BirdCLEFModel(cfg, num_classes=num_classes)\n    ckpt = torch.load(mfile, map_location=cfg.device)\n    if 'model_state_dict' in ckpt:\n        model.load_state_dict(ckpt['model_state_dict'])\n    else:\n        model.load_state_dict(ckpt)\n    model.to(cfg.device)\n    model.eval()\n    models.append(model)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-22T06:26:00.607763Z","iopub.execute_input":"2025-05-22T06:26:00.608045Z","iopub.status.idle":"2025-05-22T06:26:15.086333Z","shell.execute_reply.started":"2025-05-22T06:26:00.608022Z","shell.execute_reply":"2025-05-22T06:26:15.085565Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nfrom tqdm import tqdm\n\ndef audio_to_melspec(audio_data, cfg):\n    if np.isnan(audio_data).any():\n        mean_val = np.nanmean(audio_data)\n        audio_data = np.nan_to_num(audio_data, nan=mean_val)\n    mel_spec = librosa.feature.melspectrogram(\n        y=audio_data, sr=cfg.SR, n_fft=cfg.N_FFT,\n        hop_length=cfg.HOP_LENGTH, n_mels=cfg.N_MELS,\n        fmin=cfg.FMIN, fmax=cfg.FMAX, power=2.0\n    )\n    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n    mel_spec_norm = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)\n    return mel_spec_norm\n\ndef process_audio_segment(audio_segment, cfg):\n    segment_length = cfg.SR * cfg.WINDOW_SIZE\n    if len(audio_segment) < segment_length:\n        audio_segment = np.pad(audio_segment, (0, segment_length - len(audio_segment)), mode='constant')\n    mel = audio_to_melspec(audio_segment, cfg)\n    if mel.shape != cfg.TARGET_SHAPE:\n        mel = cv2.resize(mel, cfg.TARGET_SHAPE, interpolation=cv2.INTER_LINEAR)\n    return mel.astype(np.float32)\n\nresults = []\npseudo_mels = {}  # Dictionary to store spectrograms\ntotal_segments = 0\naudio_files = sorted(Path(cfg.test_soundscapes).glob(\"*.ogg\"))\n\nprint(f\"Processing {len(audio_files)} files...\")\nwith torch.no_grad():\n    for audio_path in tqdm(audio_files, desc=\"Audio files\", dynamic_ncols=True):\n        audio_name = audio_path.name\n        y, sr = librosa.load(audio_path, sr=cfg.SR)\n        if sr != cfg.SR:\n            y = librosa.resample(y, orig_sr=sr, target_sr=cfg.SR)\n\n        seg_samples = cfg.SR * cfg.WINDOW_SIZE\n        n_segments = int(len(y) / seg_samples)\n        total_segments += n_segments\n\n        if len(audio_files) % 100 == 0:\n            tqdm.write(f\"Total pseudo-labels so far: {len(results)}\")\n\n        for seg_idx in range(n_segments):\n            start = seg_idx * seg_samples\n            end = start + seg_samples\n            segment = y[start:end]\n            mel = process_audio_segment(segment, cfg)\n            tensor = torch.tensor(mel).unsqueeze(0).unsqueeze(0).to(cfg.device)\n        \n            if len(models) == 1:\n                probs = torch.sigmoid(models[0](tensor)).cpu().numpy().squeeze()\n            else:\n                preds = [torch.sigmoid(m(tensor)).cpu().numpy().squeeze() for m in models]\n                probs = np.mean(np.stack(preds, axis=0), axis=0)\n        \n            max_prob = float(np.max(probs))\n            max_idx = int(np.argmax(probs))\n        \n            if max_prob >= cfg.pseudo_label_threshold:\n                # This keeps the .ogg extension in the filename for CSV\n                segment_name = f\"{audio_name.replace('.ogg', '')}_{seg_idx * cfg.WINDOW_SIZE}.ogg\"\n                results.append([segment_name, species_ids[max_idx], max_prob])\n        \n                # Match the training notebook's key format: \"segment-segment\"\n                base = segment_name.replace('.ogg', '')\n                # use plain base, not base-base, so it matches train_df.samplename\n                pseudo_mels[base] = mel\n\n# DEBUG: show that our keys line up with the filenames in results\nprint(\"First 10 filenames from results (with .ogg):\")\nprint([row[0] for row in results[:10]])\nprint(\"First 10 keys in pseudo_mels dict:\")\nprint(list(pseudo_mels.keys())[:10])\n\nprint(\"\\nCheck each of the first 10:\")\nfor fn, lbl, prob in results[:10]:\n    k = fn.replace(\".ogg\",\"\")\n    print(f\"{fn}  → key='{k}'  in dict? {k in pseudo_mels}\")\n\n# Save all spectrograms to a single file\nnp.save(\"pseudo_mels.npy\", pseudo_mels)\n\nprint(f\"Done. Collected {len(results)} pseudo-labels from {total_segments} segments.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-22T06:26:15.088463Z","iopub.execute_input":"2025-05-22T06:26:15.088736Z","execution_failed":"2025-05-22T06:29:20.549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert raw pseudo-label results into train.csv-compatible format\npseudo_train = pd.DataFrame({\n    \"filename\": [row[0] for row in results],\n    \"primary_label\": [row[1] for row in results],\n    \"secondary_labels\": [[] for _ in results],\n    \"latitude\": [None] * len(results),\n    \"longitude\": [None] * len(results),\n    \"author\": [\"pseudo\"] * len(results),\n    \"rating\": [0] * len(results),\n    \"collection\": [\"pseudo\"] * len(results)\n})\n\n\npseudo_train.to_csv(\"pseudo_train.csv\", index=False)\nprint(f\"Saved {len(pseudo_train)} pseudo-labeled samples to 'pseudo_train.csv'\")\npseudo_train.head()\n\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-22T06:29:20.550Z"}},"outputs":[],"execution_count":null}]}