{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Libraries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport matplotlib.pyplot as plt\nimport soundfile as sf\nimport librosa\n\nfrom sklearn.model_selection import train_test_split\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Path","metadata":{}},{"cell_type":"code","source":"path = '/kaggle/input/birdclef-2021/'\nos.listdir(path)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Functions\nWe define some helper functions.","metadata":{}},{"cell_type":"code","source":"def read_ogg_file(full_path):\n    \"\"\" Read ogg audio file and return numpay array and samplerate\"\"\"\n    data, samplerate = sf.read(full_path)\n#     data, samplerate = librosa.load(full_path)\n    return data, samplerate","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Load Data","metadata":{}},{"cell_type":"code","source":"train_labels = pd.read_csv(path+'train_soundscape_labels.csv')\ntrain_meta = pd.read_csv(path+'train_metadata.csv')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_labels.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_meta.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels = []\nfor row in train_labels.index:\n    labels.extend(train_labels.loc[row, 'birds'].split(' '))\nlabels = list(set(labels))\n\nprint('Number of unique bird labels:', len(labels))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pickle\n\nwith open('labels.pkl', 'wb') as fp:\n    pickle.dump(labels, fp)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We encode the labels and write them into a data frame:","metadata":{}},{"cell_type":"code","source":"df_labels_train = pd.DataFrame(index=train_labels.index, columns=labels)\nfor row in train_labels.index:\n    birds = train_labels.loc[row, 'birds'].split(' ')\n    for bird in birds:\n        df_labels_train.loc[row, bird] = 1\ndf_labels_train.fillna(0, inplace=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_labels = pd.concat([train_labels, df_labels_train], axis=1)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_labels","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Parameter\nBased on the EDA we define some parameters:","metadata":{}},{"cell_type":"code","source":"import torch\n\ndata_lenght = 160000\naudio_lenght = 5\nnum_labels = len(labels)\nbatch_size = 4\n\nif torch.cuda.is_available():\n    device=torch.device('cuda:0')\nelse:\n    device=torch.device('cpu')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train, Val And Test Data","metadata":{}},{"cell_type":"code","source":"X = train_labels[['row_id', 'site', 'audio_id', 'seconds']]\ny = train_labels[labels]\n\nfrom skmultilearn.model_selection import IterativeStratification\n\nstratifier = IterativeStratification(n_splits=2, order=2, sample_distribution_per_fold=[0.25, 0.75])\n\nlist_IDs_train, list_IDs_val = next(stratifier.split(X, y))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Audio Data Generator\nWe use a Data Generator to load the data on demand.","metadata":{}},{"cell_type":"code","source":"from skimage.transform import resize\nfrom skimage.filters import gaussian\nfrom skimage.color import rgb2gray\nfrom skimage import exposure, util\nimport cv2\nimport numpy as np\nimport random\n\ndef addNoisy(img):\n    noise_img = util.random_noise(img)\n    return addChannels(noise_img)\n\ndef vertical_flip(img):\n    vertical_flip_img = img[::-1, :]\n    return addChannels(vertical_flip_img)\n\ndef contrast_stretching(img):\n    p2, p98 = np.percentile(img, (2, 98))\n    contrast_img = exposure.rescale_intensity(img, in_range=(p2, p98))\n    return addChannels(contrast_img)\n\ndef randomGaussian(img):\n    gaussian_img = gaussian(img, sigma=random.randint(0, 5))\n    return addChannels(gaussian_img)\n\ndef grayScale(img):\n    gray_img = rgb2gray(img)\n    return addChannels(gray_img)\n\ndef randomGamma(img):\n    gm = random.randrange(5, 15, 1)  / 10\n    img_gamma = exposure.adjust_gamma(img, gamma=gm)\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","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import librosa\nfrom torch.utils.data import Dataset, DataLoader\n\nclass AudioData(Dataset):\n    def __init__(self, path, list_IDs, df, data_type):\n        self.data_type = data_type\n        self.path = path\n        self.df = df\n        self.data = []\n        self.labels = []\n        \n        for i, ID in enumerate(list_IDs):\n            prefix = str(self.df.loc[ID, 'audio_id'])+'_'+self.df.loc[ID, 'site']\n            file_list = [s for s in os.listdir(self.path) if prefix in s]\n            if len(file_list) == 0:\n                # Dummy for missing test audio files\n                audio_file_fft = np.zeros((data_lenght//2))\n                spectrogram = librosa.feature.melspectrogram(audio_file_fft)\n                spec_db=librosa.power_to_db(spectrogram,top_db=80)\n            else:\n                file = file_list[0]#[s for s in os.listdir(self.path) if prefix in s][0]\n                audio_file, sr = read_ogg_file(self.path+file)\n                audio_file = audio_file[int((self.df.loc[ID, 'seconds']-5)/audio_lenght)*data_lenght:int(self.df.loc[ID, 'seconds']/audio_lenght)*data_lenght]\n                audio_file_fft = np.abs(np.fft.fft(audio_file)[: len(audio_file)//2])\n#                 # scale data\n#                 audio_file_fft = (audio_file_fft-audio_file_fft.mean())/audio_file_fft.std()\n            \n                n_fft = sr//10\n                hop_length = sr//(10*4)\n                fmin = 0\n                fmax = sr//2\n                n_mels=128\n                \n                spectrogram = librosa.feature.melspectrogram(audio_file_fft, sr=sr, n_mels=n_mels, fmin=fmin, fmax=fmax, n_fft=n_fft, hop_length=hop_length)\n                spec_db=librosa.power_to_db(spectrogram,top_db=80)\n            \n            img = spec_to_image(spec_db)\n            mel_spec = np.stack((img, img, img))\n            \n            label = self.df.loc[ID, self.df.columns[5:]].values\n            encoded = [int(w) for w in label]\n            label = torch.tensor(encoded)\n            \n            self.data.append(mel_spec)\n            self.labels.append(label)\n            \n            if data_type == \"train\" and len(file_list) > 0 and str(self.df.loc[ID, 'birds']) != \"nocall\":\n                augmentation_functions = [\n                    addNoisy, contrast_stretching,\n                    randomGaussian, grayScale,\n                    randomGamma, vertical_flip\n                ]\n                for fun in augmentation_functions:\n                    mel_spec = fun(img)\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        return self.data[idx], self.labels[idx]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Test the Data Generator","metadata":{}},{"cell_type":"code","source":"train_data = AudioData(path+'train_soundscapes/', list_IDs_train, train_labels, \"train\")\nval_data = AudioData(path+'train_soundscapes/', list_IDs_val, train_labels, \"val\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True)\nvalid_loader = DataLoader(val_data, batch_size=batch_size, shuffle=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Define Model","metadata":{}},{"cell_type":"code","source":"from tqdm import tqdm\nimport copy\nfrom torch import nn\n\nlearning_rate = 1e-3\nepochs = 10\nloss_fn = nn.MSELoss()\n\ndef setlr(optimizer, lr):\n    for param_group in optimizer.param_groups:\n        param_group['lr'] = lr\n    return optimizer\n\ndef lr_decay(optimizer, epoch):\n    if epoch%10==0:\n        new_lr = learning_rate / (10**(epoch//10))\n        optimizer = setlr(optimizer, new_lr)\n        print(f'Changed learning rate to {new_lr}')\n    return optimizer\n\ndef train(model, loss_fn, train_loader, valid_loader, epochs, optimizer, change_lr=None):\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        if change_lr:\n            optimizer = change_lr(optimizer, epoch)\n        for i, 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.float)\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            \n        train_losses.append(batch_losses)\n        print(f'Epoch: {epoch} - Train Loss : {np.mean(train_losses[-1])}')\n        \n        \n        model.eval()\n        batch_losses=[]\n        \n        correct = 0.\n        total = 0.\n        \n        for i, data in enumerate(valid_loader):\n            x, y = data\n            x = x.to(device, dtype=torch.float32)\n            y = y.to(device, dtype=torch.float)\n            y_hat = model(x)\n            loss = loss_fn(y_hat, y)\n            \n            target = y.cpu().detach().numpy()\n            predicted = y_hat.cpu().detach().numpy()\n            \n            result_target = np.round(target)\n            result_predicted = np.where(predicted > 0.5, 1, 0)\n            total += (batch_size * y.shape[1]) #batch_size * number_class\n            correct += (result_predicted == result_target).sum()\n\n            batch_losses.append(loss.item())\n\n        valid_losses.append(batch_losses)\n        \n        accuracy = 100 * correct / total\n        print(f'Epoch: {epoch} - Valid Loss: {np.mean(valid_losses[-1])} - Valid Accuracy: {accuracy}')\n                \n        # deep copy the model\n        if accuracy > best_acc:\n            best_acc = accuracy\n            best_model_wts = copy.deepcopy(model.state_dict())\n            torch.save(model.state_dict(), \"best_model_state.pt\")\n\n    # load best model weights\n    model.load_state_dict(best_model_wts)\n    return model","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torchvision.models import resnet50\n\nclass BirdCLEFModel(nn.Module):\n    def __init__(self, n_classes):\n        super().__init__()\n        resnet = resnet50(pretrained=True)\n        resnet.fc = nn.Sequential(\n            nn.Dropout(p=0.2),\n            nn.Linear(in_features=resnet.fc.in_features, out_features=n_classes)\n        )\n        self.base_model = resnet\n        self.sigm = nn.Sigmoid()\n\n    def forward(self, x):\n        return self.sigm(self.base_model(x))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!mkdir -p /root/.cache/torch/hub/checkpoints/\n!cp ../input/pretrained-pytorch-models/resnet50-19c8e357.pth /root/.cache/torch/hub/checkpoints/","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def create_model_and_train():\n    resnet_model = BirdCLEFModel(num_labels)\n    resnet_model = resnet_model.to(device)\n    optimizer = torch.optim.Adam(resnet_model.parameters(), lr=learning_rate)\n    resnet_model = train(resnet_model, loss_fn, train_loader, valid_loader, epochs, optimizer, lr_decay)\n\ncreate_model_and_train()","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}