{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.14"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21669,"databundleVersionId":1692278,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":2613.429287,"end_time":"2024-11-22T18:29:01.506360","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2024-11-22T17:45:28.077073","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"d45d8445","cell_type":"code","source":"import torch.nn as nn\nimport numpy as np\nimport copy\nimport warnings\nimport torch\nimport librosa\nimport csv\nimport random\nimport os\nimport pandas as pd\n\nfrom skimage.transform import resize\nfrom skimage.filters import gaussian\nfrom skimage import exposure, util\nfrom torchvision.models import resnet50\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\nfrom sklearn.model_selection import KFold\n\nwarnings.filterwarnings('ignore')","metadata":{"execution":{"iopub.execute_input":"2024-11-22T17:45:30.513008Z","iopub.status.busy":"2024-11-22T17:45:30.512645Z","iopub.status.idle":"2024-11-22T17:45:36.838914Z","shell.execute_reply":"2024-11-22T17:45:36.838219Z"},"papermill":{"duration":6.332244,"end_time":"2024-11-22T17:45:36.840835","exception":false,"start_time":"2024-11-22T17:45:30.508591","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"609e1526","cell_type":"code","source":"LABELS = 24\nSR = 48000\nLENGTH = 10 * SR\nF_MIN = 24000\nF_MAX = 0\nLEARNING_RATE = 2e-4\nEPOCHS = 20\nN_FOLD = 5\n\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\nprint(device)","metadata":{"execution":{"iopub.execute_input":"2024-11-22T17:45:36.854209Z","iopub.status.busy":"2024-11-22T17:45:36.853784Z","iopub.status.idle":"2024-11-22T17:45:36.913713Z","shell.execute_reply":"2024-11-22T17:45:36.912784Z"},"papermill":{"duration":0.065602,"end_time":"2024-11-22T17:45:36.915522","exception":false,"start_time":"2024-11-22T17:45:36.849920","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"d61b9a8c","cell_type":"code","source":"class AudioAugmentations:\n    def __init__(self):\n        self.augs = [self.add_noise, self.contrast_stretch, self.h_flip, self.v_flip]\n\n    def h_flip(self, image):\n        return np.stack([image[:, ::-1]] * 3)\n\n    def v_flip(self, image):\n        return np.stack([image[::-1, :]] * 3)\n\n    def add_noise(self, image):\n        noise_img = util.random_noise(image)\n        return np.stack([noise_img] * 3)\n\n    def contrast_stretch(self, image):\n        contrast_img = exposure.rescale_intensity(image)\n        return np.stack([contrast_img] * 3)\n\n    def apply_random_augmentation(self, image):\n        aug_func = random.choice(self.augs)\n        return aug_func(image)\n\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\n\ndef get_model():\n    model = resnet50(pretrained=True)\n    num_ftrs = model.fc.in_features\n    model.fc = nn.Linear(num_ftrs, LABELS)\n    return model.to(device)","metadata":{"execution":{"iopub.execute_input":"2024-11-22T17:45:36.929794Z","iopub.status.busy":"2024-11-22T17:45:36.929537Z","iopub.status.idle":"2024-11-22T17:45:36.937568Z","shell.execute_reply":"2024-11-22T17:45:36.936750Z"},"papermill":{"duration":0.013481,"end_time":"2024-11-22T17:45:36.939243","exception":false,"start_time":"2024-11-22T17:45:36.925762","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"ebff3e8b","cell_type":"code","source":"data = pd.read_csv(\"/kaggle/input/rfcx-species-audio-detection/train_tp.csv\")\nfor i in range(0, len(data)):\n    if F_MIN > float(data.iloc[i]['f_min']):\n        F_MIN = float(data.iloc[i]['f_min'])\n    if F_MAX < float(data.iloc[i]['f_max']):\n        F_MAX = float(data.iloc[i]['f_max'])\n\n# f_min_local = data['f_min'].min() * 0.9\n# f_max_local = data['f_max'].max() * 1.1\nF_MIN = int(F_MIN * 0.9)\nF_MAX = int(F_MAX * 1.1)\n\nlabel_list = data['species_id'].tolist()\ndata_list = data['recording_id'].tolist()\naudio_data = {}\n\ndef process_audio(i):\n    recording_id = data_list[i]\n    species_id = label_list[i]\n    wav, sr = librosa.load(f'/kaggle/input/rfcx-species-audio-detection/train/{recording_id}.flac', sr=None)\n    t_min = int(data.at[i, 't_min'] * sr)\n    t_max = int(data.at[i, 't_max'] * sr)\n    \n    center = np.round((t_min + t_max) / 2)\n    beginning = max(center - LENGTH // 2, 0)\n    ending = min(beginning + LENGTH, len(wav))\n    beginning = ending - LENGTH if ending - beginning < LENGTH else beginning\n    \n    slice = wav[int(beginning):int(ending)]\n    \n    spec = librosa.feature.melspectrogram(y=slice, sr=sr, fmin=F_MIN, fmax=F_MAX)\n    spec_db = librosa.power_to_db(spec, top_db=80)\n\n    img = spec_to_image(spec_db)\n    return recording_id, img","metadata":{"execution":{"iopub.execute_input":"2024-11-22T17:45:36.952367Z","iopub.status.busy":"2024-11-22T17:45:36.952107Z","iopub.status.idle":"2024-11-22T17:45:37.071705Z","shell.execute_reply":"2024-11-22T17:45:37.071036Z"},"papermill":{"duration":0.125448,"end_time":"2024-11-22T17:45:37.073696","exception":false,"start_time":"2024-11-22T17:45:36.948248","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"287d8e6c","cell_type":"code","source":"from concurrent.futures import ThreadPoolExecutor\n\nwith ThreadPoolExecutor() as executor:\n    results = list(executor.map(process_audio, range(len(data))))\n\nfor recording_id, img in results:\n    audio_data[recording_id] = img","metadata":{"execution":{"iopub.execute_input":"2024-11-22T17:45:37.081615Z","iopub.status.busy":"2024-11-22T17:45:37.081116Z","iopub.status.idle":"2024-11-22T17:47:32.683342Z","shell.execute_reply":"2024-11-22T17:47:32.682144Z"},"papermill":{"duration":115.610908,"end_time":"2024-11-22T17:47:32.688115","exception":false,"start_time":"2024-11-22T17:45:37.077207","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"81b9692d","cell_type":"code","source":"class AudioData(Dataset):\n    def __init__(self, X, y, data_type, augmentations=None):\n        self.X = X\n        self.y = y\n        self.data_type = data_type\n        self.audio_data = audio_data\n        self.augmentations = augmentations\n\n    def __len__(self):\n        return len(self.X)\n\n    def __getitem__(self, idx):\n        recording_id = self.X[idx]\n        label = self.y[idx]\n        \n        # Извлекаем аудиоданные из словаря\n        img = self.audio_data[recording_id]\n\n        # Аугментации (применяются только для тренировочных данных)\n        if self.data_type == \"train\" and self.augmentations:\n            img = self.augmentations.apply_random_augmentation(img)\n        else:\n            img = np.stack((img, img, img))\n        \n        return img, label","metadata":{"execution":{"iopub.execute_input":"2024-11-22T17:47:32.710520Z","iopub.status.busy":"2024-11-22T17:47:32.709678Z","iopub.status.idle":"2024-11-22T17:47:32.724880Z","shell.execute_reply":"2024-11-22T17:47:32.723656Z"},"papermill":{"duration":0.02943,"end_time":"2024-11-22T17:47:32.728602","exception":false,"start_time":"2024-11-22T17:47:32.699172","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"43676dd1","cell_type":"code","source":"loss_fn = nn.CrossEntropyLoss()\naudio_augmenter = AudioAugmentations()\n\n\ndef train(model, loss_fn, train_loader, valid_loader, 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 tqdm(range(1, EPOCHS + 1)):\n        model.train()\n        batch_losses = []\n        \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        with torch.no_grad():\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                \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, train_loss = %.5f, val_loss = %.5f, val_accuracy = %.5f\" % (\n            epoch, np.mean(train_losses[-1]), np.mean(valid_losses[-1]), 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\n\n\nskf = KFold(n_splits=N_FOLD, shuffle=True, random_state=563)\n\nfor fold_id, (train_index, val_index) in enumerate(skf.split(data_list, label_list)):\n    print(\"Fold\", fold_id)\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    train_data = AudioData(X_train, y_train, \"train\", augmentations=audio_augmenter)\n    valid_data = AudioData(X_val, y_val, \"valid\")\n    \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()\n    optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)\n    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=3)\n    \n    model = train(model, loss_fn, train_loader, valid_loader, optimizer, scheduler)\n    torch.save(model.state_dict(), f\"./model{fold_id}.pt\")\n\n    del train_data, valid_data, train_loader, valid_loader, model, X_train, X_val, y_train, y_val","metadata":{"execution":{"iopub.execute_input":"2024-11-22T17:47:32.772466Z","iopub.status.busy":"2024-11-22T17:47:32.769555Z","iopub.status.idle":"2024-11-22T18:08:12.831797Z","shell.execute_reply":"2024-11-22T18:08:12.830779Z"},"papermill":{"duration":1240.075734,"end_time":"2024-11-22T18:08:12.834343","exception":false,"start_time":"2024-11-22T17:47:32.758609","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"cf38b687","cell_type":"code","source":"def 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(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=F_MIN, fmax=F_MAX)\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":{"execution":{"iopub.execute_input":"2024-11-22T18:08:12.884611Z","iopub.status.busy":"2024-11-22T18:08:12.884311Z","iopub.status.idle":"2024-11-22T18:08:12.890097Z","shell.execute_reply":"2024-11-22T18:08:12.889360Z"},"papermill":{"duration":0.020519,"end_time":"2024-11-22T18:08:12.891629","exception":false,"start_time":"2024-11-22T18:08:12.871110","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"beca4f6e","cell_type":"code","source":"members = []\nfor i in range(N_FOLD):\n    print(f\"Fold № {i}\")\n    model = get_model()\n    model.load_state_dict(torch.load('./model' + str(i) + '.pt'))\n    model.eval()\n    members.append(model)\n\nos.remove('./model0.pt')\nos.remove('./model1.pt')\nos.remove('./model2.pt')\nos.remove('./model3.pt')\nos.remove('./model4.pt')","metadata":{"execution":{"iopub.execute_input":"2024-11-22T18:08:12.917318Z","iopub.status.busy":"2024-11-22T18:08:12.917045Z","iopub.status.idle":"2024-11-22T18:08:15.723998Z","shell.execute_reply":"2024-11-22T18:08:15.723243Z"},"papermill":{"duration":2.822032,"end_time":"2024-11-22T18:08:15.726036","exception":false,"start_time":"2024-11-22T18:08:12.904004","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"bfeda4e9","cell_type":"code","source":"def load_and_predict(test_file, members):\n    data = load_test_file(test_file)  # Загрузка данных\n    data = torch.tensor(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    \n    avg_maxed_output = torch.mean(torch.stack(output_list), dim=0)\n    file_id = test_file.split('.')[0]\n    return [file_id] + [out.item() for out in avg_maxed_output]\n    \n\ndef save_submission(predictions, output_file='submission.csv'):\n    with open(output_file, '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', \n                                    's11', 's12', 's13', 's14', 's15', 's16', 's17', 's18', 's19', 's20', 's21', 's22', 's23'])\n        for pred in predictions:\n            submission_writer.writerow(pred)\n\ndef generate_predictions(test_files, members):\n    predictions = []\n    \n    with ThreadPoolExecutor(max_workers=4) as executor:\n        futures = [executor.submit(load_and_predict, test_file, members) for test_file in test_files]\n        for future in futures:\n            predictions.append(future.result())\n    \n    save_submission(predictions)","metadata":{"execution":{"iopub.execute_input":"2024-11-22T18:08:15.753153Z","iopub.status.busy":"2024-11-22T18:08:15.752832Z","iopub.status.idle":"2024-11-22T18:08:15.760783Z","shell.execute_reply":"2024-11-22T18:08:15.760064Z"},"papermill":{"duration":0.02274,"end_time":"2024-11-22T18:08:15.762306","exception":false,"start_time":"2024-11-22T18:08:15.739566","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"af94ee6b","cell_type":"code","source":"test_files = os.listdir('/kaggle/input/rfcx-species-audio-detection/test/')\nprint(f\"Processing {len(test_files)} files\")\nif torch.cuda.is_available():\n    members = [m.cuda() for m in members]\n\ngenerate_predictions(test_files, members)","metadata":{"execution":{"iopub.execute_input":"2024-11-22T18:08:15.787948Z","iopub.status.busy":"2024-11-22T18:08:15.787709Z","iopub.status.idle":"2024-11-22T18:28:59.654828Z","shell.execute_reply":"2024-11-22T18:28:59.653874Z"},"papermill":{"duration":1243.895594,"end_time":"2024-11-22T18:28:59.670292","exception":false,"start_time":"2024-11-22T18:08:15.774698","status":"completed"},"tags":[]},"outputs":[],"execution_count":null}]}