{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# %%capture\n# !pip install -U scikit-learn imbalanced-learn","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:05:32.338505Z","iopub.execute_input":"2025-08-27T13:05:32.339631Z","iopub.status.idle":"2025-08-27T13:05:32.344134Z","shell.execute_reply.started":"2025-08-27T13:05:32.339606Z","shell.execute_reply":"2025-08-27T13:05:32.343197Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import polars as pl\nimport lightgbm as lgbm\nimport numpy as np\nfrom scipy.spatial.transform import Rotation as R\nimport pandas as pd","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:05:32.345966Z","iopub.execute_input":"2025-08-27T13:05:32.346208Z","iopub.status.idle":"2025-08-27T13:05:40.986785Z","shell.execute_reply.started":"2025-08-27T13:05:32.346190Z","shell.execute_reply":"2025-08-27T13:05:40.985876Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# source: https://www.kaggle.com/code/nksusth/lb-0-78-quaternions-tf-bilstm-gru-attention\ndef remove_gravity_from_acc(acc_data, rot_data):\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    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]\n    linear_accel = np.zeros_like(acc_values)\n    \n    gravity_world = np.array([0, 0, 9.81])\n\n    for i in range(num_samples):\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        try:\n            rotation = R.from_quat(quat_values[i])\n            gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)\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\n\ndef calculate_angular_velocity_from_quat(rot_data, time_delta=1/200):\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 = quat_values.shape[0]\n    angular_vel = np.zeros((num_samples, 3))\n\n    for i in range(num_samples - 1):\n        q_t = quat_values[i]\n        q_t_plus_dt = quat_values[i+1]\n\n        if np.all(np.isnan(q_t)) or np.all(np.isclose(q_t, 0)) or \\\n           np.all(np.isnan(q_t_plus_dt)) or np.all(np.isclose(q_t_plus_dt, 0)):\n            continue\n\n        try:\n            rot_t = R.from_quat(q_t)\n            rot_t_plus_dt = R.from_quat(q_t_plus_dt)\n\n            delta_rot = rot_t.inv() * rot_t_plus_dt\n            \n            angular_vel[i, :] = delta_rot.as_rotvec() / time_delta\n        except ValueError:\n            pass\n            \n    return angular_vel\n\ndef calculate_angular_distance(rot_data):\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 = quat_values.shape[0]\n    angular_dist = np.zeros(num_samples)\n\n    for i in range(num_samples - 1):\n        q1 = quat_values[i]\n        q2 = quat_values[i+1]\n\n        if np.all(np.isnan(q1)) or np.all(np.isclose(q1, 0)) or \\\n           np.all(np.isnan(q2)) or np.all(np.isclose(q2, 0)):\n            angular_dist[i] = 0\n            continue\n        try:\n            r1 = R.from_quat(q1)\n            r2 = R.from_quat(q2)\n\n            relative_rotation = r1.inv() * r2\n            \n            angle = np.linalg.norm(relative_rotation.as_rotvec())\n            angular_dist[i] = angle\n        except ValueError:\n            angular_dist[i] = 0\n            pass\n            \n    return angular_dist","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:05:40.987552Z","iopub.execute_input":"2025-08-27T13:05:40.988110Z","iopub.status.idle":"2025-08-27T13:05:41.002497Z","shell.execute_reply.started":"2025-08-27T13:05:40.988087Z","shell.execute_reply":"2025-08-27T13:05:41.001526Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = pl.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\")\ntrain_demo = pl.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:05:58.413556Z","iopub.execute_input":"2025-08-27T13:05:58.414827Z","iopub.status.idle":"2025-08-27T13:06:04.664335Z","shell.execute_reply.started":"2025-08-27T13:05:58.414784Z","shell.execute_reply":"2025-08-27T13:06:04.663431Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = train.drop([\"sequence_type\",\"orientation\",\"behavior\",\"phase\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:04.665515Z","iopub.execute_input":"2025-08-27T13:06:04.665795Z","iopub.status.idle":"2025-08-27T13:06:04.705714Z","shell.execute_reply.started":"2025-08-27T13:06:04.665771Z","shell.execute_reply":"2025-08-27T13:06:04.704740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head(2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:04.706667Z","iopub.execute_input":"2025-08-27T13:06:04.707005Z","iopub.status.idle":"2025-08-27T13:06:04.742921Z","shell.execute_reply.started":"2025-08-27T13:06:04.706983Z","shell.execute_reply":"2025-08-27T13:06:04.741977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data = train.join(train_demo,on=\"subject\",how=\"left\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:04.744407Z","iopub.execute_input":"2025-08-27T13:06:04.744677Z","iopub.status.idle":"2025-08-27T13:06:04.831787Z","shell.execute_reply.started":"2025-08-27T13:06:04.744656Z","shell.execute_reply":"2025-08-27T13:06:04.830845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data.head(2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:07.419554Z","iopub.execute_input":"2025-08-27T13:06:07.420473Z","iopub.status.idle":"2025-08-27T13:06:07.429257Z","shell.execute_reply.started":"2025-08-27T13:06:07.420442Z","shell.execute_reply":"2025-08-27T13:06:07.428130Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data = data.to_pandas()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:09.621560Z","iopub.execute_input":"2025-08-27T13:06:09.622205Z","iopub.status.idle":"2025-08-27T13:06:11.632831Z","shell.execute_reply.started":"2025-08-27T13:06:09.622177Z","shell.execute_reply":"2025-08-27T13:06:11.631985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"flags = []\nfor _,group in data.groupby('sequence_id'):\n    max_count = group[\"sequence_counter\"].max()\n    group[\"flag\"] = (group[\"sequence_counter\"] > (max_count) * 0.1) | (group[\"sequence_counter\"] < (max_count) * 0.9)\n    flags.append(group)\ndata = pd.concat(flags)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:11.635984Z","iopub.execute_input":"2025-08-27T13:06:11.636222Z","iopub.status.idle":"2025-08-27T13:06:20.710474Z","shell.execute_reply.started":"2025-08-27T13:06:11.636205Z","shell.execute_reply":"2025-08-27T13:06:20.709521Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data = data[data[\"flag\"] == True]\ndata = data.drop(columns=\"flag\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:20.711960Z","iopub.execute_input":"2025-08-27T13:06:20.712656Z","iopub.status.idle":"2025-08-27T13:06:23.323202Z","shell.execute_reply.started":"2025-08-27T13:06:20.712624Z","shell.execute_reply":"2025-08-27T13:06:23.322425Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# source: https://www.kaggle.com/code/nksusth/lb-0-78-quaternions-tf-bilstm-gru-attention\ndata['acc_mag'] = np.sqrt(data['acc_x']**2 + data['acc_y']**2 + data['acc_z']**2)\ndata['rot_angle'] = 2 * np.arccos(data['rot_w'].clip(-1, 1))\n\ndata['acc_mag_jerk'] = data.groupby('sequence_id')['acc_mag'].diff().fillna(0)\ndata['rot_angle_vel'] = data.groupby('sequence_id')['rot_angle'].diff().fillna(0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:23.324244Z","iopub.execute_input":"2025-08-27T13:06:23.324610Z","iopub.status.idle":"2025-08-27T13:06:23.517430Z","shell.execute_reply.started":"2025-08-27T13:06:23.324582Z","shell.execute_reply":"2025-08-27T13:06:23.516573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# source: https://www.kaggle.com/code/nksusth/lb-0-78-quaternions-tf-bilstm-gru-attention\nlinear_accel_list = []\nfor _, group in data.groupby('sequence_id'):\n    acc_data_group = group[['acc_x', 'acc_y', 'acc_z']]\n    rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n    linear_accel_group = remove_gravity_from_acc(acc_data_group, rot_data_group)\n    linear_accel_list.append(pd.DataFrame(linear_accel_group, columns=['linear_acc_x', 'linear_acc_y', 'linear_acc_z'], index=group.index))\n\ndf_linear_accel = pd.concat(linear_accel_list)\ndata = pd.concat([data, df_linear_accel], axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:06:23.519573Z","iopub.execute_input":"2025-08-27T13:06:23.520181Z","iopub.status.idle":"2025-08-27T13:07:18.194898Z","shell.execute_reply.started":"2025-08-27T13:06:23.520160Z","shell.execute_reply":"2025-08-27T13:07:18.193920Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data['linear_acc_mag'] = np.sqrt(data['linear_acc_x']**2 + data['linear_acc_y']**2 + data['linear_acc_z']**2)\ndata['linear_acc_mag_jerk'] = data.groupby('sequence_id')['linear_acc_mag'].diff().fillna(0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:07:18.195863Z","iopub.execute_input":"2025-08-27T13:07:18.196111Z","iopub.status.idle":"2025-08-27T13:07:18.258072Z","shell.execute_reply.started":"2025-08-27T13:07:18.196093Z","shell.execute_reply":"2025-08-27T13:07:18.257039Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# source: https://www.kaggle.com/code/nksusth/lb-0-78-quaternions-tf-bilstm-gru-attention\nangular_vel_list = []\nfor _, group in data.groupby('sequence_id'):\n    rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n    angular_vel_group = calculate_angular_velocity_from_quat(rot_data_group)\n    angular_vel_list.append(pd.DataFrame(angular_vel_group, columns=['angular_vel_x', 'angular_vel_y', 'angular_vel_z'], index=group.index))\n\ndf_angular_vel = pd.concat(angular_vel_list)\ndata = pd.concat([data, df_angular_vel], axis=1)\n\nangular_distance_list = []\nfor _, group in data.groupby('sequence_id'):\n    rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n    angular_dist_group = calculate_angular_distance(rot_data_group)\n    angular_distance_list.append(pd.DataFrame(angular_dist_group, columns=['angular_distance'], index=group.index))\n\ndf_angular_distance = pd.concat(angular_distance_list)\ndata = pd.concat([data, df_angular_distance], axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:07:18.259081Z","iopub.execute_input":"2025-08-27T13:07:18.259397Z","iopub.status.idle":"2025-08-27T13:10:08.863038Z","shell.execute_reply.started":"2025-08-27T13:07:18.259372Z","shell.execute_reply":"2025-08-27T13:10:08.862147Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def feature_engineering(data:pl.DataFrame):\n    demographic_cols = [\n    \"adult_child\", \"age\", \"sex\", \"handedness\",\n    \"height_cm\", \"shoulder_to_wrist_cm\", \"elbow_to_wrist_cm\"\n    ]\n    target_col = \"gesture\"\n    \n    # All numeric sensor columns (everything except id, demo, target)\n    stat_cols = [\n        c for c in data.columns\n        if c not in demographic_cols + [target_col, \"sequence_id\", \"row_id\",\"subject\"]\n    ]\n    \n    \n    # Build aggregation expressions\n    agg_exprs = []\n    \n    # full-stats bundle for sensor columns\n    \n    for c in stat_cols:\n        agg_exprs += [\n            pl.col(c).mean().alias(f\"{c}_mean\"),\n            pl.col(c).std().alias(f\"{c}_std\"),\n            pl.col(c).var().alias(f\"{c}_var\"),\n            # pl.col(c).mode().list.first().alias(f\"{c}_mode\"),\n            pl.col(c).quantile(0.25).alias(f\"{c}_q25\"),\n            pl.col(c).median().alias(f\"{c}_q50\"),\n            pl.col(c).quantile(0.75).alias(f\"{c}_q75\"),\n            pl.col(c).max().alias(f\"{c}_max\"),\n            pl.col(c).min().alias(f\"{c}_min\"),\n            # pl.col(c).first().alias(f\"{c}_first\"),\n            # pl.col(c).last().alias(f\"{c}_last\"),\n            # pl.col(c).quantile(0.25, \"nearest\").alias(f\"{c}_t25\"),\n            # pl.col(c).quantile(0.75, \"nearest\").alias(f\"{c}_t75\"),\n            # (pl.col(c).last() - pl.col(c).first()).alias(f\"{c}_delta\"),\n            # pl.corr(\"sequence_counter\", c).alias(f\"{c}_corr_time\"),\n            # pl.col(c).diff().mean().alias(f\"{c}_diff_mean\"),\n            # pl.col(c).diff().std().alias(f\"{c}_diff_std\"),\n            # pl.col(c).skew().alias(f\"{c}_skew\"),\n            # pl.col(c).kurtosis().alias(f\"{c}_kurt\"),\n            # pl.col(c).diff().abs().gt(0).sum().alias(f\"{c}_n_changes\"),\n            # # (pl.arg_max(c).cast(pl.Int64) / pl.count()).alias(f\"{c}_t2peak\"),\n            # (pl.col(c) - pl.col(c).median()).abs().median().alias(f\"{c}_mad\"),\n            # pl.col(c).pow(2).mean().sqrt().alias(f\"{c}_rms\"),\n            # (pl.col(c).max() - pl.col(c).min()).alias(f\"{c}_ptp\"),\n        ]\n        # agg_exprs += [\n        #     pl.when(pl.col(\"sequence_counter\") < 0.25 * pl.max(\"sequence_counter\"))\n        #       .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg1_mean\"),\n        #     pl.when((pl.col(\"sequence_counter\") > 0.25 * pl.max(\"sequence_counter\")) & (pl.col(\"sequence_counter\") < 0.5 * pl.max(\"sequence_counter\")))\n        #       .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg2_mean\"),\n        # pl.when((pl.col(\"sequence_counter\") > 0.5 * pl.max(\"sequence_counter\")) & (pl.col(\"sequence_counter\") < 0.75 * pl.max(\"sequence_counter\")))\n        #       .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg3_mean\"),\n        #     pl.when(pl.col(\"sequence_counter\") > 0.75 * pl.max(\"sequence_counter\"))\n        #       .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg4_mean\"),\n        # ]\n    for a, b in [(\"acc_x\", \"acc_y\"), (\"acc_x\", \"acc_z\"),\n             (\"acc_y\", \"acc_z\"), (\"angular_vel_x\", \"angular_vel_y\"),\n             (\"angular_vel_x\", \"angular_vel_z\"), (\"angular_vel_y\", \"angular_vel_z\")]:\n        agg_exprs += [\n            pl.corr(a, b).alias(f\"{a}_{b}_corr\"),\n            (pl.col(a) * pl.col(b)).mean().alias(f\"{a}_{b}_cov\")\n        ]\n    # first() for demographics and target\n    # agg_exprs += [\n    #     pl.col(c).first().alias(c) for c in demographic_cols + [target_col]\n    # ]\n    agg_exprs += [\n        pl.col(target_col).first().alias(target_col)\n    ]\n    agg_exprs += [pl.col(\"sequence_counter\").max().alias(c)]\n    # Group-by and aggregate\n    cleaned_data = (\n        data\n        .group_by(\"sequence_id\", maintain_order=True)\n        .agg(agg_exprs)\n    )\n    return cleaned_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:10:08.863973Z","iopub.execute_input":"2025-08-27T13:10:08.864244Z","iopub.status.idle":"2025-08-27T13:10:08.877122Z","shell.execute_reply.started":"2025-08-27T13:10:08.864224Z","shell.execute_reply":"2025-08-27T13:10:08.876234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data = pl.from_pandas(data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:10:08.879525Z","iopub.execute_input":"2025-08-27T13:10:08.879812Z","iopub.status.idle":"2025-08-27T13:10:09.951661Z","shell.execute_reply.started":"2025-08-27T13:10:08.879791Z","shell.execute_reply":"2025-08-27T13:10:09.950643Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cleaned_data = feature_engineering(data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:10:09.952726Z","iopub.execute_input":"2025-08-27T13:10:09.953050Z","iopub.status.idle":"2025-08-27T13:10:38.530009Z","shell.execute_reply.started":"2025-08-27T13:10:09.953022Z","shell.execute_reply":"2025-08-27T13:10:38.529115Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cleaned_data.head(2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:10:38.530912Z","iopub.execute_input":"2025-08-27T13:10:38.531230Z","iopub.status.idle":"2025-08-27T13:10:38.556949Z","shell.execute_reply.started":"2025-08-27T13:10:38.531201Z","shell.execute_reply":"2025-08-27T13:10:38.556077Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cleaned_data.write_parquet(\"cleaned_data.parquet\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:10:38.557833Z","iopub.execute_input":"2025-08-27T13:10:38.558135Z","iopub.status.idle":"2025-08-27T13:10:41.290784Z","shell.execute_reply.started":"2025-08-27T13:10:38.558115Z","shell.execute_reply":"2025-08-27T13:10:41.289593Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nfrom sklearn.preprocessing import LabelEncoder\ntarget_col = \"gesture\"\npdf = cleaned_data.to_pandas() \n\nle = LabelEncoder()\ny = le.fit_transform(pdf[target_col])\nX = pdf.drop(columns=[target_col, \"sequence_id\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:10:41.292161Z","iopub.execute_input":"2025-08-27T13:10:41.292583Z","iopub.status.idle":"2025-08-27T13:10:41.579173Z","shell.execute_reply.started":"2025-08-27T13:10:41.292553Z","shell.execute_reply":"2025-08-27T13:10:41.578236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"VALIDATE = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:10:41.580201Z","iopub.execute_input":"2025-08-27T13:10:41.580521Z","iopub.status.idle":"2025-08-27T13:10:41.585219Z","shell.execute_reply.started":"2025-08-27T13:10:41.580493Z","shell.execute_reply":"2025-08-27T13:10:41.584454Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import accuracy_score, f1_score\nfrom sklearn.ensemble import BaggingClassifier\nimport lightgbm as lgb\nimport numpy as np\n# from imblearn.under_sampling import RandomUnderSampler\n# from imblearn.over_sampling import SMOTE\n# from sklearn.impute import KNNImputer\n\nkfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n\nparams = dict(\n    objective=\"multiclass\",\n    num_class=len(le.classes_),\n    boosting_type=\"gbdt\",\n    learning_rate=0.2,\n    colsample_bytree = 0.9,\n    lambda_l1 = 0.5,\n    lambda_l2 = 0.3,\n    min_data_in_leaf=613,\n    num_leaves=613,\n    subsample=0.9,\n    # device=\"gpu\",\n    max_depth = -1,\n    n_estimators=20000,\n    bagging_freq = 1,\n    n_jobs=-1,\n    verbose=-1,\n    class_weight= \"balanced\",\n)\nif VALIDATE:\n    fold_acc, fold_f1 = [], []\n    for fold, (train_idx, val_idx) in enumerate(kfold.split(X, y), start=1):\n        X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]\n        y_train, y_val = y[train_idx], y[val_idx]\n        # imputer = KNNImputer(n_neighbors=7)\n        # X_train = imputer.fit_transform(X_train)\n        # sampler = RandomUnderSampler()\n        # X_train, y_train = sampler.fit_resample(X_train, y_train)\n  \n        model = lgb.LGBMClassifier(**params)\n        model.fit(\n            X_train, y_train,\n            eval_set=[(X_val, y_val)],\n            eval_metric=\"multi_logloss\",\n            callbacks=[lgb.early_stopping(25, verbose=False)],\n        )\n    \n        y_pred = model.predict(X_val)\n        acc = accuracy_score(y_val, y_pred)\n        f1  = f1_score(y_val, y_pred, average=\"macro\")\n    \n        fold_acc.append(acc)\n        fold_f1.append(f1)\n    \n    print(\"\\n======  5-Fold Summary  ======\")\n    print(f\"Accuracy:  mean={np.mean(fold_acc):.4f}  std={np.std(fold_acc):.4f}\")\n    print(f\"Macro-F1 : mean={np.mean(fold_f1):.4f}  std={np.std(fold_f1):.4f}\")\n\n# imputer = KNNImputer(n_neighbors=7)\n# X = imputer.fit_transform(X)\n# sm = SMOTE(random_state=42)\n# X, y = sm.fit_resample(X, y)\nlgb_model = lgb.LGBMClassifier(**params)\n# final_model = BaggingClassifier(estimator=lgb_model,n_estimators=10,n_jobs=-1)\nlgb_model.fit(X, y)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T13:10:41.586047Z","iopub.execute_input":"2025-08-27T13:10:41.586262Z","iopub.status.idle":"2025-08-27T16:31:24.611638Z","shell.execute_reply.started":"2025-08-27T13:10:41.586245Z","shell.execute_reply":"2025-08-27T16:31:24.610582Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"version = 2.9\nimport pickle\n\nwith open(f\"lgbm_gesture_model_{version}.pkl\", \"wb\") as f:\n    pickle.dump(lgb_model, f, protocol=pickle.HIGHEST_PROTOCOL)\n\nwith open(f\"gesture_label_encoder_{version}.pkl\", \"wb\") as f:\n    pickle.dump(le, f, protocol=pickle.HIGHEST_PROTOCOL)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T16:31:24.612854Z","iopub.execute_input":"2025-08-27T16:31:24.613176Z","iopub.status.idle":"2025-08-27T16:31:25.066189Z","shell.execute_reply.started":"2025-08-27T16:31:24.613144Z","shell.execute_reply":"2025-08-27T16:31:25.065442Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}