{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport os\nimport librosa\nimport librosa.display\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom tqdm.notebook import tqdm\nfrom skimage.transform import resize\nimport PIL\nfrom IPython.display import Image\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"TRAIN_DIR = Path('../input/rfcx-species-audio-detection/train')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"flacfiles = list(TRAIN_DIR.glob('*.flac'))\ny, sr = librosa.load(flacfiles[0], duration=10)\ny, sr","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"librosa.display.waveplot(y, sr=sr)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"spec = np.abs(librosa.stft(y))\nspec = librosa.amplitude_to_db(spec, ref=np.max)\n\nlibrosa.display.specshow(spec, sr=sr, x_axis='time', y_axis='log')\nplt.colorbar(format='%+2.0f dB')\nplt.title('Spectrogram')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"melspec = librosa.power_to_db(librosa.feature.melspectrogram(y, sr=sr, n_mels=128))\nlibrosa.display.specshow(melspec, sr=sr, x_axis='time', y_axis='log')\nplt.title('Mel Spectrogram')\nplt.colorbar(format='%+2.0f dB')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train = pd.read_csv('../input/rfcx-species-audio-detection/train_tp.csv')\ndf_train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"f_min = df_train.f_min.min()\nf_max = df_train.f_max.max()\n\nf_min, f_max","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"length = 48000*10","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for i in tqdm(range(df_train.shape[0])):\n    data, sr = librosa.load('../input/rfcx-species-audio-detection/train/' + df_train.recording_id[i] + '.flac', sr=None)\n    t_min = df_train.t_min[i]\n    t_max = df_train.t_max[i]\n    time_arr = np.arange(0, data.shape[0])/sr\n    t_center = (t_min+t_max)/2\n    t_min = t_center-5\n    if t_min < 0.0:\n        t_min = 0.0\n    t_max = t_min+10\n    if t_max > len(data)/sr:\n        t_max = len(data)/sr\n    t_min = t_max-10.0\n    data = data[np.where((time_arr >= t_min) & (time_arr <= t_max) )]\n    data = data[:length]\n    if len(data) < length:\n        print('bad', len(data), t_min, t_max)\n    mel_spec = librosa.power_to_db(librosa.feature.melspectrogram(data, n_fft=2048, hop_length=512, fmin=f_min, fmax=f_max, sr=sr, n_mels=512, power=1.5))\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)\n    \n    mel_spec = mel_spec * 255\n    mel_spec = np.round(mel_spec)\n    mel_spec = mel_spec.astype('uint8')\n    mel_spec = np.asarray(mel_spec)\n    bmp = PIL.Image.fromarray(mel_spec, 'L')\n    bmp.save('/kaggle/working/' + str(df_train.recording_id[i]) + '_' + str(df_train.species_id[i]) + '_' + str(t_center) +  '_' + '.bmp')\n    \n    ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.imshow(PIL.Image.open('/kaggle/working/003bec244_14_44.83735_.bmp'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"num_species = 24\nimport torch\nimport random\nbatch_size=16\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch.utils.data as torchdata\n\nclass RFCXDataset(torchdata.Dataset):\n    def __init__(self, filelist):\n        self.specs = []\n        self.labels = []\n        for f in filelist:\n            label = int(str.split(f, '_')[1])\n            label_arr = np.zeros(num_species, dtype=np.single)\n            label_arr[label] = 1\n            self.labels.append(label_arr)\n            \n            img = PIL.Image.open(f)\n            mel_spec = np.array(img)\n            img.close()\n            mel_spec = mel_spec / 255\n            mel_spec = np.stack((mel_spec, mel_spec, mel_spec))\n            self.specs.append(mel_spec)\n    def __len__(self):\n        return len(self.specs)\n    def __getitem__(self, item):\n        return self.specs[item], self.labels[item]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import glob\nfile_list = glob.glob('/kaggle/working/*.bmp')\nlabel_list = []\n\nfor f in file_list:\n    label = str.split(f, '_')[1]\n    label_list.append(label)\n\nfrom sklearn.model_selection import StratifiedKFold\n\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n\ntrain_files = []\nval_files = []\n\nfor fold_id, (train_id, val_id) in enumerate(skf.split(file_list, label_list)):\n    if fold_id == 0:\n        train_files = np.take(file_list, train_id)\n        val_files = np.take(file_list, val_id)\n    \nprint('Training on ' + str(len(train_files)) + ' examples')\nprint('Validating on ' + str(len(val_files)) + ' examples')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install resnest > /dev/null","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch.nn as nn\nfrom resnest.torch import resnest50\n\ntrain_dataset = RFCXDataset(train_files)\nval_dataset = RFCXDataset(val_files)\n\ntrain_loader = torchdata.DataLoader(train_dataset, batch_size=batch_size, sampler=torchdata.RandomSampler(train_dataset))\nval_loader = torchdata.DataLoader(val_dataset, batch_size=batch_size, sampler=torchdata.RandomSampler(val_dataset))\n\nmodel = resnest50(pretrained=True)\n\nmodel.fc = nn.Sequential(\n    nn.Linear(2048, 1024),\n    nn.ReLU(),\n    nn.Dropout(p=0.2),\n    nn.Linear(1024, 1024),\n    nn.ReLU(),\n    nn.Dropout(p=0.2),\n    nn.Linear(1024, num_species)\n)\n\noptimizer = torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=0.0001, momentum=0.9)\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.4)\n\n\npos_weights = torch.ones(num_species)\npos_weights = pos_weights * num_species\nloss_function = nn.BCEWithLogitsLoss(pos_weight=pos_weights)\n\nif torch.cuda.is_available():\n    model = model.cuda()\n    loss_function = loss_function.cuda()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"best_corrects = 0\n\n# Train loop\nprint('Starting training loop')\nfor e in range(0, 32):\n    # Stats\n    train_loss = []\n    train_corr = []\n    \n    # Single epoch - train\n    model.train()\n    for batch, (data, target) in enumerate(train_loader):\n        data = data.float()\n        if torch.cuda.is_available():\n            data, target = data.cuda(), target.cuda()\n            \n        optimizer.zero_grad()\n        \n        output = model(data)\n        loss = loss_function(output, target)\n        \n        loss.backward()\n        optimizer.step()\n        \n        # Stats\n        vals, answers = torch.max(output, 1)\n        vals, targets = torch.max(target, 1)\n        corrects = 0\n        for i in range(0, len(answers)):\n            if answers[i] == targets[i]:\n                corrects = corrects + 1\n        train_corr.append(corrects)\n        \n        train_loss.append(loss.item())\n    \n    # Stats\n    for g in optimizer.param_groups:\n        lr = g['lr']\n    print('Epoch ' + str(e) + ' training end. LR: ' + str(lr) + ', Loss: ' + str(sum(train_loss) / len(train_loss)) +\n          ', Correct answers: ' + str(sum(train_corr)) + '/' + str(train_dataset.__len__()))\n    \n    # Single epoch - validation\n    with torch.no_grad():\n        # Stats\n        val_loss = []\n        val_corr = []\n        \n        model.eval()\n        for batch, (data, target) in enumerate(val_loader):\n            data = data.float()\n            if torch.cuda.is_available():\n                data, target = data.cuda(), target.cuda()\n            \n            output = model(data)\n            loss = loss_function(output, target)\n            \n            # Stats\n            vals, answers = torch.max(output, 1)\n            vals, targets = torch.max(target, 1)\n            corrects = 0\n            for i in range(0, len(answers)):\n                if answers[i] == targets[i]:\n                    corrects = corrects + 1\n            val_corr.append(corrects)\n        \n            val_loss.append(loss.item())\n    \n    # Stats\n    print('Epoch ' + str(e) + ' validation end. LR: ' + str(lr) + ', Loss: ' + str(sum(val_loss) / len(val_loss)) +\n          ', Correct answers: ' + str(sum(val_corr)) + '/' + str(val_dataset.__len__()))\n    \n    # If this epoch is better than previous on validation, save model\n    # Validation loss is the more common metric, but in this case our loss is misaligned with competition metric, making accuracy a better metric\n    if sum(val_corr) > best_corrects:\n        print('Saving new best model at epoch ' + str(e) + ' (' + str(sum(val_corr)) + '/' + str(val_dataset.__len__()) + ')')\n        torch.save(model, 'best_model.pt')\n        best_corrects = sum(val_corr)\n        \n    # Call every epoch\n    scheduler.step()\n\n# Free memory\ndel model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"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    # Split for enough segments to not miss anything\n    segments = len(wav) / length\n    segments = int(np.ceil(segments))\n    \n    mel_array = []\n    \n    for i in range(0, segments):\n        # Last segment going from the end\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        # Same mel spectrogram as before\n        mel_spec = librosa.feature.melspectrogram(slice, n_fft=2048, hop_length=512, sr=sr, fmin=f_min, fmax=f_max, power=1.5)\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)\n        \n        mel_spec = np.stack((mel_spec, mel_spec, mel_spec))\n\n        mel_array.append(mel_spec)\n    \n    return mel_array","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import csv\nmodel = resnest50(pretrained=True)\n\nmodel.fc = nn.Sequential(\n    nn.Linear(2048, 1024),\n    nn.ReLU(),\n    nn.Dropout(p=0.2),\n    nn.Linear(1024, 1024),\n    nn.ReLU(),\n    nn.Dropout(p=0.2),\n    nn.Linear(1024, num_species)\n)\n\nmodel = torch.load('/kaggle/working/best_model.pt')\nmodel.eval()\n\nfor f in os.listdir('/kaggle/working/'):\n    os.remove('/kaggle/working/' + f)\n\nif torch.cuda.is_available():\n    model.cuda()\n    \n# Prediction loop\nprint('Starting prediction loop')\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('/kaggle/input/rfcx-species-audio-detection/test/')\n    print(len(test_files))\n    \n    # Every test file is split on several chunks and prediction is made for each chunk\n    for i in 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 = model(data)\n\n        # Taking max prediction from all slices per bird species\n        # Usually you want Sigmoid layer here to convert output to probabilities\n        # In this competition only relative ranking matters, and not the exact value of prediction, so we can use it directly\n        maxed_output = torch.max(output, dim=0)[0]\n        maxed_output = maxed_output.cpu().detach()\n        \n        file_id = str.split(test_files[i], '.')[0]\n        write_array = [file_id]\n        \n        for out in maxed_output:\n            write_array.append(out.item())\n    \n        submission_writer.writerow(write_array)\n        \n        if i % 100 == 0 and i > 0:\n            print('Predicted for ' + str(i) + ' of ' + str(len(test_files) + 1) + ' files')\n\nprint('Submission generated')","execution_count":null,"outputs":[]}],"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"}},"nbformat":4,"nbformat_minor":4}