{"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":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 🏆 CMI - Detect Behavior with Sensor Data - LightGBM + TOF PRO Baseline\n\nThis notebook presents a baseline solution for the **CMI - Detect Behavior with Sensor Data** competition.\n\n## 📋 Approach\n\nThe goal of this competition is to classify **BFRB-like gestures** and **non-BFRB gestures** using time series data collected from a wrist-worn device (Helios).\n\nThis solution uses:\n- **LightGBM Classifier** (`multiclass` objective)\n- Feature engineering using IMU, Thermopile and TOF PRO sensors.\n\n---\n\n## ⚙️ Pipeline\n\n### 1️⃣ Data Loading\n- train.csv, test.csv, demographics\n- Filtered `Performs gesture` phase for training.\n\n### 2️⃣ Feature Engineering\n- Aggregated features per sequence:\n    - **IMU**:\n        - `acc_x`, `acc_y`, `acc_z` → mean, std, min, max\n        - `rot_w`, `rot_x`, `rot_y`, `rot_z` → mean, std, min, max\n    - **Thermopile**:\n        - `thm_1` to `thm_5` → mean, std, min, max\n    - **TOF PRO features**:\n        - For each `tof_1` to `tof_5`: \n            - mean pixel value\n            - std pixel value\n            - % of `-1` pixels\n\n### 3️⃣ Model\n- LightGBMClassifier\n    - `objective='multiclass'`\n    - `num_leaves=31`\n    - `learning_rate=0.05`\n    - `early_stopping=50`\n- Train/validation split: 80/20\n- Label encoding for gestures.\n\n---\n\n## ✅ Results (Validation Set)\n\n| Metric | Value |\n|--------|-------|\n| Accuracy | ~67.79% |\n| Macro F1 Score | ~69.52% |\n\n---\n\n## 🚀 Submission\n\n- Implemented `GesturePredictor` class for inference on test sequences.\n- Submission saved as `submission.parquet` (required for this Code Competition).\n\n---\n\n## 💡 Next Steps\n\n- Further optimize TOF features.\n- Experiment with:\n    - **Time Series models** (RNN, Transformer, etc.)\n    - **Sensor fusion**\n    - **Ensembling**\n    - **Domain adaptation** for IMU-only test cases.\n\n---\n\nGood luck to everyone in the competition! 🚀","metadata":{}},{"cell_type":"code","source":"#1 - Imports\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport lightgbm as lgb\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, f1_score\nfrom sklearn.preprocessing import LabelEncoder","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2025-05-31T12:02:16.143676Z","iopub.execute_input":"2025-05-31T12:02:16.144596Z","iopub.status.idle":"2025-05-31T12:02:16.151170Z","shell.execute_reply.started":"2025-05-31T12:02:16.144561Z","shell.execute_reply":"2025-05-31T12:02:16.150025Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#2 - Data Loading\ntrain = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ntrain_demo = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\ntest = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\ntest_demo = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv')\n\nprint(\"Train shape:\", train.shape)\nprint(\"Test shape:\", test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T12:02:16.153277Z","iopub.execute_input":"2025-05-31T12:02:16.153574Z","iopub.status.idle":"2025-05-31T12:02:40.071005Z","shell.execute_reply.started":"2025-05-31T12:02:16.153552Z","shell.execute_reply":"2025-05-31T12:02:40.069748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#3 - Feature Engineering\n# Filter only 'Performs gesture'\ntrain_filtered = train[train['behavior'] == 'Performs gesture']\n\n# Aggregate statistical features per sequence\ndef extract_features(df):\n    features = []\n    for seq_id, seq_df in df.groupby('sequence_id'):\n        feats = {'sequence_id': seq_id}\n        for col in ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z', \n                    'thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5']:\n            feats[f'{col}_mean'] = seq_df[col].mean()\n            feats[f'{col}_std'] = seq_df[col].std()\n            feats[f'{col}_min'] = seq_df[col].min()\n            feats[f'{col}_max'] = seq_df[col].max()\n        # TOF PRO features\n        for sensor_num in range(1, 6):\n            sensor_prefix = f\"tof_{sensor_num}_\"\n            sensor_cols = [col for col in seq_df.columns if col.startswith(sensor_prefix)]\n            feats[f\"{sensor_prefix}mean_pixel\"] = seq_df[sensor_cols].mean().mean()\n            feats[f\"{sensor_prefix}std_pixel\"] = seq_df[sensor_cols].std().mean()\n            feats[f\"{sensor_prefix}neg1_pct\"] = seq_df[sensor_cols].eq(-1).mean().mean()\n        # Add gesture label\n        feats['gesture'] = seq_df['gesture'].iloc[0]\n        features.append(feats)\n    return pd.DataFrame(features)\n\nfinal_df_pro = extract_features(train_filtered)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T12:02:40.071988Z","iopub.execute_input":"2025-05-31T12:02:40.072321Z","iopub.status.idle":"2025-05-31T12:04:22.002544Z","shell.execute_reply.started":"2025-05-31T12:02:40.072298Z","shell.execute_reply":"2025-05-31T12:04:22.001279Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#4 - LightGBM Model\n\n# Prepare data\nX = final_df_pro.drop(columns=['sequence_id', 'gesture'])\ny = final_df_pro['gesture']\n\n# Encode labels\nle = LabelEncoder()\ny_encoded = le.fit_transform(y)\n\n# Split\nX_train, X_val, y_train, y_val = train_test_split(X, y_encoded, test_size=0.2, random_state=42)\n\n# Model\nlgb_clf_pro = lgb.LGBMClassifier(\n    objective='multiclass',\n    num_class=len(le.classes_),\n    learning_rate=0.05,\n    num_leaves=31,\n    random_state=42\n)\n\n# Import early_stopping callback\nfrom lightgbm import early_stopping\n\n# Train\nlgb_clf_pro.fit(\n    X_train, y_train,\n    eval_set=[(X_val, y_val)],\n    eval_metric='multi_logloss',\n    callbacks=[early_stopping(stopping_rounds=50)]\n)\n\n# Evaluate\ny_pred = lgb_clf_pro.predict(X_val)\naccuracy = accuracy_score(y_val, y_pred)\nmacro_f1 = f1_score(y_val, y_pred, average='macro')\n\nprint(f\"LightGBM (TOF PRO) Accuracy: {accuracy:.4f}\")\nprint(f\"LightGBM (TOF PRO) Macro F1 Score: {macro_f1:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T12:07:22.345941Z","iopub.execute_input":"2025-05-31T12:07:22.346396Z","iopub.status.idle":"2025-05-31T12:07:35.189213Z","shell.execute_reply.started":"2025-05-31T12:07:22.346366Z","shell.execute_reply":"2025-05-31T12:07:35.188283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#5 - GesturePredictor Class\n\nclass GesturePredictor:\n    def __init__(self):\n        self.model = lgb_clf_pro\n        self.le = le\n        self.features_to_use = X_train.columns.tolist()\n\n    def predict(self, sequence_df: pd.DataFrame) -> str:\n        feats = {}\n        for col in ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z', \n                    'thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5']:\n            feats[f'{col}_mean'] = sequence_df[col].mean()\n            feats[f'{col}_std'] = sequence_df[col].std()\n            feats[f'{col}_min'] = sequence_df[col].min()\n            feats[f'{col}_max'] = sequence_df[col].max()\n        for sensor_num in range(1, 6):\n            sensor_prefix = f\"tof_{sensor_num}_\"\n            sensor_cols = [col for col in sequence_df.columns if col.startswith(sensor_prefix)]\n            feats[f\"{sensor_prefix}mean_pixel\"] = sequence_df[sensor_cols].mean().mean()\n            feats[f\"{sensor_prefix}std_pixel\"] = sequence_df[sensor_cols].std().mean()\n            feats[f\"{sensor_prefix}neg1_pct\"] = sequence_df[sensor_cols].eq(-1).mean().mean()\n        feats_df = pd.DataFrame([feats])\n        feats_df = feats_df[self.features_to_use]\n        pred_probs = self.model.predict_proba(feats_df)\n        pred_idx = pred_probs.argmax(axis=1)[0]\n        pred_label = self.le.inverse_transform([pred_idx])[0]\n        return pred_label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T12:07:44.650172Z","iopub.execute_input":"2025-05-31T12:07:44.650530Z","iopub.status.idle":"2025-05-31T12:07:44.659900Z","shell.execute_reply.started":"2025-05-31T12:07:44.650503Z","shell.execute_reply":"2025-05-31T12:07:44.658670Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save model and LabelEncoder\nimport joblib\n\njoblib.dump(lgb_clf_pro, '/kaggle/working/lgb_clf_pro.pkl')\njoblib.dump(le, '/kaggle/working/label_encoder.pkl')\n\nprint(\"✅ Model and LabelEncoder saved!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T12:22:23.940399Z","iopub.execute_input":"2025-05-31T12:22:23.942162Z","iopub.status.idle":"2025-05-31T12:22:24.104109Z","shell.execute_reply.started":"2025-05-31T12:22:23.942122Z","shell.execute_reply":"2025-05-31T12:22:24.102992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# #6 - Kaggle Submission\n\n# Generate submission.parquet\nsubmission = pd.DataFrame({\n    \"sequence_id\": test['sequence_id'].unique(),\n    \"gesture\": [\n        GesturePredictor().predict(test[test['sequence_id'] == seq_id])\n        for seq_id in test['sequence_id'].unique()\n    ]\n})\n\nprint(submission.head())\n\n# Save as required by the competition\nsubmission.to_parquet('/kaggle/working/submission.parquet', index=False)\nprint(\"✅ submission.parquet saved!\")","metadata":{"execution":{"iopub.status.busy":"2025-05-31T12:08:55.973815Z","iopub.execute_input":"2025-05-31T12:08:55.974329Z","iopub.status.idle":"2025-05-31T12:08:56.029057Z","shell.execute_reply.started":"2025-05-31T12:08:55.974298Z","shell.execute_reply":"2025-05-31T12:08:56.028000Z"},"trusted":true},"outputs":[],"execution_count":null}]}