{"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":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import polars as pl\nimport pandas as pd\nimport numpy as np\nimport catboost as cb  # xgboostからcatboostに変更\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\", category=UserWarning)\nwarnings.filterwarnings(\"ignore\", category=FutureWarning)\n\n# --- 1. Define Constants and Helper Functions ---\n\nBEST_CATBOOST_PARAMS = {\n    'iterations': 20_000, \n    'learning_rate': 0.01,\n    'depth': 7,\n    'subsample': 0.8,\n    'rsm': 0.7,\n    'l2_leaf_reg': 3.1613893200137406,\n    'bootstrap_type': 'Bernoulli',\n    'loss_function': 'MultiClass',\n    'eval_metric': 'MultiClass',\n    'random_seed': 42,\n    'thread_count': -1,\n    'verbose': 0,\n}\n\n\ndef create_advanced_imu_features(df: pl.DataFrame) -> pl.DataFrame:\n    \"\"\"\n    Generates advanced statistical and phase-approximated features \n    from IMU data ONLY.\n    \"\"\"\n    imu_cols = [col for col in df.columns if 'acc_' in col or 'rot_' in col]\n    aggs = []\n    \n    # 1. Whole-sequence statistical features\n    for col in imu_cols:\n        aggs.extend([\n            pl.mean(col).alias(f'{col}_mean'),\n            pl.std(col).alias(f'{col}_std'),\n            pl.max(col).alias(f'{col}_max'),\n            pl.min(col).alias(f'{col}_min'),\n            pl.quantile(col, 0.25).alias(f'{col}_q25'),\n            pl.quantile(col, 0.75).alias(f'{col}_q75'),\n        ])\n\n    # 2. Difference features\n    for col in imu_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 imu_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    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})\n    true_df['sequence_type'] = true_df['gesture'].map(sequence_type_map)\n    pred_df = pd.DataFrame({'gesture': y_pred_str})\n    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 Feature Creation ---\nprint(\"Loading training data and creating advanced IMU features...\")\ntrain_df = pl.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ntrain_features = create_advanced_imu_features(train_df)\n\ntargets = train_df.group_by(\"sequence_id\").agg(\n    pl.first('gesture'),\n    pl.first('subject'),\n    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# --- 3. Final Cross-Validation with Best Parameters ---\nprint(f\"\\n--- Starting 5-Fold CV for Final IMU-Only 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    # CatBoostモデルに変更\n    model = cb.CatBoostClassifier(**BEST_CATBOOST_PARAMS)\n    \n    # fitの早期停止はCatBoostでも同様に機能する\n    model.fit(X_train, y_train, \n              eval_set=[(X_val, y_val)], \n              early_stopping_rounds=200,\n              verbose=False)\n    \n    # CatBoostのpredictは2次元配列を返すことがあるため.flatten()を追加\n    oof_preds_cat[val_idx] = model.predict_proba(X_val)\n    preds = np.argmax(oof_preds_cat[val_idx], axis=1)\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 IMU-Only CatBoost Validation Score: {mean_score:.4f} ---\")\n\n# --- 4. Train and Save Final Model on ALL Data ---\nprint(\"\\n--- Training and Saving Final IMU-Only CatBoost Model ---\")\n# CatBoostモデルに変更\nfinal_model_imu_advanced = cb.CatBoostClassifier(**BEST_CATBOOST_PARAMS)\n\n# 全データで学習（早期停止なし、指定された'iterations'で学習）\nfinal_model_imu_advanced.fit(X, y, verbose=False)\nnp.save('oof_preds_cat_imu_only.npy', oof_preds_cat)\nprint(\"Saving model, label encoder, and feature list...\")\n# ファイル名を変更\njoblib.dump(final_model_imu_advanced, 'catboost_advanced_model.pkl')\njoblib.dump(le, 'catboost_advanced_label_encoder.pkl')\nwith open('catboost_advanced_feature_list.txt', 'w') as f:\n    for feature in X.columns:\n        f.write(f\"{feature}\\n\")\n\nprint(\"\\nTraining notebook finished successfully. Artifacts are saved.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-09T06:37:43.873613Z","iopub.execute_input":"2025-06-09T06:37:43.873963Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T11:20:12.460985Z","iopub.execute_input":"2025-06-10T11:20:12.461290Z","execution_failed":"2025-06-10T11:23:50.969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}