{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":12217868,"sourceType":"datasetVersion","datasetId":7696953}],"dockerImageVersionId":31041,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Suppress pip install output\n!pip install --no-index --find-links=/kaggle/input/my-offline-wheels/kaggle_wheels_v2 scikit-learn==1.6.1 > /dev/null 2>&1\n!pip install --no-index --find-links=/kaggle/input/my-offline-wheels/kaggle_wheels_v2 imbalanced-learn==0.13.0 > /dev/null 2>&1\n\n# Verify imports\nfrom imblearn.over_sampling import SMOTE\nprint(\"SMOTE imported successfully\")\n\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom pathlib import Path\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom tensorflow.keras.utils import to_categorical, pad_sequences, Sequence\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Input, Conv1D, BatchNormalization, Activation, MaxPooling1D, Dropout, Bidirectional, LSTM, Dense, Concatenate, GaussianNoise, Layer\nfrom tensorflow.keras.regularizers import l2\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.optimizers.schedules import CosineDecayRestarts\nfrom tensorflow.keras.losses import CategoricalCrossentropy\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\nimport joblib\nimport polars as pl\nfrom scipy.stats import skew\nfrom scipy.spatial.transform import Rotation as R\nprint(\"Imports successful\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:04:46.143469Z","iopub.execute_input":"2025-06-27T19:04:46.143743Z","iopub.status.idle":"2025-06-27T19:05:11.297428Z","shell.execute_reply.started":"2025-06-27T19:04:46.143699Z","shell.execute_reply":"2025-06-27T19:05:11.296587Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fix seed\ndef seed_everything(seed):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    random.seed(seed)\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n    tf.experimental.numpy.random.seed(seed)\n    os.environ['TF_CUDNN_DETERMINISTIC'] = '1'\n    os.environ['TF_DETERMINISTIC_OPS'] = '1'\nseed_everything(42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.298944Z","iopub.execute_input":"2025-06-27T19:05:11.299396Z","iopub.status.idle":"2025-06-27T19:05:11.304320Z","shell.execute_reply.started":"2025-06-27T19:05:11.299373Z","shell.execute_reply":"2025-06-27T19:05:11.303541Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Configuration\nRAW_DIR = Path(\"/kaggle/input/cmi-detect-behavior-with-sensor-data\")\nEXPORT_DIR = Path(\"/kaggle/working\")\nBATCH_SIZE = 64\nPAD_PERCENTILE = 95\nLR_INIT = 5e-4\nEPOCHS = 99\nPATIENCE = 30\ndt = 0.001\nMIXUP_ALPHA = 0.2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.305120Z","iopub.execute_input":"2025-06-27T19:05:11.305387Z","iopub.status.idle":"2025-06-27T19:05:11.536546Z","shell.execute_reply.started":"2025-06-27T19:05:11.305365Z","shell.execute_reply":"2025-06-27T19:05:11.535643Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Remove gravity from acceleration\ndef remove_gravity_from_acc(acc_data, rot_data):\n    if isinstance(acc_data, pd.DataFrame):\n        acc_values = acc_data[['acc_x', 'acc_y', 'acc_z']].values\n    else:\n        acc_values = acc_data\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n    num_samples = acc_values.shape[0]\n    linear_accel = np.zeros_like(acc_values)\n    gravity_world = np.array([0, 0, 9.81])\n    for i in range(num_samples):\n        if np.all(np.isnan(quat_values[i])) or np.all(np.isclose(quat_values[i], 0)):\n            linear_accel[i, :] = acc_values[i, :] \n            continue\n        try:\n            rotation = R.from_quat(quat_values[i])\n            gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)\n            linear_accel[i, :] = acc_values[i, :] - gravity_sensor_frame\n        except ValueError:\n            linear_accel[i, :] = acc_values[i, :]\n    return linear_accel","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.537377Z","iopub.execute_input":"2025-06-27T19:05:11.537638Z","iopub.status.idle":"2025-06-27T19:05:11.550450Z","shell.execute_reply.started":"2025-06-27T19:05:11.537613Z","shell.execute_reply":"2025-06-27T19:05:11.549947Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Calculate angular velocity from quaternions\ndef calculate_angular_velocity_from_quat(rot_data, time_delta=0.001): # Match dt=0.001 (1000 Hz)\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n    num_samples = quat_values.shape[0]\n    angular_vel = np.zeros((num_samples, 3))\n    for i in range(num_samples - 1):\n        q_t = quat_values[i]\n        q_t_plus_dt = quat_values[i+1]\n        if np.all(np.isnan(q_t)) or np.all(np.isclose(q_t, 0)) or \\\n           np.all(np.isnan(q_t_plus_dt)) or np.all(np.isclose(q_t_plus_dt, 0)):\n            continue\n        try:\n            rot_t = R.from_quat(q_t)\n            rot_t_plus_dt = R.from_quat(q_t_plus_dt)\n            delta_rot = rot_t.inv() * rot_t_plus_dt\n            angular_vel[i, :] = delta_rot.as_rotvec() / time_delta\n        except ValueError:\n            pass\n    return angular_vel","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.552197Z","iopub.execute_input":"2025-06-27T19:05:11.552367Z","iopub.status.idle":"2025-06-27T19:05:11.568669Z","shell.execute_reply.started":"2025-06-27T19:05:11.552354Z","shell.execute_reply":"2025-06-27T19:05:11.568152Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Calculate angular distance between quaternions\ndef calculate_angular_distance(rot_data):\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n    num_samples = quat_values.shape[0]\n    angular_dist = np.zeros(num_samples)\n    for i in range(num_samples - 1):\n        q1 = quat_values[i]\n        q2 = quat_values[i+1]\n        if np.all(np.isnan(q1)) or np.all(np.isclose(q1, 0)) or \\\n           np.all(np.isnan(q2)) or np.all(np.isclose(q2, 0)):\n            angular_dist[i] = 0\n            continue\n        try:\n            r1 = R.from_quat(q1)\n            r2 = R.from_quat(q2)\n            relative_rotation = r1.inv() * r2\n            angle = np.linalg.norm(relative_rotation.as_rotvec())\n            angular_dist[i] = angle\n        except ValueError:\n            angular_dist[i] = 0\n    return angular_dist","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.569319Z","iopub.execute_input":"2025-06-27T19:05:11.569596Z","iopub.status.idle":"2025-06-27T19:05:11.582485Z","shell.execute_reply.started":"2025-06-27T19:05:11.569561Z","shell.execute_reply":"2025-06-27T19:05:11.581778Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# MixUp Generator\nclass MixupGenerator(Sequence):\n    def __init__(self, X, y, batch_size, alpha=0.2):\n        self.X, self.y = X, y\n        self.batch = batch_size\n        self.alpha = alpha\n        self.indices = np.arange(len(X))\n    def __len__(self):\n        return int(np.ceil(len(self.X) / self.batch))\n    def __getitem__(self, i):\n        idx = self.indices[i*self.batch:(i+1)*self.batch]\n        Xb, yb = self.X[idx], self.y[idx]\n        lam = np.random.beta(self.alpha, self.alpha)\n        perm = np.random.permutation(len(Xb))\n        X_mix = lam * Xb + (1-lam) * Xb[perm]\n        y_mix = lam * yb + (1-lam) * yb[perm]\n        return X_mix, y_mix\n    def on_epoch_end(self):\n        np.random.shuffle(self.indices)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.583256Z","iopub.execute_input":"2025-06-27T19:05:11.583440Z","iopub.status.idle":"2025-06-27T19:05:11.600207Z","shell.execute_reply.started":"2025-06-27T19:05:11.583425Z","shell.execute_reply":"2025-06-27T19:05:11.599547Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Simple Attention Layer\nclass SimpleAttention(Layer):\n    def __init__(self, **kwargs):\n        super(SimpleAttention, self).__init__(**kwargs)\n    def build(self, input_shape):\n        self.W = self.add_weight(name='attention_weight',\n                                 shape=(input_shape[-1], 1),\n                                 initializer='glorot_uniform',\n                                 trainable=True)\n        super(SimpleAttention, self).build(input_shape)\n    def call(self, inputs):\n        e = tf.matmul(inputs, self.W)  # (None, time_steps, 1)\n        alpha = tf.nn.softmax(e, axis=1)  # (None, time_steps, 1)\n        context = inputs * alpha  # (None, time_steps, features)\n        context = tf.reduce_sum(context, axis=1)  # (None, features)\n        return context","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.600931Z","iopub.execute_input":"2025-06-27T19:05:11.601118Z","iopub.status.idle":"2025-06-27T19:05:11.613437Z","shell.execute_reply.started":"2025-06-27T19:05:11.601103Z","shell.execute_reply":"2025-06-27T19:05:11.612759Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Two-branch model with Gaussian noise\ndef build_two_branch_model(pad_len, imu_dim, tof_dim, n_classes):\n    inp = Input(shape=(pad_len, imu_dim + tof_dim))\n    x = GaussianNoise(stddev=0.05)(inp)  # Add Gaussian noise to input\n    imu = x[:, :, :imu_dim]\n    tof = x[:, :, imu_dim:]\n    # IMU branch\n    x1 = Conv1D(64, 3, padding='same', use_bias=False, kernel_regularizer=l2(0.01))(imu)\n    x1 = BatchNormalization()(x1)\n    x1 = Activation('relu')(x1)\n    x1 = MaxPooling1D(2)(x1)\n    x1 = Dropout(0.2)(x1)\n    x1 = Conv1D(128, 3, padding='same', use_bias=False, kernel_regularizer=l2(0.01))(x1)\n    x1 = BatchNormalization()(x1)\n    x1 = Activation('relu')(x1)\n    x1 = MaxPooling1D(2)(x1)\n    x1 = Dropout(0.4)(x1)\n    x1 = Bidirectional(LSTM(64, return_sequences=True, kernel_regularizer=l2(0.01)))(x1)\n    x1 = SimpleAttention()(x1)\n    # TOF/Thermal branch\n    x2 = Conv1D(32, 3, padding='same', use_bias=False, kernel_regularizer=l2(0.01))(tof)\n    x2 = BatchNormalization()(x2)\n    x2 = Activation('relu')(x2)\n    x2 = MaxPooling1D(2)(x2)\n    x2 = Dropout(0.2)(x2)\n    x2 = Conv1D(64, 3, padding='same', use_bias=False, kernel_regularizer=l2(0.01))(x2)\n    x2 = BatchNormalization()(x2)\n    x2 = Activation('relu')(x2)\n    x2 = MaxPooling1D(2)(x2)\n    x2 = Dropout(0.4)(x2)\n    x2 = Bidirectional(LSTM(32, return_sequences=True, kernel_regularizer=l2(0.01)))(x2)\n    x2 = SimpleAttention()(x2)\n    # Merge\n    x = Concatenate()([x1, x2])\n    x = Dense(128, activation='relu')(x)\n    x = Dense(64, activation='relu')(x)\n    x = Dropout(0.4)(x)\n    out = Dense(n_classes, activation='softmax')(x)\n    return Model(inp, out)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.614160Z","iopub.execute_input":"2025-06-27T19:05:11.614393Z","iopub.status.idle":"2025-06-27T19:05:11.630176Z","shell.execute_reply.started":"2025-06-27T19:05:11.614371Z","shell.execute_reply":"2025-06-27T19:05:11.629462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Random noise injection\ndef augment_imu(df, noise_std=0.05):\n    print(\"Applying random noise injection to IMU features\")\n    df = df.copy()\n    imu_cols = ['acc_x', 'acc_y', 'acc_z', 'rot_x', 'rot_y', 'rot_z', 'rot_w']\n    noise = np.random.normal(0, noise_std, df[imu_cols].shape)\n    df[imu_cols] += noise\n    # Normalize quaternions after noise\n    rot_cols = ['rot_x', 'rot_y', 'rot_z', 'rot_w']\n    rot_norm = np.sqrt((df[rot_cols]**2).sum(axis=1)).clip(lower=1e-6)\n    df[rot_cols] = df[rot_cols].div(rot_norm, axis=0).fillna(0)\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.630915Z","iopub.execute_input":"2025-06-27T19:05:11.631228Z","iopub.status.idle":"2025-06-27T19:05:11.647568Z","shell.execute_reply.started":"2025-06-27T19:05:11.631214Z","shell.execute_reply":"2025-06-27T19:05:11.646978Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# SpecAugment for IMU features\ndef spec_augment(X, freq_mask=2, time_mask=10, n_freq=1, n_time=1):\n    print(\"Applying SpecAugment to IMU features\")\n    X_aug = X.copy()\n    n_samples, seq_len, n_features = X.shape\n    # Frequency mask\n    for i in range(n_samples):\n        for _ in range(n_freq):\n            f = np.random.randint(0, n_features - freq_mask + 1)\n            X_aug[i, :, f:f+freq_mask] = 0\n        # Time mask\n        for _ in range(n_time):\n            t = np.random.randint(0, seq_len - time_mask + 1)\n            X_aug[i, t:t+time_mask, :] = 0\n    return X_aug","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.648312Z","iopub.execute_input":"2025-06-27T19:05:11.648850Z","iopub.status.idle":"2025-06-27T19:05:11.663273Z","shell.execute_reply.started":"2025-06-27T19:05:11.648834Z","shell.execute_reply":"2025-06-27T19:05:11.662569Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training pipeline\nprint(\"Loading dataset\")\ndf = pd.read_csv(RAW_DIR / \"train.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:11.664006Z","iopub.execute_input":"2025-06-27T19:05:11.664191Z","iopub.status.idle":"2025-06-27T19:05:43.001924Z","shell.execute_reply.started":"2025-06-27T19:05:11.664174Z","shell.execute_reply":"2025-06-27T19:05:43.001242Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Apply noise injection\ndf = augment_imu(df, noise_std=0.05)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:43.002738Z","iopub.execute_input":"2025-06-27T19:05:43.002962Z","iopub.status.idle":"2025-06-27T19:05:43.869275Z","shell.execute_reply.started":"2025-06-27T19:05:43.002946Z","shell.execute_reply":"2025-06-27T19:05:43.868752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"le = LabelEncoder()\ndf['gesture_int'] = le.fit_transform(df['gesture'])\n#np.save(EXPORT_DIR / \"gesture_classes.npy\", le.classes_)\ngesture_classes = le.classes_","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:43.871491Z","iopub.execute_input":"2025-06-27T19:05:43.871673Z","iopub.status.idle":"2025-06-27T19:05:43.962228Z","shell.execute_reply.started":"2025-06-27T19:05:43.871660Z","shell.execute_reply":"2025-06-27T19:05:43.961625Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Feature engineering\nprint(\"Calculating engineered IMU features\")\ndf['acc_energy'] = df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2\ndf['rot_mag'] = np.sqrt(df['rot_x']**2 + df['rot_y']**2 + df['rot_z']**2)\ndf['acc_mag'] = np.sqrt(df['acc_energy'])\ndf['acc_skew'] = df.groupby('sequence_id')['acc_mag'].transform(lambda x: skew(x.fillna(0)))\ndf['rot_smooth'] = df.groupby('sequence_id')['rot_mag'].transform(lambda x: x.rolling(3, min_periods=1).mean().fillna(x.mean()))\n\n# New feature: Jerk\ndf[['jerk_x', 'jerk_y', 'jerk_z']] = df.groupby('sequence_id')[['acc_x', 'acc_y', 'acc_z']].transform(lambda x: x.diff().fillna(0) / dt)\n\n# New features: Linear acceleration\nprint(\"Calculating linear acceleration features\")\nlinear_accel_list = []\nfor _, group in df.groupby('sequence_id'):\n    acc_data_group = group[['acc_x', 'acc_y', 'acc_z']]\n    rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n    linear_accel_group = remove_gravity_from_acc(acc_data_group, rot_data_group)\n    linear_accel_list.append(pd.DataFrame(linear_accel_group, columns=['linear_acc_x', 'linear_acc_y', 'linear_acc_z'], index=group.index))\ndf_linear_accel = pd.concat(linear_accel_list)\ndf = pd.concat([df, df_linear_accel], axis=1)\ndf['linear_acc_mag'] = np.sqrt(df['linear_acc_x']**2 + df['linear_acc_y']**2 + df['linear_acc_z']**2)\ndf['linear_acc_mag_jerk'] = df.groupby('sequence_id')['linear_acc_mag'].transform(lambda x: x.diff().fillna(0) / dt)\n\n# New features: Angular velocity, jerk, and snap\nprint(\"Calculating angular velocity, jerk, and snap from quaternion derivatives\")\nangular_vel_list = []\nangular_jerk_list = []\nangular_snap_list = []\nfor _, group in df.groupby('sequence_id'):\n    rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n    angular_vel_group = calculate_angular_velocity_from_quat(rot_data_group, time_delta=dt)\n    angular_jerk_group = pd.DataFrame(angular_vel_group, columns=['angular_vel_x', 'angular_vel_y', 'angular_vel_z']).diff().fillna(0).values / dt\n    angular_snap_group = pd.DataFrame(angular_jerk_group, columns=['angular_jerk_x', 'angular_jerk_y', 'angular_jerk_z']).diff().fillna(0).values / dt\n    angular_vel_list.append(pd.DataFrame(angular_vel_group, columns=['angular_vel_x', 'angular_vel_y', 'angular_vel_z'], index=group.index))\n    angular_jerk_list.append(pd.DataFrame(angular_jerk_group, columns=['angular_jerk_x', 'angular_jerk_y', 'angular_jerk_z'], index=group.index))\n    angular_snap_list.append(pd.DataFrame(angular_snap_group, columns=['angular_snap_x', 'angular_snap_y', 'angular_snap_z'], index=group.index))\ndf_angular_vel = pd.concat(angular_vel_list)\ndf_angular_jerk = pd.concat(angular_jerk_list)\ndf_angular_snap = pd.concat(angular_snap_list)\ndf = pd.concat([df, df_angular_vel, df_angular_jerk, df_angular_snap], axis=1)\n\n# New features: Angular distance\nprint(\"Calculating angular distance between successive quaternions\")\nangular_distance_list = []\nfor _, group in df.groupby('sequence_id'):\n    rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n    angular_dist_group = calculate_angular_distance(rot_data_group)\n    angular_distance_list.append(pd.DataFrame(angular_dist_group, columns=['angular_distance'], index=group.index))\ndf_angular_distance = pd.concat(angular_distance_list)\ndf = pd.concat([df, df_angular_distance], axis=1)\n\n# TOF features\nprint(\"Calculating TOF features\")\nfor i in range(1, 6):\n    pixel_cols_tof = [f\"tof_{i}_v{p}\" for p in range(64)]\n    tof_sensor_data = df[pixel_cols_tof].replace(-1, np.nan)\n    df[f'tof_{i}_mean'] = tof_sensor_data.mean(axis=1)\n    df[f'tof_{i}_std'] = tof_sensor_data.std(axis=1)\n    df[f'tof_{i}_min'] = tof_sensor_data.min(axis=1)\n    df[f'tof_{i}_max'] = tof_sensor_data.max(axis=1)\n\n# Feature columns\nmeta_cols = {'gesture', 'gesture_int', 'sequence_type', 'behavior', 'orientation', 'row_id', 'subject', 'phase', 'sequence_id', 'sequence_counter'}\nimu_cols = ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z', 'acc_energy', 'rot_mag', 'acc_skew', 'rot_smooth', \n            'jerk_x', 'jerk_y', 'jerk_z', 'linear_acc_x', 'linear_acc_y', 'linear_acc_z', 'linear_acc_mag', \n            'linear_acc_mag_jerk', 'angular_vel_x', 'angular_vel_y', 'angular_vel_z', 'angular_distance',\n            'angular_jerk_x', 'angular_jerk_y', 'angular_jerk_z', 'angular_snap_x', 'angular_snap_y', 'angular_snap_z']\nthm_cols = [f'thm_{i}' for i in range(1, 6)]\ntof_cols = [f'tof_{i}_{stat}' for i in range(1, 6) for stat in ['mean', 'std', 'min', 'max']]\nfinal_feature_cols = imu_cols + thm_cols + tof_cols\nsensor_cols = thm_cols + [f'tof_{i}_v{p}' for i in range(1, 6) for p in range(64)]\nimu_dim_final = len(imu_cols)  # 29\ntof_thm_dim_final = len(thm_cols) + len(tof_cols)  # 5 + 20 = 25\nprint(f\"IMU features: {imu_dim_final} | THM + TOF: {tof_thm_dim_final} | Total: {len(final_feature_cols)} features\")\n#np.save(EXPORT_DIR / \"feature_cols.npy\", np.array(final_feature_cols))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:05:43.963150Z","iopub.execute_input":"2025-06-27T19:05:43.963327Z","iopub.status.idle":"2025-06-27T19:09:27.954585Z","shell.execute_reply.started":"2025-06-27T19:05:43.963313Z","shell.execute_reply":"2025-06-27T19:09:27.953759Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Building sequences with aggregated TOF\")\nseq_gp = df.groupby('sequence_id')\nX_list_unscaled, y_list_int, lens = [], [], []\n\nfor seq_id, seq_df_orig in seq_gp:\n    seq_df = seq_df_orig.copy()\n    for i in range(1, 6):\n        pixel_cols_tof = [f\"tof_{i}_v{p}\" for p in range(64)]\n        tof_data = seq_df[pixel_cols_tof].replace(-1, np.nan)\n        seq_df[f'tof_{i}_mean'] = tof_data.mean(axis=1)\n        seq_df[f'tof_{i}_std'] = tof_data.std(axis=1)\n        seq_df[f'tof_{i}_min'] = tof_data.min(axis=1)\n        seq_df[f'tof_{i}_max'] = tof_data.max(axis=1)\n    mat_unscaled = seq_df[final_feature_cols].ffill().bfill().fillna(0).values.astype('float32')\n    X_list_unscaled.append(mat_unscaled)\n    y_list_int.append(seq_df['gesture_int'].iloc[0])\n    lens.append(len(mat_unscaled))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:09:27.955494Z","iopub.execute_input":"2025-06-27T19:09:27.955806Z","iopub.status.idle":"2025-06-27T19:11:18.596126Z","shell.execute_reply.started":"2025-06-27T19:09:27.955781Z","shell.execute_reply":"2025-06-27T19:11:18.595556Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Fitting StandardScaler\")\nall_steps = np.concatenate(X_list_unscaled, axis=0)\nscaler = StandardScaler().fit(all_steps)\n#joblib.dump(scaler, EXPORT_DIR / \"scaler.pkl\")\ndel all_steps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:11:18.596903Z","iopub.execute_input":"2025-06-27T19:11:18.597128Z","iopub.status.idle":"2025-06-27T19:11:18.895432Z","shell.execute_reply.started":"2025-06-27T19:11:18.597111Z","shell.execute_reply":"2025-06-27T19:11:18.894912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Scaling and padding sequences\")\nX_scaled_list = [scaler.transform(x_seq) for x_seq in X_list_unscaled]\ndel X_list_unscaled\npad_len = int(np.percentile(lens, PAD_PERCENTILE))\n#np.save(EXPORT_DIR / \"sequence_maxlen.npy\", pad_len)\nX = pad_sequences(X_scaled_list, maxlen=pad_len, padding='post', truncating='post', dtype='float32')\ndel X_scaled_list\ny_int = np.array(y_list_int)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:11:18.896149Z","iopub.execute_input":"2025-06-27T19:11:18.896390Z","iopub.status.idle":"2025-06-27T19:11:20.017415Z","shell.execute_reply.started":"2025-06-27T19:11:18.896367Z","shell.execute_reply":"2025-06-27T19:11:20.016883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Applying SpecAugment\")\nX = spec_augment(X, freq_mask=2, time_mask=10, n_freq=1, n_time=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:11:20.018230Z","iopub.execute_input":"2025-06-27T19:11:20.018795Z","iopub.status.idle":"2025-06-27T19:11:20.185097Z","shell.execute_reply.started":"2025-06-27T19:11:20.018772Z","shell.execute_reply":"2025-06-27T19:11:20.184098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Applying SMOTE\")\nn_samples, seq_len, n_features = X.shape\nX_flat = X.reshape(n_samples, seq_len * n_features)\nsmote = SMOTE(random_state=42)\nX_flat_smote, y_int_smote = smote.fit_resample(X_flat, y_int)\nX_smote = X_flat_smote.reshape(-1, seq_len, n_features)\ny_smote = to_categorical(y_int_smote, num_classes=len(gesture_classes))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:11:20.185943Z","iopub.execute_input":"2025-06-27T19:11:20.186491Z","iopub.status.idle":"2025-06-27T19:11:21.242287Z","shell.execute_reply.started":"2025-06-27T19:11:20.186462Z","shell.execute_reply":"2025-06-27T19:11:21.241506Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Splitting data\")\nX_tr, X_val, y_tr, y_val = train_test_split(X_smote, y_smote, test_size=0.1, random_state=42, stratify=y_int_smote)\ndel X_smote, y_smote\n\ncw_vals = compute_class_weight('balanced', classes=np.arange(len(gesture_classes)), y=y_int_smote)\nclass_weight = dict(enumerate(cw_vals))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:11:21.243032Z","iopub.execute_input":"2025-06-27T19:11:21.243230Z","iopub.status.idle":"2025-06-27T19:11:21.324007Z","shell.execute_reply.started":"2025-06-27T19:11:21.243214Z","shell.execute_reply":"2025-06-27T19:11:21.323243Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Building and training model\")\nmodel = build_two_branch_model(pad_len, imu_dim_final, tof_thm_dim_final, len(gesture_classes))\nsteps = len(X_tr) // BATCH_SIZE\n\ntrain_gen = MixupGenerator(X_tr, y_tr, batch_size=BATCH_SIZE, alpha=MIXUP_ALPHA)\n#cb = EarlyStopping(patience=PATIENCE, restore_best_weights=True, verbose=1, monitor='val_accuracy')\nreduce_lr = ReduceLROnPlateau(monitor='val_accuracy', factor=0.5, patience=5, min_lr=1e-6)\n\nmodel.compile(optimizer=Adam(learning_rate=LR_INIT),\n              loss=CategoricalCrossentropy(label_smoothing=0.1),\n              metrics=['accuracy'])\n\nmodel.fit(train_gen, \n          steps_per_epoch=len(train_gen),\n          epochs=EPOCHS,\n          validation_data=(X_val, y_val),\n          class_weight=class_weight,\n          callbacks=[reduce_lr],\n          verbose=1)\n#model.save(EXPORT_DIR / \"gesture_two_branch.h5\")\nprint(\"Training done – model saved\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:11:21.324821Z","iopub.execute_input":"2025-06-27T19:11:21.325026Z","iopub.status.idle":"2025-06-27T19:19:08.052460Z","shell.execute_reply.started":"2025-06-27T19:11:21.325009Z","shell.execute_reply":"2025-06-27T19:19:08.051965Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    df_seq = sequence.to_pandas()\n    \n    # Feature engineering\n    df_seq['acc_energy'] = df_seq['acc_x']**2 + df_seq['acc_y']**2 + df_seq['acc_z']**2\n    df_seq['rot_mag'] = np.sqrt(df_seq['rot_x']**2 + df_seq['rot_y']**2 + df_seq['rot_z']**2)\n    df_seq['acc_mag'] = np.sqrt(df_seq['acc_energy'])\n    df_seq['acc_skew'] = skew(df_seq['acc_mag'].fillna(0))\n    df_seq['rot_smooth'] = df_seq['rot_mag'].rolling(3, min_periods=1).mean().fillna(df_seq['rot_mag'].mean())\n    df_seq[['jerk_x', 'jerk_y', 'jerk_z']] = df_seq[['acc_x', 'acc_y', 'acc_z']].diff().fillna(0) / dt\n    \n    # Linear acceleration\n    linear_accel = remove_gravity_from_acc(df_seq[['acc_x', 'acc_y', 'acc_z']], df_seq[['rot_x', 'rot_y', 'rot_z', 'rot_w']])\n    df_seq[['linear_acc_x', 'linear_acc_y', 'linear_acc_z']] = linear_accel\n    df_seq['linear_acc_mag'] = np.sqrt(df_seq['linear_acc_x']**2 + df_seq['linear_acc_y']**2 + df_seq['linear_acc_z']**2)\n    df_seq['linear_acc_mag_jerk'] = df_seq['linear_acc_mag'].diff().fillna(0)\n    \n    # Angular velocity, jerk, and snap\n    angular_vel = calculate_angular_velocity_from_quat(df_seq[['rot_x', 'rot_y', 'rot_z', 'rot_w']], time_delta=dt)\n    df_seq[['angular_vel_x', 'angular_vel_y', 'angular_vel_z']] = angular_vel\n    df_seq[['angular_jerk_x', 'angular_jerk_y', 'angular_jerk_z']] = pd.DataFrame(angular_vel, columns=['angular_vel_x', 'angular_vel_y', 'angular_vel_z']).diff().fillna(0) / dt\n    df_seq[['angular_snap_x', 'angular_snap_y', 'angular_snap_z']] = df_seq[['angular_jerk_x', 'angular_jerk_y', 'angular_jerk_z']].diff().fillna(0) / dt\n    \n    # Angular distance\n    df_seq['angular_distance'] = 2 * np.arccos(df_seq['rot_w'].clip(-1, 1))\n    \n    # TOF features\n    for i in range(1, 6):\n        pixel_cols_tof = [f\"tof_{i}_v{p}\" for p in range(64)]\n        tof_data = df_seq[pixel_cols_tof].replace(-1, np.nan)\n        df_seq[f'tof_{i}_mean'] = tof_data.mean(axis=1)\n        df_seq[f'tof_{i}_std'] = tof_data.std(axis=1)\n        df_seq[f'tof_{i}_min'] = tof_data.min(axis=1)\n        df_seq[f'tof_{i}_max'] = tof_data.max(axis=1)\n    \n    df_seq_reordered = pd.DataFrame(columns=final_feature_cols)\n    for col in final_feature_cols:\n        if col in df_seq.columns:\n            df_seq_reordered[col] = df_seq[col]\n        else:\n            df_seq_reordered[col] = 0\n    mat_unscaled = df_seq_reordered.ffill().bfill().fillna(0).values.astype('float32')\n    mat_scaled = scaler.transform(mat_unscaled)\n    pad_input = pad_sequences([mat_scaled], maxlen=pad_len, padding='post', truncating='post', dtype='float32')\n    \n    idx = int(np.argmax(model.predict(pad_input, verbose=0)[0]))\n    gesture = str(gesture_classes[idx])\n    print(f\"Predicted gesture: {gesture}\")\n    return gesture","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T19:19:08.053276Z","iopub.execute_input":"2025-06-27T19:19:08.053534Z","iopub.status.idle":"2025-06-27T19:19:08.064343Z","shell.execute_reply.started":"2025-06-27T19:19:08.053512Z","shell.execute_reply":"2025-06-27T19:19:08.063626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import kaggle_evaluation.cmi_inference_server\n\n# Submit\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-27T19:19:08.065036Z","iopub.execute_input":"2025-06-27T19:19:08.065242Z","iopub.status.idle":"2025-06-27T19:19:10.538987Z","shell.execute_reply.started":"2025-06-27T19:19:08.065227Z","shell.execute_reply":"2025-06-27T19:19:10.538285Z"}},"outputs":[],"execution_count":null}]}