{"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":[{"sourceType":"competition","sourceId":102335,"databundleVersionId":12518947},{"sourceType":"kernelVersion","sourceId":248021766}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport polars as pl\nimport pandas as pd\nimport torch\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.model_selection import train_test_split\n\n\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.optim as optim\nfrom torch.optim import AdamW\nfrom torch.optim.lr_scheduler import CosineAnnealingWarmRestarts\n\nimport os\nimport joblib\nimport random\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:20:50.837303Z","iopub.execute_input":"2025-06-29T20:20:50.837498Z","iopub.status.idle":"2025-06-29T20:20:57.813410Z","shell.execute_reply.started":"2025-06-29T20:20:50.837473Z","shell.execute_reply":"2025-06-29T20:20:57.812846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMU_DIM = 45\nBATCH_SIZE = 64\nPAD_PERCENTILE = 97\nPAD_LEN = 151\nLR_INIT = 5e-4\nWD = 3e-3\nMIXUP_ALPHA = 0.4\nEPOCHS = 200\nPATIENCE = 40\nSEED = 3126\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:20:57.814629Z","iopub.execute_input":"2025-06-29T20:20:57.815006Z","iopub.status.idle":"2025-06-29T20:20:57.916930Z","shell.execute_reply.started":"2025-06-29T20:20:57.814987Z","shell.execute_reply":"2025-06-29T20:20:57.916358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import f1_score\n\nclass ParticipantVisibleError(Exception):\n    \"\"\"Errors raised here will be shown directly to the competitor.\"\"\"\n    pass\n\n\nclass CompetitionMetric:\n    \"\"\"Hierarchical macro F1 for the CMI 2025 challenge.\"\"\"\n    def __init__(self):\n        self.target_gestures = [\n            'Above ear - pull hair',\n            'Cheek - pinch skin',\n            'Eyebrow - pull hair',\n            'Eyelash - pull hair',\n            'Forehead - pull hairline',\n            'Forehead - scratch',\n            'Neck - pinch skin',\n            'Neck - scratch',\n        ]\n        self.non_target_gestures = [\n            'Write name on leg',\n            'Wave hello',\n            'Glasses on/off',\n            'Text on phone',\n            'Write name in air',\n            'Feel around in tray and pull out an object',\n            'Scratch knee/leg skin',\n            'Pull air toward your face',\n            'Drink from bottle/cup',\n            'Pinch knee/leg skin'\n        ]\n        self.all_classes = self.target_gestures + self.non_target_gestures\n\n    def calculate_hierarchical_f1(\n        self,\n        sol: pd.DataFrame,\n        sub: pd.DataFrame\n    ) -> float:\n\n        # Validate gestures\n        invalid_types = {i for i in sub['gesture'].unique() if i not in self.all_classes}\n        if invalid_types:\n            raise ParticipantVisibleError(\n                f\"Invalid gesture values in submission: {invalid_types}\"\n            )\n\n        # Compute binary F1 (Target vs Non-Target)\n        y_true_bin = sol['gesture'].isin(self.target_gestures).values\n        y_pred_bin = sub['gesture'].isin(self.target_gestures).values\n        \n        f1_binary = f1_score(y_true_bin, y_pred_bin, pos_label=True, zero_division=0, average='binary')\n\n        # Build multi-class labels for gestures\n        y_true_mc = sol['gesture'].apply(lambda x: x if x in self.target_gestures else 'non_target')\n        y_pred_mc = sub['gesture'].apply(lambda x: x if x in self.target_gestures else 'non_target')\n\n        f1_macro = f1_score(y_true_mc, y_pred_mc, average='macro', zero_division=0)\n\n        return f1_binary, f1_macro, (f1_binary+f1_macro)/2.0","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-06-29T20:20:57.917838Z","iopub.execute_input":"2025-06-29T20:20:57.918118Z","iopub.status.idle":"2025-06-29T20:20:57.933740Z","shell.execute_reply.started":"2025-06-29T20:20:57.918095Z","shell.execute_reply":"2025-06-29T20:20:57.933142Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def F1_score(y_val, y_pred, lbl_encoder, choice=\"weighted\"):\n    metric = CompetitionMetric()\n    y_val  = pd.DataFrame({'id':range(len(y_val)), \n                           'gesture':y_val})\n    y_pred = pd.DataFrame({'id':range(len(y_pred)), \n                           'gesture':y_pred})\n\n    ## Convert numeric labels to original descriptions\n    y_val[\"gesture\"]  = lbl_encoder.inverse_transform(y_val[\"gesture\"])\n    y_pred[\"gesture\"] = lbl_encoder.inverse_transform(y_pred[\"gesture\"])\n\n    ## Computes score\n    binary, macro, weighted = metric.calculate_hierarchical_f1(y_val, y_pred)\n\n    ## Returns result\n    if choice==\"binary\": return binary\n    elif choice==\"macro\": return macro\n    elif choice==\"weighted\": return weighted\n    else: return (binary, macro, weighted)","metadata":{"trusted":true,"_kg_hide-input":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-06-29T20:20:57.934357Z","iopub.execute_input":"2025-06-29T20:20:57.934547Z","iopub.status.idle":"2025-06-29T20:20:57.951579Z","shell.execute_reply.started":"2025-06-29T20:20:57.934528Z","shell.execute_reply":"2025-06-29T20:20:57.951111Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def seed_all(seed=3126):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n\ndef worker_init_fn(worker_id):\n    np.random.seed(SEED + worker_id)\n    random.seed(SEED + worker_id)\n\nseed_all()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:20:57.953196Z","iopub.execute_input":"2025-06-29T20:20:57.953386Z","iopub.status.idle":"2025-06-29T20:20:57.973599Z","shell.execute_reply.started":"2025-06-29T20:20:57.953372Z","shell.execute_reply":"2025-06-29T20:20:57.972940Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"RAW_DIR = \"/kaggle/input/cmi-detect-behavior-with-sensor-data\"\n\nlabel_encoder = joblib.load(\"/kaggle/input/imu-data-v3/label_encoder.joblib\")\nscaler = joblib.load(\"/kaggle/input/imu-data-v3/preprocessor.joblib\")\nX = torch.load(\"/kaggle/input/imu-data-v3/X.pt\")\ny_int = np.load(\"/kaggle/input/imu-data-v3/y_int.npy\")\ny_ohe = torch.load(\"/kaggle/input/imu-data-v3/y_ohe.pt\")\nimu_features = joblib.load(\"/kaggle/input/imu-data-v3/imu_features.joblib\")\ngroups = np.load(\"/kaggle/input/imu-data-v3/groups.npy\", allow_pickle=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:20:57.974295Z","iopub.execute_input":"2025-06-29T20:20:57.974978Z","iopub.status.idle":"2025-06-29T20:21:00.246551Z","shell.execute_reply.started":"2025-06-29T20:20:57.974960Z","shell.execute_reply":"2025-06-29T20:21:00.245776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"imu_features[\"full_features\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.247425Z","iopub.execute_input":"2025-06-29T20:21:00.247714Z","iopub.status.idle":"2025-06-29T20:21:00.254842Z","shell.execute_reply.started":"2025-06-29T20:21:00.247691Z","shell.execute_reply":"2025-06-29T20:21:00.254024Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X.shape","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Design","metadata":{}},{"cell_type":"code","source":"class CoordAttention(nn.Module):\n    \"\"\"\n    Coordinate Attention for Sequences.\n    Input Dimension: (B, T, C)\n    Output Dimension: (B, T, C)\n    \"\"\"\n    def __init__(self, channels, reduction=8):\n        super(CoordAttention, self).__init__()\n        self.mid_channels = max(8, channels // reduction)\n\n        self.compression = nn.Sequential(\n            nn.Conv1d(channels, self.mid_channels, kernel_size=1, bias=False),\n            nn.BatchNorm1d(self.mid_channels),\n            nn.SiLU(inplace=True)\n        )\n\n        # Attention branches\n        self.time_conv = nn.Conv1d(1, 1, kernel_size=3, padding=1, bias=False)  \n        self.channel_conv = nn.Conv1d(self.mid_channels, channels, kernel_size=1, bias=False)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        # x: (B, T, C)\n        x_p = x.permute(0, 2, 1)  # (B, C, T)\n        f   = self.compression(x_p)  # (B, rC, T)\n\n        ## Time Attention (B, 1, T)\n        f_t = f.mean(dim=1, keepdim=True)      \n        time_attn = self.sigmoid(self.time_conv(f_t))  \n\n        ## Channel Attention (B, C, 1)\n        f_c = f.mean(dim=2, keepdim=True)      \n        channel_attn = self.sigmoid(self.channel_conv(f_c)) \n\n        ## (B, T, C)\n        out = (x_p * time_attn * channel_attn).permute(0,2,1)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.255545Z","iopub.execute_input":"2025-06-29T20:21:00.255856Z","iopub.status.idle":"2025-06-29T20:21:00.278650Z","shell.execute_reply.started":"2025-06-29T20:21:00.255833Z","shell.execute_reply":"2025-06-29T20:21:00.277877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ResCNNBlock(nn.Module):\n    \"\"\"\n    Residual CNN Block with Coordinate Attention. \n    Input Dimension: (B, T_in, C_in)\n    Output Dimension: (B, T_out, C_out)\n    \"\"\"\n    def __init__(self, in_channels, out_channels, kernel_size=3, pool_size=2, drop=0.2):\n        super(ResCNNBlock, self).__init__()\n\n        ## CNN model\n        self.cnn = nn.Sequential(\n            nn.Conv1d(in_channels, out_channels, kernel_size, padding='same', bias=False),\n            nn.BatchNorm1d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv1d(out_channels, out_channels, kernel_size, padding='same', bias=False),\n            nn.BatchNorm1d(out_channels),\n            nn.ReLU(inplace=True)\n        )\n        ## Attention module\n        self.attention = CoordAttention(out_channels)\n        ## Projection path for skip connection\n        self.shortcut_proj = None\n        if in_channels != out_channels:\n            self.shortcut_proj = nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, kernel_size=1, padding='same', bias=False),\n                nn.BatchNorm1d(out_channels)\n            )\n        ## Final processing components\n        self.relu_final = nn.ReLU(inplace=True)\n        if pool_size is not None: \n            self.max_pool = nn.MaxPool1d(pool_size)\n        else: \n            self.max_pool = None\n        self.dropout = nn.Dropout(drop)\n\n    def forward(self, x):\n        # (B, C_in, T)\n        shortcut = x.permute(0,2,1)\n        x_p = x.permute(0,2,1)\n        \n        out = self.cnn(x_p)                         # (B, C_out, T)\n        out = self.attention(out.permute(0, 2, 1))  # (B, T, C_out)\n        out = out.permute(0, 2, 1)                  # (B, C_out, T)\n        \n        ## Handle shortcut connection -> (B, C_out, T)\n        if self.shortcut_proj:\n            shortcut = self.shortcut_proj(shortcut)\n\n        ## Residual connection\n        out += shortcut\n        out = self.relu_final(out)\n        if self.max_pool is not None:\n            ## (B, C_out, T) -> (B, C_out, T//pool_size)\n            out = self.max_pool(out)\n        out = self.dropout(out)\n        ## (B, T_out, C_out)\n        out = out.permute(0, 2, 1)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.279707Z","iopub.execute_input":"2025-06-29T20:21:00.279990Z","iopub.status.idle":"2025-06-29T20:21:00.300171Z","shell.execute_reply.started":"2025-06-29T20:21:00.279969Z","shell.execute_reply":"2025-06-29T20:21:00.299364Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TempCNNBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size=3, dilation=1, drop=0.25):\n        super(TempCNNBlock, self).__init__()\n        ## Padding computation\n        padding = (kernel_size - 1) * dilation // 2\n        ## Temporal Convolution block\n        self.conv = nn.Sequential(\n            nn.Conv1d(in_channels, out_channels, kernel_size=kernel_size,\n                      dilation=dilation, padding=padding, bias=False),\n            nn.BatchNorm1d(out_channels),\n            nn.ReLU(inplace=True)\n        )\n        ## Attention module\n        self.attention = CoordAttention(out_channels)\n        ## Dropout\n        self.dropout = nn.Dropout(drop)\n        \n        ## Shortcut for skip connection\n        self.shortcut_proj = None\n        if in_channels != out_channels:\n            self.shortcut_proj = nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, kernel_size=1, padding='same', bias=False),\n                nn.BatchNorm1d(out_channels)\n            )\n        self.relu_final = nn.ReLU()\n        \n    def forward(self, x):\n        ## Processes skip connection\n        # (B, C_in, T)\n        x_p      = x.permute(0,2,1)\n        shortcut = x.permute(0,2,1)\n        # (B, C_out, T)\n        if self.shortcut_proj:\n            shortcut = self.shortcut_proj(shortcut)\n        ## Processes output\n        out = self.conv(x_p)                        # (B, C_out, T)\n        out = self.attention(out.permute(0,2,1))    # (B, T, C_out)\n        out = out.permute(0,2,1)                    # (B, C_out, T)\n        ## Skip connection\n        out = self.relu_final(out + shortcut) # (B, C_out, T)\n        out = self.dropout(out)\n        return out.permute(0,2,1)  # (B, T, C_out)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.301020Z","iopub.execute_input":"2025-06-29T20:21:00.301260Z","iopub.status.idle":"2025-06-29T20:21:00.318769Z","shell.execute_reply.started":"2025-06-29T20:21:00.301240Z","shell.execute_reply":"2025-06-29T20:21:00.318202Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MLPAttention(nn.Module):\n    def __init__(self, feature_dim):\n        super(MLPAttention, self).__init__()\n        self.attn = nn.Sequential(\n            nn.Linear(feature_dim, feature_dim//8),\n            nn.SiLU(inplace=True),\n            nn.Linear(feature_dim//8, 1)\n        )\n\n    def forward(self, x):\n        # inputs shape: (B, T, C)\n        weights = self.attn(x)  # (B, T, 1)\n        weights = F.softmax(weights, dim=1)  # (B, T, 1)\n        context = (x * weights).sum(dim=1)  # (B, C)\n        return context","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.319463Z","iopub.execute_input":"2025-06-29T20:21:00.319716Z","iopub.status.idle":"2025-06-29T20:21:00.338357Z","shell.execute_reply.started":"2025-06-29T20:21:00.319696Z","shell.execute_reply":"2025-06-29T20:21:00.337849Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class KinematicResTCNN(nn.Module):\n    \"\"\"\n    Gesture Classifier utilizing ResNet innovations with Temporal Convolution on IMU data.\n    \"\"\"\n    def __init__(self, imu_dim):\n        super(KinematicResTCNN, self).__init__()\n        ## (B,151,45) --> (B, 151//4, 512) = (B, 37, 512)\n        self.resnet = nn.Sequential(\n            ResCNNBlock(imu_dim, 128, kernel_size=3, pool_size=2, drop=0.2),\n            ResCNNBlock(128, 128, kernel_size=3, pool_size=None, drop=0.2),\n            ResCNNBlock(128, 256, kernel_size=3, pool_size=2, drop=0.2),\n            ResCNNBlock(256, 512, kernel_size=5, pool_size=None, drop=0.2),\n        )\n        ## (B, 37, 128) \n        self.temporalCNN = nn.Sequential(\n            TempCNNBlock(512, 256, kernel_size=3, dilation=1, drop=0.2),\n            TempCNNBlock(256, 256, kernel_size=3, dilation=2, drop=0.2),\n            TempCNNBlock(256, 256, kernel_size=3, dilation=4, drop=0.2),\n            TempCNNBlock(256, 128, kernel_size=3, dilation=8, drop=0.2),\n        )\n        ## (B, 37, 128) -> (B, 18)\n        self.classifier_attention = MLPAttention(128)\n        self.classifier  = nn.Sequential(\n            nn.Linear(128, 64, bias=False),\n            nn.BatchNorm1d(64),\n            nn.Dropout(.4),\n            \n            nn.Linear(64, 32, bias=False),\n            nn.BatchNorm1d(32),\n            nn.Dropout(.3),\n\n            nn.Linear(32, 18)\n        )\n\n        ## Noise added during training for regularization\n        # self.noise_std = 0.25\n        # self.noise_activation = nn.SiLU()\n        \n    def forward(self, x):\n        # (B, T, C) -> (B, T//4, 512)\n        out = self.resnet(x)\n        # (B, T//4, 512) -> (B, T//4, 128)\n        out = self.temporalCNN(out)\n        # (B, T//4, 128) -> (B, 128)\n        out = self.classifier_attention(out)\n        # (B, 128) -> (B, 18)\n        out = self.classifier(out)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.339022Z","iopub.execute_input":"2025-06-29T20:21:00.339226Z","iopub.status.idle":"2025-06-29T20:21:00.357768Z","shell.execute_reply.started":"2025-06-29T20:21:00.339204Z","shell.execute_reply":"2025-06-29T20:21:00.357133Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def init_model_weights(model:nn.Module):\n    for module in model.modules():\n        if isinstance(module, (nn.Linear, nn.Conv1d)):\n            nn.init.kaiming_normal_(module.weight, nonlinearity=\"relu\")\n            if module.bias is not None:\n                nn.init.zeros_(module.bias)\n        elif isinstance(module, nn.BatchNorm1d):\n            nn.init.ones_(module.weight)\n            nn.init.zeros_(module.bias)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.358629Z","iopub.execute_input":"2025-06-29T20:21:00.358884Z","iopub.status.idle":"2025-06-29T20:21:00.376475Z","shell.execute_reply.started":"2025-06-29T20:21:00.358855Z","shell.execute_reply":"2025-06-29T20:21:00.375998Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataset Preparation","metadata":{}},{"cell_type":"code","source":"class MixupDataset(Dataset):\n    def __init__(self, X: np.ndarray, y: np.ndarray, alpha: float = 0.2):\n        \"\"\"\n        Args:\n            X (np.ndarray): Features (e.g., padded time series data).\n                            Expected shape (num_samples, timesteps, features).\n            y (np.ndarray): Labels (e.g., one-hot encoded or class indices).\n                            Expected shape (num_samples, num_classes) for one-hot,\n                            or (num_samples,) for class indices.\n            alpha (float): Alpha parameter for the Beta distribution used in Mixup.\n        \"\"\"\n        # Convert X and y to PyTorch tensors once\n        self.X = torch.tensor(X, dtype=torch.float32)\n        self.y = torch.tensor(y, dtype=torch.float32 if (y.ndim>1) else (torch.long)) # Use long for class indices\n        self.alpha = alpha\n\n    def __len__(self): return len(self.X)\n\n    def __getitem__(self, idx):\n        \"\"\"\n        Generates one sample of data for Mixup.\n        \"\"\"\n        x, y = self.X[idx], self.y[idx]\n        \n        if self.alpha > 0:\n            lam = np.random.beta(self.alpha, self.alpha)\n            rand_idx = np.random.randint(0, len(self.X))\n            x_rand, y_rand = self.X[rand_idx], self.y[rand_idx]\n            \n            x = lam * x + (1 - lam) * x_rand\n            y = lam * y + (1 - lam) * y_rand\n            \n        return x, y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.378587Z","iopub.execute_input":"2025-06-29T20:21:00.378785Z","iopub.status.idle":"2025-06-29T20:21:00.393315Z","shell.execute_reply.started":"2025-06-29T20:21:00.378765Z","shell.execute_reply":"2025-06-29T20:21:00.392629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def label_smoothing_loss(pred, target, smoothing=0.1):\n    \"\"\"Label smoothing loss\"\"\"\n    confidence = 1.0 - smoothing\n    log_probs = F.log_softmax(pred, dim=-1)\n    nll_loss = -log_probs.gather(dim=-1, index=target.unsqueeze(1))\n    nll_loss = nll_loss.squeeze(1)\n    smooth_loss = -log_probs.mean(dim=-1)\n    loss = confidence * nll_loss + smoothing * smooth_loss\n    return loss.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.394048Z","iopub.execute_input":"2025-06-29T20:21:00.394305Z","iopub.status.idle":"2025-06-29T20:21:00.407708Z","shell.execute_reply.started":"2025-06-29T20:21:00.394282Z","shell.execute_reply":"2025-06-29T20:21:00.407165Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train, X_val, y_train, y_val = train_test_split(\n        X.numpy(), y_ohe.numpy(), \n        test_size=0.25, random_state=SEED, stratify=y_int\n)\n\nX_train = torch.tensor(X_train, dtype=torch.float32)\ny_train = torch.tensor(y_train, dtype=torch.float32)\nX_val = torch.tensor(X_val, dtype=torch.float32)\ny_val = torch.tensor(y_val, dtype=torch.float32)\n\n\ncw_vals = compute_class_weight('balanced', \n                               classes=np.arange(len(label_encoder.classes_)),\n                               y=y_int)\nclass_weights = torch.FloatTensor(cw_vals).to(DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.408360Z","iopub.execute_input":"2025-06-29T20:21:00.408569Z","iopub.status.idle":"2025-06-29T20:21:00.825566Z","shell.execute_reply.started":"2025-06-29T20:21:00.408554Z","shell.execute_reply":"2025-06-29T20:21:00.824972Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class F1EarlyStopping:\n    def __init__(self, patience=10, verbose=False):\n        self.patience = patience\n        self.verbose = verbose\n        self.counter = 0\n        self.best_f1 = 0\n        self.early_stop = False\n\n    def __call__(self, model, current_f1):\n        if current_f1 > self.best_f1:\n            self.best_f1 = current_f1\n            self.counter = 0\n            torch.save(model.state_dict(), 'best_model.pth')\n        else:\n            self.counter += 1\n            if self.verbose:\n                print(f\"F1 EarlyStopping: {self.counter}/{self.patience}\\n\")\n            if self.counter >= self.patience:\n                self.early_stop = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.826634Z","iopub.execute_input":"2025-06-29T20:21:00.826928Z","iopub.status.idle":"2025-06-29T20:21:00.831696Z","shell.execute_reply.started":"2025-06-29T20:21:00.826908Z","shell.execute_reply":"2025-06-29T20:21:00.831056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train(model, train_loader, optimizer, class_weights, lr_scheduler=None):\n    model.train()\n    total_loss, total_correct, total_samples = 0.0, 0, 0\n        \n    for batch_x, batch_y in train_loader:\n        batch_x, batch_y = batch_x.to(DEVICE), batch_y.to(DEVICE)\n                    \n        logits = model(batch_x)\n                    \n        # Handle mixup targets\n        if batch_y.ndim == 2 and batch_y.shape[1] > 1:  # MixUp or one-hot\n            sample_weights = torch.sum(batch_y * class_weights.unsqueeze(0), dim=1)\n            log_probs = F.log_softmax(logits, dim=1)\n            loss_vec = -torch.sum(log_probs * batch_y, dim=1)  # (B,)\n            loss = (loss_vec * sample_weights).mean()\n            targets = batch_y.argmax(dim=1)\n        else:\n            targets = batch_y.long()\n            loss = label_smoothing_loss(logits, targets, smoothing=0.1)            \n        \n        ## Backpropagation\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        if lr_scheduler is not None:\n            scheduler.step()\n            \n        ## Accmulate loss and accuracy\n        total_loss += loss.item() * batch_x.size(0)\n        total_correct += (logits.argmax(dim=1) == targets).sum().item()\n        total_samples += batch_x.size(0)\n\n    ## Normalize loss and accuracy\n    train_loss = total_loss/total_samples\n    train_acc  = total_correct/total_samples\n\n    return train_loss, train_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.832436Z","iopub.execute_input":"2025-06-29T20:21:00.832660Z","iopub.status.idle":"2025-06-29T20:21:00.846702Z","shell.execute_reply.started":"2025-06-29T20:21:00.832631Z","shell.execute_reply":"2025-06-29T20:21:00.846204Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate(model, val_loader, lbl_encoder):\n    model.eval()\n    val_loss = 0.0\n    total_correct, total_samples = 0, 0\n\n    all_preds = []\n    all_targets = []\n\n    with torch.inference_mode():\n        for batch_x, batch_y in val_loader:\n            batch_x, batch_y = batch_x.to(DEVICE), batch_y.to(DEVICE)\n            logits = model(batch_x)\n\n            # For both loss and metrics, assume batch_y is one-hot or integer labels\n            if batch_y.ndim == 2:\n                targets = batch_y.argmax(dim=1)\n            else:\n                targets = batch_y\n\n            # Computes loss and predictions\n            loss = F.cross_entropy(logits, targets)\n            preds = logits.argmax(dim=1)\n\n            # Track batch metrics\n            val_loss += loss.item() * batch_x.size(0)\n            total_correct += (preds == targets).sum().item()\n            total_samples += batch_x.size(0)\n\n            all_preds.append(preds.cpu())\n            all_targets.append(targets.cpu())\n\n    # Stack all predictions and targets\n    y_pred_all = torch.cat(all_preds).numpy()\n    y_val_all  = torch.cat(all_targets).numpy()\n\n    # Compute custom hierarchical F1\n    val_f1 = F1_score(y_val_all, y_pred_all, lbl_encoder, choice=\"weighted\")\n    val_loss = val_loss / total_samples\n    val_acc  = total_correct / total_samples\n\n    return val_loss, val_acc, val_f1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.847232Z","iopub.execute_input":"2025-06-29T20:21:00.847406Z","iopub.status.idle":"2025-06-29T20:21:00.864949Z","shell.execute_reply.started":"2025-06-29T20:21:00.847392Z","shell.execute_reply":"2025-06-29T20:21:00.864277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_eval(epochs, model, train_loader, val_loader, optimizer, class_weights, lbl_encoder, lr_scheduler=None):\n    train_losses, train_accuracies = [], []\n    val_losses, val_accuracies = [], []\n    val_F1 = []\n    history = {}\n    \n    for epoch in range(epochs):\n        ## Trains model\n        train_loss, train_acc = train(model, train_loader, optimizer, class_weights, lr_scheduler)\n\n        ## Evaluates model\n        val_loss, val_acc, val_f1 = evaluate(model, val_loader, lbl_encoder)\n\n        ## Append metrics\n        train_losses.append(train_loss)\n        train_accuracies.append(train_acc)\n        val_losses.append(val_loss)\n        val_accuracies.append(val_acc)\n        val_F1.append(val_f1)\n\n        ## Checks early stopping\n        if early_stopper is not None:\n            early_stopper(model, val_f1)\n            if early_stopper.early_stop:\n                print(f\"\\nEarly stopping triggered at epoch {epoch+1}\\n\")\n                break\n                \n        ## Displays any result\n        print(f\"Epoch [{epoch+1}/{epochs}]\")\n        print(f\"Train Accuracy: {train_acc:.3f}     Train Loss: {train_loss:.3f}\")\n        print(f\"Val Accuracy:   {val_acc:.3f}       Val F1:     {val_f1:.2f}     Val Loss:   {val_loss:.3f}\\n\")\n\n    ## Save results to history dictionary\n    history[\"train_losses\"] = train_losses\n    history[\"train_accuracies\"] = train_accuracies\n    history[\"val_losses\"] = val_losses\n    history[\"val_accuracies\"] = val_accuracies\n    history[\"val_F1\"] = val_F1\n    return history","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.865625Z","iopub.execute_input":"2025-06-29T20:21:00.865937Z","iopub.status.idle":"2025-06-29T20:21:00.884436Z","shell.execute_reply.started":"2025-06-29T20:21:00.865911Z","shell.execute_reply":"2025-06-29T20:21:00.883778Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Training","metadata":{}},{"cell_type":"code","source":"# Data loaders\ntrain_dataset = MixupDataset(X_train, y_train, alpha=MIXUP_ALPHA)\nval_dataset = MixupDataset(X_val, y_val, alpha=0.0)  \n    \ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4, worker_init_fn=worker_init_fn)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=4, worker_init_fn=worker_init_fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.885010Z","iopub.execute_input":"2025-06-29T20:21:00.885233Z","iopub.status.idle":"2025-06-29T20:21:00.988690Z","shell.execute_reply.started":"2025-06-29T20:21:00.885206Z","shell.execute_reply":"2025-06-29T20:21:00.988117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = KinematicResTCNN(IMU_DIM).to(DEVICE)\ninit_model_weights(model)\n\noptimizer = AdamW(model.parameters(), lr=LR_INIT, weight_decay=WD)\n\nsteps_per_epoch = len(train_loader)\nscheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10*steps_per_epoch)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:00.989673Z","iopub.execute_input":"2025-06-29T20:21:00.989904Z","iopub.status.idle":"2025-06-29T20:21:03.762436Z","shell.execute_reply.started":"2025-06-29T20:21:00.989887Z","shell.execute_reply":"2025-06-29T20:21:03.761918Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"early_stopper = F1EarlyStopping(PATIENCE, verbose=True)\n\ntrain_history = train_eval(\n    EPOCHS, \n    model, \n    train_loader, \n    val_loader, \n    optimizer, \n    class_weights, \n    label_encoder, \n    scheduler\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T20:21:03.763101Z","iopub.execute_input":"2025-06-29T20:21:03.763485Z","execution_failed":"2025-06-29T20:22:11.316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"joblib.dump(train_history, \"train_history.joblib\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-29T20:22:11.317Z"}},"outputs":[],"execution_count":null}]}