{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# TPU approach\n\nI'm learning to use TPUs (see https://www.kaggle.com/code/ryanholbrook/getting-started-with-tpus), so this is my attempt for this competition.\n\n**Note** TPU is not currently working, I've asked in the forums https://www.kaggle.com/discussions/questions-and-answers/582296 . The notebook will also run on CPU or GPU, without any code changes.\n\nThe model itself is a simple neural network in keras - I am focusing more on using TPUs than the actual competition.","metadata":{}},{"cell_type":"code","source":"!pip install -q polars","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.406Z"}},"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":{"execution_failed":"2025-05-30T13:34:35.407Z"}},"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":{"execution_failed":"2025-05-30T13:34:35.407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(tf.config.list_physical_devices('GPU'))\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.407Z"}},"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":{"execution_failed":"2025-05-30T13:34:35.407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.407Z"}},"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":{"execution_failed":"2025-05-30T13:34:35.408Z"}},"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.428660Z","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.428970Z","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":{"execution_failed":"2025-05-30T13:34:35.408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.describe()\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"', '.join(train_df.columns.to_list())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.408Z"}},"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":{"execution_failed":"2025-05-30T13:34:35.408Z"}},"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 # TOF excluded for now due to sparsity","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"FEATURES","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.408Z"}},"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 = [], []\nfor seq_id in sequence_ids:\n    df = train_df[train_df['sequence_id'] == seq_id]\n    if df[FEATURES].isnull().values.any():\n        continue  # skip incomplete sequences\n    x = df[FEATURES].values.astype(np.float32)\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    X.append(x)\n    y.append(targets.loc[targets['sequence_id'] == seq_id, 'gesture_enc'].values[0])\n\nX = np.stack(X)\ny = np.array(y)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train/Val split\nX_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.1, stratify=y, random_state=42)\nBATCH_SIZE = 64 * strategy.num_replicas_in_sync\n\ntrain_ds = tf.data.Dataset.from_tensor_slices((X_train, y_train)).shuffle(1024).batch(BATCH_SIZE).prefetch(AUTO)\nval_ds = tf.data.Dataset.from_tensor_slices((X_val, y_val)).batch(BATCH_SIZE).prefetch(AUTO)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.409Z"}},"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":"# Build model with TPU strategy\nwith strategy.scope():\n    model = tf.keras.Sequential([\n        tf.keras.layers.Input(shape=(64, len(FEATURES))),\n        tf.keras.layers.Conv1D(64, 5, padding='same', activation='relu'),\n        tf.keras.layers.Conv1D(128, 5, padding='same', activation='relu'),\n        tf.keras.layers.GlobalAveragePooling1D(),\n        tf.keras.layers.Dense(128, activation='relu'),\n        tf.keras.layers.Dense(len(label_encoder.classes_), activation='softmax')\n    ])\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(1e-3),\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy']\n    )\n\nmodel","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.409Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train!\n\nTODO: implement cross-validation in TPUs","metadata":{}},{"cell_type":"code","source":"# Train\nearly_stop = tf.keras.callbacks.EarlyStopping(\n    monitor='val_class_output_loss',\n    patience=5,\n    restore_best_weights=True, mode=\"min\"\n)\n\nhistory = model.fit(train_ds, validation_data=val_ds, epochs=50, callbacks=[early_stop])\n\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-30T13:34:35.409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluate model\nval_preds = model.predict(val_ds)\ny_val_pred = np.argmax(val_preds, axis=1)\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, y_val_pred, target_names=label_encoder.classes_))\n\ncm = confusion_matrix(y_val, 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, [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, 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":{"execution_failed":"2025-05-30T13:34:35.409Z"}},"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":{"execution_failed":"2025-05-30T13:34:35.410Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Create Submission Interface","metadata":{}},{"cell_type":"code","source":"# Inference function for the evaluation API\ndef predict(sequence, demographics) -> str:\n    sequence = sequence.to_pandas()\n    demographics = demographics.to_pandas()\n    df = pd.merge(sequence, demographics, on='subject', how='left')\n\n    df.fillna(method='ffill', inplace=True)\n    x = df[FEATURES].values.astype(np.float32)\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    x = np.expand_dims(x, axis=0)\n    probs = model.predict(x, verbose=0)[0]\n    pred_idx = np.argmax(probs)\n    return label_encoder.inverse_transform([pred_idx])[0]\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":{"execution_failed":"2025-05-30T13:34:35.410Z"}},"outputs":[],"execution_count":null}]}