{"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,"isSourceIdPinned":false,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport gc\nimport time\nimport random\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split # K-Fold yerine bunu kullanacağız\nfrom sklearn.preprocessing import MultiLabelBinarizer\nfrom sklearn.metrics import accuracy_score, f1_score\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchaudio\nimport torchaudio.transforms as AT\nimport torchaudio.functional as AF\nimport timm\nimport torch.nn.functional as F\nfrom tqdm.auto import tqdm\n\n# --- KONFİGÜRASYON (EfficientNetV2_M Tekli Split için Ayarlandı) ---\nTRAIN_AUDIO_DIR = '/kaggle/input/birdclef-2025/train_audio'\nMODEL_OUTPUT_DIR = '/kaggle/working/trained_effnetv2_m_single_split_models/'\nos.makedirs(MODEL_OUTPUT_DIR, exist_ok=True)\n\nCONFIG_EFFNET_M_SINGLE = {\n    \"seed\": 42,\n    \"sample_rate\": 32000,\n    \"duration_secs\": 5,\n    \"n_mels\": 128,\n    \"f_min\": 50,\n    \"f_max\": 16000,\n    \"n_fft\": 1024,\n    \"win_length\": 1024,\n    \"hop_length\": 512,\n    # --- EfficientNetV2_M MODEL İÇİN AYARLAR ---\n    \"base_model_name\": 'tf_efficientnetv2_m', # <<< MODEL ADI GÜNCELLENDİ\n    \"effnet_in_chans\": 3,\n    # --- --- ---\n    \"num_epochs\": 30, # Senin isteğin üzerine\n    \"batch_size\": 16,  # <<< _m için batch boyutunu DÜŞÜK BAŞLA (VRAM'e göre ayarla: 8, 16)\n    \"learning_rate\": 5e-4, # _m için biraz daha düşük LR (örn: 1e-3, 5e-4, 1e-4)\n    \"optimizer_eps\": 1e-7,\n    \"weight_decay\": 1e-5,\n    \"patience\": 7,\n    \"valid_split_ratio\": 0.15, # <<< %15 Validasyon ayrımı\n    \"threshold\": 0.5,\n    # Veri Artırma (Aynı kalabilir veya ayarlanabilir)\n    \"augment_prob\": 0.75,\n    \"gain_min_db\": -8.0,  \n    \"gain_max_db\": 8.0,\n    \"max_time_shift_ratio\": 0.15,\n    \"spec_augment_prob\": 0.6,\n    \"freq_mask_param\": int(128 * 0.15),\n    \"time_mask_param\": int(313 * 0.15),\n    \"num_freq_masks\": 2,\n    \"num_time_masks\": 2,\n    \"timm_model_drop_rate\": 0.3, # tf_efficientnetv2_m için varsayılan dropout (timm'den kontrol et)\n}\n\nCONFIG_EFFNET_M_SINGLE[\"in_chans_spectrogram\"] = 1\nCONFIG_EFFNET_M_SINGLE[\"num_frames_in_segment\"] = (CONFIG_EFFNET_M_SINGLE[\"duration_secs\"] * CONFIG_EFFNET_M_SINGLE[\"sample_rate\"]) // CONFIG_EFFNET_M_SINGLE[\"hop_length\"] + 1\nCONFIG_EFFNET_M_SINGLE[\"max_time_shift_samples\"] = int(CONFIG_EFFNET_M_SINGLE[\"duration_secs\"] * CONFIG_EFFNET_M_SINGLE[\"sample_rate\"] * CONFIG_EFFNET_M_SINGLE[\"max_time_shift_ratio\"])\n\ndef set_seed(seed=42):\n    random.seed(seed); os.environ[\"PYTHONHASHSEED\"] = str(seed); np.random.seed(seed)\n    torch.manual_seed(seed); torch.cuda.manual_seed(seed); torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True; torch.backends.cudnn.benchmark = False\n\nset_seed(CONFIG_EFFNET_M_SINGLE[\"seed\"])\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}. UYARI: Eğer CPU ise eğitim ÇOK ÇOK ÇOK UZUN sürecektir!\")\n\ndef normalize_std(spec, eps=1e-6):\n    mean = torch.mean(spec, dim=(-1, -2), keepdim=True)\n    std = torch.std(spec, dim=(-1, -2), keepdim=True)\n    return torch.where(std < eps, spec - mean, (spec - mean) / (std + eps))\n\nif not os.path.exists(TRAIN_AUDIO_DIR):\n    print(f\"HATA: Eğitim verisi dizini bulunamadı: {TRAIN_AUDIO_DIR}\"); exit()\nall_labels_list = sorted([d for d in os.listdir(TRAIN_AUDIO_DIR) if os.path.isdir(os.path.join(TRAIN_AUDIO_DIR, d))])\nCONFIG_EFFNET_M_SINGLE[\"num_classes\"] = len(all_labels_list)\nlabel_to_int_map = {label: i for i, label in enumerate(all_labels_list)}\nprint(f\"Bulunan sınıf sayısı: {CONFIG_EFFNET_M_SINGLE['num_classes']}\")\n\nfilepaths_all = []\nlabels_for_stratify_all = []\nfor bird_label in all_labels_list:\n    bird_dir = os.path.join(TRAIN_AUDIO_DIR, bird_label)\n    for filename in os.listdir(bird_dir):\n        if filename.lower().endswith(\".ogg\"):\n            filepaths_all.append(os.path.join(bird_dir, filename))\n            labels_for_stratify_all.append(label_to_int_map[bird_label])\n\nmultilabel_binarizer_global = MultiLabelBinarizer(classes=list(range(CONFIG_EFFNET_M_SINGLE[\"num_classes\"])))\n_dummy_labels_for_mlb_fit = [[l] for l in list(range(CONFIG_EFFNET_M_SINGLE[\"num_classes\"]))]\nmultilabel_binarizer_global.fit(_dummy_labels_for_mlb_fit)\n\n# <<< DEĞİŞİKLİK: K-Fold yerine train_test_split kullanılıyor >>>\ntrain_filepaths, valid_filepaths, _, _ = train_test_split(\n    filepaths_all, labels_for_stratify_all,\n    test_size=CONFIG_EFFNET_M_SINGLE[\"valid_split_ratio\"],\n    random_state=CONFIG_EFFNET_M_SINGLE[\"seed\"],\n    stratify=labels_for_stratify_all\n)\nprint(f\"Eğitim seti boyutu: {len(train_filepaths)}, Validasyon seti boyutu: {len(valid_filepaths)}\")\n\n# --- BirdSoundDataset (Değişiklik yok) ---\nclass BirdSoundDataset(Dataset):\n    def __init__(self, filepaths, config, label_to_int_map, multilabel_binarizer_instance, is_train=True):\n        self.filepaths = filepaths; self.config = config\n        self.mel_transform = AT.MelSpectrogram(\n            sample_rate=config[\"sample_rate\"], n_fft=config[\"n_fft\"], win_length=config[\"win_length\"],\n            hop_length=config[\"hop_length\"], center=True, f_min=config[\"f_min\"], f_max=config[\"f_max\"],\n            pad_mode=\"reflect\", power=2.0, norm='slaney', n_mels=config[\"n_mels\"], mel_scale=\"htk\")\n        self.sr = config[\"sample_rate\"]; self.duration_samples = config[\"sample_rate\"] * config[\"duration_secs\"]\n        self.num_classes = config[\"num_classes\"]; self.label_to_int_map = label_to_int_map\n        self.mlb = multilabel_binarizer_instance; self.is_train = is_train\n        if self.is_train:\n            self.frequency_masking = AT.FrequencyMasking(freq_mask_param=config[\"freq_mask_param\"])\n            self.time_masking = AT.TimeMasking(time_mask_param=config[\"time_mask_param\"])\n    def __len__(self): return len(self.filepaths)\n    def apply_wav_augmentations(self, waveform):\n        if random.random() < self.config[\"augment_prob\"]:\n            if random.random() < 0.5:\n                gain_db = random.uniform(self.config[\"gain_min_db\"], self.config[\"gain_max_db\"])\n                waveform = AF.gain(waveform, gain_db)\n            if random.random() < 0.5:\n                shift_samples = random.randint(-self.config[\"max_time_shift_samples\"], self.config[\"max_time_shift_samples\"])\n                if shift_samples != 0: waveform = torch.roll(waveform, shifts=shift_samples, dims=1)\n        return waveform\n    def apply_spec_augmentations(self, melspec):\n        if random.random() < self.config[\"spec_augment_prob\"]:\n            for _ in range(self.config[\"num_freq_masks\"]): melspec = self.frequency_masking(melspec)\n            for _ in range(self.config[\"num_time_masks\"]): melspec = self.time_masking(melspec)\n        return melspec\n    def __getitem__(self, idx):\n        filepath = self.filepaths[idx]\n        try:\n            waveform, sr_loaded = torchaudio.load(filepath, backend=\"soundfile\")\n        except Exception as e:\n            print(f\"HATA ({filepath} yüklenirken): {e}\")\n            dummy_mel = torch.zeros((self.config[\"in_chans_spectrogram\"], self.config[\"n_mels\"], self.config[\"num_frames_in_segment\"]))\n            dummy_labels = torch.zeros(self.num_classes, dtype=torch.float); return dummy_mel, dummy_labels\n        if sr_loaded != self.sr: waveform = AT.Resample(orig_freq=sr_loaded, new_freq=self.sr)(waveform)\n        if waveform.shape[0] > 1: waveform = torch.mean(waveform, dim=0, keepdim=True)\n        if self.is_train: waveform = self.apply_wav_augmentations(waveform)\n        current_samples = waveform.shape[1]\n        if current_samples > self.duration_samples:\n            start = random.randint(0, current_samples - self.duration_samples) if self.is_train else (current_samples - self.duration_samples) // 2\n            waveform = waveform[:, start : start + self.duration_samples]\n        elif current_samples < self.duration_samples:\n            waveform = F.pad(waveform, (0, self.duration_samples - current_samples))\n        melspec = self.mel_transform(waveform)\n        if self.is_train: melspec = self.apply_spec_augmentations(melspec)\n        melspec = torch.log(melspec + 1e-6); melspec = normalize_std(melspec)    \n        primary_label_str = os.path.basename(os.path.dirname(filepath))\n        primary_label_int = self.label_to_int_map[primary_label_str]\n        labels_encoded = self.mlb.transform([[primary_label_int]])[0]\n        labels_encoded = torch.tensor(labels_encoded, dtype=torch.float)\n        return melspec, labels_encoded\n\n# DataLoader'lar (K-Fold'suz, doğrudan train_filepaths ve valid_filepaths ile)\ntrain_dataset = BirdSoundDataset(train_filepaths, CONFIG_EFFNET_M_SINGLE, label_to_int_map, multilabel_binarizer_global, is_train=True)\nvalid_dataset = BirdSoundDataset(valid_filepaths, CONFIG_EFFNET_M_SINGLE, label_to_int_map, multilabel_binarizer_global, is_train=False)\nnum_workers = max(1, os.cpu_count() // 2 if os.cpu_count() is not None else 1)\ntrain_loader = DataLoader(train_dataset, batch_size=CONFIG_EFFNET_M_SINGLE[\"batch_size\"], shuffle=True, num_workers=num_workers, pin_memory=True, drop_last=True)\nvalid_loader = DataLoader(valid_dataset, batch_size=CONFIG_EFFNET_M_SINGLE[\"batch_size\"], shuffle=False, num_workers=num_workers, pin_memory=True)\n\n# --- MODEL TANIMI: TimmEffNetModel (Değişiklik yok) ---\nclass TimmEffNetModel(nn.Module):\n    def __init__(self, base_model_name: str, pretrained=True, num_classes=206,\n                 in_chans_spectrogram=1, effnet_in_chans=3, model_drop_rate=0.2):\n        super().__init__()\n        self.in_chans_spectrogram = in_chans_spectrogram; self.effnet_in_chans = effnet_in_chans\n        self.encoder = timm.create_model(\n            base_model_name, pretrained=pretrained, num_classes=num_classes,\n            in_chans=self.effnet_in_chans, drop_rate=model_drop_rate)\n        print(f\"EfficientNetV2 Modeli {base_model_name} yüklendi. Giriş kanalı (encoder): {self.encoder.conv_stem.in_channels if hasattr(self.encoder, 'conv_stem') else 'N/A'}, Çıkış sınıfı: {num_classes}\")\n    def forward(self, x):\n        if self.effnet_in_chans == 3 and x.size(1) == 1: x = x.repeat(1, 3, 1, 1)\n        elif self.effnet_in_chans == 1 and x.size(1) == 3: x = torch.mean(x, dim=1, keepdim=True)\n        elif hasattr(self.encoder, 'conv_stem') and self.encoder.conv_stem.in_channels != x.size(1):\n             raise ValueError(f\"Model {self.encoder.conv_stem.in_channels} giriş kanalı bekliyor, ancak {x.size(1)} alındı!\")\n        logits = self.encoder(x); return {\"logit\": logits}\n\n# --- MODEL, OPTİMİZATÖR, ZAMANLAYICI, KAYIP FONKSİYONU (Tek Seferlik Tanımlama) ---\nprint(f\"EfficientNetV2_M modeli ({CONFIG_EFFNET_M_SINGLE['base_model_name']}) oluşturuluyor...\")\nmodel = TimmEffNetModel(\n    base_model_name=CONFIG_EFFNET_M_SINGLE[\"base_model_name\"], pretrained=True,\n    num_classes=CONFIG_EFFNET_M_SINGLE[\"num_classes\"],\n    in_chans_spectrogram=CONFIG_EFFNET_M_SINGLE[\"in_chans_spectrogram\"],\n    effnet_in_chans=CONFIG_EFFNET_M_SINGLE[\"effnet_in_chans\"],\n    model_drop_rate=CONFIG_EFFNET_M_SINGLE[\"timm_model_drop_rate\"])\nmodel.to(device)\n\noptimizer = optim.AdamW(model.parameters(), lr=CONFIG_EFFNET_M_SINGLE[\"learning_rate\"], weight_decay=CONFIG_EFFNET_M_SINGLE[\"weight_decay\"])\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=CONFIG_EFFNET_M_SINGLE[\"num_epochs\"], eta_min=1e-7)\ncriterion = nn.BCEWithLogitsLoss()\n\n# --- EĞİTİM VE VALİDASYON DÖNGÜSÜ (Tekli, K-Fold'suz) ---\nbest_val_loss = float('inf')\nepochs_no_improve = 0\nsaved_model_path = None # En iyi modelin yolunu tutacak\n\nfor epoch in range(1, CONFIG_EFFNET_M_SINGLE[\"num_epochs\"] + 1):\n    start_time_epoch = time.time(); model.train(); train_loss_epoch = 0\n    all_train_preds_epoch, all_train_labels_epoch = [], []\n    progress_bar_train = tqdm(train_loader, desc=f\"Epoch {epoch}/{CONFIG_EFFNET_M_SINGLE['num_epochs']} [Train]\", unit=\"B\", leave=True) # leave=True\n    for batch_idx, (inputs, labels) in enumerate(progress_bar_train):\n        inputs, labels = inputs.to(device), labels.to(device); optimizer.zero_grad()\n        outputs = model(inputs); logits = outputs['logit']\n        loss = criterion(logits, labels); loss.backward(); optimizer.step()\n        train_loss_epoch += loss.item()\n        preds = torch.sigmoid(logits) > CONFIG_EFFNET_M_SINGLE[\"threshold\"]\n        all_train_preds_epoch.extend(preds.cpu().numpy()); all_train_labels_epoch.extend(labels.cpu().numpy())\n        if batch_idx > 0 and batch_idx % 75 == 0: progress_bar_train.set_postfix(loss=train_loss_epoch / (batch_idx+1))\n   \n    train_loss_epoch /= len(train_loader) if len(train_loader) > 0 else 1\n    train_accuracy_epoch = accuracy_score(np.array(all_train_labels_epoch), np.array(all_train_preds_epoch))\n    train_f1_epoch = f1_score(np.array(all_train_labels_epoch), np.array(all_train_preds_epoch), average='macro', zero_division=0)\n\n    model.eval(); val_loss_epoch = 0\n    all_val_preds_epoch, all_val_labels_epoch = [], []\n    progress_bar_val = tqdm(valid_loader, desc=f\"Epoch {epoch}/{CONFIG_EFFNET_M_SINGLE['num_epochs']} [Valid]\", unit=\"B\", leave=True) # leave=True\n    with torch.no_grad():\n        for inputs, labels in progress_bar_val:\n            inputs, labels = inputs.to(device), labels.to(device)\n            outputs = model(inputs); logits = outputs['logit']; loss = criterion(logits, labels)\n            val_loss_epoch += loss.item()\n            preds = torch.sigmoid(logits) > CONFIG_EFFNET_M_SINGLE[\"threshold\"]\n            all_val_preds_epoch.extend(preds.cpu().numpy()); all_val_labels_epoch.extend(labels.cpu().numpy())\n   \n    val_loss_epoch /= len(valid_loader) if len(valid_loader) > 0 else 1\n    val_accuracy_epoch = accuracy_score(np.array(all_val_labels_epoch), np.array(all_val_preds_epoch)) if len(all_val_labels_epoch) > 0 else 0.0\n    val_f1_epoch = f1_score(np.array(all_val_labels_epoch), np.array(all_val_preds_epoch), average='macro', zero_division=0) if len(all_val_labels_epoch) > 0 else 0.0\n   \n    scheduler.step()\n    epoch_duration = time.time() - start_time_epoch\n    print(f\"Epoch {epoch}/{CONFIG_EFFNET_M_SINGLE['num_epochs']} ({epoch_duration:.0f}s) \"\n          f\"TrL: {train_loss_epoch:.4f} TrA: {train_accuracy_epoch:.4f} TrF1: {train_f1_epoch:.4f} | \"\n          f\"VaL: {val_loss_epoch:.4f} VaA: {val_accuracy_epoch:.4f} VaF1: {val_f1_epoch:.4f} | \"\n          f\"LR: {optimizer.param_groups[0]['lr']:.1e}\")\n\n    if val_loss_epoch < best_val_loss:\n        best_val_loss = val_loss_epoch; epochs_no_improve = 0\n        # Dosya adından _foldX kısmını kaldırıyoruz\n        current_model_save_path = os.path.join(MODEL_OUTPUT_DIR, f\"{CONFIG_EFFNET_M_SINGLE['base_model_name']}_ep{epoch}_vl{val_loss_epoch:.4f}.pth\")\n        torch.save(model.state_dict(), current_model_save_path);\n        saved_model_path = current_model_save_path # En son kaydedilen genel en iyi modelin yolu\n        print(f\"Model kaydedildi: {saved_model_path} (En iyi val_loss: {val_loss_epoch:.4f})\")\n    else:\n        epochs_no_improve += 1\n        if epochs_no_improve >= CONFIG_EFFNET_M_SINGLE[\"patience\"]:\n            print(f\"Erken durdurma: Validasyon kaybı {CONFIG_EFFNET_M_SINGLE['patience']} epoch boyunca iyileşmedi.\")\n            break\n    gc.collect(); torch.cuda.empty_cache()\n\nprint(f\"\\nEğitim tamamlandı. En iyi validasyon kaybı: {best_val_loss:.4f}\")\nif saved_model_path and os.path.exists(saved_model_path):\n    final_model_path = os.path.join(MODEL_OUTPUT_DIR, f\"{CONFIG_EFFNET_M_SINGLE['base_model_name']}_best_overall_model.pth\")\n    import shutil; shutil.copyfile(saved_model_path, final_model_path)\n    print(f\"En iyi model ayrıca şuraya kopyalandı/kaydedildi: {final_model_path}\")\nelse: print(\"Hiçbir model (erken durdurma kriterlerini karşılayacak kadar iyileşen) kaydedilmedi.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-20T10:15:09.601729Z","iopub.execute_input":"2025-05-20T10:15:09.602071Z","iopub.status.idle":"2025-05-20T10:15:40.806713Z","shell.execute_reply.started":"2025-05-20T10:15:09.602042Z","shell.execute_reply":"2025-05-20T10:15:40.805081Z"}},"outputs":[],"execution_count":null}]}