{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":21669,"databundleVersionId":1692278,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Библиотеки и константы","metadata":{}},{"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')\n\nLABELS = 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":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"UTIL и DATA","metadata":{}},{"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)\n\ndata = 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":{"trusted":true},"outputs":[],"execution_count":null},{"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\n\n\nclass 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":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Обучение","metadata":{}},{"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":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Rerehenee","metadata":{}},{"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\n\nmembers = []\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":{"trusted":true},"outputs":[],"execution_count":null},{"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":{"trusted":true},"outputs":[],"execution_count":null},{"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":{"trusted":true},"outputs":[],"execution_count":null}]}