{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","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"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torchaudio\nimport torch\nimport torch.nn.functional as F\nimport torchaudio.transforms as T\nfrom torch.utils.data import Dataset, DataLoader\nfrom joblib import Parallel, delayed\nfrom tqdm.notebook import tqdm\nimport random\nimport tempfile\nimport os","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:26:00.056366Z","iopub.execute_input":"2025-05-11T21:26:00.056720Z","iopub.status.idle":"2025-05-11T21:26:07.683101Z","shell.execute_reply.started":"2025-05-11T21:26:00.056681Z","shell.execute_reply":"2025-05-11T21:26:07.682036Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUM_EPOCHS = 5\nBATCH_SIZE = 16\nNUM_WORKERS = 4\nAUDIO_CROP_SIZE = 32000*5\n\nTRAIN_DIR = '/kaggle/input/birdclef-2025/train_audio/'\ndf = pd.read_csv('/kaggle/input/birdclef-2025/train.csv')\nFILENAMES = list(TRAIN_DIR + f for f in df.filename)\n\n# precompute # of frames for each audio file\nNUM_FRAMES = Parallel(n_jobs=-1)(\n    delayed(lambda f: torchaudio.info(f).num_frames)(f) \n    for f in tqdm(FILENAMES, desc=\"Loading durations\")\n)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:26:07.683832Z","iopub.execute_input":"2025-05-11T21:26:07.684174Z","iopub.status.idle":"2025-05-11T21:26:34.893696Z","shell.execute_reply.started":"2025-05-11T21:26:07.684153Z","shell.execute_reply":"2025-05-11T21:26:34.892065Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# simulate dataloading during training\ndef test_dataloading(get_item, desc):\n    class TestDataset(Dataset):\n        def __init__(self):\n            pass\n        def __getitem__(self, i):\n            return get_item(i)\n        def __len__(self):\n            return len(FILENAMES)\n    dataloader = DataLoader(\n        TestDataset(),\n        batch_size=BATCH_SIZE,\n        shuffle=True,\n        num_workers=NUM_WORKERS,\n    )\n    for _epoch in tqdm(range(NUM_EPOCHS), desc=desc):\n        for _batch in dataloader:\n            pass","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:26:34.896546Z","iopub.execute_input":"2025-05-11T21:26:34.896939Z","iopub.status.idle":"2025-05-11T21:26:34.904681Z","shell.execute_reply.started":"2025-05-11T21:26:34.896911Z","shell.execute_reply":"2025-05-11T21:26:34.903604Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataloading tests","metadata":{}},{"cell_type":"code","source":"# fetch random 5 sec audio crop & pad if too short to enable batching\ndef get_audio_crop(index, frame_offset=None):\n    frame_offset = (\n        random.randint(0, max(0, NUM_FRAMES[index] - AUDIO_CROP_SIZE))\n        if frame_offset is None else frame_offset\n    )\n    audio = torchaudio.load(\n        FILENAMES[index],\n        frame_offset=frame_offset,\n        num_frames=AUDIO_CROP_SIZE,\n    )[0][0]\n    padding = AUDIO_CROP_SIZE - len(audio)\n    if padding > 0:\n        audio = F.pad(audio, (0, padding))\n    return audio\n\ntest_dataloading(get_audio_crop, desc=\"Fetch audio from disk\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:26:34.905588Z","iopub.execute_input":"2025-05-11T21:26:34.905881Z","iopub.status.idle":"2025-05-11T21:28:01.910053Z","shell.execute_reply.started":"2025-05-11T21:26:34.905846Z","shell.execute_reply":"2025-05-11T21:28:01.908754Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# compute 256x253 spectogram\nget_spec = torch.nn.Sequential(\n    torchaudio.transforms.MelSpectrogram(\n        sample_rate=32000,\n        n_mels=256,\n        n_fft=2048,\n        hop_length=625,\n        center=False,\n        power=2,\n    ),\n    torchaudio.transforms.AmplitudeToDB(\n        stype=\"power\",\n        top_db=80.0,\n    )\n)\n\ntest_dataloading(\n    lambda index: get_spec(get_audio_crop(index)),\n    desc=\"Fetch audio from disk + compute spectrogram\",\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:28:01.911777Z","iopub.execute_input":"2025-05-11T21:28:01.912083Z","iopub.status.idle":"2025-05-11T21:29:41.178684Z","shell.execute_reply.started":"2025-05-11T21:28:01.912049Z","shell.execute_reply":"2025-05-11T21:29:41.177463Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# fetch audio, pad if too short, compute spectrogram, save spectrogram to disk\nTEMP_DIR = tempfile.TemporaryDirectory().name\ndef save_spec(f):\n    audio = torchaudio.load(TRAIN_DIR + f)[0][0]\n    padding = AUDIO_CROP_SIZE - len(audio)\n    if padding > 0:\n        audio = F.pad(audio, (0, padding))\n    spec = get_spec(audio)\n    output_path = TEMP_DIR + '/' + f + '.npy'\n    os.makedirs(os.path.dirname(output_path), exist_ok=True)\n    np.save(output_path, spec.numpy().T)\n    return output_path, spec.shape[-1]\n\nPRECOMPUTED_SPECS = Parallel(n_jobs=-1)(\n    delayed(save_spec)(f) \n    for f in tqdm(list(df.filename), desc=\"Precompute spectrograms to disk\")\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:29:41.180454Z","iopub.execute_input":"2025-05-11T21:29:41.181532Z","iopub.status.idle":"2025-05-11T21:30:41.800578Z","shell.execute_reply.started":"2025-05-11T21:29:41.181482Z","shell.execute_reply":"2025-05-11T21:30:41.799299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SPEC_CROP_SIZE = get_spec(torch.randn(AUDIO_CROP_SIZE)).shape[-1] # 253\n\n# Load random 5 sec spectrogram crop\ndef get_precomputed_spec(i, frame_offset=None):\n    path, num_frames = PRECOMPUTED_SPECS[i]\n    frame_offset = (\n        random.randint(0, max(0, num_frames - SPEC_CROP_SIZE)) \n        if frame_offset is None else frame_offset\n    )\n    spec = np.load(path, mmap_mode='r')\n    spec = torch.tensor(spec[frame_offset:frame_offset+SPEC_CROP_SIZE].T)\n    return spec\n\ntest_dataloading(get_precomputed_spec, desc=\"Fetch precomputed spectrograms from disk\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:30:41.801833Z","iopub.execute_input":"2025-05-11T21:30:41.802752Z","iopub.status.idle":"2025-05-11T21:30:44.874055Z","shell.execute_reply.started":"2025-05-11T21:30:41.802719Z","shell.execute_reply":"2025-05-11T21:30:44.872460Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Quantizer:\n    def __init__(self, num_bits):\n        self.range = 2**num_bits\n        self.max = 2**(num_bits - 1) - 1\n        self.min = -2**(num_bits - 1)\n        if num_bits <= 8:\n            self.dtype = torch.int8\n        elif num_bits <= 16:\n            self.dtype = torch.int16\n        elif num_bits <= 32:\n            self.dtype = torch.int32\n\n    def quantize(self, tensor):\n        min_val = tensor.min()\n        max_val = tensor.max()\n        if min_val == max_val: # Edge case: all values are the same\n            return torch.full_like(tensor, 0, dtype=self.dtype), min_val, max_val\n        scale = self.range / (max_val - min_val)\n        quantized_tensor = torch.round((tensor - min_val) * scale + self.min).clamp(self.min, self.max).to(self.dtype)\n        return quantized_tensor, min_val, max_val\n\n    def dequantize(self, quantized_tensor, min_val, max_val):\n        if min_val == max_val:\n            return torch.full_like(quantized_tensor, min_val, dtype=torch.float32)\n        scale = (max_val - min_val) / self.range\n        return (quantized_tensor.to(torch.float32) - self.min) * scale + min_val\n\n\nq = Quantizer(8)\n\n\ndef load_quantized_spec(f):\n    audio = torchaudio.load(TRAIN_DIR + f)[0][0]\n    padding = AUDIO_CROP_SIZE - len(audio)\n    if padding > 0:\n        audio = F.pad(audio, (0, padding))\n    spec = get_spec(audio)\n    spec, *params = q.quantize(spec)\n    return spec, params\n\n\nPRECOMPUTED_QUANTIZED_SPECS = Parallel(n_jobs=-1)(\n    delayed(load_quantized_spec)(f) \n    for f in tqdm(list(df.filename), desc=\"Load quantized specs into RAM\")\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:30:44.875689Z","iopub.execute_input":"2025-05-11T21:30:44.876107Z","iopub.status.idle":"2025-05-11T21:32:28.979838Z","shell.execute_reply.started":"2025-05-11T21:30:44.876056Z","shell.execute_reply":"2025-05-11T21:32:28.978515Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_dequantized_spec(i, frame_offset=None):\n    spec, params = PRECOMPUTED_QUANTIZED_SPECS[i]\n    num_frames = spec.shape[-1]\n    frame_offset = (\n        random.randint(0, max(0, num_frames - SPEC_CROP_SIZE)) \n        if frame_offset is None else frame_offset\n    )\n    spec = spec[:, frame_offset:frame_offset+SPEC_CROP_SIZE]\n    return q.dequantize(spec, *params)\n\ntest_dataloading(get_dequantized_spec, desc=\"Dequantize specs from RAM\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:32:28.983720Z","iopub.execute_input":"2025-05-11T21:32:28.984088Z","iopub.status.idle":"2025-05-11T21:32:31.762120Z","shell.execute_reply.started":"2025-05-11T21:32:28.984063Z","shell.execute_reply":"2025-05-11T21:32:31.760752Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Verify spectrograms match","metadata":{}},{"cell_type":"code","source":"specs = [\n    get_spec(get_audio_crop(0, 0)),\n    get_precomputed_spec(0, 0),\n    get_dequantized_spec(0, 0),\n]\ntorch.allclose(specs[0], specs[1]), torch.allclose(specs[1], specs[2]), (specs[0] - specs[1]).abs().mean(), (specs[1] - specs[2]).abs().mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:32:31.763583Z","iopub.execute_input":"2025-05-11T21:32:31.763933Z","iopub.status.idle":"2025-05-11T21:32:31.966360Z","shell.execute_reply.started":"2025-05-11T21:32:31.763890Z","shell.execute_reply":"2025-05-11T21:32:31.965495Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.imshow(specs[0])\nplt.colorbar()\nplt.show()\n\nplt.imshow(specs[1])\nplt.colorbar()\nplt.show()\n\nplt.imshow(specs[2])\nplt.colorbar()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T21:32:31.967564Z","iopub.execute_input":"2025-05-11T21:32:31.967925Z","iopub.status.idle":"2025-05-11T21:32:33.164768Z","shell.execute_reply.started":"2025-05-11T21:32:31.967896Z","shell.execute_reply":"2025-05-11T21:32:33.163539Z"}},"outputs":[],"execution_count":null}]}