{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.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":21669,"databundleVersionId":1692278,"sourceType":"competition"}],"dockerImageVersionId":30840,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Для решения задачи было решено использовать подход трансферного обучения. Для этого был установлен пакет `efficientnet_pytorch` с предварительно натренированной нейронной сетью.","metadata":{}},{"cell_type":"code","source":"!pip install efficientnet_pytorch ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-27T11:09:33.222409Z","iopub.execute_input":"2025-01-27T11:09:33.222723Z","iopub.status.idle":"2025-01-27T11:09:36.509627Z","shell.execute_reply.started":"2025-01-27T11:09:33.222698Z","shell.execute_reply":"2025-01-27T11:09:36.508592Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport numpy as np\nimport random\nimport copy\nimport warnings\nimport torch\nimport librosa\nimport csv\nimport os\nimport pandas as pd\n\nfrom skimage.transform import resize\nfrom skimage.filters import gaussian\nfrom skimage.color import rgb2gray\nfrom skimage import exposure, util\nfrom efficientnet_pytorch import EfficientNet\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\nfrom sklearn.model_selection import KFold","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-01-27T11:09:36.511195Z","iopub.execute_input":"2025-01-27T11:09:36.511441Z","iopub.status.idle":"2025-01-27T11:09:36.517146Z","shell.execute_reply.started":"2025-01-27T11:09:36.511418Z","shell.execute_reply":"2025-01-27T11:09:36.516252Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Создан класс `AudioData`, для хранения записей, информации о них и вспомогательных методов `addNoisy`, `contrast_stretching`, `randomGaussian`, `randomGamma`, `vertical_flip`, `horizontal_flip`, `addChannels` для обработки записей используемых в тренировке.\n\nТакже, добавлены функции:\n* `spec_to_image` - преобразования спектрограммы в нормализованное изображение\n* `get_model` - для получения ссылки на объект предварительно натренированной нейронной сети\n* `generate_submission` - для предсказания результатов тестовых данных и составления файла `submission.csv`","metadata":{}},{"cell_type":"code","source":"class AudioData(Dataset):\n    def __init__(self, X, y, data_type, audio_data, fmin, fmax, length):\n        self.data = []\n        self.labels = []\n        self.augs = [addNoisy, contrast_stretching, randomGaussian, randomGamma, vertical_flip, horizontal_flip, addChannels]\n        self.data_type = data_type\n        self.audio_data = audio_data\n        self.fmin = fmin\n        self.fmax = fmax\n        self.length = length\n\n        for i in range(0, len(X)):\n            recording_id = X[i]\n            label = y[i]\n            mel_spec = self.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.data_type == \"train\":\n            aug = random.choice(self.augs)\n            data = aug(self.data[idx])\n        else:\n            data = addChannels(self.data[idx])\n        return data, self.labels[idx]\n\ndef horizontal_flip(img):\n    horizontal_flip_img = img[:, ::-1]\n    return addChannels(horizontal_flip_img)\n\ndef vertical_flip(img):\n    vertical_flip_img = img[::-1, :]\n    return addChannels(vertical_flip_img)\n\ndef addNoisy(img):\n    noise_img = util.random_noise(img)\n    return addChannels(noise_img)\n\ndef contrast_stretching(img):\n    contrast_img = exposure.rescale_intensity(img)\n    return addChannels(contrast_img)\n\ndef randomGaussian(img):\n    gaussian_img = gaussian(img)\n    return addChannels(gaussian_img)\n\ndef grayScale(img):\n    gray_img = rgb2gray(img)\n    return addChannels(gray_img)\n\ndef randomGamma(img):\n    img_gamma = exposure.adjust_gamma(img)\n    return addChannels(img_gamma)\n\ndef addChannels(img):\n    return np.stack((img, img, img))\n\ndef spec_to_image(spec):\n    spec = resize(spec, (224, 400))\n    eps = 1e-6\n    mean = spec.mean()\n    std = spec.std()\n    spec_norm = (spec - mean) / (std + eps)\n    spec_min, spec_max = spec_norm.min(), spec_norm.max()\n    spec_scaled = 255 * (spec_norm - spec_min) / (spec_max - spec_min)\n    spec_scaled = spec_scaled.astype(np.uint8)\n    spec_scaled = np.asarray(spec_scaled)\n    return spec_scaled\n\ndef get_model(num_labels):\n    model = EfficientNet.from_pretrained('efficientnet-b0', num_classes=num_labels)\n    model = model.to(device)\n    return model\n\n\ndef generate_submission():\n    members = []\n    for i in range(1, nfold):\n        member_model = get_model(num_labels)\n        member_model.load_state_dict(torch.load('./model'+str(i)+'.pt'))\n        member_model.eval()\n        members.append(member_model)\n\n    print('Starting prediction loop')\n    with 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\n        for i in tqdm(list(range(0, len(test_files)))):\n            data = load_test_file(test_files[i])\n            data = torch.tensor(data)\n            data = 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)\n\n    print('Submission generated')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-27T11:09:36.519120Z","iopub.execute_input":"2025-01-27T11:09:36.519370Z","iopub.status.idle":"2025-01-27T11:09:36.539877Z","shell.execute_reply.started":"2025-01-27T11:09:36.519349Z","shell.execute_reply":"2025-01-27T11:09:36.539073Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Были заданы основные константы: количество классов, устройство для вычисления, скорость обучения, количество эпох, функция потерь, количество частей, на которые будет поделен датасет для обучения, частота сэмплирования, длина звукового сигнала в сэмплах, переменные для будущего вычисления минимальной и максимальной частот","metadata":{}},{"cell_type":"code","source":"num_labels = 24\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\nwarnings.filterwarnings('ignore')\nlearning_rate = 2e-4\nepochs = 20\nloss_fn = nn.CrossEntropyLoss()\nnfold = 5\nsr = 48000\nlength = 10 * sr\nfmin = 24000\nfmax = 0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-27T11:09:36.540965Z","iopub.execute_input":"2025-01-27T11:09:36.541214Z","iopub.status.idle":"2025-01-27T11:09:36.558509Z","shell.execute_reply.started":"2025-01-27T11:09:36.541194Z","shell.execute_reply":"2025-01-27T11:09:36.557912Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Чтение и подготовка данных для обучения","metadata":{}},{"cell_type":"code","source":"data = pd.read_csv(\"../input/rfcx-species-audio-detection/train_tp.csv\")\n\nfmin = 24000\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\nlabel_list = []\ndata_list = []\naudio_data = {}\nfor i in tqdm(list(range(0, 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    wav, sr = librosa.load('../input/rfcx-species-audio-detection/train/' + recording_id + '.flac', sr=None)\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    slice = wav[int(beginning):int(ending)]\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    \n    audio_data[recording_id] = img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-27T11:09:36.559255Z","iopub.execute_input":"2025-01-27T11:09:36.559463Z","iopub.status.idle":"2025-01-27T11:12:33.468558Z","shell.execute_reply.started":"2025-01-27T11:09:36.559444Z","shell.execute_reply":"2025-01-27T11:12:33.467624Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Определена функция для тренировки сети","metadata":{}},{"cell_type":"code","source":"def 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    postfix_label = \"\"\n    \n    for epoch in tqdm(range(1,epochs+1), postfix=postfix_label):\n        model.train()\n        batch_losses=[]\n        for _, data in enumerate(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        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        postfix_label = \"epoch = %d, train_loss = %.5f, val_loss = %.5f, val_accuracy = %.5f\" % (epoch, np.mean(train_losses[-1]), np.mean(valid_losses[-1]), accuracy)\n\n    model.load_state_dict(best_model_wts)\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-27T11:12:33.469640Z","iopub.execute_input":"2025-01-27T11:12:33.470046Z","iopub.status.idle":"2025-01-27T11:12:33.478566Z","shell.execute_reply.started":"2025-01-27T11:12:33.469992Z","shell.execute_reply":"2025-01-27T11:12:33.477707Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Загрузка данных и тренировка ","metadata":{}},{"cell_type":"code","source":"skf = KFold(n_splits=nfold, shuffle=True, random_state=32)\n\nfor fold_id, (train_index, val_index) in tqdm(list(enumerate(skf.split(data_list, label_list)))):\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    train_data = AudioData(X_train, y_train, \"train\", audio_data, fmin, fmax, length)\n    valid_data = AudioData(X_val, y_val, \"valid\", audio_data, fmin, fmax, length)\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    model = get_model(num_labels)\n    optimizer = torch.optim.Adam(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    torch.save(model.state_dict(), \"./model\" + str(fold_id) + \".pt\")\n    \n    del train_data, valid_data, train_loader, valid_loader, model, X_train, X_val, y_train, y_val","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-27T11:12:33.479534Z","iopub.execute_input":"2025-01-27T11:12:33.479840Z","iopub.status.idle":"2025-01-27T11:29:28.626927Z","shell.execute_reply.started":"2025-01-27T11:12:33.479808Z","shell.execute_reply":"2025-01-27T11:29:28.626125Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Функция для загрузки файла из тестового датасета и получение необходимых признаков","metadata":{}},{"cell_type":"code","source":"def load_test_file(f):\n    wav, sr = librosa.load('../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-01-27T11:29:28.628651Z","iopub.execute_input":"2025-01-27T11:29:28.628876Z","iopub.status.idle":"2025-01-27T11:29:28.634371Z","shell.execute_reply.started":"2025-01-27T11:29:28.628856Z","shell.execute_reply":"2025-01-27T11:29:28.633558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"generate_submission()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-27T11:29:28.635196Z","iopub.execute_input":"2025-01-27T11:29:28.635399Z","execution_failed":"2025-01-27T12:25:14.433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.remove('./model0.pt')\nos.remove('./model1.pt')\nos.remove('./model2.pt')\nos.remove('./model3.pt')\nos.remove('./model4.pt')","metadata":{"trusted":true,"execution":{"execution_failed":"2025-01-27T12:25:14.433Z"}},"outputs":[],"execution_count":null}]}