{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import random\nimport os\nimport json\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.optim import AdamW\nfrom transformers import get_cosine_schedule_with_warmup\nimport torchaudio.transforms as T\nimport timm\nfrom sklearn.metrics import f1_score\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom typing import Optional\nfrom types import SimpleNamespace\nfrom tqdm import tqdm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.813615Z","iopub.execute_input":"2025-07-17T12:05:00.813901Z","iopub.status.idle":"2025-07-17T12:05:00.819300Z","shell.execute_reply.started":"2025-07-17T12:05:00.813878Z","shell.execute_reply":"2025-07-17T12:05:00.818583Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cfg = SimpleNamespace(**{})\n\ncfg.train_path = '/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv'\n\ncfg.imu_cols = [\n    'acc_x', 'acc_y', 'acc_z',\n    'rot_w', 'rot_x', 'rot_y', 'rot_z',\n]\ncfg.static_cols = [\n    'sequence_id', 'sequence_type', 'gesture', 'orientation', 'subject',\n    'adult_child', 'age', 'sex', 'handedness',\n    'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm'\n]\n\ncfg.main_target = 'gesture'\ncfg.main_num_classes = 18\ncfg.group = 'subject'\ncfg.seq_len = 100\ncfg.n_splits = 5\ncfg.curr_fold = 1\ncfg.seed = 42\n\ncfg.model_dir = 'weights'\ncfg.oof_dir = 'oofs'\n\ncfg.encoder_name = 'timm/resnet50.a1_in1k' # just for test :)\ncfg.img_sz = 224\ncfg.sepc_model_dropout = 0.3\ncfg.im_pretrained = True\n\ncfg.bs = 256\ncfg.n_epochs = 50\ncfg.patience = 5\ncfg.lr = 1e-4\ncfg.weight_decay = 1e-2\ncfg.num_warmup_steps_ratio = 0.03\ncfg.max_norm = 2.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.829049Z","iopub.execute_input":"2025-07-17T12:05:00.829297Z","iopub.status.idle":"2025-07-17T12:05:00.839856Z","shell.execute_reply.started":"2025-07-17T12:05:00.829280Z","shell.execute_reply":"2025-07-17T12:05:00.839181Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SpecNormalize(nn.Module):\n    def __init__(self, eps: float = 1e-8):\n        super().__init__()\n        self.eps = eps\n\n    def forward(self, x):\n        # batch, channel\n        # x: (batch, channel, freq, time)\n        min_ = x.min(dim=-1, keepdim=True)[0].min(dim=-2, keepdim=True)[0]\n        max_ = x.max(dim=-1, keepdim=True)[0].max(dim=-2, keepdim=True)[0]\n\n        return (x - min_) / (max_ - min_ + self.eps)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.841074Z","iopub.execute_input":"2025-07-17T12:05:00.841429Z","iopub.status.idle":"2025-07-17T12:05:00.856535Z","shell.execute_reply.started":"2025-07-17T12:05:00.841412Z","shell.execute_reply":"2025-07-17T12:05:00.855956Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SpecFeatureExtractor(nn.Module):\n    def __init__(\n        self,\n        in_channels: int,\n        height: int,\n        hop_length: int,\n        win_length: Optional[int] = None,\n        out_size: Optional[int] = None,\n    ):\n        super().__init__()\n        self.height = height\n        self.out_chans = in_channels\n        n_fft = height * 2 - 1\n        self.feature_extractor = nn.Sequential(\n            T.Spectrogram(n_fft=n_fft, hop_length=hop_length, win_length=win_length),\n            T.AmplitudeToDB(top_db=80),\n            SpecNormalize(),\n        )\n        self.out_size = out_size\n\n        if self.out_size is not None:\n            self.pool = nn.AdaptiveAvgPool2d((None, self.out_size))\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        img = self.feature_extractor(x)\n        if self.out_size is not None:\n            img = self.pool(img)\n\n        return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.864531Z","iopub.execute_input":"2025-07-17T12:05:00.865215Z","iopub.status.idle":"2025-07-17T12:05:00.878437Z","shell.execute_reply.started":"2025-07-17T12:05:00.865190Z","shell.execute_reply":"2025-07-17T12:05:00.877805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SpecCNN2d(nn.Module):\n    def __init__(self, in_channels, n_classes, dropout=0.2):\n        super().__init__()\n        \n        self.conv1 = nn.Conv2d(in_channels, 32, kernel_size=3, padding=1)\n        self.bn1 = nn.BatchNorm2d(32)\n        \n        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)\n        self.bn2 = nn.BatchNorm2d(64)\n        \n        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)\n        self.bn3 = nn.BatchNorm2d(128)\n        \n        self.conv4 = nn.Conv2d(128, 256, kernel_size=3, padding=1)\n        self.bn4 = nn.BatchNorm2d(256)\n        \n        self.pool = nn.MaxPool2d(2)\n        self.dropout = nn.Dropout(dropout)\n        \n        self.gap = nn.AdaptiveAvgPool2d(1)\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(256, 512),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(512, 256),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(256, n_classes)\n        )\n\n    def forward(self, x):\n        x = F.relu(self.bn1(self.conv1(x)))\n        x = self.pool(x)\n        x = F.relu(self.bn2(self.conv2(x)))\n        x = self.pool(x)\n        x = F.relu(self.bn3(self.conv3(x)))\n        x = F.relu(self.bn4(self.conv4(x)))\n        x = self.gap(x)\n        x = x.flatten(1)\n\n        return self.classifier(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.879424Z","iopub.execute_input":"2025-07-17T12:05:00.879653Z","iopub.status.idle":"2025-07-17T12:05:00.895358Z","shell.execute_reply.started":"2025-07-17T12:05:00.879637Z","shell.execute_reply":"2025-07-17T12:05:00.894817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class IMG_CMIModel(nn.Module):\n    def __init__(\n        self, \n        imu_vars=len(cfg.imu_cols),\n        n_classes=cfg.main_num_classes, \n        dropout=cfg.sepc_model_dropout, \n        img_sz=cfg.img_sz, \n        spec_height=12, \n        spec_hop_length=4, \n        spec_win_length=None,\n        pretrained=cfg.im_pretrained,\n        encoder=cfg.encoder_name,\n    ):\n        super().__init__()\n        \n        self.spec_feature_extractor = SpecFeatureExtractor(\n            in_channels=imu_vars,\n            height=spec_height,\n            hop_length=spec_hop_length,\n            win_length=spec_win_length,\n            out_size=None # for my cnn2d\n            # out_size=img_sz # for timm model\n        )\n        \n        # self.tmodel = timm.create_model(encoder, in_chans=imu_vars, drop_rate=dropout, num_classes=n_classes, pretrained=pretrained)\n        self.tmodel = SpecCNN2d(in_channels=imu_vars, n_classes=n_classes, dropout=dropout)\n        \n    def forward(self, _x):\n        x = _x.transpose(1, 2)\n        x = self.spec_feature_extractor(x)\n        out = self.tmodel(x)\n        \n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.895965Z","iopub.execute_input":"2025-07-17T12:05:00.896142Z","iopub.status.idle":"2025-07-17T12:05:00.916645Z","shell.execute_reply.started":"2025-07-17T12:05:00.896129Z","shell.execute_reply":"2025-07-17T12:05:00.915935Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TS_CMIDataset(Dataset):\n    def __init__(\n        self, \n        dataframe, \n        seq_len=cfg.seq_len, \n        main_target=cfg.main_target\n    ):\n        self.df = dataframe.copy().reset_index(drop=True)\n        self.seq_len = seq_len\n        self.main_target = main_target\n        \n        self.imu_cols = cfg.imu_cols\n        self.has_target = self.main_target in self.df.columns\n        \n    def _prepare_sensor_data_raw(self, row, sensor_cols):\n        processed_series_list = []\n        original_lengths = []\n        \n        for col_name in sensor_cols:\n            series = row[col_name]\n            series_array = np.asarray(series, dtype=np.float64)\n            original_lengths.append(len(series_array))\n            processed_series_list.append(series_array)\n        \n        data_stacked = np.stack(processed_series_list, axis=1)\n        \n        for i in range(data_stacked.shape[1]):\n            column_data = data_stacked[:, i]\n            if np.all(np.isnan(column_data)):\n                data_stacked[:, i] = 0.0\n            elif np.any(np.isnan(column_data)):\n                s = pd.Series(column_data)\n                s_filled = s.interpolate(method='linear', limit_direction='both').ffill().bfill().fillna(0.0)\n                data_stacked[:, i] = s_filled.values\n        \n        return data_stacked\n\n    def __len__(self):\n        return len(self.df)\n    \n    def _pad_or_truncate_final(self, data, target_len):\n        current_len = data.shape[0]\n        \n        if current_len > target_len:\n            truncated_data = data[-target_len:]\n            return truncated_data\n        elif current_len < target_len:\n            padding_rows = np.zeros((target_len - current_len, data.shape[1]), dtype=data.dtype)\n            padded_data = np.concatenate([padding_rows, data], axis=0)\n            return padded_data\n        \n        return data\n\n    def _prepare_sensor_data(self, row, sensor_cols):\n        data_stacked = self._prepare_sensor_data_raw(row, sensor_cols)\n        data_stacked = self._pad_or_truncate_final(data_stacked, self.seq_len)\n        \n        return data_stacked\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        imu_data = self._prepare_sensor_data(row, self.imu_cols)\n\n        features = {\n            'imu': torch.tensor(imu_data, dtype=torch.float32), # (seq_len, 7)\n        }\n        \n        if self.has_target:\n            features['main_target'] = torch.tensor(row[self.main_target], dtype=torch.long)\n        \n        return features","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.946940Z","iopub.execute_input":"2025-07-17T12:05:00.947447Z","iopub.status.idle":"2025-07-17T12:05:00.956546Z","shell.execute_reply.started":"2025-07-17T12:05:00.947429Z","shell.execute_reply":"2025-07-17T12:05:00.955916Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def fast_seq_agg(df):\n    sc = cfg.static_cols\n    seq_cols = [c for c in df.columns if c not in sc + ['sequence_counter', 'row_id']]\n    static_cols = [c for c in sc if c in df.columns]\n\n    df = df.sort_values(['sequence_id', 'sequence_counter']).reset_index(drop=True)\n\n    seq_id_codes, _ = pd.factorize(df['sequence_id'])\n    _, seq_start_idxs = np.unique(seq_id_codes, return_index=True)\n\n    res = {'sequence_id': df['sequence_id'].values[seq_start_idxs]}\n\n    for c in static_cols:\n        res[c] = df[c].values[seq_start_idxs]\n\n    for c in seq_cols:\n        res[c] = np.split(df[c].values, seq_start_idxs[1:])\n\n    res_df = pd.DataFrame(res)\n\n    return res_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.963279Z","iopub.execute_input":"2025-07-17T12:05:00.963869Z","iopub.status.idle":"2025-07-17T12:05:00.974620Z","shell.execute_reply.started":"2025-07-17T12:05:00.963842Z","shell.execute_reply":"2025-07-17T12:05:00.974064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def le(df):\n    mapper_main = {\n        \"Above ear - pull hair\": 0,\n        \"Cheek - pinch skin\": 1,\n        \"Eyebrow - pull hair\": 2,\n        \"Eyelash - pull hair\": 3, \n        \"Forehead - pull hairline\": 4,\n        \"Forehead - scratch\": 5,\n        \"Neck - pinch skin\": 6, \n        \"Neck - scratch\": 7,\n        \"Drink from bottle/cup\": 8,\n        \"Feel around in tray and pull out an object\": 9,\n        \"Glasses on/off\": 10,\n        \"Pinch knee/leg skin\": 11, \n        \"Pull air toward your face\": 12,\n        \"Scratch knee/leg skin\": 13,\n        \"Text on phone\": 14,\n        \"Wave hello\": 15,\n        \"Write name in air\": 16,\n        \"Write name on leg\": 17,\n    }\n\n    df[cfg.main_target] = df[cfg.main_target].map(mapper_main)\n\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.975693Z","iopub.execute_input":"2025-07-17T12:05:00.975892Z","iopub.status.idle":"2025-07-17T12:05:00.992782Z","shell.execute_reply.started":"2025-07-17T12:05:00.975872Z","shell.execute_reply":"2025-07-17T12:05:00.992069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def comp_metric(y_true, y_pred):\n    y_true = np.array(y_true)\n    y_pred = np.array(y_pred)\n    \n    bscore = f1_score(\n        np.where(y_true <= 7, 1, 0),\n        np.where(y_pred <= 7, 1, 0),\n        zero_division=0.0,\n    )\n\n    mscore = f1_score(\n        np.where(y_true <= 7, y_true, 99),\n        np.where(y_pred <= 7, y_pred, 99),\n        average=\"macro\", \n        zero_division=0.0,\n    )\n\n    return (bscore + mscore) / 2, bscore, mscore","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:00.993527Z","iopub.execute_input":"2025-07-17T12:05:00.993758Z","iopub.status.idle":"2025-07-17T12:05:01.007372Z","shell.execute_reply.started":"2025-07-17T12:05:00.993735Z","shell.execute_reply":"2025-07-17T12:05:01.006846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def seed_everything(seed: int = cfg.seed):\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.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:01.008001Z","iopub.execute_input":"2025-07-17T12:05:01.008271Z","iopub.status.idle":"2025-07-17T12:05:01.021159Z","shell.execute_reply.started":"2025-07-17T12:05:01.008255Z","shell.execute_reply":"2025-07-17T12:05:01.020489Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_epoch(train_loader, model, optimizer, main_criterion, device, scheduler, current_step=0, fold=None):\n    model.train()\n        \n    total_loss = 0\n    total_samples = 0\n    all_targets = []\n    all_preds = []\n    \n    loop = tqdm(train_loader, desc='train', leave=False)\n\n    for batch in loop:\n        optimizer.zero_grad()\n        \n        for key in batch.keys():\n            batch[key] = batch[key].to(device)\n        \n        outputs = model(batch['imu'])\n        targets = batch['main_target']\n        loss = main_criterion(outputs, targets)\n\n        loss.backward()\n\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=cfg.max_norm)\n\n        optimizer.step()\n        scheduler.step()\n\n        total_loss += loss.item() * targets.size(0)\n        total_samples += targets.size(0)\n        \n        preds = torch.argmax(outputs, dim=1)\n        all_targets.extend(targets.cpu().numpy())\n        all_preds.extend(preds.cpu().numpy())\n        \n        loop.set_postfix(loss=loss.item())\n        current_step += 1\n    \n    avg_loss = total_loss / total_samples\n    avg_m, bm, mm = comp_metric(all_targets, all_preds)\n\n    return avg_loss, avg_m, bm, mm, current_step","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:01.022629Z","iopub.execute_input":"2025-07-17T12:05:01.022958Z","iopub.status.idle":"2025-07-17T12:05:01.044171Z","shell.execute_reply.started":"2025-07-17T12:05:01.022941Z","shell.execute_reply":"2025-07-17T12:05:01.043579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def valid_epoch(val_loader, model, main_criterion, device):\n    model.eval()\n\n    total_loss = 0\n    total_samples = 0\n    all_targets = []\n    all_preds = []\n    \n    with torch.no_grad():\n        loop = tqdm(val_loader, desc='val', leave=False)\n        for batch in loop:\n            for key in batch.keys():\n                batch[key] = batch[key].to(device)\n    \n            outputs = model(batch['imu'])\n            targets = batch['main_target']\n            loss = main_criterion(outputs, targets)\n            \n            total_loss += loss.item() * targets.size(0)\n            total_samples += targets.size(0)\n            \n            preds = torch.argmax(outputs, dim=1)\n            all_targets.extend(targets.cpu().numpy())\n            all_preds.extend(preds.cpu().numpy())\n            \n            loop.set_postfix(loss=loss.item())\n    \n    avg_loss = total_loss / total_samples\n    avg_m, bm, mm = comp_metric(all_targets, all_preds)\n    \n    return avg_loss, avg_m, bm, mm, all_targets, all_preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:01.044783Z","iopub.execute_input":"2025-07-17T12:05:01.044943Z","iopub.status.idle":"2025-07-17T12:05:01.060726Z","shell.execute_reply.started":"2025-07-17T12:05:01.044930Z","shell.execute_reply":"2025-07-17T12:05:01.060177Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_training_with_stratified_group_kfold():\n    os.makedirs(cfg.model_dir, exist_ok=True)\n    os.makedirs(cfg.oof_dir, exist_ok=True)\n\n    sgkf = StratifiedGroupKFold(n_splits=cfg.n_splits, shuffle=True, random_state=cfg.seed)\n    targets = train_seq[cfg.main_target].values\n    groups = train_seq[cfg.group].values\n    \n    oof_preds = np.zeros((len(train_seq), cfg.main_num_classes))\n    oof_targets = train_seq[cfg.main_target].values\n    \n    best_models = []\n    best_f1_scores = []\n    \n    for fold, (train_idx, val_idx) in enumerate(sgkf.split(train_seq, targets, groups)):\n        print(f'fold {fold+1}/{cfg.n_splits}')\n        # if fold != cfg.curr_fold:\n        #     continue\n        \n        train_subset = train_seq.iloc[train_idx].reset_index(drop=True)\n        val_subset = train_seq.iloc[val_idx].reset_index(drop=True)\n        \n        train_dataset = TS_CMIDataset(\n            dataframe=train_subset,\n            seq_len=cfg.seq_len,\n            main_target=cfg.main_target,\n        )\n        val_dataset = TS_CMIDataset(\n            dataframe=val_subset,\n            seq_len=cfg.seq_len,\n            main_target=cfg.main_target,\n        )\n        \n        train_loader = DataLoader(\n            train_dataset, \n            batch_size=cfg.bs, \n            shuffle=True, \n            pin_memory=True, \n            persistent_workers=True, \n            prefetch_factor=4, \n            num_workers=4,\n            generator=g,\n            worker_init_fn=lambda worker_id: np.random.seed(cfg.seed + worker_id)\n        )\n        val_loader = DataLoader(\n            val_dataset, \n            batch_size=cfg.bs, \n            shuffle=False, \n            pin_memory=True, \n            persistent_workers=True, \n            prefetch_factor=4, \n            num_workers=4,\n            generator=g,\n            worker_init_fn=lambda worker_id: np.random.seed(cfg.seed + worker_id)           \n        )\n\n        model = IMG_CMIModel().to(device)\n\n        fucking_kaggle_p100 = True\n        if not fucking_kaggle_p100:\n            model = torch.compile(model, mode='max-autotune')\n\n        optimizer = AdamW(model.parameters(), lr=cfg.lr, weight_decay=cfg.weight_decay)\n        \n        main_criterion = nn.CrossEntropyLoss()\n\n        num_training_steps = cfg.n_epochs * len(train_loader)\n        num_warmup_steps = int(cfg.num_warmup_steps_ratio * num_training_steps)\n        current_step = 0\n\n        scheduler_params = {\n            'optimizer': optimizer,\n            'num_warmup_steps': num_warmup_steps,\n            'num_training_steps': num_training_steps\n        }\n        scheduler = get_cosine_schedule_with_warmup(**scheduler_params)\n        \n        best_val_score = -np.inf\n        patience_counter = 0\n        fold_checkpoints = []\n        \n        for epoch in range(cfg.n_epochs):\n            print(f'{epoch=}')\n            \n            train_loss, avg_m_train, bm_train, mm_train, current_step = train_epoch(\n                train_loader, model, optimizer, main_criterion, device, scheduler, fold\n            )\n            val_loss, avg_m_val, bm_val, mm_val, _, _ = valid_epoch(\n                val_loader, model, main_criterion, device\n            )\n            \n            print(f'{train_loss=}, {avg_m_train=}, {bm_train=}, {mm_train=},')\n            print(f'{val_loss=}, {avg_m_val=}, {bm_val=}, {mm_val=}')\n\n            model_path = os.path.join(cfg.model_dir, f'model_fold{fold}_val_f1_{avg_m_val:.4f}_epoch{epoch:03d}.pt')\n            torch.save(model.state_dict(), model_path)\n\n            fold_checkpoints.append({\n                'score': avg_m_val,\n                'epoch': epoch,\n                'model_path': model_path,\n            })\n\n            fold_checkpoints.sort(key=lambda x: x['score'], reverse=True)\n            \n            if len(fold_checkpoints) > 5:\n                to_remove = fold_checkpoints[5:]\n                fold_checkpoints = fold_checkpoints[:5]\n                \n                for checkpoint in to_remove:\n                    if os.path.exists(checkpoint['model_path']):\n                        os.remove(checkpoint['model_path'])\n\n            if avg_m_val > best_val_score:\n                best_val_score = avg_m_val\n                patience_counter = 0\n            else:\n                patience_counter += 1\n            \n            if patience_counter >= cfg.patience:\n                print('early stopping')\n                break\n\n        best_checkpoint = fold_checkpoints[0]\n        if best_checkpoint:\n            model.load_state_dict(torch.load(best_checkpoint['model_path']))\n\n        best_models.append(model)\n        best_f1_scores.append(best_val_score)\n        \n        model.eval()\n        all_preds = []\n        with torch.no_grad():\n            for batch in val_loader:\n                for key in batch.keys():\n                    batch[key] = batch[key].to(device)\n\n                outputs = model(batch['imu'])\n                all_preds.append(outputs.cpu().numpy())\n\n        all_preds = np.concatenate(all_preds, axis=0)\n        oof_preds[val_idx] = all_preds\n\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n\n    oof_pred_labels = np.argmax(oof_preds, axis=1)\n    oof_m, oof_bm, oof_mm = comp_metric(oof_targets, oof_pred_labels)\n    print(f'{oof_m=}, {oof_bm=}, {oof_mm=}, ')\n    \n    oof_preds_path = os.path.join(cfg.oof_dir, f'oof_preds.npy')\n    oof_targets_path = os.path.join(cfg.oof_dir, f'oof_targets.npy')\n    oof_pred_labels_path = os.path.join(cfg.oof_dir, f'oof_pred_labels.npy')\n\n    np.save(oof_preds_path, oof_preds)\n    np.save(oof_targets_path, oof_targets)\n    np.save(oof_pred_labels_path, oof_pred_labels)\n\n    oof_info = {\n        'oof_avg_f1': oof_m,\n        'oof_binary_f1': oof_bm,\n        'oof_macro_f1': oof_mm,\n        'best_avg_f1_scores_per_fold': best_f1_scores,\n        'mean_cv_avg_f1': np.mean(best_f1_scores),\n        'std_cv_avg_f1': np.std(best_f1_scores)\n    }\n    \n    oof_info_path = os.path.join(cfg.oof_dir, f'oof_info.json')\n    with open(oof_info_path, 'w') as f:\n        json.dump(oof_info, f, indent=2)\n\n    return best_models, oof_preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:01.061361Z","iopub.execute_input":"2025-07-17T12:05:01.061605Z","iopub.status.idle":"2025-07-17T12:05:01.085493Z","shell.execute_reply.started":"2025-07-17T12:05:01.061584Z","shell.execute_reply":"2025-07-17T12:05:01.084705Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"seed_everything()\ng = torch.Generator(device='cpu').manual_seed(cfg.seed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:01.086225Z","iopub.execute_input":"2025-07-17T12:05:01.086387Z","iopub.status.idle":"2025-07-17T12:05:01.105236Z","shell.execute_reply.started":"2025-07-17T12:05:01.086374Z","shell.execute_reply":"2025-07-17T12:05:01.104552Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = pd.read_csv(cfg.train_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:01.105973Z","iopub.execute_input":"2025-07-17T12:05:01.106306Z","iopub.status.idle":"2025-07-17T12:05:20.703676Z","shell.execute_reply.started":"2025-07-17T12:05:01.106270Z","shell.execute_reply":"2025-07-17T12:05:20.703064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = le(train)\ntrain_seq = fast_seq_agg(train)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:20.704476Z","iopub.execute_input":"2025-07-17T12:05:20.704765Z","iopub.status.idle":"2025-07-17T12:05:24.912648Z","shell.execute_reply.started":"2025-07-17T12:05:20.704738Z","shell.execute_reply":"2025-07-17T12:05:24.912007Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:24.913598Z","iopub.execute_input":"2025-07-17T12:05:24.913847Z","iopub.status.idle":"2025-07-17T12:05:24.917934Z","shell.execute_reply.started":"2025-07-17T12:05:24.913829Z","shell.execute_reply":"2025-07-17T12:05:24.917223Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_models, oof_preds = run_training_with_stratified_group_kfold()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T12:05:24.918650Z","iopub.execute_input":"2025-07-17T12:05:24.919101Z","iopub.status.idle":"2025-07-17T12:10:10.067427Z","shell.execute_reply.started":"2025-07-17T12:05:24.919079Z","shell.execute_reply":"2025-07-17T12:10:10.066689Z"}},"outputs":[],"execution_count":null}]}