{"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":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12127437,"sourceType":"datasetVersion","datasetId":7636549}],"dockerImageVersionId":31042,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install polars \n!pip install plotly","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:09.626222Z","iopub.execute_input":"2025-06-27T05:49:09.626441Z","iopub.status.idle":"2025-06-27T05:49:17.817515Z","shell.execute_reply.started":"2025-06-27T05:49:09.626412Z","shell.execute_reply":"2025-06-27T05:49:17.816751Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gc\nimport kaggle_evaluation.cmi_inference_server\nimport math\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport os\nimport pandas as pd\nimport polars as pl\nimport plotly.express as px\nimport plotly.io as pio\nimport random\nimport time\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n\nfrom glob import glob\nfrom torch.utils.data import DataLoader, Dataset, Subset\nfrom tqdm import tqdm\n\n!mkdir output\n# os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"0,1\"\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\npio.renderers.default = 'iframe'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:17.819646Z","iopub.execute_input":"2025-06-27T05:49:17.820308Z","iopub.status.idle":"2025-06-27T05:49:27.676218Z","shell.execute_reply.started":"2025-06-27T05:49:17.820282Z","shell.execute_reply":"2025-06-27T05:49:27.675508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class config:\n    BATCH_SIZE_TEST = 1\n    NUM_WORKERS = 0 # multiprocessing.cpu_count()\n    PRINT_FREQ = 20\n    SEED = 20\n\nclass paths:\n    OUTPUT_DIR = \"/kaggle/working/output\"\n    TEST_CSV = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv\"\n    TEST_DEMOGRAPHICS = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv\"\n    TRAIN_CSV = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\"\n    TRAIN_DEMOGRAPHICS = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:27.677136Z","iopub.execute_input":"2025-06-27T05:49:27.677413Z","iopub.status.idle":"2025-06-27T05:49:27.681720Z","shell.execute_reply.started":"2025-06-27T05:49:27.677380Z","shell.execute_reply":"2025-06-27T05:49:27.681150Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def format_for_scoring(df_preds: pd.DataFrame) ->tuple[pd.DataFrame, pd.DataFrame]: \n    solution = df_preds[[\"sequence_id\", \"y_true\"]].copy()\n    solution.columns = [\"id\", \"gesture\"]\n    solution[\"gesture\"] = solution[\"gesture\"].map(num_to_label)\n\n    submission = df_preds[[\"sequence_id\", \"y_pred\"]].copy()\n    submission.columns = [\"id\", \"gesture\"]\n    submission[\"gesture\"] = submission[\"gesture\"].map(num_to_label)\n    \n    return solution, submission\n\n\ndef seed_everything(seed: int):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed) \n    \n\ndef sep():\n    print(\"—\"*100)\n\n\nlabel_to_num = {\n    'Above ear - pull hair': 0,  # < ------- TARGETS START\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,  # < ------- TARGETS END\n    'Drink from bottle/cup': 8,  # < ------- NON-TARGETS START\n    'Feel around in tray and pull out an object': 8,\n    'Glasses on/off': 8,\n    'Pinch knee/leg skin': 8,\n    'Pull air toward your face': 8,\n    'Scratch knee/leg skin': 8,\n    'Text on phone': 8,\n    'Wave hello': 8,\n    'Write name in air': 8,\n    'Write name on leg': 8  # < ------- NON-TARGETS END\n}\ntype_to_num = {\"Target\": 1, \"Non-Target\":0}\nnum_to_label = {v: k for k, v in label_to_num.items()}\nnum_to_type = {v: k for k, v in type_to_num.items()}\nseed_everything(config.SEED)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:27.682569Z","iopub.execute_input":"2025-06-27T05:49:27.682844Z","iopub.status.idle":"2025-06-27T05:49:27.716690Z","shell.execute_reply.started":"2025-06-27T05:49:27.682821Z","shell.execute_reply":"2025-06-27T05:49:27.716223Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_test = pd.read_csv(paths.TEST_CSV)\ndf_test_demographics = pd.read_csv(paths.TEST_DEMOGRAPHICS)\n\nprint(f\"Test dataframe shape: {df_test.shape}\"), sep()\nprint(f\"Test demographics dataframe shape: {df_test_demographics.shape}\")\n\ndisplay(df_test.head())\ndisplay(df_test_demographics.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:27.717337Z","iopub.execute_input":"2025-06-27T05:49:27.717517Z","iopub.status.idle":"2025-06-27T05:49:27.821866Z","shell.execute_reply.started":"2025-06-27T05:49:27.717503Z","shell.execute_reply":"2025-06-27T05:49:27.821033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def min_max_scale(arr: np.ndarray) -> np.ndarray:\n    min_vals = np.nanmin(arr, axis=0)\n    max_vals = np.nanmax(arr, axis=0)\n    ranges = np.where(max_vals - min_vals == 0, 1, max_vals - min_vals)\n    scaled = (arr - min_vals) / ranges\n    return scaled\n\n\ndef standard_scale(arr: np.ndarray) -> np.ndarray:\n    means = np.nanmean(arr, axis=0)\n    stds = np.nanstd(arr, axis=0)\n    stds = np.where(stds == 0, 1, stds)  # Prevent division by zero for constant columns\n    scaled = (arr - means) / stds\n    return scaled\n\n\ndef pad_or_truncate(\n    arr: np.ndarray,\n    max_length: int = 200,\n    pad_value: int = 0,\n    mode: str = \"random\"  # \"regular\" or \"random\"\n) -> np.ndarray:\n    L, D = arr.shape\n\n    if L > max_length:\n        return arr[:max_length, :]\n\n    elif L < max_length:\n        if mode == \"regular\":\n            padding = np.full((max_length - L, D), pad_value)\n            return np.vstack((arr, padding))\n        \n        elif mode == \"random\":\n            total_padding = max_length - L\n            pad_start = np.random.randint(0, total_padding + 1)\n            pad_end = total_padding - pad_start\n\n            start_padding = np.full((pad_start, D), pad_value)\n            end_padding = np.full((pad_end, D), pad_value)\n\n            return np.vstack((start_padding, arr, end_padding))\n        \n        else:\n            raise ValueError(f\"Unknown mode: {mode}. Use 'regular' or 'random'.\")\n\n    else:\n        return arr\n\nimu_cols = [\"acc_x\", \"acc_y\", \"acc_z\", \"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"]\nX_test  = []\n\nfor sequence_id in tqdm(df_test.sequence_id.unique()):\n    ds = df_test[df_test[\"sequence_id\"] == sequence_id]\n    X = ds[imu_cols].values\n    X = pad_or_truncate(X)\n    X = np.concatenate((standard_scale(X[:, 0:3]), X[:, 3:]), axis=1)\n    X = np.where(np.isnan(X), 0.0, X)  # fill NaNs\n    X_test.append(X)\n\nX_test = np.array(X_test)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:27.822735Z","iopub.execute_input":"2025-06-27T05:49:27.823402Z","iopub.status.idle":"2025-06-27T05:49:27.848968Z","shell.execute_reply.started":"2025-06-27T05:49:27.823383Z","shell.execute_reply":"2025-06-27T05:49:27.848299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CustomDataset(Dataset):\n    def __init__(\n        self, config, df: pd.DataFrame, X: np.ndarray\n    ): \n        \n        self.config = config\n        self.df = df\n        self.X = X\n        self.indexes = self.df.sequence_id.unique()\n        \n    def __len__(self):\n        \"\"\"\n        Length of dataset.\n        \"\"\"\n        return len(self.indexes)\n        \n    def __getitem__(self, index):\n        \"\"\"\n        Get one item.\n        \"\"\"\n        sequence_id = self.indexes[index]\n        X = self.X[index]\n        output = {\n            \"X\": torch.tensor(X, dtype=torch.float32),\n            \"sequence_id\": sequence_id\n        }\n        return output ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:27.851728Z","iopub.execute_input":"2025-06-27T05:49:27.852229Z","iopub.status.idle":"2025-06-27T05:49:27.859126Z","shell.execute_reply.started":"2025-06-27T05:49:27.852193Z","shell.execute_reply":"2025-06-27T05:49:27.858551Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = CustomDataset(config, df_test, X_test)\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=config.BATCH_SIZE_TEST,\n    shuffle=False,\n    num_workers=config.NUM_WORKERS, pin_memory=True, drop_last=True\n)\nidx = np.random.choice(range(0, len(test_dataset)))\ncols = imu_cols\nX = test_dataset[idx][\"X\"]\nsequence_id = test_dataset[idx][\"sequence_id\"]\nN = X.shape[0]\ndf = pd.DataFrame(X, columns=cols)\ndf['step'] = range(N)\ndf_melted = df.melt(id_vars='step', var_name='sequence', value_name='value')\n\nfig = px.line(\n    df_melted,\n    x='step',\n    y='value',\n    color='sequence',\n    title=f\"Sequences for {sequence_id}\",\n    template='plotly_dark',\n    width=800,\n    height=600\n)\n\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:27.859814Z","iopub.execute_input":"2025-06-27T05:49:27.860066Z","iopub.status.idle":"2025-06-27T05:49:30.126002Z","shell.execute_reply.started":"2025-06-27T05:49:27.860048Z","shell.execute_reply":"2025-06-27T05:49:30.125255Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass EnhancedSEBlock(nn.Module):\n    def __init__(self, channels, reduction=8):\n        super().__init__()\n        self.avg_pool = nn.AdaptiveAvgPool1d(1)\n        self.max_pool = nn.AdaptiveMaxPool1d(1)\n        self.excitation = nn.Sequential(\n            nn.Linear(channels * 2, channels // reduction, bias=False),\n            nn.SiLU(inplace=True),  # Using SiLU (swish) as in TF reference\n            nn.Linear(channels // reduction, channels, bias=False),\n            nn.Sigmoid()\n        )\n    \n    def forward(self, x):\n        b, c, _ = x.size()\n        avg_y = self.avg_pool(x).view(b, c)\n        max_y = self.max_pool(x).view(b, c)\n        y = torch.cat([avg_y, max_y], dim=1)\n        y = self.excitation(y).view(b, c, 1)\n        return x * y.expand_as(x)\n\nclass MultiScaleConv1d(nn.Module):\n    \"\"\"Multi-scale temporal convolution block\"\"\"\n    def __init__(self, in_channels, out_channels, kernel_sizes=[3, 5, 7]):\n        super().__init__()\n        self.convs = nn.ModuleList()\n        for ks in kernel_sizes:\n            self.convs.append(nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, ks, padding=ks//2, bias=False),\n                nn.BatchNorm1d(out_channels),\n                nn.ReLU(inplace=True)\n            ))\n        \n    def forward(self, x):\n        outputs = [conv(x) for conv in self.convs]\n        return torch.cat(outputs, dim=1)\n\nclass ResidualSEBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size, pool_size=2, dropout=0.3):\n        super().__init__()\n        self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size, padding=kernel_size//2, bias=False)\n        self.bn1 = nn.BatchNorm1d(out_channels)\n        \n        self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size, padding=kernel_size//2, bias=False)\n        self.bn2 = nn.BatchNorm1d(out_channels)\n        \n        # Use the new EnhancedSEBlock\n        self.se = EnhancedSEBlock(out_channels, reduction=8)\n        \n        self.shortcut = nn.Sequential()\n        if in_channels != out_channels:\n            self.shortcut = nn.Sequential(\n                nn.Conv1d(in_channels, out_channels, 1, bias=False),\n                nn.BatchNorm1d(out_channels)\n            )\n        \n        self.pool = nn.MaxPool1d(pool_size)\n        self.dropout = nn.Dropout(dropout)\n        \n    def forward(self, x):\n        shortcut = self.shortcut(x)\n        \n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        \n        out = self.se(out)\n        \n        out += shortcut\n        out = F.relu(out)\n        \n        out = self.pool(out)\n        out = self.dropout(out)\n        \n        return out\n\nclass MetaFeatureExtractor(nn.Module):\n    def forward(self, x):\n        # x shape: (B, L, C)\n        mean = torch.mean(x, dim=1)\n        std = torch.std(x, dim=1)\n        max_val, _ = torch.max(x, dim=1)\n        min_val, _ = torch.min(x, dim=1)\n        \n        # Calculate slope: (last - first) / seq_len\n        seq_len = x.size(1)\n        if seq_len > 1:\n            slope = (x[:, -1, :] - x[:, 0, :]) / (seq_len - 1)\n        else:\n            slope = torch.zeros_like(x[:, 0, :])\n        \n        return torch.cat([mean, std, max_val, min_val, slope], dim=1)\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))\n        weights = F.softmax(scores.squeeze(-1), dim=1)\n        context = torch.sum(x * weights.unsqueeze(-1), dim=1)\n        return context\n\nclass CustomModel(nn.Module):\n    def __init__(self, num_classes):\n        super().__init__()\n        num_channels = 7 # Hardcoded for IMU data which has 7 channels\n        \n        # Meta feature extractor\n        self.meta_extractor = MetaFeatureExtractor()\n        self.meta_dense = nn.Sequential(\n            nn.Linear(5 * num_channels, 32),\n            nn.BatchNorm1d(32),\n            nn.ReLU(),\n            nn.Dropout(0.2)\n        )\n        \n        # Create multiple parallel branches for IMU channels\n        self.branches = nn.ModuleList([\n            nn.Sequential(\n                MultiScaleConv1d(1, 16, kernel_sizes=[3, 5, 7]), # out: 16*3=48 channels\n                ResidualSEBlock(48, 64, 3, dropout=0.3),\n                ResidualSEBlock(64, 64, 3, dropout=0.3)\n            ) for _ in range(num_channels)\n        ])\n        \n        # Bidirectional LSTM layer\n        self.bilstm = nn.LSTM(\n            input_size=64 * num_channels,\n            hidden_size=128,\n            num_layers=2, # Increased layers for more capacity\n            batch_first=True,\n            bidirectional=True,\n            dropout=0.2 # Add regularization\n        )\n        \n        # Multi-Head Attention layer after BiLSTM for enhanced temporal feature extraction\n        self.multihead_attn = nn.MultiheadAttention(\n            embed_dim=256,  # 2 * hidden_size for bidirectional\n            num_heads=8,\n            dropout=0.2,\n            batch_first=True\n        )\n        \n        # Attention pooling layer to aggregate sequence information\n        self.attention_pooling = AttentionLayer(256)\n        \n        # Output heads with meta feature injection\n        self.head_1 = nn.Sequential(\n            nn.Linear(256 + 32, 512),  # Attention pooled output + meta features\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, num_classes)\n        )\n        \n        self.head_2 = nn.Sequential(\n            nn.Linear(256 + 32, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, 1) # For regression task\n        )\n        \n    def forward(self, x: torch.Tensor):\n        \n        # Extract and process meta features\n        meta = self.meta_extractor(x)\n        meta_proj = self.meta_dense(meta)\n        \n        branch_outputs = []\n        for i in range(x.shape[2]):\n            # Process each channel through its dedicated branch\n            channel_input = x[:, :, i].unsqueeze(1)  # (B, 1, L)\n            processed = self.branches[i](channel_input)      # (B, 64, L/k)\n            branch_outputs.append(processed.transpose(1, 2)) # (B, L/k, 64)\n        \n        # Concatenate all branch outputs along feature dimension\n        combined = torch.cat(branch_outputs, dim=2)  # (B, L/k, 64*C)\n        \n        # Process through BiLSTM\n        bilstm_out, _ = self.bilstm(combined)  # (B, L/k, 256)\n        \n        # Apply Multi-Head Attention\n        attn_output, _ = self.multihead_attn(bilstm_out, bilstm_out, bilstm_out)\n        \n        # Add residual connection for stability\n        attended_bilstm = bilstm_out + attn_output\n\n        # Apply attention pooling to get a fixed-size vector\n        pooled_output = self.attention_pooling(attended_bilstm)  # (B, 256)\n        \n        # Combine with meta features\n        fused = torch.cat([pooled_output, meta_proj], dim=1)  # (B, 256 + 32)\n        \n        # Final predictions\n        z1 = self.head_1(fused)  # Classification\n        z2 = self.head_2(fused)  # Regression\n        return z1, z2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:30.126868Z","iopub.execute_input":"2025-06-27T05:49:30.127120Z","iopub.status.idle":"2025-06-27T05:49:30.149125Z","shell.execute_reply.started":"2025-06-27T05:49:30.127094Z","shell.execute_reply":"2025-06-27T05:49:30.148530Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n\n# EnhancedSEBlock, MetaFeatureExtractor, AttentionLayer\n\n\nclass ModelVariant_GRU(nn.Module):\n    def __init__(self, num_classes):\n        super().__init__()\n        num_channels = 7  # Hardcoded for IMU data\n\n        # 1. \n        self.meta_extractor = MetaFeatureExtractor()\n        self.meta_dense = nn.Sequential(\n            nn.Linear(5 * num_channels, 32),\n            nn.BatchNorm1d(32),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n        )\n\n        # 2. \n        self.branches = nn.ModuleList(\n            [\n                nn.Sequential(\n                    MultiScaleConv1d(1, 12, kernel_sizes=[3, 5, 7]),\n                    ResidualSEBlock(36, 48, 3, dropout=0.3),\n                    ResidualSEBlock(48, 48, 3, dropout=0.3),\n                )\n                for _ in range(num_channels)\n            ]\n        )\n\n        # 3. MultiHeadAttention\n        self.bigru = nn.GRU(\n            input_size=48 * num_channels,\n            hidden_size=128,  # hidden_size\n            num_layers=2,\n            batch_first=True,\n            bidirectional=True,\n            dropout=0.2,\n        )\n\n        # 4. Attention Pooling\n        self.attention_pooling = AttentionLayer(256)  # 128 * 2 for bidirectional\n\n        # 5. \n        self.head_1 = nn.Sequential(\n            nn.Linear(256 + 32, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512, num_classes),\n        )\n\n        self.head_2 = nn.Sequential(\n            nn.Linear(256 + 32, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512, 1),  # For regression task\n        )\n\n    def forward(self, x: torch.Tensor):\n        # Meta features\n        meta = self.meta_extractor(x)\n        meta_proj = self.meta_dense(meta)\n\n        # CNN branches\n        branch_outputs = []\n        for i in range(x.shape[2]):\n            channel_input = x[:, :, i].unsqueeze(1)\n            processed = self.branches[i](channel_input)\n            branch_outputs.append(processed.transpose(1, 2))\n\n        combined = torch.cat(branch_outputs, dim=2)\n\n        # BiGRU processing\n        gru_out, _ = self.bigru(combined)  # (B, L/k, 256)\n\n        # Attention pooling\n        pooled_output = self.attention_pooling(gru_out)  # (B, 256)\n\n        # Combine with meta features\n        fused = torch.cat([pooled_output, meta_proj], dim=1)\n\n        # Final predictions\n        z1 = self.head_1(fused)\n        z2 = self.head_2(fused)\n        return z1, z2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:30.150157Z","iopub.execute_input":"2025-06-27T05:49:30.150411Z","iopub.status.idle":"2025-06-27T05:49:30.179153Z","shell.execute_reply.started":"2025-06-27T05:49:30.150388Z","shell.execute_reply":"2025-06-27T05:49:30.178562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df_test = sequence.to_pandas()\n    imu_cols = [\"acc_x\", \"acc_y\", \"acc_z\", \"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"]\n    X_test, y_test, y_hard_test = [], [], []\n    \n    for sequence_id in tqdm(df_test.sequence_id.unique()):\n        ds = df_test[df_test[\"sequence_id\"] == sequence_id]\n        X = ds[imu_cols].values\n        X = pad_or_truncate(X)\n        X = np.concatenate((standard_scale(X[:, 0:3]), X[:, 3:]), axis=1)\n        X = np.where(np.isnan(X), 0.0, X)  # fill NaNs\n        X_test.append(X)\n\n    X_test = np.array(X_test)\n    model_paths = glob(\"/kaggle/input/mixup-gru-cv0-7358/*.pth\")\n    all_preds = []\n    for model_path in model_paths:\n        test_dataset = valid_dataset = CustomDataset(config, df_test, X_test)\n        test_loader = DataLoader(\n            test_dataset,\n            batch_size=config.BATCH_SIZE_TEST,\n            shuffle=False,\n            num_workers=config.NUM_WORKERS, \n            pin_memory=True, drop_last=False\n        )\n        model = ModelVariant_GRU(num_classes=9)\n        checkpoint = torch.load(model_path, map_location=device)\n        model.load_state_dict(checkpoint)\n        model.to(device)\n        model.eval()\n        softmax = nn.Softmax(dim=1)\n        \n        with tqdm(test_loader, unit=\"test_batch\", desc='Test') as tqdm_test_loader:\n            for step, batch in enumerate(tqdm_test_loader):\n                X = batch.pop(\"X\").to(device)\n                batch_size = X.size(0)\n                with torch.no_grad():\n                    y_preds, y_preds_hard = model(X)\n                y_preds = softmax(y_preds).to('cpu').numpy()\n                all_preds.append(y_preds)\n    all_preds = np.concatenate(all_preds)\n    all_preds = np.argmax(all_preds.mean(axis=0)).item()\n    prediction = num_to_label[all_preds]\n    return prediction\n\n# Launch inference server\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T05:49:30.179834Z","iopub.execute_input":"2025-06-27T05:49:30.180085Z","iopub.status.idle":"2025-06-27T05:49:36.162485Z","shell.execute_reply.started":"2025-06-27T05:49:30.180070Z","shell.execute_reply":"2025-06-27T05:49:36.161755Z"}},"outputs":[],"execution_count":null}]}