{"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":false,"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-08-12T08:04:27.888836Z","iopub.execute_input":"2025-08-12T08:04:27.889170Z","iopub.status.idle":"2025-08-12T08:04:27.902070Z","shell.execute_reply.started":"2025-08-12T08:04:27.889151Z","shell.execute_reply":"2025-08-12T08:04:27.900540Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 1. Imports and Setup","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport lightgbm as lgb\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import f1_score, make_scorer\nfrom sklearn.preprocessing import LabelEncoder\nimport joblib","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T08:04:27.903975Z","iopub.execute_input":"2025-08-12T08:04:27.904506Z","iopub.status.idle":"2025-08-12T08:04:27.910043Z","shell.execute_reply.started":"2025-08-12T08:04:27.904476Z","shell.execute_reply":"2025-08-12T08:04:27.908571Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2. Load and Merge Data","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ndemographics_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\n\ntrain_df = train_df.merge(demographics_df, on='subject')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T08:04:27.911334Z","iopub.execute_input":"2025-08-12T08:04:27.911729Z","iopub.status.idle":"2025-08-12T08:04:45.631777Z","shell.execute_reply.started":"2025-08-12T08:04:27.911702Z","shell.execute_reply":"2025-08-12T08:04:45.630808Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. Preprocessing","metadata":{}},{"cell_type":"code","source":"# --- 2. Preprocessing and Feature Engineering ---\nprint(\"Starting Feature Engineering...\")\n\n# Define feature engineering function\ndef create_features(df):\n    # Group by sequence and extract features\n    features = []\n    for seq_id, seq_data in df.groupby('sequence_id'):\n        row = {'sequence_id': seq_id}\n\n        # Demographics features\n        row.update(seq_data[['adult_child', 'age', 'sex', 'handedness', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']].iloc[0].to_dict())\n\n        # Target and other metadata\n        row['gesture'] = seq_data['gesture'].iloc[0]\n        row['is_full_data'] = 1 if not seq_data['thm_1'].isnull().all() else 0\n\n        # IMU Features\n        acc_cols = [f'acc_{axis}' for axis in 'xyz']\n        rot_cols = [f'rot_{axis}' for axis in 'wxyz']\n\n        for col in acc_cols + rot_cols:\n            row[f'{col}_mean'] = seq_data[col].mean()\n            row[f'{col}_std'] = seq_data[col].std()\n            row[f'{col}_min'] = seq_data[col].min()\n            row[f'{col}_max'] = seq_data[col].max()\n            row[f'{col}_skew'] = seq_data[col].skew()\n\n        # Thermopile (Thm) and Time-of-Flight (ToF) Features\n        thm_cols = [f'thm_{i}' for i in range(1, 6)]\n        tof_cols = [f'tof_{i}_v{j}' for i in range(1, 6) for j in range(64)]\n\n        # We will only create these features if it's a full-data sequence\n        if row['is_full_data'] == 1:\n            for col in thm_cols:\n                row[f'{col}_mean'] = seq_data[col].mean()\n                row[f'{col}_std'] = seq_data[col].std()\n                row[f'{col}_min'] = seq_data[col].min()\n                row[f'{col}_max'] = seq_data[col].max()\n            \n            # Inter-thermopile features\n            for i in range(1, 5):\n                row[f'thm_diff_{i}_{i+1}'] = seq_data[f'thm_{i}'].mean() - seq_data[f'thm_{i+1}'].mean()\n\n            # ToF features (treating each sensor as a unit)\n            for i in range(1, 6):\n                tof_sensor_cols = [f'tof_{i}_v{j}' for j in range(64)]\n                # Replace -1 with NaN for statistical calculations\n                tof_data = seq_data[tof_sensor_cols].replace(-1, np.nan)\n                row[f'tof_{i}_mean'] = tof_data.mean().mean()\n                row[f'tof_{i}_std'] = tof_data.std().mean()\n                row[f'tof_{i}_pixel_count'] = (~tof_data.isnull()).sum().mean()\n        \n        features.append(row)\n    \n    return pd.DataFrame(features)\n\n# Create the final feature dataframe\nfeatures_df = create_features(train_df)\nprint(\"Feature Engineering Complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T08:04:45.632757Z","iopub.execute_input":"2025-08-12T08:04:45.633171Z","iopub.status.idle":"2025-08-12T08:06:05.375958Z","shell.execute_reply.started":"2025-08-12T08:04:45.633091Z","shell.execute_reply":"2025-08-12T08:06:05.374889Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. Model Training","metadata":{}},{"cell_type":"code","source":"# --- 3. Model Training ---\n# Label encode the target variable 'gesture'\nle = LabelEncoder()\nfeatures_df['gesture_encoded'] = le.fit_transform(features_df['gesture'])\n\n# --- Custom Evaluation Metric ---\n# This is crucial for matching the competition's scoring.\ndef macro_f1_score(y_true, y_pred, labels):\n    y_pred_labels = np.argmax(y_pred, axis=1)\n    \n    # Binary F1\n    y_true_binary = (y_true != le.transform(['non_target'])[0]).astype(int)\n    y_pred_binary = (y_pred_labels != le.transform(['non_target'])[0]).astype(int)\n    binary_f1 = f1_score(y_true_binary, y_pred_binary)\n\n    # Macro F1\n    y_pred_macro = y_pred_labels.copy()\n    y_true_macro = y_true.copy()\n    non_target_idx = le.transform(['non_target'])[0]\n    \n    y_pred_macro[y_pred_macro == non_target_idx] = -1\n    y_true_macro[y_true_macro == non_target_idx] = -1\n    \n    macro_f1 = f1_score(y_true_macro[y_true_macro != -1], y_pred_macro[y_true_macro != -1], average='macro', zero_division=0)\n\n    return 'macro_f1', (binary_f1 + macro_f1) / 2, True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T08:06:05.378839Z","iopub.execute_input":"2025-08-12T08:06:05.379125Z","iopub.status.idle":"2025-08-12T08:06:05.390506Z","shell.execute_reply.started":"2025-08-12T08:06:05.379100Z","shell.execute_reply":"2025-08-12T08:06:05.389439Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Model on full data","metadata":{}},{"cell_type":"code","source":"print(\"\\nTraining Full-Data Model...\")\nfull_data_df = features_df[features_df['is_full_data'] == 1].drop(columns=['is_full_data', 'gesture'])\nX_full = full_data_df.drop(columns=['sequence_id', 'gesture_encoded'])\ny_full = full_data_df['gesture_encoded']\n\nlgb_full = lgb.LGBMClassifier(objective='multiclass', num_class=len(le.classes_), random_state=42, n_estimators=500, learning_rate=0.05, verbosity=-1)\nlgb_full.fit(X_full, y_full)\njoblib.dump(lgb_full, 'lgb_full_data_model.pkl')\njoblib.dump(le, 'label_encoder.pkl')\nprint(\"Full-Data Model Trained and Saved.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T08:07:36.479111Z","iopub.execute_input":"2025-08-12T08:07:36.479973Z","iopub.status.idle":"2025-08-12T08:08:34.007006Z","shell.execute_reply.started":"2025-08-12T08:07:36.479942Z","shell.execute_reply":"2025-08-12T08:08:34.005715Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training IMU only model","metadata":{}},{"cell_type":"code","source":"# --- Train the IMU-Only Model ---\nprint(\"\\nTraining IMU-Only Model...\")\nimu_features = [col for col in X_full.columns if 'acc_' in col or 'rot_' in col or col in demographics_df.columns]\nX_imu = features_df[imu_features]\ny_imu = features_df['gesture_encoded']\n\nlgb_imu = lgb.LGBMClassifier(objective='multiclass', num_class=len(le.classes_), random_state=42, n_estimators=500, learning_rate=0.05, verbosity=-1)\nlgb_imu.fit(X_imu, y_imu)\njoblib.dump(lgb_imu, 'lgb_imu_only_model.pkl')\nprint(\"IMU-Only Model Trained and Saved.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T08:08:34.008639Z","iopub.execute_input":"2025-08-12T08:08:34.008947Z","iopub.status.idle":"2025-08-12T08:09:09.104241Z","shell.execute_reply.started":"2025-08-12T08:08:34.008928Z","shell.execute_reply":"2025-08-12T08:09:09.103314Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"markdown","source":"## 1. Loading Models","metadata":{}},{"cell_type":"code","source":"lgb_full = None\nlgb_imu = None\nle = None\n\ndef load_models():\n    global lgb_full, lgb_imu, le\n    if lgb_full is None or lgb_imu is None or le is None:\n        try:\n            lgb_full = joblib.load('/kaggle/working/lgb_full_data_model.pkl')\n            lgb_imu = joblib.load('/kaggle/working/lgb_imu_only_model.pkl')\n            le = joblib.load('/kaggle/working/label_encoder.pkl')\n            demographics_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv') # Use train demographics for simplicity in this example\n            print(\"Models and Label Encoder loaded successfully.\")\n        except FileNotFoundError:\n            print(\"Error: Model files not found. Please ensure 'lgb_full_data_model.pkl', 'lgb_imu_only_model.pkl', and 'label_encoder.pkl' are in the working directory.\")\n            # In a real submission, this would likely cause a runtime error, which is desired.\n            raise\n\nload_models()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T08:09:09.105758Z","iopub.execute_input":"2025-08-12T08:09:09.106183Z","iopub.status.idle":"2025-08-12T08:09:09.434088Z","shell.execute_reply.started":"2025-08-12T08:09:09.106129Z","shell.execute_reply":"2025-08-12T08:09:09.432298Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Feature Engineering","metadata":{}},{"cell_type":"code","source":"# --- 2. Feature Engineering Function ---\n# This function is designed to work on a single sequence at a time.\n# It replicates the feature engineering from the training notebook.\ndef create_inference_features(sequence: pd.DataFrame, demographics: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Creates the same features as the training script for a single sequence.\n    This function assumes the input is a Pandas DataFrame.\n    \"\"\"\n    features = {}\n\n    # Demographics features\n    features.update(demographics.iloc[0][['adult_child', 'age', 'sex', 'handedness', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']].to_dict())\n\n    # Check if it's a full-data sequence\n    is_full_data = 1 if not sequence['thm_1'].isnull().all() else 0\n    features['is_full_data'] = is_full_data\n\n    # IMU Features\n    acc_cols = [f'acc_{axis}' for axis in 'xyz']\n    rot_cols = [f'rot_{axis}' for axis in 'wxyz']\n\n    for col in acc_cols + rot_cols:\n        features[f'{col}_mean'] = sequence[col].mean()\n        features[f'{col}_std'] = sequence[col].std()\n        features[f'{col}_min'] = sequence[col].min()\n        features[f'{col}_max'] = sequence[col].max()\n        features[f'{col}_skew'] = sequence[col].skew()\n\n    # Thermopile (Thm) and Time-of-Flight (ToF) Features (only for full-data)\n    if is_full_data == 1:\n        thm_cols = [f'thm_{i}' for i in range(1, 6)]\n        for col in thm_cols:\n            features[f'{col}_mean'] = sequence[col].mean()\n            features[f'{col}_std'] = sequence[col].std()\n            features[f'{col}_min'] = sequence[col].min()\n            features[f'{col}_max'] = sequence[col].max()\n        \n        for i in range(1, 5):\n            features[f'thm_diff_{i}_{i+1}'] = sequence[f'thm_{i}'].mean() - sequence[f'thm_{i+1}'].mean()\n\n        for i in range(1, 6):\n            tof_sensor_cols = [f'tof_{i}_v{j}' for j in range(64)]\n            tof_data = sequence[tof_sensor_cols].replace(-1, np.nan)\n            features[f'tof_{i}_mean'] = tof_data.mean().mean()\n            features[f'tof_{i}_std'] = tof_data.std().mean()\n            features[f'tof_{i}_pixel_count'] = (~tof_data.isnull()).sum().mean()\n    \n    return pd.DataFrame([features])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T08:09:09.436294Z","iopub.execute_input":"2025-08-12T08:09:09.436590Z","iopub.status.idle":"2025-08-12T08:09:09.449001Z","shell.execute_reply.started":"2025-08-12T08:09:09.436572Z","shell.execute_reply":"2025-08-12T08:09:09.447331Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Predict function","metadata":{}},{"cell_type":"code","source":"\nimport polars as pl# --- 3. The Competition's `predict` function ---\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    This function is called by the Kaggle inference server for each test sequence.\n    It takes a Polars DataFrame and returns a single gesture string.\n    \"\"\"\n    # Convert Polars DataFrames to Pandas for easier feature engineering\n    sequence_pd = sequence.to_pandas()\n    demographics_pd = demographics.to_pandas()\n\n    # Create the feature vector for this single sequence\n    features_df = create_inference_features(sequence_pd, demographics_pd)\n\n    # Use the appropriate model based on data availability\n    is_full_data = features_df['is_full_data'].iloc[0]\n\n    if is_full_data == 1:\n        # Use the full-data model\n        X_full_test = features_df[lgb_full.feature_name_]\n        y_pred_encoded = lgb_full.predict(X_full_test)[0]\n    else:\n        # Use the IMU-only model\n        X_imu_test = features_df[lgb_imu.feature_name_]\n        y_pred_encoded = lgb_imu.predict(X_imu_test)[0]\n    \n    # Decode the prediction back to the original gesture string\n    predicted_gesture = le.inverse_transform([y_pred_encoded])[0]\n    \n    return predicted_gesture","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T09:03:23.460783Z","iopub.execute_input":"2025-08-12T09:03:23.461117Z","iopub.status.idle":"2025-08-12T09:03:23.467413Z","shell.execute_reply.started":"2025-08-12T09:03:23.461094Z","shell.execute_reply":"2025-08-12T09:03:23.466281Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nimport pandas as pd\n\nimport kaggle_evaluation.cmi_inference_server\ninference_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-08-12T09:03:25.877009Z","iopub.execute_input":"2025-08-12T09:03:25.877507Z","iopub.status.idle":"2025-08-12T09:03:26.754478Z","shell.execute_reply.started":"2025-08-12T09:03:25.877480Z","shell.execute_reply":"2025-08-12T09:03:26.753124Z"}},"outputs":[],"execution_count":null}]}