{"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":"none","dataSources":[{"sourceId":25954,"databundleVersionId":2091745,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":1297722,"sourceType":"datasetVersion","datasetId":750498},{"sourceId":2130303,"sourceType":"datasetVersion","datasetId":1278322}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import cv2\nimport re\nimport torch\nimport torch.nn as nn\nimport time\n\nimport numpy as np\nimport librosa as lb\nimport soundfile as sf\nimport pandas as pd\n\nfrom pathlib import Path\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm.notebook import tqdm\n\nimport sys\nsys.path.append('../input/resnest50-fast-package/resnest-0.0.6b20200701/resnest')\nfrom resnest.torch import resnest50","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:10.550749Z","iopub.execute_input":"2025-11-08T16:02:10.551052Z","iopub.status.idle":"2025-11-08T16:02:10.557047Z","shell.execute_reply.started":"2025-11-08T16:02:10.551035Z","shell.execute_reply":"2025-11-08T16:02:10.555726Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUM_CLASSES = 397\nSAMPLE_RATE = 32_000\nAUDIO_DURATION = 5\nPREDICTION_THRESHOLD = 0.25\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Устройство:\", DEVICE)\n\nTEST_AUDIO_PATH = Path(\"../input/birdclef-2021/test_soundscapes\")\nSUBMISSION_SAMPLE_PATH = \"../input/birdclef-2021/sample_submission.csv\"\nTRAIN_LABELS_PATH = None\n\nif not list(TEST_AUDIO_PATH.glob(\"*.ogg\")):\n    TEST_AUDIO_PATH = Path(\"../input/birdclef-2021/train_soundscapes\")\n    SUBMISSION_SAMPLE_PATH = None\n    TRAIN_LABELS_PATH = Path(\"../input/birdclef-2021/train_soundscape_labels.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:10.589549Z","iopub.execute_input":"2025-11-08T16:02:10.589809Z","iopub.status.idle":"2025-11-08T16:02:10.615594Z","shell.execute_reply.started":"2025-11-08T16:02:10.589794Z","shell.execute_reply":"2025-11-08T16:02:10.614829Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MelSpectrogramGenerator:\n    def __init__(self, sample_rate, n_mels, f_min, f_max, **kwargs):\n        self.sample_rate = sample_rate\n        self.n_mels = n_mels\n        self.f_min = f_min\n        self.f_max = f_max\n        kwargs[\"n_fft\"] = kwargs.get(\"n_fft\", self.sample_rate // 10)\n        kwargs[\"hop_length\"] = kwargs.get(\"hop_length\", self.sample_rate // (10 * 4))\n        self.kwargs = kwargs\n\n    def __call__(self, audio):\n        mel_spec = lb.feature.melspectrogram(\n            y=audio,\n            sr=self.sample_rate, \n            n_mels=self.n_mels, \n            fmin=self.f_min, \n            fmax=self.f_max, \n            **self.kwargs\n        )\n        mel_spec_db = lb.power_to_db(mel_spec).astype(np.float32)\n        return mel_spec_db","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:10.616699Z","iopub.execute_input":"2025-11-08T16:02:10.616960Z","iopub.status.idle":"2025-11-08T16:02:10.637637Z","shell.execute_reply.started":"2025-11-08T16:02:10.616940Z","shell.execute_reply":"2025-11-08T16:02:10.636513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def convert_to_grayscale(spectrogram, epsilon=1e-6, mean_val=None, std_val=None):\n    mean_val = mean_val or spectrogram.mean()\n    std_val = std_val or spectrogram.std()\n    normalized = (spectrogram - mean_val) / (std_val + epsilon)\n    \n    min_val, max_val = normalized.min(), normalized.max()\n\n    if (max_val - min_val) > epsilon:\n        clipped = np.clip(normalized, min_val, max_val)\n        scaled = 255 * (clipped - min_val) / (max_val - min_val)\n        uint8_array = scaled.astype(np.uint8)\n    else:\n        uint8_array = np.zeros_like(spectrogram, dtype=np.uint8)\n\n    return uint8_array\n\ndef adjust_audio_length(audio, target_length):\n    if len(audio) < target_length:\n        padding = target_length - len(audio)\n        audio = np.concatenate([audio, np.zeros(padding)])\n    elif len(audio) > target_length:\n        audio = audio[:target_length]\n    return audio\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:10.638904Z","iopub.execute_input":"2025-11-08T16:02:10.639132Z","iopub.status.idle":"2025-11-08T16:02:10.664628Z","shell.execute_reply.started":"2025-11-08T16:02:10.639112Z","shell.execute_reply":"2025-11-08T16:02:10.663857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BirdAudioDataset(Dataset):\n    def __init__(self, metadata, sr=SAMPLE_RATE, n_mels=128, f_min=0, f_max=None, \n                 duration=AUDIO_DURATION, step_size=None, resample_type=\"kaiser_fast\", \n                 do_resample=True):\n        \n        self.metadata = metadata\n        self.sample_rate = sr\n        self.n_mels = n_mels\n        self.f_min = f_min\n        self.f_max = f_max or self.sample_rate // 2\n        self.duration = duration\n        self.audio_length = self.duration * self.sample_rate\n        self.step_size = step_size or self.audio_length\n        self.resample_type = resample_type\n        self.do_resample = do_resample\n\n        self.mel_generator = MelSpectrogramGenerator(\n            sample_rate=self.sample_rate,\n            n_mels=self.n_mels,\n            f_min=self.f_min,\n            f_max=self.f_max\n        )\n\n    def __len__(self):\n        return len(self.metadata)\n    \n    @staticmethod\n    def normalize_image(image_array):\n        normalized = image_array.astype(\"float32\", copy=False) / 255.0\n        stacked = np.stack([normalized, normalized, normalized])\n        return stacked\n    \n    def convert_audio_to_image(self, audio_segment):\n        mel_spectrogram = self.mel_generator(audio_segment)\n        image = convert_to_grayscale(mel_spectrogram)\n        normalized_image = self.normalize_image(image)\n        return normalized_image\n\n    def load_and_process_audio(self, file_path):\n        audio_data, original_sr = sf.read(file_path, dtype=\"float32\")\n\n        if self.do_resample and original_sr != self.sample_rate:\n            audio_data = lb.resample(audio_data, original_sr, self.sample_rate, \n                                   res_type=self.resample_type)\n        \n        audio_segments = []\n        for i in range(self.audio_length, len(audio_data) + self.step_size, self.step_size):\n            start_idx = max(0, i - self.audio_length)\n            end_idx = start_idx + self.audio_length\n            segment = audio_data[start_idx:end_idx]\n            audio_segments.append(segment)\n            \n        if len(audio_segments[-1]) < self.audio_length:\n            audio_segments = audio_segments[:-1]\n            \n        spectrogram_images = [self.convert_audio_to_image(segment) for segment in audio_segments]\n        stacked_images = np.stack(spectrogram_images)\n        \n        return stacked_images\n    \n    def __getitem__(self, index):\n        return self.load_and_process_audio(self.metadata.loc[index, \"filepath\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:10.666492Z","iopub.execute_input":"2025-11-08T16:02:10.667247Z","iopub.status.idle":"2025-11-08T16:02:10.688756Z","shell.execute_reply.started":"2025-11-08T16:02:10.667182Z","shell.execute_reply":"2025-11-08T16:02:10.687593Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"audio_files_df = pd.DataFrame(\n    [(path.stem, *path.stem.split(\"_\"), path) for path in Path(TEST_AUDIO_PATH).glob(\"*.ogg\")],\n    columns=[\"filename\", \"id\", \"site\", \"date\", \"filepath\"]\n)\nprint(f\"Загружено файлов: {audio_files_df.shape[0]}\")\nprint(audio_files_df.head())\n\ntrain_metadata = pd.read_csv(\"../input/birdclef-2021/train_metadata.csv\")\nLABEL_MAPPING = {label: idx for idx, label in enumerate(sorted(train_metadata[\"primary_label\"].unique()))}\nREVERSE_LABEL_MAPPING = {v: k for k, v in LABEL_MAPPING.items()}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:10.690105Z","iopub.execute_input":"2025-11-08T16:02:10.690334Z","iopub.status.idle":"2025-11-08T16:02:10.915940Z","shell.execute_reply.started":"2025-11-08T16:02:10.690317Z","shell.execute_reply":"2025-11-08T16:02:10.914908Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = BirdAudioDataset(metadata=audio_files_df)\nprint(f\"Размер тестового датасета: {len(test_dataset)}\")\nprint(f\"Форма первого элемента: {test_dataset[0].shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:10.917270Z","iopub.execute_input":"2025-11-08T16:02:10.917482Z","iopub.status.idle":"2025-11-08T16:02:12.747685Z","shell.execute_reply.started":"2025-11-08T16:02:10.917467Z","shell.execute_reply":"2025-11-08T16:02:12.746498Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def initialize_model(model_checkpoint_path, num_classes=NUM_CLASSES):\n    model = resnest50(pretrained=False)\n    model.fc = nn.Linear(model.fc.in_features, num_classes)\n    \n    cpu_device = torch.device(\"cpu\")\n    checkpoint = torch.load(model_checkpoint_path, map_location=cpu_device)\n    \n    updated_state_dict = {}\n    for key in list(checkpoint.keys()):\n        updated_state_dict[key.replace(\"model.\", \"\")] = checkpoint[key]\n    \n    model.load_state_dict(updated_state_dict)\n    model = model.to(DEVICE)\n    model.eval()\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:12.748771Z","iopub.execute_input":"2025-11-08T16:02:12.749052Z","iopub.status.idle":"2025-11-08T16:02:12.754555Z","shell.execute_reply.started":"2025-11-08T16:02:12.749032Z","shell.execute_reply":"2025-11-08T16:02:12.753478Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_paths = [\n    Path(\"../input/kkiller-birdclef-models-public/birdclef_resnest50_fold0_epoch_10_f1_val_06471_20210417161101.pth\"),\n]\n\ntrained_models = [\n    initialize_model(model_path.as_posix()) for model_path in model_paths\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:12.756185Z","iopub.execute_input":"2025-11-08T16:02:12.756574Z","iopub.status.idle":"2025-11-08T16:02:13.149007Z","shell.execute_reply.started":"2025-11-08T16:02:12.756558Z","shell.execute_reply":"2025-11-08T16:02:13.148252Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef apply_threshold_to_predictions(predictions, threshold=None):\n    threshold = threshold or PREDICTION_THRESHOLD\n    sorted_indices = (-predictions).argsort(1)\n    num_positive = (predictions > threshold).sum(1)\n    \n    thresholded_predictions = []\n    for indices, count in zip(sorted_indices, num_positive):\n        thresholded_predictions.append(indices[:count].cpu().numpy().tolist())\n    \n    return thresholded_predictions\n\ndef convert_to_bird_names(prediction_indices):\n    bird_name_list = []\n    for pred in prediction_indices:\n        if not pred:\n            bird_name_list.append(\"nocall\")\n        else:\n            bird_names = [REVERSE_LABEL_MAPPING[bird_id] for bird_id in pred]\n            bird_name_list.append(\" \".join(bird_names))\n    return bird_name_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:13.149900Z","iopub.execute_input":"2025-11-08T16:02:13.150137Z","iopub.status.idle":"2025-11-08T16:02:13.155646Z","shell.execute_reply.started":"2025-11-08T16:02:13.150114Z","shell.execute_reply":"2025-11-08T16:02:13.154938Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_predictions(models, dataset, return_names=True):\n    all_predictions = []\n    \n    with torch.no_grad():\n        for idx in tqdm(range(len(dataset))):\n            batch_data = torch.from_numpy(dataset[idx]).to(DEVICE)\n            \n            ensemble_prediction = 0.0\n            for model in models:\n                output = model(batch_data)\n                probabilities = torch.sigmoid(output)\n                ensemble_prediction += probabilities\n\n            ensemble_prediction /= len(models)\n            \n            if return_names:\n                thresholded = apply_threshold_to_predictions(ensemble_prediction)\n                final_predictions = convert_to_bird_names(thresholded)\n            else:\n                final_predictions = ensemble_prediction\n\n            all_predictions.append(final_predictions)\n    \n    return all_predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:13.156307Z","iopub.execute_input":"2025-11-08T16:02:13.156513Z","iopub.status.idle":"2025-11-08T16:02:13.177502Z","shell.execute_reply.started":"2025-11-08T16:02:13.156493Z","shell.execute_reply":"2025-11-08T16:02:13.176441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"raw_predictions = generate_predictions(trained_models, test_dataset, return_names=False)\nprint(f\"Получено предсказаний: {len(raw_predictions)}\")\n\nfinal_predictions = [\n    convert_to_bird_names(apply_threshold_to_predictions(pred, threshold=PREDICTION_THRESHOLD))\n    for pred in raw_predictions\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:02:13.178658Z","iopub.execute_input":"2025-11-08T16:02:13.178861Z","iopub.status.idle":"2025-11-08T16:06:39.775233Z","shell.execute_reply.started":"2025-11-08T16:02:13.178847Z","shell.execute_reply":"2025-11-08T16:06:39.773866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_submission_dataframe(metadata, predictions):\n    submission_dict = {\n        \"row_id\": [],\n        \"birds\": [],\n    }\n    \n    for row, pred_list in zip(metadata.itertuples(False), predictions):\n        row_ids = [f\"{row.id}_{row.site}_{5*i}\" for i in range(1, len(pred_list)+1)]\n        submission_dict[\"birds\"].extend(pred_list)\n        submission_dict[\"row_id\"].extend(row_ids)\n    \n    submission_df = pd.DataFrame(submission_dict)\n    \n    if SUBMISSION_SAMPLE_PATH:\n        sample_submission = pd.read_csv(SUBMISSION_SAMPLE_PATH, usecols=[\"row_id\"])\n        submission_df = sample_submission.merge(submission_df, on=\"row_id\", how=\"left\")\n        submission_df[\"birds\"] = submission_df[\"birds\"].fillna(\"nocall\")\n    \n    return submission_df\n\nsubmission_df = create_submission_dataframe(audio_files_df, final_predictions)\n\nsubmission_df.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:06:39.776506Z","iopub.execute_input":"2025-11-08T16:06:39.776807Z","iopub.status.idle":"2025-11-08T16:06:39.793683Z","shell.execute_reply.started":"2025-11-08T16:06:39.776785Z","shell.execute_reply":"2025-11-08T16:06:39.791703Z"}},"outputs":[],"execution_count":null}]}