{"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"},"colab":{"provenance":[{"file_id":"1xS68dekfo0qU8OLQlQVsE8dX_epk3X3P","timestamp":1753891409415}],"gpuType":"T4","authorship_tag":"ABX9TyNRh2JTC48GMs08z9YJKG+5"},"accelerator":"GPU","kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12691905,"sourceType":"datasetVersion","datasetId":8020830}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"This notebook takes the idea/ code from this publically shared notebook https://www.kaggle.com/code/rktqwe/lb-0-77-linear-accel-tf-bilstm-gru-attention","metadata":{"id":"plooOYrgL_5W"}},{"cell_type":"markdown","source":"*italicized text*# Import Necessary libraries here","metadata":{"id":"2GGSoGK4Upkg"}},{"cell_type":"code","source":"import polars as pl\nimport pandas as pd\nimport numpy as np\nimport pickle # To save/load scaler and other objects\nimport joblib\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, Subset\nimport torch.nn.functional as F\nfrom torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence, pad_sequence\n\nfrom scipy.spatial.transform import Rotation as R\n\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom sklearn.metrics import f1_score # For evaluating\n\n\nimport os\nimport json","metadata":{"id":"dtN8eMbvNTkq","executionInfo":{"status":"ok","timestamp":1754067052567,"user_tz":-330,"elapsed":6,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.061752Z","iopub.execute_input":"2025-08-02T03:36:33.062047Z","iopub.status.idle":"2025-08-02T03:36:33.066969Z","shell.execute_reply.started":"2025-08-02T03:36:33.062024Z","shell.execute_reply":"2025-08-02T03:36:33.06632Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Competition Metric Calculation","metadata":{"id":"DV5FcvFZeEAM"}},{"cell_type":"code","source":"\n\n\"\"\"\nHierarchical macro F1 metric for the CMI 2025 Challenge.\n\nThis script defines a single entry point `score(solution, submission, row_id_column_name)`\nthat the Kaggle metrics orchestrator will call.\nIt performs validation on submission IDs and computes a combined binary & multiclass F1 score.\n\"\"\"\nclass ParticipantVisibleError(Exception):\n    \"\"\"Errors raised here will be shown directly to the competitor.\"\"\"\n    pass\n\n\nclass CompetitionMetric:\n    \"\"\"Hierarchical macro F1 for the CMI 2025 challenge.\"\"\"\n    def __init__(self):\n        self.target_gestures = [\n            'Above ear - pull hair',\n            'Cheek - pinch skin',\n            'Eyebrow - pull hair',\n            'Eyelash - pull hair',\n            'Forehead - pull hairline',\n            'Forehead - scratch',\n            'Neck - pinch skin',\n            'Neck - scratch',\n        ]\n        self.non_target_gestures = [\n            'Write name on leg',\n            'Wave hello',\n            'Glasses on/off',\n            'Text on phone',\n            'Write name in air',\n            'Feel around in tray and pull out an object',\n            'Scratch knee/leg skin',\n            'Pull air toward your face',\n            'Drink from bottle/cup',\n            'Pinch knee/leg skin'\n        ]\n        self.all_classes = self.target_gestures + self.non_target_gestures\n\n    def calculate_hierarchical_f1(\n        self,\n        sol: pd.DataFrame,\n        sub: pd.DataFrame\n    ) -> float:\n\n        # Validate gestures\n        invalid_types = {i for i in sub['gesture'].unique() if i not in self.all_classes}\n        if invalid_types:\n            raise ParticipantVisibleError(\n                f\"Invalid gesture values in submission: {invalid_types}\"\n            )\n\n        # Compute binary F1 (Target vs Non-Target)\n        y_true_bin = sol['gesture'].isin(self.target_gestures).values\n        y_pred_bin = sub['gesture'].isin(self.target_gestures).values\n        f1_binary = f1_score(\n            y_true_bin,\n            y_pred_bin,\n            pos_label=True,\n            zero_division=0,\n            average='binary'\n        )\n\n        # Build multi-class labels for gestures\n        y_true_mc = sol['gesture'].apply(lambda x: x if x in self.target_gestures else 'non_target')\n        y_pred_mc = sub['gesture'].apply(lambda x: x if x in self.target_gestures else 'non_target')\n\n        # Compute macro F1 over all gesture classes\n        f1_macro = f1_score(\n            y_true_mc,\n            y_pred_mc,\n            average='macro',\n            zero_division=0\n        )\n        print(f1_binary)\n        print(f1_macro)\n        return 0.5 * f1_binary + 0.5 * f1_macro\n\n\ndef score(\n    solution: pd.DataFrame,\n    submission: pd.DataFrame,\n    row_id_column_name: str\n) -> float:\n    \"\"\"\n    Compute hierarchical macro F1 for the CMI 2025 challenge.\n\n    Expected input:\n      - solution and submission as pandas.DataFrame\n      - Column 'sequence_id': unique identifier for each sequence\n      - 'gesture': one of the eight target gestures or \"Non-Target\"\n\n    This metric averages:\n    1. Binary F1 on SequenceType (Target vs Non-Target)\n    2. Macro F1 on gesture (mapping non-targets to \"Non-Target\")\n\n    Raises ParticipantVisibleError for invalid submissions,\n    including invalid SequenceType or gesture values.\n\n\n    Examples\n    --------\n    >>> import pandas as pd\n    >>> row_id_column_name = \"id\"\n    >>> solution = pd.DataFrame({'id': range(4), 'gesture': ['Eyebrow - pull hair']*4})\n    >>> submission = pd.DataFrame({'id': range(4), 'gesture': ['Forehead - pull hairline']*4})\n    >>> score(solution, submission, row_id_column_name=row_id_column_name)\n    0.5\n    >>> submission = pd.DataFrame({'id': range(4), 'gesture': ['Text on phone']*4})\n    >>> score(solution, submission, row_id_column_name=row_id_column_name)\n    0.0\n    >>> score(solution, solution, row_id_column_name=row_id_column_name)\n    1.0\n    \"\"\"\n    # Validate required columns\n    for col in (row_id_column_name, 'gesture'):\n        if col not in solution.columns:\n            raise ParticipantVisibleError(f\"Solution file missing required column: '{col}'\")\n        if col not in submission.columns:\n            raise ParticipantVisibleError(f\"Submission file missing required column: '{col}'\")\n\n    metric = CompetitionMetric()\n    return metric.calculate_hierarchical_f1(solution, submission)\n\n","metadata":{"id":"xreiw_L1eBn9","executionInfo":{"status":"ok","timestamp":1754067052604,"user_tz":-330,"elapsed":6,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.069467Z","iopub.execute_input":"2025-08-02T03:36:33.06966Z","iopub.status.idle":"2025-08-02T03:36:33.090634Z","shell.execute_reply.started":"2025-08-02T03:36:33.069643Z","shell.execute_reply":"2025-08-02T03:36:33.090107Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Configuration variables","metadata":{"id":"_dmyIpyyMRqH"}},{"cell_type":"code","source":"path1 = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\"\npath2 = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\"","metadata":{"id":"dLihqJEkU9zk","executionInfo":{"status":"ok","timestamp":1754067054506,"user_tz":-330,"elapsed":1897,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"cb59a7bb-79e9-430e-8ba0-cd212e0ece71","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.091942Z","iopub.execute_input":"2025-08-02T03:36:33.092354Z","iopub.status.idle":"2025-08-02T03:36:33.110673Z","shell.execute_reply.started":"2025-08-02T03:36:33.092338Z","shell.execute_reply":"2025-08-02T03:36:33.109946Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Configuration & Global Variables ---\n# Define the features that will be used for training and prediction\nTRAIN = False\n\nFEATURE_COLUMNS = [\n    'acc_x', 'acc_y', 'acc_z', 'rot_x', 'rot_y', 'rot_z', 'rot_w',\n    'linear_acc_x', 'linear_acc_y', 'linear_acc_z', 'linear_acc_mag',\n    'rot_angle', 'acc_mag_jerk', 'rot_angle_vel'\n]\nTARGET_COLUMN = 'gesture'\nSEQUENCE_ID_COLUMN = 'sequence_id'\n\n############################################################################\n# needs attention while prediction\nMAX_SEQUENCE_LENGTH = 700 # Based on prior EDA on the train.csv\n                          # Also beware we don't know the lenght of a gesture in a\n                          # test dataset\n                          # This is crucial for padding/truncating sequences.\n############################################################################\n\nBATCH_SIZE = 32\nNUM_EPOCHS = 50\nLEARNING_RATE = 0.001\nHIDDEN_SIZE = 128\nNUM_LSTM_LAYERS = 2\nDROPOUT_RATE = 0.2\nN_SPLITS = 5 # For StratifiedGroupKFold\n\nMODEL_SAVE_PATH = 'trained_model.pth'\nSCALER_SAVE_PATH = ''\nMAPPING_SAVE_PATH = 'gesture_mapping.pkl'\nMAX_SEQ_LEN_SAVE_PATH = 'max_sequence_length.pkl' # Save this as well!\n\nLABEL_MAP_PATH = 'label_map.json'","metadata":{"id":"h1zUUgLQMKjd","executionInfo":{"status":"ok","timestamp":1754067054508,"user_tz":-330,"elapsed":61,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.24393Z","iopub.execute_input":"2025-08-02T03:36:33.244151Z","iopub.status.idle":"2025-08-02T03:36:33.249359Z","shell.execute_reply.started":"2025-08-02T03:36:33.244132Z","shell.execute_reply":"2025-08-02T03:36:33.248634Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**bold text**# Device agnostic code for PyTorch","metadata":{"id":"ORHSsbG0PT68"}},{"cell_type":"code","source":"DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {DEVICE}\")","metadata":{"id":"9UDg_1nIPTZC","executionInfo":{"status":"ok","timestamp":1754067054511,"user_tz":-330,"elapsed":58,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"910ad6cd-513a-45f7-89c3-2c93d381c6d1","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.250613Z","iopub.execute_input":"2025-08-02T03:36:33.250868Z","iopub.status.idle":"2025-08-02T03:36:33.269067Z","shell.execute_reply.started":"2025-08-02T03:36:33.250847Z","shell.execute_reply":"2025-08-02T03:36:33.26839Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load the data","metadata":{"id":"i17IiaX8LyO2"}},{"cell_type":"code","source":"if TRAIN:\n    df1 = pd.read_csv(path1)\n    df2 = pd.read_csv(path2)","metadata":{"id":"2GFYY-nxVAFg","executionInfo":{"status":"ok","timestamp":1754067074959,"user_tz":-330,"elapsed":20474,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.269657Z","iopub.execute_input":"2025-08-02T03:36:33.269863Z","iopub.status.idle":"2025-08-02T03:36:33.284653Z","shell.execute_reply.started":"2025-08-02T03:36:33.269848Z","shell.execute_reply":"2025-08-02T03:36:33.284055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    max_sequence_len = df2.groupby('sequence_id').size().max()\n    print(max_sequence_len)","metadata":{"id":"efhN_VJlRs10","executionInfo":{"status":"ok","timestamp":1754067074990,"user_tz":-330,"elapsed":20,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"cc096535-10e9-49ee-cbef-0d7436aba128","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.286452Z","iopub.execute_input":"2025-08-02T03:36:33.286638Z","iopub.status.idle":"2025-08-02T03:36:33.299779Z","shell.execute_reply.started":"2025-08-02T03:36:33.28662Z","shell.execute_reply":"2025-08-02T03:36:33.299094Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## create a new dataframe df2_imu containing the only the raw imu data","metadata":{"id":"psiSA2AGibzs"}},{"cell_type":"code","source":"if TRAIN:\n    # create a new dataframe df2_imu containing only the raw imu data\n    df2_imu = df2[\n                    [\n                      \"sequence_id\",\n                      \"acc_x\",\n                      \"acc_y\",\n                      \"acc_z\",\n                      \"rot_x\",\n                      \"rot_y\",\n                      \"rot_z\",\n                      \"rot_w\",\n                      \"gesture\"\n                    ]\n                  ]\n    df2_imu['sequence_id'] = df2_imu['sequence_id'].str.slice(start=4).astype(int)","metadata":{"id":"jgsWXKyFgdnd","executionInfo":{"status":"ok","timestamp":1754067075463,"user_tz":-330,"elapsed":404,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"1817f6dd-adef-4362-ad9f-033138fbe8a5","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.300475Z","iopub.execute_input":"2025-08-02T03:36:33.300673Z","iopub.status.idle":"2025-08-02T03:36:33.313817Z","shell.execute_reply.started":"2025-08-02T03:36:33.300658Z","shell.execute_reply":"2025-08-02T03:36:33.313222Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    acc_values = df2[[\"acc_x\",\"acc_y\",\"acc_z\"]]\n    rot_values = df2[[\"rot_x\",\"rot_y\",\"rot_z\",\"rot_w\"]]","metadata":{"id":"CFItfRqUNwVN","executionInfo":{"status":"ok","timestamp":1754067075465,"user_tz":-330,"elapsed":31,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.3145Z","iopub.execute_input":"2025-08-02T03:36:33.314711Z","iopub.status.idle":"2025-08-02T03:36:33.332894Z","shell.execute_reply.started":"2025-08-02T03:36:33.314697Z","shell.execute_reply":"2025-08-02T03:36:33.332248Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Remove gravity from raw_acclelerometer data and modify the dataframe df2_imu","metadata":{"id":"WT1RsDWAgNFH"}},{"cell_type":"code","source":"def remove_gravity_from_acc(acc_data:pd.DataFrame, rot_data:pd.DataFrame):\n\n  # check if acc_data is a pandas dataframe\n  # if yes acc_values convert it into a numpy array\n  # else have it as asis\n\n  if isinstance(acc_data, pd.DataFrame):\n      acc_values = acc_data[['acc_x', 'acc_y', 'acc_z']].values\n  else:\n      acc_values = acc_data\n\n  # check if rot_data is a pandas dataframe\n  # if yes quat_values convert it into a numpy array\n  # else have it as asis\n\n  if isinstance(rot_data, pd.DataFrame):\n      quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n  else:\n      quat_values = rot_data\n\n  num_samples = acc_values.shape[0] # no of rows in acc_values\n\n  # create a numpy array linear_accel filled with 0s whose shape is like\n  # acc_values\n  linear_accel = np.zeros_like(acc_values)\n\n  # create a numpy array with earths gravity\n  gravity_world = np.array([0, 0, 9.81])\n\n\n  # iterate as many times as there are rows in acc_values\n  for i in range(num_samples):\n      # IF all the values in i^th row of quat_values are \"NaN (not a number)\"\n      #                                OR\n      # IF all the values in i^th row of quat_values are close to 0\n      # THEN copy ALL values in i^th row of acc_values to i^th row of\n      #    linear_accel and goto the next iteration of i\n      if np.all(np.isnan(quat_values[i])) or np.all(np.isclose(quat_values[i], 0)):\n          linear_accel[i, :] = acc_values[i, :]\n          continue\n\n\n      try:\n          # create a rotation object using the i^th row of quat_values\n          rotation = R.from_quat(quat_values[i])\n\n          # rotate the gravity_world vector using the i^th row quarternion to\n          # the sensor_frame (inverse=True means rotate the gravity vector to\n          # the sensor frame)\n          gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)\n\n          # substract the rotated gravity vector (components) from the corresponding components of acc_values\n          linear_accel[i, :] = acc_values[i, :] - gravity_sensor_frame\n      except ValueError:\n            linear_accel[i, :] = acc_values[i, :]\n\n  return linear_accel","metadata":{"id":"Ifq2BigdxZUt","executionInfo":{"status":"ok","timestamp":1754067075467,"user_tz":-330,"elapsed":20,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.333592Z","iopub.execute_input":"2025-08-02T03:36:33.333794Z","iopub.status.idle":"2025-08-02T03:36:33.348535Z","shell.execute_reply.started":"2025-08-02T03:36:33.333778Z","shell.execute_reply":"2025-08-02T03:36:33.347857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    remove_gravity_from_acc(acc_values,rot_values)\n    \n    df2_imu['linear_acc_x'] = remove_gravity_from_acc(acc_values,rot_values)[:,0]\n    df2_imu['linear_acc_y'] = remove_gravity_from_acc(acc_values,rot_values)[:,1]\n    df2_imu['linear_acc_z'] = remove_gravity_from_acc(acc_values,rot_values)[:,2]","metadata":{"id":"G504J82pPI34","executionInfo":{"status":"ok","timestamp":1754067169534,"user_tz":-330,"elapsed":94076,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"cdce6400-b193-4c5e-afa1-b37ef26aeb4f","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.349267Z","iopub.execute_input":"2025-08-02T03:36:33.349568Z","iopub.status.idle":"2025-08-02T03:36:33.36807Z","shell.execute_reply.started":"2025-08-02T03:36:33.349542Z","shell.execute_reply":"2025-08-02T03:36:33.367395Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Calulate the linear_acceleration magnitude and add it to df2_imu**\n\n\n<div style=\"border: 1px solid black; padding: 10px;\">\n$ a = \\sqrt{x^2 + y^2 + z^2}$\n\n> where  \na = linear acceleration magnitude  \nx = x component of the acceleration  \ny = y component of the acceleration  \nz = z component of the acceleration  \n\n</div>","metadata":{"id":"T5UIMYdei2fA"}},{"cell_type":"code","source":"def linear_acc_mag(linear_acc : pd.DataFrame):\n  return np.sqrt(\n      linear_acc['linear_acc_x']**2 + \\\n      linear_acc['linear_acc_y']**2 + \\\n      linear_acc['linear_acc_z']**2\n      )","metadata":{"id":"yUwPRPp0jhv6","executionInfo":{"status":"ok","timestamp":1754067169537,"user_tz":-330,"elapsed":38,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.368738Z","iopub.execute_input":"2025-08-02T03:36:33.368993Z","iopub.status.idle":"2025-08-02T03:36:33.383552Z","shell.execute_reply.started":"2025-08-02T03:36:33.368972Z","shell.execute_reply":"2025-08-02T03:36:33.382848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    df2_imu['linear_acc_mag'] = linear_acc_mag(df2_imu[['linear_acc_x',   'linear_acc_y','linear_acc_z']])","metadata":{"id":"nk2wLVXUlVQE","executionInfo":{"status":"ok","timestamp":1754067169540,"user_tz":-330,"elapsed":31,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"831efd0a-2030-408e-f7c8-4d84a82165db","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.385622Z","iopub.execute_input":"2025-08-02T03:36:33.385815Z","iopub.status.idle":"2025-08-02T03:36:33.398072Z","shell.execute_reply.started":"2025-08-02T03:36:33.3858Z","shell.execute_reply":"2025-08-02T03:36:33.397369Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Calculate the rotational angle and add it to df2_imu","metadata":{"id":"4Jz0mPoauLqQ"}},{"cell_type":"code","source":"if TRAIN:\n    df2_imu['rot_angle'] = 2 * np.arccos(df2_imu['rot_w'].clip(-1, 1))","metadata":{"id":"-snbmITSl9um","executionInfo":{"status":"ok","timestamp":1754067169554,"user_tz":-330,"elapsed":26,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"30b2e0d2-b42d-48f2-e921-7a9d980b1f93","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.398634Z","iopub.execute_input":"2025-08-02T03:36:33.398788Z","iopub.status.idle":"2025-08-02T03:36:33.412818Z","shell.execute_reply.started":"2025-08-02T03:36:33.398776Z","shell.execute_reply":"2025-08-02T03:36:33.412145Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Calculate the jerk at each time point\n\n$ jerk = \\frac{da}{dt} $\n\nwhere a is acceleration","metadata":{"id":"mMsqN_IPxKC2"}},{"cell_type":"code","source":"if TRAIN:\n    df2_imu['acc_mag_jerk'] = df2_imu.groupby('sequence_id')['linear_acc_mag'].diff().fillna(0)","metadata":{"id":"JupV5Y8cxJOM","executionInfo":{"status":"ok","timestamp":1754067169568,"user_tz":-330,"elapsed":13,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"aa591aff-580e-4bcf-eb30-1bd8af1a9663","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.413477Z","iopub.execute_input":"2025-08-02T03:36:33.413647Z","iopub.status.idle":"2025-08-02T03:36:33.42824Z","shell.execute_reply.started":"2025-08-02T03:36:33.413634Z","shell.execute_reply":"2025-08-02T03:36:33.427614Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Calculation of rotational velocity and adding it to df2_imu**\n\n>If rotational angle at time $t{_1}$ is $\\theta {_1}$ and  \n if the rotational angle at time $t{_2}$ is $\\theta {_2}$ then  \n\n>$ \\omega = \\frac{\\theta {_2} - \\theta {_1}}{t{_2}-t{_1}}$  \nwhere $\\omega$ is the angular velocity\n\n","metadata":{"id":"es1jw-POyGM7"}},{"cell_type":"code","source":"if TRAIN:\n    df2_imu['rot_angle_vel'] = df2_imu.groupby('sequence_id')['rot_angle'].diff().fillna(0)","metadata":{"id":"Ur6FoBsBzygo","executionInfo":{"status":"ok","timestamp":1754067169588,"user_tz":-330,"elapsed":14,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"3588ef99-6801-4266-8be4-ee6f6cf19d4a","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.4288Z","iopub.execute_input":"2025-08-02T03:36:33.429033Z","iopub.status.idle":"2025-08-02T03:36:33.443069Z","shell.execute_reply.started":"2025-08-02T03:36:33.429012Z","shell.execute_reply":"2025-08-02T03:36:33.442488Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    df2_imu.head()","metadata":{"id":"NAvB3ogMiP3l","executionInfo":{"status":"ok","timestamp":1754067169636,"user_tz":-330,"elapsed":42,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"e161fa5a-e4bb-4101-a1d3-f6a8894385ff","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.443841Z","iopub.execute_input":"2025-08-02T03:36:33.444266Z","iopub.status.idle":"2025-08-02T03:36:33.458612Z","shell.execute_reply.started":"2025-08-02T03:36:33.444249Z","shell.execute_reply":"2025-08-02T03:36:33.457943Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    df2_imu[FEATURE_COLUMNS] = df2_imu[FEATURE_COLUMNS].ffill().bfill().fillna(0)","metadata":{"id":"UdOimSQSnfJZ","executionInfo":{"status":"ok","timestamp":1754067169807,"user_tz":-330,"elapsed":166,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"58ae18bd-aff8-4036-cfe6-1f7b8dcfb153","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.459249Z","iopub.execute_input":"2025-08-02T03:36:33.45944Z","iopub.status.idle":"2025-08-02T03:36:33.474599Z","shell.execute_reply.started":"2025-08-02T03:36:33.459426Z","shell.execute_reply":"2025-08-02T03:36:33.473862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    nan_present = df2_imu.isnull().any()\n    \n    print(\"Columns with at least one NaN value:\")\n    print(nan_present)","metadata":{"id":"7H7L9-O-oTr8","executionInfo":{"status":"ok","timestamp":1754067169927,"user_tz":-330,"elapsed":115,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"ee706f80-fbd9-4702-f4d2-043b27d06c65","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.475235Z","iopub.execute_input":"2025-08-02T03:36:33.475471Z","iopub.status.idle":"2025-08-02T03:36:33.492132Z","shell.execute_reply.started":"2025-08-02T03:36:33.47545Z","shell.execute_reply":"2025-08-02T03:36:33.491482Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Gesture Mapping","metadata":{"id":"TD7uAzmn4Qhq"}},{"cell_type":"code","source":"if TRAIN:\n    # Create integer label mapping and save it\n    label_map = {label: i for i, label in enumerate(df2_imu['gesture'].unique())}\n    with open(LABEL_MAP_PATH, 'w') as f:\n        json.dump(label_map, f)\n    df2_imu['gesture_id'] = df2_imu['gesture'].map(label_map)\n    print(f\"Gesture to ID map: {label_map}\")","metadata":{"id":"fZUSpmPq4Spm","executionInfo":{"status":"ok","timestamp":1754067170107,"user_tz":-330,"elapsed":186,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"outputId":"a5b427e2-3f8c-4e68-c3e2-ae8b21ef95e9","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.492934Z","iopub.execute_input":"2025-08-02T03:36:33.493715Z","iopub.status.idle":"2025-08-02T03:36:33.512501Z","shell.execute_reply.started":"2025-08-02T03:36:33.493694Z","shell.execute_reply":"2025-08-02T03:36:33.511935Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Seperate features, labels, and groups","metadata":{"id":"CvTtv1ac49BG"}},{"cell_type":"code","source":"if TRAIN:\n    feature_cols = [col for col in df2_imu.columns if col not in ['sequence_id', 'gesture', 'gesture_id']]\n    X = df2_imu[feature_cols]\n    y = df2_imu['gesture_id']\n    groups = df2_imu['sequence_id']\n","metadata":{"id":"khSTczaI5CEF","executionInfo":{"status":"ok","timestamp":1754067170110,"user_tz":-330,"elapsed":33,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.513202Z","iopub.execute_input":"2025-08-02T03:36:33.513447Z","iopub.status.idle":"2025-08-02T03:36:33.528432Z","shell.execute_reply.started":"2025-08-02T03:36:33.513422Z","shell.execute_reply":"2025-08-02T03:36:33.527746Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Loaders","metadata":{"id":"F5znu2o-3FS3"}},{"cell_type":"code","source":"class GestureDataset(Dataset):\n  def __init__(self, sequences):\n    \"\"\"\n    Args:\n        sequences (list): A list of dictionaries, where each dictionary\n                          represents a single gesture sequence.\n                          e.g., [{'data': np.array, 'label': int}, ...]\n    \"\"\"\n    self.sequences = sequences\n\n  def __len__(self):\n    # The total number of sequences (gestures) in the dataset\n    return len(self.sequences)\n\n  def __getitem__(self, idx):\n    # Fetch a single sequence by its index\n    sequence_data = self.sequences[idx]\n\n    # Convert the numpy array of features to a PyTorch tensor\n    features = torch.tensor(sequence_data['data'], dtype=torch.float32)\n\n    # Get the original length of the sequence before any padding\n    original_length = features.shape[0]\n\n    # Convert the label to a PyTorch tensor\n    label = torch.tensor(sequence_data['label'], dtype=torch.long)\n\n    sequence_id = sequence_data['seq_id']  # <-- This line is crucial\n\n    return features, original_length, label, sequence_id","metadata":{"id":"Fc7TMAsz3H5O","executionInfo":{"status":"ok","timestamp":1754067170119,"user_tz":-330,"elapsed":29,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.529257Z","iopub.execute_input":"2025-08-02T03:36:33.529444Z","iopub.status.idle":"2025-08-02T03:36:33.546477Z","shell.execute_reply.started":"2025-08-02T03:36:33.52943Z","shell.execute_reply":"2025-08-02T03:36:33.545756Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Building","metadata":{"id":"fR5ZCA0FTk1h"}},{"cell_type":"code","source":"class PyTorchModel(nn.Module):\n  def __init__(self, num_features, num_classes, hidden_size=64):\n    super().__init__()\n\n    self.hidden_size = hidden_size\n    self.num_classes = num_classes\n\n    # Bidirectional LSTM (input: num_features, output: 2*hidden_size)\n    self.lstm = nn.LSTM(input_size=num_features,\n                        hidden_size=self.hidden_size,\n                        batch_first=True,\n                        bidirectional=True)\n\n    # Bidirectional GRU (input: 2*hidden_size (from Bi-LSTM), output: 2*hidden_size)\n    self.gru = nn.GRU(input_size=2 * self.hidden_size, # Input from Bi-LSTM output\n                      hidden_size=self.hidden_size,\n                      batch_first=True,\n                      bidirectional=True)\n\n    # Attention mechanism components\n    # Input to attention_dense is the output of Bi-GRU: (batch, seq_len, 2*hidden_size)\n    self.attention_dense = nn.Linear(2 * self.hidden_size, 1) # Maps to (batch, seq_len, 1)\n\n    # Output layer\n    self.classifier = nn.Linear(2 * self.hidden_size, num_classes) # Input: context vector from attention\n\n\n  def forward(self, x, lengths):\n    # The input 'x' is a padded tensor from the DataLoader.\n    # Its shape is: (batch_size, max_sequence_length, num_features)\n\n    # --- 1. Pack the padded input sequence ---\n    # This creates a 'PackedSequence' object which only contains the non-padded elements.\n    # This is a crucial step for the RNNs to ignore padding.\n    # The lengths tensor must be on the CPU for this operation.\n    x_packed = pack_padded_sequence(x, lengths.cpu(), batch_first=True, enforce_sorted=False)\n\n    # --- 2. Pass the packed sequence through the LSTM and GRU layers ---\n    # These layers are designed to accept and return PackedSequence objects.\n    # The output of LSTM is a PackedSequence, and we just pass it to the GRU.\n    lstm_out_packed, _ = self.lstm(x_packed)\n    gru_out_packed, _ = self.gru(lstm_out_packed)\n\n    # --- 3. Unpack the GRU output to get a padded tensor back ---\n    # The attention mechanism and Linear layer expect a standard tensor, not a PackedSequence.\n    # This converts the PackedSequence back to a padded tensor.\n    gru_out, _ = pad_packed_sequence(gru_out_packed, batch_first=True)\n    # gru_out shape: (batch_size, max_sequence_length_in_batch, 2 * hidden_size)\n\n    # The rest of the code is your attention mechanism, which is already correct for a padded tensor.\n    # --- Simple Attention Mechanism ---\n\n    # 1. Calculate raw attention scores for each time step\n    attention_scores = torch.tanh(self.attention_dense(gru_out))\n    attention_scores = attention_scores.squeeze(-1)\n\n    # 2. Mask padding (sets scores for padded areas to -infinity before softmax)\n    # Note: We need to create a mask for the attention mechanism\n    max_sequence_length = x.size(1)\n    mask = torch.arange(max_sequence_length, device=x.device).unsqueeze(0) < lengths.unsqueeze(1)\n    attention_scores = attention_scores.masked_fill(~mask, float('-inf'))\n\n    # 3. Apply softmax to get attention weights\n    attention_weights = F.softmax(attention_scores, dim=-1)\n\n    # 4. Prepare attention_weights for element-wise multiplication\n    attention_weights = attention_weights.unsqueeze(-1)\n    attention_weights = attention_weights.expand_as(gru_out)\n\n    # 5. Element-wise multiplication: Weigh the GRU output features\n    weighted_output = gru_out * attention_weights\n\n    # 6. Sum to get the context vector\n    context_vector = torch.sum(weighted_output, dim=1)\n\n    # --- Output Layer ---\n    logits = self.classifier(context_vector)\n\n    return logits","metadata":{"id":"OUx5rZXOTkDB","executionInfo":{"status":"ok","timestamp":1754067170142,"user_tz":-330,"elapsed":15,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.548948Z","iopub.execute_input":"2025-08-02T03:36:33.549152Z","iopub.status.idle":"2025-08-02T03:36:33.56509Z","shell.execute_reply.started":"2025-08-02T03:36:33.549138Z","shell.execute_reply":"2025-08-02T03:36:33.564398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, dataloader, criterion, optimizer):\n    model.train()\n    total_loss = 0\n    correct_predictions = 0\n    total_predictions = 0\n\n    for batch_features, batch_lengths, batch_labels, _ in dataloader:\n        batch_features = batch_features.to(DEVICE)\n        batch_lengths = batch_lengths.to(DEVICE)\n        batch_labels = batch_labels.to(DEVICE)\n\n        optimizer.zero_grad()\n\n        logits = model(batch_features, batch_lengths)\n        loss = criterion(logits, batch_labels)\n\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n        _, predicted_labels = torch.max(logits, 1)\n        correct_predictions += (predicted_labels == batch_labels).sum().item()\n        total_predictions += batch_labels.size(0)\n\n    avg_loss = total_loss / len(dataloader)\n    accuracy = correct_predictions / total_predictions\n    return avg_loss, accuracy\n\ndef validate_one_epoch(model, dataloader, criterion):\n    model.eval()\n    total_loss = 0\n    correct_predictions = 0\n    total_predictions = 0\n\n    with torch.no_grad():\n        for batch_features, batch_lengths, batch_labels, _ in dataloader:\n            batch_features = batch_features.to(DEVICE)\n            batch_lengths = batch_lengths.to(DEVICE)\n            batch_labels = batch_labels.to(DEVICE)\n\n            logits = model(batch_features, batch_lengths)\n            loss = criterion(logits, batch_labels)\n\n            total_loss += loss.item()\n            _, predicted_labels = torch.max(logits, 1)\n            correct_predictions += (predicted_labels == batch_labels).sum().item()\n            total_predictions += batch_labels.size(0)\n\n    avg_loss = total_loss / len(dataloader)\n    accuracy = correct_predictions / total_predictions\n    return avg_loss, accuracy\n","metadata":{"id":"1uezlXJZkA0M","executionInfo":{"status":"ok","timestamp":1754067399204,"user_tz":-330,"elapsed":8,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.565767Z","iopub.execute_input":"2025-08-02T03:36:33.565969Z","iopub.status.idle":"2025-08-02T03:36:33.587541Z","shell.execute_reply.started":"2025-08-02T03:36:33.565945Z","shell.execute_reply":"2025-08-02T03:36:33.586821Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Cross validation Loop","metadata":{"id":"6k8dDRbR7zgw"}},{"cell_type":"code","source":"def custom_collate_fn(batch):\n    \"\"\"\n    A custom collate function to handle padding and batching of variable-length sequences.\n    This is passed to the DataLoader.\n\n    Args:\n        batch (list): A list of tuples, each containing (features, length, label)\n\n    Returns:\n        tuple: A tuple containing the padded features, sorted lengths, and sorted labels.\n    \"\"\"\n    # 1. Unzip the batch: separates the tuples into three lists\n    # e.g., batch_features = [features_1, features_2, ...]\n    #       batch_lengths = [length_1, length_2, ...]\n    #       batch_labels = [label_1, label_2, ...]\n    batch_features, batch_lengths, batch_labels, batch_ids = zip(*batch)\n\n    # 2. Pad the sequences to the length of the longest sequence in the batch\n    # `pad_sequence` handles this efficiently. `batch_first=True` gives us (B, L, F) shape.\n    padded_features = pad_sequence(batch_features, batch_first=True, padding_value=0.0)\n\n    # 3. Convert lengths and labels to tensors\n    lengths_tensor = torch.tensor(batch_lengths, dtype=torch.long)\n    labels_tensor = torch.tensor(batch_labels, dtype=torch.long)\n\n    ids_tensor = torch.tensor(batch_ids, dtype=torch.long)\n\n    # 4. Sort the padded features, lengths, and labels by length in descending order\n    # This is a requirement for torch.nn.utils.rnn.pack_padded_sequence\n    lengths_sorted, sorted_indices = lengths_tensor.sort(descending=True)\n    padded_features_sorted = padded_features[sorted_indices]\n    labels_sorted = labels_tensor[sorted_indices]\n    ids_sorted = ids_tensor[sorted_indices] # Sort the IDs too\n\n\n    return padded_features_sorted, lengths_sorted, labels_sorted, ids_sorted","metadata":{"id":"_jqY7zzN3-a9","executionInfo":{"status":"ok","timestamp":1754067170173,"user_tz":-330,"elapsed":5,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.588377Z","iopub.execute_input":"2025-08-02T03:36:33.58861Z","iopub.status.idle":"2025-08-02T03:36:33.607503Z","shell.execute_reply.started":"2025-08-02T03:36:33.58859Z","shell.execute_reply":"2025-08-02T03:36:33.60683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert DataFrames to PyTorch-friendly lists of sequences ---\n# These functions are simplified versions of what we discussed earlier\ndef df_to_sequences(dataframe):\n    sequences = []\n    for seq_id, group in dataframe.groupby('sequence_id'):\n        sequences.append({\n            'data': group[feature_cols].values,\n            'label': group['gesture_id'].iloc[0],\n            'seq_id': seq_id\n        })\n    return sequences","metadata":{"id":"7p2ocL09-Nqe","executionInfo":{"status":"ok","timestamp":1754067170180,"user_tz":-330,"elapsed":4,"user":{"displayName":"gnanasenthil pro","userId":"06521947732375327160"}},"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.608251Z","iopub.execute_input":"2025-08-02T03:36:33.608471Z","iopub.status.idle":"2025-08-02T03:36:33.62561Z","shell.execute_reply.started":"2025-08-02T03:36:33.608448Z","shell.execute_reply":"2025-08-02T03:36:33.625055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    # --- 3. Stratified Group K-Fold Cross-Validation Loop ---\n    sgkf = StratifiedGroupKFold(n_splits=N_SPLITS, shuffle=True, random_state=42)\n    \n    all_fold_metrics = []\n    \n    for fold, (train_idx, val_idx) in enumerate(sgkf.split(X, y, groups=groups)):\n        print(f\"\\n--- Fold {fold+1}/{N_SPLITS} ---\")\n    \n        # Split the dataframe into train and validation sets using the indices\n        train_df = df2_imu.iloc[train_idx]\n        val_df = df2_imu.iloc[val_idx]\n    \n        # --- 4. SCALING: Fit on train data only, then transform both ---\n        # Instantiate the scaler for this fold\n        scaler = StandardScaler()\n    \n        # Fit the scaler on the training features only\n        scaler.fit(train_df[feature_cols])\n    \n        # Save the scaler for this fold (e.g., if you want to use the best one later)\n        # import joblib\n        joblib.dump(scaler, os.path.join(SCALER_SAVE_PATH,'standard_scaler.pkl'))\n    \n        # Transform both train and validation sets\n        train_df[feature_cols] = scaler.transform(train_df[feature_cols])\n        val_df[feature_cols] = scaler.transform(val_df[feature_cols])\n    \n        train_sequences = df_to_sequences(train_df)\n        val_sequences = df_to_sequences(val_df)\n    \n        # --- 6. Create PyTorch DataLoaders ---\n        train_dataset = GestureDataset(train_sequences)\n        val_dataset = GestureDataset(val_sequences)\n    \n        train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, collate_fn=custom_collate_fn)\n        val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, collate_fn=custom_collate_fn)\n    \n        print(f\"Fold {fold+1} train gesture counts:\\n{train_df['gesture'].value_counts()}\")\n        print(f\"Fold {fold+1} val gesture counts:\\n{val_df['gesture'].value_counts()}\")\n    \n        # =========================================================================\n        # --- MODEL TRAINING AND VALIDATION PART (Step 7) ---\n        # =========================================================================\n    \n        # Instantiate the model, optimizer, and loss function for this fold\n        # A fresh model is created for each fold to ensure independent training\n        num_features = len(feature_cols)\n        num_classes = len(label_map)\n    \n        model = PyTorchModel(num_features, num_classes, hidden_size=HIDDEN_SIZE).to(DEVICE)\n        optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)\n        criterion = nn.CrossEntropyLoss()\n    \n        best_val_accuracy = 0.0\n        for epoch in range(NUM_EPOCHS):\n            # Training phase\n            train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer)\n    \n            # Validation phase\n            val_loss, val_acc = validate_one_epoch(model, val_loader, criterion)\n    \n            print(f\"  Epoch {epoch+1}/{NUM_EPOCHS} | \"\n                  f\"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | \"\n                  f\"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}\")\n    \n            # Save the best model for this fold based on validation accuracy\n            if val_acc > best_val_accuracy:\n                best_val_accuracy = val_acc\n                torch.save(model.state_dict(), MODEL_SAVE_PATH.format(fold+1))\n                print(f\"  -> Model saved for fold {fold+1} with improved accuracy: {best_val_accuracy:.4f}\")\n    \n        print(f\"Finished training for Fold {fold+1}. Best Validation Accuracy: {best_val_accuracy:.4f}\")\n    \n        ## Need to calculate the competition metrics here\n        print(\"Calculating competition metric for the best model...\")\n    \n        # 1. Load the best model's weights from the disk\n        best_model = PyTorchModel(num_features, num_classes,\n                                  hidden_size=HIDDEN_SIZE).to(DEVICE)\n        best_model.load_state_dict(torch.load(MODEL_SAVE_PATH.format(fold+1)))\n        best_model.eval() # Set the model to evaluation mode\n    \n        # 2. Run inference on the entire validation set and collect sequence_id\n        val_sequence_ids = []\n        val_true_labels = []\n        val_pred_labels = []\n        with torch.no_grad():\n            # val_loader should now return sequence_id\n            for batch_features, batch_lengths, batch_labels, batch_ids in val_loader:\n                batch_features = batch_features.to(DEVICE)\n                batch_lengths = batch_lengths.to(DEVICE)\n    \n                logits = best_model(batch_features, batch_lengths)\n                _, predicted = torch.max(logits, 1)\n    \n                val_sequence_ids.extend(batch_ids.cpu().numpy())\n                val_true_labels.extend(batch_labels.cpu().numpy())\n                val_pred_labels.extend(predicted.cpu().numpy())\n    \n        # 3. Create a map from integer IDs back to gesture names\n        # You need a variable that maps class IDs (integers) to gesture names (strings)\n        id_to_gesture_map = {v: k for k, v in label_map.items()}\n    \n        # 4. Map integer labels to gesture names and create DataFrames\n        true_gestures = [id_to_gesture_map[i] for i in val_true_labels]\n        pred_gestures = [id_to_gesture_map[i] for i in val_pred_labels]\n    \n        solution_df = pd.DataFrame({\n            'id': val_sequence_ids,\n            'gesture': true_gestures\n        })\n    \n        submission_df = pd.DataFrame({\n            'id': val_sequence_ids,\n            'gesture': pred_gestures\n        })\n    \n        # 5. Instantiate the CompetitionMetric class and call the method\n        metric_calculator = CompetitionMetric()\n        competition_metric_value = metric_calculator.calculate_hierarchical_f1(\n            sol=solution_df,\n            sub=submission_df\n        )\n    \n        print(f\"Fold {fold+1} Competition Metric: {competition_metric_value:.4f}\")\n        all_fold_metrics.append(competition_metric_value)\n    \n    \n        ## End of competition metric calculation\n    \n    print(\"\\nCross-validation training complete.\")\n    print(f\"Competition Metric per fold: {all_fold_metrics}\")\n    print(f\"Final Average Competition Metric: {np.mean(all_fold_metrics):.4f}\")","metadata":{"id":"Wxv-gkhf72tY","outputId":"c9a2d411-602b-4dc1-c558-e77bdab10354","trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.626337Z","iopub.execute_input":"2025-08-02T03:36:33.62649Z","iopub.status.idle":"2025-08-02T03:36:33.804063Z","shell.execute_reply.started":"2025-08-02T03:36:33.626478Z","shell.execute_reply":"2025-08-02T03:36:33.803332Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submission Code","metadata":{}},{"cell_type":"markdown","source":"## Submission config variables","metadata":{}},{"cell_type":"code","source":"\nif TRAIN == False:\n    MODEL_PATH = '/kaggle/input/artifact-dataaugmented/trained_model_DataAugmented.pth'\n    SCALER_PATH = '/kaggle/input/artifact-dataaugmented/standard_scaler.pkl'\n    LABEL_MAP_PATH = '/kaggle/input/artifact-dataaugmented/label_map.json'\n    MAX_SEQUENCE_LENGTH = 700\n\n    # Initialize global variables to None\n    MODEL = None\n    SCALER = None\n    LABEL_MAP = None\n    ID_TO_GESTURE_MAP = None\n    NUM_FEATURES = len(FEATURE_COLUMNS)\n    NUM_CLASSES = 0 # Will be set after loading label_map\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.804816Z","iopub.execute_input":"2025-08-02T03:36:33.805025Z","iopub.status.idle":"2025-08-02T03:36:33.823943Z","shell.execute_reply.started":"2025-08-02T03:36:33.80501Z","shell.execute_reply":"2025-08-02T03:36:33.823353Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Artifact loading","metadata":{}},{"cell_type":"code","source":"if TRAIN == False:\n    # Load artifacts\n    try:\n        # Load label map\n        with open(LABEL_MAP_PATH, 'r') as f:\n            LABEL_MAP = json.load(f)\n        ID_TO_GESTURE_MAP = {v: k for k, v in LABEL_MAP.items()}\n        NUM_CLASSES = len(LABEL_MAP)\n    \n        # Load scaler\n        SCALER = joblib.load(SCALER_PATH)\n    \n        # Load model\n        MODEL = PyTorchModel(NUM_FEATURES, NUM_CLASSES, hidden_size=HIDDEN_SIZE)\n        # Use map_location='cpu' to ensure it loads correctly regardless of GPU availability\n        MODEL.load_state_dict(torch.load(MODEL_PATH, map_location=torch.device('cpu')))\n        MODEL.to(torch.device(\"cpu\")) # Ensure model is on CPU for inference\n        MODEL.eval() # Set to evaluation mode for inference (disables dropout, batchnorm)\n\n        print(\"Inference artifacts (model, scaler, label map) loaded successfully.\")\n\n    except Exception as e:\n        print(f\"Error loading inference artifacts: {e}\")\n        # In a real competition, you might want to raise an error here\n        # or ensure a default behavior. For now, we'll let it be None.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.824599Z","iopub.execute_input":"2025-08-02T03:36:33.824826Z","iopub.status.idle":"2025-08-02T03:36:33.854188Z","shell.execute_reply.started":"2025-08-02T03:36:33.824802Z","shell.execute_reply":"2025-08-02T03:36:33.853588Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submission Predict function","metadata":{}},{"cell_type":"code","source":"# --- 3. The Predict Function for Kaggle Submission ---\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    Predicts the gesture for a given sequence of sensor data.\n\n    Args:\n        sequence (pl.DataFrame): Polars DataFrame containing sensor data for one gesture.\n                                 Expected columns: sequence_id, acc_x, acc_y, etc.\n        demographics (pl.DataFrame): Polars DataFrame containing demographic info (not used by this model).\n\n    Returns:\n        str: The predicted gesture string (e.g., 'Cheek - pinch skin').\n    \"\"\"\n    # Defensive check: Ensure artifacts are loaded. This should ideally happen globally.\n    if MODEL is None or SCALER is None or ID_TO_GESTURE_MAP is None:\n        print(\"Error: Model or scaler not loaded. Returning default gesture.\")\n        return 'Text on phone' # Fallback gesture\n\n    # 1. Convert Polars DataFrame to Pandas DataFrame for easier preprocessing\n    sequence_pd = sequence.to_pandas()\n\n    # 2. Preprocess sequence_id (if it's in 'SEQ_XXXXXX' format)\n    # This must match how you processed sequence_id during training\n    if 'sequence_id' in sequence_pd.columns and isinstance(sequence_pd['sequence_id'].iloc[0], str) and sequence_pd['sequence_id'].iloc[0].startswith('SEQ_'):\n        sequence_pd['sequence_id'] = sequence_pd['sequence_id'].str.slice(start=4).astype(int)\n    # Ensure sequence_id is int if it was already numeric but not int\n    elif 'sequence_id' in sequence_pd.columns:\n        sequence_pd['sequence_id'] = sequence_pd['sequence_id'].astype(int)\n\n    # Do feature engineering here\n    acc_values = sequence_pd[[\"acc_x\",\"acc_y\",\"acc_z\"]]\n    rot_values = sequence_pd[[\"rot_x\",\"rot_y\",\"rot_z\",\"rot_w\"]]\n    remove_gravity_from_acc(acc_values,rot_values)\n\n    sequence_pd['linear_acc_x'] = remove_gravity_from_acc(acc_values,rot_values)[:,0]\n    sequence_pd['linear_acc_y'] = remove_gravity_from_acc(acc_values,rot_values)[:,1]\n    sequence_pd['linear_acc_z'] = remove_gravity_from_acc(acc_values,rot_values)[:,2]\n\n    sequence_pd['linear_acc_mag'] = linear_acc_mag(sequence_pd[['linear_acc_x',   'linear_acc_y','linear_acc_z']])\n    \n    sequence_pd['rot_angle'] = 2 * np.arccos(sequence_pd['rot_w'].clip(-1, 1))\n\n    sequence_pd['acc_mag_jerk'] = sequence_pd.groupby('sequence_id')['linear_acc_mag'].diff().fillna(0)\n    \n    sequence_pd['rot_angle_vel'] = sequence_pd.groupby('sequence_id')['rot_angle'].diff().fillna(0)\n    \n    # 3. Extract features and handle NaNs (ffill, bfill, fillna(0))\n    # This must match your training preprocessing exactly\n    # Apply imputation within the sequence if sequence_id is reliable for grouping\n    if 'sequence_id' in sequence_pd.columns:\n        sequence_features = sequence_pd.groupby('sequence_id')[FEATURE_COLUMNS].ffill().bfill().fillna(0)\n    else: # If no sequence_id, treat as a single continuous sequence for imputation\n        sequence_features = sequence_pd[FEATURE_COLUMNS].ffill().bfill().fillna(0)\n\n    # 4. Apply the loaded StandardScaler\n    # The scaler expects a 2D array (num_samples, num_features)\n    scaled_features = SCALER.transform(sequence_features)\n\n    # 5. Convert to PyTorch tensor and prepare for model\n    # Model expects (batch_size, sequence_length, num_features)\n    # For a single sequence, batch_size is 1\n    input_tensor = torch.tensor(scaled_features, dtype=torch.float32).unsqueeze(0) # Add batch dimension\n    \n    # Original length for pack_padded_sequence. For a single sequence, it's its own length.\n    original_length = torch.tensor([scaled_features.shape[0]], dtype=torch.long) \n\n    # 6. Handle MAX_SEQUENCE_LENGTH (truncation if needed)\n    # This is crucial if inference sequences can be longer than MAX_SEQUENCE_LENGTH\n    if input_tensor.shape[1] > MAX_SEQUENCE_LENGTH:\n        input_tensor = input_tensor[:, :MAX_SEQUENCE_LENGTH, :]\n        original_length = torch.tensor([MAX_SEQUENCE_LENGTH], dtype=torch.long)\n    \n    # Move tensor to CPU (Kaggle inference environments often don't have GPUs for submission)\n    input_tensor = input_tensor.to(torch.device(\"cpu\"))\n    original_length = original_length.to(torch.device(\"cpu\"))\n\n    # 7. Run inference\n    with torch.no_grad(): # Disable gradient calculation for inference\n        logits = MODEL(input_tensor, original_length)\n        _, predicted_class_id = torch.max(logits, 1) # Get the class with the highest logit\n    \n    # 8. Map predicted ID back to gesture string\n    predicted_gesture_string = ID_TO_GESTURE_MAP[predicted_class_id.item()]\n\n    return predicted_gesture_string","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.854861Z","iopub.execute_input":"2025-08-02T03:36:33.855122Z","iopub.status.idle":"2025-08-02T03:36:33.86981Z","shell.execute_reply.started":"2025-08-02T03:36:33.855101Z","shell.execute_reply":"2025-08-02T03:36:33.869061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import kaggle_evaluation.cmi_inference_server\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T03:36:33.870539Z","iopub.execute_input":"2025-08-02T03:36:33.870771Z","iopub.status.idle":"2025-08-02T03:36:35.063945Z","shell.execute_reply.started":"2025-08-02T03:36:33.87075Z","shell.execute_reply":"2025-08-02T03:36:35.063301Z"}},"outputs":[],"execution_count":null}]}