{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"gpu","dataSources":[{"sourceId":99895,"databundleVersionId":11971489,"sourceType":"competition"},{"sourceId":238195865,"sourceType":"kernelVersion"}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install --no-build-isolation -q audiomentations","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T10:38:53.366749Z","iopub.execute_input":"2025-05-06T10:38:53.367454Z","iopub.status.idle":"2025-05-06T10:38:58.843991Z","shell.execute_reply.started":"2025-05-06T10:38:53.367428Z","shell.execute_reply":"2025-05-06T10:38:58.843233Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, numpy as np, pandas as pd, librosa, torch, torch.nn as nn, torch.optim as optim\nimport torchaudio.transforms as T\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.metrics import f1_score\nfrom sklearn.utils.class_weight import compute_class_weight\n\n# ============ Настройки ============\nSEED = 42\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\ntorch.cuda.manual_seed(SEED)\ntorch.backends.cudnn.deterministic = True\n\nAUDIO_DIR = \"/kaggle/input/itmo-acoustic-event-detectin-2025/audio_train/train\"\nTEST_DIR = \"/kaggle/input/itmo-acoustic-event-detectin-2025/audio_test/test\"\nCSV_PATH = \"/kaggle/input/itmo-acoustic-event-detectin-2025/train.csv\"\nSAMPLE_RATE, N_MELS, DURATION = 11025, 64, 2\nMAX_LEN = SAMPLE_RATE * DURATION\nBATCH_SIZE = 64\nEPOCHS = 200\nPATIENCE = 10\n\n# ============ Загрузка и кодировка ============\ndf = pd.read_csv(CSV_PATH)\nle = LabelEncoder()\ndf[\"label_idx\"] = le.fit_transform(df[\"label\"])\nNUM_CLASSES = len(le.classes_)\n\nclass_weights = compute_class_weight('balanced', classes=np.unique(df[\"label_idx\"]), y=df[\"label_idx\"])\nclass_weights = torch.tensor(class_weights, dtype=torch.float)\n\n# ============ Аугментации вручную ============\ndef apply_augmentations(y, sr):\n    if np.random.rand() < 0.5:\n        noise = np.random.normal(0, 0.005, y.shape)\n        y += noise\n    if np.random.rand() < 0.3:\n        rate = np.random.uniform(0.8, 1.2)\n        try:\n            y = librosa.effects.time_stretch(y, rate)\n        except:\n            pass\n    if np.random.rand() < 0.4:\n        steps = np.random.randint(-2, 3)\n        y = librosa.effects.pitch_shift(y, sr=sr, n_steps=steps)  # ← исправление\n    if np.random.rand() < 0.3:\n        shift = int(np.random.uniform(-0.2, 0.2) * len(y))\n        y = np.roll(y, shift)\n    return y\n\n\nTIME_MASK = T.TimeMasking(time_mask_param=15)\nFREQ_MASK = T.FrequencyMasking(freq_mask_param=8)\n\n# ============ Dataset ============\nclass AudioDataset(Dataset):\n    def __init__(self, df, augment=False, path=AUDIO_DIR):\n        self.df = df.reset_index(drop=True)\n        self.augment = augment\n        self.path = path\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        y, sr = librosa.load(os.path.join(self.path, row[\"fname\"]), sr=SAMPLE_RATE)\n        if len(y) < MAX_LEN:\n            y = np.pad(y, (0, MAX_LEN - len(y)))\n        else:\n            y = y[:MAX_LEN]\n        if self.augment:\n            y = apply_augmentations(y, sr)\n\n        mel = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=N_MELS)\n        log_mel = librosa.power_to_db(mel)\n        log_mel = torch.tensor(log_mel).unsqueeze(0)\n\n        if self.augment:\n            log_mel = TIME_MASK(log_mel)\n            log_mel = FREQ_MASK(log_mel)\n\n        return log_mel.float(), row[\"label_idx\"]\n\n    def __len__(self): return len(self.df)\n\nclass TestDataset(Dataset):\n    def __init__(self, files, path=TEST_DIR):\n        self.files = sorted(files)\n        self.path = path\n\n    def __getitem__(self, idx):\n        y, sr = librosa.load(os.path.join(self.path, self.files[idx]), sr=SAMPLE_RATE)\n        if len(y) < MAX_LEN: y = np.pad(y, (0, MAX_LEN - len(y)))\n        else: y = y[:MAX_LEN]\n        mel = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=N_MELS)\n        log_mel = librosa.power_to_db(mel)\n        return torch.tensor(log_mel).unsqueeze(0).float(), self.files[idx]\n\n    def __len__(self): return len(self.files)\n\n# ============ Mixup ============\ndef mixup(x, y, alpha=0.4):\n    lam = np.random.beta(alpha, alpha)\n    index = torch.randperm(x.size(0))\n    return lam * x + (1 - lam) * x[index], y, y[index], lam\n\n# ============ Label Smoothing ============\nclass LabelSmoothingLoss(nn.Module):\n    def __init__(self, classes, smoothing=0.1):\n        super().__init__()\n        self.confidence = 1.0 - smoothing\n        self.smoothing = smoothing\n        self.cls = classes\n\n    def forward(self, x, target):\n        logprobs = nn.functional.log_softmax(x, dim=-1)\n        true_dist = torch.zeros_like(logprobs).fill_(self.smoothing / (self.cls - 1))\n        true_dist.scatter_(1, target.unsqueeze(1), self.confidence)\n        return torch.mean(torch.sum(-true_dist * logprobs, dim=-1))\n\n# ============ Модель ============\nclass CNNClassifier(nn.Module):\n    def __init__(self, num_classes):\n        super().__init__()\n        self.net = nn.Sequential(\n            nn.Conv2d(1, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2),\n            nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2),\n            nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), nn.AdaptiveMaxPool2d((4, 4)),\n            nn.Flatten(), nn.Dropout(0.3),\n            nn.Linear(256 * 4 * 4, 256), nn.ReLU(),\n            nn.Linear(256, num_classes)\n        )\n\n    def forward(self, x): return self.net(x)\n\n# ============ Обучение ============\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ntrain_ds = AudioDataset(df, augment=True)\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)\n\nmodel = CNNClassifier(NUM_CLASSES).to(device)\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\ncriterion = LabelSmoothingLoss(NUM_CLASSES, smoothing=0.1)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=3)\n\nbest_f1, patience = 0, 0\n\nfor epoch in range(EPOCHS):\n    model.train()\n    for xb, yb in train_loader:\n        xb, yb = xb.to(device), yb.to(device)\n        x_mix, y1, y2, lam = mixup(xb, yb)\n        out = model(x_mix)\n        loss = lam * criterion(out, y1) + (1 - lam) * criterion(out, y2)\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n    # Быстрая оценка (сэмпл из train)\n    model.eval()\n    preds, targets = [], []\n    with torch.no_grad():\n        for xb, yb in train_loader:\n            xb = xb.to(device)\n            preds += model(xb).argmax(1).cpu().tolist()\n            targets += yb.tolist()\n            if len(preds) > 2000: break\n\n    f1 = f1_score(targets, preds[:len(targets)], average=\"macro\")\n    print(f\"Epoch {epoch+1}/{EPOCHS} — F1: {f1:.4f}\")\n    scheduler.step(f1)\n\n    if f1 > best_f1:\n        best_f1 = f1\n        torch.save(model.state_dict(), \"best_model.pth\")\n        patience = 0\n    else:\n        patience += 1\n        if patience >= PATIENCE:\n            print(\"Early stopping\")\n            break\n\n# ============ Сабмит ============\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\nmodel.eval()\n\ntest_ds = TestDataset(os.listdir(TEST_DIR))\ntest_loader = DataLoader(test_ds, batch_size=32)\nall_preds, all_names = [], []\n\nwith torch.no_grad():\n    for xb, names in test_loader:\n        xb = xb.to(device)\n        preds = model(xb).argmax(1).cpu().numpy()\n        all_preds.extend(preds)\n        all_names.extend(names)\n\ndecoded_preds = le.inverse_transform(all_preds)\nsubmission = pd.DataFrame({\"fname\": all_names, \"label\": decoded_preds})\nsubmission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T15:55:50.063385Z","iopub.execute_input":"2025-05-06T15:55:50.063689Z","iopub.status.idle":"2025-05-06T18:25:58.740169Z","shell.execute_reply.started":"2025-05-06T15:55:50.063667Z","shell.execute_reply":"2025-05-06T18:25:58.739491Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_submission = pd.read_csv('/kaggle/working/submission.csv')\ndf_submission.head(2)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}