{"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":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:52:28.417127Z","iopub.execute_input":"2025-06-13T03:52:28.417428Z","iopub.status.idle":"2025-06-13T03:52:30.768058Z","shell.execute_reply.started":"2025-06-13T03:52:28.417403Z","shell.execute_reply":"2025-06-13T03:52:30.767073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\")\ntest_data = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv\")\ntrain_demo = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\")\ntest_demo = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:52:35.205033Z","iopub.execute_input":"2025-06-13T03:52:35.205931Z","iopub.status.idle":"2025-06-13T03:53:13.192933Z","shell.execute_reply.started":"2025-06-13T03:52:35.205901Z","shell.execute_reply":"2025-06-13T03:53:13.191929Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_demo.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T06:03:21.785723Z","iopub.execute_input":"2025-06-13T06:03:21.786176Z","iopub.status.idle":"2025-06-13T06:03:21.798746Z","shell.execute_reply.started":"2025-06-13T06:03:21.786143Z","shell.execute_reply":"2025-06-13T06:03:21.797616Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.sample(n=5,random_state=42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:53:22.245306Z","iopub.execute_input":"2025-06-13T03:53:22.246122Z","iopub.status.idle":"2025-06-13T03:53:22.315016Z","shell.execute_reply.started":"2025-06-13T03:53:22.246064Z","shell.execute_reply":"2025-06-13T03:53:22.314219Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:53:23.570047Z","iopub.execute_input":"2025-06-13T03:53:23.570398Z","iopub.status.idle":"2025-06-13T03:53:23.577322Z","shell.execute_reply.started":"2025-06-13T03:53:23.570373Z","shell.execute_reply":"2025-06-13T03:53:23.576352Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_data.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:53:26.244942Z","iopub.execute_input":"2025-06-13T03:53:26.245753Z","iopub.status.idle":"2025-06-13T03:53:26.273001Z","shell.execute_reply.started":"2025-06-13T03:53:26.245719Z","shell.execute_reply":"2025-06-13T03:53:26.271712Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_demo.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:53:27.284917Z","iopub.execute_input":"2025-06-13T03:53:27.285303Z","iopub.status.idle":"2025-06-13T03:53:27.298592Z","shell.execute_reply.started":"2025-06-13T03:53:27.285263Z","shell.execute_reply":"2025-06-13T03:53:27.297713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_demo.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:53:29.574940Z","iopub.execute_input":"2025-06-13T03:53:29.575267Z","iopub.status.idle":"2025-06-13T03:53:29.582004Z","shell.execute_reply.started":"2025-06-13T03:53:29.575241Z","shell.execute_reply":"2025-06-13T03:53:29.580961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Merge the train data and demogrphics data \ntrain_data = train_data.merge(train_demo, on='subject', how='left')\ntest_data = test_data.merge(test_demo, on='subject', how='left')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:53:39.625201Z","iopub.execute_input":"2025-06-13T03:53:39.625860Z","iopub.status.idle":"2025-06-13T03:53:40.357183Z","shell.execute_reply.started":"2025-06-13T03:53:39.625832Z","shell.execute_reply":"2025-06-13T03:53:40.356308Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:53:42.455406Z","iopub.execute_input":"2025-06-13T03:53:42.455707Z","iopub.status.idle":"2025-06-13T03:53:42.477213Z","shell.execute_reply.started":"2025-06-13T03:53:42.455685Z","shell.execute_reply":"2025-06-13T03:53:42.476141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Handiling missing data\nmissing_count = test_data.isnull().sum()\nmissing_count = missing_count[missing_count>0]\nprint('missing_counts: ',missing_count)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:54:01.685076Z","iopub.execute_input":"2025-06-13T03:54:01.685410Z","iopub.status.idle":"2025-06-13T03:54:01.693505Z","shell.execute_reply.started":"2025-06-13T03:54:01.685388Z","shell.execute_reply":"2025-06-13T03:54:01.692365Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.isnull().sum()[train_data.isnull().sum() > 0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:54:03.884909Z","iopub.execute_input":"2025-06-13T03:54:03.885243Z","iopub.status.idle":"2025-06-13T03:54:05.669953Z","shell.execute_reply.started":"2025-06-13T03:54:03.885218Z","shell.execute_reply":"2025-06-13T03:54:05.668962Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T03:54:11.525689Z","iopub.execute_input":"2025-06-13T03:54:11.525972Z","iopub.status.idle":"2025-06-13T03:54:21.509057Z","shell.execute_reply.started":"2025-06-13T03:54:11.525953Z","shell.execute_reply":"2025-06-13T03:54:21.507848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Replace NaN values with mean of their respective columns\ntrain_data.fillna(train_data.mean(numeric_only=True), inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T04:24:29.373187Z","iopub.execute_input":"2025-06-13T04:24:29.373512Z","iopub.status.idle":"2025-06-13T04:24:32.044663Z","shell.execute_reply.started":"2025-06-13T04:24:29.373488Z","shell.execute_reply":"2025-06-13T04:24:32.043918Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T04:24:34.067903Z","iopub.execute_input":"2025-06-13T04:24:34.068260Z","iopub.status.idle":"2025-06-13T04:24:43.964534Z","shell.execute_reply.started":"2025-06-13T04:24:34.068233Z","shell.execute_reply":"2025-06-13T04:24:43.963738Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Use Label Encoder to encode\nfrom sklearn.preprocessing import LabelEncoder\n\nle = LabelEncoder()\ntrain_data['orientation'] = le.fit_transform(train_data['orientation'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T04:29:10.153081Z","iopub.execute_input":"2025-06-13T04:29:10.154027Z","iopub.status.idle":"2025-06-13T04:29:10.897773Z","shell.execute_reply.started":"2025-06-13T04:29:10.153989Z","shell.execute_reply":"2025-06-13T04:29:10.896135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Feature Selection\nfrom sklearn.preprocessing import StandardScaler\n\nsensor_cols = [col for col in train_data.columns if col.startswith(('acc_', 'gyr_', 'therm_', 'tof_'))]\ndemo_cols = ['age','height_cm','shoulder_to_wrist_cm','elbow_to_wrist_cm']\n\nscaler=StandardScaler()\ntrain_data[sensor_cols] = train_data.groupby('sequence_id')[sensor_cols].transform(lambda x: scaler.fit_transform(x.values.reshape(-1, 1)).flatten())\ntrain_data[demo_cols] = scaler.fit_transform(train_data[demo_cols])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T05:00:03.842088Z","iopub.execute_input":"2025-06-13T05:00:03.842824Z","iopub.status.idle":"2025-06-13T05:19:32.936418Z","shell.execute_reply.started":"2025-06-13T05:00:03.842793Z","shell.execute_reply":"2025-06-13T05:19:32.935401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define IMU cols and demo cols\nimu_cols = [col for col in train_data.columns if col.startswith(('acc_','gyr_'))]\ndemo_cols = ['age', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm'] + \\\n            [col for col in train_data.columns if col.endswith('_encoded') or col.startswith(('sex_', 'handedness_', 'adult_child_', 'orientation_'))]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T05:30:10.679889Z","iopub.execute_input":"2025-06-13T05:30:10.681055Z","iopub.status.idle":"2025-06-13T05:30:10.688971Z","shell.execute_reply.started":"2025-06-13T05:30:10.680939Z","shell.execute_reply":"2025-06-13T05:30:10.687403Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature Extraction","metadata":{}},{"cell_type":"code","source":"# Aggregate time-series features for gesture phase\ngesture_df = train_data[train_data['phase'] == 'Gesture']\nprint(f\"Gesture DataFrame shape: {gesture_df.shape}\")\nassert not gesture_df.empty, \"Error: gesture_df is empty. Check if 'phase' contains 'Gesture'.\"\nagg_funcs = ['mean', 'std', 'min', 'max', 'median']\ngesture_features = gesture_df.groupby('sequence_id')[sensor_cols].agg(agg_funcs).reset_index()\ngesture_features.columns = ['_'.join(col).strip() if col[1] else col[0] for col in gesture_features.columns]\nprint(f\"Gesture features shape: {gesture_features.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T05:53:53.015623Z","iopub.execute_input":"2025-06-13T05:53:53.016033Z","iopub.status.idle":"2025-06-13T05:54:02.331963Z","shell.execute_reply.started":"2025-06-13T05:53:53.016007Z","shell.execute_reply":"2025-06-13T05:54:02.330858Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')\n\n# Compute Time-Series Difference Features\ndiff_df = gesture_df[['sequence_id']].copy()\nfor col in sensor_cols:\n    diff_df[f'{col}_diff'] = gesture_df.groupby('sequence_id')[col].diff().fillna(0)\ngesture_df = pd.concat([gesture_df, diff_df[[col for col in diff_df.columns if col.endswith('_diff')]]], axis=1)\ndiff_cols = [f'{col}_diff' for col in sensor_cols]\ndiff_features = gesture_df.groupby('sequence_id')[diff_cols].agg(agg_funcs).reset_index()\ndiff_features.columns = ['_'.join(col).strip() if col[1] else col[0] for col in diff_features.columns]\nfeatures = gesture_features.merge(diff_features, on='sequence_id', how='left')\nprint(f\"Features after diff merge shape: {features.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T05:55:23.806562Z","iopub.execute_input":"2025-06-13T05:55:23.806886Z","iopub.status.idle":"2025-06-13T05:55:46.126496Z","shell.execute_reply.started":"2025-06-13T05:55:23.806864Z","shell.execute_reply":"2025-06-13T05:55:46.125421Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Incorporate Demographic Features\ndemo_features = train_data.groupby('sequence_id')[demo_cols].first().reset_index()\nfeatures = features.merge(demo_features, on='sequence_id', how='left')\nprint(f\"Features after demo merge shape: {features.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T05:55:50.800060Z","iopub.execute_input":"2025-06-13T05:55:50.800406Z","iopub.status.idle":"2025-06-13T05:55:51.081248Z","shell.execute_reply.started":"2025-06-13T05:55:50.800384Z","shell.execute_reply":"2025-06-13T05:55:51.080275Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Add Contextual Features from Transition and Pause Phases\ntransition_df = train_data[train_data['phase'] == 'Transition']\npause_df = train_data[train_data['phase'] == 'Pause']\nprint(f\"Transition DataFrame shape: {transition_df.shape}\")\nprint(f\"Pause DataFrame shape: {pause_df.shape}\")\ntransition_features = transition_df.groupby('sequence_id')[sensor_cols].agg(agg_funcs).reset_index()\ntransition_features.columns = ['transition_' + '_'.join(col).strip() if col[1] else col[0] for col in transition_features.columns]\npause_features = pause_df.groupby('sequence_id')[sensor_cols].agg(agg_funcs).reset_index()\npause_features.columns = ['pause_' + '_'.join(col).strip() if col[1] else col[0] for col in pause_features.columns]\nfeatures = features.merge(transition_features, on='sequence_id', how='left')\nfeatures = features.merge(pause_features, on='sequence_id', how='left')\nfeatures = features.fillna(0)\nprint(f\"Features after transition/pause merge shape: {features.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T05:56:33.590737Z","iopub.execute_input":"2025-06-13T05:56:33.591043Z","iopub.status.idle":"2025-06-13T05:56:41.928892Z","shell.execute_reply.started":"2025-06-13T05:56:33.591022Z","shell.execute_reply":"2025-06-13T05:56:41.927910Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create IMU-Only and Full-Sensor Feature Sets\nimu_features = features[[col for col in features.columns if any(col.startswith(c) for c in imu_cols) or\n                        col in demo_cols or\n                        col.startswith(('transition_acc_', 'transition_gyr_', 'pause_acc_', 'pause_gyr_')) or\n                        col == 'sequence_id']]\nfull_features = features\nprint(f\"IMU features shape: {imu_features.shape}\")\nprint(f\"Full features shape: {full_features.shape}\")\n\n# 2.6: Add Labels\nlabels = train_data.groupby('sequence_id').agg({\n    'sequence_type': 'first',\n    'gesture': 'first'\n}).reset_index()\nlabels['is_bfrb'] = (labels['sequence_type'] == 'target').astype(int)\nmissing_seqs = set(full_features['sequence_id']).difference(labels['sequence_id'])\nif missing_seqs:\n    print(f\"Warning: {len(missing_seqs)} sequence_ids in features not found in labels: {missing_seqs}\")\nimu_features = imu_features.merge(labels[['sequence_id', 'is_bfrb', 'gesture']], on='sequence_id', how='left')\nfull_features = full_features.merge(labels[['sequence_id', 'is_bfrb', 'gesture']], on='sequence_id', how='left')\nprint(f\"IMU features after labels merge shape: {imu_features.shape}\")\nprint(f\"Full features after labels merge shape: {full_features.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T05:58:03.994124Z","iopub.execute_input":"2025-06-13T05:58:03.994504Z","iopub.status.idle":"2025-06-13T05:58:04.674289Z","shell.execute_reply.started":"2025-06-13T05:58:03.994479Z","shell.execute_reply":"2025-06-13T05:58:04.673132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2.7: Feature Selection for Random Forest\nfrom sklearn.ensemble import RandomForestClassifier\n\nX_full = full_features.drop(['sequence_id', 'gesture', 'is_bfrb', 'sequence_type'], axis=1, errors='ignore')\ny_full = full_features['gesture']\nprint(f\"X_full shape for feature selection: {X_full.shape}\")\nassert X_full.shape[0] > 0, f\"Error: X_full has zero samples (shape: {X_full.shape}). Check previous steps for data loss.\"\nif X_full.shape[0] > 1:  # Only perform feature selection if enough samples\n    rf_model = RandomForestClassifier(n_estimators=50, random_state=42)\n    rf_model.fit(X_full, y_full)\n    feature_importance = pd.Series(rf_model.feature_importances_, index=X_full.columns)\n    top_features = feature_importance.nlargest(50).index\n    full_features_selected = full_features[['sequence_id', 'gesture', 'is_bfrb'] + list(top_features)]\n    imu_top_features = [col for col in top_features if col in imu_features.columns]\n    imu_features_selected = imu_features[['sequence_id', 'gesture', 'is_bfrb'] + imu_top_features]\nelse:\n    print(\"Warning: Insufficient samples for feature selection. Using all features.\")\n    full_features_selected = full_features\n    imu_features_selected = imu_features\nprint(f\"Selected full features shape: {full_features_selected.shape}\")\nprint(f\"Selected IMU features shape: {imu_features_selected.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T05:59:23.810822Z","iopub.execute_input":"2025-06-13T05:59:23.811177Z","iopub.status.idle":"2025-06-13T05:59:54.840019Z","shell.execute_reply.started":"2025-06-13T05:59:23.811155Z","shell.execute_reply":"2025-06-13T05:59:54.839170Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Split Data for Random Forest\nfrom sklearn.model_selection import train_test_split\n\ntrain_seq, val_seq = train_test_split(\n    full_features_selected['sequence_id'],\n    test_size=0.2,\n    stratify=full_features_selected['gesture'],\n    random_state=42\n)\ntrain_full = full_features_selected[full_features_selected['sequence_id'].isin(train_seq)]\nval_full = full_features_selected[full_features_selected['sequence_id'].isin(val_seq)]\ntrain_imu = imu_features_selected[imu_features_selected['sequence_id'].isin(train_seq)]\nval_imu = imu_features_selected[imu_features_selected['sequence_id'].isin(val_seq)]\nprint(f\"Train full shape: {train_full.shape}\")\nprint(f\"Val full shape: {val_full.shape}\")\nprint(f\"Train IMU shape: {train_imu.shape}\")\nprint(f\"Val IMU shape: {val_imu.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T06:08:46.505625Z","iopub.execute_input":"2025-06-13T06:08:46.506002Z","iopub.status.idle":"2025-06-13T06:08:46.548040Z","shell.execute_reply.started":"2025-06-13T06:08:46.505975Z","shell.execute_reply":"2025-06-13T06:08:46.546680Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train Random Forest Models","metadata":{}},{"cell_type":"code","source":"# Full-Sensor Random Forest\nX_train_full = train_full.drop(['sequence_id', 'gesture', 'is_bfrb'], axis=1)\ny_train_full = train_full['gesture']\nX_val_full = val_full.drop(['sequence_id', 'gesture', 'is_bfrb'], axis=1)\ny_val_full = val_full['gesture']\nrf_full = RandomForestClassifier(n_estimators=100, random_state=42)\nrf_full.fit(X_train_full, y_train_full)\ny_pred_full = rf_full.predict(X_val_full)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T06:12:08.336282Z","iopub.execute_input":"2025-06-13T06:12:08.336666Z","iopub.status.idle":"2025-06-13T06:12:14.779392Z","shell.execute_reply.started":"2025-06-13T06:12:08.336639Z","shell.execute_reply":"2025-06-13T06:12:14.778368Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import f1_score\nimport joblib\n\n# Custom evaluation metric (fixed)\ndef custom_f1_score(y_true, y_pred):\n    # Binary F1: Map to 1 (BFRB-like) or 0 (non_target)\n    binary_true = [1 if g != 'non_target' else 0 for g in y_true]\n    binary_pred = [1 if g != 'non_target' else 0 for g in y_pred]\n    try:\n        binary_f1 = f1_score(binary_true, binary_pred, average='binary')\n    except ValueError as e:\n        print(f\"Error computing Binary F1: {e}. Returning 0.\")\n        binary_f1 = 0.0\n    # Macro F1: Multiclass F1\n    try:\n        macro_f1 = f1_score(y_true, y_pred, average='macro')\n    except ValueError as e:\n        print(f\"Error computing Macro F1: {e}. Returning 0.\")\n        macro_f1 = 0.0\n    return (binary_f1 + macro_f1) / 2\n\nscore_full = custom_f1_score(y_val_full, y_pred_full)\nprint(f\"Random Forest Custom F1 Score (Full Sensors): {score_full}\")\njoblib.dump(rf_full, 'rf_full_model.pkl')\n\n# IMU-Only Random Forest\nX_train_imu = train_imu.drop(['sequence_id', 'gesture', 'is_bfrb'], axis=1)\ny_train_imu = train_imu['gesture']\nX_val_imu = val_imu.drop(['sequence_id', 'gesture', 'is_bfrb'], axis=1)\ny_val_imu = val_imu['gesture']\nrf_imu = RandomForestClassifier(n_estimators=100, random_state=42)\nrf_imu.fit(X_train_imu, y_train_imu)\ny_pred_imu = rf_imu.predict(X_val_imu)\nscore_imu = custom_f1_score(y_val_imu, y_pred_imu)\nprint(f\"Random Forest Custom F1 Score (IMU-Only): {score_imu}\")\njoblib.dump(rf_imu, 'rf_imu_model.pkl')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-13T06:16:37.584057Z","iopub.execute_input":"2025-06-13T06:16:37.584472Z","iopub.status.idle":"2025-06-13T06:16:45.548917Z","shell.execute_reply.started":"2025-06-13T06:16:37.584448Z","shell.execute_reply":"2025-06-13T06:16:45.547825Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}