{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"},{"sourceId":228025862,"sourceType":"kernelVersion"},{"sourceId":289251,"sourceType":"modelInstanceVersion","modelInstanceId":247811,"modelId":269335},{"sourceId":289267,"sourceType":"modelInstanceVersion","modelInstanceId":247811,"modelId":269335},{"sourceId":291416,"sourceType":"modelInstanceVersion","modelInstanceId":249680,"modelId":271197},{"sourceId":296761,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":251479,"modelId":272957}],"dockerImageVersionId":30919,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# BirdCLEF+ Inference\n## ECAPA-TDNN","metadata":{}},{"cell_type":"code","source":"# This package is responsible to normalize and put some variation on our Audio files.\n# If you Turn The Internet On, This Block doesn`t necessary to be run and instead of this you can run following command\n# !pip install audiomentations\n# !pip install /kaggle/input/birdclef-packages/other/default/3/numpy_minmax-0.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl\n# !pip install /kaggle/input/birdclef-packages/other/default/3/python_stretch-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\n# !pip install /kaggle/input/birdclef-packages/other/default/3/numpy_rms-0.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl\n# !pip install /kaggle/input/birdclef-packages/other/default/4/scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\n# !pip install /kaggle/input/birdclef-packages/other/default/3/audiomentations-0.39.0-py3-none-any.whl","metadata":{"_uuid":"606d4bce-b391-4062-b82e-dd14dd223f18","_cell_guid":"ecfe3fcd-25b1-4ec8-9b8d-2664aa05ff82","trusted":true,"scrolled":true,"execution":{"iopub.status.busy":"2025-03-22T07:05:39.989170Z","iopub.execute_input":"2025-03-22T07:05:39.989476Z","iopub.status.idle":"2025-03-22T07:06:08.034360Z","shell.execute_reply.started":"2025-03-22T07:05:39.989450Z","shell.execute_reply":"2025-03-22T07:06:08.032930Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Import Packages","metadata":{}},{"cell_type":"code","source":"import math\nimport os\nfrom typing import List\nfrom pathlib import Path\nimport pandas as pd\nfrom tqdm.notebook import tqdm\n\nimport numpy as np\n\nimport librosa\nimport torchaudio\nimport torchaudio.compliance.kaldi as Kaldi\nimport torch\nimport torch.nn.functional as F\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\n\n# import audiomentations as A\n\nimport ecapa_tdnn as ecapa","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T07:20:39.117905Z","iopub.execute_input":"2025-03-22T07:20:39.118323Z","iopub.status.idle":"2025-03-22T07:20:39.124411Z","shell.execute_reply.started":"2025-03-22T07:20:39.118288Z","shell.execute_reply":"2025-03-22T07:20:39.123483Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load Weight","metadata":{}},{"cell_type":"code","source":"DEVICE = torch.device('cpu')\nstates = torch.load('/kaggle/input/birdclef-ecapa-tdnn/pytorch/default/2/ecapa-tdnn-5sec-best-v0.0.1.pt', map_location=DEVICE)\nmeta = states['meta']\n\n# Audio parameters\nFS = 32000  \nWINDOW_SIZE = 5  \n\nclass_labels = sorted(os.listdir('/kaggle/input/birdclef-2025/train_audio/'))\nlabel2id = meta['label2id']\nid2label = {v:k for k,v in label2id.items()}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T07:06:11.879051Z","iopub.execute_input":"2025-03-22T07:06:11.879613Z","iopub.status.idle":"2025-03-22T07:06:12.906524Z","shell.execute_reply.started":"2025-03-22T07:06:11.879571Z","shell.execute_reply":"2025-03-22T07:06:12.905540Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset","metadata":{}},{"cell_type":"code","source":"class CLEFDataset(Dataset):\n    def __init__(self, audio_list:List[Path]):\n        self.audio_list = audio_list\n        # self.transform = transform\n        # self.ext = ext\n        # self.sr = sample_rate\n\n    def __len__(self)->int:\n        return len(self.audio_list)\n\n    def __getitem__(self, idx):\n        wav, sr = librosa.load(self.audio_list[idx], sr=FS)\n        wav = wav.reshape(-1, FS*5)\n        # wav = wav.numpy().squeeze()\n\n        # if self.transform is not None:\n        #     wav = self.transform(wav, self.sr)\n\n        # wav = torch.from_numpy(wav).unsqueeze(0)\n        \n        # if self.ext is not None:\n        #     wav = self.ext(wav)\n        return wav, sr, Path(self.audio_list[idx]).stem","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T07:21:15.811750Z","iopub.execute_input":"2025-03-22T07:21:15.812121Z","iopub.status.idle":"2025-03-22T07:21:15.818012Z","shell.execute_reply.started":"2025-03-22T07:21:15.812089Z","shell.execute_reply":"2025-03-22T07:21:15.816985Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prepare paths","metadata":{}},{"cell_type":"code","source":"# tests_audio = list(Path('/kaggle/input/birdclef-2025/test_soundscapes').glob('*.ogg'))\n# tests_audio = list(Path('/kaggle/input/birdclef-2025/train_soundscapes').glob('*.ogg'))[:500]\n# tests_path = '/kaggle/input/birdclef-2025/train_soundscapes'\ntests_path = '/kaggle/input/birdclef-2025/test_soundscapes'\ntests_audio = [os.path.join(tests_path, afile) for afile in sorted(os.listdir(tests_path)) if afile.endswith('.ogg')]\n\nif len(tests_audio) == 0:\n    tests_path = '/kaggle/input/birdclef-2025/train_soundscapes'\n    tests_audio = [os.path.join(tests_path, afile) for afile in sorted(os.listdir(tests_path)) if afile.endswith('.ogg')][:3]\n\n# test_transforms = A.Compose(\n#     [\n#         A.AdjustDuration(duration_samples=int(meta['wav_len'] * meta['sr']),p=1.),\n#     ]\n# )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T07:35:58.461471Z","iopub.execute_input":"2025-03-22T07:35:58.461867Z","iopub.status.idle":"2025-03-22T07:35:58.469662Z","shell.execute_reply.started":"2025-03-22T07:35:58.461835Z","shell.execute_reply":"2025-03-22T07:35:58.468725Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## FBank Feature","metadata":{}},{"cell_type":"code","source":"class FBank(object):\n    def __init__(self,\n        n_mels,\n        sample_rate,\n        mean_nor: bool = False,\n    ):\n        self.n_mels = n_mels\n        self.sample_rate = sample_rate\n        self.mean_nor = mean_nor\n\n    def __call__(self, wav, dither=0):\n        if len(wav.shape) == 1:\n            wav = wav.unsqueeze(0)\n        # select single channel\n        if wav.shape[0] > 1:\n            wav = wav[0, :]\n        assert len(wav.shape) == 2 and wav.shape[0]==1\n        feat = Kaldi.fbank(wav, num_mel_bins=self.n_mels,\n            sample_frequency=self.sample_rate, dither=dither)\n        # feat: [T, N]\n        if self.mean_nor:\n            feat = feat - feat.mean(0, keepdim=True)\n        return feat","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T07:06:21.758300Z","iopub.execute_input":"2025-03-22T07:06:21.758672Z","iopub.status.idle":"2025-03-22T07:06:21.764305Z","shell.execute_reply.started":"2025-03-22T07:06:21.758636Z","shell.execute_reply":"2025-03-22T07:06:21.763342Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_nonlinear(config_str, channels):\n    nonlinear = nn.Sequential()\n    for name in config_str.split('-'):\n        if name == 'relu':\n            nonlinear.add_module('relu', nn.ReLU(inplace=True))\n        elif name == 'prelu':\n            nonlinear.add_module('prelu', nn.PReLU(channels))\n        elif name == 'batchnorm':\n            nonlinear.add_module('batchnorm', nn.BatchNorm1d(channels))\n        elif name == 'batchnorm_':\n            nonlinear.add_module('batchnorm',\n                                 nn.BatchNorm1d(channels, affine=False))\n        else:\n            raise ValueError('Unexpected module ({}).'.format(name))\n    return nonlinear\n\nclass DenseLayer(nn.Module):\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 bias=False,\n                 config_str='batchnorm-relu'):\n        super(DenseLayer, self).__init__()\n        self.linear = nn.Conv1d(in_channels, out_channels, 1, bias=bias)\n        self.nonlinear = get_nonlinear(config_str, out_channels)\n\n    def forward(self, x):\n        if len(x.shape) == 2:\n            x = self.linear(x.unsqueeze(dim=-1)).squeeze(dim=-1)\n        else:\n            x = self.linear(x)\n        x = self.nonlinear(x)\n        return x\n\nclass CosineClassifier(nn.Module):\n    def __init__(\n        self,\n        input_dim,\n        num_blocks=0,\n        inter_dim=512,\n        out_neurons=1000,\n    ):\n\n        super().__init__()\n        self.blocks = nn.ModuleList()\n\n        for index in range(num_blocks):\n            self.blocks.append(\n                DenseLayer(input_dim, inter_dim, config_str='batchnorm')\n            )\n            input_dim = inter_dim\n\n        self.weight = nn.Parameter(\n            torch.FloatTensor(out_neurons, input_dim)\n        )\n        nn.init.xavier_uniform_(self.weight)\n\n    def forward(self, x):\n        # x: [B, dim]\n        for layer in self.blocks:\n            x = layer(x)\n\n        # normalized\n        x = F.linear(F.normalize(x), F.normalize(self.weight))\n        return x\n        \nclass BirdCLEFClassifier(nn.Module):\n    def __init__(self, backbone, n_classes:int, input_dim: int):\n        super().__init__()\n        self.backbone = backbone\n        self.head = CosineClassifier(input_dim=input_dim, out_neurons=n_classes)\n\n\n    def forward(self, x):\n        feats = self.backbone(x)\n        return self.head(feats)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T07:06:22.022629Z","iopub.execute_input":"2025-03-22T07:06:22.022936Z","iopub.status.idle":"2025-03-22T07:06:22.034579Z","shell.execute_reply.started":"2025-03-22T07:06:22.022911Z","shell.execute_reply":"2025-03-22T07:06:22.033643Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"fbank = FBank(n_mels=meta['fbank_features'], mean_nor=True, sample_rate=meta['sr'])\n\n# Define Feature Extractor\nextractor = ecapa.ECAPA_TDNN(input_size=meta['fbank_features'], lin_neurons=meta['lin_out'], channels=meta['channels'])\nextractor.fc = ecapa.Conv1d(in_channels=meta['channels'][-1] * 2, out_channels=meta['lin_out'], kernel_size=1)\n\nmodel = BirdCLEFClassifier(extractor, len(meta['label2id']), meta['lin_out'])\nmodel.load_state_dict(states['state_dict'])\n\nmodel.to(DEVICE)","metadata":{"trusted":true,"scrolled":true,"execution":{"iopub.status.busy":"2025-03-22T07:06:26.063008Z","iopub.execute_input":"2025-03-22T07:06:26.063346Z","iopub.status.idle":"2025-03-22T07:06:26.351895Z","shell.execute_reply.started":"2025-03-22T07:06:26.063317Z","shell.execute_reply":"2025-03-22T07:06:26.351095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"loader = DataLoader(CLEFDataset(tests_audio), batch_size=1, num_workers=2, shuffle=False, prefetch_factor=2)\nclass_labels_models = [id2label[i] for i in range(len(id2label))]\nmodel.eval()\n\nrow_ids = []\nmatrix = []\nwith torch.inference_mode():    \n    bar = tqdm(enumerate(loader), total=len(loader))\n    # Read Audio Paths\n    for idx, (audio, sr, soundscape) in bar:\n        audio = audio[0]\n        sr = sr.item()\n        soundscape = soundscape[0]\n        feats = []\n        for feat in audio:\n            feat = fbank(feat).unsqueeze(0)\n            feats.append(feat)\n        audio = torch.concatenate(feats, dim=0)\n        outputs = model(audio)\n        logits = F.softmax(outputs, dim=-1).detach().numpy()\n        soundscape = os.path.basename(soundscape).split('.')[0]\n        row_id = [f\"{soundscape}_{(i+1)*5}\" for i in range(0, logits.shape[0])]\n\n        row_ids += row_id\n        matrix.append(logits)\nmatrix = np.concatenate(matrix)\nmatrix = np.concatenate([np.array(row_ids).reshape(-1, 1), matrix], axis=1)\nsample_sub = pd.read_csv('/kaggle/input/birdclef-2025/sample_submission.csv')\nsub_csv = pd.DataFrame(matrix, columns=[\"row_id\", *class_labels])\nsub_csv.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-22T07:21:21.167689Z","iopub.execute_input":"2025-03-22T07:21:21.168066Z","iopub.status.idle":"2025-03-22T07:33:18.649992Z","shell.execute_reply.started":"2025-03-22T07:21:21.168028Z","shell.execute_reply":"2025-03-22T07:33:18.648627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sub_csv.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}