{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"7c433829-0a07-4dc3-8af0-0b5db1bb1687","cell_type":"markdown","source":"<a id=\"2\"></a>\n# <p style=\"background-color:#D8F999;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">CMI - Detect Behavior with Sensor Data</p>\n\n## INTRODUCTION\nThis notebook contains a solution for the CMI Behavior Detection competition. <br>\nThe goal is to classify different gestures based on sensor data from various<br>\nsources (accelerometer, gyroscope, thermopile, and time-of-flight sensors).<br> \n\nThe approach uses a combination of:\n1. Targeted feature engineering to extract motion patterns\n2. A simple but effective ensemble of LightGBM and XGBoost models\n3. Subject-based prediction shortcuts for faster inference\n4. Robust error handling for competition deployment\n\nThis solution is designed to be reliable and efficient in the Kaggle <br>\ncompetition environment, avoiding common issues with dependencies.","metadata":{}},{"id":"bdec6fd9-7ba8-4028-90cb-b295626ecb97","cell_type":"code","source":"import os\nimport time\nimport json\nimport pickle\nimport numpy as np\nimport pandas as pd\nfrom tqdm.auto import tqdm\nfrom scipy import stats, signal, fft\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Use pre-installed packages only - no internet usage\nimport lightgbm as lgb\nimport xgboost as xgb\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import f1_score, accuracy_score\n\n# Reproducibility\nSEED = 42\nnp.random.seed(SEED)\n\n# Paths\nBASE_PATH = '/kaggle/input/cmi-detect-behavior-with-sensor-data'\nTRAIN_PATH = f'{BASE_PATH}/train.csv'\nTEST_PATH = f'{BASE_PATH}/test.csv'\nTRAIN_DEMO_PATH = f'{BASE_PATH}/train_demographics.csv'\nTEST_DEMO_PATH = f'{BASE_PATH}/test_demographics.csv'\nMODELS_DIR = '/kaggle/working/models'\nos.makedirs(MODELS_DIR, exist_ok=True)\n\n# Configuration - simplified\nCONFIG = {\n    'train_size': 0.8,  # Percentage of data to use for training\n    'boosting_rounds': 1000  # Maximum number of boosting rounds\n}\n\nprint('Setup complete')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"26522dfc-4275-40b6-ac72-208f3403fe8e","cell_type":"markdown","source":"## <p style=\"background-color:#D8F999;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">DATA LOADING AND PREPROCESSING</p>\nThis section handles loading the competition data and preparing it for\nfeature extraction. We identify the different sensor types and convert\ncolumns to numeric format.","metadata":{}},{"id":"6fd375be-6e5f-4189-a900-43b8fc504812","cell_type":"code","source":"def load_data():\n    \"\"\"\n    Load and preprocess the competition data\n    \n    Returns:\n        dict: Dictionary containing training data, column identifiers, and\n              reference information for gesture classification\n    \"\"\"\n    print(\"Loading data...\")\n    \n    # Load CSV files\n    train_df = pd.read_csv(TRAIN_PATH)\n    train_demo_df = pd.read_csv(TRAIN_DEMO_PATH)\n    \n    # Identify sensor columns by type\n    acc_cols = [c for c in train_df.columns if c.startswith('acc_')]  # Accelerometer\n    rot_cols = [c for c in train_df.columns if c.startswith('rot_')]  # Gyroscope (rotation)\n    thm_cols = [c for c in train_df.columns if c.startswith('thm_')]  # Thermopile\n    tof_cols = [c for c in train_df.columns if c.startswith('tof_')]  # Time-of-Flight\n    \n    # Convert sensor columns to numeric values\n    for col in acc_cols + rot_cols + thm_cols + tof_cols:\n        train_df[col] = pd.to_numeric(train_df[col], errors='coerce')\n    \n    # Get gesture information\n    valid_gestures = train_df['gesture'].unique().tolist()\n    gesture_counts = train_df['gesture'].value_counts()\n    default_gesture = gesture_counts.index[0]  # Most common gesture to use as fallback\n    \n    # Create subject to gesture mapping - useful for quick predictions\n    # This maps each subject to their most common gesture\n    subj_gesture = train_df.groupby(['subject','gesture']).size().reset_index(name='count')\n    subject_top_gesture = subj_gesture.sort_values('count', ascending=False).drop_duplicates('subject')\n    subject2gesture = dict(zip(subject_top_gesture['subject'], subject_top_gesture['gesture']))\n    \n    print(f\"Data loaded: {len(valid_gestures)} gestures, {len(subject2gesture)} subjects\")\n    print(f\"Sensor types: Accelerometer ({len(acc_cols)}), Gyroscope ({len(rot_cols)}), \" +\n          f\"Thermopile ({len(thm_cols)}), ToF ({len(tof_cols)})\")\n    \n    return {\n        'train': train_df,\n        'train_demo': train_demo_df,\n        'acc_cols': acc_cols,\n        'rot_cols': rot_cols,\n        'thm_cols': thm_cols,\n        'tof_cols': tof_cols,\n        'valid_gestures': valid_gestures,\n        'default_gesture': default_gesture,\n        'subject2gesture': subject2gesture\n    }\n\n# Load the data\ndata_dict = load_data()\ntrain_df = data_dict['train']\ntrain_demo = data_dict['train_demo']\nacc_cols = data_dict['acc_cols']\nrot_cols = data_dict['rot_cols']\nthm_cols = data_dict['thm_cols']\ntof_cols = data_dict['tof_cols']\nvalid_gestures = data_dict['valid_gestures']\ndefault_gesture = data_dict['default_gesture']\nsubject2gesture = data_dict['subject2gesture']","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"3fa160fb-39f8-47f0-877f-55a5bde15cbf","cell_type":"markdown","source":"## <p style=\"background-color:#B9F8CF;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">FEATURE ENGINEERING</p>\n\nThis section defines functions to extract useful features from the raw\nsensor data. Features are designed to capture patterns that distinguish\ndifferent gestures.","metadata":{}},{"id":"26326210-2cc7-4c30-a125-2b0fcfc21d62","cell_type":"code","source":"def extract_features(seq_df, acc_cols, rot_cols, thm_cols, tof_cols):\n    \"\"\"\n    Extract essential features from a sequence of sensor readings\n    \n    Key feature types:\n    1. Basic sequence metadata (length, duration)\n    2. Statistical features for IMU axes (mean, std, range)\n    3. Magnitude-based features (combined sensor readings)\n    4. Motion pattern features (direction changes)\n    5. Sensor availability features\n    \n    Args:\n        seq_df: DataFrame containing a single sequence\n        acc_cols: List of accelerometer column names\n        rot_cols: List of gyroscope column names\n        thm_cols: List of thermopile column names\n        tof_cols: List of time-of-flight column names\n        \n    Returns:\n        dict: Dictionary of extracted features\n    \"\"\"\n    features = {}\n    \n    # 1. BASIC METADATA\n    features['seq_length'] = len(seq_df)  # Number of timestamps in sequence\n    features['seq_duration'] = seq_df['sequence_counter'].max() - seq_df['sequence_counter'].min()  # Duration\n    \n    # 2. STATISTICAL FEATURES FOR IMU AXES\n    # Process only the primary axes (x, y, z) to reduce computation\n    for col in acc_cols[:3] + rot_cols[:3]:\n        if col not in seq_df.columns:\n            continue\n            \n        # Clean the data and handle missing values\n        x = pd.to_numeric(seq_df[col], errors='coerce')\n        x = x.dropna().values\n        \n        if len(x) == 0:\n            # Default values if no data is available\n            features[f\"{col}_mean\"] = 0\n            features[f\"{col}_std\"] = 0\n            features[f\"{col}_range\"] = 0\n            continue\n            \n        # Basic statistical features\n        features[f\"{col}_mean\"] = np.mean(x)  # Average value\n        features[f\"{col}_std\"] = np.std(x)    # Variation in values\n        features[f\"{col}_range\"] = np.max(x) - np.min(x)  # Total range of movement\n        \n        # Signal pattern features\n        if len(x) > 1:\n            # Count how many times the signal crosses zero - indicates oscillation\n            features[f\"{col}_zero_crossings\"] = np.sum(np.diff(np.signbit(x).astype(int)) != 0)\n        else:\n            features[f\"{col}_zero_crossings\"] = 0\n    \n    # 3. MAGNITUDE FEATURES\n    # Acceleration magnitude - combines x, y, z components into a single value\n    if all(col in seq_df.columns for col in acc_cols[:3]):\n        try:\n            acc_x = pd.to_numeric(seq_df[acc_cols[0]], errors='coerce')\n            acc_y = pd.to_numeric(seq_df[acc_cols[1]], errors='coerce')\n            acc_z = pd.to_numeric(seq_df[acc_cols[2]], errors='coerce')\n            \n            # Compute acceleration magnitude using the Euclidean norm\n            acc_mag = np.sqrt(acc_x**2 + acc_y**2 + acc_z**2)\n            acc_mag = acc_mag.dropna().values\n            \n            if len(acc_mag) > 0:\n                features['acc_mag_mean'] = np.mean(acc_mag)  # Average magnitude\n                features['acc_mag_std'] = np.std(acc_mag)    # Variation in magnitude\n                features['acc_mag_max'] = np.max(acc_mag)    # Peak magnitude\n            else:\n                features['acc_mag_mean'] = 0\n                features['acc_mag_std'] = 0\n                features['acc_mag_max'] = 0\n        except Exception:\n            # Handle any errors during calculation\n            features['acc_mag_mean'] = 0\n            features['acc_mag_std'] = 0\n            features['acc_mag_max'] = 0\n    else:\n        features['acc_mag_mean'] = 0\n        features['acc_mag_std'] = 0\n        features['acc_mag_max'] = 0\n    \n    # Similar process for rotation (gyroscope) magnitude\n    if all(col in seq_df.columns for col in rot_cols[:3]):\n        try:\n            rot_x = pd.to_numeric(seq_df[rot_cols[0]], errors='coerce')\n            rot_y = pd.to_numeric(seq_df[rot_cols[1]], errors='coerce')\n            rot_z = pd.to_numeric(seq_df[rot_cols[2]], errors='coerce')\n            \n            rot_mag = np.sqrt(rot_x**2 + rot_y**2 + rot_z**2)\n            rot_mag = rot_mag.dropna().values\n            \n            if len(rot_mag) > 0:\n                features['rot_mag_mean'] = np.mean(rot_mag)\n                features['rot_mag_std'] = np.std(rot_mag)\n                features['rot_mag_max'] = np.max(rot_mag)\n            else:\n                features['rot_mag_mean'] = 0\n                features['rot_mag_std'] = 0\n                features['rot_mag_max'] = 0\n        except Exception:\n            features['rot_mag_mean'] = 0\n            features['rot_mag_std'] = 0\n            features['rot_mag_max'] = 0\n    else:\n        features['rot_mag_mean'] = 0\n        features['rot_mag_std'] = 0\n        features['rot_mag_max'] = 0\n    \n    # 4. MOTION PATTERN FEATURES\n    # Direction changes - critical for distinguishing gestures\n    if all(col in seq_df.columns for col in acc_cols[:3]):\n        try:\n            acc_x = pd.to_numeric(seq_df[acc_cols[0]], errors='coerce')\n            acc_y = pd.to_numeric(seq_df[acc_cols[1]], errors='coerce')\n            acc_z = pd.to_numeric(seq_df[acc_cols[2]], errors='coerce')\n            \n            # Count direction changes in each axis\n            dir_changes_x = np.sum(np.diff(np.signbit(acc_x).astype(int)) != 0)\n            dir_changes_y = np.sum(np.diff(np.signbit(acc_y).astype(int)) != 0)\n            dir_changes_z = np.sum(np.diff(np.signbit(acc_z).astype(int)) != 0)\n            \n            # Total direction changes across all axes\n            features['acc_dir_changes_total'] = dir_changes_x + dir_changes_y + dir_changes_z\n        except Exception:\n            features['acc_dir_changes_total'] = 0\n    else:\n        features['acc_dir_changes_total'] = 0\n    \n    # 5. SENSOR AVAILABILITY FEATURES\n    # Check which sensors are available for this sequence\n    has_thm = any(col in seq_df.columns for col in thm_cols) and not seq_df[thm_cols].isnull().all().all()\n    has_tof = any(col in seq_df.columns for col in tof_cols) and not seq_df[tof_cols].isnull().all().all()\n    features['has_thm'] = float(has_thm)  # 1.0 if thermopile data exists\n    features['has_tof'] = float(has_tof)  # 1.0 if time-of-flight data exists\n    features['has_full_sensors'] = float(has_thm and has_tof)  # 1.0 if both exist\n    \n    # Basic thermopile features (heat/temperature patterns)\n    if has_thm:\n        try:\n            thm_df = seq_df[thm_cols].apply(pd.to_numeric, errors='coerce')\n            features['thm_mean'] = thm_df.mean().mean()  # Average heat reading\n        except Exception:\n            features['thm_mean'] = 0\n    else:\n        features['thm_mean'] = 0\n    \n    # Basic time-of-flight features (distance measurements)\n    if has_tof:\n        try:\n            tof_df = seq_df[tof_cols].apply(pd.to_numeric, errors='coerce')\n            features['tof_mean'] = tof_df.mean().mean()  # Average distance\n        except Exception:\n            features['tof_mean'] = 0\n    else:\n        features['tof_mean'] = 0\n    \n    # Clean up any NaN values to prevent issues during model training\n    for k, v in features.items():\n        if isinstance(v, (float, int)) and (np.isnan(v) or np.isinf(v)):\n            features[k] = 0.0\n    \n    return features\n\ndef build_feature_matrix(df, acc_cols, rot_cols, thm_cols, tof_cols, demo_df=None):\n    \"\"\"\n    Build a complete feature matrix from all sequences in the dataset\n    \n    This function:\n    1. Processes each sequence to extract features\n    2. Combines all features into a single dataframe\n    3. Adds demographic information if available\n    \n    Args:\n        df: DataFrame containing all sequences\n        acc_cols, rot_cols, thm_cols, tof_cols: Lists of column names by sensor type\n        demo_df: Optional demographics dataframe\n        \n    Returns:\n        X: Feature matrix\n        y: Target labels\n        subjects: Subject IDs\n        seq_ids: Sequence IDs\n    \"\"\"\n    feature_list = []\n    seq_ids = []\n    subjects = []\n    labels = []\n    \n    # Process each sequence\n    for seq_id, seq_df in tqdm(df.groupby('sequence_id'), desc=\"Extracting features\"):\n        try:\n            # Extract features from this sequence\n            features = extract_features(seq_df, acc_cols, rot_cols, thm_cols, tof_cols)\n            feature_list.append(features)\n            \n            # Keep track of metadata\n            seq_ids.append(seq_id)\n            subject = seq_df['subject'].iloc[0] if 'subject' in seq_df.columns else None\n            subjects.append(subject)\n            \n            # Get label if available (for training data)\n            if 'gesture' in seq_df.columns:\n                labels.append(seq_df['gesture'].iloc[0])\n            else:\n                labels.append(None)\n        except Exception as e:\n            print(f\"Error processing sequence {seq_id}: {e}\")\n            continue\n    \n    # Convert list of feature dictionaries to DataFrame\n    X = pd.DataFrame(feature_list)\n    \n    # Add demographic features if available\n    if demo_df is not None and subjects[0] is not None and len(subjects) > 0:\n        subject_series = pd.Series(subjects)\n        \n        # Map demographics to each sequence\n        for col in demo_df.columns:\n            if col != 'subject':\n                # Create a mapping dictionary\n                demo_map = dict(zip(demo_df['subject'], demo_df[col]))\n                # Map to X DataFrame\n                X[f'demo_{col}'] = subject_series.map(demo_map).fillna(0)\n    \n    # Package everything\n    seq_ids = pd.Series(seq_ids, name='sequence_id')\n    subjects = pd.Series(subjects, name='subject')\n    y = pd.Series(labels, name='gesture')\n    \n    return X, y, subjects, seq_ids\n\n# Extract features from training data\nprint(\"Building feature matrix...\")\nX_train, y_train, subjects_train, seq_ids_train = build_feature_matrix(\n    train_df, acc_cols, rot_cols, thm_cols, tof_cols, train_demo\n)\n\nprint(f\"Feature matrix shape: {X_train.shape}\")\n\n# Label encoding - convert gesture names to numeric indices\nlabel_encoder = LabelEncoder()\ny_train_encoded = label_encoder.fit_transform(y_train)\nn_classes = len(label_encoder.classes_)\nprint(f\"Number of classes: {n_classes}\")\nprint(f\"Class mapping example: {label_encoder.classes_[0]} = 0, {label_encoder.classes_[1]} = 1\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"c9a84b00-7243-49fa-a83f-5e89d5f4554b","cell_type":"markdown","source":"\n## <p style=\"background-color:#96F7E4;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">DATA SPLITTING</p>\nSplit data for training and validation, ensuring subjects don't overlap\n","metadata":{}},{"id":"284c6ffb-7e12-4e57-9104-f5f9755cb247","cell_type":"code","source":"def simple_split(X, y, subjects, test_size=0.2):\n    \"\"\"\n    Split data with subject grouping to prevent data leakage\n    \n    This ensures that sequences from the same subject don't appear\n    in both training and validation sets.\n    \n    Args:\n        X: Feature matrix\n        y: Target labels\n        subjects: Subject IDs\n        test_size: Proportion of data to use for validation\n        \n    Returns:\n        train_mask, val_mask: Boolean masks for training and validation sets\n    \"\"\"\n    unique_subjects = subjects.unique()\n    n_test = int(len(unique_subjects) * test_size)\n    \n    # Random selection of test subjects\n    np.random.seed(SEED)\n    test_subjects = np.random.choice(unique_subjects, size=n_test, replace=False)\n    \n    # Create masks\n    is_test = subjects.isin(test_subjects)\n    is_train = ~is_test\n    \n    return is_train, is_test\n\n# Split data\ntrain_mask, val_mask = simple_split(X_train, y_train_encoded, subjects_train, test_size=0.2)\nX_tr, X_val = X_train[train_mask], X_train[val_mask]\ny_tr, y_val = y_train_encoded[train_mask], y_train_encoded[val_mask]\n\nprint(f\"Training on {len(X_tr)} samples, validating on {len(X_val)} samples\")\nprint(f\"Number of training subjects: {subjects_train[train_mask].nunique()}\")\nprint(f\"Number of validation subjects: {subjects_train[val_mask].nunique()}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"c117fa75-0269-432f-864d-f6b1606f4c15","cell_type":"markdown","source":"## <p style=\"background-color:#D0FAE5;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">MODEL TRAINING</p>\nTrain LightGBM and XGBoost models on the training data","metadata":{}},{"id":"cd9387d0-0b02-4c95-b71c-85f5c8edf769","cell_type":"code","source":"def train_models():\n    \"\"\"\n    Train LightGBM and XGBoost models\n    \n    This function:\n    1. Trains a LightGBM model using the native API\n    2. Trains an XGBoost model\n    3. Evaluates both models on the validation set\n    \n    Returns:\n        dict: Dictionary containing trained models\n    \"\"\"\n    lgb_models = []\n    xgb_models = []\n    \n    # Train LightGBM model\n    print(\"Training LightGBM model...\")\n    \n    # Create LightGBM datasets\n    lgb_train = lgb.Dataset(X_tr, y_tr)\n    lgb_val = lgb.Dataset(X_val, y_val, reference=lgb_train)\n    \n    # Set parameters for LightGBM\n    params = {\n        'objective': 'multiclass',   # Multiclass classification\n        'num_class': n_classes,      # Number of gesture classes\n        'learning_rate': 0.02,       # Learning rate\n        'num_leaves': 31,            # Maximum number of leaves in a tree\n        'max_depth': -1,             # No limit on tree depth\n        'verbose': -1                # Silent mode\n    }\n    \n    # Train with a fixed number of rounds\n    model = lgb.train(\n        params,\n        lgb_train,\n        num_boost_round=500,         # Fixed number of boosting rounds\n        valid_sets=[lgb_val]         # Validation set for monitoring\n    )\n    \n    lgb_models.append(model)\n    \n    # Train XGBoost model\n    print(\"Training XGBoost model...\")\n    xgb_model = xgb.XGBClassifier(\n        objective='multi:softprob',  # Multiclass classification with probabilities\n        num_class=n_classes,         # Number of gesture classes\n        n_estimators=500,            # Number of trees\n        learning_rate=0.02,          # Learning rate\n        max_depth=6,                 # Maximum tree depth\n        random_state=SEED,           # Random seed for reproducibility\n        verbosity=0                  # Silent mode\n    )\n    \n    # Train XGBoost\n    xgb_model.fit(X_tr, y_tr)\n    xgb_models.append(xgb_model)\n    \n    # Evaluate models on validation set\n    lgb_preds = model.predict(X_val)\n    lgb_pred_classes = np.argmax(lgb_preds, axis=1)\n    xgb_preds = xgb_model.predict(X_val)\n    \n    # Calculate F1 scores\n    lgb_f1 = f1_score(y_val, lgb_pred_classes, average='macro')\n    xgb_f1 = f1_score(y_val, xgb_preds, average='macro')\n    \n    print(f\"LightGBM F1: {lgb_f1:.4f}, XGBoost F1: {xgb_f1:.4f}\")\n    \n    return {\n        'lgb_models': lgb_models,\n        'xgb_models': xgb_models\n    }\n\n# Train models\nprint(\"Training models...\")\nmodels = train_models()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"a35f9860-b0fe-4b29-8b70-cf701bc740ef","cell_type":"markdown","source":"## <p style=\"background-color:#FEF3C6;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">MODEL PERSISTENCE</p>\nSave models and artifacts for inference","metadata":{}},{"id":"40a29f6e-4496-4168-9660-0ddfe18d81eb","cell_type":"code","source":"def save_models():\n    \"\"\"\n    Save models and related artifacts for later use\n    \n    This saves:\n    1. Trained models\n    2. Label encoder classes\n    3. Feature names\n    4. Default gesture and subject mappings\n    \"\"\"\n    os.makedirs(MODELS_DIR, exist_ok=True)\n    \n    # Save as a single package\n    artifacts = {\n        'models': models,\n        'label_encoder_classes': label_encoder.classes_.tolist(),\n        'feature_names': X_train.columns.tolist(),\n        'default_gesture': default_gesture,\n        'subject2gesture': subject2gesture,\n        'valid_gestures': valid_gestures\n    }\n    \n    with open(f'{MODELS_DIR}/model_artifacts.pkl', 'wb') as f:\n        pickle.dump(artifacts, f)\n    \n    # Save label encoder separately in JSON for reliability\n    with open(f'{MODELS_DIR}/label_encoder.json', 'w') as f:\n        json.dump({'classes': label_encoder.classes_.tolist()}, f)\n    \n    print(f\"Models saved to {MODELS_DIR}\")\n\nsave_models()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"8774218d-bcd1-4dc7-9f19-c1fc58536f36","cell_type":"markdown","source":"## <p style=\"background-color:#E9D4FF;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">INFERENCE PIPELINE</p>\nFunctions for making predictions on new data","metadata":{}},{"id":"dba812a8-8e5e-4557-b0f5-3a0cd9a302f1","cell_type":"code","source":"# Global variables for model artifacts\nGLOBAL_artifacts = None\n\ndef load_artifacts():\n    \"\"\"\n    Load model artifacts for inference\n    \n    This tries:\n    1. First using in-memory variables if available\n    2. Then loading from disk if needed\n    \n    Returns:\n        bool: True if artifacts loaded successfully, False otherwise\n    \"\"\"\n    global GLOBAL_artifacts\n    \n    if GLOBAL_artifacts is not None:\n        return True\n    \n    try:\n        # Try in-memory variables first\n        if 'models' in globals() and 'label_encoder' in globals():\n            GLOBAL_artifacts = {\n                'models': models,\n                'label_encoder_classes': label_encoder.classes_.tolist(),\n                'feature_names': X_train.columns.tolist(),\n                'default_gesture': default_gesture,\n                'subject2gesture': subject2gesture,\n                'valid_gestures': valid_gestures\n            }\n            return True\n            \n        # Try loading from disk\n        with open(f'{MODELS_DIR}/model_artifacts.pkl', 'rb') as f:\n            GLOBAL_artifacts = pickle.load(f)\n        return True\n    except Exception as e:\n        print(f\"Error loading artifacts: {e}\")\n        return False\n\ndef predict(sequence, demographics):\n    \"\"\"\n    Main prediction function for competition submission\n    \n    This function:\n    1. Processes the input sequence\n    2. Uses subject-based shortcut if available\n    3. Extracts features from the sequence\n    4. Makes predictions using the trained models\n    5. Returns the predicted gesture\n    \n    Args:\n        sequence: DataFrame with sensor readings\n        demographics: DataFrame with demographic information\n        \n    Returns:\n        str: Predicted gesture\n    \"\"\"\n    start_time = time.time()\n    \n    try:\n        # Convert inputs to pandas DataFrames if needed\n        try:\n            seq_df = sequence.to_pandas() if not isinstance(sequence, pd.DataFrame) else sequence\n        except Exception:\n            seq_df = pd.DataFrame(sequence)\n        \n        try:\n            demo_df = demographics.to_pandas() if not isinstance(demographics, pd.DataFrame) else demographics\n        except Exception:\n            demo_df = pd.DataFrame(demographics) if demographics is not None else None\n        \n        # Ensure models are loaded\n        if not load_artifacts():\n            return \"no_gesture\"\n        \n        # OPTIMIZATION: Subject-based shortcut\n        # If we've seen this subject before, predict their most common gesture\n        subject_id = None\n        if 'subject' in seq_df.columns and len(seq_df) > 0:\n            subject_id = seq_df['subject'].iloc[0]\n            \n            # Check if we have a mapping for this subject\n            if subject_id in GLOBAL_artifacts['subject2gesture']:\n                return GLOBAL_artifacts['subject2gesture'][subject_id]\n        \n        # Extract features from the sequence\n        sensor_cols = {\n            'acc_cols': [c for c in seq_df.columns if c.startswith('acc_')],\n            'rot_cols': [c for c in seq_df.columns if c.startswith('rot_')],\n            'thm_cols': [c for c in seq_df.columns if c.startswith('thm_')],\n            'tof_cols': [c for c in seq_df.columns if c.startswith('tof_')]\n        }\n        \n        try:\n            # Extract features from the sequence\n            features = extract_features(\n                seq_df, \n                sensor_cols['acc_cols'], \n                sensor_cols['rot_cols'],\n                sensor_cols['thm_cols'],\n                sensor_cols['tof_cols']\n            )\n            \n            # Create feature dataframe\n            X = pd.DataFrame([features])\n            \n            # Add demographic features\n            if demo_df is not None and subject_id is not None:\n                for col in demo_df.columns:\n                    if col != 'subject':\n                        demo_map = dict(zip(demo_df['subject'], demo_df[col]))\n                        X[f'demo_{col}'] = demo_map.get(subject_id, 0)\n            \n            # Match feature columns with training data\n            for col in GLOBAL_artifacts['feature_names']:\n                if col not in X.columns:\n                    X[col] = 0\n            \n            # Keep only features used in training\n            X = X[GLOBAL_artifacts['feature_names']]\n            \n            # Make predictions with LightGBM models\n            lgb_preds = np.zeros((1, len(GLOBAL_artifacts['label_encoder_classes'])))\n            for model in GLOBAL_artifacts['models']['lgb_models']:\n                lgb_preds += model.predict(X)\n            lgb_preds /= len(GLOBAL_artifacts['models']['lgb_models'])\n            \n            # Make predictions with XGBoost models\n            xgb_preds = np.zeros((1, len(GLOBAL_artifacts['label_encoder_classes'])))\n            for model in GLOBAL_artifacts['models']['xgb_models']:\n                xgb_preds += model.predict_proba(X)\n            xgb_preds /= len(GLOBAL_artifacts['models']['xgb_models'])\n            \n            # Ensemble predictions with weights\n            preds = 0.6 * lgb_preds + 0.4 * xgb_preds\n            pred_idx = np.argmax(preds, axis=1)[0]\n            \n            # Convert index to class name\n            pred_gesture = GLOBAL_artifacts['label_encoder_classes'][pred_idx]\n            \n            # Verify it's a valid gesture\n            if pred_gesture in GLOBAL_artifacts['valid_gestures']:\n                return pred_gesture\n            else:\n                # Fall back to default\n                return GLOBAL_artifacts['default_gesture']\n                \n        except Exception as e:\n            print(f\"Feature extraction or prediction error: {e}\")\n            return GLOBAL_artifacts['default_gesture']\n        \n    except Exception as e:\n        print(f\"Critical error in prediction: {e}\")\n        # Emergency fallback\n        if GLOBAL_artifacts is not None and 'default_gesture' in GLOBAL_artifacts:\n            return GLOBAL_artifacts['default_gesture']\n        else:\n            return \"no_gesture\"\n    finally:\n        # Log execution time for performance monitoring\n        elapsed = time.time() - start_time\n        if elapsed > 0.5:\n            print(f\"Prediction took {elapsed:.3f} seconds\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"06138807-ed94-45c9-b1ec-707c21cb5316","cell_type":"markdown","source":"## <p style=\"background-color:#BEDBFF;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">COMPETITION SUBMISSION</p>\nConnect to the Kaggle inference server for competition submission","metadata":{}},{"id":"1e176ca1-cede-4af4-933e-17979886cdd3","cell_type":"code","source":"if __name__ == \"__main__\":\n    try:\n        import kaggle_evaluation.cmi_inference_server\n        \n        print(\"Initializing inference server...\")\n        server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n        \n        if os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n            print(\"Running in competition environment\")\n            server.serve()\n        else:\n            print(\"Running local test\")\n            test_data_paths = (TEST_PATH, TEST_DEMO_PATH)\n            server.run_local_gateway(data_paths=test_data_paths)\n            \n    except Exception as e:\n        print(f\"Error with inference server: {e}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"2e250405-4f03-452a-b082-0d70c672d262","cell_type":"markdown","source":"## <p style=\"background-color:#D0FAE5;font-family:newtimeroman;color:#404040;font-size:150%;text-align:center;border-radius:10px 10px;\">CONCLUSION</p>\nThis solution demonstrates a practical approach to gesture recognition using <br>\nsensor data. Key takeaways:\n\n1. Feature engineering is crucial - raw sensor data needs to be transformed\n    into meaningful features that capture motion patterns\n\n 2. Ensemble methods work well for this problem - combining LightGBM and\n    XGBoost gives better performance than either model alone\n\n 3. Subject-based prediction is a powerful shortcut - people tend to make\n    consistent gestures, so using their past behavior is effective\n\n 4. Robust error handling is essential for competition submissions - the code\n    includes multiple fallback mechanisms to ensure it always returns a valid\n    prediction\n\n The solution balances performance with simplicity and robustness, making it\n suitable for practical applications beyond the competition.\n","metadata":{}}]}