{"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":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-04-27T01:19:19.091994Z","iopub.execute_input":"2022-04-27T01:19:19.092563Z","iopub.status.idle":"2022-04-27T01:19:22.162270Z","shell.execute_reply.started":"2022-04-27T01:19:19.092475Z","shell.execute_reply":"2022-04-27T01:19:22.161544Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import math, random\nimport torch\nimport torchaudio\nfrom torchaudio import transforms\nfrom IPython.display import Audio\n\nclass AudioUtil():\n    # ----------------------------\n    # Load an audio file. Return the signal as a tensor and the sample rate\n    # ----------------------------\n    @staticmethod\n    def open(audio_file):\n        sig, sr = torchaudio.load(audio_file)\n        return (sig, sr)\n    \n    # ----------------------------\n    # Convert the given audio to the desired number of channels\n    # ----------------------------\n    @staticmethod\n    def rechannel(aud, new_channel):\n        sig, sr = aud\n\n        if (sig.shape[0] == new_channel):\n            # Nothing to do\n            return aud\n\n        if (new_channel == 1):\n            # Convert from stereo to mono by selecting only the first channel\n            resig = sig[:1, :]\n        else:\n            # Convert from mono to stereo by duplicating the first channel\n            resig = torch.cat([sig, sig])\n\n        return ((resig, sr))\n    \n    # ----------------------------\n    # Pad (or truncate) the signal to a fixed length 'max_ms' in milliseconds\n    # ----------------------------\n    @staticmethod\n    def pad_trunc(aud, max_ms):\n        sig, sr = aud\n        num_rows, sig_len = sig.shape\n        max_len = sr//1000 * max_ms\n\n        if (sig_len > max_len):\n            # Truncate the signal to the given length\n            sig = sig[:,:max_len]\n\n        elif (sig_len < max_len):\n            # Length of padding to add at the beginning and end of the signal\n            pad_begin_len = random.randint(0, max_len - sig_len)\n            pad_end_len = max_len - sig_len - pad_begin_len\n\n            # Pad with 0s\n            pad_begin = torch.zeros((num_rows, pad_begin_len))\n            pad_end = torch.zeros((num_rows, pad_end_len))\n\n            sig = torch.cat((pad_begin, sig, pad_end), 1)\n\n        return (sig, sr)\n\n    # ----------------------------\n    # Shifts the signal to the left or right by some percent. Values at the end\n    # are 'wrapped around' to the start of the transformed signal.\n    # ----------------------------\n    @staticmethod\n    def time_shift(aud, shift_limit):\n        sig,sr = aud\n        _, sig_len = sig.shape\n        shift_amt = int(random.random() * shift_limit * sig_len)\n        return (sig.roll(shift_amt), sr)\n    \n    # ----------------------------\n    # Generate a Spectrogram\n    # ----------------------------\n    @staticmethod\n    def spectro_gram(aud, n_mels=64, n_fft=1024, hop_len=None):\n        sig,sr = aud\n        top_db = 80\n\n        # spec has shape [channel, n_mels, time], where channel is mono, stereo etc\n        spec = transforms.MelSpectrogram(sr, n_fft=n_fft, hop_length=hop_len, n_mels=n_mels)(sig)\n\n        # Convert to decibels\n        spec = transforms.AmplitudeToDB(top_db=top_db)(spec)\n        return (spec)\n    \n    # ----------------------------\n    # Augment the Spectrogram by masking out some sections of it in both the frequency\n    # dimension (ie. horizontal bars) and the time dimension (vertical bars) to prevent\n    # overfitting and to help the model generalise better. The masked sections are\n    # replaced with the mean value.\n    # ----------------------------\n    @staticmethod\n    def spectro_augment(spec, max_mask_pct=0.1, n_freq_masks=1, n_time_masks=1):\n        _, n_mels, n_steps = spec.shape\n        mask_value = spec.mean()\n        aug_spec = spec\n\n        freq_mask_param = max_mask_pct * n_mels\n        for _ in range(n_freq_masks):\n            aug_spec = transforms.FrequencyMasking(freq_mask_param)(aug_spec, mask_value)\n\n        time_mask_param = max_mask_pct * n_steps\n        for _ in range(n_time_masks):\n            aug_spec = transforms.TimeMasking(time_mask_param)(aug_spec, mask_value)\n\n        return aug_spec","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:22.164235Z","iopub.execute_input":"2022-04-27T01:19:22.164482Z","iopub.status.idle":"2022-04-27T01:19:23.746590Z","shell.execute_reply.started":"2022-04-27T01:19:22.164448Z","shell.execute_reply":"2022-04-27T01:19:23.745429Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import json\n\nwith open(\"/kaggle/input/birdclef-2022/scored_birds.json\", \"r\") as file:\n    birds = json.load(file)","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:23.750568Z","iopub.execute_input":"2022-04-27T01:19:23.750810Z","iopub.status.idle":"2022-04-27T01:19:23.759915Z","shell.execute_reply.started":"2022-04-27T01:19:23.750761Z","shell.execute_reply":"2022-04-27T01:19:23.759125Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"meta = pd.read_csv(\"/kaggle/input/birdclef-2022/train_metadata.csv\")\ntrain = meta[meta[\"rating\"] >= 3]\ntrain = train[train[\"primary_label\"].isin(birds)]","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:23.764918Z","iopub.execute_input":"2022-04-27T01:19:23.765110Z","iopub.status.idle":"2022-04-27T01:19:23.881780Z","shell.execute_reply.started":"2022-04-27T01:19:23.765086Z","shell.execute_reply":"2022-04-27T01:19:23.880872Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.utils import resample\n\ndf_majority_1 = train[train.primary_label=='skylar']\ndf_majority_2 = train[train.primary_label=='houfin']\n\n# Upsample minority class\nmajority_1_downsampled = resample(df_majority_1, \n                                 replace=False,     # sample with replacement\n                                 n_samples=20,    \n                                 random_state=42) # reproducible results# Combine majority class with upsampled minority class\n# Upsample minority class\nmajority_2_downsampled = resample(df_majority_2, \n                                 replace=False,     # sample with replacement\n                                 n_samples=20,    \n                                 random_state=42) # reproducible results# Combine majority class with upsampled minority class\n\n\ntrain_without_skylar = train[train['primary_label'] != 'skylar']\ntrain_without_majority = train_without_skylar[train_without_skylar['primary_label'] != 'houfin']\n\ntrain = pd.concat([train_without_majority, majority_1_downsampled, majority_2_downsampled])\n\n# # Display new class counts\n#train.primary_label.value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:23.883279Z","iopub.execute_input":"2022-04-27T01:19:23.883565Z","iopub.status.idle":"2022-04-27T01:19:24.803148Z","shell.execute_reply.started":"2022-04-27T01:19:23.883525Z","shell.execute_reply":"2022-04-27T01:19:24.801576Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.preprocessing import LabelEncoder\nlabels = train['primary_label'].values\nle = LabelEncoder()\nle.fit(np.unique(labels))\nencoded = le.transform(labels)","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:24.804837Z","iopub.execute_input":"2022-04-27T01:19:24.805137Z","iopub.status.idle":"2022-04-27T01:19:24.842558Z","shell.execute_reply.started":"2022-04-27T01:19:24.805095Z","shell.execute_reply":"2022-04-27T01:19:24.841830Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"paths = [f\"/kaggle/input/birdclef-2022/train_audio/{file_name}\" for file_name in train[\"filename\"].values]\ntrain_df = pd.DataFrame({'file_path': paths, 'label' : encoded})\ntrain_df.head()","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:24.844166Z","iopub.execute_input":"2022-04-27T01:19:24.844418Z","iopub.status.idle":"2022-04-27T01:19:24.862483Z","shell.execute_reply.started":"2022-04-27T01:19:24.844382Z","shell.execute_reply":"2022-04-27T01:19:24.861802Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Read in signal, rechannel, pad/truncate, shift, and create mel spectrogram\n## Return spectrogram\n#os.mkdir(\"/kaggle/working/mel_spectrograms\")\n\nimport matplotlib.pyplot as plt\ndef preprocess_audio(original_file_paths):\n    for path in original_file_paths:\n        # open audio\n        audio = AudioUtil.open(path)  \n        print(audio[1])\n        #print(audio[0].shape)\n        \n        # rechannel into 2 channels\n        rechannel = AudioUtil.rechannel(audio, 2)\n        #print(rechannel[0].shape)\n        \n        # pad and/or truncate so audio is same length \n        trunc = AudioUtil.pad_trunc(rechannel, 10000)\n        #print(trunc[0].shape)\n        \n        # time shift audio some random percentage \n        shifted = AudioUtil.time_shift(trunc, 100)\n        #print(shifted[0].shape)\n        \n        # create and modify spectrogram to avoid overfitting\n        spectro_gram = AudioUtil.spectro_gram(shifted)\n        spectro_augment = AudioUtil.spectro_augment(spectro_gram)\n        return spectro_augment\n        ","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:24.863930Z","iopub.execute_input":"2022-04-27T01:19:24.864174Z","iopub.status.idle":"2022-04-27T01:19:24.870910Z","shell.execute_reply.started":"2022-04-27T01:19:24.864141Z","shell.execute_reply":"2022-04-27T01:19:24.870018Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torch.utils.data import DataLoader, Dataset, random_split\nimport torchaudio\n\n# ----------------------------\n# Sound Dataset\n# ----------------------------\nclass SoundDS(Dataset):\n    def __init__(self, df):\n        self.df = df\n        self.duration = 10000\n        self.sr = 32000\n        self.channel = 2\n        self.shift_pct = 0.4\n\n    # ----------------------------\n    # Number of items in dataset\n    # ----------------------------\n    def __len__(self):\n        return len(self.df)    \n\n    # ----------------------------\n    # Get i'th item in dataset\n    # ----------------------------\n    def __getitem__(self, idx):\n        # Absolute file path of the audio file - concatenate the audio directory with\n        # the relative path\n        audio_file = self.df.loc[idx, 'file_path']\n        # Get the Class ID\n        class_id = self.df.loc[idx, 'label']\n\n        aud = AudioUtil.open(audio_file)\n        # Some sounds have a higher sample rate, or fewer channels compared to the\n        # majority. So make all sounds have the same number of channels and same \n        # sample rate. Unless the sample rate is the same, the pad_trunc will still\n        # result in arrays of different lengths, even though the sound duration is\n        # the same.\n        rechan = AudioUtil.rechannel(aud, self.channel)\n\n        dur_aud = AudioUtil.pad_trunc(rechan, self.duration)\n        shift_aud = AudioUtil.time_shift(dur_aud, self.shift_pct)\n        sgram = AudioUtil.spectro_gram(shift_aud, n_mels=64, n_fft=1024, hop_len=None)\n        aug_sgram = AudioUtil.spectro_augment(sgram, max_mask_pct=0.1, n_freq_masks=2, n_time_masks=2)\n\n        return aug_sgram, class_id","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:24.872455Z","iopub.execute_input":"2022-04-27T01:19:24.872786Z","iopub.status.idle":"2022-04-27T01:19:24.885074Z","shell.execute_reply.started":"2022-04-27T01:19:24.872741Z","shell.execute_reply":"2022-04-27T01:19:24.884069Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torch.utils.data import random_split\n\nmyds = SoundDS(train_df)\n\n# Random split of 80:20 between training and validation\nnum_items = len(myds)\nnum_train = round(num_items * 0.8)\nnum_val = num_items - num_train\ntrain_ds, val_ds = random_split(myds, [num_train, num_val])\n\n# Create training and validation data loaders\ntrain_dl = torch.utils.data.DataLoader(train_ds, batch_size=16, shuffle=True)\nval_dl = torch.utils.data.DataLoader(val_ds, batch_size=16, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:19:24.888136Z","iopub.execute_input":"2022-04-27T01:19:24.888583Z","iopub.status.idle":"2022-04-27T01:19:24.904090Z","shell.execute_reply.started":"2022-04-27T01:19:24.888543Z","shell.execute_reply":"2022-04-27T01:19:24.903038Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch.nn.functional as F\nfrom torch.nn import init\nimport torch.nn as nn\n\n# ----------------------------\n# Audio Classification Model\n# ----------------------------\nclass AudioClassifier (nn.Module):\n    # ----------------------------\n    # Build the model architecture\n    # ----------------------------\n    def __init__(self):\n        super().__init__()\n        conv_layers = []\n\n        # First Convolution Block with Relu and Batch Norm. Use Kaiming Initialization\n        self.conv1 = nn.Conv2d(2, 8, kernel_size=(5, 5), stride=(2, 2), padding=(2, 2))\n        self.relu1 = nn.ReLU()\n        self.bn1 = nn.BatchNorm2d(8)\n        init.kaiming_normal_(self.conv1.weight, a=0.1)\n        self.conv1.bias.data.zero_()\n        conv_layers += [self.conv1, self.relu1, self.bn1]\n\n        # Second Convolution Block\n        self.conv2 = nn.Conv2d(8, 16, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))\n        self.relu2 = nn.ReLU()\n        self.bn2 = nn.BatchNorm2d(16)\n        init.kaiming_normal_(self.conv2.weight, a=0.1)\n        self.conv2.bias.data.zero_()\n        conv_layers += [self.conv2, self.relu2, self.bn2]\n\n        # Second Convolution Block\n        self.conv3 = nn.Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))\n        self.relu3 = nn.ReLU()\n        self.bn3 = nn.BatchNorm2d(32)\n        init.kaiming_normal_(self.conv3.weight, a=0.1)\n        self.conv3.bias.data.zero_()\n        conv_layers += [self.conv3, self.relu3, self.bn3]\n\n        # Second Convolution Block\n        self.conv4 = nn.Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))\n        self.relu4 = nn.ReLU()\n        self.bn4 = nn.BatchNorm2d(64)\n        init.kaiming_normal_(self.conv4.weight, a=0.1)\n        self.conv4.bias.data.zero_()\n        conv_layers += [self.conv4, self.relu4, self.bn4]\n\n        # Linear Classifier\n        self.ap = nn.AdaptiveAvgPool2d(output_size=1)\n        self.lin = nn.Linear(in_features=64, out_features=21)\n\n        # Wrap the Convolutional Blocks\n        self.conv = nn.Sequential(*conv_layers)\n \n    # ----------------------------\n    # Forward pass computations\n    # ----------------------------\n    def forward(self, x):\n        # Run the convolutional blocks\n        x = self.conv(x)\n\n        # Adaptive pool and flatten for input to linear layer\n        x = self.ap(x)\n        x = x.view(x.shape[0], -1)\n\n        # Linear layer\n        x = self.lin(x)\n\n        # Final output\n        return x\n\n# Create the model and put it on the GPU if available\nmyModel = AudioClassifier()\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nmyModel = myModel.to(device)\n# Check that it is on Cuda\nnext(myModel.parameters()).device","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:45:36.362697Z","iopub.execute_input":"2022-04-27T01:45:36.363373Z","iopub.status.idle":"2022-04-27T01:45:36.392346Z","shell.execute_reply.started":"2022-04-27T01:45:36.363335Z","shell.execute_reply":"2022-04-27T01:45:36.391639Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ----------------------------\n# Training Loop\n# ----------------------------\nfrom tqdm import tqdm\ndef training(model, train_dl, num_epochs):\n    # Loss Function, Optimizer and Scheduler\n    criterion = nn.CrossEntropyLoss()\n    optimizer = torch.optim.Adam(model.parameters(),lr=0.001)\n    scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=0.001,\n                                                steps_per_epoch=int(len(train_dl)),\n                                                epochs=num_epochs,\n                                                anneal_strategy='linear')\n\n    # Repeat for each epoch\n    for epoch in range(num_epochs):\n        running_loss = 0.0\n        correct_prediction = 0\n        total_prediction = 0\n\n        # Repeat for each batch in the training set\n        for i, data in tqdm(enumerate(train_dl)):\n            # Get the input features and target labels, and put them on the GPU\n            inputs, labels = data[0].to(device), data[1].to(device)\n\n            # Normalize the inputs\n            inputs_m, inputs_s = inputs.mean(), inputs.std()\n            inputs = (inputs - inputs_m) / inputs_s\n\n            # Zero the parameter gradients\n            optimizer.zero_grad()\n\n            # forward + backward + optimize\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            scheduler.step()\n\n            # Keep stats for Loss and Accuracy\n            running_loss += loss.item()\n\n            # Get the predicted class with the highest score\n            _, prediction = torch.max(outputs,1)\n            # Count of predictions that matched the target label\n            correct_prediction += (prediction == labels).sum().item()\n            total_prediction += prediction.shape[0]\n\n#             if i % 10 == 0:    # print every 10 mini-batches\n#                 print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 10))\n\n        # Print stats at the end of the epoch\n        num_batches = len(train_dl)\n        avg_loss = running_loss / num_batches\n        acc = correct_prediction/total_prediction\n        print(f'Epoch: {epoch}, Loss: {avg_loss:.2f}, Accuracy: {acc:.2f}')\n        \n    print(\"Finished Training\")\n\nnum_epochs=30   \ntraining(myModel, train_dl, num_epochs)","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:45:36.599126Z","iopub.execute_input":"2022-04-27T01:45:36.599374Z","iopub.status.idle":"2022-04-27T01:55:31.456098Z","shell.execute_reply.started":"2022-04-27T01:45:36.599345Z","shell.execute_reply":"2022-04-27T01:55:31.453001Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ----------------------------\n# Inference\n# ----------------------------\ndef inference (model, val_dl):\n    correct_prediction = 0\n    total_prediction = 0\n\n    # Disable gradient updates\n    with torch.no_grad():\n        for data in val_dl:\n            # Get the input features and target labels, and put them on the GPU\n            inputs, labels = data[0].to(device), data[1].to(device)\n\n            # Normalize the inputs\n            inputs_m, inputs_s = inputs.mean(), inputs.std()\n            inputs = (inputs - inputs_m) / inputs_s\n\n            # Get predictions\n            outputs = model(inputs)\n\n            # Get the predicted class with the highest score\n            _, prediction = torch.max(outputs,1)\n            # Count of predictions that matched the target label\n            correct_prediction += (prediction == labels).sum().item()\n            total_prediction += prediction.shape[0]\n\n    acc = correct_prediction/total_prediction\n    print(f'Accuracy: {acc:.2f}, Total items: {total_prediction}')\n\n# Run inference on trained model with the validation set\ninference(myModel, val_dl)","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:55:31.459237Z","iopub.status.idle":"2022-04-27T01:55:31.461730Z","shell.execute_reply.started":"2022-04-27T01:55:31.461445Z","shell.execute_reply":"2022-04-27T01:55:31.461473Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Save Model\ntorch.save(myModel.state_dict(), \"/kaggle/working/saved_model.pth\")","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:57:50.127280Z","iopub.execute_input":"2022-04-27T01:57:50.127549Z","iopub.status.idle":"2022-04-27T01:57:50.137719Z","shell.execute_reply.started":"2022-04-27T01:57:50.127518Z","shell.execute_reply":"2022-04-27T01:57:50.136912Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load in Classifier\n# model = AudioClassifier()\n# # fill your architecture with the trained weights\n# model.load_state_dict(torch.load(\"/kaggle/working/saved_model.pth\"))","metadata":{"execution":{"iopub.status.busy":"2022-04-27T01:58:45.905865Z","iopub.execute_input":"2022-04-27T01:58:45.906606Z","iopub.status.idle":"2022-04-27T01:58:45.923364Z","shell.execute_reply.started":"2022-04-27T01:58:45.906567Z","shell.execute_reply":"2022-04-27T01:58:45.922650Z"},"trusted":true},"execution_count":null,"outputs":[]}]}