{"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"}],"dockerImageVersionId":31041,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\n\nimport pandas as pd\nimport polars as pl\nimport numpy as np\n\nimport kaggle_evaluation.cmi_inference_server\n\nimport random\n\nimport sklearn\nfrom sklearn.decomposition import PCA\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.model_selection import train_test_split\n\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch import nn\nimport torch.nn.functional as F\nfrom torch.nn.utils.rnn import pad_sequence\nimport torchmetrics\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ndevice","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:40:23.224975Z","iopub.execute_input":"2025-06-22T11:40:23.226015Z","iopub.status.idle":"2025-06-22T11:40:36.795753Z","shell.execute_reply.started":"2025-06-22T11:40:23.225977Z","shell.execute_reply":"2025-06-22T11:40:36.79503Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The evaluation API requires that you set up a server which will respond to inference requests. We have already defined the server; you just need write the predict function. When we evaluate your submission on the hidden test set the client defined in the gateway will run in a different container with direct access to the hidden test set and hand off the one sequence at a time.\n\nYour code will always have access to the published copies of the files.","metadata":{"execution":{"iopub.status.busy":"2025-05-29T22:29:18.955791Z","iopub.execute_input":"2025-05-29T22:29:18.956193Z","iopub.status.idle":"2025-05-29T22:29:18.965179Z","shell.execute_reply.started":"2025-05-29T22:29:18.956169Z","shell.execute_reply":"2025-05-29T22:29:18.963797Z"}}},{"cell_type":"code","source":"def seed_everything(seed):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    random.seed(seed)\n    np.random.seed(seed)\nseed_everything(seed=42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:40:36.796958Z","iopub.execute_input":"2025-06-22T11:40:36.797408Z","iopub.status.idle":"2025-06-22T11:40:36.801388Z","shell.execute_reply.started":"2025-06-22T11:40:36.797388Z","shell.execute_reply":"2025-06-22T11:40:36.800682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ntest = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\ntrain_demographics = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\ntest_demographics = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv')\ntrain.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:40:36.802089Z","iopub.execute_input":"2025-06-22T11:40:36.80228Z","iopub.status.idle":"2025-06-22T11:41:05.198813Z","shell.execute_reply.started":"2025-06-22T11:40:36.802265Z","shell.execute_reply":"2025-06-22T11:41:05.198175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train['sequence_id'].value_counts().plot(kind='hist')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:41:05.200273Z","iopub.execute_input":"2025-06-22T11:41:05.200492Z","iopub.status.idle":"2025-06-22T11:41:05.204192Z","shell.execute_reply.started":"2025-06-22T11:41:05.200461Z","shell.execute_reply":"2025-06-22T11:41:05.203341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# low, high = train['sequence_id'].value_counts().quantile([0.01, 0.99])\n# low, high","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:41:05.204943Z","iopub.execute_input":"2025-06-22T11:41:05.205186Z","iopub.status.idle":"2025-06-22T11:41:05.254835Z","shell.execute_reply.started":"2025-06-22T11:41:05.205166Z","shell.execute_reply":"2025-06-22T11:41:05.254197Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train.columns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:41:05.255463Z","iopub.execute_input":"2025-06-22T11:41:05.255624Z","iopub.status.idle":"2025-06-22T11:41:05.268421Z","shell.execute_reply.started":"2025-06-22T11:41:05.255611Z","shell.execute_reply":"2025-06-22T11:41:05.267944Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# grouped_gestures = train.groupby('sequence_type')['gesture'].apply(lambda x: list(set(x))).reset_index()\n# grouped_gestures","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:41:05.269042Z","iopub.execute_input":"2025-06-22T11:41:05.269198Z","iopub.status.idle":"2025-06-22T11:41:05.284109Z","shell.execute_reply.started":"2025-06-22T11:41:05.269186Z","shell.execute_reply":"2025-06-22T11:41:05.283393Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# exploded_df = grouped_gestures.explode('gesture').reset_index(drop=True)\n# exploded_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:41:05.284737Z","iopub.execute_input":"2025-06-22T11:41:05.284964Z","iopub.status.idle":"2025-06-22T11:41:05.298701Z","shell.execute_reply.started":"2025-06-22T11:41:05.284939Z","shell.execute_reply":"2025-06-22T11:41:05.297975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tof_cols = [f'tof_{s}_v{i}' for s in range(1, 6) for i in range(64)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:41:05.299595Z","iopub.execute_input":"2025-06-22T11:41:05.299854Z","iopub.status.idle":"2025-06-22T11:41:05.313979Z","shell.execute_reply.started":"2025-06-22T11:41:05.299825Z","shell.execute_reply":"2025-06-22T11:41:05.31328Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.drop(columns=['sequence_type', 'orientation', 'row_id', 'sequence_counter', 'behavior', 'phase'], inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:41:05.316287Z","iopub.execute_input":"2025-06-22T11:41:05.316483Z","iopub.status.idle":"2025-06-22T11:41:05.7792Z","shell.execute_reply.started":"2025-06-22T11:41:05.316468Z","shell.execute_reply":"2025-06-22T11:41:05.778621Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = train.groupby('sequence_id')[train.columns.drop('sequence_id')].agg(list).reset_index()\n# y_train = train['gesture'].apply(lambda x: x[0])\n# X_train = train.drop(columns=['gesture'])\n# X_train['subject'] = X_train['subject'].apply(lambda x: x[0])\ntrain['subject'] = train['subject'].apply(lambda x: x[0])\ntrain['gesture'] = train['gesture'].apply(lambda x: x[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:41:05.779879Z","iopub.execute_input":"2025-06-22T11:41:05.780064Z","iopub.status.idle":"2025-06-22T11:42:08.815686Z","shell.execute_reply.started":"2025-06-22T11:41:05.780048Z","shell.execute_reply":"2025-06-22T11:42:08.815098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = train_demographics.merge(train, on='subject', how='right')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:08.816522Z","iopub.execute_input":"2025-06-22T11:42:08.81671Z","iopub.status.idle":"2025-06-22T11:42:08.997612Z","shell.execute_reply.started":"2025-06-22T11:42:08.816696Z","shell.execute_reply":"2025-06-22T11:42:08.997061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_dataframe(df, dynamic_cols, static_cols, scaler=None, is_train=True):\n    # Static features\n    if is_train:\n        scaler = StandardScaler()\n        static_data = scaler.fit_transform(df[static_cols])\n    else:\n        static_data = scaler.transform(df[static_cols])\n    \n    static_tensors = torch.tensor(static_data, dtype=torch.float32)\n\n    # Dynamic features (per row)\n    sequence_tensors = []\n    seq_lengths = []\n\n    for _, row in df.iterrows():\n        # Each timestep = a vector across all dynamic columns\n        seq = list(zip(*[row[col] for col in dynamic_cols]))  # shape: (seq_len, num_dynamic_cols)\n        seq_tensor = torch.tensor(seq, dtype=torch.float32)\n        sequence_tensors.append(seq_tensor)\n        seq_lengths.append(len(seq))\n\n    return static_tensors, sequence_tensors, seq_lengths, scaler","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:08.998362Z","iopub.execute_input":"2025-06-22T11:42:08.998571Z","iopub.status.idle":"2025-06-22T11:42:09.003758Z","shell.execute_reply.started":"2025-06-22T11:42:08.998554Z","shell.execute_reply":"2025-06-22T11:42:09.003164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class HybridDataset(Dataset):\n    def __init__(self, static_data, sequence_data, seq_lengths, labels=None):\n        self.static_data = static_data\n        self.sequence_data = sequence_data\n        self.seq_lengths = seq_lengths\n        self.labels = labels\n\n    def __len__(self):\n        return len(self.static_data)\n\n    def __getitem__(self, idx):\n        sample = {\n            'static': self.static_data[idx],\n            'sequence': self.sequence_data[idx],\n            'seq_length': self.seq_lengths[idx]\n        }\n        if self.labels is not None:\n            sample['label'] = self.labels[idx]\n        return sample","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.004506Z","iopub.execute_input":"2025-06-22T11:42:09.005003Z","iopub.status.idle":"2025-06-22T11:42:09.023084Z","shell.execute_reply.started":"2025-06-22T11:42:09.004942Z","shell.execute_reply":"2025-06-22T11:42:09.022395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def collate_fn(batch):\n    static_batch = torch.stack([item['static'] for item in batch])\n    sequence_batch = torch.stack([item['sequence'] for item in batch])  # no pad_sequence needed\n    seq_lengths = torch.tensor([item['seq_length'] for item in batch], dtype=torch.long)\n\n    out = {\n        'static': static_batch,          # [B, static_dim]\n        'sequence': sequence_batch,      # [B, 210, seq_feat_dim]\n        'seq_length': seq_lengths        # [B]\n    }\n\n    if 'label' in batch[0]:\n        out['label'] = torch.stack([item['label'] for item in batch])  # [B]\n    return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.023677Z","iopub.execute_input":"2025-06-22T11:42:09.023886Z","iopub.status.idle":"2025-06-22T11:42:09.03847Z","shell.execute_reply.started":"2025-06-22T11:42:09.023871Z","shell.execute_reply":"2025-06-22T11:42:09.038006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.drop(columns=['subject', 'sequence_id'], inplace = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.039114Z","iopub.execute_input":"2025-06-22T11:42:09.039306Z","iopub.status.idle":"2025-06-22T11:42:09.102936Z","shell.execute_reply.started":"2025-06-22T11:42:09.039284Z","shell.execute_reply":"2025-06-22T11:42:09.10211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train, X_test, y_train, y_test = train_test_split(train.drop(columns=['gesture']), \n                                                    train['gesture'],\n                                                    test_size = 0.2, \n                                                    random_state = 42) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.103608Z","iopub.execute_input":"2025-06-22T11:42:09.103975Z","iopub.status.idle":"2025-06-22T11:42:09.232264Z","shell.execute_reply.started":"2025-06-22T11:42:09.103957Z","shell.execute_reply":"2025-06-22T11:42:09.2317Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label_encoder = LabelEncoder()\ntrain_labels_string = y_train\ntrain_targets_encoded = label_encoder.fit_transform(train_labels_string)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.23296Z","iopub.execute_input":"2025-06-22T11:42:09.233159Z","iopub.status.idle":"2025-06-22T11:42:09.238578Z","shell.execute_reply.started":"2025-06-22T11:42:09.233143Z","shell.execute_reply":"2025-06-22T11:42:09.238022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_labels_string = y_test\ntest_targets_encoded = label_encoder.fit_transform(test_labels_string)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.239348Z","iopub.execute_input":"2025-06-22T11:42:09.239609Z","iopub.status.idle":"2025-06-22T11:42:09.256551Z","shell.execute_reply.started":"2025-06-22T11:42:09.239592Z","shell.execute_reply":"2025-06-22T11:42:09.255978Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Identify dynamic and static columns\ndynamic_cols = [col for col in X_train.columns if isinstance(X_train[col].iloc[0], list)]\nstatic_cols = [col for col in X_train.columns if col not in dynamic_cols]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.257234Z","iopub.execute_input":"2025-06-22T11:42:09.257564Z","iopub.status.idle":"2025-06-22T11:42:09.281226Z","shell.execute_reply.started":"2025-06-22T11:42:09.257547Z","shell.execute_reply":"2025-06-22T11:42:09.28069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scaler = StandardScaler()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.282032Z","iopub.execute_input":"2025-06-22T11:42:09.282355Z","iopub.status.idle":"2025-06-22T11:42:09.297187Z","shell.execute_reply.started":"2025-06-22T11:42:09.282311Z","shell.execute_reply":"2025-06-22T11:42:09.296474Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TRAIN\ntrain_static, train_seq, train_lens, scaler = preprocess_dataframe(X_train, dynamic_cols, static_cols, scaler=scaler, is_train=True)\ntrain_labels = torch.tensor(train_targets_encoded, dtype = torch.long)  # .long() for classification","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:09.297951Z","iopub.execute_input":"2025-06-22T11:42:09.298618Z","iopub.status.idle":"2025-06-22T11:42:36.119607Z","shell.execute_reply.started":"2025-06-22T11:42:09.298594Z","shell.execute_reply":"2025-06-22T11:42:36.118833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define max sequence length\nMAX_SEQ_LEN = 210\n\n# These will be your processed lists\ntrain_sequence_tensors = []\ntrain_seq_lengths = []\n\n# Loop over DataFrame\nfor _, row in X_train.iterrows():\n    cleaned_dynamic_cols = []\n    for col in dynamic_cols:\n        cleaned_col = [0.0 if pd.isna(x) else x for x in row[col]]\n        cleaned_dynamic_cols.append(cleaned_col)\n    \n    seq = list(zip(*cleaned_dynamic_cols))  # shape: (seq_len, num_dynamic_cols)\n    seq_tensor = torch.tensor(seq, dtype=torch.float32)\n\n    # Truncate\n    if len(seq_tensor) > MAX_SEQ_LEN:\n        seq_tensor = seq_tensor[:MAX_SEQ_LEN]\n\n    # Record actual (capped) sequence length\n    seq_len = min(len(seq_tensor), MAX_SEQ_LEN)\n\n    # Pad if necessary\n    if len(seq_tensor) < MAX_SEQ_LEN:\n        pad_len = MAX_SEQ_LEN - len(seq_tensor)\n        pad_tensor = torch.zeros((pad_len, seq_tensor.shape[1]))\n        seq_tensor = torch.cat([seq_tensor, pad_tensor], dim=0)\n\n    # Save\n    train_sequence_tensors.append(seq_tensor)\n    train_seq_lengths.append(seq_len)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:42:36.120462Z","iopub.execute_input":"2025-06-22T11:42:36.120727Z","iopub.status.idle":"2025-06-22T11:43:30.746422Z","shell.execute_reply.started":"2025-06-22T11:42:36.120689Z","shell.execute_reply":"2025-06-22T11:43:30.745832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = HybridDataset(train_static, train_sequence_tensors, train_seq_lengths, labels = train_labels)\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, collate_fn=collate_fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:43:30.747117Z","iopub.execute_input":"2025-06-22T11:43:30.74757Z","iopub.status.idle":"2025-06-22T11:43:30.751474Z","shell.execute_reply.started":"2025-06-22T11:43:30.747551Z","shell.execute_reply":"2025-06-22T11:43:30.750831Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define max sequence length\nMAX_SEQ_LEN = 210\n\n# These will be your processed lists\ntest_sequence_tensors = []\ntest_seq_lengths = []\n\n# Loop over DataFrame\nfor _, row in X_test.iterrows():\n    cleaned_dynamic_cols = []\n    for col in dynamic_cols:\n        cleaned_col = [0.0 if pd.isna(x) else x for x in row[col]]\n        cleaned_dynamic_cols.append(cleaned_col)\n    \n    seq = list(zip(*cleaned_dynamic_cols))  # shape: (seq_len, num_dynamic_cols)\n    seq_tensor = torch.tensor(seq, dtype=torch.float32)\n\n    # Truncate\n    if len(seq_tensor) > MAX_SEQ_LEN:\n        seq_tensor = seq_tensor[:MAX_SEQ_LEN]\n\n    # Record actual (capped) sequence length\n    seq_len = min(len(seq_tensor), MAX_SEQ_LEN)\n\n    # Pad if necessary\n    if len(seq_tensor) < MAX_SEQ_LEN:\n        pad_len = MAX_SEQ_LEN - len(seq_tensor)\n        pad_tensor = torch.zeros((pad_len, seq_tensor.shape[1]))\n        seq_tensor = torch.cat([seq_tensor, pad_tensor], dim=0)\n\n    # Save\n    test_sequence_tensors.append(seq_tensor)\n    test_seq_lengths.append(seq_len)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:43:30.752292Z","iopub.execute_input":"2025-06-22T11:43:30.752546Z","iopub.status.idle":"2025-06-22T11:43:44.612273Z","shell.execute_reply.started":"2025-06-22T11:43:30.752525Z","shell.execute_reply":"2025-06-22T11:43:44.611529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TEST\ntest_static, test_seq, test_lens, _ = preprocess_dataframe(X_test, dynamic_cols, static_cols, scaler=scaler, is_train=False)\ntest_labels = torch.tensor(test_targets_encoded, dtype=torch.long)\n\ntest_dataset = HybridDataset(test_static, test_sequence_tensors, test_seq_lengths, labels = test_labels)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=True, collate_fn=collate_fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:43:44.613139Z","iopub.execute_input":"2025-06-22T11:43:44.613359Z","iopub.status.idle":"2025-06-22T11:43:51.276977Z","shell.execute_reply.started":"2025-06-22T11:43:44.613334Z","shell.execute_reply":"2025-06-22T11:43:51.276443Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\nclass HybridModel(nn.Module):\n    def __init__(self, static_dim, seq_feat_dim, hidden_dim, num_classes):\n        super(HybridModel, self).__init__()\n\n        self.static_encoder = nn.Sequential(\n            nn.Linear(static_dim, hidden_dim),\n            nn.ReLU(),\n            nn.LayerNorm(hidden_dim),\n            nn.Dropout(0.3)\n        )\n\n        self.lstm = nn.LSTM(\n            input_size=seq_feat_dim,\n            hidden_size=hidden_dim,\n            batch_first=True,\n            dropout=0.3,\n            num_layers=2,\n            bidirectional=True\n        )\n\n        # Attention layer for BiLSTM output\n        self.attn_weight = nn.Parameter(torch.randn(hidden_dim * 2, 1))\n\n        self.classifier = nn.Sequential(\n            nn.Linear(hidden_dim * 2 + hidden_dim, hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(hidden_dim, num_classes)\n        )\n\n    def forward(self, static_input, sequence_input, seq_lengths):\n        static_out = self.static_encoder(static_input)\n\n        # Pack and run LSTM\n        packed_seq = nn.utils.rnn.pack_padded_sequence(sequence_input, seq_lengths.cpu(), batch_first=True, enforce_sorted=False)\n        packed_out, _ = self.lstm(packed_seq)\n        unpacked, _ = nn.utils.rnn.pad_packed_sequence(packed_out, batch_first=True)\n\n        # Attention: compute (B, T, 1) from (B, T, H*2)\n        attn_scores = torch.matmul(unpacked, self.attn_weight)  # (B, T, 1)\n        attn_weights = torch.softmax(attn_scores, dim=1)        # (B, T, 1)\n        attn_output = (unpacked * attn_weights).sum(dim=1)      # (B, H*2)\n\n        # Combine static and attended sequence representation\n        combined = torch.cat((static_out, attn_output), dim=1)  # (B, H*3)\n        return self.classifier(combined)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:43:51.277587Z","iopub.execute_input":"2025-06-22T11:43:51.277808Z","iopub.status.idle":"2025-06-22T11:43:51.285517Z","shell.execute_reply.started":"2025-06-22T11:43:51.27777Z","shell.execute_reply":"2025-06-22T11:43:51.28482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.optim as optim\nfrom sklearn.metrics import accuracy_score\n\n# Config\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nstatic_dim = train_static.shape[1]\nseq_feat_dim = train_sequence_tensors[0].shape[1]\nhidden_dim = 64\nnum_classes = len(label_encoder.classes_)\nnum_epochs = 40\n\n# Model, loss, optimizer\nmodel = HybridModel(static_dim, seq_feat_dim, hidden_dim, num_classes).to(device)\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'max', patience=3, factor=0.5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:43:51.288916Z","iopub.execute_input":"2025-06-22T11:43:51.289171Z","iopub.status.idle":"2025-06-22T11:43:51.537321Z","shell.execute_reply.started":"2025-06-22T11:43:51.289156Z","shell.execute_reply":"2025-06-22T11:43:51.536736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(num_epochs):\n    model.train()\n    train_losses = []\n    all_preds = []\n    all_labels = []\n\n    for batch in train_loader:\n        static_input = batch['static'].to(device)\n        sequence_input = batch['sequence'].to(device)\n        seq_lengths = batch['seq_length'].to(device)\n        labels = batch['label'].to(device)\n\n        optimizer.zero_grad()\n        outputs = model(static_input, sequence_input, seq_lengths)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        train_losses.append(loss.item())\n\n        preds = torch.argmax(outputs, dim=1)\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n\n    train_acc = accuracy_score(all_labels, all_preds)\n    print(f\"Epoch {epoch+1}/{num_epochs} | Train Loss: {np.mean(train_losses):.4f} | Train Acc: {train_acc:.4f}, | \", end = '')\n\n    model.eval()\n    test_preds = []\n    test_labels_all = []\n    \n    with torch.no_grad():\n        for batch in test_loader:\n            static_input = batch['static'].to(device)\n            sequence_input = batch['sequence'].to(device)\n            seq_lengths = batch['seq_length'].to(device)\n            labels = batch['label'].to(device)\n    \n            outputs = model(static_input, sequence_input, seq_lengths)\n            preds = torch.argmax(outputs, dim=1)\n    \n            test_preds.extend(preds.cpu().numpy())\n            test_labels_all.extend(labels.cpu().numpy())\n    \n    test_acc = accuracy_score(test_labels_all, test_preds)\n    print(f\"Test Accuracy: {test_acc:.4f}\")\n    scheduler.step(test_acc)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:43:51.53799Z","iopub.execute_input":"2025-06-22T11:43:51.538229Z","iopub.status.idle":"2025-06-22T11:48:08.436573Z","shell.execute_reply.started":"2025-06-22T11:43:51.538207Z","shell.execute_reply":"2025-06-22T11:48:08.43583Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    sequence: Polars DataFrame with columns = dynamic features\n    demographics: Polars DataFrame with one row = static features\n    \"\"\"\n    # 1. Convert to numpy\n    seq_np = np.column_stack([sequence[col].to_numpy() for col in dynamic_cols])\n    static_np = demographics.select(static_cols).to_numpy()[0]  # shape: (static_dim,)\n\n    # 2. Handle missing values in sequence (optional safety net)\n    seq_np = np.nan_to_num(seq_np, nan=0.0)\n\n    # 3. Truncate or pad sequence\n    seq_len = min(len(seq_np), MAX_SEQ_LEN)\n    if len(seq_np) > MAX_SEQ_LEN:\n        seq_np = seq_np[:MAX_SEQ_LEN]\n    elif len(seq_np) < MAX_SEQ_LEN:\n        pad = np.zeros((MAX_SEQ_LEN - len(seq_np), seq_np.shape[1]))\n        seq_np = np.vstack([seq_np, pad])\n\n    # 4. Convert to torch tensors\n    seq_tensor = torch.tensor(seq_np, dtype=torch.float32).unsqueeze(0).to(device)        # (1, MAX_SEQ_LEN, seq_feat_dim)\n    static_tensor = torch.tensor(static_np, dtype=torch.float32).unsqueeze(0).to(device)  # (1, static_feat_dim)\n    seq_len_tensor = torch.tensor([seq_len], dtype=torch.long).to(device)                 # (1,)\n\n    # 5. Predict\n    with torch.no_grad():\n        logits = model(static_tensor, seq_tensor, seq_len_tensor)\n        pred_class = torch.argmax(logits, dim=1).item()\n\n    # 6. Decode if label encoder was used\n    return label_encoder.inverse_transform([pred_class])[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:48:08.437451Z","iopub.execute_input":"2025-06-22T11:48:08.437708Z","iopub.status.idle":"2025-06-22T11:48:08.444576Z","shell.execute_reply.started":"2025-06-22T11:48:08.437683Z","shell.execute_reply":"2025-06-22T11:48:08.443828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inference_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-22T11:48:08.44544Z","iopub.execute_input":"2025-06-22T11:48:08.446106Z","iopub.status.idle":"2025-06-22T11:48:09.788278Z","shell.execute_reply.started":"2025-06-22T11:48:08.446088Z","shell.execute_reply":"2025-06-22T11:48:09.787493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    # Replace this function with your inference code.\n    # You can return either a Pandas or Polars dataframe, though Polars is recommended.\n    # Each prediction (except the very first) must be returned within 30 minutes of the batch features being provided.\n #   return 'Text on phone'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:48:09.789168Z","iopub.execute_input":"2025-06-22T11:48:09.789462Z","iopub.status.idle":"2025-06-22T11:48:09.792892Z","shell.execute_reply.started":"2025-06-22T11:48:09.789439Z","shell.execute_reply":"2025-06-22T11:48:09.792282Z"}},"outputs":[],"execution_count":null}]}