{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"nvidiaTeslaT4","dataSources":[{"sourceId":25954,"databundleVersionId":2091745,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":2130303,"sourceType":"datasetVersion","datasetId":1278322},{"sourceId":1297722,"sourceType":"datasetVersion","datasetId":750498}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import shutil\nimport os\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.preprocessing import LabelEncoder\nfrom tqdm import tqdm\n\nprint(\"Установка ResNeSt...\")\ntry:\n    shutil.copytree('../input/resnest50-fast-package/resnest-0.0.6b20200701/resnest', \n                    'resnet', dirs_exist_ok=True)\n    os.system('pip install \"./resnet\" --no-deps')\n    print(\"ResNeSt установлен\")\nexcept FileNotFoundError:\n    print(\"Путь к resnest не найден, пробуем альтернативный...\")\n    os.system('pip install ../input/resnest50-fast-package/resnest-0.0.6b20200701 --no-deps')\n\nfrom resnest.torch import resnest50\n\nSR = 32000\nDURATION = 5\nTHRESHOLD = 0.25\nBATCH_SIZE = 64\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nDATA_ROOT = \"../input/birdclef-2021\"\nTEST_AUDIO = os.path.join(DATA_ROOT, \"test_soundscapes\")\nWEIGHTS = \"../input/kkiller-birdclef-models-public/birdclef_resnest50_fold0_epoch_10_f1_val_06471_20210417161101.pth\"\n\nprint(f\"\\nИнициализация завершена\")\nprint(f\"Device: {DEVICE}\")\nprint(f\"Threshold: {THRESHOLD}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:02:18.207699Z","iopub.execute_input":"2025-12-15T20:02:18.208294Z","iopub.status.idle":"2025-12-15T20:02:27.067468Z","shell.execute_reply.started":"2025-12-15T20:02:18.208271Z","shell.execute_reply":"2025-12-15T20:02:27.066755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta = pd.read_csv(os.path.join(DATA_ROOT, \"train_metadata.csv\"))\nspecies = sorted(train_meta[\"primary_label\"].unique())\nencoder = LabelEncoder().fit(species)\nNUM_CLASSES = len(species)\n\nprint(f\"Видов: {NUM_CLASSES}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:02:27.068657Z","iopub.execute_input":"2025-12-15T20:02:27.068983Z","iopub.status.idle":"2025-12-15T20:02:27.403651Z","shell.execute_reply.started":"2025-12-15T20:02:27.068965Z","shell.execute_reply":"2025-12-15T20:02:27.402764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def audio_to_melspec(audio, sr=SR):\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    log_mel = librosa.power_to_db(mel, ref=np.max)\n    \n    mean, std = log_mel.mean(), log_mel.std()\n    normalized = (log_mel - mean) / (std + 1e-8)\n    \n    vmin, vmax = normalized.min(), normalized.max()\n    if vmax - vmin > 1e-6:\n        scaled = 255 * (normalized - vmin) / (vmax - vmin)\n    else:\n        scaled = np.zeros_like(normalized)\n    \n    return scaled.astype(np.uint8)\n\ndef to_rgb(spec):\n    return np.stack([spec] * 3, axis=0).astype(np.float32) / 255.0\n\nprint(\"Функции готовы\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:02:27.404513Z","iopub.execute_input":"2025-12-15T20:02:27.404778Z","iopub.status.idle":"2025-12-15T20:02:27.411094Z","shell.execute_reply.started":"2025-12-15T20:02:27.404760Z","shell.execute_reply":"2025-12-15T20:02:27.410368Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SoundscapeDataset(Dataset):\n    def __init__(self, df, audio_dir, sr=SR, duration=DURATION):\n        self.df = df.reset_index(drop=True)\n        self.audio_dir = audio_dir\n        self.sr = sr\n        self.duration = duration\n        self.cache = {}\n    \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        row_id = row[\"row_id\"]\n        \n        file_id = \"_\".join(row_id.split(\"_\")[:2])\n        end_time = int(row_id.split(\"_\")[-1])\n        \n        try:\n            if file_id not in self.cache:\n                filename = next(f for f in os.listdir(self.audio_dir) if f.startswith(file_id))\n                audio, orig_sr = librosa.load(\n                    os.path.join(self.audio_dir, filename), \n                    sr=None, res_type='kaiser_fast'\n                )\n                if orig_sr != self.sr:\n                    audio = librosa.resample(audio, orig_sr=orig_sr, target_sr=self.sr)\n                self.cache[file_id] = audio\n            \n            audio = self.cache[file_id]\n            \n            start = max(0, (end_time - self.duration) * self.sr)\n            end = min(len(audio), end_time * self.sr)\n            segment = audio[start:end]\n            \n            if len(segment) < self.duration * self.sr:\n                segment = np.pad(segment, (0, self.duration * self.sr - len(segment)))\n            \n            spec = audio_to_melspec(segment, self.sr)\n            return to_rgb(spec)\n        \n        except:\n            return np.zeros((3, 128, 313), dtype=np.float32)\n\nprint(\"Dataset готов\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:02:27.411940Z","iopub.execute_input":"2025-12-15T20:02:27.412250Z","iopub.status.idle":"2025-12-15T20:02:27.426648Z","shell.execute_reply.started":"2025-12-15T20:02:27.412228Z","shell.execute_reply":"2025-12-15T20:02:27.425885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_model(weights_path, num_classes):\n    model = resnest50(pretrained=False)\n    model.fc = torch.nn.Linear(model.fc.in_features, num_classes)\n    \n    state = torch.load(weights_path, map_location=\"cpu\")\n    state = {k.replace(\"model.\", \"\"): v for k, v in state.items()}\n    model.load_state_dict(state)\n    \n    model.to(DEVICE)\n    model.eval()\n    return model\n\nprint(\"Загрузка модели...\")\nmodel = load_model(WEIGHTS, NUM_CLASSES)\nprint(\"Модель загружена\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:02:27.428721Z","iopub.execute_input":"2025-12-15T20:02:27.428951Z","iopub.status.idle":"2025-12-15T20:02:28.948271Z","shell.execute_reply.started":"2025-12-15T20:02:27.428932Z","shell.execute_reply":"2025-12-15T20:02:28.947577Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef predict_batch(batch, model, threshold=THRESHOLD):\n    inputs = torch.from_numpy(batch).to(DEVICE)\n    logits = model(inputs)\n    probs = torch.sigmoid(logits).cpu().numpy()\n    \n    predictions = []\n    for prob in probs:\n        indices = np.where(prob > threshold)[0]\n        if len(indices) == 0:\n            predictions.append(\"nocall\")\n        else:\n            labels = encoder.inverse_transform(indices)\n            predictions.append(\" \".join(sorted(labels)))\n    \n    return predictions\n\nprint(\"Inference функция готова\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:02:28.949069Z","iopub.execute_input":"2025-12-15T20:02:28.949345Z","iopub.status.idle":"2025-12-15T20:02:28.955142Z","shell.execute_reply.started":"2025-12-15T20:02:28.949291Z","shell.execute_reply":"2025-12-15T20:02:28.954520Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(os.path.join(DATA_ROOT, \"test.csv\"))\n\nif len(test_df) < 10:\n    print(\"Используем train_soundscapes для теста\")\n    test_df = pd.read_csv(os.path.join(DATA_ROOT, \"train_soundscape_labels.csv\"))\n    audio_dir = os.path.join(DATA_ROOT, \"train_soundscapes\")\nelse:\n    audio_dir = TEST_AUDIO\n\nprint(f\"Сегментов: {len(test_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:02:28.955809Z","iopub.execute_input":"2025-12-15T20:02:28.956098Z","iopub.status.idle":"2025-12-15T20:02:28.981963Z","shell.execute_reply.started":"2025-12-15T20:02:28.956080Z","shell.execute_reply":"2025-12-15T20:02:28.981278Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = SoundscapeDataset(test_df, audio_dir)\nloader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=0)\n\nall_predictions = []\n\nprint(\"Запуск инференса...\")\nfor batch in tqdm(loader, desc=\"Processing\"):\n    batch_array = np.stack([b.numpy() for b in batch])\n    preds = predict_batch(batch_array, model, THRESHOLD)\n    all_predictions.extend(preds)\n\nprint(f\"\\nОбработано {len(all_predictions)} сегментов\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:02:28.982721Z","iopub.execute_input":"2025-12-15T20:02:28.982936Z","iopub.status.idle":"2025-12-15T20:03:37.364450Z","shell.execute_reply.started":"2025-12-15T20:02:28.982918Z","shell.execute_reply":"2025-12-15T20:03:37.363712Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame({\n    \"row_id\": test_df[\"row_id\"],\n    \"birds\": all_predictions\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)\n\nnocall_pct = (submission[\"birds\"] == \"nocall\").sum() / len(submission) * 100\n\nprint(f\"\\n{'='*60}\")\nprint(\"СТАТИСТИКА\")\nprint(f\"{'='*60}\")\nprint(f\"Всего сегментов: {len(submission)}\")\nprint(f\"nocall: {nocall_pct:.1f}%\")\nprint(f\"с птицами: {100-nocall_pct:.1f}%\")\nprint(f\"\\nПервые 10 строк:\")\nprint(submission.head(10))\nprint(f\"\\nСохранено в submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:03:37.365174Z","iopub.execute_input":"2025-12-15T20:03:37.365653Z","iopub.status.idle":"2025-12-15T20:03:37.385730Z","shell.execute_reply.started":"2025-12-15T20:03:37.365626Z","shell.execute_reply":"2025-12-15T20:03:37.385074Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\n\nif os.path.exists('resnet'):\n    shutil.rmtree('resnet')\n    print(\"Временная папка resnet удалена\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T20:03:37.386571Z","iopub.execute_input":"2025-12-15T20:03:37.386851Z","iopub.status.idle":"2025-12-15T20:03:37.395714Z","shell.execute_reply.started":"2025-12-15T20:03:37.386826Z","shell.execute_reply":"2025-12-15T20:03:37.394940Z"}},"outputs":[],"execution_count":null}]}