{"cells":[{"cell_type":"markdown","metadata":{},"source":"# CMI Physics-Based Model V2\\n\\n**Key Improvements from Top Solutions:**\\n1. Physics-based features (gravity removal, angular velocity)\\n2. Memory-efficient sensor splitting\\n3. Optimized ToF processing (320→20 dimensions)\\n4. LightGBM + XGBoost ensemble"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"import numpy as np\nimport pandas as pd\nimport pickle\nfrom pathlib import Path\nimport gc\nimport sys\nimport warnings\n\nwarnings.filterwarnings(\"ignore\")\n\n# Add path for inference server\nsys.path.append(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\n\nprint(\"Libraries imported successfully\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Import inference server\nfrom kaggle_evaluation.cmi_inference_server import CMIInferenceServer\n\nprint(\"CMI Inference Server imported\")"},{"cell_type":"markdown","metadata":{},"source":"## Physics-Based Feature Engineering (Top Solution Method)"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def remove_gravity_from_acc(df_seq, alpha=0.8):\n    \"\"\"重力除去加速度の計算（トップ解法の必須要素）\"\"\"\n    acc_cols = [\"acc_x\", \"acc_y\", \"acc_z\"]\n    acc_data = df_seq[acc_cols].values\n\n    # Low-pass filter for gravity estimation\n    gravity = np.zeros_like(acc_data)\n    gravity[0] = acc_data[0]\n\n    for i in range(1, len(acc_data)):\n        gravity[i] = alpha * gravity[i - 1] + (1 - alpha) * acc_data[i]\n\n    # Linear acceleration = total - gravity\n    linear_acc = acc_data - gravity\n    return linear_acc\n\n\ndef calculate_angular_velocity_from_quat(df_seq):\n    \"\"\"クォータニオンから角速度を計算（0.852スコア達成手法）\"\"\"\n    quat_cols = [\"rot_x\", \"rot_y\", \"rot_z\", \"rot_w\"]\n    quat = df_seq[quat_cols].values\n\n    # Quaternion difference\n    quat_diff = np.diff(quat, axis=0)\n\n    # Angular velocity approximation\n    angular_vel = np.zeros((len(df_seq), 3))\n    angular_vel[1:, 0] = 2 * quat_diff[:, 0]\n    angular_vel[1:, 1] = 2 * quat_diff[:, 1]\n    angular_vel[1:, 2] = 2 * quat_diff[:, 2]\n\n    return angular_vel\n\n\ndef calculate_jerk(df_seq):\n    \"\"\"ジャーク（加速度変化率）の計算\"\"\"\n    acc_cols = [\"acc_x\", \"acc_y\", \"acc_z\"]\n    acc_data = df_seq[acc_cols].values\n\n    # Jerk = derivative of acceleration\n    jerk = np.zeros_like(acc_data)\n    jerk[1:] = np.diff(acc_data, axis=0)\n\n    return jerk"},{"cell_type":"markdown","metadata":{},"source":"## Memory-Efficient Feature Extraction"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def extract_tof_statistics(df_seq):\n    \"\"\"ToFセンサーの統計量抽出（320次元→20次元）\"\"\"\n    tof_features = {}\n\n    for sensor_id in range(1, 6):\n        pixel_cols = [f\"tof_{sensor_id}_v{p}\" for p in range(64)]\n        if all(col in df_seq.columns for col in pixel_cols):\n            tof_data = df_seq[pixel_cols].replace(-1, np.nan)\n\n            # 統計量のみ保持（メモリ効率化）\n            tof_features[f\"tof_{sensor_id}_mean\"] = tof_data.mean(axis=1).mean()\n            tof_features[f\"tof_{sensor_id}_std\"] = tof_data.std(axis=1).mean()\n            tof_features[f\"tof_{sensor_id}_min\"] = tof_data.min(axis=1).mean()\n            tof_features[f\"tof_{sensor_id}_max\"] = tof_data.max(axis=1).mean()\n\n    return tof_features\n\n\ndef create_physics_features(df_seq):\n    \"\"\"物理ベース特徴量の生成（トップ解法の要素統合）\"\"\"\n    features = {}\n\n    # 1. 重力除去加速度\n    linear_acc = remove_gravity_from_acc(df_seq)\n    features[\"linear_acc_x_mean\"] = linear_acc[:, 0].mean()\n    features[\"linear_acc_y_mean\"] = linear_acc[:, 1].mean()\n    features[\"linear_acc_z_mean\"] = linear_acc[:, 2].mean()\n    features[\"linear_acc_x_std\"] = linear_acc[:, 0].std()\n    features[\"linear_acc_y_std\"] = linear_acc[:, 1].std()\n    features[\"linear_acc_z_std\"] = linear_acc[:, 2].std()\n\n    # Linear acceleration magnitude\n    linear_acc_mag = np.sqrt(np.sum(linear_acc**2, axis=1))\n    features[\"linear_acc_mag_mean\"] = linear_acc_mag.mean()\n    features[\"linear_acc_mag_std\"] = linear_acc_mag.std()\n    features[\"linear_acc_mag_max\"] = linear_acc_mag.max()\n\n    # 2. 角速度\n    angular_vel = calculate_angular_velocity_from_quat(df_seq)\n    features[\"angular_vel_x_mean\"] = angular_vel[:, 0].mean()\n    features[\"angular_vel_y_mean\"] = angular_vel[:, 1].mean()\n    features[\"angular_vel_z_mean\"] = angular_vel[:, 2].mean()\n    features[\"angular_vel_x_std\"] = angular_vel[:, 0].std()\n    features[\"angular_vel_y_std\"] = angular_vel[:, 1].std()\n    features[\"angular_vel_z_std\"] = angular_vel[:, 2].std()\n\n    # Angular velocity magnitude\n    angular_vel_mag = np.sqrt(np.sum(angular_vel**2, axis=1))\n    features[\"angular_vel_mag_mean\"] = angular_vel_mag.mean()\n    features[\"angular_vel_mag_std\"] = angular_vel_mag.std()\n\n    # 3. ジャーク\n    jerk = calculate_jerk(df_seq)\n    jerk_mag = np.sqrt(np.sum(jerk**2, axis=1))\n    features[\"jerk_mag_mean\"] = jerk_mag.mean()\n    features[\"jerk_mag_std\"] = jerk_mag.std()\n    features[\"jerk_mag_max\"] = jerk_mag.max()\n\n    # 4. 基本的な加速度統計\n    acc_mag = np.sqrt(df_seq[\"acc_x\"] ** 2 + df_seq[\"acc_y\"] ** 2 + df_seq[\"acc_z\"] ** 2)\n    features[\"acc_mag_mean\"] = acc_mag.mean()\n    features[\"acc_mag_std\"] = acc_mag.std()\n    features[\"acc_mag_max\"] = acc_mag.max()\n    features[\"acc_mag_min\"] = acc_mag.min()\n\n    # 5. 回転統計\n    features[\"rot_x_mean\"] = df_seq[\"rot_x\"].mean()\n    features[\"rot_y_mean\"] = df_seq[\"rot_y\"].mean()\n    features[\"rot_z_mean\"] = df_seq[\"rot_z\"].mean()\n    features[\"rot_w_mean\"] = df_seq[\"rot_w\"].mean()\n\n    # 6. 温度センサー（実際は5個のみ）\n    temp_cols = [f\"thm_{i}\" for i in range(1, 6)]\n    if all(col in df_seq.columns for col in temp_cols):\n        temp_data = df_seq[temp_cols]\n        features[\"temp_mean\"] = temp_data.mean().mean()\n        features[\"temp_std\"] = temp_data.std().mean()\n        features[\"temp_max\"] = temp_data.max().max()\n        features[\"temp_min\"] = temp_data.min().min()\n    else:\n        # 温度センサーがない場合のデフォルト値\n        features[\"temp_mean\"] = 0\n        features[\"temp_std\"] = 0\n        features[\"temp_max\"] = 0\n        features[\"temp_min\"] = 0\n\n    # 7. ToF統計量（メモリ効率化）\n    tof_stats = extract_tof_statistics(df_seq)\n    features.update(tof_stats)\n\n    # 8. 時系列長\n    features[\"sequence_length\"] = len(df_seq)\n\n    return features"},{"cell_type":"markdown","metadata":{},"source":"## Model Training with Ensemble"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Load training data\nprint(\"Loading training data...\")\ntrain_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\")\ntrain_demo = pd.read_csv(\n    \"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\"\n)\n\n# Get actual gesture labels from data\nactual_gestures = sorted(train_df[\"gesture\"].unique())\nprint(f\"Actual gesture classes: {actual_gestures}\")\n\nprint(f\"Data loaded: {len(train_df)} samples\")\nprint(f\"Unique sequences: {train_df['sequence_id'].nunique()}\")\nprint(f\"Number of gesture classes: {len(actual_gestures)}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Feature extraction\nprint(\"Extracting features with physics-based approach...\")\n\nX = []\ny = []\nsequence_ids = train_df[\"sequence_id\"].unique()\n\nfor i, seq_id in enumerate(sequence_ids):\n    if i % 100 == 0:\n        print(f\"Processing sequence {i}/{len(sequence_ids)}\")\n\n    # Get sequence data\n    seq_data = train_df[train_df[\"sequence_id\"] == seq_id].copy()\n\n    # Extract physics features\n    features = create_physics_features(seq_data)\n\n    # Add demographics\n    demo_data = train_demo[train_demo[\"sequence_id\"] == seq_id].iloc[0]\n    features[\"age\"] = demo_data[\"Age\"]\n    features[\"is_male\"] = 1 if demo_data[\"Gender\"] == \"M\" else 0\n\n    X.append(features)\n    y.append(seq_data[\"gesture\"].iloc[0])\n\n    # Memory cleanup\n    if i % 500 == 0:\n        gc.collect()\n\n# Convert to DataFrame\nX_df = pd.DataFrame(X)\ny_series = pd.Series(y)\n\nprint(f\"Feature extraction complete: {X_df.shape}\")\nprint(f\"Features: {list(X_df.columns)[:10]}...\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Train LightGBM model\nimport lightgbm as lgb\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelEncoder\n\n# Encode labels with actual gesture classes\nle = LabelEncoder()\nle.fit(actual_gestures)  # Use actual gestures from data\ny_encoded = le.transform(y_series)\n\nprint(f\"Number of classes: {len(le.classes_)}\")\nprint(f\"Classes: {le.classes_}\")\n\n# Split data\nX_train, X_val, y_train, y_val = train_test_split(\n    X_df, y_encoded, test_size=0.2, random_state=42, stratify=y_encoded\n)\n\nprint(f\"Training set: {X_train.shape}\")\nprint(f\"Validation set: {X_val.shape}\")\n\n# LightGBM parameters (optimized for memory)\nnum_classes = len(le.classes_)\nlgb_params = {\n    \"objective\": \"multiclass\",\n    \"num_class\": num_classes,\n    \"metric\": \"multi_logloss\",\n    \"boosting_type\": \"gbdt\",\n    \"num_leaves\": 31,  # Reduced for memory\n    \"learning_rate\": 0.05,\n    \"feature_fraction\": 0.9,\n    \"bagging_fraction\": 0.8,\n    \"bagging_freq\": 5,\n    \"verbose\": 0,\n    \"seed\": 42,\n    \"num_threads\": 2,  # Limited threads for stability\n}\n\n# Train model\nprint(\"Training LightGBM model...\")\ntrain_data = lgb.Dataset(X_train, label=y_train)\nvalid_data = lgb.Dataset(X_val, label=y_val)\n\nmodel_lgb = lgb.train(\n    lgb_params,\n    train_data,\n    valid_sets=[valid_data],\n    num_boost_round=500,\n    callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)],\n)\n\nprint(\"LightGBM training complete\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Train XGBoost model\nimport xgboost as xgb\n\nprint(\"Training XGBoost model...\")\n\nxgb_params = {\n    \"objective\": \"multi:softprob\",\n    \"num_class\": num_classes,  # Use same number of classes\n    \"max_depth\": 6,\n    \"learning_rate\": 0.05,\n    \"subsample\": 0.8,\n    \"colsample_bytree\": 0.9,\n    \"eval_metric\": \"mlogloss\",\n    \"seed\": 42,\n    \"nthread\": 2,  # Limited threads\n}\n\ndtrain = xgb.DMatrix(X_train, label=y_train)\ndval = xgb.DMatrix(X_val, label=y_val)\n\nmodel_xgb = xgb.train(\n    xgb_params,\n    dtrain,\n    num_boost_round=500,\n    evals=[(dval, \"validation\")],\n    early_stopping_rounds=50,\n    verbose_eval=100,\n)\n\nprint(\"XGBoost training complete\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Validation performance\nfrom sklearn.metrics import accuracy_score\n\n# LightGBM predictions\npred_lgb = model_lgb.predict(X_val, num_iteration=model_lgb.best_iteration)\npred_lgb_class = np.argmax(pred_lgb, axis=1)\n\n# XGBoost predictions\ndval = xgb.DMatrix(X_val)\npred_xgb = model_xgb.predict(dval)\npred_xgb_class = np.argmax(pred_xgb, axis=1)\n\n# Ensemble predictions (weighted average)\nensemble_weights = [0.6, 0.4]  # LightGBM, XGBoost\npred_ensemble = ensemble_weights[0] * pred_lgb + ensemble_weights[1] * pred_xgb\npred_ensemble_class = np.argmax(pred_ensemble, axis=1)\n\nprint(f\"LightGBM Accuracy: {accuracy_score(y_val, pred_lgb_class):.4f}\")\nprint(f\"XGBoost Accuracy: {accuracy_score(y_val, pred_xgb_class):.4f}\")\nprint(f\"Ensemble Accuracy: {accuracy_score(y_val, pred_ensemble_class):.4f}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Save models\nprint(\"Saving models...\")\n\nwith open(\"model_lgb.pkl\", \"wb\") as f:\n    pickle.dump(model_lgb, f)\n\nwith open(\"model_xgb.pkl\", \"wb\") as f:\n    pickle.dump(model_xgb, f)\n\nwith open(\"label_encoder.pkl\", \"wb\") as f:\n    pickle.dump(le, f)\n\nprint(\"Models saved successfully\")"},{"cell_type":"markdown","metadata":{},"source":"## Inference Function for CMI Server"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def predict(sequence, demographics):\n    \"\"\"\n    Physics-based prediction with ensemble model.\n    Memory-optimized for stable inference server operation.\n    \"\"\"\n    try:\n        # Convert to DataFrame\n        df_seq = pd.DataFrame(sequence)\n\n        # Extract physics-based features\n        features = create_physics_features(df_seq)\n\n        # Add demographics\n        features[\"age\"] = demographics.get(\"Age\", 30)\n        features[\"is_male\"] = 1 if demographics.get(\"Gender\", \"M\") == \"M\" else 0\n\n        # Convert to DataFrame for prediction\n        X = pd.DataFrame([features])\n\n        # LightGBM prediction\n        pred_lgb = model_lgb.predict(X, num_iteration=model_lgb.best_iteration)\n\n        # XGBoost prediction\n        dtest = xgb.DMatrix(X)\n        pred_xgb = model_xgb.predict(dtest)\n\n        # Ensemble\n        pred_ensemble = 0.6 * pred_lgb + 0.4 * pred_xgb\n\n        # Get predicted class\n        pred_class_idx = np.argmax(pred_ensemble[0])\n        pred_gesture = le.inverse_transform([pred_class_idx])[0]\n\n        # Memory cleanup\n        del df_seq, features, X, pred_lgb, pred_xgb, pred_ensemble\n        gc.collect()\n\n        return pred_gesture\n\n    except Exception as e:\n        print(f\"Prediction error: {e}\")\n        # Fallback to most common gesture from training data\n        return \"Text on phone\"  # Most common in training data\n\n\nprint(\"Prediction function defined\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Test the prediction function\nprint(\"Testing prediction function...\")\n\n# Get a sample sequence\ntest_seq_id = train_df[\"sequence_id\"].iloc[0]\ntest_seq = train_df[train_df[\"sequence_id\"] == test_seq_id].drop(\n    [\"sequence_id\", \"behavior\", \"gesture\"], axis=1\n)\ntest_demo = train_demo[train_demo[\"sequence_id\"] == test_seq_id].iloc[0].to_dict()\n\n# Test prediction\ntest_pred = predict(test_seq.to_dict(\"records\"), test_demo)\nactual_gesture = train_df[train_df[\"sequence_id\"] == test_seq_id][\"gesture\"].iloc[0]\n\nprint(f\"Test prediction: {test_pred}\")\nprint(f\"Actual gesture: {actual_gesture}\")\nprint(f\"Match: {test_pred == actual_gesture}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Start inference server\nprint(\"Starting CMI Inference Server...\")\nprint(\"Server will handle predictions for test data\")\n\nserver = CMIInferenceServer(predict)\nserver.serve()\n\nprint(\"Inference complete\")"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.0"}},"nbformat":4,"nbformat_minor":4}