{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":11276039,"sourceType":"datasetVersion","datasetId":7049405},{"sourceId":11288775,"sourceType":"datasetVersion","datasetId":7058496},{"sourceId":11295314,"sourceType":"datasetVersion","datasetId":7062869},{"sourceId":11362276,"sourceType":"datasetVersion","datasetId":7111566}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **BirdCLEF 2025 Training Notebook**\n\nThis is a baseline training pipeline for BirdCLEF 2025 using EfficientNetB0 with PyTorch and Timm(for pretrained EffNet). You can check inference and preprocessing notebooks in the following links: \n\n- [EfficientNet B0 Pytorch [Inference] | BirdCLEF'25](https://www.kaggle.com/code/kadircandrisolu/efficientnet-b0-pytorch-inference-birdclef-25)\n\n  \n- [Transforming Audio-to-Mel Spec. | BirdCLEF'25](https://www.kaggle.com/code/kadircandrisolu/transforming-audio-to-mel-spec-birdclef-25)  \n\nNote that by default this notebook is in Debug Mode, so it will only train the model with 2 epochs, but the [weight](https://www.kaggle.com/datasets/kadircandrisolu/birdclef25-effnetb0-starter-weight) I used in the inference notebook was obtained after 10 epochs of training.\n\n**Features**\n* Implement with Pytorch and Timm\n* Flexible audio processing with both pre-computed and on-the-fly mel spectrograms\n* Stratified 5-fold cross-validation with ensemble capability\n* Mixup training for improved generalization\n* Spectrogram augmentations (time/frequency masking, brightness adjustment)\n* AdamW optimizer with Cosine Annealing LR scheduling\n* Debug mode for quick experimentation with smaller datasets\n\n**Pre-computed Spectrograms**\nFor faster training, you can use pre-computed mel spectrograms from [this dataset](https://www.kaggle.com/datasets/kadircandrisolu/birdclef25-mel-spectrograms) by setting `LOAD_DATA = True`","metadata":{"_uuid":"f1ab3ae2-2f0d-4ea1-ad23-71144116cc49","_cell_guid":"09705bd1-ab4a-475f-af13-bc98f6c43f18","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"markdown","source":"## Libraries","metadata":{"_uuid":"139637cb-2d41-4b9a-b4c7-cb9820ee1d53","_cell_guid":"a069e94d-849c-497c-be0a-0d41b220a8be","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"markdown","source":"","metadata":{"_uuid":"008f8c4c-03b4-4f62-a23c-4cc6f40409ae","_cell_guid":"b8e62904-81b2-45e4-ac5e-809006eeca52","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"import os\nimport logging\nimport random\nimport gc\nimport time\nimport cv2\nimport math\nimport warnings\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import roc_auc_score\nimport librosa\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.optim import lr_scheduler\nfrom torch.utils.data import Dataset, DataLoader\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom tqdm.auto import tqdm\n\nimport timm\n\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)","metadata":{"_uuid":"9a5310d9-7167-4670-ba2e-c2513e2b421f","_cell_guid":"0fd4468b-970a-41eb-98de-5ccbd6d8a41a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:18:47.483638Z","iopub.execute_input":"2025-04-12T06:18:47.483919Z","iopub.status.idle":"2025-04-12T06:19:00.852163Z","shell.execute_reply.started":"2025-04-12T06:18:47.483896Z","shell.execute_reply":"2025-04-12T06:19:00.851543Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Configuration","metadata":{"_uuid":"f2fbebba-9ca1-4134-bdfd-2c94fdba0ea8","_cell_guid":"6b214313-0bf5-40f5-a202-f7758110959b","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"class CFG:\n    seed = 42\n    debug = False\n    apex = False\n    print_freq = 100\n    num_workers = 2\n\n    OUTPUT_DIR = '/kaggle/working/'\n    train_datadir = '/kaggle/input/birdclef-2025/train_audio'\n    train_csv = '/kaggle/input/ami-kaggle-birdclef2025-traindata0406/train_folds.csv'\n    test_soundscapes = '/kaggle/input/birdclef-2025/test_soundscapes'\n    submission_csv = '/kaggle/input/birdclef-2025/sample_submission.csv'\n    taxonomy_csv = '/kaggle/input/birdclef-2025/taxonomy.csv'\n    spectrogram_npy = '/kaggle/input/mel-spec-without-human-voice/birdclef2025_melspec_without_voice_per5sec_256_256.npy'\n    feature_base_dir = \"/kaggle/input/ami-kaggle-birdclef2025-traindata0406/feature/\"\n\n\n    model_name = 'efficientnet_b0'\n    pretrained = True\n    in_channels = 1\n\n    LOAD_DATA = True\n    FS = 32000\n    TARGET_DURATION = 5.0\n    TARGET_SHAPE = (256, 256)\n\n    N_FFT = 1024\n    HOP_LENGTH = 512\n    N_MELS = 128\n    FMIN = 50\n    FMAX = 14000\n\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    epochs = 15\n    batch_size = 32\n    criterion = 'FocalLoss'\n    focal_alpha = 0.25\n    focal_gamma = 2.0\n\n    n_fold = 5\n    selected_folds = [0,1,2,3,4]\n\n    optimizer = 'AdamW'\n    lr = 5e-4\n    weight_decay = 1e-5\n\n    scheduler = 'CosineAnnealingLR'\n    min_lr = 1e-7\n    T_max = epochs\n\n    aug_prob = 1\n    mixup_alpha = 0.8\n\n    #label_column = 'class_name'  # Switch to class-level classification\n    label_column = \"primary_label\"\n\n    def update_debug_settings(self):\n        if self.debug:\n            self.epochs = 10\n            self.selected_folds = [0]\n\ncfg = CFG()\ndef extract_all_labels(df):\n    all_labels = set()\n    for _, row in df.iterrows():\n        all_labels.add(row['primary_label'])\n        try:\n            sec_labels = eval(row['secondary_labels']) if isinstance(row['secondary_labels'], str) else []\n            all_labels.update([label for label in sec_labels if label.strip()])\n        except:\n            pass\n    return sorted(list(all_labels))\n\n# モデル構築前に必ずこれを呼ぶ：\ndf = pd.read_csv(cfg.train_csv)\nall_labels = extract_all_labels(df)\ncfg.coarse_labels = all_labels\ncfg.num_classes = len(all_labels)\n\nprint(cfg.num_classes)","metadata":{"_uuid":"25bb1612-2a38-4726-bc6a-da0ac51095ab","_cell_guid":"70e4d96c-68fa-4e3b-9891-fccf17c0420a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:00.852872Z","iopub.execute_input":"2025-04-12T06:19:00.853099Z","iopub.status.idle":"2025-04-12T06:19:02.780420Z","shell.execute_reply.started":"2025-04-12T06:19:00.853079Z","shell.execute_reply":"2025-04-12T06:19:02.779657Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Utilities","metadata":{"_uuid":"26091082-0f29-4310-964b-8798b05a61da","_cell_guid":"b7c2df1b-f422-4238-926b-b4ae391d710a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"def set_seed(seed=42):\n    \"\"\"\n    Set seed for reproducibility\n    \"\"\"\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    torch.backends.cudnn.benchmark = False\n\nset_seed(cfg.seed)","metadata":{"_uuid":"1f89a318-0968-45ee-b759-1b1567a6fb66","_cell_guid":"6cbc11c2-1545-4643-a5c4-f1511d12211a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.781155Z","iopub.execute_input":"2025-04-12T06:19:02.781399Z","iopub.status.idle":"2025-04-12T06:19:02.791677Z","shell.execute_reply.started":"2025-04-12T06:19:02.781379Z","shell.execute_reply":"2025-04-12T06:19:02.790977Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Pre-processing\nThese functions handle the transformation of audio files to mel spectrograms for model input, with flexibility controlled by the `LOAD_DATA` parameter. The process involves either loading pre-computed spectrograms from this [dataset](https://www.kaggle.com/datasets/kadircandrisolu/birdclef25-mel-spectrograms) (when `LOAD_DATA=True`) or dynamically generating them (when `LOAD_DATA=False`), transforming audio data into spectrogram representations, and preparing it for the neural network.","metadata":{"_uuid":"c3d79df8-0fee-4765-b6f3-6b2fbdc26918","_cell_guid":"473b16e1-43a0-401f-8afe-8c05a3d62f17","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"def audio2melspec(audio_data, cfg):\n    \"\"\"Convert audio data to mel spectrogram\"\"\"\n    if np.isnan(audio_data).any():\n        mean_signal = np.nanmean(audio_data)\n        audio_data = np.nan_to_num(audio_data, nan=mean_signal)\n\n    mel_spec = librosa.feature.melspectrogram(\n        y=audio_data,\n        sr=cfg.FS,\n        n_fft=cfg.N_FFT,\n        hop_length=cfg.HOP_LENGTH,\n        n_mels=cfg.N_MELS,\n        fmin=cfg.FMIN,\n        fmax=cfg.FMAX,\n        power=2.0\n    )\n\n    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n    mel_spec_norm = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)\n    \n    return mel_spec_norm\n\ndef process_audio_file(audio_path, cfg):\n    \"\"\"Process a single audio file to get the mel spectrogram\"\"\"\n    try:\n        audio_data, _ = librosa.load(audio_path, sr=cfg.FS)\n\n        target_samples = int(cfg.TARGET_DURATION * cfg.FS)\n\n        if len(audio_data) < target_samples:\n            n_copy = math.ceil(target_samples / len(audio_data))\n            if n_copy > 1:\n                audio_data = np.concatenate([audio_data] * n_copy)\n\n        # Extract center 5 seconds\n        start_idx = max(0, int(len(audio_data) / 2 - target_samples / 2))\n        end_idx = min(len(audio_data), start_idx + target_samples)\n        center_audio = audio_data[start_idx:end_idx]\n\n        if len(center_audio) < target_samples:\n            center_audio = np.pad(center_audio, \n                                 (0, target_samples - len(center_audio)), \n                                 mode='constant')\n\n        mel_spec = audio2melspec(center_audio, cfg)\n        \n        if mel_spec.shape != cfg.TARGET_SHAPE:\n            mel_spec = cv2.resize(mel_spec, cfg.TARGET_SHAPE, interpolation=cv2.INTER_LINEAR)\n\n        return mel_spec.astype(np.float32)\n        \n    except Exception as e:\n        print(f\"Error processing {audio_path}: {e}\")\n        return None\n\ndef generate_spectrograms(df, cfg):\n    \"\"\"Generate spectrograms from audio files\"\"\"\n    print(\"Generating mel spectrograms from audio files...\")\n    start_time = time.time()\n\n    all_bird_data = {}\n    errors = []\n\n    for i, row in tqdm(df.iterrows(), total=len(df)):\n        if cfg.debug and i >= 1000:\n            break\n        \n        try:\n            samplename = row['samplename']\n            filepath = row['filepath']\n            \n            mel_spec = process_audio_file(filepath, cfg)\n            \n            if mel_spec is not None:\n                all_bird_data[samplename] = mel_spec\n            \n        except Exception as e:\n            print(f\"Error processing {row.filepath}: {e}\")\n            errors.append((row.filepath, str(e)))\n\n    end_time = time.time()\n    print(f\"Processing completed in {end_time - start_time:.2f} seconds\")\n    print(f\"Successfully processed {len(all_bird_data)} files out of {len(df)}\")\n    print(f\"Failed to process {len(errors)} files\")\n    \n    return all_bird_data","metadata":{"_uuid":"0bb7db16-c7b3-4e8a-92a6-c273a76b1b1b","_cell_guid":"536dd8f4-85a4-4ae3-85fd-6e67e0b291d4","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.793238Z","iopub.execute_input":"2025-04-12T06:19:02.793526Z","iopub.status.idle":"2025-04-12T06:19:02.804472Z","shell.execute_reply.started":"2025-04-12T06:19:02.793506Z","shell.execute_reply":"2025-04-12T06:19:02.803873Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset Preparation and Data Augmentations\nWe'll convert audio to mel spectrograms and apply random augmentations with 50% probability each - including time stretching, pitch shifting, and volume adjustments. This randomized approach creates diverse training samples from the same audio files","metadata":{"_uuid":"c1f9c5be-eeec-4ded-97a5-030320c0b123","_cell_guid":"ebcc4e2b-d8c7-4e48-8a9e-6a0aa5e7913b","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"\ndef make_samplename(filename):\n    # \"21211/XC896840.wav\" → \"21211_XC896840\"\n    path = Path(filename)\n    return f\"{path.parent.name}_{path.stem}\"\n\nclass BirdCLEFDatasetFromNPY(Dataset):\n    def __init__(self, df, cfg, spectrograms=None, mode=\"train\"):\n        self.df = df.reset_index(drop=True)\n        self.cfg = cfg\n        self.spectrograms = spectrograms\n        self.mode = mode\n\n        taxonomy_df = pd.read_csv(cfg.taxonomy_csv)\n        self.label_to_idx = {label: i for i, label in enumerate(taxonomy_df['primary_label'].tolist())}\n        self.num_classes = len(self.label_to_idx)\n\n        if 'samplename' not in self.df.columns:\n            self.df['samplename'] = self.df.filename.map(lambda x: x.split('/')[0] + '-' + x.split('/')[-1].split('.')[0])\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        samplename = row['samplename']\n\n        # メルスペクトログラムの読み込み\n        if self.spectrograms and samplename in self.spectrograms:\n            spec = self.spectrograms[samplename]\n        else:\n            spec = np.zeros(self.cfg.TARGET_SHAPE, dtype=np.float32)\n            if self.mode == 'train':\n                print(f\"Warning: Missing spectrogram for {samplename}\")\n\n        spec = torch.tensor(spec, dtype=torch.float32).unsqueeze(0)\n\n        if self.mode == \"train\" and random.random() < self.cfg.aug_prob:\n            spec = self.apply_spec_augmentations(spec)\n\n        target = self.encode_label(row)\n\n        return {\n            'melspec': spec,\n            'target': torch.tensor(target, dtype=torch.float32),\n            'filename': row['filename']\n        }\n    \n    def apply_spec_augmentations(self, spec):\n        \"\"\"Apply augmentations to spectrogram\"\"\"\n    \n        # Time masking (horizontal stripes)\n        if random.random() < 0.5:\n            num_masks = random.randint(1, 3)\n            for _ in range(num_masks):\n                width = random.randint(5, 20)\n                start = random.randint(0, spec.shape[2] - width)\n                spec[0, :, start:start+width] = 0\n        \n        # Frequency masking (vertical stripes)\n        if random.random() < 0.5:\n            num_masks = random.randint(1, 3)\n            for _ in range(num_masks):\n                height = random.randint(5, 20)\n                start = random.randint(0, spec.shape[1] - height)\n                spec[0, start:start+height, :] = 0\n        \n        # Random brightness/contrast\n        if random.random() < 0.5:\n            gain = random.uniform(0.8, 1.2)\n            bias = random.uniform(-0.1, 0.1)\n            spec = spec * gain + bias\n            spec = torch.clamp(spec, 0, 1) \n            \n        return spec\n    \n    def encode_label(self, row):\n        target = np.zeros(self.num_classes)\n    \n        # secondary_labels は文字列なので list に変換\n        try:\n            sec_labels = eval(row['secondary_labels']) if isinstance(row['secondary_labels'], str) else []\n        except:\n            sec_labels = []\n    \n        labels = [row['primary_label']] + [label for label in sec_labels if label.strip()]\n        for label in labels:\n            if label in self.label_to_idx:\n                target[self.label_to_idx[label]] = 1.0\n        return target","metadata":{"_uuid":"69f25b74-f06d-47f2-92e2-75887878f605","_cell_guid":"3c8cdd8b-3738-4ae4-bb5e-f6ec848e56de","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.805695Z","iopub.execute_input":"2025-04-12T06:19:02.806012Z","iopub.status.idle":"2025-04-12T06:19:02.825519Z","shell.execute_reply.started":"2025-04-12T06:19:02.805982Z","shell.execute_reply":"2025-04-12T06:19:02.824598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def collate_fn(batch):\n    \"\"\"Custom collate function to handle different sized spectrograms\"\"\"\n    batch = [item for item in batch if item is not None]\n    if len(batch) == 0:\n        return {}\n        \n    result = {key: [] for key in batch[0].keys()}\n    \n    for item in batch:\n        for key, value in item.items():\n            result[key].append(value)\n    \n    for key in result:\n        if key == 'target' and isinstance(result[key][0], torch.Tensor):\n            result[key] = torch.stack(result[key])\n        elif key == 'melspec' and isinstance(result[key][0], torch.Tensor):\n            shapes = [t.shape for t in result[key]]\n            if len(set(str(s) for s in shapes)) == 1:\n                result[key] = torch.stack(result[key])\n    \n    return result","metadata":{"_uuid":"3b56b251-d544-434c-9bcf-e75667719d88","_cell_guid":"7a83b6e2-b536-4026-a25a-f709c6716b6b","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.826249Z","iopub.execute_input":"2025-04-12T06:19:02.826468Z","iopub.status.idle":"2025-04-12T06:19:02.849127Z","shell.execute_reply.started":"2025-04-12T06:19:02.826450Z","shell.execute_reply":"2025-04-12T06:19:02.848553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class FocalLoss(nn.Module):\n    def __init__(self, alpha=0.25, gamma=2.0, reduction='mean'):\n        super(FocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.reduction = reduction\n\n    def forward(self, inputs, targets):\n        # 通常の focal loss\n        return self._focal_loss(inputs, targets)\n\n    def forward_with_mixup(self, inputs, targets_a, targets_b, lam):\n        # Mixup された2つのターゲットに対して focal loss をそれぞれ計算し、lamで加重平均\n        loss_a = self._focal_loss(inputs, targets_a, reduction='none')\n        loss_b = self._focal_loss(inputs, targets_b, reduction='none')\n        loss = lam * loss_a + (1 - lam) * loss_b\n        return loss.mean()\n\n    def _focal_loss(self, inputs, targets, reduction=None):\n        probs = torch.sigmoid(inputs)\n        probs = torch.clamp(probs, 1e-6, 1 - 1e-6)\n\n        pt = torch.where(targets == 1, probs, 1 - probs)\n        alpha_factor = torch.where(targets == 1, self.alpha, 1 - self.alpha)\n        loss = -alpha_factor * (1 - pt) ** self.gamma * torch.log(pt)\n\n        reduction = reduction or self.reduction\n        if reduction == 'mean':\n            return loss.mean()\n        elif reduction == 'sum':\n            return loss.sum()\n        return loss","metadata":{"_uuid":"ea03a660-fc88-4a3a-a9c9-9a22b65f45be","_cell_guid":"8ad2723b-8856-46ea-9db0-c25dfa963fe0","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-04-12T06:19:02.849956Z","iopub.execute_input":"2025-04-12T06:19:02.850181Z","iopub.status.idle":"2025-04-12T06:19:02.864818Z","shell.execute_reply.started":"2025-04-12T06:19:02.850163Z","shell.execute_reply":"2025-04-12T06:19:02.863999Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Definition","metadata":{"_uuid":"3796e811-40e5-4c82-83e6-9955a55fd8bd","_cell_guid":"f80d9f92-6516-4b2c-8732-ba66a0e9bb1e","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Model adjusted for new number of classes and with mixup support\nclass BirdCLEFModel(nn.Module):\n    def __init__(self, cfg):\n        super().__init__()\n        self.cfg = cfg\n        \n        taxonomy_df = pd.read_csv(cfg.taxonomy_csv)\n        cfg.num_classes = len(taxonomy_df)\n        \n        self.backbone = timm.create_model(\n            cfg.model_name,\n            pretrained=cfg.pretrained,\n            in_chans=cfg.in_channels,\n            drop_rate=0.2,\n            drop_path_rate=0.2\n        )\n        \n        if 'efficientnet' in cfg.model_name:\n            backbone_out = self.backbone.classifier.in_features\n            self.backbone.classifier = nn.Identity()\n        elif 'resnet' in cfg.model_name:\n            backbone_out = self.backbone.fc.in_features\n            self.backbone.fc = nn.Identity()\n        else:\n            backbone_out = self.backbone.get_classifier().in_features\n            self.backbone.reset_classifier(0, '')\n        \n        self.pooling = nn.AdaptiveAvgPool2d(1)\n            \n        self.feat_dim = backbone_out\n        \n        self.classifier = nn.Linear(backbone_out, cfg.num_classes)\n        \n        self.mixup_enabled = hasattr(cfg, 'mixup_alpha') and cfg.mixup_alpha > 0\n        if self.mixup_enabled:\n            self.mixup_alpha = cfg.mixup_alpha\n\n    def forward(self, x, targets=None, criterion=None):\n        if self.training and self.mixup_enabled and targets is not None:\n            mixed_x, targets_a, targets_b, lam = self.mixup_data(x, targets)\n            x = mixed_x\n        else:\n            targets_a, targets_b, lam = None, None, None\n    \n        features = self.backbone(x)\n        if isinstance(features, dict):\n            features = features['features']\n        if len(features.shape) == 4:\n            features = self.pooling(features)\n            features = features.view(features.size(0), -1)\n        logits = self.classifier(features)\n    \n        # loss を計算（criterion を引数で受け取る）\n        if self.training and self.mixup_enabled and targets is not None:\n            if hasattr(criterion, 'forward_with_mixup'):\n                loss = criterion.forward_with_mixup(logits, targets_a, targets_b, lam)\n            else:\n                loss = self.mixup_criterion(criterion, logits, targets_a, targets_b, lam)\n            return logits, loss\n    \n        return logits\n\n\n    def mixup_data(self, x, targets):\n        lam = np.random.beta(self.mixup_alpha, self.mixup_alpha)\n        batch_size = x.size()[0]\n        index = torch.randperm(batch_size).to(x.device)\n\n        mixed_x = lam * x + (1 - lam) * x[index, :]\n        targets_a, targets_b = targets, targets[index]\n        return mixed_x, targets_a, targets_b, lam\n\n    def mixup_criterion(self, criterion, pred, y_a, y_b, lam):\n        return lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)","metadata":{"_uuid":"140fe541-a784-418e-be29-1a2e37426864","_cell_guid":"9c52b23a-ae3e-4c45-9478-b3c9d8671781","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.865734Z","iopub.execute_input":"2025-04-12T06:19:02.866036Z","iopub.status.idle":"2025-04-12T06:19:02.884871Z","shell.execute_reply.started":"2025-04-12T06:19:02.866004Z","shell.execute_reply":"2025-04-12T06:19:02.884088Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Utilities\nWe are configuring our optimization strategy with the AdamW optimizer, cosine scheduling, and the BCEWithLogitsLoss criterion.","metadata":{"_uuid":"faa08776-8b1b-4cd0-9cd3-5b65b2e74753","_cell_guid":"e7f962d8-1c18-4578-82c0-1d71e3f134d3","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"def get_optimizer(model, cfg):\n  \n    if cfg.optimizer == 'Adam':\n        optimizer = optim.Adam(\n            model.parameters(),\n            lr=cfg.lr,\n            weight_decay=cfg.weight_decay\n        )\n    elif cfg.optimizer == 'AdamW':\n        optimizer = optim.AdamW(\n            model.parameters(),\n            lr=cfg.lr,\n            weight_decay=cfg.weight_decay\n        )\n    elif cfg.optimizer == 'SGD':\n        optimizer = optim.SGD(\n            model.parameters(),\n            lr=cfg.lr,\n            momentum=0.9,\n            weight_decay=cfg.weight_decay\n        )\n    else:\n        raise NotImplementedError(f\"Optimizer {cfg.optimizer} not implemented\")\n        \n    return optimizer\n\ndef get_scheduler(optimizer, cfg):\n   \n    if cfg.scheduler == 'CosineAnnealingLR':\n        scheduler = lr_scheduler.CosineAnnealingLR(\n            optimizer,\n            T_max=cfg.T_max,\n            eta_min=cfg.min_lr\n        )\n    elif cfg.scheduler == 'ReduceLROnPlateau':\n        scheduler = lr_scheduler.ReduceLROnPlateau(\n            optimizer,\n            mode='min',\n            factor=0.5,\n            patience=2,\n            min_lr=cfg.min_lr,\n            verbose=True\n        )\n    elif cfg.scheduler == 'StepLR':\n        scheduler = lr_scheduler.StepLR(\n            optimizer,\n            step_size=cfg.epochs // 3,\n            gamma=0.5\n        )\n    elif cfg.scheduler == 'OneCycleLR':\n        scheduler = None  \n    else:\n        scheduler = None\n        \n    return scheduler\n\ndef get_criterion(cfg, train_targets=None):\n    if cfg.criterion == 'BCEWithLogitsLoss':\n        if train_targets is not None:\n            label_counts = np.sum(train_targets, axis=0)\n            total = label_counts.sum()\n            class_weights = total / (label_counts + 1e-6)\n            pos_weight = torch.tensor(class_weights, dtype=torch.float32).to(cfg.device)\n            return nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n        else:\n            return nn.BCEWithLogitsLoss()\n    elif cfg.criterion == 'FocalLoss':\n        return FocalLoss(alpha=cfg.focal_alpha, gamma=cfg.focal_gamma)\n    else:\n        raise NotImplementedError(f\"Criterion {cfg.criterion} not implemented\")\n        \nimport numpy as np\nfrom sklearn.metrics import (\n    roc_auc_score, \n    accuracy_score, \n    precision_score, \n    recall_score, \n    f1_score\n)\n\ndef filtered_accuracy(targets, preds):\n    \"\"\"\n    全てのラベルが 0 のサンプルを無視して accuracy を計算\n    \"\"\"\n    # サンプルごとに1つでもラベルがあるものを抽出\n    nonzero_mask = targets.sum(axis=1) > 0\n    if nonzero_mask.sum() == 0:\n        return 0.0  # 全部ゼロならゼロ返す\n\n    filtered_targets = targets[nonzero_mask]\n    filtered_preds = preds[nonzero_mask]\n\n    # 完全一致 accuracy\n    matches = (filtered_targets == filtered_preds).all(axis=1)\n    return matches.mean()\n\ndef calculate_auc(targets, outputs):\n    \"\"\"\n    既存のAUC計算関数を想定（マルチラベル）。\n    targets: (batch_size, num_classes) - 0/1\n    outputs: (batch_size, num_classes) - raw logits\n    \"\"\"\n    probs = 1 / (1 + np.exp(-outputs))  # sigmoid\n    aucs = []\n    for i in range(targets.shape[1]):\n        if np.sum(targets[:, i]) > 0:\n            try:\n                auc_val = roc_auc_score(targets[:, i], probs[:, i])\n                aucs.append(auc_val)\n            except ValueError:\n                pass\n    if len(aucs) == 0:\n        return 0.0\n    return np.mean(aucs)\n\nfrom sklearn.metrics import jaccard_score\ndef calculate_iou(targets, preds):\n    \"\"\"\n    targets: (N, C) 二値ラベル\n    preds:   (N, C) 二値予測\n    return: macro IoU (Jaccard)\n    \"\"\"\n    return jaccard_score(targets, preds, average='macro', zero_division=0)\n\ndef calculate_metrics(targets, outputs, threshold=0.5):\n    probs = 1 / (1 + np.exp(-outputs))\n    preds = (probs > threshold).astype(int)\n\n    auc_val = calculate_auc(targets, outputs)\n    iou_val = calculate_iou(targets, preds)\n    f1_val  = f1_score(targets, preds, average='macro', zero_division=0)\n    pre_val = precision_score(targets, preds, average='macro', zero_division=0)\n    rec_val = recall_score(targets, preds, average='macro', zero_division=0)\n\n    return {\n        'auc': auc_val,\n        'iou': iou_val,\n        'f1': f1_val,\n        'precision': pre_val,\n        'recall': rec_val\n    }\ndef calculate_classwise_metrics(targets, outputs, threshold=0.5):\n    \"\"\"\n    クラスごとの F1, Precision, Recall を配列で返す\n    targets: (batch_size, num_classes)\n    outputs: (batch_size, num_classes)\n    \"\"\"\n    probs = 1 / (1 + np.exp(-outputs))\n    preds = (probs > threshold).astype(int)\n\n    f1_c   = f1_score(targets, preds, average=None, zero_division=0)\n    pre_c  = precision_score(targets, preds, average=None, zero_division=0)\n    rec_c  = recall_score(targets, preds, average=None, zero_division=0)\n    return f1_c, pre_c, rec_c","metadata":{"_uuid":"b1512428-cb05-4b25-ae76-2befacf1db86","_cell_guid":"531e0a70-9ef6-406a-b36a-b681161cebcb","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.885577Z","iopub.execute_input":"2025-04-12T06:19:02.885821Z","iopub.status.idle":"2025-04-12T06:19:02.900297Z","shell.execute_reply.started":"2025-04-12T06:19:02.885801Z","shell.execute_reply":"2025-04-12T06:19:02.899576Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Loop","metadata":{"_uuid":"901cebad-964b-44e9-a7ee-39e2ca837bb1","_cell_guid":"b7def5d3-f6ac-47ae-8d2c-8de832eee55f","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\ndef train_one_epoch(model, loader, optimizer, criterion, device, scheduler=None):\n    model.train()\n    losses = []\n    all_outputs = []\n    all_targets = []\n\n    for batch in loader:\n        inputs = batch['melspec'].to(device)\n        targets = batch['target'].to(device)\n\n        optimizer.zero_grad()\n        # train_one_epoch() の中\n        outputs = model(inputs, targets, criterion=criterion)\n\n        if isinstance(outputs, tuple):\n            logits, loss = outputs\n        else:\n            logits = outputs\n            loss = criterion(logits, targets)\n\n        loss.backward()\n        optimizer.step()\n        losses.append(loss.item())\n        all_outputs.append(logits.detach().cpu().numpy())\n        all_targets.append(targets.detach().cpu().numpy())\n\n        # OneCycleLR ならバッチごとに step\n        if scheduler is not None and isinstance(scheduler, torch.optim.lr_scheduler.OneCycleLR):\n            scheduler.step()\n\n    # エポック終了でまとめてメトリクスを計算\n    outputs = np.concatenate(all_outputs)\n    targets = np.concatenate(all_targets)\n    \n    train_loss = np.mean(losses)\n    metrics = calculate_metrics(targets, outputs, threshold=0.5)\n    f1_c, pre_c, rec_c = calculate_classwise_metrics(targets, outputs, threshold=0.5)\n\n    # 戻り値として (loss, macroメトリクス, (クラス別F1, Precision, Recall)) を返す\n    return train_loss, metrics, (f1_c, pre_c, rec_c)\n\n\nimport numpy as np\nimport torch\nfrom sklearn.metrics import confusion_matrix\n# 必要に応じてメトリクスの可視化など\n# from sklearn.metrics import ConfusionMatrixDisplay\n# import matplotlib.pyplot as plt\n\ndef validate(model, loader, criterion, device):\n    \"\"\"\n    2値分類 or マルチクラス分類の両方に対応したバリデーション関数。\n    モデルの出力 shape に応じて混同行列を算出・表示します。\n\n    前提:\n    - 2値分類(出力次元=1)なら BCEWithLogitsLoss 等を使用している想定。\n    - マルチクラス(出力次元=C>1)なら CrossEntropyLoss 等を使用している想定。\n    - calculate_metrics, calculate_classwise_metrics はユーザー独自実装。\n\n    戻り値:\n        val_loss, metrics, (f1_c, pre_c, rec_c)\n        ※混同行列の値は印刷するだけで返していません。必要なら返す形に変更可。\n    \"\"\"\n    model.eval()\n    losses = []\n    all_outputs = []\n    all_targets = []\n\n    with torch.no_grad():\n        for batch in loader:\n            inputs = batch['melspec'].to(device)\n            targets = batch['target'].to(device)\n\n            logits = model(inputs)\n            loss = criterion(logits, targets)\n\n            losses.append(loss.item())\n            all_outputs.append(logits.detach().cpu().numpy())\n            all_targets.append(targets.detach().cpu().numpy())\n\n\n    # 最後のバッチから最後のサンプルを取り出す\n    last_outputs = all_outputs[-1]\n    last_targets = all_targets[-1]\n    \n    last_prob = 1 / (1 + np.exp(-last_outputs[-1]))  # または torch.sigmoid のままでOK\n    last_target = last_targets[-1]\n    \n    print(\"=== 最後のバッチの最後のサンプル ===\")\n    print(\"True labels :\", last_target.astype(int))\n    print(\"Probabilities (sigmoid outputs):\", np.round(last_prob, 3))\n    print(\"Predicted   :\", (last_prob > 0.5).astype(int))\n    \n    # 結合\n    outputs = np.concatenate(all_outputs, axis=0)   # shape: (N, C)  (C=1 なら2値, C>1 ならマルチクラス)\n    targets = np.concatenate(all_targets, axis=0)   # shape: (N,) or (N,C) 等\n\n    val_loss = np.mean(losses)\n\n    # ---------------------------------------\n    #  2値 or マルチクラスかを判定して混同行列\n    # ---------------------------------------\n    if outputs.shape[1] == 1:\n        # ============= 2値分類 =============\n        # sigmoid -> 0.5 閾値で二値化\n        probs = 1 / (1 + np.exp(-outputs[:, 0]))  # shape: (N,)\n        preds = (probs > 0.5).astype(int)\n\n        # ターゲットが (N,1) の可能性があるので ravel()\n        targets_flat = targets.ravel()\n\n        cm = confusion_matrix(targets_flat, preds)\n        print(\"[Confusion Matrix - Binary]\")\n        print(cm)\n        # cm: [[TN, FP],\n        #      [FN, TP]]\n\n    else:\n        # ============ マルチクラス =============\n        # argmax で予測クラス (0..C-1)\n        preds = outputs.argmax(axis=1)\n\n        # ターゲットがワンホットの場合 (N, C)\n        # → argmax で整数クラスに変換\n        if targets.ndim == 2 and targets.shape[1] > 1:\n            targets_flat = targets.argmax(axis=1)\n        else:\n            targets_flat = targets.ravel()\n\n        # cm = confusion_matrix(targets_flat, preds)\n        # print(\"[Confusion Matrix - Multi-class]\")\n        # print(cm)\n        # cm: shape: (C, C)\n\n    # ---------------------------------------\n    #  ここから先はご自身のメトリクス計算を呼び出す部分\n    # ---------------------------------------\n    # ユーザ実装のメトリクス関数 (2値/マルチクラス兼用の場合、分岐が必要かも)\n    # ※ calculate_metrics, calculate_classwise_metrics は外部定義を想定\n    #   2値の場合 => sigmoid して threshold=0.5\n    #   マルチクラスの場合 => argmax 同士の比較など\n    #   といった分岐を内部でやっているならOKです\n    metrics = calculate_metrics(targets, outputs, threshold=0.5)\n    f1_c, pre_c, rec_c = calculate_classwise_metrics(targets, outputs, threshold=0.5)\n\n    # ---------------------------------------\n    # 必要に応じて、可視化などをする場合\n    # ---------------------------------------\n    # from sklearn.metrics import ConfusionMatrixDisplay\n    # disp = ConfusionMatrixDisplay(confusion_matrix=cm)\n    # disp.plot(values_format='d', cmap='Blues')\n    # plt.title(\"Validation Confusion Matrix\")\n    # plt.show()\n\n    return val_loss, metrics, (f1_c, pre_c, rec_c)","metadata":{"_uuid":"afa66553-89da-4da5-b4be-d43f8c4500a9","_cell_guid":"d96973ef-b240-48a2-8b36-b70c00adf538","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.901114Z","iopub.execute_input":"2025-04-12T06:19:02.901383Z","iopub.status.idle":"2025-04-12T06:19:02.919519Z","shell.execute_reply.started":"2025-04-12T06:19:02.901350Z","shell.execute_reply":"2025-04-12T06:19:02.918676Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training!","metadata":{"_uuid":"f0f5baf0-8553-4618-b3ec-2b93792ddadb","_cell_guid":"390240ac-22c0-4f7d-b532-f15b96077a24","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"import torch\nimport numpy as np\nimport pandas as pd\nimport gc\nimport time\nfrom pathlib import Path\nfrom torch.utils.data import DataLoader\n\ndef run_training(df, cfg):\n    \"\"\"Training function that uses predefined fold assignments in df[\"fold\"]. \n       Now includes detailed metrics each epoch.\n    \"\"\"\n\n    # クラス情報セットアップ\n    coarse_labels = df[cfg.label_column].dropna().unique().tolist()\n    coarse_labels.sort()\n    cfg.coarse_labels = coarse_labels\n    cfg.num_classes = len(coarse_labels)\n\n    if cfg.debug:\n        cfg.update_debug_settings()\n\n    # スペクトログラムのロード (LOAD_DATA = Trueの場合)\n    spectrograms = None\n    if cfg.LOAD_DATA:\n        print(\"Loading pre-computed mel spectrograms from NPY file...\")\n        try:\n            spectrograms = np.load(cfg.spectrogram_npy, allow_pickle=True).item()\n            print(f\"Loaded {len(spectrograms)} pre-computed mel spectrograms\")\n        except Exception as e:\n            print(f\"Error loading pre-computed spectrograms: {e}\")\n            print(\"Will generate spectrograms on-the-fly instead.\")\n            cfg.LOAD_DATA = False\n\n    # On-the-flyの場合はfilepath, samplenameを設定\n    if not cfg.LOAD_DATA:\n        print(\"Will generate spectrograms on-the-fly during training.\")\n        if 'filepath' not in df.columns:\n            df['filepath'] = cfg.train_datadir + '/' + df.filename\n        if 'samplename' not in df.columns:\n            df['samplename'] = df.filename.map(lambda x: x.split('/')[0] + '-' + x.split('/')[-1].split('.')[0])\n\n    best_scores = []\n    best_epochs = []\n\n    for fold in cfg.selected_folds:\n        print(f'\\n{\"=\"*30} Fold {fold} {\"=\"*30}')\n\n        # Fold分割\n        train_df = df[df['fold'] != fold].reset_index(drop=True)\n        val_df = df[df['fold'] == fold].reset_index(drop=True)\n\n        if cfg.debug:\n            train_df = train_df.sample(n=min(200, len(train_df)), random_state=cfg.seed).reset_index(drop=True)\n            val_df = val_df.sample(n=min(50, len(val_df)), random_state=cfg.seed).reset_index(drop=True)\n        print(f'Training set: {len(train_df)} samples')\n        print(f'Validation set: {len(val_df)} samples')\n\n        # Dataset & Dataloader\n        train_dataset = BirdCLEFDatasetFromNPY(train_df, cfg, spectrograms, mode=\"train\")\n        val_dataset   = BirdCLEFDatasetFromNPY(val_df, cfg, spectrograms, mode=\"valid\")\n        \n\n        train_loader = DataLoader(\n            train_dataset,\n            batch_size=cfg.batch_size,\n            shuffle=True,\n            num_workers=cfg.num_workers,\n            pin_memory=True,\n            collate_fn=collate_fn,\n            drop_last=True\n        )\n\n        val_loader = DataLoader(\n            val_dataset,\n            batch_size=cfg.batch_size,\n            shuffle=True,\n            num_workers=cfg.num_workers,\n            pin_memory=True,\n            collate_fn=collate_fn\n        )\n\n        # モデル、最適化、スケジューラ等のセットアップ\n\n        train_targets = np.array([train_dataset.encode_label(row) for _, row in train_df.iterrows()])\n        criterion = get_criterion(cfg, train_targets=train_targets)\n        model = BirdCLEFModel(cfg).to(cfg.device)\n        optimizer = get_optimizer(model, cfg)\n        criterion = get_criterion(cfg)\n\n        if cfg.scheduler == 'OneCycleLR':\n            scheduler = torch.optim.lr_scheduler.OneCycleLR(\n                optimizer,\n                max_lr=cfg.lr,\n                steps_per_epoch=len(train_loader),\n                epochs=cfg.epochs,\n                pct_start=0.1\n            )\n        else:\n            scheduler = get_scheduler(optimizer, cfg)\n\n        best_auc = 0\n        best_epoch = 0\n\n        # === エポックループ ===\n        for epoch in range(cfg.epochs):\n            print(f\"\\nEpoch {epoch+1}/{cfg.epochs}\")\n\n            # -------- Train --------\n            train_loss, train_metrics, train_classwise = train_one_epoch(\n                model, train_loader, optimizer, criterion, cfg.device,\n                scheduler if isinstance(scheduler, torch.optim.lr_scheduler.OneCycleLR) else None\n            )\n            # train_metrics は {'auc', 'accuracy', 'f1', 'precision', 'recall'}\n            # train_classwise は (f1_array, prec_array, rec_array)\n\n            # -------- Validation --------\n            val_loss, val_metrics, val_classwise = validate(\n                model, val_loader, criterion, cfg.device\n            )\n            # val_metrics, val_classwise も上記同様\n\n            # OneCycleLRでない場合はここでstep\n            if scheduler is not None and not isinstance(scheduler, torch.optim.lr_scheduler.OneCycleLR):\n                if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau):\n                    scheduler.step(val_loss)\n                else:\n                    scheduler.step()\n\n            # -------- ログ表示 --------\n            # トレーニングの macro AUC とその他指標\n            print(\n                f\"Train Loss: {train_loss:.4f} | \"\n                f\"AUC: {train_metrics['auc']:.4f} | \"\n                f\"IoU: {train_metrics['iou']:.4f} | \"\n                f\"F1: {train_metrics['f1']:.4f}\"\n            )\n            print(\n                f\"Valid Loss: {val_loss:.4f} | \"\n                f\"AUC: {val_metrics['auc']:.4f} | \"\n                f\"IoU: {val_metrics['iou']:.4f} | \"\n                f\"F1: {val_metrics['f1']:.4f}\"\n            )\n            # AUCベースでベストを更新したらモデル保存\n            if val_metrics['auc'] > best_auc:\n                best_auc = val_metrics['auc']\n                best_epoch = epoch + 1\n                print(f\"New best AUC: {best_auc:.4f} at epoch {best_epoch}\")\n\n                torch.save({\n                    'model_state_dict': model.state_dict(),\n                    'optimizer_state_dict': optimizer.state_dict(),\n                    'scheduler_state_dict': scheduler.state_dict() if scheduler else None,\n                    'epoch': epoch,\n                    'val_auc': val_metrics['auc'],\n                    'train_auc': train_metrics['auc'],\n                    'cfg': cfg\n                }, f\"model_fold{fold}.pth\")\n\n        best_scores.append(best_auc)\n        best_epochs.append(best_epoch)\n        print(f\"\\nBest AUC for fold {fold}: {best_auc:.4f} at epoch {best_epoch}\")\n\n        # 後片付け\n        del model, optimizer, scheduler, train_loader, val_loader\n        torch.cuda.empty_cache()\n        gc.collect()\n\n    # ===== クロスバリデーションの結果出力 =====\n    print(\"\\n\" + \"=\"*60)\n    print(\"Cross-Validation Results:\")\n    for i, score in enumerate(best_scores):\n        print(f\"Fold {cfg.selected_folds[i]}: {score:.4f}\")\n    print(f\"Mean AUC: {np.mean(best_scores):.4f}\")\n    print(\"=\"*60)\n\n    # ===== 実験結果をExcel保存 =====\n    experiment_result = {\n        'experiment_name': 'exp_' + time.strftime('%Y%m%d_%H%M%S'),\n        'model': cfg.model_name,\n        'folds': str(cfg.selected_folds),\n        'epochs': cfg.epochs,\n        'batch_size': cfg.batch_size,\n        'optimizer': cfg.optimizer,\n        'lr': cfg.lr,\n        'scheduler': cfg.scheduler,\n        'mixup_alpha': cfg.mixup_alpha,\n        'val_auc_mean': np.mean(best_scores),\n        'val_auc_per_fold': str(best_scores),\n        'best_epoch_per_fold': str(best_epochs),\n        'debug': cfg.debug,\n        'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')\n    }\n\n    result_path = Path(cfg.OUTPUT_DIR) / 'experiment_results.xlsx'\n\n    if result_path.exists():\n        existing = pd.read_excel(result_path)\n        updated = pd.concat([existing, pd.DataFrame([experiment_result])], ignore_index=True)\n    else:\n        updated = pd.DataFrame([experiment_result])\n\n    updated.to_excel(result_path, index=False)\n    print(f\"\\n✔️ Experiment result saved to {result_path}\")","metadata":{"_uuid":"0d7e4b9b-e5a8-4f47-81f6-90792dd7d69a","_cell_guid":"2fff1ea4-57e4-4cc5-8e9c-d061ca9d282d","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.920456Z","iopub.execute_input":"2025-04-12T06:19:02.920640Z","iopub.status.idle":"2025-04-12T06:19:02.937948Z","shell.execute_reply.started":"2025-04-12T06:19:02.920624Z","shell.execute_reply":"2025-04-12T06:19:02.937081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Main Execution ===\nif __name__ == '__main__':\n    import time\n\n    print(\"\\nLoading training data...\")\n    train_df = pd.read_csv(\"/kaggle/input/ami-kaggle-birdclef2025-traindata0406/train_folds.csv\")\n    taxonomy_df = pd.read_csv(cfg.taxonomy_csv)\n\n    print(\"\\nStarting training...\")\n    print(f\"LOAD_DATA is set to {cfg.LOAD_DATA}\")\n    if cfg.LOAD_DATA:\n        print(\"Using pre-computed mel spectrograms from NPY file\")\n    else:\n        print(\"Will generate spectrograms on-the-fly during training\")\n\n    run_training(train_df, cfg)\n\n    print(\"\\nTraining complete!\")","metadata":{"_uuid":"86dc9724-beda-464e-aa3e-3110f94a51ed","_cell_guid":"77284fd4-8fc6-4040-a469-dcd59eba2840","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-04-12T06:19:02.938682Z","iopub.execute_input":"2025-04-12T06:19:02.938877Z","iopub.status.idle":"2025-04-12T06:20:29.852612Z","shell.execute_reply.started":"2025-04-12T06:19:02.938862Z","shell.execute_reply":"2025-04-12T06:20:29.851615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"17ca057f-7e2e-4ad9-b0f4-28eb028ef4b9","_cell_guid":"aaadd439-3a5a-4457-aab4-79de21653e78","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"2977fd3a-c13a-4664-8cb0-89720df927b5","_cell_guid":"4d66212a-a24b-463a-9ccc-adcedf21e8a4","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null}]}