{"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":"gpu","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":"# Install required dependencies\n!pip install polars numpy scipy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:21.224464Z","iopub.execute_input":"2025-06-18T15:04:21.224713Z","iopub.status.idle":"2025-06-18T15:04:25.321617Z","shell.execute_reply.started":"2025-06-18T15:04:21.224693Z","shell.execute_reply":"2025-06-18T15:04:25.320947Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport 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\nfrom glob import glob\nfrom torch.utils.data import DataLoader, Dataset\nfrom tqdm import tqdm\nfrom sklearn.preprocessing import RobustScaler\nfrom scipy.fft import fft\nfrom scipy.stats import skew, kurtosis, entropy\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:25.323299Z","iopub.execute_input":"2025-06-18T15:04:25.323586Z","iopub.status.idle":"2025-06-18T15:04:32.388402Z","shell.execute_reply.started":"2025-06-18T15:04:25.323553Z","shell.execute_reply":"2025-06-18T15:04:32.387851Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create output directory\n!mkdir -p output\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\npio.renderers.default = 'iframe'\n\nclass config:\n    BATCH_SIZE_TEST = 16\n    NUM_WORKERS = 2\n    PRINT_FREQ = 20\n    SEED = 20\n    MAX_LENGTH = 200\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.389046Z","iopub.execute_input":"2025-06-18T15:04:32.389382Z","iopub.status.idle":"2025-06-18T15:04:32.773638Z","shell.execute_reply.started":"2025-06-18T15:04:32.389363Z","shell.execute_reply":"2025-06-18T15:04:32.773035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class 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\"\n\ndef 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    submission = df_preds[[\"sequence_id\", \"y_pred\"]].copy()\n    submission.columns = [\"id\", \"gesture\"]\n    submission[\"gesture\"] = submission[\"gesture\"].map(num_to_label)\n    return solution, submission\n\ndef seed_everything(seed: int):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.775600Z","iopub.execute_input":"2025-06-18T15:04:32.775842Z","iopub.status.idle":"2025-06-18T15:04:32.781240Z","shell.execute_reply.started":"2025-06-18T15:04:32.775822Z","shell.execute_reply":"2025-06-18T15:04:32.780485Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def sep():\n    print(\"—\"*100)\n\nlabel_to_num = {\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': 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\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\n# Load data\ndf_test = pd.read_csv(paths.TEST_CSV)\ndf_test_demographics = pd.read_csv(paths.TEST_DEMOGRAPHICS)\nprint(f\"Test dataframe shape: {df_test.shape}\"), sep()\nprint(f\"Test demographics dataframe shape: {df_test_demographics.shape}\")\ndisplay(df_test.head())\ndisplay(df_test_demographics.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.782118Z","iopub.execute_input":"2025-06-18T15:04:32.782348Z","iopub.status.idle":"2025-06-18T15:04:32.875772Z","shell.execute_reply.started":"2025-06-18T15:04:32.782325Z","shell.execute_reply":"2025-06-18T15:04:32.875216Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def robust_scale(arr: np.ndarray) -> np.ndarray:\n    scaler = RobustScaler()\n    arr = scaler.fit_transform(arr)\n    arr = np.where(np.isnan(arr), 0.0, arr)\n    return arr\n\ndef time_warp(arr: np.ndarray, sigma=0.2) -> np.ndarray:\n    \"\"\"Apply time warping augmentation\"\"\"\n    n = arr.shape[0]\n    time_points = np.linspace(0, 1, n)\n    random_warps = np.random.normal(loc=1.0, scale=sigma, size=n)\n    warped_time = np.cumsum(random_warps)\n    warped_time = warped_time / warped_time[-1] * n\n    indices = np.clip(np.round(warped_time).astype(int), 0, n-1)\n    return arr[indices]\n\ndef jitter(arr: np.ndarray, noise_level=0.01) -> np.ndarray:\n    \"\"\"Add jittering augmentation\"\"\"\n    noise = np.random.normal(0, noise_level, arr.shape)\n    return arr + noise\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.876389Z","iopub.execute_input":"2025-06-18T15:04:32.876595Z","iopub.status.idle":"2025-06-18T15:04:32.882291Z","shell.execute_reply.started":"2025-06-18T15:04:32.876572Z","shell.execute_reply":"2025-06-18T15:04:32.881537Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def pad_or_truncate(arr: np.ndarray, max_length: int = config.MAX_LENGTH, pad_value: float = 0.0) -> np.ndarray:\n    L, D = arr.shape\n    if L > max_length:\n        start_idx = np.random.randint(0, L - max_length + 1)\n        return arr[start_idx:start_idx + max_length, :]\n    elif L < max_length:\n        pad_width = max_length - L\n        padding = np.full((pad_width, D), pad_value)\n        return np.vstack((arr, padding))\n    return arr\n\ndef extract_freq_features(arr, max_length=config.MAX_LENGTH):\n    \"\"\"Extract FFT-based frequency features for all 7 channels\"\"\"\n    freq_features = []\n    for i in range(arr.shape[1]):  # For all 7 channels\n        fft_result = np.abs(fft(arr[:, i]))\n        freq_features.append(fft_result[:max_length//2].mean())  # Mean of first half of FFT\n    return np.array(freq_features)\n\nimu_cols = [\"acc_x\", \"acc_y\", \"acc_z\", \"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"]\nX_test = []\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    if np.random.rand() < 0.5:\n        X = time_warp(X)\n        X = jitter(X, noise_level=0.01)\n    X = pad_or_truncate(X)\n    X_scaled = X  # Use original 7 channels\n    freq_feats = extract_freq_features(X_scaled, config.MAX_LENGTH)  # 7 features\n    # Pad to 11 features to align with intended 18 channels\n    freq_feats = np.pad(freq_feats, (0, 11 - len(freq_feats)), mode='constant')\n    freq_feats_expanded = np.tile(freq_feats, (X_scaled.shape[0], 1))  # (200, 11)\n    X_test.append(np.hstack((X_scaled, freq_feats_expanded)))  # (200, 18)\n\nX_test = np.array(X_test)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.882962Z","iopub.execute_input":"2025-06-18T15:04:32.883237Z","iopub.status.idle":"2025-06-18T15:04:32.908695Z","shell.execute_reply.started":"2025-06-18T15:04:32.883220Z","shell.execute_reply":"2025-06-18T15:04:32.907998Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CustomDataset(Dataset):\n    def __init__(self, config, df: pd.DataFrame, X: np.ndarray):\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        return len(self.indexes)\n        \n    def __getitem__(self, index):\n        sequence_id = self.indexes[index]\n        return {\n            \"X\": torch.tensor(self.X[index], dtype=torch.float32),\n            \"sequence_id\": sequence_id\n        }\n\ntest_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,\n    pin_memory=True,\n    prefetch_factor=2,\n    drop_last=False\n)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.909286Z","iopub.execute_input":"2025-06-18T15:04:32.909447Z","iopub.status.idle":"2025-06-18T15:04:32.914753Z","shell.execute_reply.started":"2025-06-18T15:04:32.909433Z","shell.execute_reply":"2025-06-18T15:04:32.914242Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class 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),\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.915415Z","iopub.execute_input":"2025-06-18T15:04:32.915648Z","iopub.status.idle":"2025-06-18T15:04:32.933832Z","shell.execute_reply.started":"2025-06-18T15:04:32.915627Z","shell.execute_reply":"2025-06-18T15:04:32.933223Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nclass MultiScaleConv1d(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_sizes=[3, 5, 7]):\n        super().__init__()\n        self.convs = nn.ModuleList([\n            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            ) for ks in kernel_sizes\n        ])\n        \n    def forward(self, x):\n        outputs = [conv(x) for conv in self.convs]\n        return torch.cat(outputs, dim=1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.935485Z","iopub.execute_input":"2025-06-18T15:04:32.935673Z","iopub.status.idle":"2025-06-18T15:04:32.950211Z","shell.execute_reply.started":"2025-06-18T15:04:32.935659Z","shell.execute_reply":"2025-06-18T15:04:32.949703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\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        self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size, padding=kernel_size//2, bias=False)\n        self.bn2 = nn.BatchNorm1d(out_channels)\n        self.se = EnhancedSEBlock(out_channels, reduction=8)\n        self.shortcut = nn.Sequential(\n            nn.Conv1d(in_channels, out_channels, 1, bias=False),\n            nn.BatchNorm1d(out_channels)\n        ) if in_channels != out_channels else nn.Sequential()\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        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out = self.se(out)\n        out += shortcut\n        out = F.relu(out)\n        out = self.pool(out)\n        out = self.dropout(out)\n        return out\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.950902Z","iopub.execute_input":"2025-06-18T15:04:32.951150Z","iopub.status.idle":"2025-06-18T15:04:32.968312Z","shell.execute_reply.started":"2025-06-18T15:04:32.951125Z","shell.execute_reply":"2025-06-18T15:04:32.967751Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MetaFeatureExtractor(nn.Module):\n    def forward(self, x):\n        # Ensure x is on the correct device\n        x = x.to(device)\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        seq_len = x.size(1)\n        slope = (x[:, -1, :] - x[:, 0, :]) / (seq_len - 1) if seq_len > 1 else torch.zeros_like(x[:, 0, :]).to(device)\n        # Move to CPU for NumPy operations, then back to device\n        x_cpu = x.cpu()\n        skewness = torch.tensor(skew(x_cpu.numpy(), axis=1), dtype=torch.float32).to(device)\n        kurt = torch.tensor(kurtosis(x_cpu.numpy(), axis=1), dtype=torch.float32).to(device)\n        ent = torch.tensor([entropy(np.histogram(x_cpu[:, i].numpy(), bins=10)[0]) for i in range(x.size(2))], dtype=torch.float32).to(device).unsqueeze(0)\n        return torch.cat([mean, std, max_val, min_val, slope, skewness, kurt, ent], dim=1)\n\nclass MultiHeadAttention(nn.Module):\n    def __init__(self, hidden_dim, num_heads=4):\n        super().__init__()\n        self.hidden_dim = hidden_dim\n        self.num_heads = num_heads\n        self.head_dim = hidden_dim // num_heads\n        self.query = nn.Linear(hidden_dim, hidden_dim)\n        self.key = nn.Linear(hidden_dim, hidden_dim)\n        self.value = nn.Linear(hidden_dim, hidden_dim)\n        self.out = nn.Linear(hidden_dim, hidden_dim)\n        \n    def forward(self, x):\n        batch, seq_len, hidden = x.size()\n        q = self.query(x).view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2)\n        k = self.key(x).view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2)\n        v = self.value(x).view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2)\n        \n        scores = torch.matmul(q, k.transpose(-2, -1)) / np.sqrt(self.head_dim)\n        attn = F.softmax(scores, dim=-1)\n        out = torch.matmul(attn, v).transpose(1, 2).contiguous().view(batch, seq_len, self.hidden_dim)\n        return self.out(out)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.968998Z","iopub.execute_input":"2025-06-18T15:04:32.969686Z","iopub.status.idle":"2025-06-18T15:04:32.984697Z","shell.execute_reply.started":"2025-06-18T15:04:32.969668Z","shell.execute_reply":"2025-06-18T15:04:32.984115Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ModelVariant_GRU(nn.Module):\n    def __init__(self, num_classes):\n        super().__init__()\n        num_channels = 18  # 7 IMU + 11 freq features\n        self.meta_extractor = MetaFeatureExtractor()\n        self.meta_dense = nn.Sequential(\n            nn.Linear(8 * num_channels, 64),  # 8 features per channel * 18 channels = 144 -> 64\n            nn.BatchNorm1d(64),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n        )\n        self.branches = nn.ModuleList([\n            nn.Sequential(\n                MultiScaleConv1d(1, 12, kernel_sizes=[3, 5, 7]),  # Output: 36 channels\n                ResidualSEBlock(36, 48, 3, dropout=0.3),         # Output: 48 channels\n                ResidualSEBlock(48, 48, 3, dropout=0.3),         # Output: 48 channels\n            ) for _ in range(num_channels)\n        ])\n        self.bigru = nn.GRU(\n            input_size=48 * num_channels,  # 48 * 18 = 864\n            hidden_size=256,\n            num_layers=2,\n            batch_first=True,\n            bidirectional=True,\n            dropout=0.3,\n        )\n        self.attention = MultiHeadAttention(hidden_dim=512, num_heads=4)  # Expects 512 input\n        self.attention_pooling = nn.Linear(512, 1)  # Reduces to (batch_size, 1)\n        self.head_1 = nn.Sequential(\n            nn.Linear(512 + 64, 512),  # 512 (pooled) + 64 (meta) = 576\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.6),\n            nn.Linear(512, num_classes),\n        )\n        self.head_2 = nn.Sequential(\n            nn.Linear(512 + 64, 512),  # 512 (pooled) + 64 (meta) = 576\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.6),\n            nn.Linear(512, 1),\n        )\n\n    def forward(self, x: torch.Tensor):\n        x = x.to(device)\n        meta = self.meta_extractor(x).to(device)\n        meta_proj = self.meta_dense(meta)  # Shape: (batch_size, 64)\n        branch_outputs = []\n        for i in range(x.shape[2]):\n            channel_input = x[:, :, i].unsqueeze(1).to(device)\n            processed = self.branches[i](channel_input)  # Shape: (batch_size, 48, seq_len)\n            branch_outputs.append(processed.transpose(1, 2))  # To (batch_size, seq_len, 48)\n        combined = torch.cat(branch_outputs, dim=2).to(device)  # Shape: (batch_size, seq_len, 864)\n        gru_out, _ = self.bigru(combined)  # Shape: (batch_size, seq_len, 512)\n        attn_out = self.attention(gru_out)  # Shape: (batch_size, seq_len, 512)\n        pooled_output = torch.tanh(self.attention_pooling(attn_out)).squeeze(-1)  # Shape: (batch_size, seq_len)\n        # Ensure pooled_output is reduced to (batch_size, 512) by applying mean over seq_len\n        pooled_output = torch.mean(attn_out, dim=1)  # Shape: (batch_size, 512)\n        fused = torch.cat([pooled_output, meta_proj], dim=1)  # Shape: (batch_size, 576)\n        z1 = self.head_1(fused)  # Shape: (batch_size, num_classes)\n        z2 = self.head_2(fused)  # Shape: (batch_size, 1)\n        return z1, z2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T15:04:32.985387Z","iopub.execute_input":"2025-06-18T15:04:32.985603Z","iopub.status.idle":"2025-06-18T15:04:33.003080Z","shell.execute_reply.started":"2025-06-18T15:04:32.985578Z","shell.execute_reply":"2025-06-18T15:04:33.002360Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df_test = sequence.to_pandas()\n    X_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        if np.random.rand() < 0.5:\n            X = time_warp(X)\n            X = jitter(X, noise_level=0.01)\n        X = pad_or_truncate(X)\n        X_scaled = X  # Use original 7 channels\n        freq_feats = extract_freq_features(X_scaled, config.MAX_LENGTH)  # 7 features\n        # Pad to 11 features to align with intended 18 channels\n        freq_feats = np.pad(freq_feats, (0, 11 - len(freq_feats)), mode='constant')\n        freq_feats_expanded = np.tile(freq_feats, (X_scaled.shape[0], 1))  # (200, 11)\n        X_test.append(np.hstack((X_scaled, freq_feats_expanded)))  # (200, 18)\n    \n    X_test = np.array(X_test)\n    test_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,\n        prefetch_factor=2,\n        drop_last=False\n    )\n    \n    model_paths = glob(\"/kaggle/input/mixup-gru-cv0-7358/*.pth\")[:5]\n    all_preds = []\n    model_weights = [1.0 / len(model_paths)] * len(model_paths)\n    for model_path in model_paths:\n        model = ModelVariant_GRU(num_classes=9)\n        checkpoint = torch.load(model_path, map_location=device)\n        model_dict = model.state_dict()\n        pretrained_dict = {k: v for k, v in checkpoint.items() if k in model_dict and model_dict[k].shape == v.shape}\n        model_dict.update(pretrained_dict)\n        model.load_state_dict(model_dict)\n        model.to(device)\n        model.eval()\n        softmax = nn.Softmax(dim=1)\n        \n        mc_preds = []\n        for _ in range(5):\n            with tqdm(test_loader, unit=\"test_batch\", desc='Test') as tqdm_test_loader:\n                for batch in tqdm_test_loader:\n                    X = batch[\"X\"].to(device)\n                    with torch.no_grad():\n                        y_preds, y_preds_hard = model(X)\n                    # Apply softmax on GPU, then move to CPU and convert to numpy\n                    y_preds = softmax(y_preds).cpu().numpy()\n                    mc_preds.append(y_preds)\n        all_preds.append(np.mean(mc_preds, axis=0))\n    \n    weighted_preds = np.average(all_preds, axis=0, weights=model_weights)\n    threshold = 0.6\n    final_pred = np.argmax(weighted_preds, axis=1)[0] if weighted_preds[0].max() > threshold else 8\n    prediction = num_to_label[final_pred]\n    return prediction\n\n# Launch inference server\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\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-18T15:04:33.003881Z","iopub.execute_input":"2025-06-18T15:04:33.004067Z","iopub.status.idle":"2025-06-18T15:04:45.889442Z","shell.execute_reply.started":"2025-06-18T15:04:33.004052Z","shell.execute_reply":"2025-06-18T15:04:45.888542Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}