{"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":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## 1. Load the Sensor Data\n\nLet's start by loading the data into Python.  \nThink of this as opening two giant spreadsheets, one for **training** (learning) and one for **testing** (final exam).\n\n- Each **row** is a single moment in time for one gesture.\n- Each **column** is a measurement from one sensor (like acceleration, rotation, temperature, etc.).\n\nWe'll use Polars (fast!) to read the files, and then convert to pandas (easier for machine learning).","metadata":{}},{"cell_type":"code","source":"import polars as pl\nimport pandas as pd\n\n# Load the train and test CSV files into Polars DataFrames (like spreadsheets)\ntrain = pl.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ntest = pl.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\n\n# Convert Polars DataFrames to pandas DataFrames for compatibility with sklearn (the model library)\ntrain_pd = train.to_pandas()\ntest_pd = test.to_pandas()\n\n# Print the first few rows to check what our raw data looks like\nprint(\"First few rows of TRAIN data:\")\nprint(train_pd.head())\nprint(\"First few rows of TEST data:\")\nprint(test_pd.head())","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:29:57.364653Z","iopub.execute_input":"2025-07-27T04:29:57.364896Z","iopub.status.idle":"2025-07-27T04:30:18.049616Z","shell.execute_reply.started":"2025-07-27T04:29:57.364850Z","shell.execute_reply":"2025-07-27T04:30:18.048658Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Select IMU Features (Sensor Columns)\n\nWe want to use only the IMU sensor columns for our first model.  \nIMU (Inertial Measurement Unit) columns include **acceleration** and **rotation** readings.\n\n- Each **column** is like a different sensor on the watch (e.g., \"acc_x\" means acceleration along the x-axis).\n- By picking only columns starting with `\"acc_\"` or `\"rot_\"`, we focus on **motion data**.\n\nLet's print the names of these columns so we know what features we’re using.\n","metadata":{}},{"cell_type":"code","source":"# Pick all columns that start with \"acc_\" or \"rot_\"\nimu_cols = [col for col in train_pd.columns if col.startswith('acc_') or col.startswith('rot_')]\nprint(\"Selected IMU columns:\", imu_cols)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:30:39.272270Z","iopub.execute_input":"2025-07-27T04:30:39.272580Z","iopub.status.idle":"2025-07-27T04:30:39.278152Z","shell.execute_reply.started":"2025-07-27T04:30:39.272557Z","shell.execute_reply":"2025-07-27T04:30:39.277137Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Aggregate Data by Sequence (Gesture)\n\nEach **gesture** is made up of many rows (sensor readings over time).  \nFor machine learning, we want each gesture (sequence) to be **one row** with summary features.\n\n- **Why?** The model expects one row per example.\n- **How?** We take the average of each IMU feature for each gesture (like getting a \"summary\" for each movement).\n\nWe'll also grab the **gesture label** for each sequence so we know what action was performed.","metadata":{}},{"cell_type":"code","source":"# Group the data by sequence_id (each gesture) and calculate the mean (average) for each IMU feature\ntrain_agg = train_pd.groupby('sequence_id')[imu_cols].mean().reset_index()\n\n# For each sequence, get its gesture label (first label for that sequence)\ngesture_labels = train_pd.groupby('sequence_id')['gesture'].first().reset_index()\n\n# Merge features and gesture labels so each row represents a gesture, with its average sensor data and its name\ntrain_agg = train_agg.merge(gesture_labels, on='sequence_id')\n\nprint(\"First few rows of aggregated train data:\")\nprint(train_agg.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:30:45.240970Z","iopub.execute_input":"2025-07-27T04:30:45.241272Z","iopub.status.idle":"2025-07-27T04:30:45.439090Z","shell.execute_reply.started":"2025-07-27T04:30:45.241249Z","shell.execute_reply":"2025-07-27T04:30:45.437912Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Prepare Features and Labels\n\nNow let's get the data ready for machine learning!\n\n- **Features (`X`):** All the IMU averages (columns), describing the motion for each gesture.\n- **Labels (`y`):** The name of the gesture, which is what we want the model to predict.\n\nThink of `X` as a table where each row is a \"summary\" of a gesture, and `y` is the answer for each row (like a label on a bag).\n","metadata":{}},{"cell_type":"code","source":"X = train_agg[imu_cols]           # Features: sensor values\ny = train_agg['gesture']          # Labels: gesture names (what we want to predict)\n\n# Print some info about the labels\nprint(\"Unique gesture labels:\", y.unique())\nprint(\"How many examples per gesture label:\\n\", y.value_counts())\n\n# Drop rows with missing values (NaN) to keep only clean data\nX_clean = X.dropna()\ny_clean = y[X_clean.index]\n\nprint(\"Shape of features and labels after cleaning:\", X_clean.shape, y_clean.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:14.763964Z","iopub.execute_input":"2025-07-27T04:31:14.765140Z","iopub.status.idle":"2025-07-27T04:31:14.779166Z","shell.execute_reply.started":"2025-07-27T04:31:14.765108Z","shell.execute_reply":"2025-07-27T04:31:14.778318Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Split Data: Training Set vs. Validation Set\n\nBefore training, we need to split our data:\n- **Training set:** Used to teach the model (like study questions).\n- **Validation set:** Used to test how well the model learned (like a quiz).\n\n**This is important!**  \nIf we only check on data the model has already seen, it might just \"memorize\" the answers.  \nBy holding out some data for validation, we check if the model can generalize to new, unseen gestures.\n","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\n# 80% for training, 20% for validation (random_state=42 gives repeatable results)\nX_train, X_val, y_train, y_val = train_test_split(X_clean, y_clean, test_size=0.2, random_state=42)\n\nprint(\"Train shape (for learning):\", X_train.shape)\nprint(\"Validation shape (for checking):\", X_val.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:18.804843Z","iopub.execute_input":"2025-07-27T04:31:18.805143Z","iopub.status.idle":"2025-07-27T04:31:19.613130Z","shell.execute_reply.started":"2025-07-27T04:31:18.805124Z","shell.execute_reply":"2025-07-27T04:31:19.612157Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Train the Model (Learning Phase)\n\nNow we build our machine learning model.  \nWe use a \"logistic regression\" model that can handle many gesture types (multiclass).  \n- The model \"studies\" the training data to find patterns between the sensor features and the gesture label.\n- After training, it can make predictions on new gestures.\n\n**Example analogy:**  \nThink of the model as a judge at a talent show. It sees lots of practice performances (training set), learns what makes each act unique, and then judges new acts (validation set).","metadata":{}},{"cell_type":"code","source":"from sklearn.linear_model import LogisticRegression\n\n# max_iter=1000 gives the model more chances to learn; multinomial is for multiclass problems\nmodel = LogisticRegression(max_iter=1000, multi_class='multinomial', solver='lbfgs')\nmodel.fit(X_train, y_train)    # Model \"learns\" to predict gesture labels from IMU features","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:21.805971Z","iopub.execute_input":"2025-07-27T04:31:21.806403Z","iopub.status.idle":"2025-07-27T04:31:25.068710Z","shell.execute_reply.started":"2025-07-27T04:31:21.806381Z","shell.execute_reply":"2025-07-27T04:31:25.067832Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Validate the Model (Testing Phase)\n\nLet's see how well the model does on data it hasn't seen before!\n\n- We ask the model to predict the gesture labels for the validation set.\n- Then, we check its performance using \"macro F1 score,\" which tells us how balanced its predictions are across all gesture types.\n\nIf the F1 score is close to 1, the model is great!  \nIf it's around 0.5 or lower, it's missing a lot.","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import f1_score\n\n# Predict gesture names for validation data\ny_pred = model.predict(X_val)\n\nprint(\"Predicted gesture labels on validation set:\\n\", y_pred)\nprint(\"\\n\\n True gesture labels for validation set:\\n\", y_val)\n\n# Macro F1 score: average F1 score across all gesture classes\nprint(\"\\n\\nValidation Macro F1 Score:\", f1_score(y_val, y_pred, average='macro'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:26.787199Z","iopub.execute_input":"2025-07-27T04:31:26.787580Z","iopub.status.idle":"2025-07-27T04:31:26.811032Z","shell.execute_reply.started":"2025-07-27T04:31:26.787555Z","shell.execute_reply":"2025-07-27T04:31:26.810227Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Calculate Competition-Style Final Score\n\nThe final competition metric is the average of:\n- Binary F1 (did we get target/non-target right?)\n- Macro F1 (did we get the right gesture? All non-targets collapsed to one group)\n\nHere's how to compute both and the final average.","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import f1_score\n\n# 1. List all unique target gestures using the original training data\ntarget_gestures = train_pd.loc[train_pd['sequence_type'] == \"Target\", 'gesture'].unique()\n\nprint(\"All target gestures in the data:\")\nprint(target_gestures)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:31.681267Z","iopub.execute_input":"2025-07-27T04:31:31.681646Z","iopub.status.idle":"2025-07-27T04:31:31.766590Z","shell.execute_reply.started":"2025-07-27T04:31:31.681621Z","shell.execute_reply":"2025-07-27T04:31:31.765669Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. Calculate Binary F1\ndef gestures_to_binary(y):\n    # Map all target gestures to 1, others to 0\n    return [1 if g in target_gestures else 0 for g in y]\n\nbinary_true = gestures_to_binary(y_val)\nbinary_pred = gestures_to_binary(y_pred)\n\nbinary_f1 = f1_score(binary_true, binary_pred)\nprint(\"Binary F1 score:\", binary_f1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:35.870920Z","iopub.execute_input":"2025-07-27T04:31:35.871219Z","iopub.status.idle":"2025-07-27T04:31:35.903771Z","shell.execute_reply.started":"2025-07-27T04:31:35.871197Z","shell.execute_reply":"2025-07-27T04:31:35.902916Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. Calculate Macro F1 (collapse all non-targets into 'non_target')\ndef collapse_gestures(y):\n    return [g if g in target_gestures else 'non_target' for g in y]\n\nmacro_true = collapse_gestures(y_val)\nmacro_pred = collapse_gestures(y_pred)\n\nmacro_f1 = f1_score(macro_true, macro_pred, average='macro')\nprint(\"Macro F1 score:\", macro_f1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:37.987715Z","iopub.execute_input":"2025-07-27T04:31:37.988040Z","iopub.status.idle":"2025-07-27T04:31:38.026655Z","shell.execute_reply.started":"2025-07-27T04:31:37.988017Z","shell.execute_reply":"2025-07-27T04:31:38.025813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4. Final score: average the two\nfinal_score = (binary_f1 + macro_f1) / 2\nprint(\"Final (competition) score:\", final_score)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:40.332377Z","iopub.execute_input":"2025-07-27T04:31:40.332688Z","iopub.status.idle":"2025-07-27T04:31:40.337698Z","shell.execute_reply.started":"2025-07-27T04:31:40.332664Z","shell.execute_reply":"2025-07-27T04:31:40.336935Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Save Your Trained Model\n\nSaving the trained model allows you to reuse it for predictions (including submission).","metadata":{}},{"cell_type":"code","source":"import joblib\n\n# Suppose you used a scaler and a trained model as in previous steps\njoblib.dump(model, 'logreg_model.joblib')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:43.402499Z","iopub.execute_input":"2025-07-27T04:31:43.402802Z","iopub.status.idle":"2025-07-27T04:31:43.411073Z","shell.execute_reply.started":"2025-07-27T04:31:43.402777Z","shell.execute_reply":"2025-07-27T04:31:43.410150Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Define the Prediction Function for Kaggle Submission\n\nThis function is called for each gesture in the test set during submission. It must return the gesture name (not just target/non-target).","metadata":{}},{"cell_type":"code","source":"import joblib\n\nmodel = joblib.load('/kaggle/working/logreg_model.joblib')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:45.784101Z","iopub.execute_input":"2025-07-27T04:31:45.785021Z","iopub.status.idle":"2025-07-27T04:31:45.790031Z","shell.execute_reply.started":"2025-07-27T04:31:45.784992Z","shell.execute_reply":"2025-07-27T04:31:45.789153Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    # 1. Make sure IMU feature columns are the same as in training\n    imu_cols = [col for col in sequence.columns if col.startswith('acc_') or col.startswith('rot_')]\n\n    # 2. Compute mean values and convert to a pandas DataFrame, with the same column order\n    feature_means = [sequence[col].mean() for col in imu_cols]\n    feature_vector = pd.DataFrame([feature_means], columns=imu_cols)  # Shape (1, n_features)\n\n    # 3. If using scaler, scale the features (remove this line if not using scaler)\n    # feature_vector = scaler.transform(feature_vector)\n\n    # 4. Make sure there are no NaNs\n    feature_vector = feature_vector.fillna(0)\n\n    # 5. Predict gesture\n    gesture_pred = model.predict(feature_vector)[0]\n    return gesture_pred","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:48.544567Z","iopub.execute_input":"2025-07-27T04:31:48.545345Z","iopub.status.idle":"2025-07-27T04:31:48.550940Z","shell.execute_reply.started":"2025-07-27T04:31:48.545317Z","shell.execute_reply":"2025-07-27T04:31:48.549964Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 10. Register and Serve Your Function for Kaggle Evaluation\n\nThis code connects your `predict()` function to the Kaggle competition system, so your model will be tested and scored.","metadata":{}},{"cell_type":"code","source":"import kaggle_evaluation.cmi_inference_server\n\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nimport os\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    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T04:31:51.277774Z","iopub.execute_input":"2025-07-27T04:31:51.278132Z","iopub.status.idle":"2025-07-27T04:31:52.918472Z","shell.execute_reply.started":"2025-07-27T04:31:51.278109Z","shell.execute_reply":"2025-07-27T04:31:52.917634Z"}},"outputs":[],"execution_count":null}]}