{"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\nwarnings.filterwarnings('ignore')\n\n# Inference server import\nsys.path.append('/kaggle/input/cmi-detect-behavior-with-sensor-data')\nfrom kaggle_evaluation.cmi_inference_server import CMIInferenceServer\n\nprint('Libraries imported successfully')"},{"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\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\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\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. 温度センサー\n    temp_cols = [f'thm_{i}' for i in range(1, 17)]\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    \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('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\n\n# Gesture labels (18 classes)\ngesture_labels = [\n    'Wave hello', 'Put palms together', 'Touch face', 'Sit down',\n    'Stand up', 'Cough', 'Sneeze', 'Hop', 'Jump', 'Turn in place',\n    'Walk', 'Shake head', 'Nod head', 'Jog', 'Jumping jacks',\n    'Text on phone', 'Phone to ear', 'Eat'\n]\n\nprint(f'Data loaded: {len(train_df)} samples')\nprint(f'Unique sequences: {train_df[\"sequence_id\"].nunique()}')\nprint(f'Gesture distribution: {train_df[\"gesture\"].value_counts().to_dict()}')"},{"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\nle = LabelEncoder()\nle.fit(gesture_labels)\ny_encoded = le.transform(y_series)\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)\nlgb_params = {\n    'objective': 'multiclass',\n    'num_class': 18,\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=[\n        lgb.early_stopping(50),\n        lgb.log_evaluation(100)\n    ]\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': 18,\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\n        return 'Walk'\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(['sequence_id', 'behavior', 'gesture'], axis=1)\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}