{"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":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import (\n    Input, Conv1D, BatchNormalization, Activation, Add, \n    Bidirectional, LSTM, Dropout, Dense, GlobalAveragePooling1D,\n    GaussianNoise, MultiHeadAttention, Concatenate\n)\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping\nfrom tensorflow.keras.preprocessing.sequence import pad_sequences\nfrom tensorflow.keras.utils import to_categorical\n#score 0.63 - 0.65, good luck for you. Upvote for me if you like it \nimport os\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom tensorflow.keras.models import Sequential, load_model\nfrom tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.preprocessing.sequence import pad_sequences\nfrom tensorflow.keras.layers import LSTM, Bidirectional, Attention, GlobalAveragePooling1D\nfrom tensorflow.keras.optimizers import AdamW\nfrom tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping\nimport tensorflow as tf\nimport polars as pl\nimport kaggle_evaluation.cmi_inference_server\nprint(\"Imports loaded\")\nfrom tensorflow.keras.layers import Input, Add, LayerNormalization, MultiHeadAttention\nfrom tensorflow.keras.models import Model\n\nimport pandas as pd\nimport numpy as np\nfrom sklearn.preprocessing import StandardScaler\nfrom tensorflow.keras.preprocessing.sequence import pad_sequences\n# Custom layer definitions\nclass SelfAttention(tf.keras.layers.Layer):\n    def __init__(self, **kwargs):\n        super(SelfAttention, self).__init__(**kwargs)\n\n    def build(self, input_shape):\n        self.Wq = self.add_weight(shape=(input_shape[-1], input_shape[-1]), \n                                  initializer='glorot_uniform', \n                                  trainable=True)\n        self.Wk = self.add_weight(shape=(input_shape[-1], input_shape[-1]), \n                                  initializer='glorot_uniform', \n                                  trainable=True)\n        self.Wv = self.add_weight(shape=(input_shape[-1], input_shape[-1]), \n                                  initializer='glorot_uniform', \n                                  trainable=True)\n        super(SelfAttention, self).build(input_shape)\n\n    def call(self, inputs):\n        Q = tf.matmul(inputs, self.Wq)\n        K = tf.matmul(inputs, self.Wk)\n        V = tf.matmul(inputs, self.Wv)\n        scores = tf.matmul(Q, K, transpose_b=True) / tf.math.sqrt(tf.cast(tf.shape(K)[-1], tf.float32))\n        weights = tf.nn.softmax(scores, axis=-1)\n        return tf.matmul(weights, V)\n\n# Residual blocks\ndef residual_block_tof(x, filters, kernel_size=3, dilation_rate=1):\n    shortcut = x\n    x = Conv1D(filters, kernel_size, padding='same', dilation_rate=dilation_rate)(x)\n    x = BatchNormalization()(x)\n    x = Activation('relu')(x)\n    x = Conv1D(filters, kernel_size, padding='same', dilation_rate=dilation_rate)(x)\n    x = BatchNormalization()(x)\n    x = Add()([shortcut, x])\n    return Activation('relu')(x)\n\ndef residual_block_imu(x, filters):\n    shortcut = Conv1D(filters, 1, padding='same')(x) if x.shape[-1] != filters else x\n    x = Conv1D(filters, 3, padding='same', activation='relu')(x)\n    x = Conv1D(filters, 3, padding='same')(x)\n    return Activation('relu')(Add()([shortcut, x]))\n\n# Data preparation\ndef prepare_data(df):\n    # Encode labels\n    label_encoder = LabelEncoder()\n    df['gesture'] = label_encoder.fit_transform(df['gesture'].astype(str))\n    gesture_classes = label_encoder.classes_\n    \n    # Define feature sets\n    tof_cols = [col for col in df.columns if col.startswith('tof_')]\n    \n    excluded_cols = {'gesture', 'sequence_type', 'behavior', 'orientation', \n                    'row_id', 'subject', 'phase', 'sequence_id', 'sequence_counter'}\n    excluded_cols.update([col for col in df.columns if col.startswith(('thm_', 'tof_'))])\n    imu_cols = [col for col in df.columns if col not in excluded_cols]\n    \n    # Initialize scalers\n    tof_scaler = StandardScaler()\n    tof_scaler.fit(df[tof_cols].dropna())\n    \n    imu_scaler = StandardScaler()\n    imu_scaler.fit(df[imu_cols].dropna())\n    np.save(\"tof_scaler_mean.npy\", tof_scaler.mean_)\n    np.save(\"tof_scaler_scale.npy\", tof_scaler.scale_)\n    np.save(\"imu_scaler_mean.npy\", imu_scaler.mean_)\n    np.save(\"imu_scaler_scale.npy\", imu_scaler.scale_)\n    # Process sequences\n    sequences = df.groupby('sequence_id')\n    X_tof, X_imu, y, seq_ids = [], [], [], []\n    tof_lengths, imu_lengths = [], []\n\n    for seq_id, seq_df in sequences:\n        # Process ToF data\n        tof_data = seq_df[tof_cols].ffill().bfill().fillna(0)\n        X_tof.append(tof_scaler.transform(tof_data))\n        tof_lengths.append(len(tof_data))\n        \n        # Process IMU data\n        imu_data = seq_df[imu_cols].ffill().bfill().fillna(0)\n        X_imu.append(imu_scaler.transform(imu_data))\n        imu_lengths.append(len(imu_data))\n        \n        # Store labels and IDs\n        y.append(seq_df['gesture'].iloc[0])\n        seq_ids.append(seq_id)\n    \n    # Pad sequences\n    tof_pad_len = int(np.percentile(tof_lengths, 90))\n    imu_pad_len = int(np.percentile(imu_lengths, 90))\n    \n    X_tof = pad_sequences(X_tof, maxlen=tof_pad_len, dtype='float32', padding='post', truncating='post')\n    X_imu = pad_sequences(X_imu, maxlen=imu_pad_len, dtype='float32', padding='post', truncating='post')\n    \n    # Convert labels\n    y = to_categorical(y, num_classes=len(gesture_classes))\n    \n    return X_tof, X_imu, y, tof_pad_len, imu_pad_len, gesture_classes\n\n# Model architecture\ndef create_multimodal_model(tof_input_shape, imu_input_shape, num_classes):\n    # ToF branch\n    tof_input = Input(shape=tof_input_shape, name='tof_input')\n    x_tof = Conv1D(64, 3, padding='same', activation='relu')(tof_input)\n    x_tof = residual_block_tof(x_tof, 64)\n    x_tof = residual_block_tof(x_tof, 64, dilation_rate=2)\n    x_tof = residual_block_tof(x_tof, 64, dilation_rate=4)\n    x_tof = Bidirectional(LSTM(64, return_sequences=True))(x_tof)\n    x_tof = SelfAttention()(x_tof)\n    tof_branch = GlobalAveragePooling1D()(x_tof)\n    \n    # IMU branch\n    imu_input = Input(shape=imu_input_shape, name='imu_input')\n    x_imu = GaussianNoise(0.1)(imu_input)\n    x_imu = Conv1D(64, 3, padding='same', activation='relu')(x_imu)\n    x_imu = residual_block_imu(x_imu, 64)\n    x_imu = MaxPooling1D(2)(x_imu)\n    x_imu = residual_block_imu(x_imu, 128)\n    x_imu = MaxPooling1D(2)(x_imu)\n    x_imu = Bidirectional(LSTM(128, return_sequences=True, dropout=0.3, recurrent_dropout=0.3))(x_imu)\n    imu_branch = GlobalAveragePooling1D()(x_imu)\n    \n    # Combined model\n    combined = Concatenate()([tof_branch, imu_branch])\n    combined = Dense(384, activation='relu')(combined)\n    combined = Dropout(0.5)(combined)\n    combined = Dense(192, activation='relu')(combined)\n    combined = Dropout(0.3)(combined)\n    output = Dense(num_classes, activation='softmax')(combined)\n    \n    model = Model(inputs=[tof_input, imu_input], outputs=output)\n    model.compile(\n        optimizer=Adam(learning_rate=0.001),\n        loss='categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    return model\n\n# Main workflow\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\nX_tof, X_imu, y, tof_pad_len, imu_pad_len, gesture_classes = prepare_data(df)\n# Train-validation split\n(X_tof_train, X_tof_val,X_imu_train, X_imu_val, \ny_train, y_val) = train_test_split(X_tof, X_imu, y, test_size=0.2, random_state=42)\n    \n    # Create model\nmodel = create_multimodal_model(\n        tof_input_shape=(tof_pad_len, X_tof_train.shape[2]),\n        imu_input_shape=(imu_pad_len, X_imu_train.shape[2]),\n        num_classes=y_train.shape[1]\n)\n    \n    # Train model\ncallbacks = [\n        ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5),\n        EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True)\n    ]\n    \nhistory = model.fit(\n        [X_tof_train, X_imu_train],\n        y_train,\n        epochs=60,\n        batch_size=64,\n        validation_data=([X_tof_val, X_imu_val], y_val),\n        callbacks=callbacks\n    )\n    \n    # Save model and configuration\nmodel.save(\"multimodal_model_2.h5\")\nnp.save(\"tof_pad_len.npy\", tof_pad_len)\nnp.save(\"imu_pad_len.npy\", imu_pad_len)\nnp.save(\"gesture_classes.npy\", gesture_classes)\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from cmi_2025_metric_copy_for_import import CompetitionMetric\nimport numpy as np\nimport pandas as pd\n\n# Get predicted labels for the validation set\nprint(\"Predicting on validation set...\")\ny_val_pred_probs = model.predict([X_tof_val, X_imu_val], verbose=0)  # Dual input\ny_val_pred = np.argmax(y_val_pred_probs, axis=1)\ny_val_true = np.argmax(y_val, axis=1)\nval_pred_labels = pd.Series(y_val_pred).map(lambda i: gesture_classes[i])\nval_true_labels = pd.Series(y_val_true).map(lambda i: gesture_classes[i])\n\n# Build DataFrames for the metric\nval_submission = pd.DataFrame({'gesture': val_pred_labels})\nval_solution = pd.DataFrame({'gesture': val_true_labels})\n\n# Run competition metric\nmetric = CompetitionMetric()\nscore = metric.calculate_hierarchical_f1(val_solution, val_submission)\nprint(f\"Estimated leaderboard (val) score: {score:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import polars as pl\nimport numpy as np\nimport pandas as pd\nfrom tensorflow.keras.models import load_model\nfrom tensorflow.keras.preprocessing.sequence import pad_sequences\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    # Convert to pandas\n    df_seq = sequence.to_pandas()\n    \n    # Load configuration\n    gesture_classes = np.load(\"/kaggle/working/gesture_classes.npy\", allow_pickle=True)\n    tof_pad_len = int(np.load(\"/kaggle/working/tof_pad_len.npy\"))\n    imu_pad_len = int(np.load(\"/kaggle/working/imu_pad_len.npy\"))\n    \n    # Define feature columns\n    tof_cols = [col for col in df_seq.columns if col.startswith('tof_')]\n    \n    excluded_cols = {'sequence_type', 'behavior', 'orientation', \n                    'row_id', 'subject', 'phase', 'sequence_id', 'sequence_counter'}\n    excluded_cols.update([col for col in df_seq.columns if col.startswith('thm_')])\n    imu_cols = [col for col in df_seq.columns if col not in excluded_cols and not col.startswith('tof_')]\n    \n    # Load scaler parameters\n    tof_mean = np.load(\"/kaggle/working/tof_scaler_mean.npy\")\n    tof_scale = np.load(\"/kaggle/working/tof_scaler_scale.npy\")\n    imu_mean = np.load(\"/kaggle/working/imu_scaler_mean.npy\")\n    imu_scale = np.load(\"/kaggle/working/imu_scaler_scale.npy\")\n    \n    # Handle zero-variance features\n    tof_scale[tof_scale == 0] = 1.0\n    imu_scale[imu_scale == 0] = 1.0\n    \n    # Preprocess ToF data\n    tof_data = df_seq[tof_cols].copy().ffill().bfill().fillna(0)\n    tof_processed = (tof_data - tof_mean) / tof_scale\n    \n    # Preprocess IMU data\n    imu_data = df_seq[imu_cols].copy().ffill().bfill().fillna(0)\n    imu_processed = (imu_data - imu_mean) / imu_scale\n    \n    # Pad sequences\n    tof_padded = pad_sequences(\n        [tof_processed.values], \n        maxlen=tof_pad_len, \n        dtype='float32', \n        padding='post', \n        truncating='post'\n    )\n    \n    imu_padded = pad_sequences(\n        [imu_processed.values], \n        maxlen=imu_pad_len, \n        dtype='float32', \n        padding='post', \n        truncating='post'\n    )\n    \n    # Load model and predict\n    # model = load_model(\"/kaggle/working/multimodal_model.h5\")\n    prediction = model.predict([tof_padded, imu_padded], verbose=0)\n    predicted_index = np.argmax(prediction, axis=1)[0]\n    \n    return gesture_classes[predicted_index]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Launch inference 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},"outputs":[],"execution_count":null}]}