{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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,"isSourceIdPinned":false,"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":[{"id":"b34c63df","cell_type":"markdown","source":"# Лабораторная работа №2\n# BirdCLEF 2021 — Baseline\n\nВыполнил ***Орлов А.Л.*** студент группы ***0307*** ","metadata":{}},{"id":"0d7e965b","cell_type":"markdown","source":"## Настройки и зависимости","metadata":{}},{"id":"5a4221c6-e1fb-4331-8a54-36d0c26e3c36","cell_type":"code","source":"import os\nimport shutil\n\nshutil.copytree('../input/resnest50-fast-package/resnest-0.0.6b20200701/resnest', 'resnet', dirs_exist_ok=True)\nos.system('pip install \"./resnet\" --no-deps')\n\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\n\n# ================== CONFIG ==================\nDATA_ROOT = '../input/birdclef-2021'\nTRAIN_SHORT_AUDIO = os.path.join(DATA_ROOT, 'train_short_audio')\nTRAIN_SOUNDCAPES = os.path.join(DATA_ROOT, 'train_soundscapes')\nTEST_AUDIO_PATH = os.path.join(DATA_ROOT, 'test_soundscapes')\nTRAIN_META = os.path.join(DATA_ROOT, 'train_metadata.csv')\nTEST_META = os.path.join(DATA_ROOT, 'test.csv')\nSUBMISSION_CSV = 'submission.csv'\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nSR = 32000\nSEG_DUR = 5  # seconds\nBATCH_SIZE = 64\nTHRESH = 0.25","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T09:54:00.470623Z","iopub.execute_input":"2025-11-27T09:54:00.470941Z","iopub.status.idle":"2025-11-27T09:54:05.417709Z","shell.execute_reply.started":"2025-11-27T09:54:00.470891Z","shell.execute_reply":"2025-11-27T09:54:05.417107Z"}},"outputs":[],"execution_count":null},{"id":"8ef28ce8","cell_type":"markdown","source":"## Загрузка метаданных","metadata":{}},{"id":"e8dc7fc5-c444-4388-a661-8c632d6c242a","cell_type":"code","source":"# ================== LOAD METADATA ==================\ntrain_meta = pd.read_csv(TRAIN_META)\nspecies_list = sorted(train_meta['primary_label'].unique())\nlabel_encoder = LabelEncoder().fit(species_list)\nNUM_SPECIES = len(species_list)\n\n# ================== AUDIO -> MEL-SPECT ==================\ndef transform_audio_to_spec(signal, sr=SR):\n    mel_spec = librosa.feature.melspectrogram(\n        y=signal, sr=sr, n_mels=128, fmin=0, fmax=sr//2, n_fft=sr//10, hop_length=sr//40\n    )\n    log_mel = librosa.power_to_db(mel_spec, ref=np.max)\n    norm = (log_mel - log_mel.mean()) / (log_mel.std() + 1e-8)\n    min_val, max_val = norm.min(), norm.max()\n    if max_val - min_val > 1e-6:\n        scaled = 255 * (norm - min_val) / (max_val - min_val)\n    else:\n        scaled = np.zeros_like(norm)\n    return scaled.astype(np.uint8)\n\ndef make_rgb_tensor(spec_img):\n    return np.stack([spec_img]*3, axis=0).astype(np.float32)/255.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T09:43:39.816649Z","iopub.execute_input":"2025-11-27T09:43:39.816918Z","iopub.status.idle":"2025-11-27T09:43:43.192213Z","shell.execute_reply.started":"2025-11-27T09:43:39.816900Z","shell.execute_reply":"2025-11-27T09:43:43.191370Z"}},"outputs":[],"execution_count":null},{"id":"15e339a7","cell_type":"markdown","source":"## Dataset и аугментации","metadata":{}},{"id":"e4eae994-614d-4e8b-b329-dd4070ca3d8d","cell_type":"code","source":"\n# ================== DATASET ==================\nclass AudioSegmentDataset(Dataset):\n    def __init__(self, annotations, audio_folder, sr=SR, seg_len_sec=SEG_DUR):\n        self.annotations = annotations.reset_index(drop=True)\n        self.folder = audio_folder\n        self.sr = sr\n        self.seg_len = seg_len_sec\n        self._audio_cache = {}\n\n    def __len__(self):\n        return len(self.annotations)\n\n    def __getitem__(self, idx):\n        row = self.annotations.iloc[idx]\n        row_id = row['row_id']\n        full_prefix = '_'.join(row_id.split('_')[:2])\n        end_time = int(row_id.split('_')[-1])\n\n        if full_prefix not in self._audio_cache:\n            audio_file = next(f for f in os.listdir(self.folder) if f.startswith(full_prefix))\n            audio_full, orig_sr = librosa.load(os.path.join(self.folder, audio_file), sr=None, res_type='kaiser_fast')\n            if orig_sr != self.sr:\n                audio_full = librosa.resample(audio_full, orig_sr=orig_sr, target_sr=self.sr)\n            self._audio_cache[full_prefix] = audio_full\n        else:\n            audio_full = self._audio_cache[full_prefix]\n\n        start_sample = max(0, (end_time - self.seg_len) * self.sr)\n        end_sample = min(len(audio_full), end_time * self.sr)\n        segment = audio_full[start_sample:end_sample]\n        if len(segment) < self.seg_len * self.sr:\n            segment = np.pad(segment, (0, self.seg_len * self.sr - len(segment)))\n\n        spec = transform_audio_to_spec(segment, self.sr)\n        tensor = make_rgb_tensor(spec)\n        return tensor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T09:43:43.193095Z","iopub.execute_input":"2025-11-27T09:43:43.193313Z","iopub.status.idle":"2025-11-27T09:43:43.200615Z","shell.execute_reply.started":"2025-11-27T09:43:43.193296Z","shell.execute_reply":"2025-11-27T09:43:43.200059Z"}},"outputs":[],"execution_count":null},{"id":"0bcbcc20","cell_type":"markdown","source":"## Модель","metadata":{}},{"id":"159c40a2-2236-4d21-b64e-64ebf3d703af","cell_type":"code","source":"def build_inference_model(weight_path, num_classes):\n    net = resnest50(pretrained=False)\n    net.fc = torch.nn.Linear(net.fc.in_features, num_classes)\n    checkpoint = torch.load(weight_path, map_location='cpu')\n    clean_state = {k.replace('model.', ''): v for k,v in checkpoint.items()}\n    net.load_state_dict(clean_state)\n    net.to(DEVICE)\n    net.eval()\n    return net","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T09:43:43.201480Z","iopub.execute_input":"2025-11-27T09:43:43.201769Z","iopub.status.idle":"2025-11-27T09:43:43.220170Z","shell.execute_reply.started":"2025-11-27T09:43:43.201751Z","shell.execute_reply":"2025-11-27T09:43:43.219527Z"}},"outputs":[],"execution_count":null},{"id":"0432e22d","cell_type":"markdown","source":"## Вывод модели","metadata":{}},{"id":"62510af5-7b27-4515-bbdd-44793f25574c","cell_type":"code","source":"def run_inference_on_batch(batch_data, model, thr=THRESH):\n    with torch.no_grad():\n        inputs = torch.from_numpy(batch_data).to(DEVICE)\n        logits = model(inputs)\n        probs = torch.sigmoid(logits).cpu().numpy()\n\n    results = []\n    for prob_vec in probs:\n        active_labels = np.where(prob_vec > thr)[0]\n        if len(active_labels) == 0:\n            results.append('nocall')\n        else:\n            names = label_encoder.inverse_transform(active_labels)\n            results.append(' '.join(sorted(names)))\n    return results","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T09:43:43.221749Z","iopub.execute_input":"2025-11-27T09:43:43.222038Z","iopub.status.idle":"2025-11-27T09:43:43.234265Z","shell.execute_reply.started":"2025-11-27T09:43:43.222022Z","shell.execute_reply":"2025-11-27T09:43:43.233592Z"}},"outputs":[],"execution_count":null},{"id":"6fb33f66","cell_type":"markdown","source":"## Проход на тестовых данных","metadata":{}},{"id":"ed5ed1da-7aa0-4e9a-8d75-e3b0502c2239","cell_type":"code","source":"# ================== TEST / SUBMISSION ==================\nuse_train_as_test = False\ntry:\n    test_meta = pd.read_csv(TEST_META)\n    if len(test_meta) < 10:\n        use_train_as_test = True\nexcept:\n    use_train_as_test = True\n\nif use_train_as_test:\n    test_meta = pd.read_csv(os.path.join(DATA_ROOT, 'train_soundscape_labels.csv'))\n    audio_source = TRAIN_SOUNDCAPES\nelse:\n    audio_source = TEST_AUDIO_PATH\n\naudio_dataset = AudioSegmentDataset(test_meta, audio_source)\nloader = DataLoader(audio_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=0)\n\nMODEL_WEIGHTS_FILE = '../input/kkiller-birdclef-models-public/birdclef_resnest50_fold0_epoch_10_f1_val_06471_20210417161101.pth'\nmodel = build_inference_model(MODEL_WEIGHTS_FILE, NUM_SPECIES)\n\nfinal_predictions = []\nfor batch in tqdm(loader, desc='Running inference'):\n    stacked_batch = np.stack([b for b in batch])\n    batch_preds = run_inference_on_batch(stacked_batch, model, thr=THRESH)\n    final_predictions.extend(batch_preds)\n\nsubmission_df = pd.DataFrame({\n    'row_id': test_meta['row_id'],\n    'birds': final_predictions\n})\nsubmission_df.to_csv(SUBMISSION_CSV, index=False)\nprint(submission_df.head(10))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T09:43:43.234922Z","iopub.execute_input":"2025-11-27T09:43:43.235215Z","iopub.status.idle":"2025-11-27T09:44:35.348428Z","shell.execute_reply.started":"2025-11-27T09:43:43.235198Z","shell.execute_reply":"2025-11-27T09:44:35.347641Z"}},"outputs":[],"execution_count":null}]}