{"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":"code","source":"import numpy as np\nimport pandas as pd\nimport polars as pl\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import f1_score\nfrom scipy import stats\nimport os\nimport kaggle_evaluation.cmi_inference_server\n\n# Load data\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\")\n\n# Filter to Target sequences\ntrain_df = train_df.loc[train_df['sequence_type'] == 'Target'].reset_index(drop=True)\n\n# Define sensor columns\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_'])]\n\n# Improved feature engineering function\ndef create_features(df):\n    \"\"\"Create comprehensive features from sensor data\"\"\"\n    features_df = df[sensor_cols].copy()\n    \n    # Fill missing values with forward fill then backward fill, then -1\n    features_df = features_df.fillna(method='ffill').fillna(method='bfill').fillna(-1)\n    \n    # Basic statistics per sequence\n    seq_features = []\n    for seq_id in df['sequence_id'].unique():\n        seq_data = df[df['sequence_id'] == seq_id][sensor_cols]\n        \n        seq_feat = {}\n        \n        # Basic statistics\n        seq_feat.update({f'{col}_mean': seq_data[col].mean() for col in sensor_cols})\n        seq_feat.update({f'{col}_std': seq_data[col].std() for col in sensor_cols})\n        seq_feat.update({f'{col}_min': seq_data[col].min() for col in sensor_cols})\n        seq_feat.update({f'{col}_max': seq_data[col].max() for col in sensor_cols})\n        seq_feat.update({f'{col}_median': seq_data[col].median() for col in sensor_cols})\n        seq_feat.update({f'{col}_q25': seq_data[col].quantile(0.25) for col in sensor_cols})\n        seq_feat.update({f'{col}_q75': seq_data[col].quantile(0.75) for col in sensor_cols})\n        seq_feat.update({f'{col}_skew': seq_data[col].skew() for col in sensor_cols})\n        seq_feat.update({f'{col}_kurt': seq_data[col].kurtosis() for col in sensor_cols})\n        \n        # Magnitude features for accelerometer\n        seq_data_np = seq_data[['acc_x', 'acc_y', 'acc_z']].values\n        magnitude = np.sqrt(np.sum(seq_data_np**2, axis=1))\n        seq_feat['acc_magnitude_mean'] = np.mean(magnitude)\n        seq_feat['acc_magnitude_std'] = np.std(magnitude)\n        seq_feat['acc_magnitude_max'] = np.max(magnitude)\n        \n        # Rotation magnitude\n        rot_data = seq_data[['rot_x', 'rot_y', 'rot_z']].values\n        rot_magnitude = np.sqrt(np.sum(rot_data**2, axis=1))\n        seq_feat['rot_magnitude_mean'] = np.mean(rot_magnitude)\n        seq_feat['rot_magnitude_std'] = np.std(rot_magnitude)\n        \n        # Derivatives (rate of change)\n        for col in sensor_cols:\n            values = seq_data[col].values\n            if len(values) > 1:\n                diff = np.diff(values)\n                seq_feat[f'{col}_diff_mean'] = np.mean(diff)\n                seq_feat[f'{col}_diff_std'] = np.std(diff)\n                seq_feat[f'{col}_diff_max'] = np.max(np.abs(diff))\n        \n        # Zero crossing rate\n        for col in sensor_cols:\n            values = seq_data[col].values - seq_data[col].mean()\n            # Handle NaN values before sign calculation\n            values = values[~np.isnan(values)]  # Remove NaN values\n            if len(values) > 1:\n                zero_crossings = np.sum(np.diff(np.sign(values)) != 0)\n                seq_feat[f'{col}_zero_crossings'] = zero_crossings / len(values)\n            else:\n                seq_feat[f'{col}_zero_crossings'] = 0\n        \n        # Energy features\n        for col in sensor_cols:\n            seq_feat[f'{col}_energy'] = np.sum(seq_data[col].values**2)\n            seq_feat[f'{col}_rms'] = np.sqrt(np.mean(seq_data[col].values**2))\n        \n        # Sequence length\n        seq_feat['sequence_length'] = len(seq_data)\n        seq_feat['sequence_id'] = seq_id\n        \n        seq_features.append(seq_feat)\n    \n    return pd.DataFrame(seq_features)\n\n# Create features for training data\nprint(\"Creating features...\")\ntrain_features = create_features(train_df)\n\n# Add target variable\ngesture_mapping = train_df[['sequence_id', 'gesture']].drop_duplicates()\ntrain_features = train_features.merge(gesture_mapping, on='sequence_id')\n\n# Encode labels\nle = LabelEncoder()\ntrain_features['encoded_gesture'] = le.fit_transform(train_features['gesture'])\n\n# Prepare features for training\nfeature_cols = [col for col in train_features.columns if col not in ['sequence_id', 'gesture', 'encoded_gesture']]\nX = train_features[feature_cols]\ny = train_features['encoded_gesture']\n\n# Handle any infinite or NaN values\nX = X.replace([np.inf, -np.inf], np.nan)\nX = X.fillna(X.median())\n\n# Scale features\nscaler = StandardScaler()\nX_scaled = scaler.fit_transform(X)\n\ndef competition_metric(y_true, y_pred, le_instance, all_original_gestures):\n    \"\"\"Competition metric calculation\"\"\"\n    bfrb_gestures = [g for g in all_original_gestures if g in le_instance.classes_]\n    \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 = f1_score(y_true, y_pred, average='macro', zero_division=0)\n    final_score = (binary_f1 + macro_f1) / 2\n    return final_score\n\n# K-fold Cross Validation (5 folds)\nprint(\"Starting K-fold Cross Validation...\")\nkfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\nfold_scores = []\nall_original_gestures_in_train = train_df['gesture'].unique()\n\nmodels = []  # Store models from each fold\n\nfor fold, (train_idx, val_idx) in enumerate(kfold.split(X_scaled, y)):\n    print(f\"\\nFold {fold + 1}/5\")\n    \n    X_train, X_val = X_scaled[train_idx], X_scaled[val_idx]\n    y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]\n    \n    # Train model for this fold\n    rf_model = RandomForestClassifier(\n        n_estimators=200,\n        max_depth=15,\n        min_samples_split=5,\n        min_samples_leaf=2,\n        max_features='sqrt',\n        random_state=42,\n        n_jobs=-1,\n        class_weight='balanced'\n    )\n    \n    rf_model.fit(X_train, y_train)\n    models.append(rf_model)  # Store the model\n    \n    # Validation\n    y_val_pred = rf_model.predict(X_val)\n    fold_score = competition_metric(y_val, y_val_pred, le, all_original_gestures_in_train)\n    fold_scores.append(fold_score)\n    \n    print(f\"Fold {fold + 1} Score: {fold_score:.4f}\")\n\n# Calculate cross-validation statistics\ncv_mean = np.mean(fold_scores)\ncv_std = np.std(fold_scores)\nprint(f\"\\nCross-Validation Results:\")\nprint(f\"Mean Score: {cv_mean:.4f} (+/- {cv_std * 2:.4f})\")\nprint(f\"Individual Fold Scores: {[f'{score:.4f}' for score in fold_scores]}\")\n\n# Train final model on all data for inference\nprint(\"\\nTraining final model on all data...\")\nfinal_model = RandomForestClassifier(\n    n_estimators=200,\n    max_depth=15,\n    min_samples_split=5,\n    min_samples_leaf=2,\n    max_features='sqrt',\n    random_state=42,\n    n_jobs=-1,\n    class_weight='balanced'\n)\n\nfinal_model.fit(X_scaled, y)\n\n# Feature importance from final model\nfeature_importance = pd.DataFrame({\n    'feature': feature_cols,\n    'importance': final_model.feature_importances_\n}).sort_values('importance', ascending=False)\nprint(\"\\nTop 10 Most Important Features:\")\nprint(feature_importance.head(10))\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"Prediction function for inference\"\"\"\n    sequence_pd = sequence.to_pandas()\n    \n    # Create features for the single sequence\n    seq_features = []\n    seq_data = sequence_pd[sensor_cols].fillna(method='ffill').fillna(method='bfill').fillna(-1)\n    \n    seq_feat = {}\n    \n    # Apply same feature engineering as training\n    for col in sensor_cols:\n        seq_feat[f'{col}_mean'] = seq_data[col].mean()\n        seq_feat[f'{col}_std'] = seq_data[col].std()\n        seq_feat[f'{col}_min'] = seq_data[col].min()\n        seq_feat[f'{col}_max'] = seq_data[col].max()\n        seq_feat[f'{col}_median'] = seq_data[col].median()\n        seq_feat[f'{col}_q25'] = seq_data[col].quantile(0.25)\n        seq_feat[f'{col}_q75'] = seq_data[col].quantile(0.75)\n        seq_feat[f'{col}_skew'] = seq_data[col].skew()\n        seq_feat[f'{col}_kurt'] = seq_data[col].kurtosis()\n    \n    # Magnitude features\n    acc_data = seq_data[['acc_x', 'acc_y', 'acc_z']].values\n    magnitude = np.sqrt(np.sum(acc_data**2, axis=1))\n    seq_feat['acc_magnitude_mean'] = np.mean(magnitude)\n    seq_feat['acc_magnitude_std'] = np.std(magnitude)\n    seq_feat['acc_magnitude_max'] = np.max(magnitude)\n    \n    rot_data = seq_data[['rot_x', 'rot_y', 'rot_z']].values\n    rot_magnitude = np.sqrt(np.sum(rot_data**2, axis=1))\n    seq_feat['rot_magnitude_mean'] = np.mean(rot_magnitude)\n    seq_feat['rot_magnitude_std'] = np.std(rot_magnitude)\n    \n    # Derivatives\n    for col in sensor_cols:\n        values = seq_data[col].values\n        if len(values) > 1:\n            diff = np.diff(values)\n            seq_feat[f'{col}_diff_mean'] = np.mean(diff)\n            seq_feat[f'{col}_diff_std'] = np.std(diff)\n            seq_feat[f'{col}_diff_max'] = np.max(np.abs(diff))\n        else:\n            seq_feat[f'{col}_diff_mean'] = 0\n            seq_feat[f'{col}_diff_std'] = 0\n            seq_feat[f'{col}_diff_max'] = 0\n    \n    # Zero crossing rate\n    for col in sensor_cols:\n        values = seq_data[col].values - seq_data[col].mean()\n        # Handle NaN values before sign calculation\n        values = values[~np.isnan(values)]  # Remove NaN values\n        if len(values) > 1:\n            zero_crossings = np.sum(np.diff(np.sign(values)) != 0)\n            seq_feat[f'{col}_zero_crossings'] = zero_crossings / len(values)\n        else:\n            seq_feat[f'{col}_zero_crossings'] = 0\n    \n    # Energy features\n    for col in sensor_cols:\n        seq_feat[f'{col}_energy'] = np.sum(seq_data[col].values**2)\n        seq_feat[f'{col}_rms'] = np.sqrt(np.mean(seq_data[col].values**2))\n    \n    seq_feat['sequence_length'] = len(seq_data)\n    \n    # Convert to DataFrame and align with training features\n    seq_feat_df = pd.DataFrame([seq_feat])\n    \n    # Ensure all features are present and in correct order\n    for col in feature_cols:\n        if col not in seq_feat_df.columns:\n            seq_feat_df[col] = 0\n    \n    X_inference = seq_feat_df[feature_cols]\n    X_inference = X_inference.replace([np.inf, -np.inf], np.nan).fillna(0)\n    X_inference_scaled = scaler.transform(X_inference)\n    \n    predicted_label_id = final_model.predict(X_inference_scaled)[0]\n    predicted_gesture_str = le.inverse_transform([predicted_label_id])[0]\n    \n    return predicted_gesture_str\n\n# Inference server setup\ntry:\n    inference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n    \n    if os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n        inference_server.serve()\n    else:\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        )\nexcept ValueError as e:\n    if \"server has already started\" in str(e):\n        print(\"Server already running. Please restart the kernel and run again.\")\n        print(\"Go to Kernel → Restart in Jupyter notebook\")\n    else:\n        raise e","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-30T08:27:07.601383Z","iopub.execute_input":"2025-05-30T08:27:07.601682Z","iopub.status.idle":"2025-05-30T08:30:16.464746Z","shell.execute_reply.started":"2025-05-30T08:27:07.601662Z","shell.execute_reply":"2025-05-30T08:30:16.463994Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}