{"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":"gpu","dataSources":[{"sourceId":8900,"databundleVersionId":862232,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport sys\nimport glob\nimport math\nimport time\nimport random\nimport librosa\nimport numpy as np\nimport pandas as pd\nimport soundfile as sf\nimport torch\nimport torch.nn as nn\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport librosa.display\n\nfrom pathlib import Path\nfrom tqdm.notebook import tqdm\nfrom dataclasses import dataclass\nfrom sklearn.model_selection import train_test_split\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.cuda.amp import autocast, GradScaler\nfrom torch.optim import Adam\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\n\nplt.style.use('ggplot')\n\ndef seed_everything(seed=42):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n\nseed_everything(42)\nprint(\"Environment Ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T21:38:57.758696Z","iopub.execute_input":"2026-01-20T21:38:57.758889Z","iopub.status.idle":"2026-01-20T21:39:03.442751Z","shell.execute_reply.started":"2026-01-20T21:38:57.758869Z","shell.execute_reply":"2026-01-20T21:39:03.441958Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@dataclass\nclass Config:\n    sr: int = 32000\n    duration: float = 4.0\n    samples: int = int(sr * duration)\n    n_mels: int = 128\n    n_fft: int = 2048\n    hop_length: int = 512\n    fmin: int = 20\n    fmax: int = sr // 2\n    power: float = 2.0\n    \n    epochs: int = 8\n    batch_size: int = 32\n    lr: float = 1e-3\n    num_workers: int = 2\n    \n    root_dir: Path = Path(\"/kaggle/input/freesound-audio-tagging\")\n    train_csv: Path = root_dir / \"train.csv\"\n    test_csv: Path = root_dir / \"test_post_competition.csv\"\n    train_audio_dir: Path = root_dir / \"audio_train\"\n    test_audio_dir: Path = root_dir / \"audio_test\"\n    \n    device: torch.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ncfg = Config()\nprint(f\"Configuration loaded. Device: {cfg.device}\")\nprint(f\"Sample Length: {cfg.samples} points\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T21:39:03.444177Z","iopub.execute_input":"2026-01-20T21:39:03.444530Z","iopub.status.idle":"2026-01-20T21:39:03.503858Z","shell.execute_reply.started":"2026-01-20T21:39:03.444505Z","shell.execute_reply":"2026-01-20T21:39:03.502966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(cfg.train_csv)\nunique_labels = sorted(train_df['label'].unique().tolist())\nlabel_to_idx = {l: i for i, l in enumerate(unique_labels)}\nidx_to_label = {i: l for l, i in label_to_idx.items()}\nNUM_CLASSES = len(unique_labels)\n\nprint(f\"Total Classes: {NUM_CLASSES}\")\n\nplt.figure(figsize=(15, 6))\ntop_labels = train_df['label'].value_counts().head(20)\nsns.barplot(x=top_labels.index, y=top_labels.values, palette='viridis')\nplt.xticks(rotation=45, ha='right')\nplt.title(\"Top 20 Classes Distribution\")\nplt.show()\n\ndef show_sample(index, df, cfg):\n    row = df.iloc[index]\n    fname = row['fname']\n    path = cfg.train_audio_dir / fname\n    \n    y, orig_sr = sf.read(path, dtype='float32')\n    if orig_sr != cfg.sr:\n        y = librosa.resample(y, orig_sr=orig_sr, target_sr=cfg.sr)\n    \n    melspec = librosa.feature.melspectrogram(\n        y=y, sr=cfg.sr, n_fft=cfg.n_fft, hop_length=cfg.hop_length,\n        n_mels=cfg.n_mels, fmin=cfg.fmin, fmax=cfg.fmax, power=cfg.power\n    )\n    melspec = librosa.power_to_db(melspec, ref=np.max)\n    \n    plt.figure(figsize=(10, 4))\n    librosa.display.specshow(melspec, sr=cfg.sr, x_axis='time', y_axis='mel', \n                             fmin=cfg.fmin, fmax=cfg.fmax)\n    plt.colorbar(format='%+2.0f dB')\n    plt.title(f\"Label: {row['label']} | File: {fname}\")\n    plt.tight_layout()\n    plt.show()\n\nshow_sample(0, train_df, cfg)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T21:39:03.504799Z","iopub.execute_input":"2026-01-20T21:39:03.505019Z","iopub.status.idle":"2026-01-20T21:39:16.021655Z","shell.execute_reply.started":"2026-01-20T21:39:03.504999Z","shell.execute_reply":"2026-01-20T21:39:16.020845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_audio(path, cfg):\n    try:\n        data, orig_sr = sf.read(path, dtype='float32')\n    except Exception:\n        data, orig_sr = librosa.load(path, sr=None)\n        \n    if data.ndim > 1:\n        data = np.mean(data, axis=1)\n    if orig_sr != cfg.sr:\n        data = librosa.resample(y=data, orig_sr=orig_sr, target_sr=cfg.sr)\n        \n    # Padding / Cropping\n    if len(data) < cfg.samples:\n        pad = cfg.samples - len(data)\n        data = np.pad(data, (0, pad), mode='constant')\n    else:\n        data = data[:cfg.samples]\n    return data\n\ndef wav_to_log_mel(wav, cfg):\n    mel = librosa.feature.melspectrogram(\n        y=wav, sr=cfg.sr, n_fft=cfg.n_fft, hop_length=cfg.hop_length,\n        n_mels=cfg.n_mels, fmin=cfg.fmin, fmax=cfg.fmax, power=cfg.power\n    )\n    log_mel = librosa.power_to_db(mel, ref=np.max)\n    return log_mel.astype(np.float32)\n\ndef label_to_multihot(label_str, label_map, num_classes):\n    arr = np.zeros(num_classes, dtype=np.float32)\n    if isinstance(label_str, str) and label_str.strip() != '':\n        if label_str in label_map:\n            arr[label_map[label_str]] = 1.0\n    return arr\n\nclass FreesoundDataset(Dataset):\n    def __init__(self, df, audio_dir, cfg, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.audio_dir = Path(audio_dir)\n        self.cfg = cfg\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        fname = row['fname']\n        path = self.audio_dir / fname\n        \n        if not path.exists():\n            found = list(self.audio_dir.rglob(fname))\n            if found:\n                path = found[0]\n            else:\n                raise FileNotFoundError(f\"{path} not found\")\n        \n        wav = load_audio(str(path), self.cfg)\n        feat = wav_to_log_mel(wav, self.cfg)\n        \n        feat = (feat - feat.mean()) / (feat.std() + 1e-9)\n        \n        x = torch.from_numpy(feat).unsqueeze(0)\n        \n        if self.is_test:\n            return x.float(), fname\n        else:\n            y = label_to_multihot(row['label'], label_to_idx, NUM_CLASSES)\n            return x.float(), torch.from_numpy(y)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T21:39:16.022619Z","iopub.execute_input":"2026-01-20T21:39:16.023101Z","iopub.status.idle":"2026-01-20T21:39:16.033134Z","shell.execute_reply.started":"2026-01-20T21:39:16.023077Z","shell.execute_reply":"2026-01-20T21:39:16.032543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ConvBlock(nn.Module):\n    def __init__(self, in_ch, out_ch, pool=True):\n        super().__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_ch, out_ch, kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n        )\n        self.pool = nn.MaxPool2d(2) if pool else nn.Identity()\n\n    def forward(self, x):\n        x = self.conv(x)\n        x = self.pool(x)\n        return x\n\nclass AudioCNN(nn.Module):\n    def __init__(self, n_classes=NUM_CLASSES, in_ch=1):\n        super().__init__()\n        self.enc = nn.Sequential(\n            ConvBlock(in_ch, 16),\n            ConvBlock(16, 32),\n            ConvBlock(32, 64),\n            ConvBlock(64, 128),\n            ConvBlock(128, 256, pool=False),\n        )\n        self.global_pool = nn.AdaptiveAvgPool2d((1, 1))\n        self.fc = nn.Linear(256, n_classes)\n\n    def forward(self, x):\n        x = self.enc(x)\n        x = self.global_pool(x)\n        x = x.view(x.size(0), -1)\n        x = self.fc(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T21:39:16.034714Z","iopub.execute_input":"2026-01-20T21:39:16.034967Z","iopub.status.idle":"2026-01-20T21:39:16.051372Z","shell.execute_reply.started":"2026-01-20T21:39:16.034948Z","shell.execute_reply":"2026-01-20T21:39:16.050764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apk(actual, predicted, k=3):\n    if len(predicted) > k:\n        predicted = predicted[:k]\n    score = 0.0\n    hits = 0.0\n    for i, p in enumerate(predicted):\n        if p in actual and p not in predicted[:i]:\n            hits += 1.0\n            score += hits / (i + 1.0)\n    denom = min(len(actual), k)\n    return score / denom if denom > 0 else 0.0\n\ndef mapk(actuals, predicteds, k=3):\n    return np.mean([apk(a, p, k) for a, p in zip(actuals, predicteds)])\n\ndef train_epoch(model, loader, optimizer, scaler, criterion, device):\n    model.train()\n    running_loss = 0.0\n    \n    for x, y in tqdm(loader, desc=\"Training\", leave=False):\n        x, y = x.to(device), y.to(device)\n        \n        optimizer.zero_grad()\n        with autocast():\n            logits = model(x)\n            loss = criterion(logits, y)\n            \n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        \n        running_loss += loss.item() * x.size(0)\n        \n    return running_loss / len(loader.dataset)\n\ndef valid_epoch(model, loader, criterion, device, labels_list):\n    model.eval()\n    running_loss = 0.0\n    actuals = []\n    predicteds = []\n    \n    with torch.no_grad():\n        for x, y in loader:\n            x, y = x.to(device), y.to(device)\n            logits = model(x)\n            loss = criterion(logits, y)\n            running_loss += loss.item() * x.size(0)\n            \n            # Prepare for MAP@3 Calculation\n            probs = torch.sigmoid(logits).cpu().numpy()\n            y_np = y.cpu().numpy()\n            \n            for i in range(len(probs)):\n                # Get top 3 preds\n                p = probs[i]\n                topk = np.argsort(p)[-3:][::-1]\n                predicteds.append([labels_list[t] for t in topk])\n                \n                # Get actual GT\n                gt_idx = np.where(y_np[i] > 0.5)[0].tolist()\n                actuals.append([labels_list[g] for g in gt_idx])\n                \n    map3 = mapk(actuals, predicteds, k=3)\n    return running_loss / len(loader.dataset), map3\n\ntr_df, val_df = train_test_split(train_df, test_size=0.1, random_state=42, shuffle=True)\nprint(f\"Train: {len(tr_df)}, Val: {len(val_df)}\")\n\ntrain_ds = FreesoundDataset(tr_df, cfg.train_audio_dir, cfg)\nval_ds = FreesoundDataset(val_df, cfg.train_audio_dir, cfg)\n\ntrain_loader = DataLoader(train_ds, batch_size=cfg.batch_size, shuffle=True, \n                          num_workers=cfg.num_workers, pin_memory=True)\nval_loader = DataLoader(val_ds, batch_size=cfg.batch_size, shuffle=False, \n                        num_workers=cfg.num_workers, pin_memory=True)\n\nmodel = AudioCNN(n_classes=NUM_CLASSES).to(cfg.device)\noptimizer = Adam(model.parameters(), lr=cfg.lr, weight_decay=1e-5)\nscheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=2)\nscaler = GradScaler()\ncriterion = nn.BCEWithLogitsLoss()\n\nbest_map3 = 0.0\nhistory = {'train_loss': [], 'val_loss': [], 'val_map3': []}\n\nfor epoch in range(cfg.epochs):\n    print(f\"Epoch {epoch+1}/{cfg.epochs}\")\n    t_loss = train_epoch(model, train_loader, optimizer, scaler, criterion, cfg.device)\n    v_loss, v_map3 = valid_epoch(model, val_loader, criterion, cfg.device, unique_labels)\n    \n    scheduler.step(v_loss)\n    \n    history['train_loss'].append(t_loss)\n    history['val_loss'].append(v_loss)\n    history['val_map3'].append(v_map3)\n    \n    print(f\"  Loss: {t_loss:.4f} | Val Loss: {v_loss:.4f} | MAP@3: {v_map3:.4f}\")\n    \n    if v_map3 > best_map3:\n        best_map3 = v_map3\n        torch.save(model.state_dict(), \"best_model.pth\")\n        print(\"  -> Saved Best Model\")\n\nplt.figure(figsize=(12, 4))\nplt.subplot(1, 2, 1)\nplt.plot(history['train_loss'], label='Train Loss')\nplt.plot(history['val_loss'], label='Val Loss')\nplt.title('Loss')\nplt.legend()\nplt.subplot(1, 2, 2)\nplt.plot(history['val_map3'], label='MAP@3', color='green')\nplt.title('Validation MAP@3')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T21:39:16.052280Z","iopub.execute_input":"2026-01-20T21:39:16.052602Z","iopub.status.idle":"2026-01-20T22:18:47.724284Z","shell.execute_reply.started":"2026-01-20T21:39:16.052572Z","shell.execute_reply":"2026-01-20T22:18:47.723624Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(cfg.test_csv)\ntest_ds = FreesoundDataset(test_df, cfg.test_audio_dir, cfg, is_test=True)\ntest_loader = DataLoader(test_ds, batch_size=cfg.batch_size, shuffle=False, \n                         num_workers=cfg.num_workers, pin_memory=True)\n\nif os.path.exists(\"best_model.pth\"):\n    model.load_state_dict(torch.load(\"best_model.pth\", map_location=cfg.device))\nmodel.eval()\n\nrows = []\nwith torch.no_grad():\n    for x, fnames in tqdm(test_loader, desc=\"Inference\"):\n        x = x.to(cfg.device)\n        logits = model(x)\n        probs = torch.sigmoid(logits).cpu().numpy()\n        \n        for i in range(len(probs)):\n            p = probs[i]\n            # Get Top 3 Labels\n            topk = np.argsort(p)[-3:][::-1]\n            labels_pred = [unique_labels[idx] for idx in topk]\n            rows.append((fnames[i], \" \".join(labels_pred)))\n\nsub = pd.DataFrame(rows, columns=[\"fname\", \"label\"])\nsub.to_csv(\"submission.csv\", index=False)\nprint(f\"Submission saved: {len(sub)} rows.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T22:18:47.725974Z","iopub.execute_input":"2026-01-20T22:18:47.726443Z","iopub.status.idle":"2026-01-20T22:24:16.010474Z","shell.execute_reply.started":"2026-01-20T22:18:47.726411Z","shell.execute_reply":"2026-01-20T22:24:16.009804Z"}},"outputs":[],"execution_count":null}]}