{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.11"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":258958155,"sourceType":"kernelVersion"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":6147.816935,"end_time":"2025-08-11T22:22:31.386924","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-08-11T20:40:03.569989","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q schedulefree","metadata":{"papermill":{"duration":75.51841,"end_time":"2025-08-11T20:41:23.435554","exception":false,"start_time":"2025-08-11T20:40:07.917144","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T04:56:38.878134Z","iopub.execute_input":"2025-09-03T04:56:38.878420Z","iopub.status.idle":"2025-09-03T04:58:01.224916Z","shell.execute_reply.started":"2025-09-03T04:56:38.878400Z","shell.execute_reply":"2025-09-03T04:58:01.223922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pickle\nimport random\nfrom dataclasses import dataclass\n\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom matplotlib import pyplot as plt\nfrom schedulefree import RAdamScheduleFree\nfrom scipy.spatial.transform import Rotation as R\nfrom sklearn.metrics import f1_score\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom sklearn.preprocessing import StandardScaler\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchinfo import summary","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","papermill":{"duration":6.602852,"end_time":"2025-08-11T20:41:30.065116","exception":false,"start_time":"2025-08-11T20:41:23.462264","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T04:58:01.226125Z","iopub.execute_input":"2025-09-03T04:58:01.226469Z","iopub.status.idle":"2025-09-03T04:58:05.964027Z","shell.execute_reply.started":"2025-09-03T04:58:01.226412Z","shell.execute_reply":"2025-09-03T04:58:05.963336Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def seed_everything(seed):\n    os.environ[\"CUBLAS_WORKSPACE_CONFIG\"] = \":4096:8\"\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n    torch.manual_seed(seed)\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.use_deterministic_algorithms(True)\n\n\nseed_everything(0)","metadata":{"papermill":{"duration":0.035037,"end_time":"2025-08-11T20:41:30.125373","exception":false,"start_time":"2025-08-11T20:41:30.090336","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T04:58:05.965606Z","iopub.execute_input":"2025-09-03T04:58:05.965981Z","iopub.status.idle":"2025-09-03T04:58:05.975214Z","shell.execute_reply.started":"2025-09-03T04:58:05.965959Z","shell.execute_reply":"2025-09-03T04:58:05.974477Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 0. Setting","metadata":{"papermill":{"duration":0.024406,"end_time":"2025-08-11T20:41:30.175339","exception":false,"start_time":"2025-08-11T20:41:30.150933","status":"completed"},"tags":[]}},{"cell_type":"code","source":"INPUT_DIR = \"/kaggle/input/cmi-detect-behavior-with-sensor-data\"\nSEGMENTATION_DIR = \"/kaggle/input/cmi3-1-gesture-segmentation\"\nMODEL_DIR = \"./\"\nOUTPUT_DIR = \"./\"","metadata":{"papermill":{"duration":0.030001,"end_time":"2025-08-11T20:41:30.229332","exception":false,"start_time":"2025-08-11T20:41:30.199331","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T04:58:05.976030Z","iopub.execute_input":"2025-09-03T04:58:05.976372Z","iopub.status.idle":"2025-09-03T04:58:05.994240Z","shell.execute_reply.started":"2025-09-03T04:58:05.976351Z","shell.execute_reply":"2025-09-03T04:58:05.993506Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@dataclass\nclass Config:\n    seq_len: int = 75\n    n_splits: int = 10\n    n_seeds: int = 5\n    n_epochs: int = 15\n    batch_size: int = 32\n    lr: float = 5e-3\n    betas: tuple[float, float] = (0.9, 0.999)\n    label_smoothing: float = 0.1\n    aux_loss_weight: float = 0.5\n\n\nconfig = Config()","metadata":{"papermill":{"duration":0.031257,"end_time":"2025-08-11T20:41:30.285029","exception":false,"start_time":"2025-08-11T20:41:30.253772","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T04:58:05.995133Z","iopub.execute_input":"2025-09-03T04:58:05.995397Z","iopub.status.idle":"2025-09-03T04:58:06.011303Z","shell.execute_reply.started":"2025-09-03T04:58:05.995370Z","shell.execute_reply":"2025-09-03T04:58:06.010468Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Preprocessing","metadata":{"papermill":{"duration":0.024025,"end_time":"2025-08-11T20:41:30.333188","exception":false,"start_time":"2025-08-11T20:41:30.309163","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class Preprocessor:\n    def __init__(self):\n        self.scaler = StandardScaler()\n        self.scaler.set_output(transform=\"pandas\")\n\n    def _add_linear_acc(self, df: pl.DataFrame) -> pl.DataFrame:\n        acc_values = df.select([\"acc_x\", \"acc_y\", \"acc_z\"]).to_numpy()\n        quat_values = df.select([\"rot_x\", \"rot_y\", \"rot_z\", \"rot_w\"]).to_numpy()\n        linear_acc_values = np.full_like(acc_values, np.nan)\n        gravity_world = np.array([0, 0, 9.81])\n        for i in range(len(df)):\n            if np.all(np.isnan(quat_values[i])):\n                continue\n            rotation = R.from_quat(quat_values[i])\n            gravity_values = rotation.apply(gravity_world, inverse=True)\n            linear_acc_values[i, :] = acc_values[i, :] - gravity_values\n\n        df_add = pl.DataFrame(linear_acc_values, schema=[\"linear_acc_x\", \"linear_acc_y\", \"linear_acc_z\"]).fill_nan(None)\n        df = pl.concat([df, df_add], how=\"horizontal\")\n\n        return df\n\n    def _add_rotvec_diff(self, df: pl.DataFrame) -> pl.DataFrame:\n        ids = df.select(\"sequence_id\").to_series().to_numpy()\n        quat_values = df.select([\"rot_x\", \"rot_y\", \"rot_z\", \"rot_w\"]).to_numpy()\n        rotvec_diff_values = np.full((len(df), 3), np.nan)\n        for i in range(1, len(df)):\n            if ids[i - 1] != ids[i]:\n                continue\n            q1 = quat_values[i - 1]\n            q2 = quat_values[i]\n            if np.all(np.isnan(q1)) or np.all(np.isnan(q2)):\n                continue\n            rot1 = R.from_quat(q1)\n            rot2 = R.from_quat(q2)\n            rotvec_diff_values[i, :] = (rot1.inv() * rot2).as_rotvec()\n\n        df_add = pl.DataFrame(rotvec_diff_values, schema=[\"rotvec_diff_x\", \"rotvec_diff_y\", \"rotvec_diff_z\"]).fill_nan(None)\n        df = pl.concat([df, df_add], how=\"horizontal\")\n\n        return df\n\n    def _cancel_z_rotation(self, df: pl.DataFrame) -> pl.DataFrame:\n        quat_values = df.select([\"rot_x\", \"rot_y\", \"rot_z\", \"rot_w\"]).to_numpy()\n        rotate_flags = df.select(\"rotate\").to_series().to_numpy()\n        for i in range(len(df)):\n            if np.all(np.isnan(quat_values[i])):\n                continue\n            rotation = R.from_rotvec([0, 0, 130 + 180 * rotate_flags[i]], degrees=True) * R.from_quat(quat_values[i])\n            quat_values[i, :] = rotation.as_quat(canonical=True, scalar_first=True)\n\n        df_add = pl.DataFrame(quat_values, schema=[\"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"]).fill_nan(None)\n        df = pl.concat([df.drop([\"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"]), df_add], how=\"horizontal\")\n\n        return df\n\n    def _add_global_acc(self, df: pl.DataFrame) -> pl.DataFrame:\n        acc_values = df.select([\"acc_x\", \"acc_y\", \"acc_z\"]).to_numpy()\n        quat_values = df.select([\"rot_x\", \"rot_y\", \"rot_z\", \"rot_w\"]).to_numpy()\n        global_acc_values = np.full_like(acc_values, np.nan)\n        for i in range(len(df)):\n            if np.all(np.isnan(quat_values[i])):\n                continue\n            rotation = R.from_quat(quat_values[i])\n            global_acc_values[i, :] = rotation.apply(acc_values[i, :])\n\n        df_add = pl.DataFrame(global_acc_values, schema=[\"global_acc_x\", \"global_acc_y\", \"global_acc_z\"]).fill_nan(None)\n        df = pl.concat([df, df_add], how=\"horizontal\")\n\n        return df\n\n    def _handle_left_handed(self, df: pl.DataFrame) -> pl.DataFrame:\n        df = (\n            df.with_columns(\n                *[\n                    pl.when(pl.col(\"handedness\") == 0).then(-pl.col(col)).otherwise(pl.col(col)).alias(col)\n                    for col in [\n                        \"acc_x\",\n                        \"linear_acc_x\",\n                        \"global_acc_x\",\n                        \"rot_y\",\n                        \"rot_z\",\n                        \"rotvec_diff_y\",\n                        \"rotvec_diff_z\",\n                    ]\n                ],\n            )\n            .with_columns(\n                pl.when(pl.col(\"handedness\") == 0).then(pl.col(\"thm_5\")).otherwise(pl.col(\"thm_3\")).alias(\"thm_3\"),\n                pl.when(pl.col(\"handedness\") == 0).then(pl.col(\"thm_3\")).otherwise(pl.col(\"thm_5\")).alias(\"thm_5\"),\n                *[\n                    pl.when(pl.col(\"handedness\") == 0)\n                    .then(pl.col(f\"tof_5_v{i}\"))\n                    .otherwise(pl.col(f\"tof_3_v{i}\"))\n                    .alias(f\"tof_3_v{i}\")\n                    for i in range(64)\n                ],\n                *[\n                    pl.when(pl.col(\"handedness\") == 0)\n                    .then(pl.col(f\"tof_3_v{i}\"))\n                    .otherwise(pl.col(f\"tof_5_v{i}\"))\n                    .alias(f\"tof_5_v{i}\")\n                    for i in range(64)\n                ],\n            )\n            .with_columns(\n                *[\n                    pl.when(pl.col(\"handedness\") == 0)\n                    .then(pl.col(f\"tof_{i}_v{8 * j + 7 - k}\"))\n                    .otherwise(pl.col(f\"tof_{i}_v{8 * j + k}\"))\n                    .alias(f\"tof_{i}_v{8 * j + k}\")\n                    for i in range(1, 6)\n                    for j in range(8)\n                    for k in range(8)\n                ],\n            )\n        )\n\n        return df\n\n    def _handle_rotated_device(self, df: pl.DataFrame) -> pl.DataFrame:\n        df = (\n            df.with_columns(\n                *[\n                    pl.when(pl.col(\"rotate\") == 1).then(-pl.col(col)).otherwise(pl.col(col)).alias(col)\n                    for col in [\n                        \"acc_x\",\n                        \"acc_y\",\n                        \"linear_acc_x\",\n                        \"linear_acc_y\",\n                        \"global_acc_x\",\n                        \"global_acc_y\",\n                        \"rot_x\",\n                        \"rot_y\",\n                        \"rotvec_diff_x\",\n                        \"rotvec_diff_y\",\n                    ]\n                ],\n            )\n            .with_columns(\n                pl.when(pl.col(\"rotate\") == 1).then(pl.col(\"thm_4\")).otherwise(pl.col(\"thm_2\")).alias(\"thm_2\"),\n                pl.when(pl.col(\"rotate\") == 1).then(pl.col(\"thm_2\")).otherwise(pl.col(\"thm_4\")).alias(\"thm_4\"),\n                pl.when(pl.col(\"rotate\") == 1).then(pl.col(\"thm_5\")).otherwise(pl.col(\"thm_3\")).alias(\"thm_3\"),\n                pl.when(pl.col(\"rotate\") == 1).then(pl.col(\"thm_3\")).otherwise(pl.col(\"thm_5\")).alias(\"thm_5\"),\n                *[\n                    pl.when(pl.col(\"rotate\") == 1)\n                    .then(pl.col(f\"tof_4_v{i}\"))\n                    .otherwise(pl.col(f\"tof_2_v{i}\"))\n                    .alias(f\"tof_2_v{i}\")\n                    for i in range(64)\n                ],\n                *[\n                    pl.when(pl.col(\"rotate\") == 1)\n                    .then(pl.col(f\"tof_2_v{i}\"))\n                    .otherwise(pl.col(f\"tof_4_v{i}\"))\n                    .alias(f\"tof_4_v{i}\")\n                    for i in range(64)\n                ],\n                *[\n                    pl.when(pl.col(\"rotate\") == 1)\n                    .then(pl.col(f\"tof_5_v{i}\"))\n                    .otherwise(pl.col(f\"tof_3_v{i}\"))\n                    .alias(f\"tof_3_v{i}\")\n                    for i in range(64)\n                ],\n                *[\n                    pl.when(pl.col(\"rotate\") == 1)\n                    .then(pl.col(f\"tof_3_v{i}\"))\n                    .otherwise(pl.col(f\"tof_5_v{i}\"))\n                    .alias(f\"tof_5_v{i}\")\n                    for i in range(64)\n                ],\n            )\n            .with_columns(\n                *[\n                    pl.when(pl.col(\"rotate\") == 1)\n                    .then(pl.col(f\"tof_{i}_v{63 - j}\"))\n                    .otherwise(pl.col(f\"tof_{i}_v{j}\"))\n                    .alias(f\"tof_{i}_v{j}\")\n                    for i in range(1, 6)\n                    for j in range(64)\n                ],\n            )\n        )\n\n        return df\n\n    def preprocess(self, df: pl.DataFrame) -> pl.DataFrame:\n        if \"rotate\" not in df.columns:\n            df = df.with_columns(pl.lit(0).alias(\"rotate\"))\n\n        df = self._add_linear_acc(df)\n        df = self._add_rotvec_diff(df)\n        df = self._cancel_z_rotation(df)\n        df = self._add_global_acc(df)\n        df = self._handle_left_handed(df)\n        df = self._handle_rotated_device(df)\n\n        # other preprocessing\n        df = df.with_columns(\n            pl.col(\"^thm_.*$\").clip(lower_bound=20).replace({20: None}),\n            pl.col(\"^tof_._v.*$\").replace({-1: 255}),\n        )\n\n        return df\n\n    def _get_reversed_sequence_tail(self, df: pl.DataFrame, seq_len: int) -> pl.DataFrame:\n        df_tail = (\n            df.sort([\"sequence_id\", \"sequence_counter\"], descending=[False, True])\n            .group_by(\"sequence_id\", maintain_order=True)\n            .head(seq_len)\n            .with_columns(pl.col(\"sequence_counter\").cum_count().over(\"sequence_id\"))\n        )\n\n        return df_tail\n\n    def get_feature_array(self, df: pl.DataFrame, list_features: list, seq_len: int, fit: bool = False) -> np.ndarray:\n        df_tail = self._get_reversed_sequence_tail(df, seq_len)\n        if fit:\n            self.scaler.fit(df_tail.select(list_features))\n        df_tail = df_tail.to_pandas()\n        df_tail[list_features] = self.scaler.transform(df_tail[list_features])\n        df_tail = pl.from_pandas(df_tail).fill_null(0.0)\n\n        feature_arrays = []\n        for _, df_group in df_tail.group_by(\"sequence_id\", maintain_order=True):\n            array = df_group.select(list_features).to_numpy().T\n            array = np.pad(array, ((0, 0), (0, seq_len - array.shape[-1])))\n            feature_arrays.append(array)\n\n        return np.stack(feature_arrays, axis=0)","metadata":{"papermill":{"duration":0.054593,"end_time":"2025-08-11T20:41:30.412054","exception":false,"start_time":"2025-08-11T20:41:30.357461","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:01:08.250045Z","iopub.execute_input":"2025-09-03T05:01:08.250990Z","iopub.status.idle":"2025-09-03T05:01:08.291969Z","shell.execute_reply.started":"2025-09-03T05:01:08.250949Z","shell.execute_reply":"2025-09-03T05:01:08.291132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"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]\nnon_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]\nall_gestures = target_gestures + non_target_gestures\ndict_gestures = {v: i for i, v in enumerate(all_gestures)}","metadata":{"papermill":{"duration":0.030744,"end_time":"2025-08-11T20:41:30.5277","exception":false,"start_time":"2025-08-11T20:41:30.496956","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:01:14.846585Z","iopub.execute_input":"2025-09-03T05:01:14.847152Z","iopub.status.idle":"2025-09-03T05:01:14.852181Z","shell.execute_reply.started":"2025-09-03T05:01:14.847127Z","shell.execute_reply":"2025-09-03T05:01:14.851319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pl.read_csv(f\"{INPUT_DIR}/train.csv\")\ndf_demo = pl.read_csv(f\"{INPUT_DIR}/train_demographics.csv\")\ndf = df.join(df_demo, on=\"subject\", how=\"left\")","metadata":{"papermill":{"duration":12.079124,"end_time":"2025-08-11T20:41:42.631592","exception":false,"start_time":"2025-08-11T20:41:30.552468","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:01:15.090291Z","iopub.execute_input":"2025-09-03T05:01:15.091237Z","iopub.status.idle":"2025-09-03T05:01:21.125312Z","shell.execute_reply.started":"2025-09-03T05:01:15.091203Z","shell.execute_reply":"2025-09-03T05:01:21.124432Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"subjects_rotated = [\"SUBJ_019262\", \"SUBJ_045235\"]\ndf = df.with_columns(pl.col(\"subject\").is_in(subjects_rotated).cast(pl.Int16).alias(\"rotate\"))","metadata":{"papermill":{"duration":0.732307,"end_time":"2025-08-11T20:41:43.393439","exception":false,"start_time":"2025-08-11T20:41:42.661132","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:01:21.126651Z","iopub.execute_input":"2025-09-03T05:01:21.126915Z","iopub.status.idle":"2025-09-03T05:01:22.639235Z","shell.execute_reply.started":"2025-09-03T05:01:21.126895Z","shell.execute_reply":"2025-09-03T05:01:22.638334Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pp = Preprocessor()\ndf = pp.preprocess(df)","metadata":{"papermill":{"duration":61.040439,"end_time":"2025-08-11T20:42:44.459164","exception":false,"start_time":"2025-08-11T20:41:43.418725","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:01:22.640178Z","iopub.execute_input":"2025-09-03T05:01:22.640505Z","iopub.status.idle":"2025-09-03T05:02:40.655738Z","shell.execute_reply.started":"2025-09-03T05:01:22.640478Z","shell.execute_reply":"2025-09-03T05:02:40.654958Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"n_imu_blocks = 4\nfeature_blocks_except_tof = [\n    [\"acc_x\", \"acc_y\", \"acc_z\"],\n    [\"rot_w\", \"rot_x\", \"rot_y\", \"rot_z\"],\n    [\"linear_acc_x\", \"linear_acc_y\", \"linear_acc_z\", \"global_acc_x\", \"global_acc_y\", \"global_acc_z\"],\n    [\"rotvec_diff_x\", \"rotvec_diff_y\", \"rotvec_diff_z\"],\n    [\"thm_1\", \"thm_2\", \"thm_3\", \"thm_4\", \"thm_5\"],\n]\nlist_features = sum(feature_blocks_except_tof, []) + [f\"tof_{i}_v{j}\" for i in range(1, 6) for j in range(64)]","metadata":{"papermill":{"duration":0.031274,"end_time":"2025-08-11T20:42:44.515338","exception":false,"start_time":"2025-08-11T20:42:44.484064","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:03:43.390324Z","iopub.execute_input":"2025-09-03T05:03:43.390744Z","iopub.status.idle":"2025-09-03T05:03:43.396522Z","shell.execute_reply.started":"2025-09-03T05:03:43.390716Z","shell.execute_reply":"2025-09-03T05:03:43.395503Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X = pp.get_feature_array(df, list_features, seq_len=config.seq_len, fit=True)\nwith open(f\"{MODEL_DIR}/preprocessor.pickle\", \"wb\") as f:\n    pickle.dump(pp, f)\n\n# predicted gesture segment: gesture = 1, non-gesture = -1, padding = 0\ngesture_segment = np.load(f\"{SEGMENTATION_DIR}/gesture_segment.npy\")\ngesture_segment = (gesture_segment[:, np.newaxis, : config.seq_len] * 2 - 1) * (X != 0).max(axis=1, keepdims=True)\n\nX = np.concatenate([gesture_segment, X], axis=1)\nX, X_tof = X[:, :-320], X[:, -320:]\nX_tof = X_tof.transpose(0, 2, 1)\nX_tof = X_tof.reshape(X_tof.shape[0], X_tof.shape[1], 5, 8, 8)  # shape: (n_sequence, seq_len, 5, 8, 8)\nprint(X.shape, X_tof.shape)","metadata":{"papermill":{"duration":27.714702,"end_time":"2025-08-11T20:43:12.263562","exception":false,"start_time":"2025-08-11T20:42:44.54886","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:03:43.510438Z","iopub.execute_input":"2025-09-03T05:03:43.511064Z","iopub.status.idle":"2025-09-03T05:04:13.353393Z","shell.execute_reply.started":"2025-09-03T05:03:43.511037Z","shell.execute_reply":"2025-09-03T05:04:13.352469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"orientations = sorted(df[\"orientation\"].unique())\ndict_orientations = {v: i for i, v in enumerate(orientations)}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:13.354676Z","iopub.execute_input":"2025-09-03T05:04:13.354977Z","iopub.status.idle":"2025-09-03T05:04:13.372152Z","shell.execute_reply.started":"2025-09-03T05:04:13.354955Z","shell.execute_reply":"2025-09-03T05:04:13.371458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"Y_main = (\n    df.select([\"sequence_id\", \"gesture\"])\n    .unique(maintain_order=True)\n    .select(pl.col(\"gesture\").replace(dict_gestures).cast(pl.Int8))\n    .to_series()\n    .to_numpy()\n)\nY_aux = (\n    df.select([\"sequence_id\", \"orientation\"])\n    .unique(maintain_order=True)\n    .select(pl.col(\"orientation\").replace(dict_orientations).cast(pl.Int8))\n    .to_series()\n    .to_numpy()\n)\nY = np.stack([Y_main, Y_aux], axis=1)\ngroups = df.select([\"sequence_id\", \"subject\"]).unique(maintain_order=True).select(\"subject\").to_series().to_numpy()","metadata":{"papermill":{"duration":0.065618,"end_time":"2025-08-11T20:43:12.354147","exception":false,"start_time":"2025-08-11T20:43:12.288529","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:13.372871Z","iopub.execute_input":"2025-09-03T05:04:13.373066Z","iopub.status.idle":"2025-09-03T05:04:13.461380Z","shell.execute_reply.started":"2025-09-03T05:04:13.373050Z","shell.execute_reply":"2025-09-03T05:04:13.460491Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols_fig = sum(feature_blocks_except_tof, [])\nnrows = (len(cols_fig) + 3) // 4\nfig, axes = plt.subplots(nrows=nrows, ncols=4, figsize=(12, nrows * 2.5))\naxes = axes.flatten()\nfor i, col in enumerate(cols_fig):\n    for j in range(2):\n        axes[i].hist(\n            df.filter(pl.col(\"handedness\") == j).select(col),\n            bins=100,\n            density=True,\n            alpha=0.5,\n            label=f\"handedness = {j}\",\n        )\n    axes[i].set_xlabel(col)\n    axes[i].legend()\nfig.tight_layout()\nplt.show()","metadata":{"papermill":{"duration":11.854555,"end_time":"2025-08-11T20:43:24.233339","exception":false,"start_time":"2025-08-11T20:43:12.378784","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:13.463371Z","iopub.execute_input":"2025-09-03T05:04:13.463669Z","iopub.status.idle":"2025-09-03T05:04:28.687890Z","shell.execute_reply.started":"2025-09-03T05:04:13.463642Z","shell.execute_reply":"2025-09-03T05:04:28.687004Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Define NN classes and functions","metadata":{"papermill":{"duration":0.026697,"end_time":"2025-08-11T20:43:24.286935","exception":false,"start_time":"2025-08-11T20:43:24.260238","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class MyDataset(Dataset):\n    def __init__(self, X, X_tof, Y=None):\n        self.X = torch.FloatTensor(X)\n        self.X_tof = torch.FloatTensor(X_tof)\n        if Y is not None:\n            self.Y = torch.LongTensor(Y)\n\n    def __len__(self):\n        return self.X.shape[0]\n\n    def __getitem__(self, idx):\n        if \"Y\" in dir(self):\n            return (self.X[idx], self.X_tof[idx], self.Y[idx])\n        else:\n            return (self.X[idx], self.X_tof[idx], torch.Tensor())","metadata":{"papermill":{"duration":0.033595,"end_time":"2025-08-11T20:43:24.34656","exception":false,"start_time":"2025-08-11T20:43:24.312965","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:28.688788Z","iopub.execute_input":"2025-09-03T05:04:28.689035Z","iopub.status.idle":"2025-09-03T05:04:28.695173Z","shell.execute_reply.started":"2025-09-03T05:04:28.689017Z","shell.execute_reply":"2025-09-03T05:04:28.694170Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TofBlock(nn.Module):\n    def __init__(self, out_channels, kernel_size):\n        super().__init__()\n\n        self.layers = nn.Sequential(\n            self.ConvReLUBN(in_channels=1, out_channels=out_channels, kernel_size=kernel_size),\n            nn.MaxPool2d(kernel_size=2),\n            self.ConvReLUBN(in_channels=out_channels, out_channels=out_channels, kernel_size=kernel_size),\n            nn.MaxPool2d(kernel_size=2),\n            self.ConvReLUBN(in_channels=out_channels, out_channels=out_channels, kernel_size=kernel_size),\n            nn.MaxPool2d(kernel_size=2),\n        )\n\n    def forward(self, x):\n        out = self.layers(x)\n        return out\n\n    # ----- inner class -----\n\n    class ConvReLUBN(nn.Module):\n        def __init__(self, in_channels, out_channels, kernel_size):\n            super().__init__()\n\n            self.layers = nn.Sequential(\n                nn.Conv2d(in_channels, out_channels, kernel_size, padding=\"same\"),\n                nn.ReLU(),\n                nn.BatchNorm2d(out_channels),\n            )\n\n        def forward(self, x):\n            out = self.layers(x)\n            return out\n\n\nclass CNN1dBranch(nn.Module):\n    def __init__(\n        self,\n        input_channels,\n        output_channels,\n        initial_channels_per_feature,\n        cnn1d_channels,\n        cnn1d_kernel_size,\n        mlp_dropout,\n    ):\n        super().__init__()\n\n        self.initial_layers = self.ConvReLUBN(\n            input_channels,\n            input_channels * initial_channels_per_feature,\n            cnn1d_kernel_size,\n            groups=input_channels,\n        )\n\n        cnn1d_layers = nn.ModuleList()\n        for i in range(len(cnn1d_channels)):\n            if i == 0:\n                in_channels = input_channels * initial_channels_per_feature\n            else:\n                in_channels = cnn1d_channels[i - 1]\n            out_channels = cnn1d_channels[i]\n\n            cnn1d_layers.append(self.ConvReLUBN(in_channels, out_channels, cnn1d_kernel_size))\n            if i < len(cnn1d_channels) - 1:\n                cnn1d_layers.append(nn.MaxPool1d(kernel_size=3, stride=3))\n        self.cnn1d_layers = nn.Sequential(*cnn1d_layers)\n\n        n_channels = cnn1d_channels[-1] * 2\n        self.mlp = nn.Sequential(\n            nn.Linear(n_channels, n_channels // 2),\n            nn.ReLU(),\n            nn.Dropout(mlp_dropout),\n            nn.Linear(n_channels // 2, n_channels // 4),\n            nn.ReLU(),\n            nn.Dropout(mlp_dropout),\n            nn.Linear(n_channels // 4, output_channels),\n        )\n\n    def forward(self, x, gesture_segment):\n        x = self.initial_layers(x)\n        x = self.cnn1d_layers(x)\n        x1 = (x * (gesture_segment > 0)).sum(dim=2) / (gesture_segment > 0).sum(dim=2).clamp(min=1)\n        x2 = (x * (gesture_segment < 0)).sum(dim=2) / (gesture_segment < 0).sum(dim=2).clamp(min=1)\n        x = torch.cat([x1, x2], dim=1)\n        out = self.mlp(x)\n        return x, out\n\n    # ----- inner class -----\n\n    class ConvReLUBN(nn.Module):\n        def __init__(self, in_channels, out_channels, kernel_size, stride=1, groups=1):\n            super().__init__()\n\n            if stride == 1:\n                padding = \"same\"\n            else:\n                padding = (kernel_size - stride) // 2\n            self.layers = nn.Sequential(\n                nn.Conv1d(\n                    in_channels,\n                    out_channels,\n                    kernel_size=kernel_size,\n                    stride=stride,\n                    padding=padding,\n                    groups=groups,\n                ),\n                nn.ReLU(),\n                nn.BatchNorm1d(out_channels),\n            )\n\n        def forward(self, x):\n            out = self.layers(x)\n            return out\n\n\nclass MultiBranchClassifier(nn.Module):\n    def __init__(\n        self,\n        n_imu_blocks,\n        input_channels,\n        output_channels,\n        initial_channels_per_feature,\n        cnn1d_channels,\n        cnn1d_kernel_size,\n        tof_out_channels,\n        tof_kernel_size,\n        mlp_dropout,\n    ):\n        super().__init__()\n\n        self.n_imu_blocks = n_imu_blocks\n        self.block_idxes = [1] + [1 + sum(input_channels[: i + 1]) for i in range(len(input_channels))]\n        self.cnn_branches = nn.ModuleList(\n            [\n                CNN1dBranch(\n                    input_channels[i],\n                    output_channels,\n                    initial_channels_per_feature,\n                    cnn1d_channels,\n                    cnn1d_kernel_size,\n                    mlp_dropout,\n                )\n                for i in range(len(input_channels))\n            ]\n            + [\n                CNN1dBranch(\n                    tof_out_channels * 5,\n                    output_channels,\n                    initial_channels_per_feature,\n                    cnn1d_channels,\n                    cnn1d_kernel_size,\n                    mlp_dropout,\n                )\n            ]\n        )\n        self.tof_blocks = nn.ModuleList(\n            [\n                TofBlock(\n                    out_channels=tof_out_channels,\n                    kernel_size=tof_kernel_size,\n                )\n                for _ in range(5)\n            ]\n        )\n\n        n_channels = cnn1d_channels[-1] * (len(input_channels) + 1) * 2\n        self.mlp_all = nn.Sequential(\n            nn.Linear(n_channels, n_channels // 2),\n            nn.ReLU(),\n            nn.Dropout(mlp_dropout),\n            nn.Linear(n_channels // 2, n_channels // 4),\n            nn.ReLU(),\n            nn.Dropout(mlp_dropout),\n            nn.Linear(n_channels // 4, output_channels),\n        )\n        self.ensemble_all = nn.Linear((len(input_channels) + 2) * output_channels, output_channels)\n\n        n_channels = cnn1d_channels[-1] * self.n_imu_blocks * 2\n        self.mlp_imu = nn.Sequential(\n            nn.Linear(n_channels, n_channels // 2),\n            nn.ReLU(),\n            nn.Dropout(mlp_dropout),\n            nn.Linear(n_channels // 2, n_channels // 4),\n            nn.ReLU(),\n            nn.Dropout(mlp_dropout),\n            nn.Linear(n_channels // 4, output_channels),\n        )\n        self.ensemble_imu = nn.Linear(output_channels * (self.n_imu_blocks + 1), output_channels)\n\n    def forward(self, x, x_tof):\n        list_x = []\n        list_out = []\n\n        gesture_segment = F.avg_pool1d(x[:, :1], kernel_size=3, stride=3)\n        for i in range(len(self.block_idxes) - 1):\n            x_block = x[:, self.block_idxes[i] : self.block_idxes[i + 1]]\n            x_block, out = self.cnn_branches[i](x_block, gesture_segment)\n            list_x.append(x_block)\n            list_out.append(out)\n\n        list_x_tof = []\n        for i in range(5):\n            x_block = x_tof[:, :, i].reshape(-1, 1, 8, 8)\n            out = self.tof_blocks[i](x_block)\n            out = out.reshape(x.shape[0], -1, out.shape[1]).transpose(1, 2)\n            list_x_tof.append(out)\n        x_tof = torch.cat(list_x_tof, dim=1)\n        x_tof, out = self.cnn_branches[-1](x_tof, gesture_segment)\n        list_x.append(x_tof)\n        list_out.append(out)\n\n        x_all = torch.cat(list_x, dim=1)\n        out_all = self.mlp_all(x_all)\n        out_all = self.ensemble_all(torch.cat([out_all] + list_out, dim=1))\n\n        x_imu = torch.cat(list_x[: self.n_imu_blocks], dim=1)\n        out_imu = self.mlp_imu(x_imu)\n        out_imu = self.ensemble_imu(torch.cat([out_imu] + list_out[: self.n_imu_blocks], dim=1))\n\n        out = torch.stack([out_all, out_imu] + list_out, dim=1)\n        return out","metadata":{"papermill":{"duration":0.049854,"end_time":"2025-08-11T20:43:24.423254","exception":false,"start_time":"2025-08-11T20:43:24.3734","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:28.696092Z","iopub.execute_input":"2025-09-03T05:04:28.696345Z","iopub.status.idle":"2025-09-03T05:04:28.722232Z","shell.execute_reply.started":"2025-09-03T05:04:28.696320Z","shell.execute_reply":"2025-09-03T05:04:28.721455Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train(model, data_loader, optimizer, criterion, device):\n    model.train()\n    optimizer.train()\n\n    for batch in data_loader:\n        X = batch[0].to(device)\n        X_tof = batch[1].to(device)\n        Y = batch[2].to(device)\n\n        preds = model(X, X_tof)\n        loss_weights = [1 / 4, 1 / 4] + [1 / (preds.shape[1] - 2) / 2] * (preds.shape[1] - 2)\n\n        for i in range(preds.shape[1]):\n            if i == 0:\n                loss_main = criterion(preds[:, i, : len(all_gestures)], Y[:, 0]) * loss_weights[i]\n                loss_aux = criterion(preds[:, i, len(all_gestures) :], Y[:, 1]) * loss_weights[i]\n            else:\n                loss_main += criterion(preds[:, i, : len(all_gestures)], Y[:, 0]) * loss_weights[i]\n                loss_aux += criterion(preds[:, i, len(all_gestures) :], Y[:, 1]) * loss_weights[i]\n\n        loss = loss_main + loss_aux * config.aux_loss_weight\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n    model.eval()\n    optimizer.eval()\n\n\ndef predict(model, data_loader, device):\n    preds_all = []\n    for batch in data_loader:\n        X = batch[0].to(device)\n        X_tof = batch[1].to(device)\n\n        with torch.inference_mode():\n            preds = model(X, X_tof)[:, :, : len(all_gestures)]\n        preds = preds.cpu().numpy()\n        preds_all.append(preds)\n\n    return np.concatenate(preds_all, axis=0)","metadata":{"papermill":{"duration":0.04027,"end_time":"2025-08-11T20:43:24.493033","exception":false,"start_time":"2025-08-11T20:43:24.452763","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:28.723075Z","iopub.execute_input":"2025-09-03T05:04:28.723308Z","iopub.status.idle":"2025-09-03T05:04:28.742834Z","shell.execute_reply.started":"2025-09-03T05:04:28.723291Z","shell.execute_reply":"2025-09-03T05:04:28.742133Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def metric(y, preds):\n    f1_binary = f1_score(y < len(target_gestures), np.argmax(preds, axis=1) < len(target_gestures), average=\"binary\")\n    f1_macro = f1_score(\n        y.clip(max=len(target_gestures)), np.argmax(preds, axis=1).clip(max=len(target_gestures)), average=\"macro\"\n    )\n    return (f1_binary + f1_macro) / 2","metadata":{"papermill":{"duration":0.03814,"end_time":"2025-08-11T20:43:24.562463","exception":false,"start_time":"2025-08-11T20:43:24.524323","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:28.743597Z","iopub.execute_input":"2025-09-03T05:04:28.743816Z","iopub.status.idle":"2025-09-03T05:04:28.759492Z","shell.execute_reply.started":"2025-09-03T05:04:28.743800Z","shell.execute_reply":"2025-09-03T05:04:28.758618Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Train NN","metadata":{"papermill":{"duration":0.02836,"end_time":"2025-08-11T20:43:24.620686","exception":false,"start_time":"2025-08-11T20:43:24.592326","status":"completed"},"tags":[]}},{"cell_type":"code","source":"%%time\n\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nsgkf = StratifiedGroupKFold(n_splits=config.n_splits, shuffle=True, random_state=0)\npreds_oof = np.zeros((config.n_seeds, config.n_epochs, len(Y), len(feature_blocks_except_tof) + 3, len(all_gestures)))\nfor fold, (idx_train, idx_valid) in enumerate(sgkf.split(X, Y_main, groups)):\n    X_train = X[idx_train]\n    X_tof_train = X_tof[idx_train]\n    Y_train = Y[idx_train]\n    ds_train = MyDataset(X_train, X_tof_train, Y_train)\n    dl_train = DataLoader(\n        ds_train,\n        batch_size=config.batch_size,\n        shuffle=True,\n        num_workers=0,\n        pin_memory=True,\n        drop_last=True,\n    )\n\n    X_valid = X[idx_valid]\n    X_tof_valid = X_tof[idx_valid]\n    Y_valid = Y[idx_valid]\n    ds_valid = MyDataset(X_valid, X_tof_valid, Y_valid)\n    dl_valid = DataLoader(\n        ds_valid,\n        batch_size=config.batch_size,\n        shuffle=False,\n        num_workers=0,\n        pin_memory=True,\n        drop_last=False,\n    )\n\n    for seed in range(config.n_seeds):\n        seed_everything(seed)\n        model = MultiBranchClassifier(\n            n_imu_blocks=n_imu_blocks,\n            input_channels=[len(features) for features in feature_blocks_except_tof],\n            output_channels=len(all_gestures) + len(orientations),\n            initial_channels_per_feature=16,\n            cnn1d_channels=(256, 256),\n            cnn1d_kernel_size=3,\n            tof_out_channels=32,\n            tof_kernel_size=3,\n            mlp_dropout=0.3,\n        )\n        if fold == 0 and seed == 0:\n            print(\n                summary(\n                    model=model,\n                    input_size=(\n                        (config.batch_size,) + X.shape[1:],\n                        (config.batch_size,) + X_tof.shape[1:],\n                    ),\n                    col_names=[\"input_size\", \"output_size\", \"num_params\"],\n                    col_width=20,\n                )\n            )\n        model.to(device)\n        optimizer = RAdamScheduleFree(model.parameters(), lr=config.lr, betas=config.betas)\n        criterion = nn.CrossEntropyLoss(label_smoothing=config.label_smoothing)\n\n        print(f\"fold-seed: {fold}-{seed}\")\n        for epoch in range(config.n_epochs):\n            train(model, dl_train, optimizer, criterion, device)\n            preds_oof[seed, epoch, idx_valid] = predict(model, dl_valid, device)\n            if epoch >= config.n_epochs - 5:\n                torch.save(model.state_dict(), f\"{MODEL_DIR}/model_{fold}_{seed}_{epoch}.pth\")","metadata":{"papermill":{"duration":5923.985933,"end_time":"2025-08-11T22:22:08.63299","exception":false,"start_time":"2025-08-11T20:43:24.647057","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:46.495910Z","iopub.execute_input":"2025-09-03T05:04:46.496568Z","iopub.status.idle":"2025-09-03T05:04:53.954252Z","shell.execute_reply.started":"2025-09-03T05:04:46.496540Z","shell.execute_reply":"2025-09-03T05:04:53.953381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"np.save(f\"{OUTPUT_DIR}/preds_oof.npy\", preds_oof)","metadata":{"papermill":{"duration":0.453056,"end_time":"2025-08-11T22:22:09.112529","exception":false,"start_time":"2025-08-11T22:22:08.659473","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T04:59:36.319584Z","iopub.status.idle":"2025-09-03T04:59:36.319841Z","shell.execute_reply.started":"2025-09-03T04:59:36.319722Z","shell.execute_reply":"2025-09-03T04:59:36.319733Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Evaluation","metadata":{"papermill":{"duration":0.030722,"end_time":"2025-08-11T22:22:09.171946","exception":false,"start_time":"2025-08-11T22:22:09.141224","status":"completed"},"tags":[]}},{"cell_type":"code","source":"list_scores = [[] for _ in range(preds_oof.shape[3])]\nbest_epoch = 0\nbest_score = 0\nfor epoch in range(config.n_epochs):\n    print(f\"epoch: {str(epoch).zfill(2)}   score:\", end=\"\")\n    for i in range(config.n_seeds):\n        preds = preds_oof[: i + 1, epoch].mean(axis=0)\n        scores = [metric(Y_main, preds[:, i]) for i in range(preds.shape[1])]\n        print(f\"   {scores[0]:.4f} {scores[1]:.4f}\", end=\"\")\n    for i in range(preds.shape[1]):\n        list_scores[i].append(scores[i])\n    if (scores[0] + scores[1]) / 2 > best_score:\n        best_score = (scores[0] + scores[1]) / 2\n        best_epoch = epoch\n        best_preds = preds_oof[:, epoch].mean(axis=0)\n        print(\" best!\", end=\"\")\n    print()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:04:59.131038Z","iopub.execute_input":"2025-09-03T05:04:59.131514Z","iopub.status.idle":"2025-09-03T05:05:04.562654Z","shell.execute_reply.started":"2025-09-03T05:04:59.131490Z","shell.execute_reply":"2025-09-03T05:05:04.561847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(list_scores[0], marker=\".\", label=\"all\")\nplt.plot(list_scores[1], marker=\".\", label=\"imu-only\")\nplt.ylim(bottom=0.80)\nplt.grid()\nplt.legend()\nplt.show()","metadata":{"papermill":{"duration":0.167675,"end_time":"2025-08-11T22:22:10.962149","exception":false,"start_time":"2025-08-11T22:22:10.794474","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:05:07.451535Z","iopub.execute_input":"2025-09-03T05:05:07.452399Z","iopub.status.idle":"2025-09-03T05:05:07.619718Z","shell.execute_reply.started":"2025-09-03T05:05:07.452370Z","shell.execute_reply":"2025-09-03T05:05:07.618646Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds_names = [\"all\", \"imu\", \"acc\", \"rot\", \"derived_acc\", \"rotvec_diff\", \"thm\", \"tof\"]\nfor i, list_score in enumerate(list_scores[2:]):\n    plt.plot(list_score, marker=\".\", label=preds_names[2:][i])\nplt.grid()\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:05:11.174170Z","iopub.execute_input":"2025-09-03T05:05:11.174507Z","iopub.status.idle":"2025-09-03T05:05:11.383640Z","shell.execute_reply.started":"2025-09-03T05:05:11.174478Z","shell.execute_reply":"2025-09-03T05:05:11.382614Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds = best_preds\npreds = np.exp(preds) / np.exp(preds).sum(axis=-1, keepdims=True)","metadata":{"papermill":{"duration":0.040623,"end_time":"2025-08-11T22:22:11.031179","exception":false,"start_time":"2025-08-11T22:22:10.990556","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:05:23.165303Z","iopub.execute_input":"2025-09-03T05:05:23.166317Z","iopub.status.idle":"2025-09-03T05:05:23.181256Z","shell.execute_reply.started":"2025-09-03T05:05:23.166284Z","shell.execute_reply":"2025-09-03T05:05:23.180461Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_pred = (\n    df.select([\"subject\", \"handedness\", \"rotate\", \"sequence_id\"])\n    .unique(maintain_order=True)\n    .with_columns(\n        pl.lit(Y_main).alias(\"target\"),\n        *[pl.lit(np.argmax(preds[:, i], axis=-1)).alias(f\"pred_{preds_names[i]}\") for i in range(preds.shape[1])],\n    )\n)","metadata":{"papermill":{"duration":0.734125,"end_time":"2025-08-11T22:22:27.07748","exception":false,"start_time":"2025-08-11T22:22:26.343355","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:05:24.171324Z","iopub.execute_input":"2025-09-03T05:05:24.171690Z","iopub.status.idle":"2025-09-03T05:05:24.206737Z","shell.execute_reply.started":"2025-09-03T05:05:24.171664Z","shell.execute_reply":"2025-09-03T05:05:24.205632Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with pl.Config(tbl_rows=-1):\n    display(\n        df_pred.with_columns(\n            [\n                (pl.col(\"target\") == pl.col(f\"pred_{preds_names[i]}\")).alias(f\"acc_{preds_names[i]}\")\n                for i in range(preds.shape[1])\n            ],\n        )\n        .group_by([\"handedness\", \"rotate\"])\n        .agg(\n            pl.col(\"subject\").n_unique(),\n            pl.col(\"sequence_id\").n_unique(),\n            pl.col(\"^acc.*$\").mean().round(2),\n        )\n        .sort(\"sequence_id\", descending=True)\n    )","metadata":{"papermill":{"duration":0.055572,"end_time":"2025-08-11T22:22:27.243365","exception":false,"start_time":"2025-08-11T22:22:27.187793","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:05:41.947844Z","iopub.execute_input":"2025-09-03T05:05:41.948380Z","iopub.status.idle":"2025-09-03T05:05:41.958997Z","shell.execute_reply.started":"2025-09-03T05:05:41.948353Z","shell.execute_reply":"2025-09-03T05:05:41.958259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with pl.Config(tbl_rows=-1):\n    display(\n        df_pred.with_columns(\n            [\n                (pl.col(\"target\") == pl.col(f\"pred_{preds_names[i]}\")).alias(f\"acc_{preds_names[i]}\")\n                for i in range(preds.shape[1])\n            ],\n        )\n        .group_by([\"subject\", \"handedness\", \"rotate\"])\n        .agg(\n            pl.col(\"sequence_id\").n_unique(),\n            pl.col(\"^acc.*$\").mean().round(2),\n        )\n        .sort(\"subject\")\n    )\n","metadata":{"papermill":{"duration":0.047385,"end_time":"2025-08-11T22:22:27.319143","exception":false,"start_time":"2025-08-11T22:22:27.271758","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:05:58.352826Z","iopub.execute_input":"2025-09-03T05:05:58.353140Z","iopub.status.idle":"2025-09-03T05:05:58.375087Z","shell.execute_reply.started":"2025-09-03T05:05:58.353116Z","shell.execute_reply":"2025-09-03T05:05:58.374236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with pl.Config(tbl_rows=-1):\n    display(\n        df_pred.with_columns(\n            [\n                (pl.col(\"target\") == pl.col(f\"pred_{preds_names[i]}\")).alias(f\"acc_{preds_names[i]}\")\n                for i in range(preds.shape[1])\n            ],\n        )\n        .group_by(pl.all().exclude([\"sequence_id\", \"target\", \"^pred.*$\", \"^acc.*$\"]))\n        .agg(\n            pl.col(\"sequence_id\").n_unique(),\n            pl.col(\"^acc.*$\").mean().round(2),\n        )\n        .sort(\"acc_all\", descending=True)\n    )","metadata":{"papermill":{"duration":0.052544,"end_time":"2025-08-11T22:22:27.400989","exception":false,"start_time":"2025-08-11T22:22:27.348445","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:06:11.335580Z","iopub.execute_input":"2025-09-03T05:06:11.335897Z","iopub.status.idle":"2025-09-03T05:06:11.356595Z","shell.execute_reply.started":"2025-09-03T05:06:11.335873Z","shell.execute_reply":"2025-09-03T05:06:11.355774Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with pl.Config(tbl_rows=-1):\n    display(\n        df_pred.with_columns(\n            [\n                (pl.col(\"target\") == pl.col(f\"pred_{preds_names[i]}\")).alias(f\"acc_{preds_names[i]}\")\n                for i in range(preds.shape[1])\n            ],\n        )\n        .group_by(\"target\")\n        .agg(\n            pl.col(\"subject\").n_unique(),\n            pl.col(\"sequence_id\").n_unique(),\n            pl.col(\"^acc.*$\").mean().round(2),\n        )\n        .sort(\"target\")\n    )","metadata":{"papermill":{"duration":0.040372,"end_time":"2025-08-11T22:22:27.471915","exception":false,"start_time":"2025-08-11T22:22:27.431543","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:06:19.170278Z","iopub.execute_input":"2025-09-03T05:06:19.170951Z","iopub.status.idle":"2025-09-03T05:06:19.184862Z","shell.execute_reply.started":"2025-09-03T05:06:19.170923Z","shell.execute_reply":"2025-09-03T05:06:19.183987Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.crosstab(df_pred[\"pred_all\"].to_pandas(), df_pred[\"target\"].to_pandas())","metadata":{"papermill":{"duration":0.072127,"end_time":"2025-08-11T22:22:27.57255","exception":false,"start_time":"2025-08-11T22:22:27.500423","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:06:22.149312Z","iopub.execute_input":"2025-09-03T05:06:22.149641Z","iopub.status.idle":"2025-09-03T05:06:22.196641Z","shell.execute_reply.started":"2025-09-03T05:06:22.149615Z","shell.execute_reply":"2025-09-03T05:06:22.195809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.crosstab(df_pred[\"pred_imu\"].to_pandas(), df_pred[\"target\"].to_pandas())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-03T05:06:37.674712Z","iopub.execute_input":"2025-09-03T05:06:37.675510Z","iopub.status.idle":"2025-09-03T05:06:37.694249Z","shell.execute_reply.started":"2025-09-03T05:06:37.675472Z","shell.execute_reply":"2025-09-03T05:06:37.693508Z"}},"outputs":[],"execution_count":null}]}