{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12655987,"sourceType":"datasetVersion","datasetId":7998067},{"sourceId":12718988,"sourceType":"datasetVersion","datasetId":8039048}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"colab":{"provenance":[],"collapsed_sections":["RTbRyh-r5MXm","cgSHxyPo5MXp","WWfeKRNw5MXp","caVYLA8q5MXr"],"gpuType":"T4","name":"notebook604a6d182a"},"accelerator":"GPU","widgets":{"application/vnd.jupyter.widget-state+json":{"ccd9f2d6d7fd4c3ea253ed780f227095":{"model_module":"@jupyter-widgets/controls","model_name":"VBoxModel","model_module_version":"2.0.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"VBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"VBoxView","box_style":"","children":["IPY_MODEL_54a4174c24b74bdbbb725b8a5b10202d","IPY_MODEL_3923ae9e3c08428d9975e9ab0947bf99","IPY_MODEL_53502df953f44480b1a7b1ce43369617","IPY_MODEL_fb717899c0ca42ff93871777ab8dbdda","IPY_MODEL_c806762e14304ead8b893b1e2c96a4af"],"layout":"IPY_MODEL_af016a8b1d7747cb8c8e40b77ba1dabe","tabbable":null,"tooltip":null}},"54a4174c24b74bdbbb725b8a5b10202d":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"2.0.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_ebe513aca7b94196bb1642fbcd853e16","placeholder":"​","style":"IPY_MODEL_12bd7db521c444529f8183a6e1dc0a11","tabbable":null,"tooltip":null,"value":"<center> <img\nsrc=https://www.kaggle.com/static/images/site-logo.png\nalt='Kaggle'> <br> Create an API token from <a\nhref=\"https://www.kaggle.com/settings/account\" target=\"_blank\">your Kaggle\nsettings page</a> and paste it below along with your Kaggle username. <br> </center>"}},"3923ae9e3c08428d9975e9ab0947bf99":{"model_module":"@jupyter-widgets/controls","model_name":"TextModel","model_module_version":"2.0.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"TextModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"TextView","continuous_update":true,"description":"Username:","description_allow_html":false,"disabled":false,"layout":"IPY_MODEL_62e0799ec74749239a254766114a9844","placeholder":"​","style":"IPY_MODEL_1a8f14ff5e884e54a280b3e19f7b297b","tabbable":null,"tooltip":null,"value":""}},"53502df953f44480b1a7b1ce43369617":{"model_module":"@jupyter-widgets/controls","model_name":"PasswordModel","model_module_version":"2.0.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"PasswordModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"PasswordView","continuous_update":true,"description":"Token:","description_allow_html":false,"disabled":false,"layout":"IPY_MODEL_400c9bfec38a44018b7843cd59df433c","placeholder":"​","style":"IPY_MODEL_ca5875a9d8ca4a9ba7c2acbff92ed01d","tabbable":null,"tooltip":null,"value":""}},"fb717899c0ca42ff93871777ab8dbdda":{"model_module":"@jupyter-widgets/controls","model_name":"ButtonModel","model_module_version":"2.0.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ButtonModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ButtonView","button_style":"","description":"Login","disabled":false,"icon":"","layout":"IPY_MODEL_fd433df46eb4481e846775a8469230eb","style":"IPY_MODEL_e1af31a414054159ba6b2a606a2ae834","tabbable":null,"tooltip":null}},"c806762e14304ead8b893b1e2c96a4af":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"2.0.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_65e60b18a5484907bdbef4c4e0e6ef13","placeholder":"​","style":"IPY_MODEL_7b25a91b1cb14ffeb95ced369002f637","tabbable":null,"tooltip":null,"value":"\n<b>Thank You</b></center>"}},"af016a8b1d7747cb8c8e40b77ba1dabe":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":"center","align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":"flex","flex":null,"flex_flow":"column","grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"50%"}},"ebe513aca7b94196bb1642fbcd853e16":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"12bd7db521c444529f8183a6e1dc0a11":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLStyleModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"62e0799ec74749239a254766114a9844":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"1a8f14ff5e884e54a280b3e19f7b297b":{"model_module":"@jupyter-widgets/controls","model_name":"TextStyleModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"TextStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"400c9bfec38a44018b7843cd59df433c":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"ca5875a9d8ca4a9ba7c2acbff92ed01d":{"model_module":"@jupyter-widgets/controls","model_name":"TextStyleModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"TextStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"fd433df46eb4481e846775a8469230eb":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"e1af31a414054159ba6b2a606a2ae834":{"model_module":"@jupyter-widgets/controls","model_name":"ButtonStyleModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ButtonStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","button_color":null,"font_family":null,"font_size":null,"font_style":null,"font_variant":null,"font_weight":null,"text_color":null,"text_decoration":null}},"65e60b18a5484907bdbef4c4e0e6ef13":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"7b25a91b1cb14ffeb95ced369002f637":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLStyleModel","model_module_version":"2.0.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}}}}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# IMPORTS","metadata":{"id":"RTbRyh-r5MXm"}},{"cell_type":"code","source":"import pandas as pd\nimport polars as pl\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport joblib\nimport os\nimport zipfile\nimport types\nimport kaggle_evaluation.cmi_inference_server\n\n\nimport sklearn\nfrom sklearn.preprocessing import LabelEncoder,StandardScaler\nfrom sklearn.model_selection import train_test_split,StratifiedKFold\nfrom sklearn.metrics import f1_score, accuracy_score\nfrom scipy.interpolate import CubicSpline\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom scipy.spatial.transform import Rotation as R\nfrom scipy.fft import rfft, rfftfreq\nfrom scipy.signal import welch\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split, TensorDataset,Subset\nfrom torch.nn import TransformerEncoder, TransformerEncoderLayer","metadata":{"trusted":true,"id":"C1jZnmZF5MXo","execution":{"iopub.status.busy":"2025-08-11T15:36:07.754990Z","iopub.execute_input":"2025-08-11T15:36:07.755200Z","iopub.status.idle":"2025-08-11T15:36:15.298983Z","shell.execute_reply.started":"2025-08-11T15:36:07.755176Z","shell.execute_reply":"2025-08-11T15:36:15.298383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_DATA_PATH = \"/kaggle/input/model-and-encoder/kaggle/working\"\nINPUT_PATH = \"/kaggle/input/cmi-detect-behavior-with-sensor-data\"\nOUTPUT_PATH  = \"/kaggle/working/\"","metadata":{"trusted":true,"id":"8bc7Raq05MXo","execution":{"iopub.status.busy":"2025-08-11T15:36:15.300589Z","iopub.execute_input":"2025-08-11T15:36:15.300954Z","iopub.status.idle":"2025-08-11T15:36:15.304355Z","shell.execute_reply.started":"2025-08-11T15:36:15.300937Z","shell.execute_reply":"2025-08-11T15:36:15.303842Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Loading Datasets","metadata":{"id":"cgSHxyPo5MXp"}},{"cell_type":"code","source":"def load_df(base_path: str):\n    \"\"\"\n    Loads training and testing dataframes and merges them with demographics.\n    Does NOT perform imputation here; NaNs will be handled in prepare_and_impute_data.\n\n    Args:\n        base_path (str): The base directory where CSV files are located.\n\n    Returns:\n        tuple: (merged_train_df, merged_test_df, train_labels)\n    \"\"\"\n    print(\"Loading datasets...\")\n    try:\n        train_df = pd.read_csv(os.path.join(base_path, \"train.csv\"))\n        train_demo = pd.read_csv(os.path.join(base_path, \"train_demographics.csv\"))\n        test_df = pd.read_csv(os.path.join(base_path, \"test.csv\"))\n        test_demo = pd.read_csv(os.path.join(base_path, \"test_demographics.csv\"))\n    except FileNotFoundError as e:\n        print(f\"Error loading file: {e}. Please ensure data is downloaded and extracted to {base_path}\")\n        return None, None, None\n\n    # Get labels\n    if 'gesture' in train_df.columns:\n        train_labels = train_df[['gesture', 'sequence_id']].copy()\n    else:\n        print(\"Warning: 'gesture' column not found in train_df. Cannot extract labels.\")\n        train_labels = None\n\n    # Select common IMU columns\n    common_cols = list(set(train_df.columns) & set(test_df.columns))\n    feature_cols = [col for col in common_cols if col not in ['sequence_id', 'sequence_counter', 'row_id']]\n\n    train_df = train_df[feature_cols + ['sequence_id', 'sequence_counter']]\n    test_df = test_df[feature_cols + ['sequence_id', 'sequence_counter']]\n\n    # Select common demographic columns\n    common_demo_cols = list(set(train_demo.columns) & set(test_demo.columns))\n    demo_feature_cols = [col for col in common_demo_cols if col != 'subject']\n\n    train_demo = train_demo[demo_feature_cols + ['subject']]\n    test_demo = test_demo[demo_feature_cols + ['subject']]\n\n    # Merge demo into IMU based on 'subject'\n    train_df = train_df.merge(train_demo, on=\"subject\", how=\"left\")\n    test_df = test_df.merge(test_demo, on=\"subject\", how=\"left\")\n\n    print(f\"train demo shape (before imputation): {train_df.shape}\")\n    print(f\"test  demo shape (before imputation): {test_df.shape}\")\n\n    train_df = train_df.sort_values(by=[\"sequence_id\", \"sequence_counter\"])\n    train_df.drop(columns = ['subject'])\n    return train_df, train_labels\n","metadata":{"trusted":true,"id":"tAoSgZz05MXp","execution":{"iopub.status.busy":"2025-08-11T15:36:15.305185Z","iopub.execute_input":"2025-08-11T15:36:15.305592Z","iopub.status.idle":"2025-08-11T15:36:15.320900Z","shell.execute_reply.started":"2025-08-11T15:36:15.305575Z","shell.execute_reply":"2025-08-11T15:36:15.320282Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# DATA PREPROCESSING","metadata":{"id":"WWfeKRNw5MXp"}},{"cell_type":"code","source":"def SeparatingModals(data_df):\n    \"\"\"\n    Gets the merged dataset as input and separates it into four different DataFrames:\n    - IMU sensor data\n    - TOF sensor data\n    - THM sensor data\n    - Demographics (excluding subject, sequence_id, sequence_counter)\n    \"\"\"\n\n    imu_features = ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z']\n    tof_features = [col for col in data_df.columns if col.startswith('tof')]\n    thm_features = [col for col in data_df.columns if col.startswith('thm')]\n\n    # Exclude IMU/TOF/THM and sequence/subject identifiers from demographics\n    demographics = [\n        col for col in data_df.columns\n        if col not in imu_features + tof_features + thm_features\n        and col not in ['sequence_id', 'sequence_counter', 'subject']\n    ]\n\n    # Columns present in actual data\n    imu_cols_present = [col for col in imu_features if col in data_df.columns]\n    tof_cols_present = [col for col in tof_features if col in data_df.columns]\n    thm_cols_present = [col for col in thm_features if col in data_df.columns]\n    demo_cols_present = [col for col in demographics if col in data_df.columns]\n\n    # Construct modal dataframes\n    imu_df = data_df[imu_cols_present + [\"sequence_id\",\"sequence_counter\"] + thm_cols_present]\n    tof_df = data_df[tof_cols_present + [\"sequence_id\",\"sequence_counter\"] + thm_cols_present]\n    demo_df = data_df[demo_cols_present + [\"sequence_id\",\"sequence_counter\"]]\n\n    print(\"Total len: \" + str(len(set(imu_df.columns.tolist() + tof_df.columns.tolist() + demo_df.columns.tolist()))))\n\n    imu_df = imu_df.sort_index(axis=1)\n    tof_df = tof_df.sort_index(axis=1)\n    demo_df = demo_df.sort_index(axis =1 )\n\n    return imu_df,tof_df,demo_df","metadata":{"trusted":true,"id":"aDI3cctq5MXq","execution":{"iopub.status.busy":"2025-08-11T15:36:15.321614Z","iopub.execute_input":"2025-08-11T15:36:15.322137Z","iopub.status.idle":"2025-08-11T15:36:15.341595Z","shell.execute_reply.started":"2025-08-11T15:36:15.322110Z","shell.execute_reply":"2025-08-11T15:36:15.340915Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def spline_interpolation(df: pd.DataFrame, percentile: float = 0.95, test: bool = False):\n    \"\"\"\n    Interpolates IMU sequences using CubicSpline interpolation.\n\n    Args:\n        df (pd.DataFrame): Input DataFrame with columns ['sequence_id', 'sequence_counter', ...features]\n        percentile (float): Percentile to determine target sequence length (ignored if test=True)\n        test (bool): If True, use fixed target_timesteps=126\n\n    Returns:\n        interpolated_df (pd.DataFrame): Interpolated sequences in flat format\n        input_mask (torch.BoolTensor): Mask of valid input values (B, T, F)\n        padding_mask (torch.BoolTensor): Padding mask for transformer (B, T)\n    \"\"\"\n\n    df = df.sort_values(['sequence_id', 'sequence_counter']).reset_index(drop=True)\n\n    feature_columns = [col for col in df.columns if col not in ['sequence_id', 'sequence_counter']]\n    grouped = df.groupby('sequence_id')\n\n    sequence_ids = list(grouped.groups.keys())\n    sequence_lengths = grouped.size()\n\n    # Determine target length\n    target_timesteps = 126\n\n    interpolated_sequences = []\n    input_masks = []\n    padding_masks = []\n    sequence_info = []\n\n    for seq_id in sequence_ids :\n        group = grouped.get_group(seq_id)\n        time = group['sequence_counter'].values\n        features = group[feature_columns].values\n        L = len(time)\n\n        # Skip sequences too short\n        if L < 2:\n            continue\n\n        # Build new time grid\n        if L < target_timesteps:\n            new_time = np.linspace(time[0], time[-1], target_timesteps)\n        else:\n            idxs = np.round((np.arange(target_timesteps) / (target_timesteps - 1)) * (L - 1)).astype(int)\n            new_time = time[idxs]\n\n        interpolated_features = np.zeros((target_timesteps, len(feature_columns)))\n        mask = np.zeros_like(interpolated_features, dtype=bool)\n\n        for i, col in enumerate(feature_columns):\n            y = features[:, i]\n\n            if np.all(np.isnan(y)):\n                interpolated_features[:, i] = 0.0\n                continue\n\n            valid = ~np.isnan(y)\n            x_valid = time[valid]\n            y_valid = y[valid]\n\n            if len(x_valid) > 1:\n                try:\n                    spline = CubicSpline(x_valid, y_valid)\n                    interpolated = spline(new_time)\n                except Exception:\n                    interpolated = np.interp(new_time, x_valid, y_valid)\n            else:\n                interpolated = np.full(target_timesteps, y_valid[0])\n\n            interpolated_features[:, i] = interpolated\n            mask[:, i] = True  # Mark as filled\n\n        interpolated_sequences.append(interpolated_features)\n        input_masks.append(mask)\n        padding_masks.append(~mask.any(axis=1))  # timestep-level padding mask\n        sequence_info.extend([(seq_id, t) for t in range(target_timesteps)])\n\n    # === This part is OUTSIDE the loop ===\n    if not interpolated_sequences:\n        print(\"NOT INTERPOLATED\")\n        return pd.DataFrame(), torch.BoolTensor(), torch.BoolTensor()\n\n    # Stack sequences into numpy arrays\n    sequences_np = np.stack(interpolated_sequences)   # (B, T, F)\n    masks_np = np.stack(input_masks)                  # (B, T, F)\n    padding_np = np.stack(padding_masks)              # (B, T)\n\n    # Flatten for DataFrame reconstruction\n    flattened_data = sequences_np.reshape(-1, len(feature_columns))\n    info_df = pd.DataFrame(sequence_info, columns=['sequence_id', 'sequence_counter'])\n    data_df = pd.concat([\n        info_df.reset_index(drop=True),\n        pd.DataFrame(flattened_data, columns=feature_columns).reset_index(drop=True)\n    ], axis=1)\n\n    # Convert masks to tensors\n    input_mask_tensor = torch.from_numpy(masks_np).bool()\n    padding_mask_tensor = torch.from_numpy(padding_np).bool()\n\n    return data_df, input_mask_tensor, padding_mask_tensor\n","metadata":{"trusted":true,"id":"Tg6zCZV-X2D2","execution":{"iopub.status.busy":"2025-08-11T15:36:15.342344Z","iopub.execute_input":"2025-08-11T15:36:15.342588Z","iopub.status.idle":"2025-08-11T15:36:15.366846Z","shell.execute_reply.started":"2025-08-11T15:36:15.342562Z","shell.execute_reply":"2025-08-11T15:36:15.366084Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def encode_labels(\n    labels: pd.DataFrame,\n    Load: bool = False,\n):\n    \"\"\"\n    Label encodes the 'gesture' column and returns a DataFrame with sequence_id and encoded labels.\n\n    Args:\n        labels : pd.DataFrame with 'sequence_id' and 'gesture' columns.\n        Load   : Whether to load an existing label encoder.\n\n    Returns:\n        encoded_labels_df : pd.DataFrame with ['sequence_id', 'label'] columns\n    \"\"\"\n\n    encoder_path = os.path.join(OUTPUT_PATH, \"encoder.pkl\")\n\n    # Remove duplicates and sort\n    labels = labels.sort_values(by='sequence_id').drop_duplicates(subset=['sequence_id'])\n    gestures = labels['gesture'].reset_index(drop=True)\n    sequence_ids = labels['sequence_id'].reset_index(drop=True)\n\n    # Encode or load encoder\n    if Load and os.path.exists(encoder_path):\n        le = joblib.load(encoder_path)\n    else:\n        le = LabelEncoder()\n        le.fit(gestures)\n        joblib.dump(le, encoder_path)\n\n    encoded_labels = le.transform(gestures)\n\n    # Return as DataFrame\n    encoded_labels_df = pd.DataFrame({\n        'sequence_id': sequence_ids,\n        'label': encoded_labels\n    })\n\n    return encoded_labels_df\n","metadata":{"trusted":true,"id":"Zumz2iel5MXr","execution":{"iopub.status.busy":"2025-08-11T15:36:15.391363Z","iopub.execute_input":"2025-08-11T15:36:15.391554Z","iopub.status.idle":"2025-08-11T15:36:15.409540Z","shell.execute_reply.started":"2025-08-11T15:36:15.391539Z","shell.execute_reply":"2025-08-11T15:36:15.408839Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# FEATURE ENGINEERING","metadata":{"id":"caVYLA8q5MXr"}},{"cell_type":"code","source":"\n# Per-time-step features\ndef get_quantNorm(df):\n    \"\"\"Compute quaternion norm (magnitude)\"\"\"\n    return np.sqrt(df[\"rot_w\"]**2 + df[\"rot_x\"]**2 + df[\"rot_y\"]**2 + df[\"rot_z\"]**2)\n\ndef normalize_quaternions(df):\n    \"\"\"Normalize quaternions to unit length, handling zero-norm safely\"\"\"\n    norm = get_quantNorm(df)\n    norm = np.where(norm < 1e-8, 1e-8, norm)  # Avoid division by zero\n    return pd.DataFrame({\n        \"rot_w\": df[\"rot_w\"] / norm,\n        \"rot_x\": df[\"rot_x\"] / norm,\n        \"rot_y\": df[\"rot_y\"] / norm,\n        \"rot_z\": df[\"rot_z\"] / norm,\n    })\n\ndef get_angleOfRotation(df):\n    \"\"\"Get rotation angle from quaternion\"\"\"\n    return 2 * np.arccos(np.clip(np.abs(df[\"rot_w\"]), 0.0, 1.0))\n\ndef get_rotationAxis(df):\n    \"\"\"Extract rotation axis from quaternion\"\"\"\n    angles = get_angleOfRotation(df)\n    sin_half_angle = np.sin(angles / 2)\n\n    # Avoid division by near-zero by setting axis to 0\n    mask = np.abs(sin_half_angle) < 1e-6\n\n    return pd.DataFrame({\n        \"axis_x\": np.where(mask, 0, df[\"rot_x\"] / sin_half_angle),\n        \"axis_y\": np.where(mask, 0, df[\"rot_y\"] / sin_half_angle),\n        \"axis_z\": np.where(mask, 0, df[\"rot_z\"] / sin_half_angle),\n    })\n\n\ndef get_magAcceleration(df):\n    \"\"\"Compute magnitude of acceleration vector\"\"\"\n    return np.sqrt(df[\"acc_x\"]**2 + df[\"acc_y\"]**2 + df[\"acc_z\"]**2)\n\ndef get_acceleration_derivatives(df, group_col=\"sequence_id\"):\n    \"\"\"Compute velocity (1st derivative) and jerk (2nd derivative) of acceleration\"\"\"\n    result = df.copy()\n    for axis in ['x', 'y', 'z']:\n        acc_col = f\"acc_{axis}\"\n        vel_col = f\"acc_vel_{axis}\"\n        jerk_col = f\"jerk_{axis}\"\n\n        result[vel_col] = result.groupby(group_col)[acc_col].diff().fillna(0)\n        result[jerk_col] = result.groupby(group_col)[vel_col].diff().fillna(0)\n    return result\n\n\ndef compute_angular_velocity(df, group_col='sequence_id'):\n    \"\"\"Compute angular velocity from quaternion sequence\"\"\"\n    df = df.copy()\n    ang_vel_x, ang_vel_y, ang_vel_z = [], [], []\n\n    for _, group in df.groupby(group_col):\n        quats_raw = group[[\"rot_x\", \"rot_y\", \"rot_z\", \"rot_w\"]].values\n        norms = np.linalg.norm(quats_raw, axis=1)\n        zero_norm_mask = norms < 1e-8\n        quats_cleaned = np.where(zero_norm_mask[:, None], [0.0, 0.0, 0.0, 1.0], quats_raw)\n        quats = R.from_quat(quats_cleaned)\n\n        if len(quats) < 2:\n            # Fill whole group with zeros instead of NaNs\n            ang_vel_x.extend([0.0] * len(group))\n            ang_vel_y.extend([0.0] * len(group))\n            ang_vel_z.extend([0.0] * len(group))\n            continue\n\n        rel_rots = quats[1:] * quats[:-1].inv()\n        rotvecs = rel_rots.as_rotvec()\n        rotvecs = np.vstack([[0.0, 0.0, 0.0], rotvecs])  # Pad first row with zeros\n\n        ang_vel_x.extend(rotvecs[:, 0])\n        ang_vel_y.extend(rotvecs[:, 1])\n        ang_vel_z.extend(rotvecs[:, 2])\n\n    df[\"ang_vel_x\"] = ang_vel_x\n    df[\"ang_vel_y\"] = ang_vel_y\n    df[\"ang_vel_z\"] = ang_vel_z\n\n    return df\n\n\ndef compute_angular_acceleration(df, group_col=\"sequence_id\"):\n    \"\"\"Compute angular acceleration from angular velocity\"\"\"\n    df = df.copy()\n    for axis in ['x', 'y', 'z']:\n        col = f\"ang_vel_{axis}\"\n        acc_col = f\"ang_acc_{axis}\"\n        df[acc_col] = df.groupby(group_col)[col].diff().fillna(0)\n    return df\n\n","metadata":{"trusted":true,"id":"tshh0tS-5MXs","execution":{"iopub.status.busy":"2025-08-11T15:36:15.410396Z","iopub.execute_input":"2025-08-11T15:36:15.410668Z","iopub.status.idle":"2025-08-11T15:36:15.428710Z","shell.execute_reply.started":"2025-08-11T15:36:15.410650Z","shell.execute_reply":"2025-08-11T15:36:15.428075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_all_dynamic_features(imu_data):\n    \"\"\"Compute all dynamic features from IMU data\n\n    Args:\n        imu_data: DataFrame with columns: acc_x, acc_y, acc_z, rot_w, rot_x, rot_y, rot_z, sequence_id\n\n    Returns:\n        DataFrame with additional computed features\n\n    Note: All derivative features are computed per sequence step since\n    actual time intervals are unknown.\n    \"\"\"\n    # Create a copy to avoid modifying the original DataFrame in place unintentionally\n    imu_data_processed = imu_data.copy()\n\n    # Normalize quaternions and add related features\n    normalized_quats = normalize_quaternions(imu_data_processed)\n    imu_data_processed[['rot_w', 'rot_x', 'rot_y', 'rot_z']] = normalized_quats[['rot_w', 'rot_x', 'rot_y', 'rot_z']]\n    imu_data_processed[\"quat_norm\"] = get_quantNorm(imu_data_processed)\n    imu_data_processed[\"angle_of_rotation\"] = get_angleOfRotation(imu_data_processed)\n\n    # Add rotation axis features\n    rotation_axis_df = get_rotationAxis(imu_data_processed)\n    imu_data_processed[[\"axis_x\", \"axis_y\", \"axis_z\"]] = rotation_axis_df[[\"axis_x\", \"axis_y\", \"axis_z\"]]\n\n    # Add acceleration magnitude feature\n    imu_data_processed[\"acc_magnitude\"] = get_magAcceleration(imu_data_processed)\n\n    # Per sequence features - compute derivatives per step\n    imu_data_processed = get_acceleration_derivatives(imu_data_processed)\n\n    # Angular change and acceleration per step\n    imu_data_processed = compute_angular_velocity(imu_data_processed)\n    imu_data_processed = compute_angular_acceleration(imu_data_processed)\n\n    return imu_data_processed","metadata":{"trusted":true,"id":"RpSbV5Oe5MXt","execution":{"iopub.status.busy":"2025-08-11T15:36:15.429497Z","iopub.execute_input":"2025-08-11T15:36:15.429733Z","iopub.status.idle":"2025-08-11T15:36:15.447611Z","shell.execute_reply.started":"2025-08-11T15:36:15.429710Z","shell.execute_reply":"2025-08-11T15:36:15.447067Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def fill_motion_features(df, group_col='sequence_id'):\n    \"\"\"Fill NaNs in motion-related columns using bfill then ffill per group\"\"\"\n    df = df.copy()\n\n    fill_cols = [\n        'acc_vel_x', 'acc_vel_y', 'acc_vel_z',\n        'ang_vel_x', 'ang_vel_y', 'ang_vel_z',\n        'ang_acc_x', 'ang_acc_y', 'ang_acc_z',\n        'jerk_x', 'jerk_y', 'jerk_z',\n    ]\n\n    for col in fill_cols:\n        if col in df.columns:\n            df[col] = df.groupby(group_col)[col].transform(lambda g: g.bfill().ffill())\n\n    return df","metadata":{"id":"7z06EPsFHwoZ","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.448495Z","iopub.execute_input":"2025-08-11T15:36:15.448728Z","iopub.status.idle":"2025-08-11T15:36:15.465672Z","shell.execute_reply.started":"2025-08-11T15:36:15.448705Z","shell.execute_reply":"2025-08-11T15:36:15.465055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nFS = 1\n\ndef clean_signal(signal):\n    signal = np.array(signal)\n    signal = signal[~np.isnan(signal)]\n    return signal if len(signal) >= 10 else np.array([])\n\n# ---------- Feature Functions (assume signal already cleaned) ----------\n\ndef dominant_frequency(signal, fs=FS):\n    freqs = rfftfreq(len(signal), 1/fs)\n    fft_vals = np.abs(rfft(signal))\n    return freqs[np.argmax(fft_vals)]\n\ndef spectral_entropy(signal, fs=FS):\n    if len(signal) < 4:\n        return np.nan\n    nperseg = min(len(signal), 128)\n    freqs, psd = welch(signal, fs=fs, nperseg=nperseg)\n    psd_sum = np.sum(psd)\n    if psd_sum == 0:\n        return 0.0\n    psd_norm = psd / psd_sum\n    return -np.sum(psd_norm * np.log2(psd_norm + 1e-12))\n\ndef band_power(signal, fs=FS, band=(0.5, 3)):\n    if len(signal) < 4:\n        return np.nan\n    nperseg = min(len(signal), 128)\n    freqs, psd = welch(signal, fs=fs, nperseg=nperseg)\n    idx_band = np.logical_and(freqs >= band[0], freqs <= band[1])\n    if np.any(idx_band):\n        return np.trapz(psd[idx_band], freqs[idx_band])\n    return 0.0\n\ndef signal_to_noise_ratio(signal, fs=FS, signal_band=(0.5, 3), noise_band=(4, 20)):\n    signal_energy = band_power(signal, fs, band=signal_band)\n    noise_energy = band_power(signal, fs, band=noise_band)\n    return signal_energy / (noise_energy + 1e-6)\n\n# ---------- Feature Aggregation ----------\n\ndef compute_freq_features(signal_raw, axis_name=\"acc_x\", fs=FS):\n    signal = clean_signal(signal_raw)\n    if len(signal) == 0:\n        return {\n            f\"{axis_name}_dom_freq\": np.nan,\n            f\"{axis_name}_spectral_entropy\": np.nan,\n            f\"{axis_name}_band_power_0.5_3\": np.nan,\n            f\"{axis_name}_snr\": np.nan,\n        }\n\n    return {\n        f\"{axis_name}_dom_freq\": dominant_frequency(signal, fs),\n        f\"{axis_name}_spectral_entropy\": spectral_entropy(signal, fs),\n        f\"{axis_name}_band_power_0.5_3\": band_power(signal, fs, band=(0.5, 3)),\n        f\"{axis_name}_snr\": signal_to_noise_ratio(signal, fs)\n    }\n\n# ---------- Apply to DataFrame per Sequence ----------\n\ndef add_frequency_features_to_df(df, axes=[\"acc_x\", \"acc_y\", \"acc_z\"], fs=FS):\n    features = []\n\n    for seq_id, group in df.groupby(\"sequence_id\"):\n        feat_dict = {\"sequence_id\": seq_id}\n        for axis in axes:\n            signal = group[axis].values\n            feat_dict.update(compute_freq_features(signal, axis_name=axis, fs=fs))\n        features.append(feat_dict)\n\n    feat_df = pd.DataFrame(features)\n    df_with_features = df.merge(feat_df, on=\"sequence_id\", how=\"left\")\n    return df_with_features\n","metadata":{"trusted":true,"id":"0XWODq6V5MXt","execution":{"iopub.status.busy":"2025-08-11T15:36:15.466436Z","iopub.execute_input":"2025-08-11T15:36:15.466664Z","iopub.status.idle":"2025-08-11T15:36:15.484921Z","shell.execute_reply.started":"2025-08-11T15:36:15.466640Z","shell.execute_reply":"2025-08-11T15:36:15.484271Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"****","metadata":{"id":"SOO-RdKY5MXu"}},{"cell_type":"markdown","source":"#Models","metadata":{"id":"s5FiGBSEH145"}},{"cell_type":"code","source":"class TimeSeriesDataset(Dataset):\n    def __init__(self, data, input_mask, padding_mask, labels):\n        self.data = data\n        self.input_mask = input_mask\n        self.padding_mask = padding_mask\n        self.labels = labels\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        return (\n            self.data[idx],\n            self.input_mask[idx],\n            self.padding_mask[idx],\n            self.labels[idx],\n        )","metadata":{"trusted":true,"id":"eEQY4A505MXu","execution":{"iopub.status.busy":"2025-08-11T15:36:15.485627Z","iopub.execute_input":"2025-08-11T15:36:15.486111Z","iopub.status.idle":"2025-08-11T15:36:15.502324Z","shell.execute_reply.started":"2025-08-11T15:36:15.486088Z","shell.execute_reply":"2025-08-11T15:36:15.501621Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MaskNetwork(nn.Module):\n    def __init__(self, num_masks, num_features, hidden_dim=64):\n        super().__init__()\n        self.net = nn.Sequential(\n            nn.Linear(num_masks, hidden_dim),\n            nn.ReLU(),\n            nn.Linear(hidden_dim, num_features),\n            nn.Tanh()\n        )\n\n    def forward(self, raw_mask):  # raw_mask: (B, T, M)\n        B, T, M = raw_mask.shape\n        x = raw_mask.view(B * T, M)\n        x = self.net(x)\n        return x.view(B, T, -1)\n","metadata":{"id":"puJiMI5SVD5M","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.503037Z","iopub.execute_input":"2025-08-11T15:36:15.503213Z","iopub.status.idle":"2025-08-11T15:36:15.516325Z","shell.execute_reply.started":"2025-08-11T15:36:15.503199Z","shell.execute_reply":"2025-08-11T15:36:15.515832Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Vanilla Transformer","metadata":{"id":"XBnROsXBUKOt"}},{"cell_type":"code","source":"# class VanillaTransformer(nn.Module):\n#     def __init__(self, in_channels, mask_inp, num_classes, d_model=96, nhead=4, num_layers=3, max_len=512):\n#         super().__init__()\n#         self.input_proj = nn.Linear(2 * in_channels, d_model)\n#         self.mask_token = MaskNetwork(mask_inp, in_channels)\n#         self.positional_embedding = nn.Parameter(torch.zeros(1, max_len, d_model))\n#         encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead, batch_first=True)\n#         self.transformer = nn.TransformerEncoder(encoder_layer, num_layers)\n#         self.pool = nn.AdaptiveAvgPool1d(1)\n#         self.fc = nn.Linear(d_model, num_classes)\n\n#     def forward(self, x, input_mask, padding_mask):\n#         mask_feats = self.mask_token(input_mask.float())\n#         x_aug = torch.cat([x, mask_feats], dim=-1)\n#         x_proj = self.input_proj(x_aug)\n#         x_enc = self.transformer(x_proj + self.positional_embedding[:, :x_proj.size(1)],\n#                                  src_key_padding_mask=padding_mask)\n#         x_pooled = self.pool(x_enc.transpose(1,2)).squeeze(-1)\n#         return self.fc(x_pooled)","metadata":{"id":"1wiqIoy2qzr8","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.517025Z","iopub.execute_input":"2025-08-11T15:36:15.517216Z","iopub.status.idle":"2025-08-11T15:36:15.531595Z","shell.execute_reply.started":"2025-08-11T15:36:15.517202Z","shell.execute_reply":"2025-08-11T15:36:15.530923Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn.functional as F\n\nclass TailDropout(nn.Module):\n    def __init__(self, p=0.7, tail_size=5):\n        super().__init__()\n        self.p = p\n        self.tail_size = tail_size\n\n    def forward(self, x):\n        if not self.training or self.p == 0.0:\n            return x\n        # Drop only the last tail_size features\n        noise = torch.ones_like(x)\n        noise[:, :, -self.tail_size:] = torch.bernoulli((1 - self.p) * noise[:, :, -self.tail_size:])\n        return x * noise\n\n# --- CNN Preprocessing ---\nclass CNNPreprocessor(nn.Module):\n    def __init__(self, in_channels, out_channels, dropout_p=0.5, tail_size=5, kernel_size=3):\n        super().__init__()\n        self.conv = nn.Conv1d(in_channels, out_channels, kernel_size, padding=1)\n        self.norm = nn.LayerNorm(out_channels)\n        self.relu = nn.ReLU()\n        self.tail_dropout = TailDropout(p=dropout_p, tail_size=tail_size)\n\n    def forward(self, x):\n        x = self.conv(x.transpose(1, 2))  # (B, out_channels, T)\n        x = x.transpose(1, 2)             # (B, T, out_channels)\n        x = self.norm(x)                  # LayerNorm over last dim\n        x = self.relu(x)\n        x = self.tail_dropout(x)\n        return x\n\n\n# --- Attention Pooling ---\nclass AttentionPool(nn.Module):\n    def __init__(self, d_model):\n        super().__init__()\n        self.attn = nn.Linear(d_model, 1)\n\n    def forward(self, x):\n        weights = torch.softmax(self.attn(x), dim=1)  # (B, T, 1)\n        pooled = (x * weights).sum(dim=1)  # (B, D)\n        return pooled\n\n\n# --- Vanilla Transformer Model ---\nclass VanillaTransformer(nn.Module):\n    def __init__(self, in_channels, mask_inp, num_classes,cnn_out_channels = None, d_model=96, nhead=4, num_layers=3, max_len=512):\n        super().__init__()\n\n        if cnn_out_channels is None:\n            cnn_out_channels = 2 * in_channels\n\n        self.mask_token = MaskNetwork(mask_inp, in_channels)\n\n        # CNN layer: keeps input dim same\n        self.cnn = CNNPreprocessor(in_channels, cnn_out_channels)\n\n        self.input_proj = nn.Linear(cnn_out_channels + in_channels, d_model)\n\n        # Positional embeddings\n        self.positional_embedding = nn.Parameter(torch.zeros(1, max_len, d_model))\n\n        # Transformer encoder\n        encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead, batch_first=True)\n        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers)\n\n        # Attention pooling\n        self.attn_pool = AttentionPool(d_model)\n\n        # Final classifier\n        self.fc = nn.Linear(d_model, num_classes)\n\n    def forward(self, x, input_mask, padding_mask):\n        # CNN preprocessing\n        x = self.cnn(x)\n\n        # Mask feature injection\n        mask_feats = self.mask_token(input_mask.float())\n        x_aug = torch.cat([x, mask_feats], dim=-1)\n\n        # Projection + positional encoding\n        x_proj = self.input_proj(x_aug)\n        x_pos = x_proj + self.positional_embedding[:, :x_proj.size(1)]\n\n        # Transformer\n        x_enc = self.transformer(x_pos, src_key_padding_mask=padding_mask)\n\n        # Attention pooling\n        x_pooled = self.attn_pool(x_enc)\n\n        return self.fc(x_pooled)\n","metadata":{"trusted":true,"id":"rijaTNuhX2D9","execution":{"iopub.status.busy":"2025-08-11T15:36:15.532333Z","iopub.execute_input":"2025-08-11T15:36:15.532567Z","iopub.status.idle":"2025-08-11T15:36:15.552972Z","shell.execute_reply.started":"2025-08-11T15:36:15.532552Z","shell.execute_reply":"2025-08-11T15:36:15.552287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 3. Evaluation helper ---\ndef compute_custom_f1(preds, labels, target_gestures):\n    labels = np.array(labels)\n    preds = np.array(preds)\n\n    # --- Binary F1: is target or not ---\n    binary_target = np.isin(labels, target_gestures)\n    binary_pred = np.isin(preds, target_gestures)\n    binary_f1 = f1_score(binary_target, binary_pred)\n\n    # --- Macro F1: collapse non-target classes into single int label ---\n    NON_TARGET = 999  # Use a number, not a string\n\n    labels_macro = [cls if cls in target_gestures else NON_TARGET for cls in labels]\n    preds_macro = [cls if cls in target_gestures else NON_TARGET for cls in preds]\n    macro_f1 = f1_score(labels_macro, preds_macro, average='macro')\n\n    return (binary_f1 + macro_f1) / 2","metadata":{"trusted":true,"id":"r0lS7cbo5MXv","execution":{"iopub.status.busy":"2025-08-11T15:36:15.553768Z","iopub.execute_input":"2025-08-11T15:36:15.554038Z","iopub.status.idle":"2025-08-11T15:36:15.570366Z","shell.execute_reply.started":"2025-08-11T15:36:15.554016Z","shell.execute_reply":"2025-08-11T15:36:15.569839Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Utility Functions","metadata":{"id":"AhgOeUIMPA6W"}},{"cell_type":"code","source":"def delete_unnecessary_variables():\n  for name, val in globals().copy().items():\n      if isinstance(val, (pd.DataFrame, pl.DataFrame, torch.Tensor)) and not name.startswith(\"_\"):\n          del globals()[name]","metadata":{"id":"uDtvgM89-0TD","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.571032Z","iopub.execute_input":"2025-08-11T15:36:15.571477Z","iopub.status.idle":"2025-08-11T15:36:15.586697Z","shell.execute_reply.started":"2025-08-11T15:36:15.571460Z","shell.execute_reply":"2025-08-11T15:36:15.586137Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def prepare_data_for_model(imu_data):\n    \"\"\"\n    Prepares IMU data DataFrame for model input.\n\n    - Non-`thm` columns are sorted alphabetically and placed first.\n    - Columns starting with `thm` are sorted and placed last.\n\n    Args:\n        imu_data: DataFrame with IMU data, including 'sequence_id' and 'sequence_counter'.\n\n    Returns:\n        numpy.ndarray: A 3D numpy array of shape (num_sequences, sequence_length, num_features).\n    \"\"\"\n    # Step 1 & 2: Sort by sequence_id and sequence_counter\n    imu_data_sorted = imu_data.sort_values(by=[\"sequence_id\", \"sequence_counter\"])\n\n    # Step 3: Rearrange columns\n    all_columns = imu_data_sorted.columns.tolist()\n    feature_columns = [col for col in all_columns if col not in [\"sequence_id\", \"sequence_counter\"]]\n\n    # Split into non-thm and thm columns\n    non_thm_cols = sorted([col for col in feature_columns if not col.startswith(\"thm\")])\n    thm_cols = sorted([col for col in feature_columns if col.startswith(\"thm\")])\n\n    ordered_cols = non_thm_cols + thm_cols\n    final_columns = [\"sequence_id\", \"sequence_counter\"] + ordered_cols  # preserve metadata columns\n\n    imu_data_sorted = imu_data_sorted[final_columns]\n\n    # Step 4: Group by sequence_id and convert to 3D numpy array\n    grouped = imu_data_sorted.groupby(\"sequence_id\")\n\n    sequences = []\n    for seq_id, group in grouped:\n        features = group.drop(columns=[\"sequence_id\", \"sequence_counter\"]).values\n        sequences.append(features)\n\n    x_seq = np.array(sequences)\n    print(\"X_seq shape:\", x_seq.shape)\n    return x_seq\n","metadata":{"id":"NiPYp_8fAuIz","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.587412Z","iopub.execute_input":"2025-08-11T15:36:15.587673Z","iopub.status.idle":"2025-08-11T15:36:15.605425Z","shell.execute_reply.started":"2025-08-11T15:36:15.587648Z","shell.execute_reply":"2025-08-11T15:36:15.604828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_ensemble(models,data,input_mask,padding_mask):\n    logits = [model(data,input_mask=input_mask,padding_mask = padding_mask) for model in models]\n    avg_logits = torch.stack(logits).mean(dim=0)\n    return torch.argmax(avg_logits, dim=1)","metadata":{"id":"QwK7Q6PWqA_4","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.606118Z","iopub.execute_input":"2025-08-11T15:36:15.606320Z","iopub.status.idle":"2025-08-11T15:36:15.619814Z","shell.execute_reply.started":"2025-08-11T15:36:15.606305Z","shell.execute_reply":"2025-08-11T15:36:15.619303Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training Models","metadata":{"id":"lML0qlV9-Q5t"}},{"cell_type":"code","source":"# def save_sequences_for_train() :\n#     try:\n#       train_data ,_, train_labels  = load_and_merge_data(INPUT_PATH)\n#       imu_data,tof,demo_graphics = SeparatingModals(train_data);\n#     except Exception as e:\n#       print(\"File not found.\")\n#       return\n\n#     #filling na values\n#     imu_data,input_mask_train,padding_mask_train = spline_interpolation(imu_data)\n#     print(\"Percentage of NAN values : \" , imu_data.isna().mean().mean())\n#     labels = encode_labels(train_labels)\n\n\n#     imu_data = compute_all_dynamic_features(imu_data)\n#     print(\"Percentage of NAN values : \" , imu_data.isna().mean().mean())\n\n#     if (imu_data.isna().mean().mean()):\n#       imu_data = fill_motion_features(imu_data)\n#       print(\"filled all na values \")\n\n#     imu_data = add_frequency_features_to_df(imu_data, axes=[\"acc_x\", \"acc_y\", \"acc_z\"], fs=1)\n#     print(\"Percentage of NAN values : \" , imu_data.isna().mean().mean())\n#     target_actions  = [\"Above ear - pull hair\",\n#                     \"Forehead - pull hairline\",\n#                     \"Forehead - scratch\",\n#                     \"Eyebrow - pull hair\",\n#                     \"Eyelash - pull hair\",\n#                     \"Neck - pinch skin\",\n#                     \"Neck - scratch\",\n#                     \"Cheek - pinch skin\"]\n\n#     print(\"filled all na values \")\n\n#     target_gestures = []\n#     le = joblib.load(os.path.join(BASE_DATA_PATH, \"label_encoder.pkl\"))\n\n#     for action in target_actions:\n#         if action in le.classes_:\n#             target_gestures.append(le.transform([action])[0])\n\n\n\n#     imu_seq = prepare_data_for_model(imu_data)\n\n#     scaler = TimeSeriesScalerMeanVariance()\n#     imu_seq = scaler.fit_transform(imu_seq)\n#     joblib.dump(scaler, os.path.join(BASE_DATA_PATH, \"scaler.pkl\"))\n\n#     #save data to base_data_path\n#     imu_seq = torch.tensor(imu_seq, dtype=torch.float32)\n#     padding_mask_train = padding_mask_train.to(dtype=torch.float32)\n#     input_mask_train = input_mask_train.to(dtype=torch.float32)\n#     labels = torch.tensor(labels, dtype=torch.int)\n\n#     print(\"imu_seq shape : \" , imu_seq.shape)\n#     print(\"padding_mask shape : \" , padding_mask_train.shape)\n#     print(\"labels shape : \" , labels.shape)\n#     print(\"input_mask shape : \" , input_mask_train.shape)\n\n#     joblib.dump(imu_seq, os.path.join(BASE_DATA_PATH, \"imu_seq_train.pkl\"))\n#     joblib.dump(padding_mask_train, os.path.join(BASE_DATA_PATH, \"padding_mask_train.pkl\"))\n#     joblib.dump(input_mask_train, os.path.join(BASE_DATA_PATH, \"input_mask_train.pkl\"))\n#     joblib.dump(labels,os.path.join(BASE_DATA_PATH, \"labels_train.pkl\"))\n#     joblib.dump(target_gestures,os.path.join(BASE_DATA_PATH, \"target_gestures.pkl\"))\n#     delete_unnecessary_variables()\n","metadata":{"id":"Ye_wzFC_-QQc","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.620602Z","iopub.execute_input":"2025-08-11T15:36:15.620813Z","iopub.status.idle":"2025-08-11T15:36:15.635079Z","shell.execute_reply.started":"2025-08-11T15:36:15.620779Z","shell.execute_reply":"2025-08-11T15:36:15.634583Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# save_sequences_for_train()","metadata":{"id":"kDvNzIIBpSp_","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.635733Z","iopub.execute_input":"2025-08-11T15:36:15.636121Z","iopub.status.idle":"2025-08-11T15:36:15.652828Z","shell.execute_reply.started":"2025-08-11T15:36:15.636098Z","shell.execute_reply":"2025-08-11T15:36:15.652236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"models_path = os.path.join(OUTPUT_PATH,\"Models\")\nos.makedirs(models_path, exist_ok=True)","metadata":{"trusted":true,"id":"1kA3tNxoX2D_","execution":{"iopub.status.busy":"2025-08-11T15:36:15.655821Z","iopub.execute_input":"2025-08-11T15:36:15.656259Z","iopub.status.idle":"2025-08-11T15:36:15.667353Z","shell.execute_reply.started":"2025-08-11T15:36:15.656242Z","shell.execute_reply":"2025-08-11T15:36:15.666832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import wandb\n\ndef train_vanilla_transformer(\n    train_data, train_input_mask, train_padding_mask, train_labels,\n    in_channels, mask_dim, num_classes,\n    device, epochs=35, batch_size=32, lr=1e-3, target_gestures=None\n):\n    # Init wandb\n    wandb.init(project=\"imu_only\", config={\n        \"epochs\": epochs,\n        \"batch_size\": batch_size,\n        \"in_channels\": in_channels,\n        \"mask_dim\": mask_dim,\n        \"num_classes\": num_classes,\n        \"model\": \"VanillaTransformer + CNN + AttentionPool\"\n    })\n\n    ds = TimeSeriesDataset(train_data, train_input_mask, train_padding_mask, train_labels)\n    skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n    fold_models = []\n\n    print(\"entered training\")\n    for fold, (tr_idx, val_idx) in enumerate(skf.split(train_data, train_labels)):\n        print(f\"\\n=== Training Fold {fold+1}/5 ===\")\n        wandb.run.name = f\"fold-{fold+1}\"\n\n        train_ds = Subset(ds, tr_idx)\n        val_ds = Subset(ds, val_idx)\n        train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n        val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False)\n\n        model = VanillaTransformer(in_channels, mask_dim, num_classes).to(device)\n        criterion = nn.CrossEntropyLoss()\n        optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)\n\n        for epoch in range(1, epochs+1):\n            model.train()\n            total_loss = 0\n\n            for x, m, p, y in train_loader:\n                x, m, p, y = x.to(device), m.to(device), p.to(device), y.to(device)\n                optimizer.zero_grad()\n                out = model(x, m, p)  # shape: (B, num_classes)\n                loss = criterion(out, y)\n                loss.backward()\n                optimizer.step()\n                total_loss += loss.item()\n\n            avg_train_loss = total_loss / len(train_loader)\n\n            # Evaluation\n            model.eval()\n            all_val_preds, all_val_labels = [], []\n            with torch.no_grad():\n                for x_val, m_val, p_val, y_val in val_loader:\n                    x_val, m_val, p_val, y_val = x_val.to(device), m_val.to(device), p_val.to(device), y_val.to(device)\n                    val_out = model(x_val, m_val, p_val)\n                    val_preds = val_out.argmax(dim=1).cpu().numpy()\n                    all_val_preds.extend(val_preds)\n                    all_val_labels.extend(y_val.cpu().numpy())\n\n            val_f1 = compute_custom_f1(all_val_preds, all_val_labels, target_gestures)\n\n            print(f\"[Fold {fold+1}] Epoch {epoch} | Loss: {avg_train_loss:.4f} | Val F1: {val_f1:.4f}\")\n\n            # Log to wandb\n            wandb.log({\n                \"fold\": fold + 1,\n                \"epoch\": epoch,\n                \"train_loss\": avg_train_loss,\n                \"val_f1\": val_f1,\n            })\n\n\n        # Save model\n        model_path = os.path.join(models_path, f\"transformer_fold_{fold}.pt\")\n        torch.save(model.state_dict(), model_path)\n        wandb.save(model_path)\n        fold_models.append(model)\n\n    wandb.finish()\n    return fold_models","metadata":{"id":"za7OIJmEMXeL","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:15.668415Z","iopub.execute_input":"2025-08-11T15:36:15.668655Z","iopub.status.idle":"2025-08-11T15:36:18.175428Z","shell.execute_reply.started":"2025-08-11T15:36:15.668633Z","shell.execute_reply":"2025-08-11T15:36:18.174909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def CMI_preprocessor(data,test = False):\n\n    train_df = data\n\n    imu_data,_,_ = SeparatingModals(train_df)\n\n    imu_data, input_mask_tensor, padding_mask_tensor = spline_interpolation(imu_data)\n\n    imu_data  =  compute_all_dynamic_features(imu_data)\n\n    imu_data = add_frequency_features_to_df(imu_data, axes=[\"acc_x\", \"acc_y\", \"acc_z\"], fs=1)\n\n    imu_data_seq = prepare_data_for_model(imu_data)\n\n    #getting target gestures\n    target_actions  = [\"Above ear - pull hair\",\n                        \"Forehead - pull hairline\",\n                        \"Forehead - scratch\",\n                        \"Eyebrow - pull hair\",\n                        \"Eyelash - pull hair\",\n                        \"Neck - pinch skin\",\n                        \"Neck - scratch\",\n                        \"Cheek - pinch skin\"]\n\n    target_gestures = []\n    le = joblib.load(os.path.join(BASE_DATA_PATH,\"encoder.pkl\"))\n\n    for action in target_actions:\n        if action in le.classes_:\n            target_gestures.append(le.transform([action])[0])\n\n    #scale imu_data_seq\n    scaler_path = os.path.join(OUTPUT_PATH,\"scaler.pkl\")\n    if not test :\n        scaler = StandardScaler()\n        num_seqs, seq_len, num_feats = imu_data_seq.shape\n        imu_data_seq = scaler.fit_transform(imu_data_seq.reshape(-1, num_feats)).reshape(num_seqs, seq_len, num_feats)\n        joblib.dump(scaler,scaler_path)\n    else :\n        scaler = joblib.load(scaler_path)\n        num_seqs, seq_len, num_feats = imu_data_seq.shape\n        imu_data_seq = scaler.transform(imu_data_seq.reshape(-1, num_feats)).reshape(num_seqs, seq_len, num_feats)\n\n    imu_data_tensor = torch.tensor(imu_data_seq, dtype=torch.float32)\n\n\n    return imu_data_tensor,input_mask_tensor,padding_mask_tensor,target_gestures","metadata":{"trusted":true,"id":"_s0XdWIsX2EA","execution":{"iopub.status.busy":"2025-08-11T15:41:20.831227Z","iopub.execute_input":"2025-08-11T15:41:20.831775Z","iopub.status.idle":"2025-08-11T15:41:20.838279Z","shell.execute_reply.started":"2025-08-11T15:41:20.831749Z","shell.execute_reply":"2025-08-11T15:41:20.837581Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------\n# Testing Function\n# --------------------\ndef evaluate_ensemble(models, data, input_mask, padding_mask, labels, device, target_gestures):\n    loader = DataLoader(TimeSeriesDataset(data, input_mask, padding_mask, labels),\n                        batch_size=32, shuffle=False)\n    all_preds, all_labels = [], []\n\n    # set all models to evaluation mode\n    for model in models:\n        model.eval()\n\n    with torch.no_grad():\n        for x, m, p, y in loader:\n            x, m, p = x.to(device), m.to(device), p.to(device)\n\n            # average predictions\n            logits = torch.stack([model(x, m, p) for model in models]).mean(0)\n            preds = logits.argmax(dim=1).cpu().numpy()\n\n            all_preds.extend(preds)\n            all_labels.extend(y.numpy())\n\n    f1 = compute_custom_f1(all_preds, all_labels, target_gestures)\n    acc = np.mean(np.array(all_labels) == np.array(all_preds))\n\n    print(f\"Ensemble Test F1: {f1:.4f}, Accuracy: {acc:.4f}\")\n    return f1, acc\n\n\n\ndef pred_ensemble(models, data, input_mask, padding_mask, device):\n    \"\"\"\n    Generate ensemble prediction for a single sequence, returning an integer.\n    \n    Args:\n        models (list): List of PyTorch models.\n        data (torch.Tensor): Input data, shape [1, sequence_length, features].\n        input_mask (torch.Tensor): Input mask, shape [1, ...].\n        padding_mask (torch.Tensor): Padding mask, shape [1, sequence_length].\n        device (torch.device): Device ('cuda' or 'cpu').\n    \n    Returns:\n        int: Predicted class index.\n    \"\"\"\n    \n    data = data.to(device)\n    input_mask = input_mask.to(device)\n    padding_mask = padding_mask.to(device)\n    \n    for model in models:\n        model.eval()\n    \n    with torch.no_grad():\n        logits = torch.stack([model(data, input_mask, padding_mask) for model in models]).mean(dim=0)\n        pred = logits.argmax(dim=1).item()  # Extract scalar with .item()\n    \n    return pred","metadata":{"id":"FWOzdGIwq50M","trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:36:18.183742Z","iopub.execute_input":"2025-08-11T15:36:18.184026Z","iopub.status.idle":"2025-08-11T15:36:18.208205Z","shell.execute_reply.started":"2025-08-11T15:36:18.184003Z","shell.execute_reply":"2025-08-11T15:36:18.207494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_training_and_evaluation():\n    # --------------------\n    # Load and preprocess\n    # --------------------\n    data_df, labels = load_df(INPUT_PATH)\n    labels = encode_labels(labels)\n\n    print(\"labels before splitting shape:\", labels.shape)\n\n    labels = labels.sort_values(by=\"sequence_id\").reset_index(drop=True)\n    sequence_ids = np.sort(data_df[\"sequence_id\"].unique())\n\n    # --------------------\n    # Train/Test Split\n    # --------------------\n    train_ids, test_ids = train_test_split(\n        sequence_ids,\n        test_size=0.2,\n        random_state=42,\n        stratify=labels[\"label\"]\n    )\n\n    train_df = data_df[data_df[\"sequence_id\"].isin(train_ids)].reset_index(drop=True)\n    test_df = data_df[data_df[\"sequence_id\"].isin(test_ids)].reset_index(drop=True)\n\n    # Keep sequence_id for later label matching\n    train_labels_df = labels[labels[\"sequence_id\"].isin(train_ids)].reset_index(drop=True)\n    test_labels_df = labels[labels[\"sequence_id\"].isin(test_ids)].reset_index(drop=True)\n\n    train_labels = torch.tensor(train_labels_df[\"label\"].values, dtype=torch.long)\n    print(\"train labels after splitting shape:\", train_labels.shape)\n\n    test_labels_array = test_labels_df[\"label\"].values\n    print(\"test labels after splitting shape:\", test_labels_array.shape)\n\n    # --------------------\n    # Preprocess training data\n    # --------------------\n    imu_data_tensor, input_mask_tensor, padding_mask_tensor, target_gestures = CMI_preprocessor(train_df, test=False)\n    \n    print(\"imu_data shape : \" , imu_data_tensor.shape)\n    print(\"input_mask shape : \" , input_mask_tensor.shape)\n    print(\"padding_mask shape : \" , padding_mask_tensor.shape)\n    print(\"target gestures :\" , len(target_gestures))\n    \n    # Device setup\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    in_ch = imu_data_tensor.shape[2]\n    mask_dim = input_mask_tensor.shape[2]\n    num_cls = int(train_labels.max().item() + 1)\n    print(\"number of classes:\", num_cls)\n\n    # --------------------\n    # Train models\n    # --------------------\n    # models = train_vanilla_transformer(\n    #     imu_data_tensor, input_mask_tensor, padding_mask_tensor, train_labels,\n    #     in_ch, mask_dim, num_cls, device, target_gestures=target_gestures\n    # )\n\n    # --------------------\n    # Load pretrained ensemble\n    # --------------------\n    print(\"TESTING>>>>>>\")\n    models = []\n    models_path = \"/kaggle/input/model-and-encoder/kaggle/working/Models\"  # Path to saved models\n    for fold in range(5):\n        model_path = os.path.join(models_path, f\"transformer_fold_{fold}.pt\")\n        model = VanillaTransformer(in_ch, mask_dim, num_cls).to(device)\n        model.load_state_dict(torch.load(model_path, map_location=device))\n        model.eval()\n        models.append(model)\n\n    # --------------------\n    # Evaluate sequence-by-sequence\n    # --------------------\n    all_preds = []\n    all_labels = []\n    acc_list = []\n\n    for seq_id in tqdm(np.sort(test_df[\"sequence_id\"].unique())):\n        # Extract single sequence\n        seq_df = test_df[test_df[\"sequence_id\"] == seq_id].reset_index(drop=True)\n\n        # Preprocess single sequence\n        imu_seq, mask_seq, pad_seq, _ = CMI_preprocessor(seq_df, test=True)\n\n        # Predict with ensemble\n        pred_class = pred_ensemble(models, imu_seq, mask_seq, pad_seq, device)\n\n        # True label lookup\n        true_class = test_labels_df.loc[test_labels_df[\"sequence_id\"] == seq_id, \"label\"].iloc[0]\n\n        all_preds.append(pred_class)\n        all_labels.append(true_class)\n        acc_list.append(int(pred_class == true_class))\n\n    # --------------------\n    # Metrics\n    # --------------------\n    avg_accuracy = np.mean(acc_list)\n    avg_f1 = compute_custom_f1(all_labels, all_preds, target_gestures)\n\n    print(f\"Average Accuracy: {avg_accuracy:.4f}\")\n    print(f\"Average F1 Score: {avg_f1:.4f}\")\n    return avg_accuracy,avg_f1","metadata":{"id":"4ETX4VLCq-Ey","trusted":true,"outputId":"fdf97a1b-8a14-41ca-856e-c50fac3834dc","execution":{"iopub.status.busy":"2025-08-11T15:51:45.174516Z","iopub.execute_input":"2025-08-11T15:51:45.174840Z","iopub.status.idle":"2025-08-11T15:51:45.185389Z","shell.execute_reply.started":"2025-08-11T15:51:45.174786Z","shell.execute_reply":"2025-08-11T15:51:45.184845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"avg_accuracy,avg_f1 = run_training_and_evaluation()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:51:48.219298Z","iopub.execute_input":"2025-08-11T15:51:48.219564Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"TEST ACCURACY :\",avg_accuracy)\nprint(\"TEST F1 :\",avg_f1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T16:07:12.797446Z","iopub.execute_input":"2025-08-11T16:07:12.798163Z","iopub.status.idle":"2025-08-11T16:07:12.802654Z","shell.execute_reply.started":"2025-08-11T16:07:12.798130Z","shell.execute_reply":"2025-08-11T16:07:12.801830Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Predicting","metadata":{"id":"SPUnQesr5MXv"}},{"cell_type":"code","source":"def predict(data, demographics):\n    data = data.to_pandas()\n    imu_data_tensor, input_mask_tensor, padding_mask_tensor, target_gestures = CMI_preprocessor(data, test=True)\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n    in_ch = imu_data_tensor.shape[2]\n    mask_dim = input_mask_tensor.shape[2]\n    num_cls = 18\n    \n    models = []\n    models_path = \"/kaggle/input/model-and-encoder/kaggle/working/Models\"\n    for fold in range(5):\n        model_path = os.path.join(models_path, f\"transformer_fold_{fold}.pt\")\n        model = VanillaTransformer(in_ch, mask_dim, num_cls).to(device)\n        model.load_state_dict(torch.load(model_path, map_location=device))\n        model.eval()\n        models.append(model)\n        \n    pred_class = pred_ensemble(models, imu_data_tensor, input_mask_tensor, padding_mask_tensor, device)\n    \n    # Ensure pred_class is 1D before decoding\n    pred_class = np.array(pred_class).reshape(-1)\n    \n    encoder = joblib.load(\"/kaggle/input/model-and-encoder/kaggle/working/encoder.pkl\")\n    gesture = encoder.inverse_transform(pred_class)[0]\n\n    \n    print(\"label:\", pred_class, \"gesture:\", gesture)\n    return gesture\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T15:37:42.170963Z","iopub.status.idle":"2025-08-11T15:37:42.171170Z","shell.execute_reply.started":"2025-08-11T15:37:42.171072Z","shell.execute_reply":"2025-08-11T15:37:42.171081Z"}},"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,"id":"JM0CqulP5MXw","execution":{"iopub.status.busy":"2025-08-11T15:37:42.172176Z","iopub.status.idle":"2025-08-11T15:37:42.172453Z","shell.execute_reply.started":"2025-08-11T15:37:42.172292Z","shell.execute_reply":"2025-08-11T15:37:42.172304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}