{"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"},{"sourceId":244295572,"sourceType":"kernelVersion"}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CMI 2025: Single LightGBM Model [Infer]\n\nThis notebook is **Part 2** of a two-notebook pipeline and is intended for submission. It is designed to be lightweight and fast, loading a pre-trained model to perform inference within the competition's time limits.\n\n## Prerequisite\n\nThis notebook **requires the output files** from the corresponding training notebook. Please add the output of the training notebook as a data source before running.\n\n**Training Notebook is here:**\n- [CMI 2025: Single LightGBM Model [Train]](https://www.kaggle.com/code/masayakawamata/cmi2025-single-lgbm-train/)\n\n## Workflow\n\n1.  **Load Artifacts**: Loads the pre-trained `final_model.pkl`, `label_encoder.pkl`, and `feature_list.txt` from the training notebook's output.\n2.  **Define Feature Function**: Defines the *exact same* `create_features` function that was used in training.\n3.  **Predict**: The `predict` function is called for each test sequence. It generates features for the sequence, uses the loaded model to make a prediction, and converts the prediction into the final gesture label.","metadata":{}},{"cell_type":"code","source":"import os, joblib, polars as pl, pandas as pd, numpy as np\nimport lightgbm as lgb\nfrom sklearn.preprocessing import LabelEncoder\nimport kaggle_evaluation.cmi_inference_server\nimport warnings\n\n# Suppress warnings\nwarnings.filterwarnings(\"ignore\", category=UserWarning)\n\n# --- 1. Load Pre-trained Artifacts ---\nINPUT_DIR = '/kaggle/input/cmi2025-single-lgbm-train' \n\nMODEL_PATH = os.path.join(INPUT_DIR, 'imu_advanced_model.pkl')\nLE_PATH = os.path.join(INPUT_DIR, 'imu_advanced_label_encoder.pkl')\nFEATURES_PATH = os.path.join(INPUT_DIR, 'imu_advanced_feature_list.txt')\n\nprint(\"Loading pre-trained model and assets for IMU-Only model...\")\nfinal_model = joblib.load(MODEL_PATH)\nle = joblib.load(LE_PATH)\nwith open(FEATURES_PATH, 'r') as f:\n    feature_list = [line.strip() for line in f.readlines()]\nprint(\"Assets loaded successfully.\")\n\n\n# --- 2. Define Feature Engineering Function (must be identical to training) ---\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\n# --- 3. Define Predict Function ---\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    Receives a single test sequence and predicts the gesture.\n    \"\"\"\n    # Create features from the input sequence using the advanced function\n    feature_df = create_advanced_imu_features(sequence)\n    \n    # Convert to pandas and ensure column order matches the training data\n    feature_df_pd = feature_df.drop('sequence_id').to_pandas()\n    X_test = feature_df_pd[feature_list] \n    \n    # Predict the class index\n    pred_idx = final_model.predict(X_test)\n    \n    # Convert the index back to the original string label\n    pred_label = le.inverse_transform(pred_idx)[0]\n    \n    return pred_label\n\n# --- 4. Start the Inference Server ---\nprint(\"Starting inference server...\")\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    # This branch runs when you submit the notebook\n    inference_server.serve()\nelse:\n    # This branch runs for local testing in the notebook editor\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":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-08T01:36:47.554800Z","iopub.execute_input":"2025-06-08T01:36:47.555132Z","iopub.status.idle":"2025-06-08T01:37:02.384374Z","shell.execute_reply.started":"2025-06-08T01:36:47.555104Z","shell.execute_reply":"2025-06-08T01:37:02.383460Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}