{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":12499214,"sourceType":"datasetVersion","datasetId":7888453},{"sourceId":12509073,"sourceType":"datasetVersion","datasetId":7895396},{"sourceId":12509551,"sourceType":"datasetVersion","datasetId":7895720},{"sourceId":12517548,"sourceType":"datasetVersion","datasetId":7901294},{"sourceId":12560361,"sourceType":"datasetVersion","datasetId":7931179},{"sourceId":12560690,"sourceType":"datasetVersion","datasetId":7931427},{"sourceId":12565874,"sourceType":"datasetVersion","datasetId":7935271},{"sourceId":12567039,"sourceType":"datasetVersion","datasetId":7936102},{"sourceId":12576344,"sourceType":"datasetVersion","datasetId":7942573},{"sourceId":12579525,"sourceType":"datasetVersion","datasetId":7944568},{"sourceId":12583550,"sourceType":"datasetVersion","datasetId":7947456},{"sourceId":12583896,"sourceType":"datasetVersion","datasetId":7947689}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Import libraries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport torch\nfrom torch.utils.data import TensorDataset, DataLoader\nfrom torch import nn\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.model_selection import train_test_split, GroupKFold\nfrom sklearn.impute import KNNImputer\nfrom sklearn.metrics import f1_score\nimport random\nimport os\nimport polars as pl\nfrom collections import defaultdict\nfrom scipy.spatial.transform import Rotation as R\nimport kaggle_evaluation.cmi_inference_server","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:39.098188Z","iopub.execute_input":"2025-07-26T13:58:39.098520Z","iopub.status.idle":"2025-07-26T13:58:39.103658Z","shell.execute_reply.started":"2025-07-26T13:58:39.098499Z","shell.execute_reply":"2025-07-26T13:58:39.103061Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load dataset","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ntrain_dem = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\ntest_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\ntest_dem = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:39.104779Z","iopub.execute_input":"2025-07-26T13:58:39.105062Z","iopub.status.idle":"2025-07-26T13:58:54.710843Z","shell.execute_reply.started":"2025-07-26T13:58:39.105019Z","shell.execute_reply":"2025-07-26T13:58:54.710286Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"set(train_df.columns) - set(test_df.columns)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:54.712132Z","iopub.execute_input":"2025-07-26T13:58:54.712353Z","iopub.status.idle":"2025-07-26T13:58:54.717508Z","shell.execute_reply.started":"2025-07-26T13:58:54.712334Z","shell.execute_reply":"2025-07-26T13:58:54.716741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_only = train_df[list(set(train_df.columns) - set(test_df))]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:54.718180Z","iopub.execute_input":"2025-07-26T13:58:54.718388Z","iopub.status.idle":"2025-07-26T13:58:54.751516Z","shell.execute_reply.started":"2025-07-26T13:58:54.718370Z","shell.execute_reply":"2025-07-26T13:58:54.750773Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_only","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:54.753376Z","iopub.execute_input":"2025-07-26T13:58:54.753625Z","iopub.status.idle":"2025-07-26T13:58:54.763227Z","shell.execute_reply.started":"2025-07-26T13:58:54.753609Z","shell.execute_reply":"2025-07-26T13:58:54.762417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:54.763962Z","iopub.execute_input":"2025-07-26T13:58:54.764345Z","iopub.status.idle":"2025-07-26T13:58:54.790799Z","shell.execute_reply.started":"2025-07-26T13:58:54.764312Z","shell.execute_reply":"2025-07-26T13:58:54.790194Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# EDA","metadata":{}},{"cell_type":"code","source":"missing_ratio = train_df.isnull().mean()\n\nmissing_ratio.sort_values(ascending=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:54.791454Z","iopub.execute_input":"2025-07-26T13:58:54.791695Z","iopub.status.idle":"2025-07-26T13:58:55.560146Z","shell.execute_reply.started":"2025-07-26T13:58:54.791677Z","shell.execute_reply":"2025-07-26T13:58:55.559306Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"missing_ratio = test_df.isnull().mean()\n\nmissing_ratio.sort_values(ascending=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:55.561326Z","iopub.execute_input":"2025-07-26T13:58:55.561596Z","iopub.status.idle":"2025-07-26T13:58:55.570725Z","shell.execute_reply.started":"2025-07-26T13:58:55.561565Z","shell.execute_reply":"2025-07-26T13:58:55.569860Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.groupby('sequence_id')['gesture'].unique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:55.571631Z","iopub.execute_input":"2025-07-26T13:58:55.571831Z","iopub.status.idle":"2025-07-26T13:58:55.904701Z","shell.execute_reply.started":"2025-07-26T13:58:55.571815Z","shell.execute_reply":"2025-07-26T13:58:55.903948Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lens = train_df.groupby('sequence_id')['sequence_counter'].count()\npad_ratio = 1 - (lens.sum() / (len(lens)*max(lens)))\nprint(f\"Pad waste: {pad_ratio:.1%}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:55.905478Z","iopub.execute_input":"2025-07-26T13:58:55.905798Z","iopub.status.idle":"2025-07-26T13:58:55.940811Z","shell.execute_reply.started":"2025-07-26T13:58:55.905766Z","shell.execute_reply":"2025-07-26T13:58:55.940284Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:55.943180Z","iopub.execute_input":"2025-07-26T13:58:55.943414Z","iopub.status.idle":"2025-07-26T13:58:55.956629Z","shell.execute_reply.started":"2025-07-26T13:58:55.943397Z","shell.execute_reply":"2025-07-26T13:58:55.955935Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:55.957287Z","iopub.execute_input":"2025-07-26T13:58:55.957469Z","iopub.status.idle":"2025-07-26T13:58:55.981119Z","shell.execute_reply.started":"2025-07-26T13:58:55.957454Z","shell.execute_reply":"2025-07-26T13:58:55.980475Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Config**","metadata":{}},{"cell_type":"code","source":"train_df['gesture'].unique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:55.981867Z","iopub.execute_input":"2025-07-26T13:58:55.982087Z","iopub.status.idle":"2025-07-26T13:58:56.024113Z","shell.execute_reply.started":"2025-07-26T13:58:55.982070Z","shell.execute_reply":"2025-07-26T13:58:56.023551Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    SEED:       int   = 19\n    WIN_LEN:    int   = 100\n    STRIDE:     int   = 30\n    BATCH:      int   = 64\n    EPOCHS:     int   = 30\n    LR:         float = 5e-4\n    WD:         float = 1e-4\n    D_MODEL:    int   = 128\n    N_HEAD:     int   = 8\n    N_LAYER:    int   = 3\n    N_SPLIT:    int   = 5\n    TARGET_IDS: set   = set(range(len(train_df['gesture'].unique())))\n\ndef set_seed(seed=CFG.SEED):\n    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed); torch.backends.cudnn.deterministic = True\nset_seed()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:56.024705Z","iopub.execute_input":"2025-07-26T13:58:56.024939Z","iopub.status.idle":"2025-07-26T13:58:56.058094Z","shell.execute_reply.started":"2025-07-26T13:58:56.024922Z","shell.execute_reply":"2025-07-26T13:58:56.057461Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Preprocessing","metadata":{}},{"cell_type":"code","source":"train_df = train_df.dropna(subset = ['acc_x', 'acc_y', 'acc_z', 'rot_x','rot_y','rot_z','rot_w'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:56.058744Z","iopub.execute_input":"2025-07-26T13:58:56.059000Z","iopub.status.idle":"2025-07-26T13:58:56.630000Z","shell.execute_reply.started":"2025-07-26T13:58:56.058977Z","shell.execute_reply":"2025-07-26T13:58:56.629432Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ec = LabelEncoder()\ntrain_df.loc[:, 'sequence_type_ec'] = ec.fit_transform(train_df['sequence_type'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:56.630729Z","iopub.execute_input":"2025-07-26T13:58:56.630989Z","iopub.status.idle":"2025-07-26T13:58:56.723712Z","shell.execute_reply.started":"2025-07-26T13:58:56.630966Z","shell.execute_reply":"2025-07-26T13:58:56.723186Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def remove_gravity_from_acc(df):\n    acc_vals = df[['acc_x','acc_y','acc_z']].values  \n    quat_vals = df[['rot_x','rot_y','rot_z','rot_w']].values \n\n    N = len(df)\n    linear_acc = np.zeros((N,3), dtype=float)\n    gravity_world = np.array([0, 0, 9.81])\n\n    for i in range(N):\n        q = quat_vals[i]\n        rot = R.from_quat(q)\n        g_sensor = rot.apply(gravity_world, inverse=True)\n        linear_acc[i] = acc_vals[i] - g_sensor\n\n    return linear_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:56.724420Z","iopub.execute_input":"2025-07-26T13:58:56.724604Z","iopub.status.idle":"2025-07-26T13:58:56.729408Z","shell.execute_reply.started":"2025-07-26T13:58:56.724589Z","shell.execute_reply":"2025-07-26T13:58:56.728801Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calc_angular_velocity(df, delta_t = 1/200):\n    quat_vals = df[['rot_x','rot_y','rot_z','rot_w']].values \n    angular_velocity = np.zeros((len(quat_vals), 3))\n\n    for i in range(0, len(quat_vals) - 1):\n        quat_t = R.from_quat(quat_vals[i])\n        quat_t_plus = R.from_quat(quat_vals[i + 1])\n        delta_rot = quat_t.inv() * quat_t_plus\n        delta_rot = delta_rot.as_rotvec()\n        angular_velocity[i] = delta_rot / delta_t\n    angular_velocity[-1] = angular_velocity[-2]\n    \n    return angular_velocity","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:56.730126Z","iopub.execute_input":"2025-07-26T13:58:56.730325Z","iopub.status.idle":"2025-07-26T13:58:56.746525Z","shell.execute_reply.started":"2025-07-26T13:58:56.730310Z","shell.execute_reply":"2025-07-26T13:58:56.746000Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DataPreprocessing:\n    def __init__(self, df):\n        self.df = df.copy()\n        pref = ('acc_', 'rot_')\n        self.imu_cols = [c for c in df.columns if c.startswith(pref)]\n        self.label_encoder = LabelEncoder()\n        self.num_cols = self.df.select_dtypes(include=[np.number]).columns\n        self.knn = KNNImputer(n_neighbors=5)\n        self.scale = StandardScaler()\n        self.classes_ = None\n\n\n    def fit(self):\n        self.df['gesture_lbl'] = self.label_encoder.fit(self.df['gesture'])\n        self.classes_ = self.label_encoder.classes_\n        self.df[self.imu_cols] = self.knn.fit(self.df[self.imu_cols])\n        return self\n\n    def transform(self, df, is_train = 0):\n        df = df.copy()\n        if self.classes_ is None:\n            raise RuntimeError('Call .fit() before transform().')\n\n        df[self.imu_cols] = self.knn.transform(df[self.imu_cols])\n\n        df[['angular_velocity_x', 'angular_velocity_y', 'angular_velocity_z']] = calc_angular_velocity(df)\n        df['acc_mag'] = np.sqrt(df['acc_x'] ** 2 + df['acc_y'] ** 2 + df['acc_z'] ** 2)\n        df['rot_ang'] = 2 * np.arccos(df['rot_w'].clip(-1, 1))\n        df[['acc_x','acc_y','acc_z']] = remove_gravity_from_acc(df)\n\n        has_label = 'gesture' in df.columns\n        if has_label and 'gesture_lbl' not in df.columns:\n            df = df.copy()\n            df['gesture_lbl'] = self.label_encoder.transform(df['gesture'])\n\n        arrays, labels, subjects, seq_ids = [], [], [], []\n        for sid, sub_df in df.groupby('sequence_id'):\n            sub_df = sub_df.sort_values('sequence_counter')\n            cols = self.imu_cols  + ['acc_mag', 'rot_ang', 'angular_velocity_x'\n                                     , 'angular_velocity_y', 'angular_velocity_z']\n            arr = sub_df[cols].to_numpy(np.float32)  \n            label   = sub_df['gesture_lbl'].iloc[0] if has_label else None\n            subject = sub_df['subject'].iloc[0]\n\n            T = len(arr)\n            for i in range(0, T, CFG.STRIDE):\n                seg = arr[i:min(T, i+CFG.WIN_LEN)]\n                if seg.shape[0] < CFG.WIN_LEN:        \n                    pad = np.zeros((CFG.WIN_LEN-seg.shape[0], seg.shape[1]), np.float32)\n                    seg = np.vstack([seg, pad])\n                    arrays.append(seg)\n                    seq_ids.append(sid)\n                    subjects.append(subject)\n                    if has_label:\n                        labels.append(label)\n                    break\n                arrays.append(seg)\n                seq_ids.append(sid)\n                subjects.append(subject)\n                if has_label:\n                    labels.append(label)\n\n        X = np.stack(arrays).astype(np.float32)\n        S, T, F = X.shape\n        X2d = X.reshape(-1, F) \n        if is_train == 1:\n            self.scale.fit(X2d)\n        X2d_scaled = self.scale.transform(X2d)\n        X = X2d_scaled.reshape(S, T, F)\n        y = np.array(labels, dtype=np.int64) if has_label else None\n        subjects = np.array(subjects)\n        seq_ids  = np.array(seq_ids)\n        return X, y, subjects, seq_ids","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:56.747095Z","iopub.execute_input":"2025-07-26T13:58:56.747304Z","iopub.status.idle":"2025-07-26T13:58:56.768616Z","shell.execute_reply.started":"2025-07-26T13:58:56.747289Z","shell.execute_reply":"2025-07-26T13:58:56.768135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dp = DataPreprocessing(train_df)\ndp.fit()\ndp.transform(train_df, is_train = 1)\nclasses = dp.classes_\nenc = dp.label_encoder","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:58:56.769296Z","iopub.execute_input":"2025-07-26T13:58:56.769493Z","iopub.status.idle":"2025-07-26T13:59:29.405654Z","shell.execute_reply.started":"2025-07-26T13:58:56.769472Z","shell.execute_reply":"2025-07-26T13:59:29.404833Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Modeling","metadata":{}},{"cell_type":"code","source":"TARGETS = [\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_TARGETS = [\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_CLASSES = set(TARGETS) | set(NON_TARGETS)\n\ndef score(\n    sol: pd.DataFrame,\n    sub: pd.DataFrame,\n) -> tuple[float, float, float]:\n    bad = set(sub['gesture']) - ALL_CLASSES\n\n    y_true = sol['gesture'].values\n    y_pred = sub['gesture'].values\n\n    is_t_true = np.isin(y_true, TARGETS)\n    is_t_pred = np.isin(y_pred, TARGETS)\n    f1_bin = f1_score(is_t_true, is_t_pred, zero_division=0)\n\n    def multiclass(a):\n        return np.where(np.isin(a, TARGETS), a, 'non_target')\n\n    f1_mac = f1_score(\n        multiclass(y_true),\n        multiclass(y_pred),\n        average='macro',\n        zero_division=0\n    )\n\n    hier = 0.5 * f1_bin + 0.5 * f1_mac\n    return hier, f1_bin, f1_mac","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:59:29.406491Z","iopub.execute_input":"2025-07-26T13:59:29.406724Z","iopub.status.idle":"2025-07-26T13:59:29.413161Z","shell.execute_reply.started":"2025-07-26T13:59:29.406705Z","shell.execute_reply":"2025-07-26T13:59:29.412325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AutoCorrelation(nn.Module):\n    def __init__(self, d_model: int, n_heads: int):\n        super().__init__()\n        self.mha = nn.MultiheadAttention(d_model, n_heads, batch_first=True)\n\n    def forward(self, x_q, x_kv=None):\n        if x_kv is None:\n            x_kv = x_q\n        out, _ = self.mha(x_q, x_kv, x_kv, need_weights=False)\n        return out\n\nclass EncoderBlock(nn.Module):\n    def __init__(self, d, h, p=0.1):\n        super().__init__()\n        self.ac = AutoCorrelation(d,h)\n        self.ff = nn.Sequential(nn.Linear(d,4*d), nn.GELU(), nn.Dropout(p), nn.Linear(4*d,d))\n        self.n1, self.n2 = nn.LayerNorm(d), nn.LayerNorm(d)\n        self.drop = nn.Dropout(p)\n    def forward(self,x):\n        x = x + self.drop(self.ac(self.n1(x)))\n        x = x + self.drop(self.ff(self.n2(x)))\n        return x\n\nclass AutoformerEncoderCls(nn.Module):\n    def __init__(self, c_in, seq_len, n_cls):\n        super().__init__()\n        self.proj = nn.Linear(c_in, CFG.D_MODEL)\n        self.pos  = nn.Parameter(torch.randn(1, seq_len, CFG.D_MODEL))\n        self.enc  = nn.ModuleList([EncoderBlock(CFG.D_MODEL, CFG.N_HEAD) for _ in range(CFG.N_LAYER)])\n        self.head = nn.Sequential(nn.LayerNorm(CFG.D_MODEL)\n                                , nn.Linear(CFG.D_MODEL, 512)\n                                , nn.LayerNorm(512)\n                                , nn.Linear(512, 256)\n                                , nn.LayerNorm(256)\n                                , nn.Dropout(0.2)\n                                , nn.Linear(256, n_cls))\n    def forward(self, x):       \n        z = self.proj(x) + self.pos[:, :x.size(1)]\n        for blk in self.enc: z = blk(z)\n        return self.head(z.mean(1))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:59:29.413902Z","iopub.execute_input":"2025-07-26T13:59:29.414198Z","iopub.status.idle":"2025-07-26T13:59:29.435351Z","shell.execute_reply.started":"2025-07-26T13:59:29.414169Z","shell.execute_reply":"2025-07-26T13:59:29.434737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def Train(X_train, y_train, X_val, y_val, seq_ids_val, cfg, ckpt_dir, save_name):\n    os.makedirs(ckpt_dir, exist_ok=True)\n    device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    \n    tr_loader = DataLoader(\n        TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train)),\n        batch_size=cfg.BATCH, shuffle=True\n    )\n    if y_val is not None:\n        va_loader = DataLoader(\n            TensorDataset(torch.from_numpy(X_val), torch.from_numpy(y_val)),\n            batch_size=cfg.BATCH, shuffle=False\n        )\n\n    model = AutoformerEncoderCls(X_train.shape[-1], cfg.WIN_LEN, len(dp.classes_)).to(device)\n    opt = torch.optim.AdamW(model.parameters(), lr=cfg.LR, weight_decay=cfg.WD)\n    memo = []\n    best_hf1 = 0\n    best_model = model\n    \n    for epoch in range(cfg.EPOCHS):\n        model.train()\n        for xb, yb in tr_loader:\n            xb, yb = xb.to(device), yb.to(device)\n            logits = model(xb)\n            loss = torch.nn.functional.cross_entropy(logits, yb)\n            opt.zero_grad()\n            loss.backward()\n            opt.step()\n\n        if y_val is not None:\n            all_preds = []\n            all_true  = []\n            model.eval()\n            with torch.no_grad():\n                for xb, yb in va_loader:\n                    xb = xb.to(device)\n                    preds = model(xb).argmax(dim=1).cpu().numpy()\n                    all_preds.extend(preds)\n                    all_true.extend(yb.numpy())\n            df_sol = pd.DataFrame({\n                \"sequence_id\": seq_ids_val,\n                \"gesture\": dp.label_encoder.inverse_transform(all_true)\n            })\n            df_sub = pd.DataFrame({\n                \"sequence_id\": seq_ids_val,\n                \"gesture\": dp.label_encoder.inverse_transform(all_preds)\n            })\n            hf1, _, _ = score(df_sol, df_sub)\n            if hf1 > best_hf1:\n                best_hf1 = hf1\n                best_model = model\n            memo.append(score(df_sol, df_sub))\n            \n    torch.save(best_model, os.path.join(ckpt_dir, save_name))\n    return best_hf1, memo","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:59:29.436134Z","iopub.execute_input":"2025-07-26T13:59:29.436389Z","iopub.status.idle":"2025-07-26T13:59:29.457923Z","shell.execute_reply.started":"2025-07-26T13:59:29.436365Z","shell.execute_reply":"2025-07-26T13:59:29.457413Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def CrossKFolds(train, dp, cfg, ckpt_dir=\"chkpts\"):\n    X, y, subj, seq_ids = dp.transform(train)\n\n    gkf = GroupKFold(cfg.N_SPLIT)\n    scores = []\n\n    for fold, (tr_idx, va_idx) in enumerate(gkf.split(X, y, groups=subj), start=1):\n        best_hf1, memo = Train(X[tr_idx], y[tr_idx], X[va_idx], y[va_idx], seq_ids[va_idx], cfg, ckpt_dir, f'fold{fold}.pt')\n\n        hier_f1, bin_f1, macro_f1 = [], [], []\n        for h, b, m in memo:\n            hier_f1.append(h)\n            bin_f1.append(b)\n            macro_f1.append(m)\n\n        plt.figure(figsize=(10, 5))\n        \n        plt.plot(hier_f1, label='Hierarchical F1')\n        plt.plot(bin_f1, label='Binary F1')\n        plt.plot(macro_f1, label='Macro F1')\n        \n        plt.xlabel('Epoch')\n        plt.ylabel('F1 Score')\n        plt.title(f'F1 Scores over Time for fold {fold}')\n        plt.legend()\n        plt.grid(True)\n        plt.tight_layout()\n        plt.show()\n\n        print('hier_f1: ', best_hf1)\n\n        scores.append(best_hf1)\n\n    print(\"Mean Hierarchical F1 =\", np.mean(scores))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:59:29.458879Z","iopub.execute_input":"2025-07-26T13:59:29.459148Z","iopub.status.idle":"2025-07-26T13:59:29.476716Z","shell.execute_reply.started":"2025-07-26T13:59:29.459126Z","shell.execute_reply":"2025-07-26T13:59:29.476196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CrossKFolds(train_df, dp, CFG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:59:43.273557Z","iopub.execute_input":"2025-07-26T13:59:43.274153Z","iopub.status.idle":"2025-07-26T14:07:29.690386Z","shell.execute_reply.started":"2025-07-26T13:59:43.274126Z","shell.execute_reply":"2025-07-26T14:07:29.689588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !zip -r chkpts.zip /kaggle/working/chkpts","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T14:07:36.858572Z","iopub.execute_input":"2025-07-26T14:07:36.859299Z","iopub.status.idle":"2025-07-26T14:07:37.903392Z","shell.execute_reply.started":"2025-07-26T14:07:36.859271Z","shell.execute_reply":"2025-07-26T14:07:37.901898Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Build submission","metadata":{}},{"cell_type":"code","source":"DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodels = []\nfor fold in range(1, 6):\n    m = torch.load(\n        f\"/kaggle/input/checkpoint-13/fold{fold}.pt\",\n        map_location=DEVICE,\n        weights_only=False\n    )\n    m.eval()\n    models.append(m)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:59:29.519283Z","iopub.execute_input":"2025-07-26T13:59:29.519531Z","iopub.status.idle":"2025-07-26T13:59:29.600859Z","shell.execute_reply.started":"2025-07-26T13:59:29.519504Z","shell.execute_reply":"2025-07-26T13:59:29.600380Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence_df: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    seq_pd = sequence_df.to_pandas()\n\n    X, _, _, _ = dp.transform(seq_pd) \n    X = torch.from_numpy(X).to(DEVICE)\n\n    with torch.no_grad():\n        logits_sum = torch.zeros(X.size(0), len(enc.classes_), device=DEVICE)\n        for m in models:    \n            logits_sum += m(X)\n        mean_log = logits_sum.mean(0)  \n\n    pred_id = int(mean_log.argmax().item())\n    return enc.inverse_transform([pred_id])[0] ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:59:29.601500Z","iopub.execute_input":"2025-07-26T13:59:29.601737Z","iopub.status.idle":"2025-07-26T13:59:29.606392Z","shell.execute_reply.started":"2025-07-26T13:59:29.601717Z","shell.execute_reply":"2025-07-26T13:59:29.605866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-26T13:59:29.609354Z","iopub.execute_input":"2025-07-26T13:59:29.609550Z","iopub.status.idle":"2025-07-26T13:59:30.576580Z","shell.execute_reply.started":"2025-07-26T13:59:29.609534Z","shell.execute_reply":"2025-07-26T13:59:30.575803Z"}},"outputs":[],"execution_count":null}]}