{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.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":21669,"databundleVersionId":1692278,"sourceType":"competition"}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Импорты и базовые настройки","metadata":{}},{"cell_type":"code","source":"\nimport os\nimport csv\nimport copy\nimport random\nimport warnings\n\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport torch\nimport torch.nn as nn\n\nfrom tqdm import tqdm\nfrom sklearn.model_selection import KFold\nfrom torchvision.models import resnet101\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom skimage import exposure, util\nfrom skimage.color import rgb2gray\nfrom skimage.filters import gaussian\nfrom skimage.transform import resize\n\nwarnings.filterwarnings(\"ignore\")\n\nNUM_CLASSES = 24\nDEVICE = \"cuda:0\" if torch.cuda.is_available() else \"cpu\"\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-25T17:12:12.047927Z","iopub.execute_input":"2025-12-25T17:12:12.048757Z","iopub.status.idle":"2025-12-25T17:12:22.139666Z","shell.execute_reply.started":"2025-12-25T17:12:12.048718Z","shell.execute_reply":"2025-12-25T17:12:22.139056Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Аугментации + преобразование спектрограмм","metadata":{}},{"cell_type":"code","source":"def to_3ch(img):\n    return np.stack((img, img, img))\n\n\ndef aug_flip_h(img):\n    return to_3ch(img[:, ::-1])\n\n\ndef aug_flip_v(img):\n    return to_3ch(img[::-1, :])\n\n\ndef aug_noise(img):\n    return to_3ch(util.random_noise(img))\n\n\ndef aug_gauss(img):\n    return to_3ch(gaussian(img))\n\n\ndef aug_contrast(img):\n    return to_3ch(exposure.rescale_intensity(img))\n\n\ndef aug_gamma(img):\n    return to_3ch(exposure.adjust_gamma(img))\n\n\ndef aug_gray(img):\n    return to_3ch(rgb2gray(img))\n\n\ndef normalize_mel(spec):\n    spec = resize(spec, (224, 400))\n    eps = 1e-6\n    norm = (spec - spec.mean()) / (spec.std() + eps)\n    mn, mx = norm.min(), norm.max()\n    scaled = 255 * (norm - mn) / (mx - mn)\n    return scaled.astype(np.uint8)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T17:12:22.140886Z","iopub.execute_input":"2025-12-25T17:12:22.141293Z","iopub.status.idle":"2025-12-25T17:12:22.147229Z","shell.execute_reply.started":"2025-12-25T17:12:22.141269Z","shell.execute_reply":"2025-12-25T17:12:22.146718Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Модель","metadata":{}},{"cell_type":"code","source":"\ndef build_model():\n    net = resnet101(pretrained=True)\n    inp = net.fc.in_features\n    net.fc = nn.Linear(inp, NUM_CLASSES)\n    net.to(DEVICE)\n    return net\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T17:12:22.147963Z","iopub.execute_input":"2025-12-25T17:12:22.148243Z","iopub.status.idle":"2025-12-25T17:12:22.165427Z","shell.execute_reply.started":"2025-12-25T17:12:22.148211Z","shell.execute_reply":"2025-12-25T17:12:22.164832Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Подготовка train данных","metadata":{}},{"cell_type":"code","source":"\nsr = 48000\nchunk_len = sr * 10\n\ntrain_df = pd.read_csv(\"../input/rfcx-species-audio-detection/train_tp.csv\")\n\nlow = 24000\nhigh = 0\n\nfor _, r in train_df.iterrows():\n    low = min(low, float(r[\"f_min\"]))\n    high = max(high, float(r[\"f_max\"]))\n\nfmin = int(low * 0.9)\nfmax = int(high * 1.1)\n\nids = []\nlabels = []\nmelbank = {}\n\nfor i in range(len(train_df)):\n    rid = train_df.recording_id.iloc[i]\n    sid = int(train_df.species_id.iloc[i])\n    ids.append(rid)\n    labels.append(sid)\n\n    audio, sr_now = librosa.load(\n        f\"../input/rfcx-species-audio-detection/train/{rid}.flac\", sr=None\n    )\n\n    t1 = int(train_df.t_min.iloc[i] * sr_now)\n    t2 = int(train_df.t_max.iloc[i] * sr_now)\n\n    center = int((t1 + t2) / 2)\n    start = max(0, center - chunk_len // 2)\n    end = start + chunk_len\n\n    if end > len(audio):\n        end = len(audio)\n        start = end - chunk_len\n\n    clip = audio[start:end]\n\n    mel = librosa.feature.melspectrogram(y=clip, sr=sr_now, fmin=fmin, fmax=fmax)\n    mel_db = librosa.power_to_db(mel, top_db=80)\n\n    melbank[rid] = normalize_mel(mel_db)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T17:12:22.167251Z","iopub.execute_input":"2025-12-25T17:12:22.167462Z","iopub.status.idle":"2025-12-25T17:15:34.636671Z","shell.execute_reply.started":"2025-12-25T17:12:22.167443Z","shell.execute_reply":"2025-12-25T17:15:34.635995Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Dataset","metadata":{}},{"cell_type":"code","source":"\nclass BirdDataset(Dataset):\n    def __init__(self, X, y, mode):\n        self.X = X\n        self.y = y\n        self.mode = mode\n\n        self.transforms = [\n            aug_noise,\n            aug_contrast,\n            aug_gauss,\n            aug_gamma,\n            aug_flip_v,\n            aug_flip_h,\n            to_3ch\n        ]\n\n    def __len__(self):\n        return len(self.X)\n\n    def __getitem__(self, idx):\n        rid = self.X[idx]\n        img = melbank[rid]\n\n        if self.mode == \"train\":\n            fn = random.choice(self.transforms)\n            img = fn(img)\n        else:\n            img = to_3ch(img)\n\n        return img, self.y[idx]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T17:15:34.637455Z","iopub.execute_input":"2025-12-25T17:15:34.637933Z","iopub.status.idle":"2025-12-25T17:15:34.646839Z","shell.execute_reply.started":"2025-12-25T17:15:34.637906Z","shell.execute_reply":"2025-12-25T17:15:34.644100Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Тренировка","metadata":{}},{"cell_type":"code","source":"\nLR = 2e-4\nEPOCHS = 20\ncriterion = nn.CrossEntropyLoss()\n\ndef fit(model, loss_fn, train_loader, valid_loader, epochs, optimizer, scheduler):\n    best = 0\n    best_weights = copy.deepcopy(model.state_dict())\n\n    for ep in tqdm(range(1, epochs + 1)):\n        model.train()\n        train_loss = []\n\n        for x, y in train_loader:\n            optimizer.zero_grad()\n\n            x = x.float().to(DEVICE)\n            y = y.long().to(DEVICE)\n\n            logits = model(x)\n            loss = loss_fn(logits, y)\n            loss.backward()\n            optimizer.step()\n\n            train_loss.append(loss.item())\n\n        model.eval()\n        val_loss = []\n        true = []\n        pred = []\n\n        with torch.no_grad():\n            for x, y in valid_loader:\n                x = x.float().to(DEVICE)\n                y = y.long().to(DEVICE)\n\n                out = model(x)\n                loss = loss_fn(out, y)\n\n                val_loss.append(loss.item())\n                true.append(y.cpu().numpy())\n                pred.append(out.cpu().numpy())\n\n        true = np.concatenate(true)\n        pred = np.concatenate(pred)\n\n        acc = (pred.argmax(1) == true).mean()\n\n        print(f\"epoch={ep}, train={np.mean(train_loss):.5f}, val={np.mean(val_loss):.5f}, acc={acc:.5f}\")\n\n        scheduler.step(np.mean(val_loss))\n\n        if acc > best:\n            best = acc\n            best_weights = copy.deepcopy(model.state_dict())\n\n    model.load_state_dict(best_weights)\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T17:15:34.647596Z","iopub.execute_input":"2025-12-25T17:15:34.647833Z","iopub.status.idle":"2025-12-25T17:15:34.669029Z","shell.execute_reply.started":"2025-12-25T17:15:34.647810Z","shell.execute_reply":"2025-12-25T17:15:34.668459Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"KFold обучение + сохранение моделей","metadata":{}},{"cell_type":"code","source":"\nNFOLDS = 5\nkf = KFold(n_splits=NFOLDS, shuffle=True, random_state=32)\n\nfor fold, (tr, vl) in enumerate(kf.split(ids, labels)):\n    print(\"FOLD\", fold)\n\n    X_tr = np.take(ids, tr)\n    y_tr = np.take(labels, tr)\n    X_vl = np.take(ids, vl)\n    y_vl = np.take(labels, vl)\n\n    train_ds = BirdDataset(X_tr, y_tr, \"train\")\n    val_ds = BirdDataset(X_vl, y_vl, \"valid\")\n\n    train_ld = DataLoader(train_ds, batch_size=8, shuffle=True, drop_last=True)\n    val_ld = DataLoader(val_ds, batch_size=8, shuffle=True, drop_last=True)\n\n    model = build_model()\n    opt = torch.optim.Adam(model.parameters(), lr=LR)\n    sched = torch.optim.lr_scheduler.ReduceLROnPlateau(opt, \"min\", patience=3)\n\n    model = fit(model, criterion, train_ld, val_ld, EPOCHS, opt, sched)\n\n    torch.save(model.state_dict(), f\"./fold_model_{fold}.pt\")\n\n    del model, train_ds, val_ds, train_ld, val_ld\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T17:15:34.669840Z","iopub.execute_input":"2025-12-25T17:15:34.670272Z","iopub.status.idle":"2025-12-25T18:11:37.483791Z","shell.execute_reply.started":"2025-12-25T17:15:34.670249Z","shell.execute_reply":"2025-12-25T18:11:37.483190Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Подготовка тестовых фрагментов","metadata":{}},{"cell_type":"code","source":"def prepare_test_audio(filename):\n    audio, s = librosa.load(f\"../input/rfcx-species-audio-detection/test/{filename}\", sr=None)\n\n    segs = int(np.ceil(len(audio) / chunk_len))\n    result = []\n\n    for i in range(segs):\n        if (i + 1) * chunk_len > len(audio):\n            part = audio[len(audio) - chunk_len:]\n        else:\n            part = audio[i * chunk_len:(i + 1) * chunk_len]\n\n        mel = librosa.feature.melspectrogram(y=part, sr=s, fmin=fmin, fmax=fmax)\n        mel_db = librosa.power_to_db(mel, top_db=80)\n\n        img = normalize_mel(mel_db)\n        img = np.stack((img, img, img))\n        result.append(img)\n\n    return result\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T18:11:37.485133Z","iopub.execute_input":"2025-12-25T18:11:37.485391Z","iopub.status.idle":"2025-12-25T18:11:37.490947Z","shell.execute_reply.started":"2025-12-25T18:11:37.485370Z","shell.execute_reply":"2025-12-25T18:11:37.490292Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Загрузка моделей ансамбля","metadata":{}},{"cell_type":"code","source":"ensemble = []\n\nfor i in range(NFOLDS):\n    m = build_model()\n    m.load_state_dict(torch.load(f\"./fold_model_{i}.pt\"))\n    m.eval()\n    ensemble.append(m)\n\nfor i in range(NFOLDS):\n    os.remove(f\"./fold_model_{i}.pt\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T18:11:37.491860Z","iopub.execute_input":"2025-12-25T18:11:37.492068Z","iopub.status.idle":"2025-12-25T18:11:42.225078Z","shell.execute_reply.started":"2025-12-25T18:11:37.492049Z","shell.execute_reply":"2025-12-25T18:11:42.224214Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Предсказание и генерация submission.csv","metadata":{}},{"cell_type":"code","source":"print(\"Predicting...\")\n\nwith open(\"submission.csv\", \"w\", newline=\"\") as file:\n    wr = csv.writer(file)\n    wr.writerow([\"recording_id\"] + [f\"s{i}\" for i in range(24)])\n\n    test_files = os.listdir(\"../input/rfcx-species-audio-detection/test/\")\n    print(\"Files:\", len(test_files))\n\n    for i, fname in enumerate(test_files):\n        data = torch.tensor(prepare_test_audio(fname)).float()\n\n        if torch.cuda.is_available():\n            data = data.cuda()\n\n        preds = []\n\n        for m in ensemble:\n            out = m(data)\n            best = torch.max(out, dim=0)[0].cpu().detach()\n            preds.append(best)\n\n        final = torch.mean(torch.stack(preds), dim=0)\n\n        rec_id = fname.split(\".\")[0]\n        row = [rec_id] + [x.item() for x in final]\n        wr.writerow(row)\n\n        if i % 100 == 0 and i > 0:\n            print(f\"Done {i}/{len(test_files)}\")\n\nprint(\"submission.csv готов!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T18:11:42.226860Z","iopub.execute_input":"2025-12-25T18:11:42.227105Z","execution_failed":"2025-12-25T18:16:23.344Z"}},"outputs":[],"execution_count":null}]}