{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":70203,"databundleVersionId":8068726}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport os\nimport torch\nimport torchaudio\nimport torchaudio.transforms as T\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\n\nclass BirdDataset(Dataset):\n    def __init__(self, df, bird2id, duration=5, sample_rate=32000):\n        self.df = df\n        self.bird2id = bird2id\n        self.duration = duration\n        self.sr = sample_rate\n        self.num_samples = duration * sample_rate\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        path = row['path']\n        \n        waveform, _ = torchaudio.load(path)\n        \n        if waveform.size(1) > self.num_samples:\n            start = torch.randint(0, waveform.size(1) - self.num_samples, (1,)).item()\n            waveform = waveform[:, start : start + self.num_samples]\n        elif waveform.size(1) < self.num_samples:\n            pad = self.num_samples - waveform.size(1)\n            waveform = torch.nn.functional.pad(waveform, (0, pad))\n        \n        label = self.bird2id[row['primary_label']]\n        return waveform, torch.tensor(label, dtype=torch.long)\n\nbase_path = \"/kaggle/input/birdclef-2024\"\ndf = pd.read_csv(f\"{base_path}/train_metadata.csv\")\ndf['path'] = df['filename'].apply(lambda x: os.path.join(base_path, \"train_audio\", x))\n\ntrain_data, val_data = train_test_split(\n    df[['path', 'primary_label']], \n    test_size=0.2, \n    random_state=42, \n    stratify=df['primary_label'] \n)\n\nbirds = sorted(df['primary_label'].unique())\nbird2id = {bird: i for i, bird in enumerate(birds)}\nnum_classes = len(birds)\n\ndataset = BirdDataset(train_data, bird2id)\ntrain = DataLoader(\n    dataset, \n    batch_size=64, \n    shuffle=True, \n    num_workers=4,\n    pin_memory=True,\n    prefetch_factor=2\n)\ndataset = BirdDataset(val_data, bird2id)\nval = DataLoader(\n    dataset, \n    batch_size=128, \n    shuffle=True, \n    num_workers=4,\n    pin_memory=True,\n    prefetch_factor=2\n)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\nimport torch.nn.functional as F\n\nclass CNN(nn.Module):\n    def __init__(self, num_classes=182):\n        super().__init__()\n        self.layer1 = nn.Sequential(\n            nn.Conv2d(1, 32, kernel_size=3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.MaxPool2d(2)\n        )\n        self.layer2 = nn.Sequential(\n            nn.Conv2d(32, 64, kernel_size=3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.MaxPool2d(2)\n        )\n        self.layer3 = nn.Sequential(\n            nn.Conv2d(64, 128, kernel_size=3, padding=1),\n            nn.BatchNorm2d(128),\n            nn.ReLU(),\n            nn.MaxPool2d(2)\n        )\n        \n        self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))\n        self.dropout = nn.Dropout(0.3) \n        self.fc = nn.Linear(128, num_classes)\n\n    def forward(self, x):\n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.avg_pool(x).view(x.size(0), -1)\n        x = self.dropout(x) \n        return self.fc(x)\n\n\nnum_classes = len(birds)\ncnn = CNN(num_classes)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ncnn.to(device)\n\ncnn","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.optim as optim\nfrom tqdm.auto import tqdm\n\ndef prepare_spec(images, mel_layer):\n    # images: [Batch, 1, Time] -> [Batch, Time]\n    if images.dim() == 3:\n        images = images.squeeze(1)\n        \n    with torch.no_grad():\n        spec = mel_layer(images) # [Batch, 128, 313]\n        spec = spec.unsqueeze(1) # [Batch, 1, 128, 313]\n        spec = (spec + 1e-10).log()\n        spec = (spec - spec.mean()) / (spec.std() + 1e-10)\n    return spec\n\ndef train_epoch(model, loader, criterion, optimizer, scheduler, device, mel_layer, desc):\n    model.train()\n    running_loss = 0.0\n    running_corrects = 0\n    \n    pbar = tqdm(loader, desc=desc, leave=False)\n    \n    for images, labels in pbar:\n        images, labels = images.to(device), labels.to(device)\n        \n        spec = prepare_spec(images, mel_layer)\n        \n        optimizer.zero_grad()\n        outputs = model(spec)\n        \n        target = torch.zeros(labels.size(0), num_classes).to(device)\n        target.scatter_(1, labels.view(-1, 1), 1.0)\n        \n        loss = criterion(outputs, target)\n        loss.backward()\n        \n        optimizer.step()\n        scheduler.step()\n        \n        running_loss += loss.item()\n        preds = torch.argmax(outputs, dim=1)\n        running_corrects += torch.sum(preds == labels.data)\n        \n        pbar.set_postfix({\n            'loss': f\"{loss.item():.4f}\",\n            'acc': f\"{torch.sum(preds == labels.data).item() / len(labels):.4f}\"\n        })\n        \n    epoch_loss = running_loss / len(loader)\n    epoch_acc = running_corrects.double() / len(loader.dataset)\n    return epoch_loss, epoch_acc\n\n\ndef validate_epoch(model, loader, criterion, device, mel_layer):\n    model.eval()\n    running_loss = 0.0\n    running_corrects = 0\n    \n    with torch.no_grad():\n        for images, labels in loader:\n            images, labels = images.to(device), labels.to(device)\n            spec = prepare_spec(images, mel_layer)\n            \n            outputs = model(spec)\n            target = torch.zeros(labels.size(0), 182).to(device).scatter_(1, labels.view(-1, 1), 1.0)\n            \n            loss = criterion(outputs, target)\n            running_loss += loss.item()\n            preds = torch.argmax(outputs, dim=1)\n            running_corrects += torch.sum(preds == labels.data)\n            \n    return running_loss / len(loader), running_corrects.double() / len(loader.dataset)\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nEPOCHS = 50\n\noptimizer = optim.Adam(cnn.parameters(), lr=1e-3, weight_decay=1e-4)\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=len(train) * EPOCHS)\n\npos_weight = torch.tensor([40.0]).to(DEVICE) \ncriterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n\nmel = T.MelSpectrogram(\n    sample_rate=32000,\n    n_fft=2048,\n    hop_length=512,\n    n_mels=128,\n    f_min=500,\n    f_max=16000\n).to(DEVICE)\n\nbest_acc = 0.0\nfor epoch in range(EPOCHS):\n    train_loss, train_acc = train_epoch(\n        cnn, train, criterion, optimizer, scheduler, device, mel, f\"Epoch {epoch+1:>{len(str(EPOCHS))}}\"\n    )\n    val_loss, val_acc = validate_epoch(\n        cnn, val, criterion, device, mel\n    )\n    \n    print(f\"Epoch {epoch+1:>{len(str(EPOCHS))}}: Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.4f} \")\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(cnn.state_dict(), \"best_model.pth\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}