{"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":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# ⌚️Voting: LGBM+XGB+CB\ncopied from [jiaoyouzhang](https://www.kaggle.com/code/jiaoyouzhang/cmi-2025-only-lightgbm/notebook)","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport polars as pl\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import StratifiedKFold, StratifiedShuffleSplit\nfrom sklearn.metrics import f1_score, classification_report\nfrom sklearn.ensemble import VotingClassifier\nimport lightgbm as lgb\nimport xgboost as xgb\nimport catboost as cb\nfrom sklearn.base import BaseEstimator, ClassifierMixin\nimport os\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# 导入评估\nimport kaggle_evaluation.cmi_inference_server","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T05:18:10.069634Z","iopub.execute_input":"2025-06-08T05:18:10.069834Z","iopub.status.idle":"2025-06-08T05:18:17.983371Z","shell.execute_reply.started":"2025-06-08T05:18:10.069813Z","shell.execute_reply":"2025-06-08T05:18:17.982638Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 导入数据\ntrain_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\")\ntest_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv\")\ntest_demographics_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv\")\ntrain_demographics_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\")\n\nprint(f\"Train shape: {train_df.shape}\")\nprint(f\"Test shape: {test_df.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T05:18:17.984184Z","iopub.execute_input":"2025-06-08T05:18:17.984662Z","iopub.status.idle":"2025-06-08T05:18:50.594933Z","shell.execute_reply.started":"2025-06-08T05:18:17.984645Z","shell.execute_reply":"2025-06-08T05:18:50.594176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 仅筛选目标序列（BFRB 手势）\ntrain_df = train_df.loc[train_df['sequence_type'] == 'Target'].reset_index(drop=True)\nprint(f\"Target sequences shape: {train_df.shape}\")\n\n# 分析手势分布\nprint(\"\\nGesture distribution:\")\nprint(train_df['gesture'].value_counts())\n\n# 定义传感器列\nsensor_cols = ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z']\nall_sensor_cols = [col for col in train_df.columns if any(s in col for s in ['acc_', 'rot_', 'thm_', 'tof_'])]\nprint(f\"\\nTotal sensor columns: {len(all_sensor_cols)}\")\n\n# 缺失值统一填充为 - 1\ntrain_df[all_sensor_cols] = train_df[all_sensor_cols].fillna(-1)\n\n# 对目标gesture进行编码\nle = LabelEncoder()\ntrain_df['encoded_gesture'] = le.fit_transform(train_df['gesture'])\nprint(f\"\\nEncoded gestures: {len(le.classes_)} classes\")\nprint(\"Classes:\", le.classes_)\n\ndef create_sequence_features(df, demographics_df=None):\n    \"\"\"\n    Create comprehensive features from sensor sequences\n    \"\"\"\n    features = []\n    \n    # Group by sequence_id to create sequence-level features\n    for seq_id, group in df.groupby('sequence_id'):\n        seq_features = {'sequence_id': seq_id}\n        \n        # Basic sequence info\n        seq_features['sequence_length'] = len(group)\n        seq_features['subject'] = group['subject'].iloc[0]\n        \n        # Add demographics if available\n        if demographics_df is not None and not demographics_df.empty:\n            subject_demo = demographics_df[demographics_df['subject'] == seq_features['subject']]\n            if not subject_demo.empty:\n                seq_features['adult_child'] = subject_demo['adult_child'].iloc[0]\n                seq_features['age'] = subject_demo['age'].iloc[0]\n                seq_features['sex'] = subject_demo['sex'].iloc[0]\n                seq_features['handedness'] = subject_demo['handedness'].iloc[0]\n                seq_features['height_cm'] = subject_demo['height_cm'].iloc[0]\n                seq_features['shoulder_to_wrist_cm'] = subject_demo['shoulder_to_wrist_cm'].iloc[0]\n                seq_features['elbow_to_wrist_cm'] = subject_demo['elbow_to_wrist_cm'].iloc[0]\n            else:\n                # Set default values if demographics not found\n                seq_features['adult_child'] = -1\n                seq_features['age'] = -1\n                seq_features['sex'] = -1\n                seq_features['handedness'] = -1\n                seq_features['height_cm'] = -1\n                seq_features['shoulder_to_wrist_cm'] = -1\n                seq_features['elbow_to_wrist_cm'] = -1\n        else:\n            # Set default values if demographics not available\n            seq_features['adult_child'] = -1\n            seq_features['age'] = -1\n            seq_features['sex'] = -1\n            seq_features['handedness'] = -1\n            seq_features['height_cm'] = -1\n            seq_features['shoulder_to_wrist_cm'] = -1\n            seq_features['elbow_to_wrist_cm'] = -1\n        \n        # Behavior phase encoding (if available)\n        if 'behavior' in group.columns:\n            behavior_counts = group['behavior'].value_counts()\n            for behavior in ['Transition', 'Pause', 'Gesture']:\n                seq_features[f'{behavior.lower()}_count'] = behavior_counts.get(behavior, 0)\n                seq_features[f'{behavior.lower()}_ratio'] = behavior_counts.get(behavior, 0) / len(group)\n        else:\n            # Set default values if behavior column is not available\n            for behavior in ['Transition', 'Pause', 'Gesture']:\n                seq_features[f'{behavior.lower()}_count'] = 0\n                seq_features[f'{behavior.lower()}_ratio'] = 0\n        \n        # Statistical features for each sensor type\n        sensor_groups = {\n            'acc': ['acc_x', 'acc_y', 'acc_z'],\n            'rot': ['rot_w', 'rot_x', 'rot_y', 'rot_z'],\n            'thm': [col for col in all_sensor_cols if 'thm_' in col],\n            'tof': [col for col in all_sensor_cols if 'tof_' in col]\n        }\n        \n        for sensor_type, cols in sensor_groups.items():\n            available_cols = [col for col in cols if col in group.columns]\n            if available_cols:\n                sensor_data = group[available_cols].values\n                \n                # Basic statistics\n                seq_features[f'{sensor_type}_mean'] = np.mean(sensor_data)\n                seq_features[f'{sensor_type}_std'] = np.std(sensor_data)\n                seq_features[f'{sensor_type}_min'] = np.min(sensor_data)\n                seq_features[f'{sensor_type}_max'] = np.max(sensor_data)\n                seq_features[f'{sensor_type}_range'] = np.max(sensor_data) - np.min(sensor_data)\n                seq_features[f'{sensor_type}_median'] = np.median(sensor_data)\n                \n                # Percentiles\n                seq_features[f'{sensor_type}_q25'] = np.percentile(sensor_data, 25)\n                seq_features[f'{sensor_type}_q75'] = np.percentile(sensor_data, 75)\n                seq_features[f'{sensor_type}_iqr'] = np.percentile(sensor_data, 75) - np.percentile(sensor_data, 25)\n                \n                # Advanced statistics\n                seq_features[f'{sensor_type}_skew'] = pd.Series(sensor_data.flatten()).skew()\n                seq_features[f'{sensor_type}_kurtosis'] = pd.Series(sensor_data.flatten()).kurtosis()\n                \n                # Signal characteristics\n                seq_features[f'{sensor_type}_zero_crossings'] = np.sum(np.diff(np.sign(sensor_data.flatten())) != 0)\n                seq_features[f'{sensor_type}_energy'] = np.sum(sensor_data**2)\n                seq_features[f'{sensor_type}_rms'] = np.sqrt(np.mean(sensor_data**2))\n        \n        # Specific features for IMU data (acceleration and rotation)\n        if all(col in group.columns for col in ['acc_x', 'acc_y', 'acc_z']):\n            acc_data = group[['acc_x', 'acc_y', 'acc_z']].values\n            # Magnitude of acceleration\n            acc_magnitude = np.sqrt(np.sum(acc_data**2, axis=1))\n            seq_features['acc_magnitude_mean'] = np.mean(acc_magnitude)\n            seq_features['acc_magnitude_std'] = np.std(acc_magnitude)\n            seq_features['acc_magnitude_max'] = np.max(acc_magnitude)\n            \n        if all(col in group.columns for col in ['rot_w', 'rot_x', 'rot_y', 'rot_z']):\n            rot_data = group[['rot_w', 'rot_x', 'rot_y', 'rot_z']].values\n            # Rotation magnitude\n            rot_magnitude = np.sqrt(np.sum(rot_data**2, axis=1))\n            seq_features['rot_magnitude_mean'] = np.mean(rot_magnitude)\n            seq_features['rot_magnitude_std'] = np.std(rot_magnitude)\n            seq_features['rot_magnitude_max'] = np.max(rot_magnitude)\n        \n        # Add target if available\n        if 'encoded_gesture' in group.columns:\n            seq_features['target'] = group['encoded_gesture'].iloc[0]\n            seq_features['gesture'] = group['gesture'].iloc[0]\n        \n        features.append(seq_features)\n    \n    return pd.DataFrame(features)\n\n# Create features\nprint(\"\\nCreating sequence-level features...\")\ntrain_features = create_sequence_features(train_df, train_demographics_df)\nprint(f\"Training features shape: {train_features.shape}\")\n\n# Prepare features and target\nfeature_cols = [col for col in train_features.columns \n                if col not in ['sequence_id', 'target', 'gesture', 'subject']]\nX = train_features[feature_cols].fillna(-1)\ny = train_features['target']\n\nprint(f\"Feature matrix shape: {X.shape}\")\nprint(f\"Target shape: {y.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T05:18:50.596494Z","iopub.execute_input":"2025-06-08T05:18:50.596693Z","iopub.status.idle":"2025-06-08T05:20:15.366904Z","shell.execute_reply.started":"2025-06-08T05:18:50.596678Z","shell.execute_reply":"2025-06-08T05:20:15.366244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def competition_metric(y_true, y_pred, le_instance, all_original_gestures):\n    \"\"\"\n    Competition metric calculation\n    \"\"\"\n    bfrb_gestures = [g for g in all_original_gestures if g in le_instance.classes_]\n    \n    # Binary F1: All are Target in this filtered dataset\n    y_true_binary = np.ones_like(y_true, dtype=int)\n    y_pred_binary = np.ones_like(y_pred, dtype=int)\n    binary_f1 = f1_score(y_true_binary, y_pred_binary, average='binary', pos_label=1, zero_division=0)\n    \n    # Macro F1: specific gesture classification\n    macro_f1 = f1_score(y_true, y_pred, average='macro', zero_division=0)\n    \n    final_score = (binary_f1 + macro_f1) / 2\n    return final_score, binary_f1, macro_f1\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T05:20:15.367854Z","iopub.execute_input":"2025-06-08T05:20:15.368075Z","iopub.status.idle":"2025-06-08T05:20:15.372891Z","shell.execute_reply.started":"2025-06-08T05:20:15.368057Z","shell.execute_reply":"2025-06-08T05:20:15.372259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 创建一个包装器，使CatBoost支持与LightGBM/XGBoost一致的接口\nclass CatBoostWrapper(BaseEstimator, ClassifierMixin):\n    def __init__(self, **params):\n        self.model = cb.CatBoostClassifier(**params)\n        \n    def fit(self, X, y, eval_set=None, eval_metric=None, callbacks=None):\n        if eval_set:\n            X_val, y_val = eval_set[0]\n            self.model.fit(\n                X, y,\n                eval_set=(X_val, y_val),\n                early_stopping_rounds=100,\n                verbose=100,\n                use_best_model=True\n            )\n        else:\n            self.model.fit(X, y)\n        return self\n    \n    def predict(self, X):\n        return self.model.predict(X)\n    \n    def predict_proba(self, X):\n        return self.model.predict_proba(X)\n\n# 交叉验证设置\nprint(\"\\nSetting up cross-validation...\")\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\ncv_scores_single = {'lightgbm': [], 'xgboost': [], 'catboost': []}\ncv_scores_ensemble = []\nmodels = {'lightgbm': [], 'xgboost': [], 'catboost': [], 'ensemble': []}\n\nall_original_gestures_in_train = train_df['gesture'].unique()\n\n# 定义三种模型的参数\nlgb_params = {\n    'objective': 'multiclass',\n    'n_estimators': 1000,\n    'learning_rate': 0.08,\n    'max_depth': 15,\n    'reg_alpha': 0.8,\n    'reg_lambda': 4.0,\n    'num_leaves': 31,\n    'min_child_samples': 32,\n    'colsample_bytree': 0.3,\n    'subsample': 0.5,\n    'subsample_freq': 0,\n    'cat_smooth': 30.0,\n    'is_unbalance': True,\n    'max_bin': 127,\n    'verbose': -1,\n    'metric': 'multi_logloss',\n    'device': 'gpu',\n    'n_jobs': -1\n}\n\nxgb_params = {\n    'objective': 'multi:softprob',\n    'num_class': len(np.unique(y)),\n    'n_estimators': 1000,\n    'learning_rate': 0.08,\n    'max_depth': 15,\n    'reg_alpha': 0.8,\n    'reg_lambda': 4.0,\n    'subsample': 0.5,\n    'colsample_bytree': 0.3,\n    'min_child_weight': 32,\n    'tree_method': 'gpu_hist',\n    'gpu_id': 0,\n    'verbosity': 0,\n    'eval_metric': 'mlogloss'\n}\n\ncat_params = {\n    'objective': 'MultiClass',\n    'n_estimators': 1500,\n    'learning_rate': 0.08,\n    'max_depth': 15,\n    'reg_lambda': 4.0,\n    'subsample': 0.5,\n    'bootstrap_type': 'Bernoulli',\n    'early_stopping_rounds': 100,\n    'verbose': 100,\n    'task_type': 'GPU',\n    'devices': '0',\n    'boosting_type': 'Plain'\n}\n\n# 训练三种模型并进行集成\nprint(\"\\nTraining models with cross-validation...\")\nfor fold, (train_idx, val_idx) in enumerate(cv.split(X, y)):\n    print(f\"\\nFold {fold + 1}/5\")\n    \n    X_train_fold, X_val_fold = X.iloc[train_idx], X.iloc[val_idx]\n    y_train_fold, y_val_fold = y.iloc[train_idx], y.iloc[val_idx]\n    \n    # 1. LightGBM模型\n    print(\"\\nTraining LightGBM...\")\n    lgb_model = lgb.LGBMClassifier(**lgb_params)\n    \n    lgb_callbacks = [lgb.early_stopping(stopping_rounds=100, verbose=100)]\n    lgb_model.fit(\n        X_train_fold, y_train_fold,\n        eval_set=[(X_val_fold, y_val_fold)],\n        eval_metric='multi_logloss',\n        callbacks=lgb_callbacks\n    )\n    models['lightgbm'].append(lgb_model)\n    \n    # 2. XGBoost模型\n    print(\"\\nTraining XGBoost...\")\n    xgb_model = xgb.XGBClassifier(**xgb_params)\n    xgb_model.fit(\n        X_train_fold, y_train_fold,\n        eval_set=[(X_val_fold, y_val_fold)],\n        early_stopping_rounds=100,\n        verbose=100\n    )\n    models['xgboost'].append(xgb_model)\n    \n    # 3. CatBoost模型\n    print(\"\\nTraining CatBoost...\")\n    cat_model = CatBoostWrapper(**cat_params)\n    cat_model.fit(\n        X_train_fold, y_train_fold,\n        eval_set=[(X_val_fold, y_val_fold)]\n    )\n    models['catboost'].append(cat_model)\n    \n    # 4. 集成模型 (软投票)\n    print(\"\\nTraining Voting Ensemble...\")\n    estimators = [\n        ('lightgbm', lgb_model),\n        ('xgboost', xgb_model),\n        ('catboost', cat_model)\n    ]\n    ensemble = VotingClassifier(estimators, voting='soft', weights=[1, 1, 1])\n    ensemble.fit(X_train_fold, y_train_fold)\n    models['ensemble'].append(ensemble)\n    \n    # 评估所有模型\n    for model_name in ['lightgbm', 'xgboost', 'catboost', 'ensemble']:\n        current_model = models[model_name][-1]\n        y_pred_fold = current_model.predict(X_val_fold)\n        \n        # 计算分数\n        score, binary_f1, macro_f1 = competition_metric(\n            y_val_fold, y_pred_fold, le, all_original_gestures_in_train\n        )\n        \n        if model_name == 'ensemble':\n            cv_scores_ensemble.append(score)\n        else:\n            cv_scores_single[model_name].append(score)\n        \n        print(f\"Fold {fold + 1} - {model_name.capitalize()} Score: {score:.4f} (Binary F1: {binary_f1:.4f}, Macro F1: {macro_f1:.4f})\")\n\n# 打印交叉验证结果\nprint(\"\\nCross-validation results:\")\nfor model_name in ['lightgbm', 'xgboost', 'catboost']:\n    scores = cv_scores_single[model_name]\n    print(f\"{model_name.capitalize()} - Mean Score: {np.mean(scores):.4f} (+/- {np.std(scores) * 2:.4f})\")\n    print(f\"Individual fold scores: {scores}\")\n    print()\n\nprint(f\"Ensemble - Mean Score: {np.mean(cv_scores_ensemble):.4f} (+/- {np.std(cv_scores_ensemble) * 2:.4f})\")\nprint(f\"Individual fold scores: {cv_scores_ensemble}\")\n\n# 训练最终模型\nprint(\"\\nTraining final models on all training data...\")\n\n# 1. 训练最终的LightGBM模型\nfinal_lgb = lgb.LGBMClassifier(**lgb_params)\nfinal_lgb.fit(X, y)\n\n# 2. 训练最终的XGBoost模型\nfinal_xgb = xgb.XGBClassifier(**xgb_params)\nfinal_xgb.fit(X, y)\n\n# 3. 训练最终的CatBoost模型\nfinal_cat = CatBoostWrapper(**cat_params)\nfinal_cat.fit(X, y)\n\n# 4. 训练最终的集成模型\nfinal_ensemble = VotingClassifier(\n    [('lightgbm', final_lgb), ('xgboost', final_xgb), ('catboost', final_cat)],\n    voting='soft',\n    weights=[1, 1, 1]\n)\nfinal_ensemble.fit(X, y)\n\nprint(\"\\nAll models trained successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-08T05:20:15.373667Z","iopub.execute_input":"2025-06-08T05:20:15.373930Z","iopub.status.idle":"2025-06-08T07:50:37.210236Z","shell.execute_reply.started":"2025-06-08T05:20:15.373904Z","shell.execute_reply":"2025-06-08T07:50:37.209593Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport polars as pl\nimport numpy as np\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    改进的预测函数，增加更多错误处理和特征检查\n    \"\"\"\n    try:\n        # 转换为pandas格式\n        sequence_pd = sequence.to_pandas()\n        demographics_pd = demographics.to_pandas()\n        \n        # 确保序列数据不为空\n        if sequence_pd.empty:\n            print(\"Warning: Empty sequence data\")\n            return 'Text on phone'  # 使用默认返回值\n        \n        # 确保人口统计数据至少有一个subject\n        if demographics_pd.empty or 'subject' not in demographics_pd.columns:\n            print(\"Warning: Empty or invalid demographics data\")\n            demographics_pd = pd.DataFrame({\n                'subject': [-1],\n                'adult_child': [-1],\n                'age': [-1],\n                'sex': [-1],\n                'handedness': [-1],\n                'height_cm': [-1],\n                'shoulder_to_wrist_cm': [-1],\n                'elbow_to_wrist_cm': [-1]\n            })\n        \n        # 创建序列特征（示例实现，需要根据实际情况修改）\n        # 这里需要你实现特征工程逻辑\n        seq_features = pd.DataFrame({'dummy_feature': [0]})\n        \n        # 检查特征是否为空\n        if seq_features.empty:\n            print(\"Warning: Empty feature vector created\")\n            return 'Text on phone'  # 使用默认返回值\n        \n        # 准备特征向量（示例实现，需要根据实际情况修改）\n        # 这里需要你定义实际的特征列\n        feature_cols = ['dummy_feature']\n        X_inference = seq_features.copy()\n        \n        # 确保所有预期的特征都存在，缺失的特征用-1填充\n        for col in feature_cols:\n            if col not in X_inference.columns:\n                X_inference[col] = -1\n        X_inference = X_inference[feature_cols].fillna(-1)\n        \n        # 检查特征向量是否全为NaN\n        if X_inference.isna().all().all():\n            print(\"Warning: All features are NaN\")\n            return 'Text on phone'  # 使用默认返回值\n        \n        # 使用集成模型进行预测（示例实现，需要根据实际情况修改）\n        # 这里需要你加载实际的模型\n        predicted_label_id = 0  # 示例预测结果\n        \n        # 手势标签映射（示例实现，需要根据实际情况修改）\n        gestures = ['Text on phone', 'Cheek - pinch skin', 'Other gestures']\n        predicted_gesture_str = gestures[predicted_label_id]\n        \n        return predicted_gesture_str\n        \n    except Exception as e:\n        print(f\"Error in prediction: {e}\")\n        return 'Text on phone'  # 使用默认返回值\n\n\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-06-08T07:50:37.211252Z","iopub.execute_input":"2025-06-08T07:50:37.211555Z","iopub.status.idle":"2025-06-08T07:50:38.501469Z","shell.execute_reply.started":"2025-06-08T07:50:37.211526Z","shell.execute_reply":"2025-06-08T07:50:38.500824Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Please give me a VOTE if it helps you, and feel free to discuss if you have any questions.🌟","metadata":{}}]}