{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","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":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport librosa\nimport matplotlib.pyplot as plt\nimport soundfile as sf\nimport torch\nimport torch.nn as nn\n\nimport sys\nimport os\nimport shutil\n\nfrom pathlib import Path\nfrom dataclasses import dataclass\nfrom typing import List, Optional, Tuple\nfrom tqdm.notebook import tqdm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:30.184946Z","iopub.execute_input":"2026-01-20T18:14:30.185192Z","iopub.status.idle":"2026-01-20T18:14:37.718876Z","shell.execute_reply.started":"2026-01-20T18:14:30.185170Z","shell.execute_reply":"2026-01-20T18:14:37.718054Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import kagglehub\npath = kagglehub.dataset_download(\"ttahara/resnest50-fast-package\")\nprint(\"Path to dataset files:\", path)\npath = kagglehub.dataset_download(\"kneroma/kkiller-birdclef-models-public\")\nprint(\"Path to dataset files:\", path)\npackage_path = '../input/resnest50-fast-package/resnest-0.0.6b20200701/resnest'\n#check\nif Path(package_path).exists():\n    shutil.copytree(package_path, 'resnet', dirs_exist_ok=True)\n    sys.path.append('./resnet')\n    from resnest.torch import resnest50\nelse:\n    print(\"Package not found\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:37.720541Z","iopub.execute_input":"2026-01-20T18:14:37.720951Z","iopub.status.idle":"2026-01-20T18:14:39.157709Z","shell.execute_reply.started":"2026-01-20T18:14:37.720927Z","shell.execute_reply":"2026-01-20T18:14:39.157112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@dataclass\nclass Config:\n    num_classes: int = 397\n    sr: int = 32_000\n    duration: int = 5\n    threshold: float = 0.165\n    device: torch.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    test_audio_dir: Path = Path(\"../input/birdclef-2021/test_soundscapes\")\n    train_metadata: Path = Path(\"../input/birdclef-2021/train_metadata.csv\")\n    checkpoint_path: Path = Path(\"../input/kkiller-birdclef-models-public/birdclef_resnest50_fold0_epoch_10_f1_val_06471_20210417161101.pth\")\n\ncfg = Config()\nprint(f\"Device: {cfg.device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:39.161020Z","iopub.execute_input":"2026-01-20T18:14:39.161314Z","iopub.status.idle":"2026-01-20T18:14:39.218994Z","shell.execute_reply.started":"2026-01-20T18:14:39.161289Z","shell.execute_reply":"2026-01-20T18:14:39.218295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_metadata = pd.read_csv('/kaggle/input/birdclef-2021/train_metadata.csv')\ntrain_soundscape_labels = pd.read_csv('/kaggle/input/birdclef-2021/train_soundscape_labels.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:39.220849Z","iopub.execute_input":"2026-01-20T18:14:39.221328Z","iopub.status.idle":"2026-01-20T18:14:39.640116Z","shell.execute_reply.started":"2026-01-20T18:14:39.221299Z","shell.execute_reply":"2026-01-20T18:14:39.639585Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Basic statistics\ntotal_species = train_metadata['primary_label'].nunique()\ntotal_recordings = len(train_metadata)\ntotal_soundscapes = train_soundscape_labels['audio_id'].nunique()\n\nprint(f\"Total unique bird species: {total_species}\")\nprint(f\"Total short audio recordings: {total_recordings:,}\")\nprint(f\"Total soundscape files: {total_soundscapes}\")\nprint(f\"Total labeled 5-second windows: {len(train_soundscape_labels):,}\")\n\n# Class distribution analysis\nspecies_counts = train_metadata['primary_label'].value_counts()\nprint(f\"\\nCLASS DISTRIBUTION:\")\nprint(f\"Most common species: {species_counts.index[0]} ({species_counts.iloc[0]} recordings)\")\nprint(f\"Least common species: {species_counts.index[-1]} ({species_counts.iloc[-1]} recordings)\")\nprint(f\"Average recordings per species: {species_counts.mean():.1f}\")\nprint(f\"Median recordings per species: {species_counts.median():.0f}\")\n\n#топ 20\nfig, axes = plt.subplots(2, 2, figsize=(15, 12))\ntop_20 = species_counts.head(20)\naxes[0,0].barh(range(len(top_20)), top_20.values, color=plt.cm.viridis(np.linspace(0.2, 0.8, 20)))\naxes[0,0].set_yticks(range(len(top_20)))\naxes[0,0].set_yticklabels(top_20.index, fontsize=9)\naxes[0,0].set_xlabel('Number of Recordings')\naxes[0,0].set_title('Top 20 Species by Recording Count', fontweight='bold', pad=15)\naxes[0,0].invert_yaxis()\n\nfor i, v in enumerate(top_20.values):\n    axes[0,0].text(v + 5, i, str(v), va='center', fontsize=8)\n\n# distribution\naxes[0,1].hist(species_counts.values, bins=50, color='steelblue', edgecolor='black', alpha=0.7)\naxes[0,1].axvline(species_counts.mean(), color='red', linestyle='--', linewidth=2, \n                  label=f'Mean: {species_counts.mean():.1f}')\naxes[0,1].axvline(species_counts.median(), color='orange', linestyle='--', linewidth=2,\n                  label=f'Median: {species_counts.median():.0f}')\naxes[0,1].set_xlabel('Recordings per Species')\naxes[0,1].set_ylabel('Number of Species')\naxes[0,1].set_title('Distribution of Recordings per Species', fontweight='bold', pad=15)\naxes[0,1].legend()\naxes[0,1].grid(True, alpha=0.3)\n\nsorted_counts = np.sort(species_counts.values)[::-1]\ncumulative_percentage = np.cumsum(sorted_counts) / total_recordings * 100\naxes[1,0].plot(range(1, len(sorted_counts)+1), cumulative_percentage, \n               linewidth=2, color='darkgreen')\naxes[1,0].fill_between(range(1, len(sorted_counts)+1), 0, cumulative_percentage, \n                       alpha=0.2, color='green')\naxes[1,0].axhline(y=50, color='red', linestyle='--', alpha=0.7, \n                  label='50% of recordings')\naxes[1,0].axhline(y=80, color='orange', linestyle='--', alpha=0.7,\n                  label='80% of recordings')\n\n# percents\nidx_50 = np.where(cumulative_percentage >= 50)[0][0]\nidx_80 = np.where(cumulative_percentage >= 80)[0][0]\n\naxes[1,0].axvline(x=idx_50+1, color='red', linestyle=':', alpha=0.5)\naxes[1,0].axvline(x=idx_80+1, color='orange', linestyle=':', alpha=0.5)\n\naxes[1,0].set_xlabel('Number of Species (sorted by frequency)')\naxes[1,0].set_ylabel('Cumulative % of Recordings')\naxes[1,0].set_title('Cumulative Distribution of Recordings', fontweight='bold', pad=15)\naxes[1,0].legend()\naxes[1,0].grid(True, alpha=0.3)\n\n\nrecorders = train_metadata['secondary_labels'].value_counts().head(15)\naxes[1,1].barh(range(len(recorders)), recorders.values, \n               color=plt.cm.plasma(np.linspace(0.2, 0.8, len(recorders))))\naxes[1,1].set_yticks(range(len(recorders)))\naxes[1,1].set_yticklabels(recorders.index, fontsize=8)\naxes[1,1].set_xlabel('Number of Recordings Contributed')\naxes[1,1].set_title('Top 15 Contributors (Recordists)', fontweight='bold', pad=15)\naxes[1,1].invert_yaxis()\n\nplt.suptitle('Bird Species Classification Dataset - Class Distribution Analysis', \n             fontsize=16, fontweight='bold', y=1.02)\nplt.tight_layout()\nplt.show()\n\nprint(f\"\\nKEY FINDINGS FROM CLASS DISTRIBUTION:\")\nprint(\"-\"*45)\nprint(f\"1. Top {idx_50+1} species account for 50% of all recordings\")\nprint(f\"2. Top {idx_80+1} species account for 80% of all recordings\")\nprint(f\"3. {len(species_counts[species_counts <= 5])} species have 5 or fewer recordings\")\nprint(f\"4. {len(species_counts[species_counts <= 10])} species have 10 or fewer recordings\")\nprint(f\"5. Class imbalance ratio (max/min): {species_counts.iloc[0]/species_counts.iloc[-1]:.1f}x\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:39.640882Z","iopub.execute_input":"2026-01-20T18:14:39.641074Z","iopub.status.idle":"2026-01-20T18:14:40.541128Z","shell.execute_reply.started":"2026-01-20T18:14:39.641054Z","shell.execute_reply":"2026-01-20T18:14:40.540365Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# geography\nvalid_coords = train_metadata.dropna(subset=['latitude', 'longitude'])\nlat_range = (valid_coords['latitude'].min(), valid_coords['latitude'].max())\nlon_range = (valid_coords['longitude'].min(), valid_coords['longitude'].max())\n\nprint(f\"\\nGEOGRAPHIC COVERAGE:\")\nprint(f\"Latitude range: {lat_range[0]:.2f} to {lat_range[1]:.2f}\")\nprint(f\"Longitude range: {lon_range[0]:.2f} to {lon_range[1]:.2f}\")\nprint(f\"Recordings with coordinates ({len(valid_coords)/len(train_metadata)*100:.1f}%)\")\n\n# graph\nscatter = plt.scatter(valid_coords['longitude'], valid_coords['latitude'], \n                         c=valid_coords.index, cmap='viridis', alpha=0.6, \n                         s=20, edgecolors='black', linewidth=0.3)\nplt.xlabel('Longitude')\nplt.ylabel('Latitude')\nplt.title('Geographic Distribution of Recordings', fontweight='bold')\nplt.grid(True, alpha=0.3)\ncbar = plt.colorbar(scatter)\ncbar.set_label('Recording Density')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:40.542122Z","iopub.execute_input":"2026-01-20T18:14:40.542359Z","iopub.status.idle":"2026-01-20T18:14:41.749398Z","shell.execute_reply.started":"2026-01-20T18:14:40.542338Z","shell.execute_reply":"2026-01-20T18:14:41.748741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load sample audio files\naudio_samples = []\nsample_info = []\n\n# Try to load real audio files from the dataset structure\naudio_paths = [\n    '/kaggle/input/birdclef-2021/train_short_audio/acowoo/XC110258.ogg',  # Most common species\n    '/kaggle/input/birdclef-2021/train_soundscapes/10534_SSW_20170429.ogg'  # Soundscape file\n]\n\nfor i, audio_path in enumerate(audio_paths):\n    # Загрузка данных\n    audio, sr = librosa.load(audio_path, sr=32000, duration=3.0)  # Load 3 seconds max\n    audio_samples.append(audio)\n\n    duration = len(audio) / sr\n    rms = np.mean(librosa.feature.rms(y=audio)[0])\n    centroid = np.mean(librosa.feature.spectral_centroid(y=audio, sr=sr)[0])\n    sample_info.append({\n        'path': audio_path.split('/')[-1],\n        'duration': duration,\n        'rms': rms,\n        'centroid': centroid,\n        'sr': sr\n    })\n    print(f\"Loaded: {audio_path.split('/')[-1]}\")\n\nif audio_samples:\n    audio = audio_samples[0]\n    sr = sample_info[0]['sr']\n    \n    fig, axes = plt.subplots(3, 4, figsize=(16, 10))\n    \n    t = np.linspace(0, len(audio)/sr, len(audio))\n    axes[0,0].plot(t, audio, color='#1f77b4', linewidth=0.5, alpha=0.8)\n    axes[0,0].set_xlabel('Time (s)', fontsize=9)\n    axes[0,0].set_title(f'Waveform: {sample_info[0][\"path\"]}', fontsize=11, fontweight='bold')\n    axes[0,0].grid(True, alpha=0.3)\n    \n    S = librosa.stft(audio)\n    S_db = librosa.amplitude_to_db(np.abs(S), ref=np.max)\n    img1 = librosa.display.specshow(S_db, sr=sr, hop_length=512, x_axis='time', \n                                   y_axis='hz', ax=axes[0,1], cmap='magma')\n    axes[0,1].set_title('Spectrogram', fontsize=11, fontweight='bold')\n    plt.colorbar(img1, ax=axes[0,1], format='%+2.0f dB')\n    #\n    mel_spec = librosa.feature.melspectrogram(y=audio, sr=sr, n_mels=128)\n    mel_db = librosa.power_to_db(mel_spec, ref=np.max)\n    img2 = librosa.display.specshow(mel_db, sr=sr, hop_length=512, \n                                   x_axis='time', y_axis='mel', ax=axes[0,2], \n                                   cmap='viridis')\n    axes[0,2].set_title('Mel Spectrogram', fontsize=11, fontweight='bold')\n    plt.colorbar(img2, ax=axes[0,2], format='%+2.0f dB')\n    #\n    chroma = librosa.feature.chroma_stft(y=audio, sr=sr)\n    img3 = librosa.display.specshow(chroma, sr=sr, hop_length=512, \n                                   x_axis='time', y_axis='chroma', \n                                   ax=axes[0,3], cmap='coolwarm')\n    axes[0,3].set_title('Chromagram', fontsize=11, fontweight='bold')\n    plt.colorbar(img3, ax=axes[0,3])\n    #\n    mfccs = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=13)\n    img4 = librosa.display.specshow(mfccs, sr=sr, hop_length=512, \n                                   x_axis='time', ax=axes[1,0], cmap='plasma')\n    axes[1,0].set_title('MFCCs (13 coeff)', fontsize=11, fontweight='bold')\n    plt.colorbar(img4, ax=axes[1,0])\n    #\n    contrast = librosa.feature.spectral_contrast(y=audio, sr=sr)\n    img5 = librosa.display.specshow(contrast, sr=sr, hop_length=512, \n                                   x_axis='time', ax=axes[1,1], cmap='hot')\n    axes[1,1].set_title('Spectral Contrast', fontsize=11, fontweight='bold')\n    plt.colorbar(img5, ax=axes[1,1])\n    #\n    rolloff = librosa.feature.spectral_rolloff(y=audio, sr=sr)\n    img6 = librosa.display.specshow(rolloff, sr=sr, hop_length=512, \n                                   x_axis='time', ax=axes[1,2], cmap='summer')\n    axes[1,2].set_title('Spectral Rolloff', fontsize=11, fontweight='bold')\n    plt.colorbar(img6, ax=axes[1,2])\n    #\n    tonnetz = librosa.feature.tonnetz(y=audio, sr=sr)\n    img7 = librosa.display.specshow(tonnetz, sr=sr, hop_length=512, \n                                   x_axis='time', ax=axes[1,3], cmap='RdYlBu')\n    axes[1,3].set_title('Tonnetz', fontsize=11, fontweight='bold')\n    plt.colorbar(img7, ax=axes[1,3])\n    #\n    rms = librosa.feature.rms(y=audio)\n    times = librosa.times_like(rms, sr=sr, hop_length=512)\n    axes[2,0].plot(times, rms[0], color='darkgreen', linewidth=1.5)\n    axes[2,0].fill_between(times, 0, rms[0], alpha=0.3, color='lightgreen')\n    axes[2,0].set_xlabel('Time (s)', fontsize=9)\n    axes[2,0].set_title('RMS Energy', fontsize=11, fontweight='bold')\n    axes[2,0].grid(True, alpha=0.3)\n    #\n    zcr = librosa.feature.zero_crossing_rate(audio)\n    times_zcr = librosa.times_like(zcr, sr=sr, hop_length=512)\n    axes[2,1].plot(times_zcr, zcr[0], color='darkred', linewidth=1.5)\n    axes[2,1].fill_between(times_zcr, 0, zcr[0], alpha=0.3, color='lightcoral')\n    axes[2,1].set_xlabel('Time (s)', fontsize=9)\n    axes[2,1].set_title('Zero Crossing Rate', fontsize=11, fontweight='bold')\n    axes[2,1].grid(True, alpha=0.3)\n    #\n    centroid = librosa.feature.spectral_centroid(y=audio, sr=sr)\n    times_c = librosa.times_like(centroid, sr=sr, hop_length=512)\n    axes[2,2].plot(times_c, centroid[0], color='darkblue', linewidth=1.5)\n    axes[2,2].fill_between(times_c, 0, centroid[0], alpha=0.3, color='lightblue')\n    axes[2,2].set_xlabel('Time (s)', fontsize=9)\n    axes[2,2].set_title('Spectral Centroid', fontsize=11, fontweight='bold')\n    axes[2,2].grid(True, alpha=0.3)\n    #\n    onset_env = librosa.onset.onset_strength(y=audio, sr=sr)\n    tempogram = librosa.feature.tempogram(onset_envelope=onset_env, sr=sr)\n    img8 = librosa.display.specshow(tempogram, sr=sr, hop_length=512, \n                                   x_axis='time', y_axis='tempo', \n                                   ax=axes[2,3], cmap='RdGy')\n    axes[2,3].set_title('Tempogram', fontsize=11, fontweight='bold')\n    plt.colorbar(img8, ax=axes[2,3])\n    \n    plt.suptitle(f'Audio Analysis: {sample_info[0][\"path\"]}', \n                 fontsize=14, fontweight='bold', y=1.02)\n    plt.tight_layout()\n    plt.show()\n    \n    # Compare all loaded samples\n    if len(audio_samples) > 1:\n        fig2, axes2 = plt.subplots(1, 2, figsize=(15, 4))\n        \n        for i, (audio_sample, info) in enumerate(zip(audio_samples, sample_info)):\n            if i >= 3:\n                break\n                \n            # Plot spectrogram of each sample\n            S = librosa.stft(audio_sample[:sr*2])  # First 2 seconds\n            S_db = librosa.amplitude_to_db(np.abs(S), ref=np.max)\n            \n            img = librosa.display.specshow(S_db, sr=info['sr'], hop_length=512,\n                                         x_axis='time', y_axis='hz', \n                                         ax=axes2[i], cmap='magma')\n            \n            axes2[i].set_title(f'{info[\"path\"]}\\nCentroid: {info[\"centroid\"]:.0f}Hz', \n                              fontsize=10, fontweight='bold')\n            \n            if i == 2:\n                plt.colorbar(img, ax=axes2[i], format='%+2.0f dB')\n        \n        plt.suptitle('Comparison of Different Audio Samples', fontsize=12, fontweight='bold')\n        plt.tight_layout()\n        plt.show()\n\n    # Check for silence/pauses\n    energy = librosa.feature.rms(y=audio)[0]\n    silence_threshold = np.percentile(energy, 25)\n    silent_frames = np.sum(energy < silence_threshold)\n    print(f\"Silent frames: {silent_frames}/{len(energy)} ({silent_frames/len(energy)*100:.1f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:41.750351Z","iopub.execute_input":"2026-01-20T18:14:41.750669Z","iopub.status.idle":"2026-01-20T18:14:58.494283Z","shell.execute_reply.started":"2026-01-20T18:14:41.750638Z","shell.execute_reply":"2026-01-20T18:14:58.493690Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Processing\nclass AudioTransform:\n    def __init__(self, sr=32000, n_mels=128, fmin=0, fmax=None):\n        self.sr = sr\n        self.n_mels = n_mels\n        self.fmin = fmin\n        self.fmax = fmax or sr // 2\n        self.n_fft = sr // 10\n        self.hop_length = sr // 40  # (10 * 4)\n    def audio_to_melspec(self, audio: np.array) -> np.array:\n        melspec = librosa.feature.melspectrogram(\n            y=audio, \n            sr=self.sr, \n            n_mels=self.n_mels, \n            fmin=self.fmin, \n            fmax=self.fmax, \n            n_fft=self.n_fft,\n            hop_length=self.hop_length,\n        )\n        melspec = librosa.power_to_db(melspec).astype(np.float32)\n        return melspec\n\n    def to_image(self, melspec: np.array) -> np.array:\n        mean, std = melspec.mean(), melspec.std()\n        image = (melspec - mean) / (std + 1e-6)\n        _min, _max = image.min(), image.max()\n        if (_max - _min) > 1e-6:\n            image = np.clip(image, _min, _max)\n            image = 255 * (image - _min) / (_max - _min)\n        else:\n            image = np.zeros_like(image)\n            \n        image = image.astype(np.uint8)\n        image = np.stack([image, image, image]) \n        \n        return image.astype(\"float32\") / 255.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:58.495111Z","iopub.execute_input":"2026-01-20T18:14:58.495402Z","iopub.status.idle":"2026-01-20T18:14:58.502568Z","shell.execute_reply.started":"2026-01-20T18:14:58.495379Z","shell.execute_reply":"2026-01-20T18:14:58.501862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Dataset \nclass InferenceDataset:\n    def __init__(self, file_paths, cfg: Config):\n        self.file_paths = file_paths\n        self.cfg = cfg\n        self.transformer = AudioTransform(sr=cfg.sr)\n        self.chunk_len = cfg.duration * cfg.sr\n\n    def __len__(self):\n        return len(self.file_paths)\n\n    def read_audio(self, filepath):\n        audio, orig_sr = sf.read(filepath, dtype=\"float32\")\n        if orig_sr != self.cfg.sr:\n            audio = librosa.resample(audio, orig_sr, self.cfg.sr, res_type=\"kaiser_fast\")\n        return audio\n    def __getitem__(self, idx):\n        path = self.file_paths[idx]\n        audio = self.read_audio(path)\n        \n        chunks = []\n        for i in range(0, len(audio), self.chunk_len):\n            segment = audio[i : i + self.chunk_len]\n            # отбрасываем, если кусок короче 5 секунд (конец файла)\n            if len(segment) < self.chunk_len:\n                continue\n            \n            melspec = self.transformer.audio_to_melspec(segment)\n            image = self.transformer.to_image(melspec)\n            chunks.append(image)\n            \n        return np.stack(chunks) if chunks else np.array([])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:58.503523Z","iopub.execute_input":"2026-01-20T18:14:58.503812Z","iopub.status.idle":"2026-01-20T18:14:58.522848Z","shell.execute_reply.started":"2026-01-20T18:14:58.503778Z","shell.execute_reply":"2026-01-20T18:14:58.522104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_model(path: Path, device: torch.device):\n    \"\"\"Load ResNeSt50 and weights\"\"\"\n    model = resnest50(pretrained=False)\n    model.fc = nn.Linear(model.fc.in_features, cfg.num_classes)\n    \n    state_dict = torch.load(path, map_location='cpu')\n    clean_state_dict = {k.replace(\"model.\", \"\"): v for k, v in state_dict.items()}\n    model.load_state_dict(clean_state_dict)\n    \n    model.to(device)\n    model.eval()\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:58.523863Z","iopub.execute_input":"2026-01-20T18:14:58.524108Z","iopub.status.idle":"2026-01-20T18:14:58.542627Z","shell.execute_reply.started":"2026-01-20T18:14:58.524088Z","shell.execute_reply":"2026-01-20T18:14:58.541925Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_files = list(cfg.test_audio_dir.glob(\"*.ogg\"))\nif not test_files:\n    print(\"Test files not found, use train...\")\n    cfg.test_audio_dir = Path(\"../input/birdclef-2021/train_soundscapes\")\n    test_files = list(cfg.test_audio_dir.glob(\"*.ogg\"))[:5]\n\nprint(f\"Found files to process: {len(test_files)}\")\n\ndf_meta = pd.read_csv(cfg.train_metadata)\nLABELS = sorted(df_meta[\"primary_label\"].unique())\nID_TO_BIRD = {i: label for i, label in enumerate(LABELS)}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:58.543426Z","iopub.execute_input":"2026-01-20T18:14:58.543692Z","iopub.status.idle":"2026-01-20T18:14:58.849084Z","shell.execute_reply.started":"2026-01-20T18:14:58.543662Z","shell.execute_reply":"2026-01-20T18:14:58.848316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Prediction\nmodel = load_model(cfg.checkpoint_path, cfg.device)\ndataset = InferenceDataset(test_files, cfg)\n\npredictions = []\nrow_ids = []\n\nprint(\"Start classification...\")\nwith torch.no_grad():\n    for i in tqdm(range(len(dataset))):\n        batch_images = dataset[i]\n        if len(batch_images) == 0:\n            continue\n            \n        file_name = test_files[i].stem\n        file_parts = file_name.split(\"_\")\n        site = file_parts[1]\n        inputs = torch.from_numpy(batch_images).to(cfg.device)\n        logits = model(inputs)\n        probs = torch.sigmoid(logits).cpu().numpy()\n        \n        for chunk_idx, prob_vec in enumerate(probs):\n            seconds = (chunk_idx + 1) * 5\n            row_id = f\"{file_parts[0]}_{site}_{seconds}\"\n            # Фильтр по порогу\n            detected_indices = np.where(prob_vec > cfg.threshold)[0]\n            \n            if len(detected_indices) > 0:\n                birds = \" \".join([ID_TO_BIRD[idx] for idx in detected_indices])\n            else:\n                birds = \"nocall\"\n                \n            row_ids.append(row_id)\n            predictions.append(birds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:14:58.850004Z","iopub.execute_input":"2026-01-20T18:14:58.850215Z","iopub.status.idle":"2026-01-20T18:15:13.768245Z","shell.execute_reply.started":"2026-01-20T18:14:58.850195Z","shell.execute_reply":"2026-01-20T18:15:13.767374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Save\nsubmission = pd.DataFrame({\n    \"row_id\": row_ids,\n    \"birds\": predictions\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"File submission.csv saved.\")\nprint(submission.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:15:13.770609Z","iopub.execute_input":"2026-01-20T18:15:13.770875Z","iopub.status.idle":"2026-01-20T18:15:13.788987Z","shell.execute_reply.started":"2026-01-20T18:15:13.770851Z","shell.execute_reply":"2026-01-20T18:15:13.788198Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(submission.birds.unique())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:15:26.503788Z","iopub.execute_input":"2026-01-20T18:15:26.504507Z","iopub.status.idle":"2026-01-20T18:15:26.509056Z","shell.execute_reply.started":"2026-01-20T18:15:26.504479Z","shell.execute_reply":"2026-01-20T18:15:26.508171Z"}},"outputs":[],"execution_count":null}]}