{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":44224,"databundleVersionId":5188730,"sourceType":"competition"}],"dockerImageVersionId":30698,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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# !pip install audiomentations\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport torch\nfrom torch import nn\nfrom torch.optim import Adam\nfrom torch.utils.data import Dataset, DataLoader\n\nimport librosa\nfrom scipy.signal import stft\n# from audiomentations import Compose, AddGaussianNoise, TimeStretch, PitchShift, Shift, AddShortNoises, RepeatPart, TimeMask, ApplyImpulseResponse, AdjustDuration\nfrom IPython.display import Audio\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score, f1_score, classification_report, confusion_matrix\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\nimport glob\nfrom tqdm.notebook import tqdm, trange\n\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nsr = 32000\nseq_len = sr*5\nwidth = int(sr*0.5)\nhop = int(sr*0.25)\n\n# for 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":"2024-05-13T04:33:18.610578Z","iopub.execute_input":"2024-05-13T04:33:18.611218Z","iopub.status.idle":"2024-05-13T04:33:23.632700Z","shell.execute_reply.started":"2024-05-13T04:33:18.611184Z","shell.execute_reply":"2024-05-13T04:33:23.631595Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Helper Functions","metadata":{}},{"cell_type":"code","source":"def read_data():\n    df = pd.read_csv('/kaggle/input/birdclef-2023/train_metadata.csv')\n    df['filepath'] = df['filename'].apply(lambda x: '/kaggle/input/birdclef-2023/train_audio/'+ x)\n    \n    return df\n\n\ndef balance_df(df):\n    sample_count = max(df.primary_label.value_counts())\n    \n    balanced_df = []\n    augmentation_proba = {}\n    \n    for i, label in enumerate(df.primary_label.unique()):\n        selected_ids = np.random.choice(df[df['primary_label'] == label].index, sample_count)\n        balanced_df.append(df.loc[selected_ids])\n        augmentation_proba[label] = 1 - (len(df[df['primary_label'] == label]) / sample_count)\n        \n    balanced_df = pd.concat(balanced_df, axis=0)\n    \n    return balanced_df, augmentation_proba\n\ndef balance_test(df):\n    sample_count = 10\n    \n    balanced_df = []\n    augmentation_proba = {}\n    \n    for i, label in enumerate(df.primary_label.unique()):\n        selected_ids = np.random.choice(df[df['primary_label'] == label].index, sample_count) if len(df[df['primary_label'] == label]) < sample_count else df[df['primary_label'] == label].index\n        balanced_df.append(df.loc[selected_ids])\n        augmentation_proba[label] = 1 - (len(df[df['primary_label'] == label]) / sample_count) if len(df[df['primary_label'] == label]) < sample_count else 0\n        \n    balanced_df = pd.concat(balanced_df, axis=0)\n    \n    return balanced_df, augmentation_proba       \n\ndef stratified_train_test_split(df, test_size=0.3):\n    train_data, test_data = [], []\n    \n    for i, label in enumerate(df.primary_label.unique()):\n        if len(df[df.primary_label == label]) == 1:\n            train = df[df.primary_label == label]\n            test = df[df.primary_label == label]\n        else:            \n            train, test = train_test_split(df[df.primary_label == label], test_size=test_size)\n        train_data.append(train)\n        test_data.append(test)\n\n    train_data = pd.concat(train_data)\n    test_data = pd.concat(test_data)\n    \n    return train_data, test_data","metadata":{"execution":{"iopub.status.busy":"2024-05-13T04:33:23.634849Z","iopub.execute_input":"2024-05-13T04:33:23.635280Z","iopub.status.idle":"2024-05-13T04:33:23.651124Z","shell.execute_reply.started":"2024-05-13T04:33:23.635253Z","shell.execute_reply":"2024-05-13T04:33:23.650030Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = read_data()\ntrain, test = stratified_train_test_split(df, test_size=0.2)\ntrain, aug_prob = balance_df(train)\nlabel2idx = {label: idx for idx, label in enumerate(train['primary_label'].unique())}\nidx2label = {idx: label for idx, label in enumerate(train['primary_label'].unique())}","metadata":{"execution":{"iopub.status.busy":"2024-05-13T04:33:23.652339Z","iopub.execute_input":"2024-05-13T04:33:23.652665Z","iopub.status.idle":"2024-05-13T04:33:27.400267Z","shell.execute_reply.started":"2024-05-13T04:33:23.652639Z","shell.execute_reply":"2024-05-13T04:33:27.399446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# DataLoader","metadata":{}},{"cell_type":"code","source":"class BirdDataset(Dataset):\n    def __init__(self, df, width, hop, seq_len = seq_len):\n        super().__init__()\n        self.df = df.reset_index(drop = True)\n        self.width = width\n        self.hop = hop\n        self.seq_len = seq_len\n        \n    def __len__(self):\n        return len(self.df)\n    \n    \n    def prepare_X(self, path):\n        wav, _ = librosa.load(path, sr=sr)\n        \n        if len(wav) < self.seq_len:\n            padded_wav = np.zeros((self.seq_len)).astype(np.float64)\n            padded_wav[:len(wav)] = wav\n            wav = padded_wav\n            del padded_wav\n        \n        start_idx = np.random.choice(range(len(wav) - self.seq_len)) if len(wav) > self.seq_len else 0 \n        wav = wav[start_idx: start_idx + self.seq_len]\n        \n        num_slices = int((len(wav) - self.width) / (self.width - self.hop))\n        wav = [wav[i*self.width - i*self.hop: (i+1)*self.width - i*self.hop] for i in range(num_slices)]\n        \n        return wav\n        \n        \n    def __getitem__(self, idx):\n        if self.__len__() <= idx:\n            raise KeyError\n            \n        wav_path = self.df.loc[idx, 'filepath'] \n#         print(wav_path)\n        wav = self.prepare_X(wav_path)\n        wav = torch.tensor(wav).to(device)\n        \n        label = self.df.loc[idx, 'primary_label']\n        label = label2idx[label]\n        y = torch.zeros(264)\n        y[label] = 1\n        label = torch.tensor(label).to(device)\n        \n        return wav, y\n    \ndataset = BirdDataset(train, width, hop)\n# dataset[100][0].shape","metadata":{"execution":{"iopub.status.busy":"2024-05-13T04:33:27.402438Z","iopub.execute_input":"2024-05-13T04:33:27.402796Z","iopub.status.idle":"2024-05-13T04:33:27.460819Z","shell.execute_reply.started":"2024-05-13T04:33:27.402764Z","shell.execute_reply":"2024-05-13T04:33:27.460030Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# def get_len(x):\n#     wav, _ = librosa.load(x, sr=32_000)\n#     return len(wav)\n\n# df['length'] = df['filepath'].apply(lambda x: get_len(x))","metadata":{"execution":{"iopub.status.busy":"2024-05-13T02:57:18.750867Z","iopub.execute_input":"2024-05-13T02:57:18.751203Z","iopub.status.idle":"2024-05-13T02:57:18.755296Z","shell.execute_reply.started":"2024-05-13T02:57:18.751180Z","shell.execute_reply":"2024-05-13T02:57:18.754394Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = BirdDataset(train, width, hop)\ntest = BirdDataset(test, width, hop)\n\ntrain = DataLoader(train, batch_size=8)\ntest = DataLoader(test, batch_size=8)","metadata":{"execution":{"iopub.status.busy":"2024-05-13T04:33:27.461807Z","iopub.execute_input":"2024-05-13T04:33:27.462082Z","iopub.status.idle":"2024-05-13T04:33:27.515466Z","shell.execute_reply.started":"2024-05-13T04:33:27.462057Z","shell.execute_reply":"2024-05-13T04:33:27.514655Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X, y = dataset[100]\nplt.subplots(len(X), 1, figsize=(10, 20))\nfor i in range(len(X)):\n    plt.subplot(len(X),1,  i + 1)\n    plt.plot(X.cpu().numpy().T[:, i])\nplt.show()    ","metadata":{"execution":{"iopub.status.busy":"2024-05-13T02:57:21.391157Z","iopub.execute_input":"2024-05-13T02:57:21.392165Z","iopub.status.idle":"2024-05-13T02:57:32.945788Z","shell.execute_reply.started":"2024-05-13T02:57:21.392123Z","shell.execute_reply":"2024-05-13T02:57:32.944891Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model Implementation","metadata":{}},{"cell_type":"code","source":"class WaveBlock(nn.Module):\n    def __init__(self, in_features, filters, kernel_size, n):\n        super().__init__()\n        self.filters = filters\n        self.kernel_size = kernel_size\n        self.n = n\n        \n        self.cas_conv1 = nn.Conv1d(in_features, filters, 1)\n        \n        dilation_rates = [2**i for i in range(n)]\n        \n        self.tanh_out_layers = nn.ModuleList([])\n        self.sig_out_layers = nn.ModuleList([])\n        self.cas_conv_layers = nn.ModuleList([])\n        \n        for dilation_rate in dilation_rates:\n            tanh_out = nn.Sequential(*[\n                nn.Conv1d(filters, filters, kernel_size, dilation=dilation_rate, padding='same'),\n                nn.Tanh()\n            ])\n            self.tanh_out_layers.append(tanh_out)\n            \n            sig_out = nn.Sequential(*[\n                nn.Conv1d(filters, filters, kernel_size, dilation=dilation_rate, padding='same'),\n                nn.Sigmoid()\n            ])\n            self.sig_out_layers.append(sig_out)\n            \n            self.cas_conv_layers.append(nn.Conv1d(filters, filters, 1))\n            \n    def forward(self, x):\n            \n        x = self.cas_conv1(x)\n        res_x = x\n        \n        for tanh_layer, sig_layer, conv_layer in zip(self.tanh_out_layers, self.sig_out_layers, self.cas_conv_layers):\n            x = tanh_layer(x)*sig_layer(x)\n                \n            x = conv_layer(x)\n            \n        x = x + res_x\n        del res_x\n            \n        return x\n            \n\n\n\nclass BirdNet(nn.Module):\n    def __init__(self, temporal_feature_size = 64, kernel_size =3, hidden_size = 256, num_classes = 264):\n        super().__init__()\n        self.representation_block = nn.Sequential(*[\n            WaveBlock(1, 8, kernel_size, 16),\n            nn.BatchNorm1d(8),\n            nn.ReLU(),\n            WaveBlock(8, 16, kernel_size, 8),\n            nn.BatchNorm1d(16),\n            nn.ReLU(),\n            WaveBlock(16, 32, kernel_size, 4),\n            nn.BatchNorm1d(32),\n            nn.ReLU(),\n            WaveBlock(32, temporal_feature_size, kernel_size, 1),\n            \n        ])\n        \n        self.temporal_block = nn.LSTM(temporal_feature_size, hidden_size) \n        self.classifier = nn.Linear(hidden_size, num_classes)\n        \n    def forward(self, x):\n        x = torch.concat([self.representation_block(x[:, i, :].unsqueeze(1)).unsqueeze(0) for i in range(x.shape[1])])\n        x = torch.mean(x, dim=-1)\n        x = torch.sum(self.temporal_block(x)[0], axis=0)\n        x = nn.ReLU()(x)\n        x = self.classifier(x)\n        x = nn.Sigmoid()(x)\n        \n        return x\n    \nmodel = BirdNet().to(device)\nx = torch.rand((16, 26, 16000)).to(device)\npred = model(x)\npred.shape","metadata":{"execution":{"iopub.status.busy":"2024-05-13T04:33:28.362923Z","iopub.execute_input":"2024-05-13T04:33:28.363678Z","iopub.status.idle":"2024-05-13T04:33:29.821453Z","shell.execute_reply.started":"2024-05-13T04:33:28.363645Z","shell.execute_reply":"2024-05-13T04:33:29.820569Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"epochs = 5\nmodel = BirdNet().to(device)\ncriterion = nn.BCELoss()\nlr = 1e-4\noptimizer = Adam(model.parameters(), lr=lr)","metadata":{"execution":{"iopub.status.busy":"2024-05-13T04:33:29.925067Z","iopub.execute_input":"2024-05-13T04:33:29.925381Z","iopub.status.idle":"2024-05-13T04:33:32.474872Z","shell.execute_reply.started":"2024-05-13T04:33:29.925355Z","shell.execute_reply":"2024-05-13T04:33:32.473978Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training Loop","metadata":{}},{"cell_type":"code","source":"history = {}\n\n\nfor epoch in trange(epochs): \n    model.train()\n    for i, (X, y) in enumerate(tqdm(train)):\n        X = X.to(device).to(torch.float)\n        y = y.to(device).to(torch.float)\n        \n        optimizer.zero_grad()\n        pred = model(X)\n#         print('D')\n        loss = criterion(pred, y)\n        loss.backward()\n        optimizer.step()\n        \n        if (i % 100 == 0) & (i != 0):\n            preds = []\n            ys = []\n            with torch.no_grad():\n                for j, (X, y) in enumerate(tqdm(test)):\n                    X = X.to(device).to(torch.float)\n                    y = y.to(device).to(torch.float)\n\n                    pred = model(X)\n\n                    preds.append(pred)\n                    ys.append(y)\n\n                preds = torch.concat(preds, dim=0).detach().cpu().numpy()\n                ys = torch.concat(ys, dim=0).detach().cpu().numpy()\n\n                scores = []\n                for i in range(preds.shape[-1]):\n                    score = f1_score(ys[:, i], (preds[:, i] > .5).astype(int))\n                    scores.append(score)\n                \n                plt.figure(figsize=(15, 5))\n                plt.bar(x = range(264), height=scores)\n                plt.show()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}