{"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":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 数据导入","metadata":{}},{"cell_type":"code","source":"\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import f1_score, classification_report, confusion_matrix\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.manifold import TSNE\nimport warnings\nimport pandas as pd\nimport numpy as np\nimport os\nimport matplotlib\nmatplotlib.use('Agg') # Use non-interactive backend for Kaggle\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nwarnings.filterwarnings('ignore')\n\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 数据准备","metadata":{}},{"cell_type":"code","source":"def load_and_prepare_data():\n    \"\"\"Loads and preprocesses data, returning dataframes and imputation values.\"\"\"\n    print(\"\\n--- Loading and Preparing Data ---\")\n    \n    # Load raw data\n    train_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\n    train_demographics = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\n    test_df = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\n    test_demographics = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv')\n \n    print(f\"Initial train shape: {train_df.shape}\")\n    print(f\"Initial test shape: {test_df.shape}\")\n    \n    # Merge demographics\n    train_df = train_df.merge(train_demographics, on='subject', how='left')\n    test_df = test_df.merge(test_demographics, on='subject', how='left')\n    \n    # Prepare imputation dictionary from training data\n    demo_cols = ['adult_child', 'age', 'sex', 'handedness', 'height_cm', \n                 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']\n    imputation_values = {}\n    for col in demo_cols:\n        if col in train_df.columns:\n            if col in ['adult_child', 'sex', 'handedness']:\n                mode_val = train_df[col].mode()[0] if not train_df[col].mode().empty else 0\n                imputation_values[col] = mode_val\n            else:\n                median_val = train_df[col].median()\n                imputation_values[col] = median_val\n\n    # Apply imputation to both train and test sets\n    for df in [train_df, test_df]:\n        for col, value in imputation_values.items():\n            if col in df.columns:\n                df[col].fillna(value, inplace=True)\n    \n    # Handle sensor data missing values\n    sensor_cols = [col for col in train_df.columns if any(col.startswith(x) for x in ['acc_', 'rot_', 'thm_', 'tof_'])]\n    \n    for df in [train_df, test_df]:\n        for col in sensor_cols:\n            if col in df.columns:\n                if col.startswith('tof_'):\n                    df[col].fillna(-1, inplace=True)  # ToF: -1 for no signal\n                else:\n                    # IMU and Thermopile: forward/backward fill within each sequence\n                    df[col] = df.groupby('sequence_id')[col].fillna(method='ffill').fillna(method='bfill')\n                    df[col].fillna(0, inplace=True)\n    \n    print(\"Data loading and preprocessing complete.\")\n    return train_df, test_df, imputation_values\n\n# Load the data\ntrain_df, test_df, imputation_values = load_and_prepare_data()\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 数据处理","metadata":{}},{"cell_type":"code","source":"def create_sequences(df, max_length=150, min_length=5):\n    \"\"\"Creates time-series sequences from the dataframe.\"\"\"\n    sequences = []\n    labels = []\n    sequence_ids = []\n    \n    imu_cols = [col for col in df.columns if col.startswith(('acc_', 'rot_'))]\n    thermal_cols = [col for col in df.columns if col.startswith('thm_')]\n    tof_cols = [col for col in df.columns if col.startswith('tof_')]\n    sensor_cols = imu_cols + thermal_cols + tof_cols\n    \n    print(f\"\\n--- Creating Sequences ---\")\n    print(f\"Sensor features: IMU={len(imu_cols)}, Thermal={len(thermal_cols)}, ToF={len(tof_cols)}\")\n    \n    for seq_id in df['sequence_id'].unique():\n        seq_data = df[df['sequence_id'] == seq_id].sort_values('sequence_counter')\n        \n        if len(seq_data) < min_length:\n            continue\n            \n        sensor_values = seq_data[sensor_cols].values.astype(np.float32)\n        sensor_values = np.nan_to_num(sensor_values, nan=0.0)\n        sensor_values[sensor_values == -1] = 0\n        \n        if len(sensor_values) > max_length:\n            sensor_values = sensor_values[:max_length]\n        \n        sequences.append(sensor_values)\n        sequence_ids.append(seq_id)\n        \n        if 'gesture' in seq_data.columns:\n            labels.append(seq_data['gesture'].iloc[0])\n    \n    print(f\"Created {len(sequences)} sequences.\")\n    return sequences, labels, sequence_ids, len(imu_cols), len(thermal_cols), len(tof_cols)\n\n# Create train and test sequences\ntrain_sequences, train_labels, train_seq_ids, n_imu, n_thermal, n_tof = create_sequences(train_df)\n# For test sequences, we don't have a minimum length\ntest_sequences, _, test_seq_ids, _, _, _ = create_sequences(test_df, min_length=1)\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 模型","metadata":{}},{"cell_type":"code","source":"class ConvBlock(nn.Module):\n    \"\"\"A 1D convolutional block with BatchNorm and activation.\"\"\"\n    def __init__(self, in_channels, out_channels, kernel_size=3, padding=1):\n        super().__init__()\n        self.conv = nn.Conv1d(in_channels, out_channels, kernel_size=kernel_size, padding=padding)\n        self.bn = nn.BatchNorm1d(out_channels)\n        self.relu = nn.ReLU()\n\n    def forward(self, x):\n        # x shape: (batch, sequence, features)\n        x = x.permute(0, 2, 1)  # -> (batch, features, sequence)\n        x = self.conv(x)\n        x = self.bn(x)\n        x = self.relu(x)\n        return x.permute(0, 2, 1)  # -> (batch, sequence, features)\n\nclass TemporalAttentionModel(nn.Module):\n    \"\"\"A temporal attention model with 1D Convolutions for sensor fusion.\"\"\"\n    \n    def __init__(self, n_imu, n_thermal, n_tof, num_classes, hidden_dim=128, conv_out_dim=64):\n        super().__init__()\n        self.n_imu = n_imu\n        self.n_thermal = n_thermal  \n        self.n_tof = n_tof\n        \n        # 1D Convolutional Blocks to extract features\n        self.imu_conv = ConvBlock(n_imu, conv_out_dim)\n        self.thermal_conv = ConvBlock(n_thermal, conv_out_dim)\n        self.tof_conv = ConvBlock(n_tof, conv_out_dim)\n        \n        # LSTMs to process sequences of convolved features\n        self.imu_encoder = nn.LSTM(conv_out_dim, hidden_dim//2, batch_first=True, bidirectional=True)\n        self.thermal_encoder = nn.LSTM(conv_out_dim, hidden_dim//2, batch_first=True, bidirectional=True)\n        self.tof_encoder = nn.LSTM(conv_out_dim, hidden_dim//2, batch_first=True, bidirectional=True)\n        \n        # New learnable fusion layer\n        self.fusion_layer = nn.Sequential(\n            nn.Linear(hidden_dim * 3, hidden_dim),\n            nn.ReLU(),\n            nn.LayerNorm(hidden_dim)\n        )\n        \n        self.attention = nn.MultiheadAttention(hidden_dim, num_heads=8, batch_first=True)\n        \n        self.classifier = nn.Sequential(\n            nn.LayerNorm(hidden_dim),\n            nn.Dropout(0.2),\n            nn.Linear(hidden_dim, hidden_dim//2),\n            nn.ReLU(),\n            nn.Dropout(0.1),\n            nn.Linear(hidden_dim//2, num_classes)\n        )\n        \n    def forward(self, x, mask=None, return_embedding=False):\n        imu_data = x[:, :, :self.n_imu]\n        thermal_data = x[:, :, self.n_imu:self.n_imu+self.n_thermal]\n        tof_data = x[:, :, self.n_imu+self.n_thermal:self.n_imu+self.n_thermal+self.n_tof]\n        \n        # Apply convolutions\n        imu_conv_out = self.imu_conv(imu_data)\n        thermal_conv_out = self.thermal_conv(thermal_data)\n        tof_conv_out = self.tof_conv(tof_data)\n\n        # Apply LSTMs\n        imu_out, _ = self.imu_encoder(imu_conv_out)\n        thermal_out, _ = self.thermal_encoder(thermal_conv_out) \n        tof_out, _ = self.tof_encoder(tof_conv_out)\n        \n        # Concatenate sensor outputs instead of averaging\n        concatenated_out = torch.cat((imu_out, thermal_out, tof_out), dim=2)\n        \n        # Apply the new fusion layer\n        fused = self.fusion_layer(concatenated_out)\n        \n        attended, _ = self.attention(fused, fused, fused, key_padding_mask=mask)\n        \n        if mask is not None:\n            mask_expanded = (~mask).unsqueeze(-1).float()\n            pooled = (attended * mask_expanded).sum(dim=1) / mask_expanded.sum(dim=1).clamp(min=1e-9)\n        else:\n            pooled = attended.mean(dim=1)\n        \n        output = self.classifier(pooled)\n\n        if return_embedding:\n            return output, pooled\n        return output\n\nprint(\"\\Model success.\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 数据采集","metadata":{}},{"cell_type":"code","source":"MAX_LENGTH = 150\n\nclass CMIDataset(Dataset):\n    \"\"\"Custom PyTorch dataset for CMI data.\"\"\"\n    def __init__(self, sequences, labels=None, max_length=MAX_LENGTH):\n        self.sequences = sequences\n        self.labels = labels\n        self.max_length = max_length\n        \n    def __len__(self):\n        return len(self.sequences)\n    \n    def __getitem__(self, idx):\n        seq = self.sequences[idx]\n        seq_len = len(seq)\n        \n        if seq_len < self.max_length:\n            padding = np.zeros((self.max_length - seq_len, seq.shape[1]), dtype=np.float32)\n            seq = np.vstack([seq, padding])\n            mask = np.concatenate([np.zeros(seq_len), np.ones(self.max_length - seq_len)])\n        else:\n            seq = seq[:self.max_length]\n            mask = np.zeros(self.max_length)\n        \n        result = {\n            'sequence': torch.tensor(seq, dtype=torch.float32),\n            'mask': torch.tensor(mask, dtype=torch.bool)\n        }\n        \n        if self.labels is not None:\n            result['label'] = torch.tensor(self.labels[idx], dtype=torch.long)\n            \n        return result\n\n# Prepare label encoder\nlabel_encoder = LabelEncoder()\nencoded_labels = label_encoder.fit_transform(train_labels)\nnum_classes = len(label_encoder.classes_)\nprint(f\"\\nFound {num_classes} gesture classes.\")\n\n# Create datasets and dataloaders\ntrain_dataset = CMIDataset(train_sequences, encoded_labels)\ntrain_idx, val_idx = train_test_split(range(len(train_dataset)), test_size=0.2, random_state=42, stratify=encoded_labels)\ntrain_subset = torch.utils.data.Subset(train_dataset, train_idx)\nval_subset = torch.utils.data.Subset(train_dataset, val_idx)\n\ntrain_loader = DataLoader(train_subset, batch_size=32, shuffle=True)\nval_loader = DataLoader(val_subset, batch_size=32, shuffle=False)\nprint(f\"Train samples: {len(train_subset)}, Validation samples: {len(val_subset)}\")\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 训练","metadata":{}},{"cell_type":"code","source":"train_subset_labels = [encoded_labels[i] for i in train_idx]\nclass_weights = compute_class_weight(\n    class_weight='balanced',\n    classes=np.unique(train_subset_labels),\n    y=train_subset_labels\n)\nclass_weights_tensor = torch.tensor(class_weights, dtype=torch.float32).to(device)\nprint(f\"\\nCalculated class weights to handle data imbalance.\")\n\nmodel = TemporalAttentionModel(n_imu, n_thermal, n_tof, num_classes).to(device)\noptimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.02)\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)\n\ndef train_epoch(model, loader, optimizer, criterion):\n    model.train()\n    total_loss, correct, total = 0, 0, 0\n    for batch in loader:\n        sequences = batch['sequence'].to(device)\n        labels = batch['label'].to(device)\n        masks = batch['mask'].to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(sequences, masks)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        optimizer.step()\n        \n        total_loss += loss.item()\n        _, predicted = outputs.max(1)\n        total += labels.size(0)\n        correct += predicted.eq(labels).sum().item()\n    return total_loss / len(loader), 100.0 * correct / total\n\ndef validate(model, loader, criterion):\n    model.eval()\n    total_loss = 0\n    all_preds, all_labels = [], []\n    with torch.no_grad():\n        for batch in loader:\n            sequences = batch['sequence'].to(device)\n            labels = batch['label'].to(device)\n            masks = batch['mask'].to(device)\n            \n            outputs = model(sequences, masks)\n            loss = criterion(outputs, labels)\n            \n            total_loss += loss.item()\n            _, predicted = outputs.max(1)\n            all_preds.extend(predicted.cpu().numpy())\n            all_labels.extend(labels.cpu().numpy())\n    \n    f1 = f1_score(all_labels, all_preds, average='weighted')\n    return total_loss / len(loader), f1\n\nprint(\"\\n--- Starting Training ---\")\nbest_val_loss = float('inf')\npatience, no_improve = 10, 0\n\n# Lists to store metrics for plotting\nhistory = {\n    'train_loss': [],\n    'val_loss': [],\n    'val_f1': []\n}\n\nfor epoch in range(50):\n    train_loss, train_acc = train_epoch(model, train_loader, optimizer, criterion)\n    val_loss, val_f1 = validate(model, val_loader, criterion)\n    scheduler.step()\n\n    history['train_loss'].append(train_loss)\n    history['val_loss'].append(val_loss)\n    history['val_f1'].append(val_f1)\n    \n    print(f'Epoch {epoch+1:02d}/50 | Train Loss: {train_loss:.4f}, Acc: {train_acc:.2f}% | Val Loss: {val_loss:.4f}, F1: {val_f1:.4f}')\n    \n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        best_f1_at_min_loss = val_f1\n        torch.save(model.state_dict(), 'best_model.pth')\n        print(f'  -> Validation loss decreased. Model saved. Val F1: {val_f1:.4f}')\n        no_improve = 0\n    else:\n        no_improve += 1\n        \n    if no_improve >= patience:\n        print(f\"Early stopping after {patience} epochs with no improvement in validation loss.\")\n        break\nprint(f\"Finished training. Best validation loss: {best_val_loss:.4f}, with F1 score: {best_f1_at_min_loss:.4f}\")\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 可视化","metadata":{}},{"cell_type":"code","source":"print(\"\\n--- Generating Analysis and Visualizations ---\")\n\n# --- Plotting Training History ---\nepochs_ran = len(history['train_loss'])\nplt.figure(figsize=(12, 5))\n\n# Loss Curve\nplt.subplot(1, 2, 1)\nplt.plot(range(1, epochs_ran + 1), history['train_loss'], label='Train Loss')\nplt.plot(range(1, epochs_ran + 1), history['val_loss'], label='Validation Loss')\nplt.title('Training and Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend()\nplt.grid(True)\n\n# F1 Score Curve\nplt.subplot(1, 2, 2)\nplt.plot(range(1, epochs_ran + 1), history['val_f1'], label='Validation F1 Score', color='orange')\nplt.title('Validation F1 Score')\nplt.xlabel('Epochs')\nplt.ylabel('F1 Score')\nplt.legend()\nplt.grid(True)\n\nplt.tight_layout()\nplt.savefig('training_history.png')\nprint(\"Saved training history plot to training_history.png\")\nplt.close()\n\n\n# --- Detailed Report on Best Model ---\nprint(\"\\n--- Detailed Report on Best Model on Validation Set ---\")\n# Load the best model\nmodel.load_state_dict(torch.load('best_model.pth'))\nmodel.eval()\n\n# Get predictions on the validation set\nall_preds = []\nall_labels = []\nall_embeddings = []\nwith torch.no_grad():\n    for batch in val_loader:\n        sequences = batch['sequence'].to(device)\n        labels = batch['label'].to(device)\n        masks = batch['mask'].to(device)\n        outputs, embeddings = model(sequences, masks, return_embedding=True)\n        _, predicted = outputs.max(1)\n        all_preds.extend(predicted.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        all_embeddings.append(embeddings.cpu().numpy())\n\nall_embeddings = np.concatenate(all_embeddings, axis=0)\n\n# Print Classification Report\nclass_names = label_encoder.classes_\nprint(classification_report(all_labels, all_preds, target_names=class_names))\n\n# Plot Confusion Matrix\ncm = confusion_matrix(all_labels, all_preds)\nplt.figure(figsize=(12, 10))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_names, yticklabels=class_names)\nplt.title('Confusion Matrix for Best Model')\nplt.xlabel('Predicted Label')\nplt.ylabel('True Label')\nplt.savefig('confusion_matrix.png')\nprint(\"Saved confusion matrix plot to confusion_matrix.png\")\nplt.close()\n\n# --- t-SNE Visualization ---\nprint(\"\\n--- Generating t-SNE visualization of embeddings ---\")\ntsne = TSNE(n_components=2, verbose=1, perplexity=40, n_iter=300, random_state=42)\ntsne_results = tsne.fit_transform(all_embeddings)\n\nplt.figure(figsize=(16, 12))\nscatter = plt.scatter(tsne_results[:,0], tsne_results[:,1], c=all_labels, cmap='viridis', alpha=0.7)\nplt.title('t-SNE Visualization of Validation Set Embeddings')\nplt.xlabel('t-SNE Component 1')\nplt.ylabel('t-SNE Component 2')\n# Create a legend\nlegend1 = plt.legend(handles=scatter.legend_elements(num=len(class_names))[0], labels=list(class_names), title=\"Classes\")\nplt.gca().add_artist(legend1)\nplt.grid(True)\nplt.savefig('tsne_embeddings.png')\nprint(\"Saved t-SNE plot to tsne_embeddings.png\")\nplt.close()\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 调用kaggle推理API 上传","metadata":{}},{"cell_type":"code","source":"import polars as pl\nimport kaggle_evaluation.cmi_inference_server\nimport sys\nimport traceback\n\nprint(\"\\n--- Setting up Inference Server ---\")\n\n# Load the best model trained in the previous steps\nmodel.load_state_dict(torch.load('best_model.pth'))\nmodel.to(device)\nmodel.eval()\nprint(\"Best model loaded for inference.\")\n\n# Get the list of sensor columns used during training\nsensor_cols = [col for col in train_df.columns if any(col.startswith(x) for x in ['acc_', 'rot_', 'thm_', 'tof_'])]\n\ndef preprocess_for_inference(df_seq, df_demo):\n    \"\"\"Preprocesses a single sequence for inference, mirroring the training logic.\"\"\"\n    df = df_seq.merge(df_demo, on='subject', how='left')\n\n    # Fill demo NaNs using pre-calculated values from the training set\n    for col, value in imputation_values.items():\n        if col in df.columns:\n            df[col].fillna(value, inplace=True)\n            \n    # Fill any remaining NaNs (if columns were missing in original test_demo)\n    demo_cols_from_train = list(imputation_values.keys())\n    for col in demo_cols_from_train:\n        if col in df.columns:\n            df[col].fillna(0, inplace=True)\n\n    # Fill sensor NaNs\n    for col in sensor_cols:\n        if col in df.columns:\n            df[col] = df[col].ffill().bfill() # ffill/bfill for single sequence\n            df[col].fillna(0, inplace=True)\n        else:\n            # If a sensor column from training is missing, add it with zeros\n            # matching the length of the current sequence.\n            df[col] = np.zeros(len(df_seq))\n\n    # Extract sensor data in the correct order\n    sensor_data_list = [df[col].values for col in sensor_cols]\n    sensor_data = np.stack(sensor_data_list, axis=1).astype(np.float32)\n    sensor_data = np.nan_to_num(sensor_data, nan=0.0)\n    sensor_data[sensor_data == -1] = 0\n\n    # Padding / Truncating\n    seq_len = len(sensor_data)\n    mask = np.zeros(MAX_LENGTH, dtype=bool)\n\n    if seq_len > MAX_LENGTH:\n        sensor_data = sensor_data[:MAX_LENGTH]\n    elif seq_len < MAX_LENGTH:\n        padding = np.zeros((MAX_LENGTH - seq_len, sensor_data.shape[1]), dtype=np.float32)\n        sensor_data = np.vstack([sensor_data, padding])\n        mask[seq_len:] = True\n\n    return torch.tensor(sensor_data, dtype=torch.float32).unsqueeze(0), torch.tensor(mask, dtype=torch.bool).unsqueeze(0)\n\n\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"The main prediction function for the Kaggle server.\n    This function is wrapped in a try/except block to be robust against all errors.\n    \"\"\"\n    try:\n        df_seq = sequence.to_pandas()\n        df_demo = demographics.to_pandas()\n\n        sequence_tensor, mask_tensor = preprocess_for_inference(df_seq, df_demo)\n        sequence_tensor = sequence_tensor.to(device)\n        mask_tensor = mask_tensor.to(device)\n\n        with torch.no_grad():\n            outputs = model(sequence_tensor, mask_tensor)\n            _, predicted_idx = outputs.max(1)\n            prediction = label_encoder.inverse_transform(predicted_idx.cpu().numpy())[0]\n        \n        return str(prediction)\n\n    except Exception as e:\n        # This block makes the function robust. In case of any failure,\n        # it prints the error to stderr (for debugging if visible) and\n        # returns a default valid gesture. This prevents the gateway from crashing.\n        print(\"--- PREDICTION FAILED ---\", file=sys.stderr)\n        traceback.print_exc(file=sys.stderr)\n        print(\"--------------------------\", file=sys.stderr)\n        return \"Drink\"\n\n\n# --- Start Server ---\n# This part of the code will only run when submitted to Kaggle.\n# For local testing, it will run a local gateway.\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    print(\"Competition environment detected. Starting server...\")\n    inference_server.serve()\nelse:\n    print(\"Local environment detected. Running local gateway for testing...\")\n    # The local gateway simulates the Kaggle environment with the provided test files.\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    print(\"Local gateway finished.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}