{"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":"markdown","source":"# Make Baseline Model\n - Model:lightgbm\n - Validation:Stratified K-Fold\n - fold num:5","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"# SetUp","metadata":{}},{"cell_type":"code","source":"# -*- coding: utf-8 -*-\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import StratifiedKFold, StratifiedShuffleSplit\nfrom sklearn.metrics import f1_score, classification_report\nimport lightgbm as lgb\nimport os\nimport warnings\nwarnings.filterwarnings('ignore')\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import accuracy_score, f1_score\nimport joblib\nfrom functools import reduce\n\n# Import evaluation API\nimport kaggle_evaluation.cmi_inference_server\npd.set_option('display.max_rows', 500)\nrandom_seed = 42","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:11:44.947706Z","iopub.execute_input":"2025-06-14T03:11:44.948155Z","iopub.status.idle":"2025-06-14T03:11:52.523930Z","shell.execute_reply.started":"2025-06-14T03:11:44.948137Z","shell.execute_reply":"2025-06-14T03:11:52.523185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def of file paths\ndata_path = \"/kaggle/input/cmi-detect-behavior-with-sensor-data/\"\n\n# load datasets\ntrain = pl.read_csv(os.path.join(data_path, \"train.csv\"))\ntest = pl.read_csv(os.path.join(data_path, \"test.csv\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:11:52.524751Z","iopub.execute_input":"2025-06-14T03:11:52.525256Z","iopub.status.idle":"2025-06-14T03:12:03.442923Z","shell.execute_reply.started":"2025-06-14T03:11:52.525212Z","shell.execute_reply":"2025-06-14T03:12:03.442358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = train.drop([\"sequence_type\",\"orientation\",\"behavior\",\"phase\"])\n\n# check concat data\nprint(f\"Train shape: {train.shape}\")\nprint(f\"Test shape: {test.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:03.444234Z","iopub.execute_input":"2025-06-14T03:12:03.444532Z","iopub.status.idle":"2025-06-14T03:12:03.482122Z","shell.execute_reply.started":"2025-06-14T03:12:03.444513Z","shell.execute_reply":"2025-06-14T03:12:03.481603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head(1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:03.482726Z","iopub.execute_input":"2025-06-14T03:12:03.482976Z","iopub.status.idle":"2025-06-14T03:12:03.501086Z","shell.execute_reply.started":"2025-06-14T03:12:03.482957Z","shell.execute_reply":"2025-06-14T03:12:03.500552Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test.head(1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:03.501641Z","iopub.execute_input":"2025-06-14T03:12:03.501797Z","iopub.status.idle":"2025-06-14T03:12:03.507827Z","shell.execute_reply.started":"2025-06-14T03:12:03.501785Z","shell.execute_reply":"2025-06-14T03:12:03.507218Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature Enginerring","metadata":{}},{"cell_type":"code","source":"def extract_acc_features(data: pl.DataFrame) -> pl.DataFrame:\n    \"\"\"\n    閾値ベースの加速度特徴量を抽出（Polars使用、agg_exprsスタイル）。\n    \n    Parameters\n    ----------\n    data : pl.DataFrame\n        入力データ。以下のカラムが必要：\n        - sequence_id\n        - acc_x, acc_y, acc_z\n\n    Returns\n    -------\n    pl.DataFrame\n        sequence_idごとの特徴量を格納したDataFrame\n    \"\"\"\n    # ---- 前処理（特徴量計算） ----\n    data = data.with_columns([\n        # delta_acc = √(Δx² + Δy² + Δz²)\n        ((pl.col(\"acc_x\").diff().pow(2) +\n          pl.col(\"acc_y\").diff().pow(2) +\n          pl.col(\"acc_z\").diff().pow(2)).sqrt()).alias(\"delta_acc\"),\n    ])\n    data = data.with_columns([\n        # AVM = √(x² + y² + z²)\n        ((pl.col(\"acc_x\").pow(2) +\n          pl.col(\"acc_y\").pow(2) +\n          pl.col(\"acc_z\").pow(2)).sqrt()).alias(\"avm\")\n    ])\n    data = data.with_columns([\n        # 閾値フラグ\n        (pl.col(\"delta_acc\") > 0.2).cast(pl.Int8).alias(\"motion_gt_0.2g\"),\n        (pl.col(\"avm\") > 1.5).cast(pl.Int8).alias(\"high_intensity_flag\")\n    ])\n\n    # ---- 姿勢変化 GM ----\n    gm_df = (\n        data.group_by(\"sequence_id\")\n        .agg([\n            pl.mean(\"acc_x\").alias(\"mean_x\"),\n            pl.mean(\"acc_y\").alias(\"mean_y\"),\n            pl.mean(\"acc_z\").alias(\"mean_z\"),\n        ])\n        .with_columns([\n            ((pl.col(\"mean_x\").pow(2) + pl.col(\"mean_y\").pow(2) + pl.col(\"mean_z\").pow(2)).sqrt()).alias(\"gm\")\n        ])\n        .select([\"sequence_id\", \"gm\"])\n    )\n\n    # ---- 閾値ベース特徴量集約 ----\n    agg_exprs = [\n        pl.sum(\"motion_gt_0.2g\").alias(\"motion_count_gt_0.2g\"),\n        pl.sum(\"high_intensity_flag\").alias(\"high_intensity_count\"),\n        pl.mean(\"high_intensity_flag\").alias(\"high_intensity_ratio\"),\n    ]\n\n    thresh_features = data.group_by(\"sequence_id\").agg(agg_exprs)\n\n    # ---- 結合 ----\n    result = thresh_features.join(gm_df, on=\"sequence_id\", how=\"left\")\n    result = data.join(result, on= \"sequence_id\", how=\"left\")\n\n    return result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:03.508492Z","iopub.execute_input":"2025-06-14T03:12:03.508715Z","iopub.status.idle":"2025-06-14T03:12:03.521880Z","shell.execute_reply.started":"2025-06-14T03:12:03.508699Z","shell.execute_reply":"2025-06-14T03:12:03.521299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def feature_engineering_train(data: pl.DataFrame) -> pl.DataFrame:\n    target_col = \"gesture\"\n\n    # 特徴量の増加\n    data = extract_acc_features(data=data)\n\n    # 数値センサーカラムの抽出\n    stat_cols = [\n        c for c in data.columns\n        if c not in [target_col, \"sequence_id\", \"row_id\", \"sequence_counter\", \"subject\"]\n    ]\n\n    # ---- 通常の sequence_id 単位の集約 ----\n    agg_exprs = []\n    for c in stat_cols:\n        agg_exprs.extend([\n            pl.col(c).mean().alias(f\"{c}_mean\"),\n            pl.col(c).std().alias(f\"{c}_std\"),\n            pl.col(c).var().alias(f\"{c}_var\"),\n            pl.col(c).quantile(0.25).alias(f\"{c}_q25\"),\n            pl.col(c).median().alias(f\"{c}_q50\"),\n            pl.col(c).quantile(0.75).alias(f\"{c}_q75\"),\n            pl.col(c).max().alias(f\"{c}_max\"),\n            pl.col(c).min().alias(f\"{c}_min\"),\n        ])\n\n    final = data.group_by(\"sequence_id\").agg(agg_exprs)\n    final = final.join(data[[\"sequence_id\", target_col]].unique(), on=\"sequence_id\", how=\"left\")\n\n    return final","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:03.522588Z","iopub.execute_input":"2025-06-14T03:12:03.522900Z","iopub.status.idle":"2025-06-14T03:12:03.538986Z","shell.execute_reply.started":"2025-06-14T03:12:03.522878Z","shell.execute_reply":"2025-06-14T03:12:03.538484Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cleaned_data = feature_engineering_train(train)\ncleaned_data.head(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:03.539584Z","iopub.execute_input":"2025-06-14T03:12:03.539774Z","iopub.status.idle":"2025-06-14T03:12:09.243465Z","shell.execute_reply.started":"2025-06-14T03:12:03.539754Z","shell.execute_reply":"2025-06-14T03:12:09.242977Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Modeling","metadata":{}},{"cell_type":"code","source":"target_col = \"gesture\"\npdf = cleaned_data.to_pandas()  # keeps nullable dtypes\n\nle = LabelEncoder()\ny = le.fit_transform(pdf[target_col])\nX = pdf.drop(columns=[target_col, \"sequence_id\"])         # drop id + label\n\njoblib.dump(le, 'le.joblib')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:09.245633Z","iopub.execute_input":"2025-06-14T03:12:09.245843Z","iopub.status.idle":"2025-06-14T03:12:09.492846Z","shell.execute_reply.started":"2025-06-14T03:12:09.245825Z","shell.execute_reply":"2025-06-14T03:12:09.491956Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def competition_metric(y_true, y_pred, le_instance, all_original_gestures):\n    \"\"\"\n    Competition metric calculation\n    \"\"\"\n    bfrb_gestures = [g for g in all_original_gestures if g in le_instance.classes_]\n    \n    # Binary F1: All are Target in this filtered dataset\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: specific gesture classification\n    macro_f1 = f1_score(y_true, y_pred, average='macro', zero_division=0)\n    \n    final_score = (binary_f1 + macro_f1) / 2\n    return final_score, binary_f1, macro_f1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:09.493794Z","iopub.execute_input":"2025-06-14T03:12:09.494080Z","iopub.status.idle":"2025-06-14T03:12:09.499514Z","shell.execute_reply.started":"2025-06-14T03:12:09.494055Z","shell.execute_reply":"2025-06-14T03:12:09.498744Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\ncv_scores = []\nmodels = []\n\nall_original_gestures_in_train = pdf['gesture'].unique()\n\ncallbacks = [lgb.early_stopping(stopping_rounds=100, verbose=100)]\n\n# LightGBM model with cross-validation\nprint(\"\\nTraining LightGBM models with cross-validation...\")\nfor fold, (train_idx, val_idx) in enumerate(cv.split(X, y)):\n    print(f\"\\nFold {fold + 1}/5\")\n    \n    X_train_fold, X_val_fold = X.iloc[train_idx], X.iloc[val_idx]\n    y_train_fold, y_val_fold = y[train_idx], y[val_idx]\n    \n    # LightGBM model with GPU acceleration\n    model = lgb.LGBMClassifier(\n        objective='multiclass',\n        n_estimators= 1000,\n        learning_rate= 0.08,\n        max_depth= 15,\n        reg_alpha= 0.8,\n        lambda_l2= 4.0,  \n        num_leaves=31, \n        min_child_samples= 32,\n        colsample_bytree= 0.85,\n        subsample= 0.5,\n        subsample_freq=0,\n        cat_smooth=20.0,\n        is_unbalance=True,\n        max_bin=127,\n        verbose=-1,  \n        metric='multi_logloss',   \n        device='gpu',  \n    )\n\n    # Train model with verbose output\n    model.fit(\n        X_train_fold, y_train_fold,\n        eval_set=[(X_val_fold, y_val_fold)],  \n        eval_metric='multi_logloss',  \n        callbacks=callbacks\n    )\n    \n    # Predict\n    y_pred_fold = model.predict(X_val_fold)\n\n    # Calculate score\n    score, binary_f1, macro_f1 = competition_metric(\n        y_true=y_val_fold,\n        y_pred=y_pred_fold,\n        all_original_gestures=all_original_gestures_in_train,\n        le_instance=le\n    )\n    \n    cv_scores.append(score)\n    models.append(model)\n    joblib.dump(model, f'model_lgb{fold}.joblib')\n    \n    print(f\"Fold {fold + 1} - Competition Score: {score:.4f} (Binary F1: {binary_f1:.4f}, Macro F1: {macro_f1:.4f})\")\n\n    # feature importances\n    feature_importances = model.feature_importances_    \n    feature_names = X.columns    \n    feature_importance_df = pd.DataFrame({\n        'Feature': feature_names,\n        'Importance': feature_importances\n    })    \n    feature_importance_df = feature_importance_df.sort_values(by='Importance', ascending=False)    \n    print(feature_importance_df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:12:09.500366Z","iopub.execute_input":"2025-06-14T03:12:09.501012Z","iopub.status.idle":"2025-06-14T03:30:14.990753Z","shell.execute_reply.started":"2025-06-14T03:12:09.500988Z","shell.execute_reply":"2025-06-14T03:30:14.989932Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"\\nCross-validation results:\")\nprint(f\"Mean CV Score: {np.mean(cv_scores):.4f} (+/- {np.std(cv_scores) * 2:.4f})\")\nprint(f\"Individual fold scores: {cv_scores}\")\n\n# Train final model on all data with GPU acceleration\nprint(\"\\nTraining final model on all training data...\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:30:14.991612Z","iopub.execute_input":"2025-06-14T03:30:14.991880Z","iopub.status.idle":"2025-06-14T03:30:14.996553Z","shell.execute_reply.started":"2025-06-14T03:30:14.991856Z","shell.execute_reply":"2025-06-14T03:30:14.995757Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Prediction","metadata":{}},{"cell_type":"code","source":"def feature_engineering_inference(data: pl.DataFrame) -> pl.DataFrame:\n    # 特徴量の増加\n    data = extract_acc_features(data=data)\n\n    # 数値センサーカラムの抽出\n    stat_cols = [\n        c for c in data.columns\n        if c not in [\"sequence_id\", \"row_id\", \"sequence_counter\", \"subject\"]\n    ]\n\n    # ---- 通常の sequence_id 単位の集約 ----\n    agg_exprs = []\n    for c in stat_cols:\n        agg_exprs.extend([\n            pl.col(c).mean().alias(f\"{c}_mean\"),\n            pl.col(c).std().alias(f\"{c}_std\"),\n            pl.col(c).var().alias(f\"{c}_var\"),\n            pl.col(c).quantile(0.25).alias(f\"{c}_q25\"),\n            pl.col(c).median().alias(f\"{c}_q50\"),\n            pl.col(c).quantile(0.75).alias(f\"{c}_q75\"),\n            pl.col(c).max().alias(f\"{c}_max\"),\n            pl.col(c).min().alias(f\"{c}_min\"),\n        ])\n\n    final = data.group_by(\"sequence_id\").agg(agg_exprs)\n\n    return final","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:30:14.997295Z","iopub.execute_input":"2025-06-14T03:30:14.997537Z","iopub.status.idle":"2025-06-14T03:30:15.013020Z","shell.execute_reply.started":"2025-06-14T03:30:14.997522Z","shell.execute_reply":"2025-06-14T03:30:15.012296Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    cleaned_data = feature_engineering_inference(sequence)\n    pdf = cleaned_data.to_pandas().drop(columns=[\"sequence_id\"])\n    # Predict using ensemble of CV models\n    predictions = []\n    for model in models:\n        pred = model.predict(pdf)\n        # Ensure we get a scalar value\n        if isinstance(pred, np.ndarray):\n            pred = pred[0]\n        predictions.append(int(pred))\n    \n    # Use majority vote or most confident prediction\n    predicted_label_id = max(set(predictions), key=predictions.count)\n    \n    # Convert back to gesture string\n    predicted_gesture_str = le.inverse_transform([predicted_label_id])[0]\n    \n    return predicted_gesture_str","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:30:15.013830Z","iopub.execute_input":"2025-06-14T03:30:15.014060Z","iopub.status.idle":"2025-06-14T03:30:15.030593Z","shell.execute_reply.started":"2025-06-14T03:30:15.014045Z","shell.execute_reply":"2025-06-14T03:30:15.029912Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"code","source":"import os\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\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    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-14T03:30:15.031168Z","iopub.execute_input":"2025-06-14T03:30:15.031376Z","iopub.status.idle":"2025-06-14T03:30:16.305904Z","shell.execute_reply.started":"2025-06-14T03:30:15.031356Z","shell.execute_reply":"2025-06-14T03:30:16.305329Z"}},"outputs":[],"execution_count":null}]}