{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":21669,"databundleVersionId":1692278,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch.nn as nn\nimport numpy as np\nimport torch\nimport librosa\nimport os\nimport torchvision\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import KFold\nfrom skimage.transform import resize\nfrom skimage.filters import gaussian\nfrom skimage.color import rgb2gray\nfrom skimage import exposure, util\nimport pandas as pd\nimport copy\nfrom tqdm import tqdm\nimport random\nimport csv\nimport scipy\nimport warnings","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:55:55.417784Z","iopub.execute_input":"2025-11-30T19:55:55.418525Z","iopub.status.idle":"2025-11-30T19:55:55.422675Z","shell.execute_reply.started":"2025-11-30T19:55:55.418500Z","shell.execute_reply":"2025-11-30T19:55:55.422084Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = 'cuda:0' if torch.cuda.is_available() else 'cpu'\nsr = 48000\nlength = 10 * sr\n\ndata = pd.read_csv(\"../input/rfcx-species-audio-detection/train_tp.csv\")\nfmin = sr / 2\nfmax = 0\nfor i in range(0, len(data)):\n    if fmin > float(data.iloc[i]['f_min']):\n        fmin = float(data.iloc[i]['f_min'])\n    if fmax < float(data.iloc[i]['f_max']):\n        fmax = float(data.iloc[i]['f_max'])\n        \nfmin = int(fmin * 0.9)\nfmax = int(fmax * 1.1)\n\nprint(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:53:55.353462Z","iopub.execute_input":"2025-11-30T19:53:55.354380Z","iopub.status.idle":"2025-11-30T19:53:55.563953Z","shell.execute_reply.started":"2025-11-30T19:53:55.354339Z","shell.execute_reply":"2025-11-30T19:53:55.563101Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def spec_to_image(spec):\n    spec = resize(spec, (224, 400))\n    spec_norm = (spec - spec.mean()) / (spec.std() + 1e-6)\n    spec_scaled = 255 * (spec_norm - spec_norm.min()) / (spec_norm.max() - spec_norm.min())\n    return np.asarray(spec_scaled.astype(np.uint8))\n\n# фильтр низких частот, чтобы убрать насекомых\ndef load_and_filter_audio(file_path, sr=None, remove_insect_freq=10000):\n    wav, sr = librosa.load(file_path, sr=sr)\n    \n    sos = scipy.signal.butter(4, remove_insect_freq, btype='lowpass', fs=sr, output='sos')\n    wav = scipy.signal.sosfilt(sos, wav)\n    \n    return wav, sr","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:53:59.217058Z","iopub.execute_input":"2025-11-30T19:53:59.217706Z","iopub.status.idle":"2025-11-30T19:53:59.222997Z","shell.execute_reply.started":"2025-11-30T19:53:59.217677Z","shell.execute_reply":"2025-11-30T19:53:59.222099Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def convert_to_rgb(img):\n    return np.stack((img, img, img))\n\ndef add_noise(img):\n    return convert_to_rgb(util.random_noise(img))\n\ndef enhance_contrast(img):\n    return convert_to_rgb(exposure.rescale_intensity(img))\n\ndef apply_gaussian_blur(img):\n    return convert_to_rgb(gaussian(img))\n\ndef adjust_gamma_correction(img):\n    return convert_to_rgb(exposure.adjust_gamma(img))\n\nclass RFCXDataset(Dataset):\n    def __init__(self, X, y, is_train):\n        self.data = []\n        self.labels = []\n        self.augs = [add_noise, enhance_contrast, apply_gaussian_blur, adjust_gamma_correction, convert_to_rgb, convert_to_rgb]\n        self.is_train=is_train\n        for i in range(0, len(X)):\n            recording_id = X[i]\n            label = y[i]\n            mel_spec = audio_data[recording_id]\n            self.data.append(mel_spec)\n            self.labels.append(label)\n                \n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        if self.is_train:\n            data = random.choice(self.augs)(self.data[idx])\n        else:\n            data = convert_to_rgb(self.data[idx])\n        return data, self.labels[idx]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:54:02.334619Z","iopub.execute_input":"2025-11-30T19:54:02.334883Z","iopub.status.idle":"2025-11-30T19:54:02.341782Z","shell.execute_reply.started":"2025-11-30T19:54:02.334864Z","shell.execute_reply":"2025-11-30T19:54:02.341036Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_and_process_data(data, fmin, fmax, length):\n    \"\"\"\n    Загружает аудиоданные и создает мел-спектрограммы\n    \"\"\"\n    label_list = []\n    data_list = []\n    audio_data = {}\n    \n    for i in range(len(data)):\n        recording_id = data.recording_id.values[i]\n        species_id = int(data.species_id.values[i])\n        data_list.append(recording_id)\n        label_list.append(species_id)\n\n        # загрузка и фильтрация аудио\n        wav, sr = load_and_filter_audio('../input/rfcx-species-audio-detection/train/' + recording_id + '.flac', sr=None)\n        \n        # вычисление временного сегмента\n        t_min = float(data.t_min.values[i]) * sr\n        t_max = float(data.t_max.values[i]) * sr\n        center = np.round((t_min + t_max) / 2)\n        beginning = center - length / 2\n        if beginning < 0:\n            beginning = 0\n        ending = beginning + length\n        if ending > len(wav):\n            ending = len(wav)\n            beginning = ending - length\n        \n        # извлечение сегмента и создание спектрограммы\n        slice = wav[int(beginning):int(ending)]\n        spec = librosa.feature.melspectrogram(y=slice, sr=sr, fmin=fmin, fmax=fmax)\n        spec_db = librosa.power_to_db(spec, top_db=80)\n        audio_data[recording_id] = spec_to_image(spec_db)\n    \n    return data_list, label_list, audio_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:54:06.381052Z","iopub.execute_input":"2025-11-30T19:54:06.381607Z","iopub.status.idle":"2025-11-30T19:54:06.388373Z","shell.execute_reply.started":"2025-11-30T19:54:06.381580Z","shell.execute_reply":"2025-11-30T19:54:06.387513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learning_rate = 1e-4\nepochs = 14\nloss_fn = nn.CrossEntropyLoss()\n\ndef train(model, loss_fn, train_loader, valid_loader, epochs, optimizer, scheduler):\n    best_model_wts = copy.deepcopy(model.state_dict())\n    best_acc = 0.0\n    train_losses = []\n    valid_losses = []\n    \n    for epoch in range(1,epochs+1):\n        model.train()\n        batch_losses=[]\n        for _, data in enumerate(tqdm(train_loader)):\n            x, y = data\n            optimizer.zero_grad()\n            x = x.to(device, dtype=torch.float32)\n            y = y.to(device, dtype=torch.long)\n            y_hat = model(x)\n            loss = loss_fn(y_hat, y)\n            loss.backward()\n            batch_losses.append(loss.item())\n            optimizer.step()\n        train_losses.append(batch_losses)\n\n        model.eval()\n        batch_losses=[]\n        trace_y = []\n        trace_yhat = []\n        \n        for _, data in enumerate(valid_loader):\n            x, y = data\n            x = x.to(device, dtype=torch.float32)\n            y = y.to(device, dtype=torch.long)\n            y_hat = model(x)\n            loss = loss_fn(y_hat, y)\n            trace_y.append(y.cpu().detach().numpy())\n            trace_yhat.append(y_hat.cpu().detach().numpy())      \n            batch_losses.append(loss.item())\n        valid_losses.append(batch_losses)\n        trace_y = np.concatenate(trace_y)\n        trace_yhat = np.concatenate(trace_yhat)\n        accuracy = np.mean(trace_yhat.argmax(axis=1)==trace_y)\n        \n        print(\"epoch = %d, val_accuracy = %.5f\" % (epoch, accuracy))\n\n        scheduler.step(np.mean(valid_losses[-1]))\n        if accuracy > best_acc:\n            best_acc = accuracy\n            best_model_wts = copy.deepcopy(model.state_dict())\n\n    model.load_state_dict(best_model_wts)\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:54:09.970095Z","iopub.execute_input":"2025-11-30T19:54:09.970361Z","iopub.status.idle":"2025-11-30T19:54:09.978343Z","shell.execute_reply.started":"2025-11-30T19:54:09.970340Z","shell.execute_reply":"2025-11-30T19:54:09.977793Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def initialize_model():\n    model = torchvision.models.efficientnet_b2(weights=torchvision.models.EfficientNet_B2_Weights.DEFAULT)\n    num_features = model.classifier[1].in_features\n    model.classifier = torch.nn.Sequential(\n        torch.nn.Dropout(p=0.2, inplace=True),\n        torch.nn.Linear(num_features, 24)\n    )\n    model.to(device)\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:54:43.471715Z","iopub.execute_input":"2025-11-30T19:54:43.472009Z","iopub.status.idle":"2025-11-30T19:54:43.475868Z","shell.execute_reply.started":"2025-11-30T19:54:43.471987Z","shell.execute_reply":"2025-11-30T19:54:43.475293Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_with_cross_validation(data_list, label_list, fold_num=5, epochs=14, learning_rate=1e-4):\n    \"\"\"\n    Выполняет кросс-валидацию и обучение моделей\n    \"\"\"\n    skf = KFold(n_splits=fold_num, shuffle=True, random_state=32)\n    \n    for fold_id, (train_index, val_index) in enumerate(skf.split(data_list, label_list)):\n        print(f\"Training fold {fold_id + 1}/{fold_num}\")\n        \n        # разделение данных\n        X_train = np.take(data_list, train_index)\n        y_train = np.take(label_list, train_index, axis=0)\n        X_val = np.take(data_list, val_index)\n        y_val = np.take(label_list, val_index, axis=0)\n\n        # создание даталоадеров\n        train_data = RFCXDataset(X_train, y_train, True)\n        valid_data = RFCXDataset(X_val, y_val, False)\n        train_loader = DataLoader(train_data, batch_size=8, shuffle=True, drop_last=True)\n        valid_loader = DataLoader(valid_data, batch_size=8, shuffle=True, drop_last=True)\n\n        # инициализация модели\n        model = initialize_model()\n        \n        # обучение\n        optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)\n        scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=3)\n        model = train(model, loss_fn, train_loader, valid_loader, epochs, optimizer, scheduler)\n        \n        # сохранение модели\n        torch.save(model.state_dict(), f\"./efficientnet_{fold_id}.pt\")\n        \n        # очистка памяти\n        del train_data, valid_data, train_loader, valid_loader, model, X_train, X_val, y_train, y_val\n        torch.cuda.empty_cache()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:54:47.840320Z","iopub.execute_input":"2025-11-30T19:54:47.841016Z","iopub.status.idle":"2025-11-30T19:54:47.847553Z","shell.execute_reply.started":"2025-11-30T19:54:47.840987Z","shell.execute_reply":"2025-11-30T19:54:47.846906Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_ensemble_model(fold_num=5):\n    \"\"\"\n    Создает ансамбль моделей из сохраненных весов\n    \"\"\"\n    ensemble_members = []\n    \n    for i in range(fold_num):\n        model = initialize_model()\n        model.load_state_dict(torch.load(f'./efficientnet_{i}.pt'))\n        model.eval()\n        ensemble_members.append(model)\n    \n    return ensemble_members\n\ndef cleanup_model_files(fold_num=5):\n    for i in range(fold_num):\n        os.remove(f'./efficientnet_{i}.pt')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:54:51.432613Z","iopub.execute_input":"2025-11-30T19:54:51.433284Z","iopub.status.idle":"2025-11-30T19:54:51.437435Z","shell.execute_reply.started":"2025-11-30T19:54:51.433256Z","shell.execute_reply":"2025-11-30T19:54:51.436766Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_test_file(f):\n    wav, sr = load_and_filter_audio('../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(0, 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        spec=librosa.feature.melspectrogram(y=slice, sr=sr, fmin=fmin, fmax=fmax)\n        spec_db=librosa.power_to_db(spec,top_db=80)\n\n        img = spec_to_image(spec_db)\n        mel_spec = np.stack((img, img, img))\n        mel_array.append(mel_spec)\n    \n    return mel_array","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:54:53.742343Z","iopub.execute_input":"2025-11-30T19:54:53.743073Z","iopub.status.idle":"2025-11-30T19:54:53.748270Z","shell.execute_reply.started":"2025-11-30T19:54:53.743047Z","shell.execute_reply":"2025-11-30T19:54:53.747464Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"warnings.filterwarnings('ignore', category=FutureWarning, module='librosa')\nwarnings.filterwarnings('ignore', category=UserWarning, message='PySoundFile failed*')\ndata_list, label_list, audio_data = load_and_process_data(data, fmin, fmax, length)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T19:56:00.867669Z","iopub.execute_input":"2025-11-30T19:56:00.867921Z","iopub.status.idle":"2025-11-30T19:59:21.097047Z","shell.execute_reply.started":"2025-11-30T19:56:00.867904Z","shell.execute_reply":"2025-11-30T19:59:21.096353Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# обучение\ntrain_with_cross_validation(data_list, label_list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T20:00:06.452783Z","iopub.execute_input":"2025-11-30T20:00:06.453529Z","iopub.status.idle":"2025-11-30T20:17:23.877521Z","shell.execute_reply.started":"2025-11-30T20:00:06.453496Z","shell.execute_reply":"2025-11-30T20:17:23.876713Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# создание ансамбля моделей\nfold_num = 5\nmembers = create_ensemble_model(fold_num)\ncleanup_model_files(fold_num)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T20:18:38.648536Z","iopub.execute_input":"2025-11-30T20:18:38.648840Z","iopub.status.idle":"2025-11-30T20:18:40.999299Z","shell.execute_reply.started":"2025-11-30T20:18:38.648819Z","shell.execute_reply":"2025-11-30T20:18:40.998748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# создание файла submission\nwith open('submission.csv', 'w', newline='') as csvfile:\n    submission_writer = csv.writer(csvfile, delimiter=',')\n    submission_writer.writerow(['recording_id','s0','s1','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11',\n                               's12','s13','s14','s15','s16','s17','s18','s19','s20','s21','s22','s23'])\n    \n    test_files = os.listdir('../input/rfcx-species-audio-detection/test/')\n    print(len(test_files))\n    \n    for i in range(0, len(test_files)):\n        data_arrays = load_test_file(test_files[i])\n        data = np.array(data_arrays)\n        data = torch.from_numpy(data).float()\n        if torch.cuda.is_available():\n            data = data.cuda()\n\n        output_list = []\n        for m in members:\n            output = m(data)\n            maxed_output = torch.max(output, dim=0)[0]\n            maxed_output = maxed_output.cpu().detach()\n            output_list.append(maxed_output)\n        avg_maxed_output = torch.mean(torch.stack(output_list), dim=0)\n        \n        file_id = str.split(test_files[i], '.')[0]\n        write_array = [file_id]\n        \n        for out in avg_maxed_output:\n            write_array.append(out.item())\n    \n        submission_writer.writerow(write_array)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T20:46:48.855285Z","iopub.execute_input":"2025-11-30T20:46:48.856013Z","iopub.status.idle":"2025-11-30T21:03:44.428750Z","shell.execute_reply.started":"2025-11-30T20:46:48.855981Z","shell.execute_reply":"2025-11-30T21:03:44.428072Z"}},"outputs":[],"execution_count":null}]}