{"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":"# Neural Network approach\n\nTHis notebook tries several neural network architectures to solve the challenge. See the `build_bfrb_model` function below for examples.\n\nIt is based on a previous attempt at using TPUs, but it can work with GPUs as well.","metadata":{}},{"cell_type":"code","source":"!pip install -q polars","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:16:46.467365Z","iopub.execute_input":"2025-06-01T13:16:46.467700Z","iopub.status.idle":"2025-06-01T13:16:49.875484Z","shell.execute_reply.started":"2025-06-01T13:16:46.467662Z","shell.execute_reply":"2025-06-01T13:16:49.874565Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math, re, os\nimport tensorflow as tf\nimport numpy as np\nimport polars as pl\nfrom matplotlib import pyplot as plt\n#from kaggle_datasets import KaggleDatasets\nfrom sklearn.metrics import f1_score, precision_score, recall_score, confusion_matrix\nfrom sklearn.model_selection import train_test_split\n\nimport pandas as pd\nfrom sklearn.preprocessing import LabelEncoder\nimport kaggle_evaluation.cmi_inference_server\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:16:49.876430Z","iopub.execute_input":"2025-06-01T13:16:49.876716Z","iopub.status.idle":"2025-06-01T13:16:54.226192Z","shell.execute_reply.started":"2025-06-01T13:16:49.876688Z","shell.execute_reply":"2025-06-01T13:16:54.225607Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# copy&paste from https://www.kaggle.com/code/ryanholbrook/getting-started-with-tpus\nprint(\"Tensorflow version \" + tf.__version__)\nAUTO = tf.data.experimental.AUTOTUNE\n\n# Detect TPU, return appropriate distribution strategy\ntry:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver() \n    print('Running on TPU ', tpu.master())\nexcept ValueError:\n    tpu = None\n    print(\"not running on TPU\")\n\nif tpu:\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)\nelse:\n    strategy = tf.distribute.get_strategy() \n\nprint(\"REPLICAS: \", strategy.num_replicas_in_sync)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:16:54.231387Z","iopub.execute_input":"2025-06-01T13:16:54.231637Z","iopub.status.idle":"2025-06-01T13:16:54.237723Z","shell.execute_reply.started":"2025-06-01T13:16:54.231617Z","shell.execute_reply":"2025-06-01T13:16:54.236858Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(tf.config.list_physical_devices('GPU'))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:16:54.238818Z","iopub.execute_input":"2025-06-01T13:16:54.239015Z","iopub.status.idle":"2025-06-01T13:16:54.427528Z","shell.execute_reply.started":"2025-06-01T13:16:54.238999Z","shell.execute_reply":"2025-06-01T13:16:54.426635Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Read Data","metadata":{}},{"cell_type":"code","source":"# Load data\ntrain_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\")\ntargets = train_df[['sequence_id', 'gesture']].drop_duplicates()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:16:54.428510Z","iopub.execute_input":"2025-06-01T13:16:54.429444Z","iopub.status.idle":"2025-06-01T13:17:17.106488Z","shell.execute_reply.started":"2025-06-01T13:16:54.429419Z","shell.execute_reply":"2025-06-01T13:17:17.105843Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:17:17.107334Z","iopub.execute_input":"2025-06-01T13:17:17.107570Z","iopub.status.idle":"2025-06-01T13:17:17.132529Z","shell.execute_reply.started":"2025-06-01T13:17:17.107534Z","shell.execute_reply":"2025-06-01T13:17:17.131677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_demographics = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\")\ntrain_demographics.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:17:17.133412Z","iopub.execute_input":"2025-06-01T13:17:17.133942Z","iopub.status.idle":"2025-06-01T13:17:17.148177Z","shell.execute_reply.started":"2025-06-01T13:17:17.133922Z","shell.execute_reply":"2025-06-01T13:17:17.147438Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Merge all in one dataset\n\nThere may be smarter way to do this merge, but for now, let's put it all together.","metadata":{"execution":{"iopub.status.busy":"2025-05-30T09:01:03.42866Z","iopub.execute_input":"2025-05-30T09:01:03.428997Z","iopub.status.idle":"2025-05-30T09:01:03.441963Z","shell.execute_reply.started":"2025-05-30T09:01:03.42897Z","shell.execute_reply":"2025-05-30T09:01:03.437703Z"}}},{"cell_type":"code","source":"train_df = train_df.merge(train_demographics, on='subject', how='left')\ntrain_df.fillna(method='ffill', inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:17:17.148935Z","iopub.execute_input":"2025-06-01T13:17:17.149189Z","iopub.status.idle":"2025-06-01T13:17:19.348345Z","shell.execute_reply.started":"2025-06-01T13:17:17.149166Z","shell.execute_reply":"2025-06-01T13:17:19.347509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:17:19.349294Z","iopub.execute_input":"2025-06-01T13:17:19.349528Z","iopub.status.idle":"2025-06-01T13:17:19.368118Z","shell.execute_reply.started":"2025-06-01T13:17:19.349510Z","shell.execute_reply":"2025-06-01T13:17:19.367308Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.describe()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:17:19.369097Z","iopub.execute_input":"2025-06-01T13:17:19.369340Z","iopub.status.idle":"2025-06-01T13:17:27.055978Z","shell.execute_reply.started":"2025-06-01T13:17:19.369312Z","shell.execute_reply":"2025-06-01T13:17:27.055017Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"', '.join(train_df.columns.to_list())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:17:27.056966Z","iopub.execute_input":"2025-06-01T13:17:27.057718Z","iopub.status.idle":"2025-06-01T13:17:27.062765Z","shell.execute_reply.started":"2025-06-01T13:17:27.057688Z","shell.execute_reply":"2025-06-01T13:17:27.061956Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prepare Targets and Feature Selection\n\nWe only use a few features for now, to simplify the model.","metadata":{}},{"cell_type":"code","source":"\n# Encode gesture labels\nlabel_encoder = LabelEncoder()\ntargets['gesture_enc'] = label_encoder.fit_transform(targets['gesture'])\ngesture2id = dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_)))\n\n# Features to use (IMU only)\nFEATURES = [\n    'acc_x', 'acc_y', 'acc_z',\n    'rot_w', 'rot_x', 'rot_y', 'rot_z'\n]\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:17:27.065851Z","iopub.execute_input":"2025-06-01T13:17:27.066096Z","iopub.status.idle":"2025-06-01T13:17:27.081640Z","shell.execute_reply.started":"2025-06-01T13:17:27.066069Z","shell.execute_reply":"2025-06-01T13:17:27.080824Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Feature selection\nIMU_FEATURES = ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z']\nTHERMO_FEATURES = [f'thm_{i}' for i in range(1, 6)]\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', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']\n\nFEATURES = IMU_FEATURES + THERMO_FEATURES + DEMO_FEATURES  + TOF_FEATURES \nprint(f\"We have {len(FEATURES)} features\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:17:27.082442Z","iopub.execute_input":"2025-06-01T13:17:27.082747Z","iopub.status.idle":"2025-06-01T13:17:27.101017Z","shell.execute_reply.started":"2025-06-01T13:17:27.082729Z","shell.execute_reply":"2025-06-01T13:17:27.100327Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prepare Sequences","metadata":{}},{"cell_type":"code","source":"# Prepare sequences\nsequence_ids = train_df['sequence_id'].unique()\nX, y, y_binary = [], [], []\n\ndef is_bfrb(label_id):\n    label_name = label_encoder.inverse_transform([label_id])[0]\n    return int(label_name != 'non_target')\n\nexpected_feature_dim = len(FEATURES)\nprint(f\"Expecting feature size: {expected_feature_dim}\")\n\nfor seq_id in sequence_ids:\n    df = train_df[train_df['sequence_id'] == seq_id]\n\n    #print(seq_id)\n    \n    # Skip if any FEATURE is missing\n    if df[FEATURES].isnull().values.any():\n        continue\n\n    x = df[FEATURES].values.astype(np.float32)\n    \n    # Pad or truncate to fixed length\n    if x.shape[0] < 64:\n        pad_width = 64 - x.shape[0]\n        x = np.pad(x, ((0, pad_width), (0, 0)), mode='edge')\n    else:\n        x = x[:64]\n    \n    # Final shape check\n    if x.shape != (64, expected_feature_dim):\n        print(f\"Skipping sequence {seq_id} with bad shape: {x.shape}\")\n        continue\n\n    # Append class and binary labels\n    X.append(x)\n    label = targets.loc[targets['sequence_id'] == seq_id, 'gesture_enc'].values[0]\n    y.append(label)\n    y_binary.append(is_bfrb(label))\n\nX = np.stack(X)\ny = np.array(y)\ny_binary = np.array(y_binary)\nprint(f\"Final shape of X: {X.shape}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:23:50.262248Z","iopub.status.idle":"2025-06-01T13:23:50.262487Z","shell.execute_reply.started":"2025-06-01T13:23:50.262374Z","shell.execute_reply":"2025-06-01T13:23:50.262384Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# X_train, X_val, y_train_class, y_val_class, y_train_bin, y_val_bin = train_test_split(\n#     X, y, y_binary, test_size=0.1, stratify=y, random_state=42\n# )\n# BATCH_SIZE = 64 * strategy.num_replicas_in_sync\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:23:50.029931Z","iopub.execute_input":"2025-06-01T13:23:50.030174Z","iopub.status.idle":"2025-06-01T13:23:50.243110Z","shell.execute_reply.started":"2025-06-01T13:23:50.030148Z","shell.execute_reply":"2025-06-01T13:23:50.242468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Split features into time-series and demographic parts\nX_time = X[:, :, :len(IMU_FEATURES + THERMO_FEATURES + TOF_FEATURES)]\nX_demo = X[:, 0, -len(DEMO_FEATURES):]  # Take demo features from first timestep\n\n# Train/validation split\nX_train_time, X_val_time, X_train_demo, X_val_demo, y_train_class, y_val_class, y_train_bin, y_val_bin = train_test_split(\n    X_time, X_demo, y, y_binary, test_size=0.1, stratify=y, random_state=42\n)\n\n# Define datasets\nBATCH_SIZE = 64 * strategy.num_replicas_in_sync\ntrain_ds = tf.data.Dataset.from_tensor_slices((\n    {\n        \"time_series\": X_train_time,\n        \"demographics\": X_train_demo\n    },\n    {\n        \"class_output\": y_train_class,\n        \"binary_output\": y_train_bin\n    }\n)).shuffle(1024).batch(BATCH_SIZE).prefetch(AUTO)\n\nval_ds = tf.data.Dataset.from_tensor_slices((\n    {\n        \"time_series\": X_val_time,\n        \"demographics\": X_val_demo\n    },\n    {\n        \"class_output\": y_val_class,\n        \"binary_output\": y_val_bin\n    }\n)).batch(BATCH_SIZE).prefetch(AUTO)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:25:59.001902Z","iopub.execute_input":"2025-06-01T13:25:59.002534Z","iopub.status.idle":"2025-06-01T13:26:01.220304Z","shell.execute_reply.started":"2025-06-01T13:25:59.002507Z","shell.execute_reply":"2025-06-01T13:26:01.219684Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Build TPU model\n\nApparently Keras is easier for developing TPU models.","metadata":{}},{"cell_type":"code","source":"def build_bfrb_model(architecture: str, input_shapes: dict, num_classes: int) -> tf.keras.Model:\n    def se_block(input_tensor, ratio=8):\n        channel_axis = -1\n        filters = input_tensor.shape[channel_axis]\n        se_shape = (1, filters)\n\n        se = tf.keras.layers.GlobalAveragePooling1D()(input_tensor)\n        se = tf.keras.layers.Reshape(se_shape)(se)\n        se = tf.keras.layers.Dense(filters // ratio, activation='relu', kernel_initializer='he_normal', use_bias=False)(se)\n        se = tf.keras.layers.Dense(filters, activation='sigmoid', kernel_initializer='he_normal', use_bias=False)(se)\n        return tf.keras.layers.Multiply()([input_tensor, se])\n\n    def residual_se_cnn_block(x, filters, kernel_size):\n        shortcut = x\n        x = tf.keras.layers.Conv1D(filters, kernel_size, padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-4))(x)\n        x = tf.keras.layers.BatchNormalization()(x)\n        x = tf.keras.layers.ReLU()(x)\n        x = tf.keras.layers.Conv1D(filters, kernel_size, padding='same', kernel_regularizer=tf.keras.regularizers.l2(1e-4))(x)\n        x = tf.keras.layers.BatchNormalization()(x)\n        x = se_block(x)\n        x = tf.keras.layers.Add()([x, shortcut])\n        x = tf.keras.layers.ReLU()(x)\n        return x\n\n    def attention_block(inputs):\n        query = tf.keras.layers.Dense(64)(inputs)\n        key = tf.keras.layers.Dense(64)(inputs)\n        value = tf.keras.layers.Dense(64)(inputs)\n        attention = tf.keras.layers.Attention()([query, key, value])\n        return tf.keras.layers.GlobalAveragePooling1D()(attention)\n\n    time_input = tf.keras.Input(shape=input_shapes['time_series'], name=\"time_series\")\n    demo_input = tf.keras.Input(shape=input_shapes['demographics'], name=\"demographics\")\n\n    # --- Time-series branch ---\n    x = time_input\n    if architecture == \"A1\":\n        x = tf.keras.layers.Conv1D(64, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.Conv1D(128, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.GlobalAveragePooling1D()(x)\n    elif architecture == \"A2\":\n        x = tf.keras.layers.Conv1D(64, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.Conv1D(128, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.Conv1D(256, 3, padding='same', activation='relu')(x)\n        x = tf.keras.layers.GlobalAveragePooling1D()(x)\n        x = tf.keras.layers.Dropout(0.3)(x)\n    elif architecture == \"A3\":\n        x = tf.keras.layers.Conv1D(64, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.Conv1D(128, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.LSTM(64, return_sequences=True)(x)\n        x = attention_block(x)\n    elif architecture == \"A4\":\n        x = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64, return_sequences=True))(x)\n        x = attention_block(x)\n    elif architecture == \"A6\":\n        x = tf.keras.layers.Conv1D(64, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.Conv1D(128, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.Flatten()(x)\n        x = tf.keras.layers.Dropout(0.3)(x)\n    elif architecture == \"A7\":\n        x = tf.keras.layers.Conv1D(64, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.BatchNormalization()(x)\n        x = tf.keras.layers.Conv1D(128, 5, padding='same', activation='relu')(x)\n        x = tf.keras.layers.BatchNormalization()(x)\n        x = tf.keras.layers.GlobalAveragePooling1D()(x)\n        x = tf.keras.layers.Dropout(0.3)(x)\n    elif architecture == \"A8\":\n        x = tf.keras.layers.Conv1D(64, 5, padding='same')(x)\n        x = residual_se_cnn_block(x, 64, 5)\n        x = residual_se_cnn_block(x, 64, 5)\n        x = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64, return_sequences=True))(x)\n        x = attention_block(x)\n    else:\n        raise ValueError(f\"Unknown architecture ID: {architecture}\")\n\n    x = tf.keras.layers.Dense(128, activation='relu')(x)\n\n    # --- Demographic branch ---\n    d = tf.keras.layers.Dense(64, activation='relu')(demo_input)\n    d = tf.keras.layers.Dense(32, activation='relu')(d)\n\n    # --- Merge branches ---\n    merged = tf.keras.layers.Concatenate()([x, d])\n    merged = tf.keras.layers.Dense(128, activation='relu')(merged)\n\n    # --- Heads ---\n    class_output = tf.keras.layers.Dense(num_classes, activation='softmax', name='class_output')(merged)\n    binary_output = tf.keras.layers.Dense(1, activation='sigmoid', name='binary_output')(merged)\n\n    model = tf.keras.Model(inputs=[time_input, demo_input], outputs=[class_output, binary_output])\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(1e-3),\n        loss={\n            'class_output': tf.keras.losses.SparseCategoricalCrossentropy(),\n            'binary_output': tf.keras.losses.BinaryCrossentropy(),\n        },\n        metrics={\n            'class_output': 'accuracy',\n            'binary_output': 'accuracy',\n        }\n    )\n    return model\n\nwith strategy.scope():\n    model = build_bfrb_model(\n        architecture=\"A6\",\n        input_shapes={\n            \"time_series\": (64, len(IMU_FEATURES + THERMO_FEATURES + TOF_FEATURES)),  # e.g. (64, 332)\n            \"demographics\": (len(DEMO_FEATURES),)     # e.g. (7,)\n        },\n        num_classes=len(label_encoder.classes_)\n    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:26:05.853135Z","iopub.execute_input":"2025-06-01T13:26:05.853769Z","iopub.status.idle":"2025-06-01T13:26:06.697871Z","shell.execute_reply.started":"2025-06-01T13:26:05.853742Z","shell.execute_reply":"2025-06-01T13:26:06.696940Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train!\n","metadata":{}},{"cell_type":"code","source":"# Train\n\nearly_stop = tf.keras.callbacks.EarlyStopping(\n    monitor='val_class_output_loss',\n    patience=10,\n    restore_best_weights=True, mode=\"min\"\n)\n\nhistory = model.fit(train_ds, validation_data=val_ds, epochs=50, callbacks=[early_stop])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:27:13.865093Z","iopub.execute_input":"2025-06-01T13:27:13.866139Z","iopub.status.idle":"2025-06-01T13:27:25.234768Z","shell.execute_reply.started":"2025-06-01T13:27:13.866108Z","shell.execute_reply":"2025-06-01T13:27:25.234128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Plot classification (gesture) loss and accuracy\nplt.figure(figsize=(12, 5))\n\n# Loss\nplt.subplot(1, 2, 1)\nplt.plot(history.history['class_output_loss'], label='Train Gesture Loss')\nplt.plot(history.history['val_class_output_loss'], label='Val Gesture Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Gesture Classification Loss')\nplt.legend()\n\n# Accuracy\nplt.subplot(1, 2, 2)\nplt.plot(history.history['class_output_accuracy'], label='Train Gesture Acc')\nplt.plot(history.history['val_class_output_accuracy'], label='Val Gesture Acc')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Gesture Classification Accuracy')\nplt.legend()\n\nplt.tight_layout()\nplt.show()\n\n# Plot binary head loss and accuracy\nplt.figure(figsize=(12, 5))\n\n# Loss\nplt.subplot(1, 2, 1)\nplt.plot(history.history['binary_output_loss'], label='Train Binary Loss')\nplt.plot(history.history['val_binary_output_loss'], label='Val Binary Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Binary Detection Loss')\nplt.legend()\n\n# Accuracy\nplt.subplot(1, 2, 2)\nplt.plot(history.history['binary_output_accuracy'], label='Train Binary Acc')\nplt.plot(history.history['val_binary_output_accuracy'], label='Val Binary Acc')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Binary Detection Accuracy')\nplt.legend()\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:27:29.262487Z","iopub.execute_input":"2025-06-01T13:27:29.262844Z","iopub.status.idle":"2025-06-01T13:27:30.234318Z","shell.execute_reply.started":"2025-06-01T13:27:29.262818Z","shell.execute_reply":"2025-06-01T13:27:30.233312Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluate model (multi-head)\nval_preds = model.predict(val_ds)\ngesture_preds = val_preds[0]  # class_output\nbinary_preds = val_preds[1]   # binary_output\n\ny_val_pred = np.argmax(gesture_preds, axis=1)\nbinary_pred = (binary_preds > 0.5).astype(int).flatten()\n\nfrom sklearn.metrics import classification_report, confusion_matrix, f1_score\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nprint(\"Validation Classification Report:\")\nprint(classification_report(y_val_class, y_val_pred, target_names=label_encoder.classes_))\n\ncm = confusion_matrix(y_val_class, y_val_pred)\nplt.figure(figsize=(12, 10))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=label_encoder.classes_, yticklabels=label_encoder.classes_)\nplt.title(\"Confusion Matrix\")\nplt.ylabel(\"True Label\")\nplt.xlabel(\"Predicted Label\")\nplt.xticks(rotation=45)\nplt.yticks(rotation=0)\nplt.tight_layout()\nplt.show()\n\nbinary_true = np.isin(y_val_class, [label_encoder.transform([g])[0] for g in label_encoder.classes_ if g != 'non_target']).astype(int)\nbinary_pred = np.isin(y_val_pred, [label_encoder.transform([g])[0] for g in label_encoder.classes_ if g != 'non_target']).astype(int)\n\nbinary_f1 = f1_score(binary_true, binary_pred)\nmacro_f1 = f1_score(y_val_class, y_val_pred, average='macro')\nfinal_score = 0.5 * (binary_f1 + macro_f1)\n\nprint(f\"Binary F1: {binary_f1:.4f}, Macro F1: {macro_f1:.4f}, Final Score: {final_score:.4f}\")\n\nprint(f\"Binary F1: {binary_f1:.4f}, Macro F1: {macro_f1:.4f}, Final Score: {final_score:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:27:32.593083Z","iopub.execute_input":"2025-06-01T13:27:32.593377Z","iopub.status.idle":"2025-06-01T13:27:34.902013Z","shell.execute_reply.started":"2025-06-01T13:27:32.593355Z","shell.execute_reply":"2025-06-01T13:27:34.900893Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Ground truth binary labels (1 = target, 0 = non-target)\nbinary_true = np.isin(y_val_class, [\n    label_encoder.transform([g])[0] \n    for g in label_encoder.classes_ if g != 'non_target'\n]).astype(int)\n\n# Already computed earlier: binary_pred = (binary_preds > 0.5).astype(int).flatten()\n\n# Binary classification report\nprint(\"\\nBinary Classification Report:\")\nprint(classification_report(\n    binary_true, \n    binary_pred, \n    labels=[0, 1], \n    target_names=['non_target', 'target']\n))\n\n\n# Binary confusion matrix\nbinary_cm = confusion_matrix(binary_true, binary_pred)\nplt.figure(figsize=(6, 5))\nsns.heatmap(binary_cm, annot=True, fmt='d', cmap='Greens', \n            xticklabels=['non_target', 'target'], \n            yticklabels=['non_target', 'target'])\nplt.title(\"Confusion Matrix - Binary Head\")\nplt.ylabel(\"True Label\")\nplt.xlabel(\"Predicted Label\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:27:36.113088Z","iopub.execute_input":"2025-06-01T13:27:36.114093Z","iopub.status.idle":"2025-06-01T13:27:36.331314Z","shell.execute_reply.started":"2025-06-01T13:27:36.114066Z","shell.execute_reply":"2025-06-01T13:27:36.330509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import f1_score\nimport matplotlib.pyplot as plt\n\nf1_per_class = f1_score(y_val_class, y_val_pred, average=None)\nplt.figure(figsize=(12, 5))\nplt.bar(label_encoder.classes_, f1_per_class)\nplt.xticks(rotation=45, ha='right')\nplt.ylabel(\"F1 Score\")\nplt.title(\"Per-Class F1 Scores\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:27:41.041221Z","iopub.execute_input":"2025-06-01T13:27:41.041986Z","iopub.status.idle":"2025-06-01T13:27:41.319572Z","shell.execute_reply.started":"2025-06-01T13:27:41.041959Z","shell.execute_reply":"2025-06-01T13:27:41.318820Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"binary_f1 = f1_score(binary_true, binary_pred)\nmacro_f1 = f1_score(y_val_class, y_val_pred, average='macro')\nfinal_score = 0.5 * (binary_f1 + macro_f1)\n\nprint(f\"Binary F1: {binary_f1:.4f}, Macro F1: {macro_f1:.4f}, Final Score: {final_score:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:27:47.825783Z","iopub.execute_input":"2025-06-01T13:27:47.826525Z","iopub.status.idle":"2025-06-01T13:27:47.836252Z","shell.execute_reply.started":"2025-06-01T13:27:47.826497Z","shell.execute_reply":"2025-06-01T13:27:47.835291Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize gesture-wise F1 scores:\n\nfrom sklearn.metrics import precision_recall_fscore_support\n\n_, _, f1s, _ = precision_recall_fscore_support(y_val_class, y_val_pred, labels=range(len(label_encoder.classes_)))\nplt.figure(figsize=(12, 4))\nsns.barplot(x=label_encoder.classes_, y=f1s)\nplt.xticks(rotation=45)\nplt.title(\"Per-Class F1 Score\")\nplt.ylabel(\"F1 Score\")\nplt.xlabel(\"Gesture Class\")\nplt.ylim(0, 1)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:27:47.837761Z","iopub.execute_input":"2025-06-01T13:27:47.838007Z","iopub.status.idle":"2025-06-01T13:27:48.129341Z","shell.execute_reply.started":"2025-06-01T13:27:47.837989Z","shell.execute_reply":"2025-06-01T13:27:48.128596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#model.save(\"./model\")\n#pd.Series(label_encoder.classes_).to_csv(\"gesture_labels.csv\", index=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T13:27:48.130198Z","iopub.execute_input":"2025-06-01T13:27:48.131007Z","iopub.status.idle":"2025-06-01T13:27:48.134325Z","shell.execute_reply.started":"2025-06-01T13:27:48.130983Z","shell.execute_reply":"2025-06-01T13:27:48.133652Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Create Submission Interface","metadata":{}},{"cell_type":"code","source":"def predict(sequence, demographics) -> str:\n    # Convert to pandas if needed\n    sequence = sequence.to_pandas()\n    demographics = demographics.to_pandas()\n    \n    # Merge and fill missing\n    df = pd.merge(sequence, demographics, on='subject', how='left')\n    df.fillna(method='ffill', inplace=True)\n\n    # Extract features\n    x_time = df[IMU_FEATURES + THERMO_FEATURES + TOF_FEATURES].values.astype(np.float32)\n    x_demo = df[DEMO_FEATURES].iloc[0].values.astype(np.float32)  # take from first row\n\n    # Pad/crop time-series\n    if x_time.shape[0] < 64:\n        pad_width = 64 - x_time.shape[0]\n        x_time = np.pad(x_time, ((0, pad_width), (0, 0)), mode='edge')\n    else:\n        x_time = x_time[:64]\n\n    # Add batch dimension\n    x_time = np.expand_dims(x_time, axis=0)\n    x_demo = np.expand_dims(x_demo, axis=0)\n\n    # Predict\n    class_probs, _ = model.predict({\"time_series\": x_time, \"demographics\": x_demo}, verbose=0)\n    pred_idx = np.argmax(class_probs[0])\n    return label_encoder.inverse_transform([pred_idx])[0]\n\n\n# Launch evaluation server\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-01T13:49:47.280168Z","iopub.execute_input":"2025-06-01T13:49:47.281027Z","iopub.status.idle":"2025-06-01T13:49:49.271446Z","shell.execute_reply.started":"2025-06-01T13:49:47.280999Z","shell.execute_reply":"2025-06-01T13:49:49.270580Z"}},"outputs":[],"execution_count":null}]}