{"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":"# <p style=\"background-color:#f3ab60;font-family:newtimeroman;color:#662e2e;font-size:130%;text-align:center;border-radius:40px 40px;\">BirdCLEF 2022</p>","metadata":{}},{"cell_type":"markdown","source":"<h1 align='center'>Introduction 📝</h1>\nThe goal of the competition is to identify which birds are calling in the recordings. This notebook will be helpful for all the begineers who have very little to no knowledge in this domain. In this kernel I will briefly go through the metadata and audio data with some quick EDA. Then I will focus on the main part of the kernel which is the data processing and training audio based data model using pytorch. \n\nThis is a work in progress notebook and I will keep on updating it as I learn more (as I have also participated in the audio based competition for the first time😅)","metadata":{}},{"cell_type":"markdown","source":"##  <font color=\"red\"> Please do an upvote if you find this kernel useful.</font>","metadata":{}},{"cell_type":"markdown","source":"<h1 align='center'>Table of Contents 📜</h1>\n<ul style=\"list-style-type:square\">\n    <li><a href=\"#1\">Importing Libraries</a></li>\n    <li><a href=\"#2\">Reading the data</a></li>\n    <li><a href=\"#3\">Quick EDA</a></li>\n    <ul style=\"list-style-type:disc\">\n        <li><a href=\"#3.1\">Train_Metadata</a></li>\n        <li><a href=\"#3.2\">Audio Files</a></li>\n    </ul>\n    <li><a href=\"#4\">Data Preprocessing</a></li>\n    <li><a href=\"#5\">Model</a></li>\n    <li><a href=\"#6\">Utility Functions</a></li>\n    <li><a href=\"#7\">Training</a></li>\n</ul>\n\n","metadata":{}},{"cell_type":"markdown","source":"<a id='1'></a>\n# Importing Libraries 📚","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport ast\nimport random\nimport numpy as np \nimport pandas as pd \nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport seaborn as sns\nfrom tqdm import tqdm\nimport torchaudio\nimport IPython.display as ipd\nfrom collections import Counter\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import f1_score\nfrom sklearn.utils import class_weight\n\nimport torch\nimport torch.nn as nn\nfrom torch.optim import Adam\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models\n\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-05-24T10:49:58.986983Z","iopub.execute_input":"2022-05-24T10:49:58.988096Z","iopub.status.idle":"2022-05-24T10:50:02.534339Z","shell.execute_reply.started":"2022-05-24T10:49:58.987941Z","shell.execute_reply":"2022-05-24T10:50:02.533194Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class config:\n    seed=2022\n    num_fold = 9\n    sample_rate= 32_000\n    n_fft=1024\n    hop_length=512\n    n_mels=64\n    duration=5\n    num_classes = 152\n    train_batch_size = 32\n    valid_batch_size = 64\n    model_name = 'resnet50'\n    epochs = 2\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    learning_rate = 1e-3","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:02.537012Z","iopub.execute_input":"2022-05-24T10:50:02.537369Z","iopub.status.idle":"2022-05-24T10:50:02.608887Z","shell.execute_reply.started":"2022-05-24T10:50:02.537319Z","shell.execute_reply":"2022-05-24T10:50:02.606744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\nseed_everything(config.seed)","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:02.610725Z","iopub.execute_input":"2022-05-24T10:50:02.611154Z","iopub.status.idle":"2022-05-24T10:50:02.623761Z","shell.execute_reply.started":"2022-05-24T10:50:02.611106Z","shell.execute_reply":"2022-05-24T10:50:02.622757Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id='2'></a>\n# Reading the data 📖","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv('../input/birdclef-2022/train_metadata.csv')\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:02.625706Z","iopub.execute_input":"2022-05-24T10:50:02.626136Z","iopub.status.idle":"2022-05-24T10:50:02.791177Z","shell.execute_reply.started":"2022-05-24T10:50:02.626091Z","shell.execute_reply":"2022-05-24T10:50:02.790172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.info()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:02.794783Z","iopub.execute_input":"2022-05-24T10:50:02.795118Z","iopub.status.idle":"2022-05-24T10:50:02.838086Z","shell.execute_reply.started":"2022-05-24T10:50:02.795076Z","shell.execute_reply":"2022-05-24T10:50:02.836952Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.describe()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:02.839845Z","iopub.execute_input":"2022-05-24T10:50:02.840176Z","iopub.status.idle":"2022-05-24T10:50:02.869627Z","shell.execute_reply.started":"2022-05-24T10:50:02.840131Z","shell.execute_reply":"2022-05-24T10:50:02.868633Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id='3'></a>\n# Quick EDA 📊","metadata":{}},{"cell_type":"markdown","source":"<a id='3.1'></a>\n## Analyse Train_Metadata","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(20, 6))\n\nsns.countplot(df['primary_label'])\nplt.xticks(rotation=90)\nplt.title(\"Distribution of Primary Labels\", fontsize=20)\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:02.873092Z","iopub.execute_input":"2022-05-24T10:50:02.873354Z","iopub.status.idle":"2022-05-24T10:50:05.181665Z","shell.execute_reply.started":"2022-05-24T10:50:02.873322Z","shell.execute_reply":"2022-05-24T10:50:05.180629Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20, 6))\n\nsns.countplot(df['rating'])\nplt.title(\"Distribution of Ratings\", fontsize=20)\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:05.183402Z","iopub.execute_input":"2022-05-24T10:50:05.184325Z","iopub.status.idle":"2022-05-24T10:50:05.47785Z","shell.execute_reply.started":"2022-05-24T10:50:05.184276Z","shell.execute_reply":"2022-05-24T10:50:05.476862Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['type'] = df['type'].apply(lambda x : ast.literal_eval(x))\n\ntop = Counter([typ.lower() for lst in df['type'] for typ in lst])\n\ntop = dict(top.most_common(10))\n\nplt.figure(figsize=(20, 6))\n\nsns.barplot(x=list(top.keys()), y=list(top.values()), palette='hls')\nplt.title(\"Top 10 song types\")\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:05.479585Z","iopub.execute_input":"2022-05-24T10:50:05.480098Z","iopub.status.idle":"2022-05-24T10:50:05.935278Z","shell.execute_reply.started":"2022-05-24T10:50:05.480053Z","shell.execute_reply":"2022-05-24T10:50:05.934192Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id='3.2'></a>\n## Analyse Audio Files","metadata":{}},{"cell_type":"markdown","source":"### Let's listen few audios","metadata":{}},{"cell_type":"code","source":"filename_1 = df[\"filename\"].values[0] # first training example\nipd.Audio(f\"../input/birdclef-2022/train_audio/{filename_1}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:05.937115Z","iopub.execute_input":"2022-05-24T10:50:05.937471Z","iopub.status.idle":"2022-05-24T10:50:05.958848Z","shell.execute_reply.started":"2022-05-24T10:50:05.937426Z","shell.execute_reply":"2022-05-24T10:50:05.957841Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"filename_2 = df[\"filename\"].values[-1] # last training example\nipd.Audio(f\"../input/birdclef-2022/train_audio/{filename_2}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:05.960047Z","iopub.execute_input":"2022-05-24T10:50:05.961183Z","iopub.status.idle":"2022-05-24T10:50:06.00658Z","shell.execute_reply.started":"2022-05-24T10:50:05.961139Z","shell.execute_reply":"2022-05-24T10:50:06.005328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Now let us load the the audio and plot the waveform.\n<b>Note - I will be using Torchaudio(which is a library for audio with PyTorch) for processing audio data.</b><br>\n<center>\n<img src = \"https://torch.mlverse.org/css/images/hex/torchaudio.png\" style=\"width:200px;height:200px\"><br>\n<a href=\"https://pytorch.org/audio/stable/index.html\">TORCHAUDIO DOCUMENTATION</a>    \n</center>","metadata":{}},{"cell_type":"markdown","source":"[](http://)","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots(2, 1, figsize=(20, 10))\nfig.suptitle(\"Sound Waves\", fontsize=15)\n\nsignal_1, sr = torchaudio.load(f\"../input/birdclef-2022/train_audio/{filename_1}\")\n# The audio data consist of two things-\n# Sound: sequence of vibrations in varying pressure strengths (y)\n# Sample Rate: (sr) is the number of samples of audio carried per second, measured in Hz or kHz\n\nsns.lineplot(x=np.arange(len(signal_1[0,:].detach().numpy())), y=signal_1[0,:].detach().numpy(), ax=ax[0], color='#4400FF')\nax[0].set_title(\"Audio 1\")\n\nsignal_2, sr = torchaudio.load(f\"../input/birdclef-2022/train_audio/{filename_2}\")\nsns.lineplot(x=np.arange(len(signal_2[0,:].detach().numpy())), y=signal_2[0,:].detach().numpy(), ax=ax[1], color='#4400FF')\nax[1].set_title(\"Audio 2\")\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:50:06.008463Z","iopub.execute_input":"2022-05-24T10:50:06.008858Z","iopub.status.idle":"2022-05-24T10:51:15.693806Z","shell.execute_reply.started":"2022-05-24T10:50:06.008814Z","shell.execute_reply":"2022-05-24T10:51:15.692757Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id='4'></a>\n# Dataset Preprocessing 🛠️","metadata":{}},{"cell_type":"markdown","source":"### First of all, as our target variable is in string format, we have to convert it to integer and here I have used LabelEncoder to perform this work.","metadata":{}},{"cell_type":"code","source":"encoder = LabelEncoder()\ndf['primary_label_encoded'] = encoder.fit_transform(df['primary_label'])\n\ny = torch.FloatTensor(df['primary_label_encoded'])\n\nclass_weights=class_weight.compute_class_weight(class_weight = 'balanced',classes = np.unique(y), y = y.numpy())\nclass_weights=torch.tensor(class_weights,dtype=torch.float)\n\nclass_weights = class_weights.to(config.device)","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:15.695306Z","iopub.execute_input":"2022-05-24T10:51:15.696722Z","iopub.status.idle":"2022-05-24T10:51:19.787003Z","shell.execute_reply.started":"2022-05-24T10:51:15.696676Z","shell.execute_reply":"2022-05-24T10:51:19.785942Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"bird_labels = df['primary_label'].unique()\n","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:19.792438Z","iopub.execute_input":"2022-05-24T10:51:19.792699Z","iopub.status.idle":"2022-05-24T10:51:19.800344Z","shell.execute_reply.started":"2022-05-24T10:51:19.792668Z","shell.execute_reply":"2022-05-24T10:51:19.798129Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Next we created folds.","metadata":{}},{"cell_type":"code","source":"skf = StratifiedKFold(n_splits=config.num_fold)\nfor k, (_, val_ind) in enumerate(skf.split(X=df, y=df['primary_label_encoded'])):\n    df.loc[val_ind, 'fold'] = k","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:19.802443Z","iopub.execute_input":"2022-05-24T10:51:19.802813Z","iopub.status.idle":"2022-05-24T10:51:19.825009Z","shell.execute_reply.started":"2022-05-24T10:51:19.802769Z","shell.execute_reply":"2022-05-24T10:51:19.824026Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we will focus on our input variable. Our input in this are audio files and these audios cannot be understood by the models directly. So to use them, we convert it into an understandable format by performing some type of feature extraction technique.\n\n## Feature Extraction\nThere are several different feature extractions in audio processing but I will not cover all those in this notebook. Genereally, the features that are extracted are in the form of images which we then use them to train our model. <br>\nI would recommed this playlist for audio processing to understand the basics - https://www.youtube.com/playlist?list=PL-wATfeyAMNqIee7cH3q1bh4QJFAaeNv0 <br>\nHere I will be extracting MelSpectrogram which is a type of spectrogram where the frequencies are converted to the mel scale.","metadata":{}},{"cell_type":"markdown","source":"### Now let us look at the Mel Spectrogram for the audio loaded during the EDA.","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots(1, 2, figsize=(20, 7))\nfig.suptitle(\"Mel Spectrogram\", fontsize=15)\n\nmel_spectrogram = torchaudio.transforms.MelSpectrogram(sample_rate=config.sample_rate, \n                                                      n_fft=config.n_fft, \n                                                      hop_length=config.hop_length, \n                                                      n_mels=config.n_mels)\n\nmel_1 = mel_spectrogram(signal_1)\nax[0].imshow(mel_1.log2()[0,:,:].detach().numpy(), aspect='auto', cmap='cool')\nax[0].set_title(\"Audio 1\")\n\nmel_2 = mel_spectrogram(signal_2)\nax[1].imshow(mel_2.log2()[0,:,:].detach().numpy(), aspect='auto', cmap='cool')\nax[1].set_title(\"Audio 2\")\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:19.828893Z","iopub.execute_input":"2022-05-24T10:51:19.829169Z","iopub.status.idle":"2022-05-24T10:51:20.631604Z","shell.execute_reply.started":"2022-05-24T10:51:19.829119Z","shell.execute_reply":"2022-05-24T10:51:20.630711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### So similarly we will extract mel spectrogram for each audio and will train the model using them. But wait, this is not the end. There are several things which we need to consider before extracting spectrograms from the audio files. We want our dataset to be uniform and to do that we should consider the below points:-\n* As I mentioned above the audio data consist of two things - sample rate and sound. Not all the audio have same sample rate, and this is a huge problem if we want uniformity in the melspectrogram which we extract. So we resample the data so that all the data have same sample rates.\n* Next if we talk about the sound, the dimension of sound is - (num_channels, num_samples). If we talk about number of channels, then each audio signals can have different number of channels. So we will ensure that they are mono, i.e., num_channels = 1.\n* Lastly, each audio signal have different time durations which lead to difference in number of samples. So we ensure same number of samples by applying padding if it is less than the desired samples or by truncating if it is more than the desired samples.\n\n### Now I will implement the custom Dataset class in which I will also implement all the above points. ","metadata":{}},{"cell_type":"code","source":"class BirdClefDataset(Dataset):\n    def __init__(self, df, transformation, target_sample_rate, duration):\n        self.audio_paths = df['filename'].values\n        self.labels = df['primary_label_encoded'].values\n        self.transformation = transformation\n        self.target_sample_rate = target_sample_rate\n        self.num_samples = target_sample_rate*duration\n        \n    def __len__(self):\n        return len(self.audio_paths)\n    \n    def __getitem__(self, index):\n        audio_path = f'../input/birdclef-2022/train_audio/{self.audio_paths[index]}'\n        signal, sr = torchaudio.load(audio_path) # loaded the audio\n        \n        # Now we first checked if the sample rate is same as TARGET_SAMPLE_RATE and if it not equal we perform resampling\n        if sr != self.target_sample_rate:\n            resampler = torchaudio.transforms.Resample(sr, self.target_sample_rate)\n            signal = resampler(signal)\n        \n        # Next we check the number of channels of the signal\n        #signal -> (num_channels, num_samples) - Eg.-(2, 14000) -> (1, 14000)\n        if signal.shape[0]>1:\n            signal = torch.mean(signal, axis=0, keepdim=True)\n        \n        # Lastly we check the number of samples of the signal\n        #signal -> (num_channels, num_samples) - Eg.-(1, 14000) -> (1, self.num_samples)\n        # If it is more than the required number of samples, we truncate the signal\n        if signal.shape[1] > self.num_samples:\n            signal = signal[:, :self.num_samples]\n        \n        # If it is less than the required number of samples, we pad the signal\n        if signal.shape[1]<self.num_samples:\n            num_missing_samples = self.num_samples - signal.shape[1]\n            last_dim_padding = (0, num_missing_samples)\n            signal = F.pad(signal, last_dim_padding)\n        \n        # Finally all the process has been done and now we will extract mel spectrogram from the signal\n        mel = self.transformation(signal)\n        \n        # For pretrained models, we need 3 channel image, so for that we concatenate the extracted mel\n        image = torch.cat([mel, mel, mel])\n        \n        # Normalized the image\n        max_val = torch.abs(image).max()\n        image = image / max_val\n        \n        label = torch.tensor(self.labels[index])\n        \n        return image, label","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.633306Z","iopub.execute_input":"2022-05-24T10:51:20.633817Z","iopub.status.idle":"2022-05-24T10:51:20.651719Z","shell.execute_reply.started":"2022-05-24T10:51:20.633762Z","shell.execute_reply":"2022-05-24T10:51:20.650588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Function to get data according to the folds\ndef get_data(fold):\n    train_df = df[df['fold'] != fold].reset_index(drop=True)\n    valid_df = df[df['fold'] == fold].reset_index(drop=True)\n    \n    train_dataset = BirdClefDataset(train_df, mel_spectrogram, config.sample_rate, config.duration)\n    valid_dataset = BirdClefDataset(valid_df, mel_spectrogram, config.sample_rate, config.duration)\n    \n    train_loader = DataLoader(train_dataset, batch_size=config.train_batch_size, shuffle=True)\n    valid_loader = DataLoader(valid_dataset, batch_size=config.valid_batch_size, shuffle=False)\n    \n    return train_loader, valid_loader","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.653476Z","iopub.execute_input":"2022-05-24T10:51:20.653972Z","iopub.status.idle":"2022-05-24T10:51:20.669722Z","shell.execute_reply.started":"2022-05-24T10:51:20.653925Z","shell.execute_reply":"2022-05-24T10:51:20.668477Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id='5'></a>\n# Model 🤖","metadata":{}},{"cell_type":"markdown","source":"### So I will first start with a custom CNN model. After that, we will the see the usage of pretrained and other advanced models.","metadata":{}},{"cell_type":"code","source":"class BirdClefModel(nn.Module):\n    def __init__(self):\n        super(BirdClefModel, self).__init__()\n        self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1)\n        self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1)\n        self.conv3 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1)\n        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)\n        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)\n        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)\n        self.fc1 = nn.Linear(128*8*39, 64)\n        self.fc2 = nn.Linear(64, config.num_classes)\n        #self.softmax = nn.Softmax(dim = None)\n        \n    def forward(self, x):\n        x = F.relu(self.conv1(x))\n        x = self.pool1(x)\n        x = F.relu(self.conv2(x))\n        x = self.pool2(x)\n        x = F.relu(self.conv3(x))\n        x = self.pool3(x)\n        x = x.view(x.size(0), -1)\n        x = F.relu(self.fc1(x))\n        x = self.fc2(x)\n        #x = self.softmax(x)\n        \n        return x","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.671818Z","iopub.execute_input":"2022-05-24T10:51:20.672177Z","iopub.status.idle":"2022-05-24T10:51:20.685956Z","shell.execute_reply.started":"2022-05-24T10:51:20.672122Z","shell.execute_reply":"2022-05-24T10:51:20.684522Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Now we will fine tune a pretrained model. Here I have used Resnet50. You can use any pretrained model and do experiments.","metadata":{}},{"cell_type":"code","source":"class BirdCLEFResnet(nn.Module):\n    def __init__(self):\n        super(BirdCLEFResnet, self).__init__()\n        self.base_model = models.__getattribute__(config.model_name)(pretrained=True)\n        for param in self.base_model.parameters():\n            param.requires_grad = False\n            \n        in_features = self.base_model.fc.in_features\n        \n        self.base_model.fc = nn.Sequential(\n            nn.Linear(in_features, 1024), \n            nn.ReLU(), \n            nn.Dropout(p=0.2),\n            nn.Linear(1024, 512), \n            nn.ReLU(), \n            nn.Dropout(p=0.2),\n            nn.Linear(512, config.num_classes))\n        \n    def forward(self, x):\n        x = self.base_model(x)\n        return x","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.688229Z","iopub.execute_input":"2022-05-24T10:51:20.688631Z","iopub.status.idle":"2022-05-24T10:51:20.702618Z","shell.execute_reply.started":"2022-05-24T10:51:20.688569Z","shell.execute_reply":"2022-05-24T10:51:20.701513Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id='6'></a>\n# Utility Functions 📋","metadata":{}},{"cell_type":"markdown","source":"### Next we define some functions to train the model. These are the basic functions which we use to train any pytorch based models.","metadata":{"execution":{"iopub.status.busy":"2022-03-20T18:12:56.001998Z","iopub.execute_input":"2022-03-20T18:12:56.002486Z","iopub.status.idle":"2022-03-20T18:12:56.00889Z","shell.execute_reply.started":"2022-03-20T18:12:56.002451Z","shell.execute_reply":"2022-03-20T18:12:56.00809Z"}}},{"cell_type":"code","source":"def loss_fn(outputs, labels):\n    return nn.CrossEntropyLoss(weight = class_weights)(outputs, labels)\n\ndef train(model, data_loader, optimizer, scheduler, device, epoch):\n    model.train()\n    \n    running_loss = 0\n    loop = tqdm(data_loader, position=0)\n    for i, (mels, labels) in enumerate(loop):\n        mels = mels.to(device)\n        labels = labels.to(device)\n        \n        outputs = model(mels)\n        _, preds = torch.max(outputs, 1)\n        \n        loss = loss_fn(outputs, labels)\n        \n        loss.backward()\n        optimizer.step()\n        optimizer.zero_grad()\n        \n        if scheduler is not None:\n            scheduler.step()\n            \n        running_loss += loss.item()\n        \n        loop.set_description(f\"Epoch [{epoch+1}/{config.epochs}]\")\n        loop.set_postfix(loss=loss.item())\n\n    return running_loss/len(data_loader)","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.704655Z","iopub.execute_input":"2022-05-24T10:51:20.705035Z","iopub.status.idle":"2022-05-24T10:51:20.71677Z","shell.execute_reply.started":"2022-05-24T10:51:20.70498Z","shell.execute_reply":"2022-05-24T10:51:20.715724Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def valid(model, data_loader, device, epoch):\n    model.eval()\n    \n    running_loss = 0\n    pred = []\n    label = []\n    \n    loop = tqdm(data_loader, position=0)\n    for mels, labels in loop:\n        mels = mels.to(device)\n        labels = labels.to(device)\n        \n        outputs = model(mels)\n        _, preds = torch.max(outputs, 1)\n        \n        loss = loss_fn(outputs, labels)\n            \n        running_loss += loss.item()\n        \n        pred.extend(preds.view(-1).cpu().detach().numpy())\n        label.extend(labels.view(-1).cpu().detach().numpy())\n        \n        loop.set_description(f\"Epoch [{epoch+1}/{config.epochs}]\")\n        loop.set_postfix(loss=loss.item())\n        \n    valid_f1 = f1_score(label, pred, average='macro')\n    \n    return running_loss/len(data_loader), valid_f1","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.718985Z","iopub.execute_input":"2022-05-24T10:51:20.7197Z","iopub.status.idle":"2022-05-24T10:51:20.732428Z","shell.execute_reply.started":"2022-05-24T10:51:20.719643Z","shell.execute_reply":"2022-05-24T10:51:20.731188Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#checkpoint = {'model': Classifier(),\n          #'state_dict': model.state_dict(),\n          #'optimizer' : optimizer.state_dict()}\n\n#torch.save(checkpoint, 'checkpoint.pth')\n\ndef load_checkpoint(filepath):\n    checkpoint = torch.load(filepath)\n    model.load_state_dict(checkpoint['state_dict'])\n    optimizer.load_state_dict(checkpoint['optimizer'])\n    #epoch = checkpoint['epoch']\n    loss = checkpoint['loss']\n    \n\n    model.train()\n    return model\n\n","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.735633Z","iopub.execute_input":"2022-05-24T10:51:20.736819Z","iopub.status.idle":"2022-05-24T10:51:20.744601Z","shell.execute_reply.started":"2022-05-24T10:51:20.736768Z","shell.execute_reply":"2022-05-24T10:51:20.743501Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = BirdClefModel().to(config.device) # check version 3 for this\n#model = BirdCLEFResnet().to(config.device)\n\noptimizer = Adam(model.parameters(), lr=config.learning_rate)\n\ncheckpoint = {'model': model, 'state_dict': model.state_dict(), 'optimizer' : optimizer.state_dict(), 'loss' : 0.5}\ntorch.save(checkpoint, f'./model_0.bin')\n\ndef run(fold):\n    train_loader, valid_loader = get_data(fold)\n    \n    \n    \n    #model = torch.load(f'./model_{fold}.bin')\n    #model.load_state_dict(torch.load(f'./model_{fold}.bin'))\n    model = load_checkpoint(f'./model_{fold}.bin')\n    \n    \n    \n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, eta_min=1e-5, T_max=10)\n    \n    best_valid_f1 = 0\n    for epoch in range(config.epochs):\n        train_loss = train(model, train_loader, optimizer, scheduler, config.device, epoch)\n        valid_loss, valid_f1 = valid(model, valid_loader, config.device, epoch)\n        if valid_f1 > best_valid_f1:\n            print(f\"Validation F1 Improved - {best_valid_f1} ---> {valid_f1}\")\n            checkpoint = {'model': model, 'state_dict': model.state_dict(), 'optimizer' : optimizer.state_dict(), 'loss' : valid_loss}\n            torch.save(checkpoint, f'./model_{fold + 1}.bin')\n            print(f\"Saved model checkpoint at ./model_{fold + 1}.bin\")\n            best_valid_f1 = valid_f1\n            \n    return best_valid_f1","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.746755Z","iopub.execute_input":"2022-05-24T10:51:20.747443Z","iopub.status.idle":"2022-05-24T10:51:20.822912Z","shell.execute_reply.started":"2022-05-24T10:51:20.747394Z","shell.execute_reply":"2022-05-24T10:51:20.821964Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id='7'></a>\n# Training ⚙️","metadata":{}},{"cell_type":"code","source":"for fold in range(config.num_fold):\n    print(\"=\" * 30)\n    print(\"Training Fold - \", fold)\n    print(\"=\" * 30)\n    best_valid_f1 = run(fold)\n    print(f'Best F1 Score: {best_valid_f1:.5f}')\n    \n    gc.collect()\n    torch.cuda.empty_cache()    \n    ","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:51:20.824777Z","iopub.execute_input":"2022-05-24T10:51:20.82515Z","iopub.status.idle":"2022-05-24T10:52:56.476151Z","shell.execute_reply.started":"2022-05-24T10:51:20.825065Z","shell.execute_reply":"2022-05-24T10:52:56.474419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**TESTING**","metadata":{}},{"cell_type":"code","source":"import json\n\nTEST_AUDIO_PATH = '../input/birdclef-2022/test_soundscapes/'\n\nwith open('../input/birdclef-2022/scored_birds.json') as fp:\n    SCORED_BIRDS = json.load(fp)","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.477659Z","iopub.status.idle":"2022-05-24T10:52:56.478889Z","shell.execute_reply.started":"2022-05-24T10:52:56.478584Z","shell.execute_reply":"2022-05-24T10:52:56.478617Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import math\ndef create_df_test_from_path():\n    files = sorted(os.listdir(TEST_AUDIO_PATH))\n    data = []\n    submission = []\n    for f in files:\n        wv, sr = torchaudio.load(TEST_AUDIO_PATH + f)\n        n_chunks = math.ceil(len(wv[0]) / sr / 5)\n        filename = f\n        row_prefix = f[:-4]\n        bird = SCORED_BIRDS[0]\n        for bird in SCORED_BIRDS:\n            for chunk in range(1, n_chunks + 1):\n            \n                row_id = f\"{f[:-4]}_{bird}_{chunk*5}\"\n            \n                ending_second = chunk*5\n                submission.append((filename, row_prefix, ending_second, [bird]))\n            \n        for chunk in range(1, n_chunks + 1):\n            \n            ending_second = chunk*5\n            data.append((filename, row_prefix, ending_second))    \n            \n            \n    return  pd.DataFrame(submission, columns=['filename', 'row_prefix', 'ending_second', 'birds']), pd.DataFrame(data, columns=['filename', 'row_prefix', 'ending_second'])\n        \nsubmission_df, test_df = create_df_test_from_path()","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.480545Z","iopub.status.idle":"2022-05-24T10:52:56.481423Z","shell.execute_reply.started":"2022-05-24T10:52:56.481088Z","shell.execute_reply":"2022-05-24T10:52:56.48112Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class TestDataset(Dataset):\n    def __init__(self, df, transformation, target_sample_rate, duration):\n        self.audio_paths = df['filename'].values\n        #self.labels = df['birds'].values\n        self.transformation = transformation\n        self.target_sample_rate = target_sample_rate\n        self.num_samples = target_sample_rate*duration\n        self.end_sample = df['ending_second'].values * target_sample_rate\n        \n    def __len__(self):\n        return len(self.audio_paths)\n    \n    def __getitem__(self, index):\n        audio_path = f'../input/birdclef-2022/test_soundscapes/{self.audio_paths[index]}'\n        signal, sr = torchaudio.load(audio_path) # loaded the audio\n        \n        # Now we first checked if the sample rate is same as TARGET_SAMPLE_RATE and if it not equal we perform resampling\n        if sr != self.target_sample_rate:\n            resampler = torchaudio.transforms.Resample(sr, self.target_sample_rate)\n            signal = resampler(signal)\n        \n        # Next we check the number of channels of the signal\n        #signal -> (num_channels, num_samples) - Eg.-(2, 14000) -> (1, 14000)\n        if signal.shape[0]>1:\n            signal = torch.mean(signal, axis=0, keepdim=True)\n        \n        # Seperate the 5 second chunk we want from the signal\n        signal = signal[:, (self.end_sample[index]-self.num_samples):self.end_sample[index]]\n        \n        # Lastly we check the number of samples of the signal\n        #signal -> (num_channels, num_samples) - Eg.-(1, 14000) -> (1, self.num_samples)\n        # If it is more than the required number of samples, we truncate the signal\n        if signal.shape[1] > self.num_samples:\n            signal = signal[:, :self.num_samples]\n        \n        # If it is less than the required number of samples, we pad the signal\n        if signal.shape[1]<self.num_samples:\n            num_missing_samples = self.num_samples - signal.shape[1]\n            last_dim_padding = (0, num_missing_samples)\n            signal = F.pad(signal, last_dim_padding)\n        \n        # Finally all the process has been done and now we will extract mel spectrogram from the signal\n        mel = self.transformation(signal)\n        \n        # For pretrained models, we need 3 channel image, so for that we concatenate the extracted mel\n        image = torch.cat([mel, mel, mel])\n        \n        # Normalized the image\n        max_val = torch.abs(image).max()\n        image = image / max_val\n        \n        #label = torch.tensor(self.labels[index])\n        \n        return image","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.483108Z","iopub.status.idle":"2022-05-24T10:52:56.483961Z","shell.execute_reply.started":"2022-05-24T10:52:56.483658Z","shell.execute_reply":"2022-05-24T10:52:56.48369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def test(model, data_loader, device):\n    model.eval()\n    \n    \n    pred = []\n    #label = []\n    \n    loop = tqdm(data_loader, position=0)\n    for mels in loop:\n        mels = mels.to(device)\n        #labels = labels.to(device)\n        \n        outputs = model(mels)\n        _, preds = torch.max(outputs, 1)\n        \n    \n    return preds, outputs","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.485632Z","iopub.status.idle":"2022-05-24T10:52:56.486494Z","shell.execute_reply.started":"2022-05-24T10:52:56.486174Z","shell.execute_reply":"2022-05-24T10:52:56.486205Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load Test data\ntest_dataset = TestDataset(test_df, mel_spectrogram, config.sample_rate, config.duration)\n\ntest_loader = DataLoader(test_dataset, batch_size=config.train_batch_size, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.48812Z","iopub.status.idle":"2022-05-24T10:52:56.488984Z","shell.execute_reply.started":"2022-05-24T10:52:56.488679Z","shell.execute_reply":"2022-05-24T10:52:56.488711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission_df","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.49068Z","iopub.status.idle":"2022-05-24T10:52:56.491521Z","shell.execute_reply.started":"2022-05-24T10:52:56.491192Z","shell.execute_reply":"2022-05-24T10:52:56.491224Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_df","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.49319Z","iopub.status.idle":"2022-05-24T10:52:56.494074Z","shell.execute_reply.started":"2022-05-24T10:52:56.493753Z","shell.execute_reply":"2022-05-24T10:52:56.493785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_test_checkpoint(filepath):\n    checkpoint = torch.load(filepath)\n    model.load_state_dict(checkpoint['state_dict'])\n    optimizer.load_state_dict(checkpoint['optimizer'])\n    #epoch = checkpoint['epoch']\n    loss = checkpoint['loss']\n    \n\n    model.eval()\n    return model","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.495775Z","iopub.status.idle":"2022-05-24T10:52:56.49665Z","shell.execute_reply.started":"2022-05-24T10:52:56.496313Z","shell.execute_reply":"2022-05-24T10:52:56.496345Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#model = BirdCLEFResnet().to(config.device)\nmodel = load_test_checkpoint( f'./model_{config.num_fold}.bin')\npreds, outputs = test(model, test_loader, config.device)\n\noutputs = nn.Softmax(dim = None)(outputs)","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.49828Z","iopub.status.idle":"2022-05-24T10:52:56.499122Z","shell.execute_reply.started":"2022-05-24T10:52:56.498815Z","shell.execute_reply":"2022-05-24T10:52:56.498846Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions = []\nfor idx in range(len(submission_df)):\n    \n    p = preds[int((submission_df.iloc[idx,2] / 5) - 1)]\n    \n    row_id = submission_df.iloc[idx,1] +'_'+ submission_df.iloc[idx,3][0] + '_' + str(submission_df.iloc[idx,2])\n    if (bird_labels[p] == submission_df.iloc[idx,3][0] and outputs[int((submission_df.iloc[idx,2] / 5) - 1)][p] > 0.0):\n        predictions.append([row_id, True])\n    else:\n        predictions.append([row_id, False])","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.500811Z","iopub.status.idle":"2022-05-24T10:52:56.501695Z","shell.execute_reply.started":"2022-05-24T10:52:56.501345Z","shell.execute_reply":"2022-05-24T10:52:56.501377Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions_df = pd.DataFrame(predictions,columns=['row_id', 'target'])","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.503348Z","iopub.status.idle":"2022-05-24T10:52:56.504215Z","shell.execute_reply.started":"2022-05-24T10:52:56.503898Z","shell.execute_reply":"2022-05-24T10:52:56.50393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions_df.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.505869Z","iopub.status.idle":"2022-05-24T10:52:56.506743Z","shell.execute_reply.started":"2022-05-24T10:52:56.506411Z","shell.execute_reply":"2022-05-24T10:52:56.506441Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions_df","metadata":{"execution":{"iopub.status.busy":"2022-05-24T10:52:56.508433Z","iopub.status.idle":"2022-05-24T10:52:56.509316Z","shell.execute_reply.started":"2022-05-24T10:52:56.508956Z","shell.execute_reply":"2022-05-24T10:52:56.508987Z"},"trusted":true},"execution_count":null,"outputs":[]}]}