{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"74df6fde","cell_type":"code","source":"import os, sys, time, random, glob, zipfile, warnings\nfrom pathlib import Path\nfrom collections import Counter\nimport numpy as np, pandas as pd\nimport torch, torch.nn as nn, torch.nn.functional as F\nimport librosa, soundfile as sf, sklearn, matplotlib\nimport matplotlib.pyplot as plt\nfrom joblib import Parallel, delayed\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import label_ranking_average_precision_score\nwarnings.filterwarnings('ignore')\n\nprint('python', sys.version.split()[0]); print('torch', torch.__version__)\nprint('librosa', librosa.__version__); print('soundfile', sf.__version__); print('numpy', np.__version__)\nprint('pandas', pd.__version__); print('sklearn', sklearn.__version__); print('matplotlib', matplotlib.__version__)\n\nSEED = 42\ndef seed_everything(s=SEED):\n    random.seed(s); os.environ['PYTHONHASHSEED'] = str(s)\n    np.random.seed(s); torch.manual_seed(s); torch.cuda.manual_seed_all(s)\n    torch.backends.cudnn.deterministic = True; torch.backends.cudnn.benchmark = False\nseed_everything()\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint('device:', DEVICE, '|', torch.cuda.get_device_name(0) if DEVICE == 'cuda' else 'CPU only')\nNOTEBOOK_START = time.time()\n\n# ---- Competition metric: lwlrap (label-weighted label-ranking average precision) ----\ndef lwlrap(truth, scores):\n    w = (truth > 0).sum(1); nz = np.flatnonzero(w)\n    return label_ranking_average_precision_score(truth[nz] > 0, scores[nz], sample_weight=w[nz])\n\ndef per_class_lwlrap(truth, scores):\n    n, k = scores.shape; prec_sum = np.zeros(k); cnt = np.zeros(k)\n    for s in range(n):\n        pos = np.flatnonzero(truth[s])\n        if len(pos) == 0: continue\n        order = np.argsort(-scores[s]); rank = np.empty(k, int); rank[order] = np.arange(k)\n        pr = rank[pos]\n        for c, r in zip(pos, pr):\n            prec_sum[c] += (pr <= r).sum() / (r + 1); cnt[c] += 1\n    return prec_sum / np.maximum(cnt, 1), cnt\n\n# quick self-test: perfect scores -> lwlrap = 1.0\n_t = np.eye(5)[[0,1,2]]; assert abs(lwlrap(_t, _t*0.9 + 0.01) - 1.0) < 1e-9\nprint('lwlrap self-test OK')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T21:39:12.220096Z","iopub.execute_input":"2026-10-17T21:39:12.220633Z","iopub.status.idle":"2026-10-17T21:39:19.151792Z","shell.execute_reply.started":"2026-10-17T21:39:12.2206Z","shell.execute_reply":"2026-10-17T21:39:19.151016Z"}},"outputs":[],"execution_count":null},{"id":"dfecbb06","cell_type":"code","source":"hits = glob.glob('/kaggle/input/**/train_curated.csv', recursive=True)\nprint('Found csv:', hits)\nassert len(hits) > 0, 'train_curated.csv nahi mili! Right panel > Add Input se Freesound Audio Tagging 2019 add karein.'\nDATA = Path(hits[0]).parent\nprint('DATA =', DATA)\nfor p in sorted(DATA.iterdir()): print('  ', p.name, '(dir)' if p.is_dir() else f'({p.stat().st_size/1e6:.1f} MB)')\n\ntrain_df = pd.read_csv(DATA/'train_curated.csv')\nsub_df = pd.read_csv(DATA/'sample_submission.csv')\nCLASSES = list(sub_df.columns[1:]); N_CLASSES = len(CLASSES); cls2idx = {c:i for i,c in enumerate(CLASSES)}\n\n# ---- audio / feature config ----\nSR = 22050; N_FFT = 1024; HOP = 441; N_MELS = 128     # hop 441 = 20 ms\nCROP = 200            # frames = 4 sec window\nMAX_FRAMES = 1500     # cap long clips at 30 sec\nprint('\\ntrain_curated rows:', train_df.shape, '| test rows (sample_submission):', sub_df.shape, '| classes:', N_CLASSES)\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T21:39:19.153284Z","iopub.execute_input":"2026-10-17T21:39:19.153653Z","iopub.status.idle":"2026-10-17T21:39:19.265103Z","shell.execute_reply.started":"2026-10-17T21:39:19.153629Z","shell.execute_reply":"2026-10-17T21:39:19.264396Z"}},"outputs":[],"execution_count":null},{"id":"bb99c911","cell_type":"code","source":"WORK = Path('/kaggle/working/audio'); WORK.mkdir(parents=True, exist_ok=True)\n\ndef prepare_audio(split):\n    # Return folder containing .wav files for `split` ('train_curated' or 'test').\n    # /kaggle/input read-only hai, isliye zip ho to /kaggle/working me extract karte hain.\n    d = DATA/split\n    if d.is_dir() and any(d.glob('*.wav')): print(f'{split}: wav files already unzipped in input'); return d\n    out = WORK/split\n    wavs = list(out.rglob('*.wav')) if out.exists() else []\n    if not wavs:\n        zs = [DATA/f'{split}.zip'] if (DATA/f'{split}.zip').exists() else [Path(z) for z in glob.glob(f'/kaggle/input/**/{split}.zip', recursive=True)]\n        if not zs: print(f'WARNING: {split} audio/zip nahi mila'); return None\n        print(f'{split}: extracting {zs[0]} ...'); t0 = time.time()\n        with zipfile.ZipFile(zs[0]) as z: z.extractall(out)\n        wavs = list(out.rglob('*.wav')); print(f'  extracted {len(wavs)} wavs in {time.time()-t0:.0f}s')\n    return wavs[0].parent\n\nTRAIN_DIR = prepare_audio('train_curated'); TEST_DIR = prepare_audio('test')\nassert TRAIN_DIR is not None, 'train_curated audio nahi mili'\nprint('TRAIN_DIR =', TRAIN_DIR, '| files:', len(list(TRAIN_DIR.glob('*.wav'))))\nprint('TEST_DIR  =', TEST_DIR, '| files:', len(list(TEST_DIR.glob('*.wav'))) if TEST_DIR else 'N/A (final commit run me milega)')\n\n# ------------------------------ EDA ------------------------------\nprint('\\nMissing values:\\n', train_df.isna().sum())\nprint('Duplicate filenames:', train_df.fname.duplicated().sum(), '| duplicate rows:', train_df.duplicated().sum())\ntrain_df['labels_list'] = train_df.labels.str.split(',')\ntrain_df['n_labels'] = train_df.labels_list.str.len()\nprint('\\nLabels per clip:\\n', train_df.n_labels.value_counts().sort_index())\ncnt = train_df.explode('labels_list').labels_list.value_counts()\nprint('\\nClasses: %d | min %d | max %d | mean %.1f samples per class' % (len(cnt), cnt.min(), cnt.max(), cnt.mean()))\ntrain_df['dur'] = [sf.info(TRAIN_DIR/f).duration for f in train_df.fname]\nprint('\\nDuration (s):\\n', train_df.dur.describe())\n\nfig, ax = plt.subplots(1, 3, figsize=(21, 4.5))\ncnt.plot.bar(ax=ax[0], title='Class distribution (80 classes)'); ax[0].tick_params(labelsize=5)\ntrain_df.dur.hist(bins=40, ax=ax[1]); ax[1].set_title('Clip duration (s)'); ax[1].set_xlabel('seconds')\ntrain_df.n_labels.value_counts().sort_index().plot.bar(ax=ax[2], title='Labels per clip'); plt.tight_layout(); plt.show()\n\n# ---- sample waveform + log-mel spectrogram ----\nfig, ax = plt.subplots(2, 3, figsize=(18, 6))\nfor i, r in enumerate(train_df.sample(3, random_state=SEED).itertuples()):\n    y, _ = librosa.load(TRAIN_DIR/r.fname, sr=SR)\n    ax[0, i].plot(np.arange(len(y))/SR, y); ax[0, i].set_title(f'{r.labels}'); ax[0, i].set_xlabel('sec')\n    m = librosa.power_to_db(librosa.feature.melspectrogram(y=y, sr=SR, n_fft=N_FFT, hop_length=HOP, n_mels=N_MELS))\n    im = ax[1, i].imshow(m, origin='lower', aspect='auto'); ax[1, i].set_title('log-mel spectrogram'); ax[1, i].set_ylabel('mel bin')\nplt.tight_layout(); plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T21:39:19.266234Z","iopub.execute_input":"2026-10-17T21:39:19.266587Z","iopub.status.idle":"2026-10-17T21:41:54.535839Z","shell.execute_reply.started":"2026-10-17T21:39:19.266565Z","shell.execute_reply":"2026-10-17T21:41:54.534889Z"}},"outputs":[],"execution_count":null},{"id":"f4cfa93c","cell_type":"code","source":"# ---- multi-hot targets (80 classes) ----\nY_all = np.zeros((len(train_df), N_CLASSES), dtype=np.float32)\nfor i, labs in enumerate(train_df.labels_list):\n    for l in labs: Y_all[i, cls2idx[l]] = 1\nprint('Y_all:', Y_all.shape, '| avg labels/clip: %.2f' % Y_all.sum(1).mean())\n\n# ---- log-mel features (precomputed once, stored float16) ----\ndef logmel(path):\n    y, _ = librosa.load(path, sr=SR)\n    m = librosa.feature.melspectrogram(y=y, sr=SR, n_fft=N_FFT, hop_length=HOP, n_mels=N_MELS)\n    return librosa.power_to_db(m, ref=1.0, top_db=None)[:, :MAX_FRAMES].astype(np.float16)\n\nt0 = time.time()\nX_all = Parallel(n_jobs=-1)(delayed(logmel)(TRAIN_DIR/f) for f in train_df.fname)\nprint('log-mel features: %d clips in %.0fs' % (len(X_all), time.time() - t0))\n\n# ---- hold-out split 80/20 (stratified on first label, seed fixed) ----\nidx = np.arange(len(X_all)); first_label = train_df.labels_list.str[0].values\ntry:\n    tr_idx, va_idx = train_test_split(idx, test_size=0.2, random_state=SEED, stratify=first_label); STRATIFIED = True\nexcept ValueError:       # agar koi first-label class me <2 samples hon\n    tr_idx, va_idx = train_test_split(idx, test_size=0.2, random_state=SEED); STRATIFIED = False\nprint('stratified split:', STRATIFIED)\nprint('train:', len(tr_idx), '| val:', len(va_idx))\n\n# ---- normalisation stats from TRAIN split only ----\nvals = np.concatenate([X_all[j][:, :CROP].astype(np.float32).ravel() for j in tr_idx[:800]])\nMEAN, STD = float(vals.mean()), float(vals.std()); print('MEAN %.2f  STD %.2f' % (MEAN, STD))\n\ndef crop_pad(m, train):\n    T = m.shape[1]\n    if T < CROP: m = np.pad(m, ((0,0), (0, CROP-T)), mode='constant', constant_values=float(m.min())); T = CROP\n    s = np.random.randint(0, T-CROP+1) if train else max(0, (T-CROP)//2)   # random crop (train) / centre crop (val)\n    return m[:, s:s+CROP]\n\nclass AudioDS(torch.utils.data.Dataset):\n    def __init__(self, ids, train): self.ids, self.train = ids, train\n    def __len__(self): return len(self.ids)\n    def __getitem__(self, i):\n        j = self.ids[i]\n        x = (crop_pad(X_all[j], self.train).astype(np.float32) - MEAN) / STD\n        return torch.from_numpy(x)[None], torch.from_numpy(Y_all[j])\n\ndef seed_worker(w):                      # har epoch/worker ke liye alag (lekin reproducible) seed -> random crops epoch-to-epoch badalte hain\n    s = torch.initial_seed() % 2**32; np.random.seed(s); random.seed(s)\ng = torch.Generator(); g.manual_seed(SEED); BS = 64\ntrain_dl = torch.utils.data.DataLoader(AudioDS(tr_idx, True), batch_size=BS, shuffle=True, num_workers=2, worker_init_fn=seed_worker, generator=g, drop_last=True)\nval_dl = torch.utils.data.DataLoader(AudioDS(va_idx, False), batch_size=128, shuffle=False, num_workers=2)\nxb, yb = next(iter(train_dl)); print('batch:', xb.shape, yb.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T21:41:54.537297Z","iopub.execute_input":"2026-10-17T21:41:54.537656Z","iopub.status.idle":"2026-10-17T21:42:25.64982Z","shell.execute_reply.started":"2026-10-17T21:41:54.537627Z","shell.execute_reply":"2026-10-17T21:42:25.648722Z"}},"outputs":[],"execution_count":null},{"id":"1b609601","cell_type":"code","source":"class Head(nn.Module):                       # pool freq (mean), then time (max+mean) -> FC(80)\n    def __init__(self, ch, drop):\n        super().__init__(); self.drop = nn.Dropout(drop); self.fc = nn.Linear(ch*2, N_CLASSES)\n    def forward(self, x):\n        x = x.mean(2); x = torch.cat([x.max(2)[0], x.mean(2)], 1)\n        return self.fc(self.drop(x))\n\nclass M0_Baseline(nn.Module):               # 3 plain Conv-ReLU-MaxPool blocks, no BN / dropout\n    def __init__(self):\n        super().__init__(); ch = [1, 32, 64, 128]\n        self.f = nn.Sequential(*[m for i in range(3) for m in (nn.Conv2d(ch[i], ch[i+1], 3, padding=1), nn.ReLU(), nn.MaxPool2d(2))])\n        self.h = Head(128, 0.0)\n    def forward(self, x): return self.h(self.f(x))\n\ndef conv_block(i, o):\n    return nn.Sequential(nn.Conv2d(i,o,3,padding=1,bias=False), nn.BatchNorm2d(o), nn.ReLU(inplace=True),\n                         nn.Conv2d(o,o,3,padding=1,bias=False), nn.BatchNorm2d(o), nn.ReLU(inplace=True))\nclass M1_VGG(nn.Module):                    # VGG-style double-conv + BatchNorm + Dropout 0.3\n    def __init__(self):\n        super().__init__(); ch = [1, 64, 128, 256, 512]\n        self.blocks = nn.ModuleList([conv_block(ch[i], ch[i+1]) for i in range(4)]); self.h = Head(512, 0.3)\n    def forward(self, x):\n        for i, b in enumerate(self.blocks):\n            x = b(x); x = F.avg_pool2d(x, 2) if i < 3 else x\n        return self.h(x)\n\nclass ResBlock(nn.Module):\n    def __init__(self, i, o, stride):\n        super().__init__()\n        self.c1 = nn.Conv2d(i,o,3,stride,1,bias=False); self.b1 = nn.BatchNorm2d(o)\n        self.c2 = nn.Conv2d(o,o,3,1,1,bias=False); self.b2 = nn.BatchNorm2d(o)\n        self.sc = nn.Sequential() if (i == o and stride == 1) else nn.Sequential(nn.Conv2d(i,o,1,stride,bias=False), nn.BatchNorm2d(o))\n    def forward(self, x):\n        y = F.relu(self.b1(self.c1(x))); y = self.b2(self.c2(y)); return F.relu(y + self.sc(x))\nclass M2_ResNet(nn.Module):                 # small ResNet with skip connections, Dropout 0.4\n    def __init__(self):\n        super().__init__()\n        self.stem = nn.Sequential(nn.Conv2d(1,32,3,1,1,bias=False), nn.BatchNorm2d(32), nn.ReLU(inplace=True))\n        self.layers = nn.Sequential(ResBlock(32,64,2), ResBlock(64,64,1), ResBlock(64,128,2), ResBlock(128,128,1),\n                                    ResBlock(128,256,2), ResBlock(256,256,1), ResBlock(256,512,2))\n        self.h = Head(512, 0.4)\n    def forward(self, x): return self.h(self.layers(self.stem(x)))\n\ndef n_params(m): return sum(p.numel() for p in m.parameters() if p.requires_grad)\nfor M in (M0_Baseline, M1_VGG, M2_ResNet):\n    m = M(); out = m(torch.zeros(2, 1, N_MELS, CROP)); print(f'{M.__name__:12s} trainable params: {n_params(m)/1e6:.2f}M | output {tuple(out.shape)}')\ntry:\n    from torchinfo import summary\n    summary(M1_VGG(), input_size=(1, 1, N_MELS, CROP), verbose=1)\nexcept Exception:\n    print(M1_VGG())     # torchinfo na ho to plain print","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T21:42:25.65293Z","iopub.execute_input":"2026-10-17T21:42:25.654087Z","iopub.status.idle":"2026-10-17T21:42:27.311587Z","shell.execute_reply.started":"2026-10-17T21:42:25.654046Z","shell.execute_reply":"2026-10-17T21:42:27.310616Z"}},"outputs":[],"execution_count":null},{"id":"a6502b84","cell_type":"code","source":"EPOCH_SCALE = 1.0       # dry-run: 0.1 | final run: 1.0\n\ndef spec_augment(x, n_freq=2, f_w=16, n_time=2, t_w=24):\n    B, _, Fq, T = x.shape\n    for b in range(B):\n        for _ in range(n_freq):\n            w = np.random.randint(0, f_w); s = np.random.randint(0, Fq-w); x[b,:,s:s+w,:] = 0\n        for _ in range(n_time):\n            w = np.random.randint(0, t_w); s = np.random.randint(0, T-w); x[b,:,:,s:s+w] = 0\n    return x\n\n@torch.no_grad()\ndef predict_val(model):\n    model.eval(); P, Y = [], []\n    for x, y in val_dl:\n        with torch.autocast(device_type=DEVICE, enabled=(DEVICE == 'cuda')):\n            P.append(torch.sigmoid(model(x.to(DEVICE)).float()).cpu().numpy())\n        Y.append(y.numpy())\n    return np.concatenate(P), np.concatenate(Y)\n\ndef run_experiment(name, model_cls, epochs, lr, wd, aug, mixup, patience=10, desc=''):\n    seed_everything(); epochs = max(2, int(epochs * EPOCH_SCALE))\n    model = model_cls().to(DEVICE)\n    opt = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=wd)\n    sched = torch.optim.lr_scheduler.OneCycleLR(opt, max_lr=lr, epochs=epochs, steps_per_epoch=len(train_dl))\n    scaler = torch.amp.GradScaler('cuda', enabled=(DEVICE == 'cuda')); crit = nn.BCEWithLogitsLoss()\n    hist = dict(tr_loss=[], va_loss=[], va_lwlrap=[]); best, best_ep, bad = -1, 0, 0; t0 = time.time()\n    for ep in range(epochs):\n        model.train(); tl = 0\n        for x, y in train_dl:\n            x, y = x.to(DEVICE), y.to(DEVICE)\n            if aug: x = spec_augment(x)\n            if mixup > 0:\n                lam = float(np.random.beta(mixup, mixup)); perm = torch.randperm(x.size(0), device=DEVICE)\n                x = lam*x + (1-lam)*x[perm]; y = lam*y + (1-lam)*y[perm]\n            opt.zero_grad()\n            with torch.autocast(device_type=DEVICE, enabled=(DEVICE == 'cuda')):\n                out = model(x)\n            loss = crit(out.float(), y)\n            scaler.scale(loss).backward(); scaler.step(opt); scaler.update(); sched.step(); tl += loss.item()\n        P, Y = predict_val(model)\n        vl = float(F.binary_cross_entropy(torch.tensor(P).clamp(1e-6, 1-1e-6), torch.tensor(Y))); sc = lwlrap(Y, P)\n        hist['tr_loss'].append(tl/len(train_dl)); hist['va_loss'].append(vl); hist['va_lwlrap'].append(sc)\n        print(f'[{name}] ep {ep+1:02d}/{epochs} train_loss {tl/len(train_dl):.4f} val_loss {vl:.4f} val_lwlrap {sc:.4f}')\n        if sc > best: best, best_ep, bad = sc, ep+1, 0; torch.save(model.state_dict(), f'/kaggle/working/{name}.pt')\n        else:\n            bad += 1\n            if bad >= patience: print('early stopping'); break\n    tt = (time.time() - t0)/60\n    print(f'>> {name}: best val lwlrap {best:.4f} @ epoch {best_ep} | time {tt:.1f} min\\n')\n    return dict(name=name, cls=model_cls, params=n_params(model_cls()), best=best, best_ep=best_ep, time_min=tt, hist=hist,\n                desc=desc, lr=lr, wd=wd, aug=aug, mixup=mixup, epochs=epochs)\n\nresults = []\nresults.append(run_experiment('M0_baseline', M0_Baseline, 30, 1e-3, 0.0,  False, 0.0, desc='no BN/dropout/aug, OneCycle'))\nresults.append(run_experiment('M1_vgg',      M1_VGG,      40, 2e-3, 1e-2, True,  0.0, desc='BN, dropout0.3, SpecAug, wd1e-2'))\nresults.append(run_experiment('M2_resnet',   M2_ResNet,   50, 2e-3, 1e-2, True,  0.4, desc='ResNet, dropout0.4, SpecAug, Mixup0.4'))\nresults.append(run_experiment('M1b_vgg_tuned', M1_VGG, 40, 1e-3, 1e-3, True, 0.0, desc='hyperparameter tuning: lower lr + lower wd'))\nTOTAL_TRAIN_MIN = sum(r['time_min'] for r in results); print('Total GPU training time: %.1f min (limit 120)' % TOTAL_TRAIN_MIN)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T21:42:27.312767Z","iopub.execute_input":"2026-10-17T21:42:27.313545Z","iopub.status.idle":"2026-10-17T22:13:32.339003Z","shell.execute_reply.started":"2026-10-17T21:42:27.313519Z","shell.execute_reply":"2026-10-17T22:13:32.337871Z"}},"outputs":[],"execution_count":null},{"id":"b014825e","cell_type":"code","source":"fig, ax = plt.subplots(len(results), 2, figsize=(13, 3.8*len(results)))\nfor i, r in enumerate(results):\n    h = r['hist']; e = range(1, len(h['tr_loss'])+1)\n    ax[i,0].plot(e, h['tr_loss'], label='train'); ax[i,0].plot(e, h['va_loss'], label='val'); ax[i,0].set_title(r['name']+' - loss'); ax[i,0].legend(); ax[i,0].set_xlabel('epoch')\n    ax[i,1].plot(e, h['va_lwlrap'], color='green'); ax[i,1].axvline(r['best_ep'], ls='--', c='gray'); ax[i,1].set_title(r['name']+' - val lwlrap'); ax[i,1].set_xlabel('epoch')\nplt.tight_layout(); plt.show()\n\nsummary_df = pd.DataFrame([{\n    'Model': r['name'], 'Architecture': r['cls'].__name__, 'Params (M)': round(r['params']/1e6, 2),\n    'Key hyperparameters': f\"lr={r['lr']}, wd={r['wd']}, aug={r['aug']}, mixup={r['mixup']}, epochs={r['epochs']} ({r['desc']})\",\n    'Best epoch': r['best_ep'], 'Val lwlrap': round(r['best'], 4), 'Train time (min)': round(r['time_min'], 1),\n    'Kaggle Private score': 'fill after submission'} for r in results])\npd.set_option('display.max_colwidth', 90); display(summary_df)\n\nplt.figure(figsize=(6, 3.5)); bars = plt.bar(summary_df.Model, summary_df['Val lwlrap'], color=['#999', '#4c9be8', '#2e7d32'])\nfor b, v in zip(bars, summary_df['Val lwlrap']): plt.text(b.get_x()+b.get_width()/2, v+0.005, f'{v:.3f}', ha='center')\nplt.ylim(0, min(1, summary_df['Val lwlrap'].max()+0.1)); plt.title('Validation lwlrap: baseline vs improved'); plt.show()\nbest = max(results, key=lambda r: r['best']); print('BEST MODEL:', best['name'], '| val lwlrap %.4f' % best['best'])\n\n# ---- Report Section 6: Training Setup table ----\nsetup_df = pd.DataFrame([{\n    'Model': r['name'], 'Loss': 'BCEWithLogitsLoss', 'Optimiser': 'AdamW', 'Max LR (OneCycle)': r['lr'], 'Weight decay': r['wd'],\n    'Batch size': BS, 'Epochs planned/run': f\"{r['epochs']}/{len(r['hist']['tr_loss'])}\",\n    'Dropout': r['cls']().h.drop.p, 'Augmentation': ('SpecAugment' if r['aug'] else 'none') + (f\" + Mixup({r['mixup']})\" if r['mixup'] else ''),\n    'Callbacks': 'OneCycleLR, early stopping (patience 10), best-val checkpoint', 'Train time (min)': round(r['time_min'], 1)} for r in results])\ndisplay(setup_df.T)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T22:13:32.341011Z","iopub.execute_input":"2026-10-17T22:13:32.342044Z","iopub.status.idle":"2026-10-17T22:13:33.749194Z","shell.execute_reply.started":"2026-10-17T22:13:32.342004Z","shell.execute_reply":"2026-10-17T22:13:33.748498Z"}},"outputs":[],"execution_count":null},{"id":"047ac209","cell_type":"code","source":"assert TEST_DIR is not None, 'Test audio nahi mila. Interactive session me test.zip na ho to Save & Run All (commit) par ye cell chalega.'\ntest_files = sub_df.fname.tolist()\nt0 = time.time(); X_test = Parallel(n_jobs=-1)(delayed(logmel)(TEST_DIR/f) for f in test_files)\nprint('test features: %d clips in %.0fs' % (len(X_test), time.time()-t0))\n\n@torch.no_grad()\ndef predict_clip(model, m):\n    m = (m.astype(np.float32) - MEAN) / STD\n    if m.shape[1] < CROP: m = np.pad(m, ((0,0), (0, CROP-m.shape[1])), constant_values=float(m.min()))\n    starts = list(range(0, m.shape[1]-CROP+1, CROP//2)) or [0]\n    w = torch.from_numpy(np.stack([m[:, s:s+CROP] for s in starts]))[:, None].to(DEVICE)\n    with torch.autocast(device_type=DEVICE, enabled=(DEVICE == 'cuda')):\n        return torch.sigmoid(model(w).float()).mean(0).cpu().numpy()\n\ndef make_submission(model, path):\n    model.eval(); preds = np.stack([predict_clip(model, m) for m in X_test])\n    sub = pd.DataFrame(preds, columns=CLASSES); sub.insert(0, 'fname', test_files); sub.to_csv(path, index=False); return sub\n\ndef load_model(r):\n    m = r['cls']().to(DEVICE); m.load_state_dict(torch.load(f\"/kaggle/working/{r['name']}.pt\", map_location=DEVICE)); return m.eval()\n\nfor r in results:\n    make_submission(load_model(r), f\"/kaggle/working/submission_{r['name']}.csv\"); print('saved submission_%s.csv' % r['name'])\n# submission.csv kis model se banani hai? 'best' ya 'M0_baseline' / 'M1_vgg' / 'M1b_vgg_tuned' / 'M2_resnet'\nSUBMIT_MODEL = 'best'\nchosen = max(results, key=lambda r: r['best']) if SUBMIT_MODEL == 'best' else next(r for r in results if r['name'] == SUBMIT_MODEL)\nsub = make_submission(load_model(chosen), '/kaggle/working/submission.csv')\nprint('FINAL submission.csv from', chosen['name'], sub.shape); sub.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T22:13:33.750247Z","iopub.execute_input":"2026-10-17T22:13:33.750477Z","iopub.status.idle":"2026-10-17T22:15:16.318625Z","shell.execute_reply.started":"2026-10-17T22:13:33.750458Z","shell.execute_reply":"2026-10-17T22:15:16.317888Z"}},"outputs":[],"execution_count":null},{"id":"331ab41b","cell_type":"code","source":"rows = []\nfor r in results:\n    P, Y = predict_val(load_model(r)); sc = lwlrap(Y, P)\n    rows.append({'Model': r['name'], 'Logged best val lwlrap': round(r['best'], 4), 'Re-verified val lwlrap': round(sc, 4), 'Match': abs(sc - r['best']) < 1e-3})\ndisplay(pd.DataFrame(rows))\n\nbest = max(results, key=lambda r: r['best']); P, Y = predict_val(load_model(best))\nprint('Best model:', best['name'], '| validation lwlrap = %.4f' % lwlrap(Y, P))\npc, cnts = per_class_lwlrap(Y, P)\ndfc = pd.DataFrame({'class': CLASSES, 'lwlrap': pc, 'val_samples': cnts.astype(int)}).sort_values('lwlrap')\nprint('\\nWORST 10 classes:'); display(dfc.head(10)); print('BEST 10 classes:'); display(dfc.tail(10))\nplt.figure(figsize=(18, 4)); plt.bar(range(N_CLASSES), dfc.lwlrap.values); plt.xticks(range(N_CLASSES), dfc['class'], rotation=90, fontsize=6)\nplt.title('Per-class validation lwlrap (sorted)'); plt.tight_layout(); plt.show()\n\n# multi-label \"confusion\": true label vs wrongly predicted top-1 class\npairs = Counter()\nfor p, y in zip(P, Y):\n    top = p.argmax()\n    if y[top] == 0:\n        for t in np.flatnonzero(y): pairs[(CLASSES[t], CLASSES[top])] += 1\nprint('Most frequent mistakes (true -> predicted top-1):')\nfor (t, p_), c in pairs.most_common(12): print(f'{c:3d}  {t}  ->  {p_}')\nprint('Top-1 hit rate: %.3f' % np.mean([y[p.argmax()] > 0 for p, y in zip(P, Y)]))\n\n# ---- Report Section 7.1: per-class Precision / Recall / F1 / Support (threshold 0.3) ----\nfrom sklearn.metrics import precision_recall_fscore_support\nTH = 0.3\npr, rc, f1, sup = precision_recall_fscore_support(Y, (P >= TH).astype(int), zero_division=0)\nmetrics_df = pd.DataFrame({'Class': CLASSES, 'Precision': pr.round(3), 'Recall': rc.round(3), 'F1': f1.round(3), 'Support': sup.astype(int)})\nprint(f'Threshold = {TH}'); display(metrics_df.sort_values('F1').head(8)); display(metrics_df.sort_values('F1').tail(8))\nprint('MACRO AVERAGE  precision %.3f | recall %.3f | F1 %.3f' % (pr.mean(), rc.mean(), f1.mean()))\n\n# ---- Report Section 7.2: confusion matrix (true label vs top-1 predicted class) ----\ncm = np.zeros((N_CLASSES, N_CLASSES))\nfor p, y in zip(P, Y):\n    top = p.argmax()\n    for t in np.flatnonzero(y): cm[t, top] += 1\nplt.figure(figsize=(11, 9)); plt.imshow(np.log1p(cm), cmap='viridis'); plt.colorbar(label='log(1 + count)')\nplt.xlabel('Predicted (top-1)'); plt.ylabel('True label'); plt.title(f'Confusion matrix - {best[\"name\"]} (80 classes)'); plt.show()\noff = cm.copy(); np.fill_diagonal(off, 0)\nprint('Most confused class pairs (true -> predicted):')\nfor k in np.argsort(off.ravel())[::-1][:8]:\n    t, p_ = divmod(k, N_CLASSES); print(f'{int(off[t, p_]):3d}  {CLASSES[t]}  ->  {CLASSES[p_]}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T22:15:16.319704Z","iopub.execute_input":"2026-10-17T22:15:16.320132Z","iopub.status.idle":"2026-10-17T22:15:23.352472Z","shell.execute_reply.started":"2026-10-17T22:15:16.32011Z","shell.execute_reply":"2026-10-17T22:15:23.351519Z"}},"outputs":[],"execution_count":null},{"id":"1a67c70f","cell_type":"code","source":"sub = pd.read_csv('/kaggle/working/submission.csv'); ref = pd.read_csv(DATA/'sample_submission.csv')\nchecks = {\n    'submission.csv exists': os.path.exists('/kaggle/working/submission.csv'),\n    'columns match sample_submission': list(sub.columns) == list(ref.columns),\n    'fname order matches sample_submission': (sub.fname.values == ref.fname.values).all(),\n    'row count matches': len(sub) == len(ref),\n    'no NaN values': not sub.isna().any().any(),\n    'probabilities in [0,1]': bool(sub[CLASSES].min().min() >= 0 and sub[CLASSES].max().max() <= 1),\n    'total training time < 120 min': TOTAL_TRAIN_MIN < 120,\n    'seed fixed (42)': SEED == 42,\n}\nfor k, v in checks.items(): print(('PASS  ' if v else 'FAIL  ') + k)\nassert all(checks.values()), 'Kuch check fail hai - upar dekhein'\nprint('\\nTotal training time: %.1f min | Total notebook time: %.1f min' % (TOTAL_TRAIN_MIN, (time.time()-NOTEBOOK_START)/60))\nprint('Best model:', max(results, key=lambda r: r['best'])['name'])\nprint('submission.csv generated from:', chosen['name'])\nprint('\\nFiles in /kaggle/working:'); [print('  ', f) for f in sorted(os.listdir('/kaggle/working')) if f.endswith(('.csv', '.pt'))]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-17T22:26:03.051747Z","iopub.execute_input":"2026-10-17T22:26:03.052352Z","iopub.status.idle":"2026-10-17T22:26:03.144713Z","shell.execute_reply.started":"2026-10-17T22:26:03.052322Z","shell.execute_reply":"2026-10-17T22:26:03.144019Z"}},"outputs":[],"execution_count":null}]}