{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":25954,"databundleVersionId":2091745,"sourceType":"competition"},{"sourceId":1297722,"sourceType":"datasetVersion","datasetId":750498},{"sourceId":2130303,"sourceType":"datasetVersion","datasetId":1278322}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import shutil\nshutil.copytree('../input/resnest50-fast-package/resnest-0.0.6b20200701/resnest', 'resnet', dirs_exist_ok=True) \n!pip install \"./resnet\" --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:56:56.525573Z","iopub.execute_input":"2025-11-11T20:56:56.525901Z","iopub.status.idle":"2025-11-11T20:56:59.828071Z","shell.execute_reply.started":"2025-11-11T20:56:56.525870Z","shell.execute_reply":"2025-11-11T20:56:59.827114Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom resnest.torch import resnest50\nfrom sklearn.preprocessing import LabelEncoder\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:56:59.829191Z","iopub.execute_input":"2025-11-11T20:56:59.829431Z","iopub.status.idle":"2025-11-11T20:56:59.835064Z","shell.execute_reply.started":"2025-11-11T20:56:59.829404Z","shell.execute_reply":"2025-11-11T20:56:59.834257Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#1. Определение путей и устройств\nDATA_DIR = \"../input/birdclef-2021\"\nTEST_AUDIO_DIR = os.path.join(DATA_DIR, \"test_soundscapes\")\nSAMPLE_RATE = 32000\nSEGMENT_SEC = 5\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:56:59.835995Z","iopub.execute_input":"2025-11-11T20:56:59.836315Z","iopub.status.idle":"2025-11-11T20:56:59.849969Z","shell.execute_reply.started":"2025-11-11T20:56:59.836292Z","shell.execute_reply":"2025-11-11T20:56:59.849383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#2. Подготовка меток\nmetadata = pd.read_csv(os.path.join(DATA_DIR, \"train_metadata.csv\"))\nall_birds = sorted(set(metadata[\"primary_label\"]))\n\nlabel_encoder = LabelEncoder().fit(all_birds)\nNUM_CLASSES = len(all_birds)\nprint(f\"Using {NUM_CLASSES} scored bird species.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:56:59.850741Z","iopub.execute_input":"2025-11-11T20:56:59.850984Z","iopub.status.idle":"2025-11-11T20:57:00.135889Z","shell.execute_reply.started":"2025-11-11T20:56:59.850961Z","shell.execute_reply":"2025-11-11T20:57:00.135029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Пример названий птиц\nall_birds[:11]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:00.136865Z","iopub.execute_input":"2025-11-11T20:57:00.137428Z","iopub.status.idle":"2025-11-11T20:57:00.142793Z","shell.execute_reply.started":"2025-11-11T20:57:00.137407Z","shell.execute_reply":"2025-11-11T20:57:00.141942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#3. Аудио в спектрограмму\ndef audio_to_melspec(audio: np.ndarray, sr: int = SAMPLE_RATE) -> np.ndarray:\n    mel = librosa.feature.melspectrogram(\n        y=audio, sr=sr, n_mels=128, fmin=0, fmax=sr//2,\n        n_fft=sr//10, hop_length=sr//40\n    )\n    db = librosa.power_to_db(mel, ref=np.max)\n    \n    # Z-нормализация\n    db = (db - db.mean()) / (db.std() + 1e-8)\n    \n    db_min, db_max = db.min(), db.max()\n    if db_max - db_min > 1e-6:\n        db = 255 * (db - db_min) / (db_max - db_min)\n    else:\n        db = np.zeros_like(db)\n    return db.astype(np.uint8)\n\ndef spec_to_tensor(spec: np.ndarray) -> np.ndarray:\n    # Convert 2D spectrogram to 3-channel float tensor [C, H, W].\"\"\"\n    rgb = np.stack([spec] * 3, axis=0).astype(np.float32) / 255.0\n    return rgb","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:00.143692Z","iopub.execute_input":"2025-11-11T20:57:00.143985Z","iopub.status.idle":"2025-11-11T20:57:00.156646Z","shell.execute_reply.started":"2025-11-11T20:57:00.143957Z","shell.execute_reply":"2025-11-11T20:57:00.155825Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BirdSoundDataset(Dataset):\n    def __init__(self, df, audio_dir, sr=32000, segment_sec=5):\n        self.df = df\n        self.audio_dir = audio_dir\n        self.sr = sr\n        self.segment_sec = segment_sec\n        self.cache = {}  # кеширование\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row_id = self.df.iloc[idx][\"row_id\"]\n        parts = row_id.split(\"_\")\n        file_id, site, end_sec = parts[0], parts[1], int(parts[2])\n        prefix = f\"{file_id}_{site}\"\n\n        try:\n            if prefix not in self.cache:\n                audio_file = next(f for f in os.listdir(self.audio_dir) if f.startswith(prefix))\n                audio_path = os.path.join(self.audio_dir, audio_file)\n                audio, sr = librosa.load(audio_path, sr=None, res_type='kaiser_fast')\n                if sr != self.sr:\n                    audio = librosa.resample(audio, orig_sr=sr, target_sr=self.sr)\n                self.cache[prefix] = audio\n            else:\n                audio = self.cache[prefix]\n\n            start = max(0, (end_sec - self.segment_sec) * self.sr)\n            end = min(len(audio), end_sec * self.sr)\n            segment = audio[start:end]\n            if len(segment) < self.segment_sec * self.sr:\n                segment = np.pad(segment, (0, self.segment_sec * self.sr - len(segment)))\n\n            mel = audio_to_melspec(segment, self.sr)\n            tensor = spec_to_tensor(mel)\n            return tensor\n\n        except Exception:\n            return np.zeros((3, 128, 313), dtype=np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:00.157467Z","iopub.execute_input":"2025-11-11T20:57:00.157824Z","iopub.status.idle":"2025-11-11T20:57:00.171280Z","shell.execute_reply.started":"2025-11-11T20:57:00.157799Z","shell.execute_reply":"2025-11-11T20:57:00.170688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#4. Загрузка модели\ndef load_resnest_model(weights_path: str, num_classes: int):\n    model = resnest50(pretrained=False)\n    model.fc = torch.nn.Linear(model.fc.in_features, num_classes)\n    \n    # Загрузка весов\n    state_dict = torch.load(weights_path, map_location=\"cpu\")\n    new_state = {}\n    for k, v in state_dict.items():\n        new_key = k.replace(\"model.\", \"\") if k.startswith(\"model.\") else k\n        new_state[new_key] = v\n    model.load_state_dict(new_state)\n    \n    model.to(device)\n    model.eval()\n    return model\n\n# Загружаем предобученную модель\nMODEL_PATH = \"../input/kkiller-birdclef-models-public/birdclef_resnest50_fold0_epoch_10_f1_val_06471_20210417161101.pth\"\nmodel = load_resnest_model(MODEL_PATH, NUM_CLASSES)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:00.172027Z","iopub.execute_input":"2025-11-11T20:57:00.172199Z","iopub.status.idle":"2025-11-11T20:57:00.782894Z","shell.execute_reply.started":"2025-11-11T20:57:00.172185Z","shell.execute_reply":"2025-11-11T20:57:00.782273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#5. Inference с порогом\ndef predict_batch(batch: np.ndarray, model, threshold: float = 0.1):\n    with torch.no_grad():\n        inputs = torch.from_numpy(batch).to(device)\n        logits = model(inputs)\n        probs = torch.sigmoid(logits).cpu().numpy()\n    \n    predictions = []\n    for p in probs:\n        active = np.where(p > threshold)[0]\n        if len(active) == 0:\n            predictions.append(\"nocall\")\n        else:\n            bird_names = label_encoder.inverse_transform(active)\n            predictions.append(\" \".join(sorted(bird_names)))\n    return predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:00.786737Z","iopub.execute_input":"2025-11-11T20:57:00.786960Z","iopub.status.idle":"2025-11-11T20:57:00.792161Z","shell.execute_reply.started":"2025-11-11T20:57:00.786943Z","shell.execute_reply":"2025-11-11T20:57:00.791356Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#6. Работа с данными \ntest_df = pd.read_csv(os.path.join(DATA_DIR, \"test.csv\"))\nif len(test_df) < 5:  \n    test_df = pd.read_csv(os.path.join(DATA_DIR, \"train_soundscape_labels.csv\"))\n    audio_dir = os.path.join(DATA_DIR, \"train_soundscapes\")\nelse:\n    audio_dir = TEST_AUDIO_DIR\n\nprint(f\"Processing {len(test_df)} segments...\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:00.792826Z","iopub.execute_input":"2025-11-11T20:57:00.793062Z","iopub.status.idle":"2025-11-11T20:57:00.812948Z","shell.execute_reply.started":"2025-11-11T20:57:00.793046Z","shell.execute_reply":"2025-11-11T20:57:00.811887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Создаём датасет и DataLoader\ndataset = BirdSoundDataset(test_df, audio_dir)\ndataloader = DataLoader(dataset, batch_size=64, shuffle=False, num_workers=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:00.813745Z","iopub.execute_input":"2025-11-11T20:57:00.813938Z","iopub.status.idle":"2025-11-11T20:57:00.826444Z","shell.execute_reply.started":"2025-11-11T20:57:00.813922Z","shell.execute_reply":"2025-11-11T20:57:00.825636Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_preds = []\nfor batch in tqdm(dataloader, desc=\"Inference\"):\n    batch_np = np.stack([b.numpy() for b in batch])\n    preds = predict_batch(batch_np, model, threshold=0.1)\n    all_preds.extend(preds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:00.827169Z","iopub.execute_input":"2025-11-11T20:57:00.827345Z","iopub.status.idle":"2025-11-11T20:57:51.615011Z","shell.execute_reply.started":"2025-11-11T20:57:00.827331Z","shell.execute_reply":"2025-11-11T20:57:51.614296Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import Audio, display\n\n#Найдём индексы, где предсказание — не \"nocall\"\nnon_nocall_indices = [i for i, pred in enumerate(all_preds) if pred != \"nocall\"]\nindices_to_show = non_nocall_indices[:6]\nnum_viz = len(indices_to_show)\n\nif num_viz > 0:\n    for idx, i in enumerate(indices_to_show):\n        #Получаем аудио\n        row_id = test_df.iloc[i][\"row_id\"]\n        parts = row_id.split(\"_\")\n        file_id, site, end_sec = parts[0], parts[1], int(parts[2])\n        prefix = f\"{file_id}_{site}\"\n\n        audio_file = next(f for f in os.listdir(audio_dir) if f.startswith(prefix))\n        audio_path = os.path.join(audio_dir, audio_file)\n        audio_full, sr_orig = librosa.load(audio_path, sr=None, res_type='kaiser_fast')\n        if sr_orig != SAMPLE_RATE:\n            audio_full = librosa.resample(audio_full, orig_sr=sr_orig, target_sr=SAMPLE_RATE)\n        \n        start = max(0, (end_sec - SEGMENT_SEC) * SAMPLE_RATE)\n        end = min(len(audio_full), end_sec * SAMPLE_RATE)\n        segment_audio = audio_full[start:end]\n        if len(segment_audio) < SEGMENT_SEC * SAMPLE_RATE:\n            segment_audio = np.pad(segment_audio, (0, SEGMENT_SEC * SAMPLE_RATE - len(segment_audio)))\n\n        #Получаем спектрограмму\n        spec_tensor = dataset[i] \n        pred_birds = all_preds[i]\n        spec_img = spec_tensor[0]\n\n        #Выводим аудио\n        print(f\"\\nСегмент {i+1} | row_id: {row_id} | Prediction: '{pred_birds}'\")\n        display(Audio(segment_audio, rate=SAMPLE_RATE))\n\n        #Выводим спектрограмму\n        plt.figure(figsize=(14, 3))\n        im = plt.imshow(spec_img, aspect='auto', origin='lower', cmap='magma')\n        plt.title(f\"Mel-spectrogram | {row_id}\", fontsize=12)\n        plt.xlabel(\"Time frames\")\n        plt.ylabel(\"Mel bins\")\n        plt.colorbar(im, shrink=0.6)\n        plt.tight_layout()\n        plt.show()\n\nelse:\n    print(\"Нет сегментов с предсказанием, отличным от 'nocall'.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:51.615938Z","iopub.execute_input":"2025-11-11T20:57:51.616184Z","iopub.status.idle":"2025-11-11T20:57:57.610059Z","shell.execute_reply.started":"2025-11-11T20:57:51.616165Z","shell.execute_reply":"2025-11-11T20:57:57.609305Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#7. Сохранение submission\nsubmission = pd.DataFrame({\n    \"row_id\": test_df[\"row_id\"],\n    \"birds\": all_preds\n})\nsubmission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:57.610895Z","iopub.execute_input":"2025-11-11T20:57:57.611144Z","iopub.status.idle":"2025-11-11T20:57:57.620590Z","shell.execute_reply.started":"2025-11-11T20:57:57.611118Z","shell.execute_reply":"2025-11-11T20:57:57.619886Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(submission)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:57:57.621344Z","iopub.execute_input":"2025-11-11T20:57:57.621572Z","iopub.status.idle":"2025-11-11T20:57:57.636219Z","shell.execute_reply.started":"2025-11-11T20:57:57.621547Z","shell.execute_reply":"2025-11-11T20:57:57.635476Z"}},"outputs":[],"execution_count":null}]}