{"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"},{"sourceId":242954653,"sourceType":"kernelVersion"}],"dockerImageVersionId":31041,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **📦 Cell 1: Core & ML Library Imports**","metadata":{}},{"cell_type":"code","source":"################### Core Libraries #####################\nimport numpy as np\nimport pandas as pd\nimport os\nimport random\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\n\n################## Kaggle Evaluation ####################\nimport kaggle_evaluation.cmi_inference_server","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🔁 Cell 2: Reproducibility Configuration**","metadata":{}},{"cell_type":"code","source":"# Set seeds for reproducibility\nseed = 1\nos.environ['PYTHONHASHSEED'] = str(seed)\nrandom.seed(seed)\nnp.random.seed(seed)\ntf.random.set_seed(seed)\n\n# Configure TensorFlow for deterministic behavior\nos.environ['TF_DETERMINISTIC_OPS'] = '1'\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **📋 Cell 3: Feature Definitions and Label Mappings**","metadata":{}},{"cell_type":"code","source":"# Define sensor and demographic feature lists\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# Gesture label mappings\ngesture_mapping = {\n    \"Above ear - pull hair\": 0, \"Cheek - pinch skin\": 1, \"Drink from bottle/cup\": 2,\n    \"Eyebrow - pull hair\": 3, \"Eyelash - pull hair\": 4, \"Feel around in tray and pull out an object\": 5,\n    \"Forehead - pull hairline\": 6, \"Forehead - scratch\": 7, \"Glasses on/off\": 8,\n    \"Neck - pinch skin\": 9, \"Neck - scratch\": 10, \"Pinch knee/leg skin\": 11,\n    \"Pull air toward your face\": 12, \"Scratch knee/leg skin\": 13, \"Text on phone\": 14,\n    \"Wave hello\": 15, \"Write name in air\": 16, \"Write name on leg\": 17\n}\ninv_gesture_mapping = {v: k for k, v in gesture_mapping.items()}\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **📂 Cell 4: Load and Preprocess Dataset**","metadata":{}},{"cell_type":"code","source":"# Load gesture and demographic data and merge them\ndef 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    merged = train.merge(train_demo, on=\"subject\", how=\"left\").fillna(0)\n    return merged\n\n# Encode gesture labels as integers\ndef encode_target(df):\n    df[\"encoded_gesture\"] = df[\"gesture\"].map(gesture_mapping).fillna(0).astype(int)\n    return df\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🧼 Cell 5: Time-Series Feature Extraction**","metadata":{}},{"cell_type":"code","source":"# Extract multi-modal time series data per sequence\ndef extract_time_series(df, group_col=\"sequence_id\"):\n    X_accel, X_rot, X_thm, X_tof, y, demo_all = [], [], [], [], [], []\n    max_len = 60\n    grouped = df.groupby(group_col)\n\n    for _, group in grouped:\n        def crop_or_pad(arr, pad_value=0.0):\n            if arr.shape[0] >= max_len:\n                return arr[-max_len:]\n            pad_width = ((0, max_len - arr.shape[0]), (0, 0))\n            return np.pad(arr, pad_width=pad_width, mode='constant', constant_values=pad_value)\n\n        accel = crop_or_pad(group[accel_features].to_numpy())\n        rot = crop_or_pad(group[rot_features].to_numpy())\n        thm = crop_or_pad(group[thm_features].to_numpy())\n\n        tof_raw = group[tof_features].to_numpy()\n        if tof_raw.shape[0] >= max_len:\n            tof_raw = tof_raw[-max_len:]\n        else:\n            tof_raw = np.pad(tof_raw, ((0, max_len - tof_raw.shape[0]), (0, 0)), mode='constant', constant_values=0.0)\n        tof_images = tof_raw.reshape((max_len, 8, 8, 5))\n\n        X_accel.append(accel)\n        X_rot.append(rot)\n        X_thm.append(thm)\n        X_tof.append(tof_images)\n        demo_all.append(group[demo_features].iloc[0].to_numpy())\n        y.append(group[\"encoded_gesture\"].iloc[0])\n\n    return {\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},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **📊 Cell 6: Data Preparation and Splitting**","metadata":{}},{"cell_type":"code","source":"# Prepare and split dataset\ndf = load_and_merge()\ndf = encode_target(df)\ndata = extract_time_series(df)\n\nX_accel, X_rot, X_thm, X_tof, X_demo, y = data[\"X_accel\"], data[\"X_rot\"], data[\"X_thm\"], data[\"X_tof\"], data[\"X_demo\"], data[\"y\"]\n\nnum_classes = len(np.unique(y))\ny_cat = tf.keras.utils.to_categorical(y, num_classes=num_classes)\n\nXa_train, Xa_val, Xr_train, Xr_val, Xt_train, Xt_val, Xtof_train, Xtof_val, Xd_train, Xd_val, y_train, y_val = train_test_split(\n    X_accel, X_rot, X_thm, X_tof, X_demo, y_cat, test_size=0.15, stratify=y_cat, random_state=42\n)\n\ninput_shapes = {\n    'X_accel': Xa_train.shape[1:],\n    'X_rot': Xr_train.shape[1:],\n    'X_thm': Xt_train.shape[1:],\n    'X_tof': Xtof_train.shape[1:],\n    'X_demo': Xd_train.shape[1:]\n}","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🧠 Cell 7: Model Architecture**","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.layers import (\n    Input, Dense, Conv1D, GRU, GlobalAveragePooling1D, GlobalAveragePooling2D,\n    TimeDistributed, Conv2D, Dropout, Concatenate, Reshape,\n    Multiply, Softmax, Add, MultiHeadAttention, AlphaDropout, Lambda,\n    GaussianNoise\n)\nfrom tensorflow.keras.models import Model\n\n\n# -------- Squeeze-and-Excite Block --------\ndef squeeze_excite_block(input_tensor, ratio=8):\n    filters = input_tensor.shape[-1]\n    se = GlobalAveragePooling1D()(input_tensor)\n    se = Dense(filters // ratio, activation='selu')(se)\n    se = Dense(filters, activation='sigmoid')(se)\n    se = Reshape((1, filters))(se)\n    return Multiply()([input_tensor, se])\n\n\n# -------- Attention Block --------\ndef attention_block(inputs):\n    dim = inputs.shape[-1]\n    query = Dense(dim, activation='tanh')(inputs)\n    score = Dense(1)(query)\n    weights = Softmax(axis=1)(score)\n    attended = Multiply()([inputs, weights])\n    return attended\n\n\n# -------- Transformer Encoder Block --------\ndef transformer_encoder_block(x, num_heads=2, ff_dim=64, dropout_rate=0.1):\n    attn_output = MultiHeadAttention(num_heads=num_heads, key_dim=ff_dim)(x, x)\n    attn_output = Dropout(dropout_rate)(attn_output)\n    out1 = Add()([x, attn_output])\n\n    ff_output = Dense(ff_dim, activation='selu')(out1)\n    ff_output = Dense(x.shape[-1], activation='selu')(ff_output)\n    ff_output = Dropout(dropout_rate)(ff_output)\n    return Add()([out1, ff_output])\n\n\n# -------- Main Model Definition --------\ndef build_model(input_shapes, num_classes, activation='selu'):\n    inputs, processed = [], []\n\n    # Accelerometer, Gyroscope, Thermopile branches\n    for name in ['X_accel', 'X_rot', 'X_thm']:\n        inp = Input(shape=input_shapes[name], name=name)\n        x = Conv1D(64, 3, padding='same', activation=activation)(inp)\n        x = GaussianNoise(0.25)(x)\n        x = squeeze_excite_block(x)\n        x = GRU(64, return_sequences=True)(x)\n        x = transformer_encoder_block(x, num_heads=2)\n        x = GlobalAveragePooling1D()(x)\n        x = attention_block(x)\n        inputs.append(inp)\n        processed.append(x)\n\n    # TOF input (3D tensor with Conv2D)\n    inp_tof = Input(shape=input_shapes['X_tof'], name='X_tof')\n    x = TimeDistributed(Conv2D(64, (3, 3), padding='same', activation=activation))(inp_tof)\n    x = TimeDistributed(GaussianNoise(0.25))(x)\n    x = TimeDistributed(Conv2D(64, (3, 3), padding='same', activation=activation))(x)\n    x = TimeDistributed(GlobalAveragePooling2D())(x)\n    x = Dropout(0.1)(x)\n    x = GRU(64, return_sequences=True)(x)\n    x = transformer_encoder_block(x, num_heads=2)\n    x = GlobalAveragePooling1D()(x)\n    x = attention_block(x)\n    inputs.append(inp_tof)\n    processed.append(x)\n\n    # Demographic input\n    inp_demo = Input(shape=input_shapes['X_demo'], name='X_demo')\n    x = Dense(64, activation=activation)(inp_demo)\n    x = GaussianNoise(0.25)(x)\n    x = Dense(64, activation=activation)(x)\n    x = attention_block(x)\n    inputs.append(inp_demo)\n    processed.append(x)\n\n    # Fusion\n    x = Concatenate()(processed)\n    x = Dense(128, activation=activation)(x)\n    x = AlphaDropout(0.1)(x)\n    x = Dense(128, activation=activation)(x)\n    x = AlphaDropout(0.1)(x)\n\n    # Output layer\n    output = Dense(num_classes, activation='softmax')(x)\n\n    return Model(inputs=inputs, outputs=output)\n\n\n\n\nmodel = build_model(input_shapes, num_classes=num_classes, activation='selu')\nmodel.summary()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🚂 Cell 8: Model Training**","metadata":{}},{"cell_type":"code","source":"# Compile and fit model\nmodel.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])\nhistory = model.fit(\n    [Xa_train, Xr_train, Xt_train, Xtof_train, Xd_train],\n    y_train,\n    validation_data=([Xa_val, Xr_val, Xt_val, Xtof_val, Xd_val], y_val),\n    epochs=20,\n    batch_size=64\n)\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-10T10:37:06.528Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **📈 Cell 9: Plot Training History**","metadata":{}},{"cell_type":"code","source":"# Visualize training accuracy and loss\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'], label=\"Train Acc\")\nplt.plot(history.history['val_accuracy'], label=\"Val Acc\")\nplt.title(\"Accuracy\")\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(history.history['loss'], label=\"Train Loss\")\nplt.plot(history.history['val_loss'], label=\"Val Loss\")\nplt.title(\"Loss\")\nplt.legend()\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-10T10:37:06.528Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🧾 Cell 10: Validation Metrics & Hierarchical F1**","metadata":{}},{"cell_type":"code","source":"# Validation performance evaluation\ny_pred = model.predict([Xa_val, Xr_val, Xt_val, Xtof_val, Xd_val])\ny_pred_labels = np.argmax(y_pred, axis=1)\ny_true_labels = np.argmax(y_val, axis=1)\n\nprint(classification_report(y_true_labels, y_pred_labels))\n\n# Hierarchical F1 calculation\nfrom cmi_2025_metric_copy_for_import import CompetitionMetric\n\nval_submission = pd.DataFrame({'gesture': pd.Series(y_pred_labels).map(inv_gesture_mapping)})\nval_solution = pd.DataFrame({'gesture': pd.Series(y_true_labels).map(inv_gesture_mapping)})\n\nmetric = CompetitionMetric()\nscore = metric.calculate_hierarchical_f1(val_solution, val_submission)\nprint(f\"Estimated leaderboard (val) score: {score:.4f}\")\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-10T10:37:06.529Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **💾 Cell 11: Save Model**","metadata":{}},{"cell_type":"code","source":"# Save trained model\nmodel.save(\"gesture_cnn_model.h5\")\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-10T10:37:06.529Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **🧪 Cell 12: Inference Server Integration**","metadata":{}},{"cell_type":"code","source":"# Inference logic for competition submission\nimport polars as pl\nimport numpy as np\nimport os\nfrom tensorflow.keras.models import load_model\n\nmodel = load_model(\"/kaggle/working/gesture_cnn_model.h5\")\n\ndef extract_single_example(sequence: pl.DataFrame, demographics: pl.DataFrame):\n    max_len = 60\n\n    def crop_or_pad(arr, pad_value=0.0):\n        if arr.shape[0] >= max_len:\n            return arr[-max_len:]\n        pad_width = ((0, max_len - arr.shape[0]), (0, 0))\n        return np.pad(arr, pad_width, mode='constant', constant_values=pad_value).astype(np.float32)\n\n    group = sequence.to_pandas()\n    demo = demographics.to_pandas().iloc[0]\n\n    accel = crop_or_pad(group[accel_features].to_numpy())\n    rot = crop_or_pad(group[rot_features].to_numpy())\n    thm = crop_or_pad(group[thm_features].to_numpy())\n\n    tof_raw = group[tof_features].to_numpy()\n    if tof_raw.shape[0] >= max_len:\n        tof_raw = tof_raw[-max_len:]\n    else:\n        tof_raw = np.pad(tof_raw, ((0, max_len - tof_raw.shape[0]), (0, 0)), mode='constant')\n    tof_images = tof_raw.reshape((max_len, 8, 8, 5)).astype(np.float32)\n\n    return {\n        \"X_accel\": accel[np.newaxis],\n        \"X_rot\": rot[np.newaxis],\n        \"X_thm\": thm[np.newaxis],\n        \"X_tof\": tof_images[np.newaxis],\n        \"X_demo\": demo[demo_features].to_numpy(dtype=np.float32)[np.newaxis]\n    }\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    try:\n        data = extract_single_example(sequence, demographics)\n        pred_probs = model.predict([\n            data[\"X_accel\"],\n            data[\"X_rot\"],\n            data[\"X_thm\"],\n            data[\"X_tof\"],\n            data[\"X_demo\"]\n        ], verbose=1)\n        return inv_gesture_mapping[int(np.argmax(pred_probs[0]))]\n    except Exception as e:\n        print(\"Prediction error:\", e)\n        return \"Text on phone\"\n\n# Inference server logic\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    )\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-10T10:37:06.529Z"}},"outputs":[],"execution_count":null}]}