{"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":21669,"databundleVersionId":1692278,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install timm","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport librosa\nimport random\nimport numpy as np\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\n\nfrom skimage.transform import resize\nfrom PIL import Image\n\nimport torch\nimport torch.utils.data as torchdata\nfrom sklearn.model_selection import StratifiedKFold\nimport torch.nn as nn\nimport timm\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Константы\nTRAIN_TP = '/kaggle/input/rfcx-species-audio-detection/train_tp.csv'\nAUDIO_DATA = '/kaggle/input/rfcx-species-audio-detection/train/'\nWORKING_DIR = '/kaggle/working/'\nfft = 2048\nhop = 512\nsr = 48000\nlength = 10 * sr\nsave_to_disk = False\nnum_birds = 24\nbatch_size = 16","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Установка seed для воспроизводимости\nrng_seed = 1234\nrandom.seed(rng_seed)\nnp.random.seed(rng_seed)\nos.environ['PYTHONHASHSEED'] = str(rng_seed)\ntorch.manual_seed(rng_seed)\ntorch.cuda.manual_seed(rng_seed)\ntorch.backends.cudnn.deterministic = True\ntorch.backends.cudnn.benchmark = False","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Загрузка данных\ndf = pd.read_csv(TRAIN_TP)\nfmin = int(df['f_min'].min() * 0.9)\nfmax = int(df['f_max'].max() * 1.1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Функция для визуализации спектрограмм\ndef visualize_spectrograms(df, num_samples=2):\n    \"\"\"Визуализация случайных спектрограмм\"\"\"\n    plt.figure(figsize=(15, 5 * num_samples))\n    \n    for i in range(num_samples):\n        # Выбираем случайную запись\n        idx = np.random.randint(len(df))\n        row = df.iloc[idx]\n        \n        # Загружаем аудио\n        wav, sr = librosa.load(f\"{AUDIO_DATA}{row['recording_id']}.flac\", sr=None)\n        \n        # Вырезаем сегмент\n        t_min = float(row['t_min']) * sr\n        t_max = float(row['t_max']) * sr\n        center = np.round((t_min + t_max) / 2)\n        beginning = center - length / 2\n        if beginning < 0:\n            beginning = 0\n        \n        ending = beginning + length\n        if ending > len(wav):\n            ending = len(wav)\n            beginning = ending - length\n            \n        slice = wav[int(beginning):int(ending)]\n        \n        # Создаем спектрограмму\n        mel_spec = librosa.feature.melspectrogram(\n            y=slice, n_fft=fft, hop_length=hop, \n            sr=sr, fmin=fmin, fmax=fmax, power=1.5\n        )\n        \n        # Нормализуем\n        mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n        \n        # Визуализация\n        plt.subplot(num_samples, 2, i*2 + 1)\n        librosa.display.specshow(mel_spec_db, sr=sr, hop_length=hop, \n                                fmin=fmin, fmax=fmax, x_axis='time', y_axis='mel')\n        plt.colorbar(format='%+2.0f dB')\n        plt.title(f'Spectrogram {i+1}: Species {row[\"species_id\"]}')\n        \n        plt.subplot(num_samples, 2, i*2 + 2)\n        plt.imshow(mel_spec_db, aspect='auto', origin='lower')\n        plt.colorbar()\n        plt.title(f'Raw Mel Spectrogram {i+1}')\n        plt.xlabel('Time frames')\n        plt.ylabel('Mel bins')\n    \n    plt.tight_layout()\n    plt.savefig(f'{WORKING_DIR}/sample_spectrograms.png', dpi=100)\n    plt.show()\n\n# Выводим примеры спектрограмм\nprint(\"Визуализация примеров спектрограмм...\")\nvisualize_spectrograms(df, num_samples=2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Создание спектрограмм для обучения\nprint(\"\\nСоздание спектрограмм для обучения...\")\nfor idx, row in tqdm(df.iterrows(), total=min(100, len(df)), desc='Создание спектрограмм'):\n    wav, sr = librosa.load(f\"{AUDIO_DATA}{row['recording_id']}.flac\", sr=None)\n    \n    t_min = float(row['t_min']) * sr\n    t_max = float(row['t_max']) * sr\n    \n    center = np.round((t_min + t_max) / 2)\n    beginning = center - length / 2\n    if beginning < 0:\n        beginning = 0\n    \n    ending = beginning + length\n    if ending > len(wav):\n        ending = len(wav)\n        beginning = ending - length\n        \n    slice = wav[int(beginning):int(ending)]\n    \n    mel_spec = librosa.feature.melspectrogram(\n        y=slice, n_fft=fft, hop_length=hop, \n        sr=sr, fmin=fmin, fmax=fmax, power=1.5\n    )\n    mel_spec = resize(mel_spec, (224, 400))\n    \n    # Нормализация\n    mel_spec = mel_spec - np.min(mel_spec)\n    mel_spec = mel_spec / (np.max(mel_spec) + 1e-8)\n    mel_spec = mel_spec * 255\n    mel_spec = np.round(mel_spec)    \n    mel_spec = mel_spec.astype('uint8')\n    \n    # Сохранение\n    bmp = Image.fromarray(mel_spec, 'L')\n    bmp.save(f\"{WORKING_DIR}{row['recording_id']}_{row['species_id']}_{int(center)}.bmp\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Dataset\nclass RainforestDataset(torchdata.Dataset):\n    def __init__(self, filelist, augment=False):\n        self.specs = []\n        self.labels = []\n        self.augment = augment\n        \n        for f in tqdm(filelist, desc='Загрузка данных'):\n            label = int(str.split(f, '_')[1])\n            label_array = np.zeros(num_birds, dtype=np.float32)\n            label_array[label] = 1.0\n            self.labels.append(label_array)\n\n            img = Image.open(WORKING_DIR + f)\n            mel_spec = np.array(img, dtype=np.float32)\n            img.close()\n\n            # Нормализация\n            mel_spec = mel_spec / 255.0\n            \n            # Аугментация\n            if self.augment:\n                mel_spec = self._augment_spectrogram(mel_spec)\n            \n            # Конвертация в 3 канала\n            mel_spec = np.stack((mel_spec, mel_spec, mel_spec))\n            \n            self.specs.append(mel_spec)\n    \n    def _augment_spectrogram(self, spec):\n        \"\"\"Простая аугментация спектрограммы\"\"\"\n        if random.random() > 0.5:\n            # Случайное отражение по времени\n            spec = np.fliplr(spec)\n        \n        if random.random() > 0.5:\n            # Добавление шума\n            noise = np.random.normal(0, 0.02, spec.shape)\n            spec = spec + noise\n            spec = np.clip(spec, 0, 1)\n        \n        return spec\n    \n    def __len__(self):\n        return len(self.specs)\n    \n    def __getitem__(self, item):\n        spec = torch.tensor(self.specs[item], dtype=torch.float32)\n        label = torch.tensor(self.labels[item], dtype=torch.float32)\n        return spec, label","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Подготовка данных\nprint(\"\\nПодготовка данных...\")\nfile_list = []\nlabel_list = []\n\nfor f in os.listdir(WORKING_DIR):\n    if '.bmp' in f:\n        file_list.append(f)\n        label = str.split(f, '_')[1]\n        label_list.append(label)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Стратифицированное разделение\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=rng_seed)\ntrain_files = []\nval_files = []\n\nfor fold_id, (train_index, val_index) in enumerate(skf.split(file_list, label_list)):\n    if fold_id == 0:\n        train_files = np.take(file_list, train_index)\n        val_files = np.take(file_list, val_index)\n\nprint(f\"Train samples: {len(train_files)}, Val samples: {len(val_files)}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Создание датасетов и загрузчиков\ntrain_dataset = RainforestDataset(train_files, augment=True)\nval_dataset = RainforestDataset(val_files, augment=False)\n\ntrain_loader = torchdata.DataLoader(\n    train_dataset, batch_size=batch_size, \n    sampler=torchdata.RandomSampler(train_dataset),\n    num_workers=2\n)\nval_loader = torchdata.DataLoader(\n    val_dataset, batch_size=batch_size, \n    sampler=torchdata.SequentialSampler(val_dataset),\n    num_workers=2\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Модель\nclass EnhancedBirdClassifier(nn.Module):\n    def __init__(self, base_model_name='tf_efficientnet_b4_ns', num_classes=24):\n        super(EnhancedBirdClassifier, self).__init__()\n        \n        # Базовый претренированный энкодер\n        self.base_model = timm.create_model(\n            base_model_name, \n            pretrained=True,\n            num_classes=0,\n            global_pool=''\n        )\n        \n        # Размер фичей\n        with torch.no_grad():\n            dummy = torch.randn(1, 3, 224, 400)\n            features = self.base_model(dummy)\n            feature_size = features.shape[1]\n            spatial_size = features.shape[2] * features.shape[3]\n        \n        # Attention механизм\n        self.attention = nn.Sequential(\n            nn.Conv2d(feature_size, 128, kernel_size=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(128, 1, kernel_size=1),\n            nn.Sigmoid()\n        )\n        \n        # Классификатор с BatchNorm и Dropout\n        self.classifier = nn.Sequential(\n            nn.Linear(feature_size, 1024),\n            nn.BatchNorm1d(1024),\n            nn.ReLU(inplace=True),\n            nn.Dropout(p=0.3),\n            \n            nn.Linear(1024, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(inplace=True),\n            nn.Dropout(p=0.3),\n            \n            nn.Linear(512, num_classes)\n        )\n        \n        # Global pooling\n        self.global_pool = nn.AdaptiveAvgPool2d(1)\n    \n    def forward(self, x):\n        # Extract features\n        features = self.base_model(x)\n        \n        # Attention weights\n        attention_weights = self.attention(features)\n        \n        # Apply attention\n        attended_features = features * attention_weights\n        \n        # Global pooling\n        pooled = self.global_pool(attended_features)\n        pooled = pooled.view(pooled.size(0), -1)\n        \n        # Classification\n        output = self.classifier(pooled)\n        \n        return output, attention_weights\n\n# Инициализация модели\nprint(\"\\nИнициализация модели...\")\nmodel = EnhancedBirdClassifier(base_model_name='tf_efficientnet_b4_ns', num_classes=num_birds)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Оптимизатор и планировщик\noptimizer = torch.optim.AdamW(\n    model.parameters(), \n    lr=0.001, \n    weight_decay=0.01\n)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(\n    optimizer, \n    T_0=10, \n    T_mult=1, \n    eta_min=1e-6\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Функция потерь с учетом дисбаланса классов\nclass_counts = df['species_id'].value_counts().sort_index().values\nclass_weights = 1.0 / class_counts\nclass_weights = class_weights / class_weights.sum() * num_birds\npos_weights = torch.tensor(class_weights, dtype=torch.float32)\n\nloss_function = nn.BCEWithLogitsLoss(pos_weight=pos_weights)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Перемещение на GPU если доступно\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\nmodel = model.to(device)\nloss_function = loss_function.to(device)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Функция для расчета точности\ndef calculate_accuracy(outputs, targets):\n    \"\"\"Вычисляет точность предсказаний\"\"\"\n    with torch.no_grad():\n        preds = torch.sigmoid(outputs)\n        preds = (preds > 0.5).float()\n        correct = (preds == targets).float().sum()\n        accuracy = correct / targets.numel()\n    return accuracy.item()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Обучение модели\nprint(\"\\nНачало обучения...\")\nbest_val_accuracy = 0\ntrain_losses = []\nval_losses = []\ntrain_accuracies = []\nval_accuracies = []\n\nfor epoch in tqdm(range(30), desc='Эпохи'):\n    # Обучение\n    model.train()\n    epoch_train_loss = 0\n    epoch_train_acc = 0\n    \n    for batch_idx, (data, target) in enumerate(train_loader):\n        data, target = data.to(device), target.to(device)\n        \n        optimizer.zero_grad()\n        \n        output, _ = model(data)\n        loss = loss_function(output, target)\n        \n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n        optimizer.step()\n        \n        epoch_train_loss += loss.item()\n        epoch_train_acc += calculate_accuracy(output, target)\n    \n    train_losses.append(epoch_train_loss / len(train_loader))\n    train_accuracies.append(epoch_train_acc / len(train_loader))\n    \n    # Валидация\n    model.eval()\n    epoch_val_loss = 0\n    epoch_val_acc = 0\n    \n    with torch.no_grad():\n        for data, target in val_loader:\n            data, target = data.to(device), target.to(device)\n            \n            output, _ = model(data)\n            loss = loss_function(output, target)\n            \n            epoch_val_loss += loss.item()\n            epoch_val_acc += calculate_accuracy(output, target)\n    \n    val_losses.append(epoch_val_loss / len(val_loader))\n    val_accuracies.append(epoch_val_acc / len(val_loader))\n    \n    # Обновление планировщика\n    scheduler.step()\n    \n    # Сохранение лучшей модели\n    current_val_acc = val_accuracies[-1]\n    if current_val_acc > best_val_accuracy:\n        best_val_accuracy = current_val_acc\n        torch.save({\n            'epoch': epoch,\n            'model_state_dict': model.state_dict(),\n            'optimizer_state_dict': optimizer.state_dict(),\n            'val_accuracy': best_val_accuracy,\n            'train_losses': train_losses,\n            'val_losses': val_losses,\n        }, f'{WORKING_DIR}/best_model.pth')\n    \n    # Вывод статистики\n    if (epoch + 1) % 5 == 0:\n        print(f'\\nEpoch {epoch + 1}:')\n        print(f'Train Loss: {train_losses[-1]:.4f}, Train Acc: {train_accuracies[-1]:.4f}')\n        print(f'Val Loss: {val_losses[-1]:.4f}, Val Acc: {val_accuracies[-1]:.4f}')\n        print(f'Learning Rate: {scheduler.get_last_lr()[0]:.6f}')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Загрузка лучшей модели для тестирования\nprint(\"\\nЗагрузка лучшей модели для тестирования...\")\ncheckpoint = torch.load(f'{WORKING_DIR}/best_model.pth')\nmodel.load_state_dict(checkpoint['model_state_dict'])\nmodel.eval()\nprint(\"\\nПеревод модели в режим оценки...\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Функция для обработки тестовых файлов\ndef load_test_file(f):\n    wav, sr = librosa.load('/kaggle/input/rfcx-species-audio-detection/test/' + f, sr=None)\n\n    segments = len(wav) / length\n    segments = int(np.ceil(segments))\n    \n    mel_array = []\n    \n    for i in range(segments):\n        if (i + 1) * length > len(wav):\n            slice = wav[len(wav) - length:len(wav)]\n        else:\n            slice = wav[i * length:(i + 1) * length]\n\n        mel_spec = librosa.feature.melspectrogram(\n            y=slice, n_fft=fft, hop_length=hop, \n            sr=sr, fmin=fmin, fmax=fmax, power=1.5\n        )\n        mel_spec = resize(mel_spec, (224, 400))\n    \n        mel_spec = mel_spec - np.min(mel_spec)\n        mel_spec = mel_spec / (np.max(mel_spec) + 1e-8)\n        mel_spec = np.stack((mel_spec, mel_spec, mel_spec))\n\n        mel_array.append(mel_spec)\n    \n    return mel_array","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Обработка тестовых данных\nprint(\"\\nОбработка тестовых данных...\")\nresults = []\ntest_files = os.listdir('/kaggle/input/rfcx-species-audio-detection/test/')\n\nfor file_name in tqdm(test_files, desc='Processing test files'):\n    data = load_test_file(file_name)\n    data = torch.tensor(data).float().to(device)\n    \n    with torch.no_grad():\n        outputs = []\n        for segment in data:\n            segment = segment.unsqueeze(0)\n            output, _ = model(segment)\n            outputs.append(torch.sigmoid(output))\n        \n        aggregated = torch.stack(outputs).max(dim=0)[0]\n        \n    file_id = file_name.split('.')[0]\n    row = [file_id] + aggregated.cpu().numpy().flatten().tolist()\n    results.append(row)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Сохранение результатов\ncolumns = ['recording_id'] + [f's{i}' for i in range(num_birds)]\nsubmission_df = pd.DataFrame(results, columns=columns)\nsubmission_df.to_csv(f'{WORKING_DIR}/submission.csv', index=False)\n\nprint(f\"\\nСоздан файл submission.csv с {len(submission_df)} записями\")\nprint(\"Готово!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}