{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.12"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21669,"databundleVersionId":1692278,"sourceType":"competition"}],"dockerImageVersionId":30839,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":6172.257611,"end_time":"2024-12-16T00:58:53.630757","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2024-12-15T23:16:01.373146","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport copy\nimport numpy as np\nimport pandas as pd\nimport librosa\nfrom skimage.transform import resize\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.models import regnet_y_16gf\nfrom tqdm import tqdm\nfrom sklearn.model_selection import StratifiedKFold\nimport matplotlib.pyplot as plt","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:27.407787Z","iopub.execute_input":"2025-01-18T10:39:27.408277Z","iopub.status.idle":"2025-01-18T10:39:34.654539Z","shell.execute_reply.started":"2025-01-18T10:39:27.408235Z","shell.execute_reply":"2025-01-18T10:39:34.653540Z"},"papermill":{"duration":6.288564,"end_time":"2024-12-15T23:16:10.072949","exception":false,"start_time":"2024-12-15T23:16:03.784385","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(\"/kaggle/input/rfcx-species-audio-detection/train_tp.csv\")\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:34.655671Z","iopub.execute_input":"2025-01-18T10:39:34.656095Z","iopub.status.idle":"2025-01-18T10:39:34.717047Z","shell.execute_reply.started":"2025-01-18T10:39:34.656071Z","shell.execute_reply":"2025-01-18T10:39:34.716093Z"},"papermill":{"duration":0.04077,"end_time":"2024-12-15T23:16:10.239523","exception":false,"start_time":"2024-12-15T23:16:10.198753","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUM_CLASSES = len(df[\"species_id\"].unique())\nFMIN = df[\"f_min\"].min() * 0.9\nFMAX = 24000\nDEVICE = \"cuda:0\" if torch.cuda.is_available() else \"cpu\"\nFOLD_NUM = 5\nprint(NUM_CLASSES, FMIN, FMAX, DEVICE)","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:34.719233Z","iopub.execute_input":"2025-01-18T10:39:34.719613Z","iopub.status.idle":"2025-01-18T10:39:34.791953Z","shell.execute_reply.started":"2025-01-18T10:39:34.719578Z","shell.execute_reply":"2025-01-18T10:39:34.790903Z"},"papermill":{"duration":0.094632,"end_time":"2024-12-15T23:16:10.349896","exception":false,"start_time":"2024-12-15T23:16:10.255264","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_DIR = \"/kaggle/input/rfcx-species-audio-detection/train/\"\nTEST_DIR = \"/kaggle/input/rfcx-species-audio-detection/test/\"","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:34.793650Z","iopub.execute_input":"2025-01-18T10:39:34.793993Z","iopub.status.idle":"2025-01-18T10:39:34.809727Z","shell.execute_reply.started":"2025-01-18T10:39:34.793967Z","shell.execute_reply":"2025-01-18T10:39:34.808602Z"},"papermill":{"duration":0.010837,"end_time":"2024-12-15T23:16:10.098471","exception":false,"start_time":"2024-12-15T23:16:10.087634","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cache_audio = {}\ncache_splitted = {}","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:34.810685Z","iopub.execute_input":"2025-01-18T10:39:34.810994Z","iopub.status.idle":"2025-01-18T10:39:34.826478Z","shell.execute_reply.started":"2025-01-18T10:39:34.810971Z","shell.execute_reply":"2025-01-18T10:39:34.825506Z"},"papermill":{"duration":0.010592,"end_time":"2024-12-15T23:16:10.123248","exception":false,"start_time":"2024-12-15T23:16:10.112656","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_audio(audio_dir: str, fileid: str, nocache: bool = False):\n    if fileid in cache_audio:\n        return cache_audio[fileid]\n    filename = f\"{audio_dir}/{fileid}.flac\"\n    if not os.path.isfile(filename):\n        raise ValueError(f\"Cannot find {fileid} in audio dir\")\n    waveform, sample_rate = librosa.load(filename, sr=None)\n    if not nocache:\n        cache_audio[fileid] = (waveform, sample_rate)\n    return (waveform, sample_rate)\n\n\ndef get_audio_part(\n    audio_dir: str,\n    fileid: str,\n    tmin: float,\n    tmax: float,\n    duration: int,\n    nocache: bool = False,\n):\n    ts = round((float(tmax) + float(tmin)) / 2, 3)\n    key = f\"{fileid}_{ts}\"\n    if key in cache_splitted:\n        return cache_splitted[key]\n    wave, rate = get_audio(audio_dir, fileid)\n    hd = duration / 2\n    start = int((ts - hd) * rate)\n    end = int((ts + hd) * rate)\n    wave_len = len(wave)\n    dur_diff = end - start - rate * duration\n    if dur_diff != 0:\n        end -= dur_diff\n    if start < 0:\n        end -= start\n        start = 0\n    if end > wave_len:\n        start -= end - wave_len\n        end = wave_len\n    if start < 0 or end > wave_len:\n        raise ValueError(\n            f\"Start or end beyond the wave length: start={start} end={end} len={wave_len}\"\n        )\n    waveform_part = wave[start:end]\n    if not nocache:\n        cache_splitted[key] = (waveform_part, rate)\n    return (waveform_part, rate)","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:34.827650Z","iopub.execute_input":"2025-01-18T10:39:34.828025Z","iopub.status.idle":"2025-01-18T10:39:34.844073Z","shell.execute_reply.started":"2025-01-18T10:39:34.827992Z","shell.execute_reply":"2025-01-18T10:39:34.843158Z"},"papermill":{"duration":0.016242,"end_time":"2024-12-15T23:16:10.162097","exception":false,"start_time":"2024-12-15T23:16:10.145855","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def melspectrogram(w, r, fmin, fmax):\n    return librosa.power_to_db(\n        librosa.feature.melspectrogram(y=w, sr=r, n_mels=128, fmin=fmin, fmax=fmax)\n    ).astype(np.float32)\n\n\ndef to_image(X):\n    X = resize(X, (224, 400))\n    eps = 1e-6\n    mean = X.mean()\n    std = X.std()\n    X = (X - mean) / (std + eps)\n    _min, _max = X.min(), X.max()\n    if (_max - _min) > eps:\n        V = np.clip(X, _min, _max)\n        V = (V - _min) / (_max - _min)\n        V = V.astype(np.float32)\n    else:\n        V = np.zeros_like(X, dtype=np.float32)\n    V = V[np.newaxis, ...]\n    return np.concatenate((V, V, V))\n\n\nclass TrainDataset(Dataset):\n    def __init__(self, data, audio_dir, fmin, fmax, duration: int = 10):\n        self.data = data\n        self.audio_dir = audio_dir\n        self.fmin = fmin\n        self.fmax = fmax\n        self.duration = duration\n        self.cache = {}\n\n    def __len__(self):\n        return len(self.data)\n\n    def audio_to_image(self, audio, sr):\n        return to_image(melspectrogram(audio, sr, self.fmin, self.fmax))\n\n    def __getitem__(self, idx):\n        if idx in self.cache:\n            return self.cache[idx]\n\n        row_data = self.data.iloc[idx]\n        fileid = row_data[\"recording_id\"]\n        tmin = row_data[\"t_min\"]\n        tmax = row_data[\"t_max\"]\n        s_id = row_data[\"species_id\"]\n        wave, sr = get_audio_part(self.audio_dir, fileid, tmin, tmax, self.duration)\n        self.cache[idx] = (self.audio_to_image(wave, sr), s_id)\n        return self.cache[idx]","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:34.845053Z","iopub.execute_input":"2025-01-18T10:39:34.845372Z","iopub.status.idle":"2025-01-18T10:39:34.863796Z","shell.execute_reply.started":"2025-01-18T10:39:34.845342Z","shell.execute_reply":"2025-01-18T10:39:34.862942Z"},"papermill":{"duration":0.015824,"end_time":"2024-12-15T23:16:10.193147","exception":false,"start_time":"2024-12-15T23:16:10.177323","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data = TrainDataset(df, TRAIN_DIR, FMIN, FMAX)\ntest_item = train_data[0][0]\nprint(test_item.shape)\n\nnum = 3\nfig, axs = plt.subplots(num, 1, figsize=(5, 10))\nfor i in range(num):\n    axs[i].imshow(np.transpose(train_data[1 + i][0], axes=[1, 2, 0]))\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:34.866131Z","iopub.execute_input":"2025-01-18T10:39:34.866433Z","iopub.status.idle":"2025-01-18T10:39:51.461405Z","shell.execute_reply.started":"2025-01-18T10:39:34.866403Z","shell.execute_reply":"2025-01-18T10:39:51.460482Z"},"papermill":{"duration":15.483861,"end_time":"2024-12-15T23:16:25.848917","exception":false,"start_time":"2024-12-15T23:16:10.365056","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_model(num_labels, device):\n    model = regnet_y_16gf(weights=\"IMAGENET1K_SWAG_E2E_V1\")\n    num_ftrs = model.fc.in_features\n    model.fc = torch.nn.Linear(num_ftrs, num_labels)\n    model = model.to(device)\n    return model","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:39:51.462736Z","iopub.execute_input":"2025-01-18T10:39:51.463140Z","iopub.status.idle":"2025-01-18T10:39:51.467528Z","shell.execute_reply.started":"2025-01-18T10:39:51.463115Z","shell.execute_reply":"2025-01-18T10:39:51.466436Z"},"papermill":{"duration":0.017799,"end_time":"2024-12-15T23:16:25.900264","exception":false,"start_time":"2024-12-15T23:16:25.882465","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model(\n    model, loss_fn, train_loader, valid_loader, epochs, optimizer, scheduler\n):\n    best_model_wts = copy.deepcopy(model.state_dict())\n    best_acc = 0.0\n    train_losses = []\n    valid_losses = []\n\n    for epoch in range(epochs):\n        model.train()\n        batch_losses = []\n        for x, y in train_loader:\n            optimizer.zero_grad()\n            x = x.to(DEVICE, dtype=torch.float32)\n            y = y.to(DEVICE, dtype=torch.long)\n            y_hat = model(x)\n            loss = loss_fn(y_hat, y)\n            loss.backward()\n            batch_losses.append(loss.item())\n            optimizer.step()\n        train_losses.append(batch_losses)\n\n        model.eval()\n        batch_losses = []\n        trace_y = []\n        trace_yhat = []\n\n        for x, y in valid_loader:\n            x = x.to(DEVICE, dtype=torch.float32)\n            y = y.to(DEVICE, dtype=torch.long)\n            y_hat = model(x)\n            loss = loss_fn(y_hat, y)\n            trace_y.append(y.cpu().detach().numpy())\n            trace_yhat.append(y_hat.cpu().detach().numpy())\n            batch_losses.append(loss.item())\n        valid_losses.append(batch_losses)\n        trace_y = np.concatenate(trace_y)\n        trace_yhat = np.concatenate(trace_yhat)\n        accuracy = np.mean(trace_yhat.argmax(axis=1) == trace_y)\n\n        print(\n            f\"Epoch {epoch}: train_loss = {np.mean(train_losses[-1]):.5f}, val_loss = {np.mean(valid_losses[-1]):.5f}, val_accuracy = {accuracy:.5f}\"\n        )\n\n        scheduler.step(np.mean(valid_losses[-1]))\n        if accuracy > best_acc:\n            print(f\"Best accuracy {best_acc:.5f} -> {accuracy:.5f}\")\n            best_acc = accuracy\n            best_model_wts = copy.deepcopy(model.state_dict())\n        model.load_state_dict(best_model_wts)\n    return model","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:41:42.399845Z","iopub.execute_input":"2025-01-18T10:41:42.400156Z","iopub.status.idle":"2025-01-18T10:41:42.408778Z","shell.execute_reply.started":"2025-01-18T10:41:42.400132Z","shell.execute_reply":"2025-01-18T10:41:42.407685Z"},"papermill":{"duration":0.021894,"end_time":"2024-12-15T23:16:25.955090","exception":false,"start_time":"2024-12-15T23:16:25.933196","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_with_kfold(k: int):\n    kfold = StratifiedKFold(n_splits=k, shuffle=True, random_state=42)\n\n    for fold_id, (train_index, val_index) in enumerate(\n        kfold.split(df, df[\"species_id\"].tolist())\n    ):\n        print(f\"Train #{fold_id}\")\n\n        learning_rate = 2e-4\n        epochs = 20\n        loss_fn = torch.nn.CrossEntropyLoss()\n\n        train_data = TrainDataset(df.loc[train_index], TRAIN_DIR, FMIN, FMAX)\n        valid_data = TrainDataset(df.loc[val_index], TRAIN_DIR, FMIN, FMAX)\n        train_loader = DataLoader(\n            train_data, batch_size=16, shuffle=True, drop_last=True\n        )\n        valid_loader = DataLoader(\n            valid_data, batch_size=16, shuffle=True, drop_last=True\n        )\n        model = get_model(NUM_CLASSES, DEVICE)\n        optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n        scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n            optimizer, \"min\", patience=3\n        )\n        model = train_model(\n            model, loss_fn, train_loader, valid_loader, epochs, optimizer, scheduler\n        )\n        print()\n        torch.save(model.state_dict(), f\"./model{fold_id}.pt\")","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:41:44.498797Z","iopub.execute_input":"2025-01-18T10:41:44.499141Z","iopub.status.idle":"2025-01-18T10:41:44.505643Z","shell.execute_reply.started":"2025-01-18T10:41:44.499115Z","shell.execute_reply":"2025-01-18T10:41:44.504453Z"},"papermill":{"duration":0.020009,"end_time":"2024-12-15T23:16:25.986733","exception":false,"start_time":"2024-12-15T23:16:25.966724","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_with_kfold(FOLD_NUM)","metadata":{"execution":{"iopub.status.busy":"2025-01-18T10:41:47.386533Z","iopub.execute_input":"2025-01-18T10:41:47.386922Z","execution_failed":"2025-01-18T10:46:07.083Z"},"papermill":{"duration":4430.037117,"end_time":"2024-12-16T00:30:16.034812","exception":false,"start_time":"2024-12-15T23:16:25.997695","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cache_audio = {}\ncache_splitted = {}\n\n\nimport gc\n\ngc.collect()","metadata":{"papermill":{"duration":0.578802,"end_time":"2024-12-16T00:30:16.724319","exception":false,"start_time":"2024-12-16T00:30:16.145517","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-01-18T10:41:45.872967Z","iopub.execute_input":"2025-01-18T10:41:45.873308Z","iopub.status.idle":"2025-01-18T10:41:46.133222Z","shell.execute_reply.started":"2025-01-18T10:41:45.873280Z","shell.execute_reply":"2025-01-18T10:41:46.132252Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"models = []\nfor i in range(FOLD_NUM):\n    model = get_model(NUM_CLASSES, DEVICE)\n    model.load_state_dict(torch.load(f\"./model{i}.pt\", weights_only=True))\n    models.append(model.eval())","metadata":{"papermill":{"duration":4.963947,"end_time":"2024-12-16T00:30:21.751015","exception":false,"start_time":"2024-12-16T00:30:16.787068","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TestDataset(Dataset):\n    def __init__(\n        self, data, audio_dir, fmin, fmax, duration: int = 10, nocache: bool = False\n    ):\n        self.data = data\n        self.audio_dir = audio_dir\n        self.fmin = fmin\n        self.fmax = fmax\n        self.duration = duration\n        self.cache = {}\n        self.nocache = nocache\n\n    def __len__(self):\n        return len(self.data)\n\n    def audio_to_image(self, audio, sr):\n        return to_image(melspectrogram(audio, sr, self.fmin, self.fmax))\n\n    def __getitem__(self, idx):\n        if idx in self.cache:\n            return self.cache[idx]\n\n        fileid = self.data[idx]\n        fileid = fileid[: fileid.rfind(\".flac\")]\n        wave, rate = get_audio(self.audio_dir, fileid, self.nocache)\n        wave_len = len(wave) / rate\n        output = []\n        for i in range(int(np.ceil(wave_len / self.duration))):\n            tmin = i * self.duration\n            tmax = tmin + self.duration\n            wave, sr = get_audio_part(\n                self.audio_dir, fileid, tmin, tmax, self.duration, self.nocache\n            )\n            output.append(self.audio_to_image(wave, sr))\n        if not self.nocache:\n            self.cache[idx] = (fileid, np.array(output))\n            return self.cache[idx]\n        return (fileid, np.array(output))","metadata":{"papermill":{"duration":0.031867,"end_time":"2024-12-16T00:30:21.847562","exception":false,"start_time":"2024-12-16T00:30:21.815695","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(models, device):\n    test_dataset = TestDataset(\n        os.listdir(TEST_DIR),\n        TEST_DIR,\n        FMIN,\n        FMAX,\n        nocache=True,\n    )\n    res_indexes = []\n    res_probabilities = []\n    one_model_rows = [[] for _ in models]\n    with torch.no_grad():\n        for i in tqdm(range(len(test_dataset))):\n            fileid, data = test_dataset[i]\n            data = torch.tensor(data).float()\n            if device == \"cuda:0\":\n                data = data.cuda()\n\n            pred = [torch.max(mdl(data), dim=0)[0].cpu().detach() for mdl in models]\n            for pred_, model_row in zip(pred, one_model_rows):\n                model_row.append([fileid] + [res.item() for res in pred_] )\n            avg_pred = torch.mean(torch.stack(pred), dim=0)\n            res_indexes.append(fileid)\n            res_probabilities.append([avg.item() for avg in avg_pred])\n    return res_indexes, res_probabilities, one_model_rows\n\ndef make_submission(preds):\n    res_indexes, res_probabilities, one_model_rows = preds\n    df_sub = pd.DataFrame(\n        res_probabilities, columns=[f\"s{i}\" for i in range(NUM_CLASSES)]\n    )\n    df_sub[\"recording_id\"] = res_indexes\n    df_sub.to_csv(\"submission.csv\", index=False)\n    return df_sub, one_model_rows","metadata":{"papermill":{"duration":0.029742,"end_time":"2024-12-16T00:30:21.940424","exception":false,"start_time":"2024-12-16T00:30:21.910682","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df, one_model_rows = make_submission(predict(models, DEVICE))","metadata":{"papermill":{"duration":1708.58378,"end_time":"2024-12-16T00:58:50.595346","exception":false,"start_time":"2024-12-16T00:30:22.011566","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ids = df.recording_id\ncol_names = [\"recording_id\"]+[f\"s{i}\" for i in range(NUM_CLASSES)]\nfor n, omrows in enumerate(one_model_rows):\n    df = pd.DataFrame(omrows, columns=col_names)\n    df.to_csv(f\"submission_{n}.csv\", index=False)\n    for j, omrows2 in enumerate(one_model_rows[n + 1 :]):\n        arr = np.array([[i[1:] for i in omrows], [i[1:] for i in omrows2]])\n        rows_mean = np.mean(arr, axis=0)\n        df = pd.DataFrame(rows_mean, columns=col_names[1:])\n        df.insert(0, col_names[0], ids)\n        df.to_csv(f\"submission_{n}_{n+j+1}.csv\", index=False)\n        for k, omrows3 in enumerate(one_model_rows[n + j + 2 :]):\n            arr = np.array(\n                [\n                    [i[1:] for i in omrows],\n                    [i[1:] for i in omrows2],\n                    [i[1:] for i in omrows3],\n                ]\n            )\n            rows_mean = np.mean(arr, axis=0)\n            df = pd.DataFrame(rows_mean, columns=col_names[1:])\n            df.insert(0, col_names[0], ids)\n            df.to_csv(f\"submission_{n}_{n+j+1}_{n+j+k+2}.csv\", index=False)","metadata":{"papermill":{"duration":0.106578,"end_time":"2024-12-16T00:58:50.810106","exception":false,"start_time":"2024-12-16T00:58:50.703528","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}