{"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":"# **🧩 Cell 1: Imports**","metadata":{}},{"cell_type":"code","source":"# === Core Libraries ===\nimport numpy as np\nimport pandas as pd\nimport os\nimport kaggle_evaluation.cmi_inference_server\n\n# === ML/DL Libraries ===\nimport tensorflow as tf\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix\nfrom sklearn.preprocessing import LabelEncoder\n\n# === Visualization ===\nimport matplotlib.pyplot as plt\nimport seaborn as sns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:19:24.203547Z","iopub.execute_input":"2025-06-23T09:19:24.203831Z","iopub.status.idle":"2025-06-23T09:19:38.592412Z","shell.execute_reply.started":"2025-06-23T09:19:24.203810Z","shell.execute_reply":"2025-06-23T09:19:38.591629Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **📥 Cell 2: Constants and Feature Groups**","metadata":{}},{"cell_type":"code","source":"# Feature sets\nsequence_features = ['sequence_counter']\naccel_features = ['acc_x', 'acc_y', 'acc_z']\nrot_features = ['rot_w', 'rot_x', 'rot_y', 'rot_z']\nthm_features = ['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5']\ntof_features = [f\"tof_{i}_v{j}\" for i in range(1, 6) for j in range(64)]\ndemo_features = ['adult_child', 'age', 'sex', 'handedness', 'height_cm',\n                 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']\n\n# Label encoding\ngesture_mapping = {\n    \"Cheek - pinch skin\": 1,\n    \"Forehead - pull hairline\": 2,\n    \"Neck - scratch\": 3,\n    \"Neck - pinch skin\": 4,\n    \"Eyelash - pull hair\": 5,\n    \"Eyebrow - pull hair\": 6,\n    \"Forehead - scratch\": 7,\n    \"Above ear - pull hair\": 8,\n    \"Non-Target\": 0\n}\ninv_gesture_mapping = {v: k for k, v in gesture_mapping.items()}\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:19:38.593568Z","iopub.execute_input":"2025-06-23T09:19:38.594212Z","iopub.status.idle":"2025-06-23T09:19:38.599468Z","shell.execute_reply.started":"2025-06-23T09:19:38.594191Z","shell.execute_reply":"2025-06-23T09:19:38.598739Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🧹 Cell 3: Data Loading and Preprocessing**","metadata":{}},{"cell_type":"code","source":"def load_and_merge():\n    train = pd.read_csv(\"../input/cmi-detect-behavior-with-sensor-data/train.csv\")\n    train = train[train.phase == \"Gesture\"]\n    train_demo = pd.read_csv(\"../input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\")\n    return train.merge(train_demo, on=\"subject\", how=\"left\")\n\ndef encode_target(df):\n    df[\"encoded_gesture\"] = df[\"gesture\"].map(gesture_mapping).fillna(0).astype(int)\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:19:38.600287Z","iopub.execute_input":"2025-06-23T09:19:38.600730Z","iopub.status.idle":"2025-06-23T09:19:38.828674Z","shell.execute_reply.started":"2025-06-23T09:19:38.600708Z","shell.execute_reply":"2025-06-23T09:19:38.827900Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **📐 Cell 4: Feature Extraction**","metadata":{}},{"cell_type":"code","source":"def extract_time_series(df, group_col=\"sequence_id\"):\n    X_seq, X_accel, X_rot, X_thm, X_tof, y, demo_all = [], [], [], [], [], [], []\n\n    grouped = df.groupby(group_col)\n    for _, group in grouped:\n        if len(group) < 21:\n            continue\n        group = group.tail(21)  # Fixed-length sequences\n\n        X_seq.append(group[sequence_features].to_numpy())\n        X_accel.append(group[accel_features].to_numpy())\n        X_rot.append(group[rot_features].to_numpy())\n        X_thm.append(group[thm_features].to_numpy())\n        X_tof.append(group[tof_features].to_numpy())\n        demo_all.append(group[demo_features].iloc[0].to_numpy())\n        y.append(group[\"encoded_gesture\"].iloc[0])\n\n    return {\n        \"X_seq\": np.array(X_seq),\n        \"X_accel\": np.array(X_accel),\n        \"X_rot\": np.array(X_rot),\n        \"X_thm\": np.array(X_thm),\n        \"X_tof\": np.array(X_tof),\n        \"X_demo\": np.array(demo_all),\n        \"y\": np.array(y)\n    }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:19:38.830783Z","iopub.execute_input":"2025-06-23T09:19:38.831041Z","iopub.status.idle":"2025-06-23T09:19:38.845590Z","shell.execute_reply.started":"2025-06-23T09:19:38.831015Z","shell.execute_reply":"2025-06-23T09:19:38.844938Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🏗️ Cell 5: Build CNN-LSTM + MLP Hybrid Model**","metadata":{}},{"cell_type":"code","source":"def build_model(input_shapes, demo_dim, num_classes=9):\n    inputs = []\n    lstm_outputs = []\n\n    for name in ['X_seq', 'X_accel', 'X_rot', 'X_thm', 'X_tof']:\n        inp = tf.keras.Input(shape=input_shapes[name], name=name)\n        x = tf.keras.layers.Conv1D(32, 3, activation='relu', padding='same')(inp)\n        x = tf.keras.layers.MaxPooling1D(2)(x)\n        x = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32))(x)\n        inputs.append(inp)\n        lstm_outputs.append(x)\n\n    demo_input = tf.keras.Input(shape=(demo_dim,), name=\"X_demo\")\n    x_demo = tf.keras.layers.Dense(32, activation='relu')(demo_input)\n    x_demo = tf.keras.layers.Dense(16, activation='relu')(x_demo)\n    inputs.append(demo_input)\n\n    merged = tf.keras.layers.Concatenate()(lstm_outputs + [x_demo])\n    x = tf.keras.layers.Dense(64, activation='relu')(merged)\n    x = tf.keras.layers.Dropout(0.3)(x)\n    out = tf.keras.layers.Dense(num_classes, activation='softmax')(x)\n\n    model = tf.keras.Model(inputs=inputs, outputs=out)\n    model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:19:38.846270Z","iopub.execute_input":"2025-06-23T09:19:38.846513Z","iopub.status.idle":"2025-06-23T09:19:38.865236Z","shell.execute_reply.started":"2025-06-23T09:19:38.846490Z","shell.execute_reply":"2025-06-23T09:19:38.864661Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🚂 Cell 6: Prepare Data and Train**","metadata":{}},{"cell_type":"code","source":"# Load and prepare data\ndf = load_and_merge()\ndf = encode_target(df)\ndata = extract_time_series(df)\n\n# Split features\nX = {k: data[k] for k in data if k != \"y\"}\ny = data[\"y\"]\nX_train = {k: v[:int(0.8*len(y))] for k, v in X.items()}\nX_val = {k: v[int(0.8*len(y)):] for k, v in X.items()}\ny_train = y[:int(0.8*len(y))]\ny_val = y[int(0.8*len(y)):]\n\n# Build and train model\ninput_shapes = {k: X[k].shape[1:] for k in ['X_seq', 'X_accel', 'X_rot', 'X_thm', 'X_tof']}\ndemo_dim = X['X_demo'].shape[1]\nmodel = build_model(input_shapes, demo_dim)\nmodel.summary()\n\n# Train\nhistory = model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=30, batch_size=32)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:19:38.866083Z","iopub.execute_input":"2025-06-23T09:19:38.866331Z","iopub.status.idle":"2025-06-23T09:23:04.905056Z","shell.execute_reply.started":"2025-06-23T09:19:38.866309Z","shell.execute_reply":"2025-06-23T09:23:04.904286Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **📊 Cell 7: Training Curves**","metadata":{}},{"cell_type":"code","source":"def plot_training_history(history):\n    plt.figure(figsize=(14, 5))\n\n    plt.subplot(1, 2, 1)\n    plt.plot(history.history['accuracy'], label='Train Acc')\n    plt.plot(history.history['val_accuracy'], label='Val Acc')\n    plt.title('Model Accuracy')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.legend()\n    plt.grid(True)\n\n    plt.subplot(1, 2, 2)\n    plt.plot(history.history['loss'], label='Train Loss')\n    plt.plot(history.history['val_loss'], label='Val Loss')\n    plt.title('Model Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.legend()\n    plt.grid(True)\n\n    plt.tight_layout()\n    plt.show()\n\n# Call it\nplot_training_history(history)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:23:04.906390Z","iopub.execute_input":"2025-06-23T09:23:04.906588Z","iopub.status.idle":"2025-06-23T09:23:05.349852Z","shell.execute_reply.started":"2025-06-23T09:23:04.906573Z","shell.execute_reply":"2025-06-23T09:23:05.348933Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **📈 Cell 8: Classification Report + Confusion Matrix**","metadata":{}},{"cell_type":"code","source":"def plot_classification_report(y_true, y_pred, class_names):\n    print(classification_report(y_true, y_pred, target_names=class_names))\n    cm = confusion_matrix(y_true, y_pred)\n    plt.figure(figsize=(10, 8))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n                xticklabels=class_names, yticklabels=class_names)\n    plt.xlabel(\"Predicted\")\n    plt.ylabel(\"True\")\n    plt.title(\"Confusion Matrix\")\n    plt.xticks(rotation=45)\n    plt.tight_layout()\n    plt.show()\n\n# Predict\ny_pred = np.argmax(model.predict(X_val), axis=1)\nplot_classification_report(y_val, y_pred, class_names=list(gesture_mapping.keys()))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:23:05.351077Z","iopub.execute_input":"2025-06-23T09:23:05.351321Z","iopub.status.idle":"2025-06-23T09:23:09.154402Z","shell.execute_reply.started":"2025-06-23T09:23:05.351299Z","shell.execute_reply":"2025-06-23T09:23:09.153551Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **💾 Cell 9: Save Model**","metadata":{}},{"cell_type":"code","source":"model.save(\"hybrid_cnn_lstm_model.h5\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:23:09.155308Z","iopub.execute_input":"2025-06-23T09:23:09.155834Z","iopub.status.idle":"2025-06-23T09:23:09.296009Z","shell.execute_reply.started":"2025-06-23T09:23:09.155805Z","shell.execute_reply":"2025-06-23T09:23:09.295410Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🔮 Cell 10: Inference Function for Submissions**","metadata":{}},{"cell_type":"code","source":"def predict(sequence, demographics) -> str:\n    # ✅ Ensure pandas DataFrames\n    if not isinstance(sequence, pd.DataFrame):\n        sequence = sequence.to_pandas()\n    if not isinstance(demographics, pd.DataFrame):\n        demographics = demographics.to_pandas()\n\n    # Merge demographics\n    sequence = sequence.merge(demographics, on=\"subject\", how=\"left\")\n    sequence = sequence.tail(21)\n\n    # Feature extraction\n    X_input = {\n        'X_seq': sequence[sequence_features].to_numpy()[None, :],\n        'X_accel': sequence[accel_features].to_numpy()[None, :],\n        'X_rot': sequence[rot_features].to_numpy()[None, :],\n        'X_thm': sequence[thm_features].to_numpy()[None, :],\n        'X_tof': sequence[tof_features].to_numpy()[None, :],\n        'X_demo': sequence[demo_features].iloc[0].to_numpy()[None, :]\n    }\n\n    # Load model and predict\n    model = tf.keras.models.load_model(\"hybrid_cnn_lstm_model.h5\")\n    pred = np.argmax(model.predict(X_input), axis=1)[0]\n    return \"Text on phone\" if pred == 0 else inv_gesture_mapping.get(pred, \"Text on phone\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:23:09.298099Z","iopub.execute_input":"2025-06-23T09:23:09.298317Z","iopub.status.idle":"2025-06-23T09:23:09.304315Z","shell.execute_reply.started":"2025-06-23T09:23:09.298300Z","shell.execute_reply":"2025-06-23T09:23:09.303738Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🧠 Cell 11: Inference Server Integration (Kaggle-Compatible)**","metadata":{}},{"cell_type":"code","source":"\n\n# Initialize the inference server using your predict function\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\n# Automatically switch between local gateway and competition rerun\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    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T09:23:09.304964Z","iopub.execute_input":"2025-06-23T09:23:09.305181Z","iopub.status.idle":"2025-06-23T09:23:14.000610Z","shell.execute_reply.started":"2025-06-23T09:23:09.305165Z","shell.execute_reply":"2025-06-23T09:23:13.999875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}