{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# =================================================================\n# ■■■ Notebook: All-Sensors CatBoost Training (Corrected) ■■■\n# =================================================================\nimport polars as pl\nimport pandas as pd\nimport numpy as np\nimport catboost as cb\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.metrics import f1_score\nimport joblib\nimport warnings\n\n# Suppress warnings for cleaner output\nwarnings.filterwarnings(\"ignore\")\n\n# --- 1. Define Constants and Helper Functions ---\nBEST_CATBOOST_PARAMS = {\n    'iterations': 10000, 'learning_rate': 0.02, 'depth': 7,\n    'subsample': 0.8, 'rsm': 0.7, 'l2_leaf_reg': 3.0,\n    'bootstrap_type': 'Bernoulli', 'loss_function': 'MultiClass',\n    'eval_metric': 'MultiClass', 'random_seed': 42, 'thread_count': -1,\n}\n\n# This function now works on the pre-aggregated dataframe\ndef create_final_features(df: pl.DataFrame) -> pl.DataFrame:\n    \"\"\"Generates final sequence-level features from pre-aggregated sensor data.\"\"\"\n    \n    # ★★★★★ FIX IS HERE: Select ONLY numeric sensor columns for feature generation ★★★★★\n    feature_cols = [col for col in df.columns if any(prefix in col for prefix in ['acc_', 'rot_', 'thm_', 'tof_'])]\n    aggs = []\n    \n    # 1. Whole-sequence statistical features\n    for col in feature_cols:\n        aggs.extend([\n            pl.mean(col).alias(f'{col}_mean'),\n            pl.std(col).alias(f'{col}_std'),\n        ])\n\n    # 2. Difference features\n    for col in feature_cols:\n        aggs.extend([\n            (pl.col(col).diff().fill_null(0)).mean().alias(f'{col}_diff_mean'),\n            (pl.col(col).diff().fill_null(0)).std().alias(f'{col}_diff_std'),\n        ])\n        \n    # 3. Phase-approximated features\n    for part_name, part_expr in [\n        ('first_30pct', pl.col('sequence_counter') < pl.max('sequence_counter') * 0.3),\n        ('middle_40pct', (pl.col('sequence_counter') >= pl.max('sequence_counter') * 0.3) & (pl.col('sequence_counter') < pl.max('sequence_counter') * 0.7)),\n        ('last_30pct', pl.col('sequence_counter') >= pl.max('sequence_counter') * 0.7),\n    ]:\n        for col in feature_cols:\n            aggs.extend([\n                (pl.when(part_expr).then(pl.col(col))).mean().alias(f'{col}_mean_{part_name}'),\n                (pl.when(part_expr).then(pl.col(col))).std().alias(f'{col}_std_{part_name}'),\n            ])\n    \n    feature_df = df.group_by('sequence_id').agg(aggs).fill_null(0)\n    return feature_df\n\ndef competition_metric(y_true, y_pred, sequence_type_map, le):\n    \"\"\"Calculates the official competition metric.\"\"\"\n    # (The rest of this function is unchanged)\n    y_pred = y_pred.flatten()\n    y_true_str = le.inverse_transform(y_true)\n    y_pred_str = le.inverse_transform(y_pred)\n    true_df = pd.DataFrame({'gesture': y_true_str}); true_df['sequence_type'] = true_df['gesture'].map(sequence_type_map)\n    pred_df = pd.DataFrame({'gesture': y_pred_str}); pred_df['sequence_type'] = pred_df['gesture'].map(sequence_type_map)\n    binary_f1 = f1_score(true_df['sequence_type'], pred_df['sequence_type'], pos_label='Target', zero_division=0)\n    true_df.loc[true_df['sequence_type'] == 'Non-Target', 'gesture'] = 'non_target'\n    pred_df.loc[pred_df['sequence_type'] == 'Non-Target', 'gesture'] = 'non_target'\n    all_labels = np.union1d(true_df['gesture'].unique(), pred_df['gesture'].unique())\n    macro_f1 = f1_score(true_df['gesture'], pred_df['gesture'], average='macro', labels=all_labels, zero_division=0)\n    return (binary_f1 + macro_f1) / 2\n    \n# --- 2. Data Loading and Hierarchical Feature Engineering ---\nprint(\"Loading training data...\")\ntrain_df = pl.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\n\n# KEY STEP 1: Filter for 'All_sensors' data ONLY\nall_sensors_ids = train_df.filter(pl.col('thm_1').is_not_null())['sequence_id'].unique()\ntrain_df = train_df.filter(pl.col('sequence_id').is_in(all_sensors_ids))\nprint(f\"Filtered data to {len(train_df)} rows for 'All-Sensors' model.\")\n\n# KEY STEP 2: Aggregate ToF pixels for each of the 5 sensors\nprint(\"Performing hierarchical feature engineering for ToF sensors...\")\nfor i in range(1, 6): # Loop through ToF sensors 1 to 5\n    tof_cols_for_sensor_i = [f'tof_{i}_v{j}' for j in range(64)]\n    train_df = train_df.with_columns([\n        pl.concat_list([pl.col(c) for c in tof_cols_for_sensor_i]).list.mean().alias(f'tof_{i}_pixels_mean'),\n        pl.concat_list([pl.col(c) for c in tof_cols_for_sensor_i]).list.std().alias(f'tof_{i}_pixels_std'),\n        pl.concat_list([pl.col(c) for c in tof_cols_for_sensor_i]).list.max().alias(f'tof_{i}_pixels_max'),\n        pl.concat_list([pl.col(c) for c in tof_cols_for_sensor_i]).list.min().alias(f'tof_{i}_pixels_min'),\n    ])\n\n# Now, drop the original 320 ToF columns\noriginal_tof_cols = [c for c in train_df.columns if 'tof_' in c and '_pixels_' not in c]\ntrain_df_preprocessed = train_df.drop(original_tof_cols)\n\n# --- 3. Final Feature Creation ---\nprint(\"Creating final sequence-level features...\")\ntrain_features = create_final_features(train_df_preprocessed)\ntargets = train_df_preprocessed.group_by(\"sequence_id\").agg(\n    pl.first('gesture'), pl.first('subject'), pl.first('sequence_type')\n)\ntrain_features_full = train_features.join(targets, on='sequence_id', how='left')\ntrain_features_pd = train_features_full.to_pandas()\n\n# --- 4. Cross-Validation and Training ---\nprint(f\"\\n--- Starting 5-Fold CV for All-Sensors CatBoost Model ---\")\nprint(f\"Number of features: {len(train_features_pd.columns) - 4}\")\n\nX = train_features_pd.drop(columns=['sequence_id', 'gesture', 'subject', 'sequence_type'])\ny_str = train_features_pd['gesture']\ngroups = train_features_pd['subject']\nle = LabelEncoder()\ny = le.fit_transform(y_str)\ngesture_to_type_map = train_features_pd.drop_duplicates('gesture').set_index('gesture')['sequence_type'].to_dict()\n\nsgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=42)\nval_scores = []\noof_preds_cat = np.zeros((len(X), 18)) # 18 is the number of classes\n\nfor fold, (train_idx, val_idx) in enumerate(sgkf.split(X, y, groups=groups)):\n    print(f\"\\n--- Fold {fold+1}/5 ---\")\n    X_train, y_train = X.iloc[train_idx], y[train_idx]\n    X_val, y_val = X.iloc[val_idx], y[val_idx]\n    \n    model = cb.CatBoostClassifier(**BEST_CATBOOST_PARAMS)\n    model.fit(X_train, y_train, eval_set=[(X_val, y_val)], early_stopping_rounds=200, verbose=False)\n    oof_preds_cat[val_idx] = model.predict_proba(X_val)\n    \n    preds = model.predict(X_val).flatten()\n    score = competition_metric(y_val, preds, gesture_to_type_map, le)\n    val_scores.append(score)\n    print(f\"Fold {fold+1} Score: {score:.4f}\")\n\nmean_score = np.mean(val_scores)\nprint(f\"\\n--- Average Final All-Sensors CatBoost Score: {mean_score:.4f} ---\")\nnp.save('oof_preds_cat_all_sensors.npy', oof_preds_cat)\n\n\n# --- 5. Train and Save Final Model ---\nprint(\"\\n--- Training and Saving Final All-Sensors CatBoost Model ---\")\nfinal_model_all_sensors = cb.CatBoostClassifier(**BEST_CATBOOST_PARAMS)\nfinal_model_all_sensors.fit(X, y, verbose=False)\njoblib.dump(final_model_all_sensors, 'catboost_all_sensors_model.pkl')\njoblib.dump(le, 'catboost_label_encoder.pkl') # Save encoder\nwith open('catboost_feature_list.txt', 'w') as f:\n    for feature in X.columns: f.write(f\"{feature}\\n\")\n\nprint(\"\\nTraining notebook finished successfully.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}