{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"},{"sourceId":11632994,"sourceType":"datasetVersion","datasetId":7298712}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport time\nimport logging\nfrom pathlib import Path\nfrom dataclasses import dataclass\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import roc_auc_score\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.optim import AdamW, Adam, SGD\nfrom torch.optim.lr_scheduler import CosineAnnealingLR, ReduceLROnPlateau, StepLR, OneCycleLR\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm.auto import tqdm\nimport timm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:43:51.178425Z","iopub.execute_input":"2025-05-01T03:43:51.178878Z","iopub.status.idle":"2025-05-01T03:44:12.082088Z","shell.execute_reply.started":"2025-05-01T03:43:51.178854Z","shell.execute_reply":"2025-05-01T03:44:12.081512Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@dataclass\nclass Config:\n    train_csv: str = '/kaggle/input/birdclef-2025/train.csv'\n    taxonomy_csv: str = '/kaggle/input/birdclef-2025/taxonomy.csv'\n    spectrogram_npy: str = '/kaggle/input/falcon-birdclef-cnn-preprocessed-dataset/falcon_birdclef_cnn_preprocessed_dataset.npy'\n    train_datadir: str = '/kaggle/input/birdclef-2025/train_audio'\n    LOAD_DATA: bool = True\n    n_fold: int = 5\n    selected_folds: tuple = (0, 1, 2, 3, 4)\n    seed: int = 42\n    debug: bool = False\n    batch_size: int = 32\n    num_workers: int = 4\n    epochs: int = 10\n    device: str = 'cuda' if torch.cuda.is_available() else 'cpu'\n    model_name: str = 'efficientnet_b0'\n    pretrained: bool = True\n    in_channels: int = 1\n    optimizer: str = 'AdamW'\n    scheduler: str = 'CosineAnnealingLR'\n    T_max: int = 10\n    min_lr: float = 1e-5\n    criterion: str = 'BCEWithLogitsLoss'\n    lr: float = 1e-3\n    weight_decay: float = 1e-6\n\ncfg = Config()\nlogging.basicConfig(level=logging.INFO)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:44:12.083682Z","iopub.execute_input":"2025-05-01T03:44:12.084127Z","iopub.status.idle":"2025-05-01T03:44:12.188946Z","shell.execute_reply.started":"2025-05-01T03:44:12.084105Z","shell.execute_reply":"2025-05-01T03:44:12.188227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def set_seed(seed: int = 42):\n    import random\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_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nset_seed(cfg.seed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:44:12.189808Z","iopub.execute_input":"2025-05-01T03:44:12.190136Z","iopub.status.idle":"2025-05-01T03:44:12.218593Z","shell.execute_reply.started":"2025-05-01T03:44:12.190106Z","shell.execute_reply":"2025-05-01T03:44:12.21787Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"logging.info(\"Loading spectrograms...\")\nspec_path = Path(cfg.spectrogram_npy)\nspectrograms = np.load(spec_path, allow_pickle=True).item()\nlogging.info(f\"Loaded {len(spectrograms)} spectrograms\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:44:12.219439Z","iopub.execute_input":"2025-05-01T03:44:12.219681Z","iopub.status.idle":"2025-05-01T03:45:06.005073Z","shell.execute_reply.started":"2025-05-01T03:44:12.219647Z","shell.execute_reply":"2025-05-01T03:45:06.004464Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SpectrogramDataset(Dataset):\n    def __init__(self, df: pd.DataFrame, cfg: Config, specs: dict, mode: str = 'train'):\n        self.df = df.copy()\n        self.specs = specs\n        self.cfg = cfg\n        self.mode = mode\n\n        # prepare sample keys\n        if 'sample_key' not in self.df.columns:\n            self.df['sample_key'] = (\n                self.df.filename\n                .str.replace('/', '_')\n                .str.replace('.wav', '')\n            )\n\n        # label mapping\n        taxonomy = pd.read_csv(cfg.taxonomy_csv)\n        labels = taxonomy['primary_label'].tolist()\n        self.label_to_idx = {lbl: idx for idx, lbl in enumerate(labels)}\n        self.num_classes = len(labels)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx: int):\n        row = self.df.iloc[idx]\n        key = row['sample_key']\n        spec = self.specs.get(key)\n\n        if spec is None:\n            # fallback: zero tensor\n            spec = np.zeros((cfg.in_channels, 256, 256), dtype=np.float32)\n        else:\n            spec = np.expand_dims(spec, axis=0)  # add channel\n\n        spec = torch.tensor(spec, dtype=torch.float32)\n\n        # encode primary label\n        target = np.zeros(self.num_classes, dtype=np.float32)\n        primary = row['primary_label']\n        if primary in self.label_to_idx:\n            target[self.label_to_idx[primary]] = 1.0\n\n        # include secondary if present\n        sec = row.get('secondary_labels')\n        if isinstance(sec, str) and sec:\n            for s in eval(sec):\n                if s in self.label_to_idx:\n                    target[self.label_to_idx[s]] = 1.0\n\n        target = torch.tensor(target)\n        return spec, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.00573Z","iopub.execute_input":"2025-05-01T03:45:06.00594Z","iopub.status.idle":"2025-05-01T03:45:06.014126Z","shell.execute_reply.started":"2025-05-01T03:45:06.005923Z","shell.execute_reply":"2025-05-01T03:45:06.013487Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def collate_specs(batch):\n    specs, targets = zip(*batch)\n    specs = torch.stack(specs)\n    targets = torch.stack(targets)\n    return specs, targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.014892Z","iopub.execute_input":"2025-05-01T03:45:06.015711Z","iopub.status.idle":"2025-05-01T03:45:06.039358Z","shell.execute_reply.started":"2025-05-01T03:45:06.015687Z","shell.execute_reply":"2025-05-01T03:45:06.038682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CLEFClassifier(nn.Module):\n    def __init__(self, cfg: Config):\n        super().__init__()\n        taxonomy = pd.read_csv(cfg.taxonomy_csv)\n        num_classes = len(taxonomy)\n\n        back = timm.create_model(\n            cfg.model_name,\n            pretrained=cfg.pretrained,\n            in_chans=cfg.in_channels,\n            num_classes=0,\n        )\n        self.encoder = back\n        self.pool = nn.AdaptiveAvgPool2d(1)\n        feat_dim = back.num_features\n        self.head = nn.Linear(feat_dim, num_classes)\n\n    def forward(self, x):\n        feats = self.encoder(x)\n        if feats.dim() == 4:\n            feats = self.pool(feats)\n            feats = feats.view(feats.size(0), -1)\n        return self.head(feats)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.04127Z","iopub.execute_input":"2025-05-01T03:45:06.041459Z","iopub.status.idle":"2025-05-01T03:45:06.056517Z","shell.execute_reply.started":"2025-05-01T03:45:06.041443Z","shell.execute_reply":"2025-05-01T03:45:06.056024Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_optimizer(model, cfg):\n    if cfg.optimizer == 'AdamW':\n        return AdamW(model.parameters(), lr=cfg.lr, weight_decay=cfg.weight_decay)\n    if cfg.optimizer == 'Adam':\n        return Adam(model.parameters(), lr=cfg.lr, weight_decay=cfg.weight_decay)\n    if cfg.optimizer == 'SGD':\n        return SGD(model.parameters(), lr=cfg.lr, weight_decay=cfg.weight_decay, momentum=0.9)\n    raise ValueError('Unknown optimizer')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.057161Z","iopub.execute_input":"2025-05-01T03:45:06.057328Z","iopub.status.idle":"2025-05-01T03:45:06.086975Z","shell.execute_reply.started":"2025-05-01T03:45:06.057314Z","shell.execute_reply":"2025-05-01T03:45:06.086431Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_scheduler(opt, cfg):\n    if cfg.scheduler == 'CosineAnnealingLR':\n        return CosineAnnealingLR(opt, T_max=cfg.T_max, eta_min=cfg.min_lr)\n    if cfg.scheduler == 'ReduceLROnPlateau':\n        return ReduceLROnPlateau(opt, factor=0.5, patience=2, min_lr=cfg.min_lr)\n    if cfg.scheduler == 'StepLR':\n        return StepLR(opt, step_size=cfg.epochs//3, gamma=0.5)\n    if cfg.scheduler == 'OneCycleLR':\n        return OneCycleLR(opt, max_lr=cfg.lr, steps_per_epoch=1, epochs=cfg.epochs)\n    return None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.087528Z","iopub.execute_input":"2025-05-01T03:45:06.087709Z","iopub.status.idle":"2025-05-01T03:45:06.106506Z","shell.execute_reply.started":"2025-05-01T03:45:06.087686Z","shell.execute_reply":"2025-05-01T03:45:06.105868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_loss(cfg):\n    if cfg.criterion == 'BCEWithLogitsLoss':\n        return nn.BCEWithLogitsLoss()\n    raise ValueError('Unknown loss')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.107264Z","iopub.execute_input":"2025-05-01T03:45:06.107522Z","iopub.status.idle":"2025-05-01T03:45:06.126699Z","shell.execute_reply.started":"2025-05-01T03:45:06.107501Z","shell.execute_reply":"2025-05-01T03:45:06.12622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def epoch_step(model, loader, opt, loss_fn, device, scheduler=None, train=True):\n    model.train() if train else model.eval()\n    losses, all_t, all_p = [], [], []\n\n    loader = tqdm(loader, desc='Train' if train else 'Valid')\n    for batch in loader:\n        specs, targets = batch\n        specs, targets = specs.to(device), targets.to(device)\n\n        if train:\n            opt.zero_grad()\n            preds = model(specs)\n            loss = loss_fn(preds, targets)\n            loss.backward()\n            opt.step()\n        else:\n            with torch.no_grad():\n                preds = model(specs)\n                loss = loss_fn(preds, targets)\n\n        probs = torch.sigmoid(preds).detach().cpu().numpy()\n        y = targets.detach().cpu().numpy()\n        losses.append(loss.item())\n        all_t.append(y)\n        all_p.append(probs)\n        if scheduler and train:\n            if isinstance(scheduler, OneCycleLR):\n                scheduler.step()\n\n    all_t = np.vstack(all_t)\n    all_p = np.vstack(all_p)\n    auc = np.mean([roc_auc_score(all_t[:, i], all_p[:, i]) \n                   for i in range(all_t.shape[1]) if all_t[:, i].sum() > 0])\n    return np.mean(losses), auc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.12772Z","iopub.execute_input":"2025-05-01T03:45:06.127975Z","iopub.status.idle":"2025-05-01T03:45:06.14516Z","shell.execute_reply.started":"2025-05-01T03:45:06.127955Z","shell.execute_reply":"2025-05-01T03:45:06.14464Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_cv(df: pd.DataFrame, cfg: Config):\n    skf = StratifiedKFold(n_splits=cfg.n_fold, shuffle=True, random_state=cfg.seed)\n    scores = []\n\n    for fold, (tr_idx, val_idx) in enumerate(skf.split(df, df['primary_label'])):\n        if fold not in cfg.selected_folds:\n            continue\n        print(f\"\\n--- Fold {fold} ---\")\n        tr_df, v_df = df.iloc[tr_idx], df.iloc[val_idx]\n        train_ds = SpectrogramDataset(tr_df, cfg, spectrograms, 'train')\n        val_ds   = SpectrogramDataset(v_df, cfg, spectrograms, 'valid')\n        tr_loader = DataLoader(train_ds, batch_size=cfg.batch_size,\n                               shuffle=True, num_workers=cfg.num_workers,\n                               collate_fn=collate_specs)\n        v_loader  = DataLoader(val_ds,   batch_size=cfg.batch_size,\n                               shuffle=False, num_workers=cfg.num_workers,\n                               collate_fn=collate_specs)\n\n        model = CLEFClassifier(cfg).to(cfg.device)\n        opt = make_optimizer(model, cfg)\n        sch = make_scheduler(opt, cfg)\n        loss_fn = make_loss(cfg)\n\n        best_auc = 0.0\n        for epoch in range(cfg.epochs):\n            print(f\"Epoch {epoch+1}/{cfg.epochs}\")\n            train_loss, train_auc = epoch_step(model, tr_loader, opt, loss_fn, cfg.device, sch, True)\n            valid_loss, valid_auc = epoch_step(model, v_loader, None, loss_fn, cfg.device, None, False)\n            print(f\"  Train AUC: {train_auc:.4f}, Valid AUC: {valid_auc:.4f}\")\n\n            if valid_auc > best_auc:\n                best_auc = valid_auc\n                torch.save(model.state_dict(), f\"best_fold{fold}.pt\")\n        scores.append(best_auc)\n        print(f\"Fold {fold} best AUC: {best_auc:.4f}\")\n\n    print(f\"\\nMean AUC across folds: {np.mean(scores):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.145702Z","iopub.execute_input":"2025-05-01T03:45:06.145894Z","iopub.status.idle":"2025-05-01T03:45:06.168704Z","shell.execute_reply.started":"2025-05-01T03:45:06.145879Z","shell.execute_reply":"2025-05-01T03:45:06.168216Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if __name__ == '__main__':\n    df = pd.read_csv(cfg.train_csv)\n    run_cv(df, cfg)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-01T03:45:06.169298Z","iopub.execute_input":"2025-05-01T03:45:06.169534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# — after run_cv(df, cfg) completes —\n# Pick one of the saved fold‐models (e.g. fold 0) and re‐save it as model.pth\nbest_fold = cfg.selected_folds[0]\nbest_file = f\"best_fold{best_fold}.pt\"\nprint(f\"Loading weights from {best_file} and saving as model.pth\")\n\n# Instantiate a fresh model, load weights, then dump\nfinal_model = CLEFClassifier(cfg).to(cfg.device)\nfinal_model.load_state_dict(torch.load(best_file, map_location=cfg.device))\ntorch.save(final_model.state_dict(), 'model.pth')\nprint(\"✅ Saved final_model.state_dict() → model.pth\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}