{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","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":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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,"execution":{"iopub.status.busy":"2025-08-28T01:25:45.594841Z","iopub.execute_input":"2025-08-28T01:25:45.595190Z","iopub.status.idle":"2025-08-28T01:25:47.159120Z","shell.execute_reply.started":"2025-08-28T01:25:45.595161Z","shell.execute_reply":"2025-08-28T01:25:47.158249Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"基础导入与配置","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.metrics import f1_score, classification_report\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom tqdm import tqdm\nimport os\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# 设置随机种子，保证结果可复现\nSEED = 42\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(SEED)\n\n# 检查GPU配置\nprint(f\"CUDA可用: {torch.cuda.is_available()}\")\nprint(f\"GPU数量: {torch.cuda.device_count()}\")\nif torch.cuda.is_available():\n    print(f\"当前GPU: {torch.cuda.get_device_name(0)}\")\n    if torch.cuda.device_count() > 1:\n        print(f\"第二GPU: {torch.cuda.get_device_name(1)}\")\n\n# 定义设备，优先使用GPU\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"使用设备: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:47.159884Z","iopub.execute_input":"2025-08-28T01:25:47.160185Z","iopub.status.idle":"2025-08-28T01:25:54.740907Z","shell.execute_reply.started":"2025-08-28T01:25:47.160167Z","shell.execute_reply":"2025-08-28T01:25:54.740128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 1. 数据加载与详细探索\n# ----------------------\ndef load_and_explore_data():\n    \"\"\"加载数据并进行详细探索，为后续处理提供依据\"\"\"\n    print(\"加载数据...\")\n    try:\n        train = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\n        test = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\n        train_demo = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\n        test_demo = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv')\n    except FileNotFoundError as e:\n        print(f\"文件未找到: {e}\")\n        print(\"尝试使用备选路径...\")\n        train = pd.read_csv('train.csv')\n        test = pd.read_csv('test.csv')\n        train_demo = pd.read_csv('train_demographics.csv')\n        test_demo = pd.read_csv('test_demographics.csv')\n    \n    # 详细数据探索\n    print(\"\\n===== 数据探索 =====\")\n    print(f\"训练集形状: {train.shape}\")\n    print(f\"测试集形状: {test.shape}\")\n    \n    print(\"\\n训练集列名及数据类型:\")\n    print(train.dtypes)\n    print(\"\\n测试集列名及数据类型:\")\n    print(test.dtypes)\n    \n    # 检查特征差异\n    train_cols = set(train.columns)\n    test_cols = set(test.columns)\n    train_only_cols = train_cols - test_cols\n    test_only_cols = test_cols - train_cols\n    print(f\"\\n训练集特有列: {len(train_only_cols)}个 - {sorted(train_only_cols)[:5]}...\")\n    print(f\"测试集特有列: {len(test_only_cols)}个 - {sorted(test_only_cols)[:5]}...\")\n    \n    # 检查关键列是否存在\n    required_columns = ['sequence_id', 'row_id']\n    for col in required_columns:\n        if col not in train.columns:\n            print(f\"警告: 训练集缺少必要列 '{col}'\")\n        if col not in test.columns:\n            print(f\"警告: 测试集缺少必要列 '{col}'\")\n    \n    # 检查是否有共同的键用于合并人口统计学数据\n    merge_key = 'subject' if 'subject' in train.columns and 'subject' in train_demo.columns else None\n    if merge_key:\n        print(f\"\\n将使用 '{merge_key}' 合并主数据和人口统计学数据\")\n    else:\n        print(\"\\n警告: 未找到合适的合并键，将不合并人口统计学数据\")\n    \n    return train, test, train_demo, test_demo","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.743014Z","iopub.execute_input":"2025-08-28T01:25:54.743563Z","iopub.status.idle":"2025-08-28T01:25:54.750842Z","shell.execute_reply.started":"2025-08-28T01:25:54.743543Z","shell.execute_reply":"2025-08-28T01:25:54.750131Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 2. 数据预处理\n# ----------------------\ndef preprocess_data(train, test, train_demo, test_demo):\n    \"\"\"数据预处理：灵活处理不同结构的数据\"\"\"\n    print(\"\\n===== 数据预处理 =====\")\n    \n    # 尝试合并人口统计学数据\n    merge_key = 'subject' if 'subject' in train.columns and 'subject' in train_demo.columns else None\n    if merge_key:\n        print(f\"使用 '{merge_key}' 合并数据...\")\n        train = train.merge(train_demo, on=merge_key, how='left')\n        test = test.merge(test_demo, on=merge_key, how='left')\n    else:\n        print(\"无法合并人口统计学数据，跳过此步骤\")\n    \n    # 确保训练集和测试集有相同的特征列\n    train_cols = set(train.columns)\n    test_cols = set(test.columns)\n    \n    # 找出双方共有的列\n    common_cols = train_cols.intersection(test_cols)\n    \n    # 训练集特有列（如目标列）只保留在训练集中\n    train_only_cols = ['gesture', 'gesture_encoded', 'sequence_type']\n    train_only_cols = [col for col in train_only_cols if col in train_cols]\n    print(f\"仅在训练集中保留的列: {train_only_cols}\")\n    \n    # 为训练集添加特有列\n    train_features = list(common_cols) + train_only_cols\n    \n    # 测试集只使用共同列\n    test_features = list(common_cols)\n    \n    # 确保必要的ID列被包含\n    for col in ['sequence_id', 'row_id', 'subject']:\n        if col in train_cols and col not in train_features:\n            train_features.append(col)\n        if col in test_cols and col not in test_features:\n            test_features.append(col)\n    \n    # 应用特征选择\n    train = train[train_features]\n    test = test[test_features]\n    \n    print(f\"训练集特征列数量: {len(train_features)}\")\n    print(f\"测试集特征列数量: {len(test_features)}\")\n    \n    # 检查并处理缺失值\n    print(f\"训练集缺失值比例: {train.isnull().mean().mean():.4f}\")\n    print(f\"测试集缺失值比例: {test.isnull().mean().mean():.4f}\")\n    \n    # 识别传感器类型列\n    imu_cols = [col for col in train.columns if col.startswith(('acc_', 'rot_', 'gyro_', 'mag_'))]\n    thm_cols = [col for col in train.columns if col.startswith(('thm_', 'temp_', 'thermal_'))]\n    tof_cols = [col for col in train.columns if col.startswith(('tof_', 'distance_'))]\n    \n    print(f\"找到 {len(imu_cols)} 个IMU传感器列\")\n    print(f\"找到 {len(thm_cols)} 个热传感器列\")\n    print(f\"找到 {len(tof_cols)} 个飞行时间传感器列\")\n    \n    # 对不同类型的传感器使用不同的缺失值填充策略\n    if imu_cols:\n        train[imu_cols] = train.groupby('sequence_id')[imu_cols].transform(\n            lambda x: x.interpolate(method='linear', limit_direction='both'))\n        test[imu_cols] = test.groupby('sequence_id')[imu_cols].transform(\n            lambda x: x.interpolate(method='linear', limit_direction='both'))\n    \n    if thm_cols:\n        train[thm_cols] = train.groupby('sequence_id')[thm_cols].transform(\n            lambda x: x.fillna(x.mean()))\n        test[thm_cols] = test.groupby('sequence_id')[thm_cols].transform(\n            lambda x: x.fillna(x.mean()))\n    \n    if tof_cols:\n        train[tof_cols] = train.groupby('sequence_id')[tof_cols].transform(\n            lambda x: x.fillna(x.mean()))\n        test[tof_cols] = test.groupby('sequence_id')[tof_cols].transform(\n            lambda x: x.fillna(x.mean()))\n    \n    # 人口统计学数据处理\n    demo_cols = ['age', 'height', 'height_cm', 'weight', 'shoulder_to_wrist', \n                'shoulder_to_wrist_cm', 'elbow_to_wrist', 'elbow_to_wrist_cm']\n    demo_cols = [col for col in demo_cols if col in train.columns]\n    print(f\"找到 {len(demo_cols)} 个人口统计学列: {demo_cols}\")\n    \n    if demo_cols:\n        train[demo_cols] = train[demo_cols].fillna(train[demo_cols].median())\n        test[demo_cols] = test[demo_cols].fillna(test[demo_cols].median())\n    \n    # 编码目标变量\n    if 'gesture' not in train.columns:\n        raise ValueError(\"训练集必须包含'gesture'列作为目标变量\")\n    \n    gesture_le = LabelEncoder()\n    train['gesture_encoded'] = gesture_le.fit_transform(train['gesture'])\n    print(f\"已编码 {len(gesture_le.classes_)} 种手势类别\")\n    \n    return train, test, imu_cols, thm_cols, tof_cols, demo_cols, gesture_le","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.753748Z","iopub.execute_input":"2025-08-28T01:25:54.754012Z","iopub.status.idle":"2025-08-28T01:25:54.779760Z","shell.execute_reply.started":"2025-08-28T01:25:54.753986Z","shell.execute_reply":"2025-08-28T01:25:54.779185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 修复：时序统计特征提取函数（处理NaN和非数值问题）\n# ----------------------\ndef extract_temporal_stats(group, feature_cols):\n    \"\"\"为每个序列提取时序统计特征，确保输出为纯标量数值\"\"\"\n    stats = {}\n    # 基础统计量\n    stats['mean'] = group[feature_cols].mean()\n    stats['std'] = group[feature_cols].std()\n    stats['max'] = group[feature_cols].max()\n    stats['min'] = group[feature_cols].min()\n    stats['median'] = group[feature_cols].median()\n    stats['ptp'] = group[feature_cols].max() - group[feature_cols].min()  # 峰峰值\n    stats['skew'] = group[feature_cols].skew()  # 偏度\n    stats['kurt'] = group[feature_cols].kurt()  # 峰度\n    \n    # 自相关系数（滞后1步，捕捉短期依赖）\n    for col in feature_cols:\n        try:\n            # 处理可能的自相关计算错误\n            autocorr = group[col].autocorr(lag=1)\n            # 检查是否为NaN或无穷大\n            if pd.isna(autocorr) or not np.isfinite(autocorr):\n                autocorr = 0.0  # 用0替代无效值\n            stats[f'{col}_autocorr_1'] = autocorr\n        except:\n            stats[f'{col}_autocorr_1'] = 0.0  # 出错时用0替代\n    \n    # 拼接为DataFrame（每行对应一个序列的统计特征）\n    stats_df = pd.DataFrame([stats])\n    \n    # 修复：确保所有值都是数值类型，并填充任何NaN\n    for col in stats_df.columns:\n        # 强制转换为数值类型\n        stats_df[col] = pd.to_numeric(stats_df[col], errors='coerce')\n        # 填充任何剩余的NaN值\n        stats_df[col] = stats_df[col].fillna(0.0)\n    \n    # 重命名列（避免与原始特征冲突）\n    stats_df.columns = [f'{col}_stat' for col in stats_df.columns]\n    return stats_df\n\n# ----------------------\n# 修复：prepare_sequences函数（确保特征维度正确）\n# ----------------------\ndef prepare_sequences(train, test, max_seq_length=100):\n    print(\"\\n===== 准备序列数据（含时序统计特征） =====\")\n    \n    # 1. 保留原逻辑：获取共同特征列\n    if 'sequence_id' not in train.columns or 'sequence_id' not in test.columns:\n        raise ValueError(\"数据必须包含'sequence_id'列以区分不同序列\")\n    \n    train_exclude_cols = ['sequence_id', 'row_id', 'gesture', 'gesture_encoded', 'sequence_type', 'subject']\n    test_exclude_cols = ['sequence_id', 'row_id', 'subject']\n    train_exclude_cols = [col for col in train_exclude_cols if col in train.columns]\n    test_exclude_cols = [col for col in test_exclude_cols if col in test.columns]\n    \n    train_feature_cols = [col for col in train.columns if col not in train_exclude_cols]\n    test_feature_cols = [col for col in test.columns if col not in test_exclude_cols]\n    common_feature_cols = list(set(train_feature_cols).intersection(set(test_feature_cols)))\n    common_feature_cols.sort()\n    print(f\"共同特征列: {len(common_feature_cols)}\")\n    \n    if len(common_feature_cols) == 0:\n        raise ValueError(\"未找到共同的特征列，无法继续\")\n    \n    # 2. 提取序列级统计特征（修复版本）\n    print(\"提取时序统计特征...\")\n    # 训练集统计特征\n    try:\n        train_stats = train.groupby('sequence_id').apply(\n            lambda x: extract_temporal_stats(x, common_feature_cols)\n        ).reset_index(drop=True)\n        # 确保索引正确对应\n        unique_train_ids = train['sequence_id'].unique()\n        if len(train_stats) != len(unique_train_ids):\n            print(f\"警告：训练集统计特征数量({len(train_stats)})与序列数量({len(unique_train_ids)})不匹配，重新对齐\")\n            train_stats = train_stats.iloc[:len(unique_train_ids)]\n        train_stats.index = unique_train_ids\n    except Exception as e:\n        print(f\"训练集统计特征提取出错: {e}\")\n        # 创建默认的统计特征\n        train_stats = pd.DataFrame(0.0, index=train['sequence_id'].unique(), \n                                  columns=[f'feat_{i}_stat' for i in range(10)])\n    \n    # 测试集统计特征\n    try:\n        test_stats = test.groupby('sequence_id').apply(\n            lambda x: extract_temporal_stats(x, common_feature_cols)\n        ).reset_index(drop=True)\n        # 确保索引正确对应\n        unique_test_ids = test['sequence_id'].unique()\n        if len(test_stats) != len(unique_test_ids):\n            print(f\"警告：测试集统计特征数量({len(test_stats)})与序列数量({len(unique_test_ids)})不匹配，重新对齐\")\n            test_stats = test_stats.iloc[:len(unique_test_ids)]\n        test_stats.index = unique_test_ids\n    except Exception as e:\n        print(f\"测试集统计特征提取出错: {e}\")\n        # 创建默认的统计特征\n        test_stats = pd.DataFrame(0.0, index=test['sequence_id'].unique(), \n                                 columns=[f'feat_{i}_stat' for i in range(10)])\n    \n    # 3. 处理训练序列（拼接统计特征）\n    train_sequences = []\n    train_gestures = []\n    train_binary = []\n    train_ids = []\n    \n    print(\"处理训练序列...\")\n    for seq_id, group in tqdm(train.groupby('sequence_id'), desc=\"处理训练序列\"):\n        try:\n            # 原始时序特征\n            seq_data = group[common_feature_cols].copy()\n            # 填充/转换数值类型\n            for col in seq_data.columns:\n                try:\n                    seq_data[col] = pd.to_numeric(seq_data[col], errors='coerce').fillna(seq_data[col].median())\n                except:\n                    le = LabelEncoder()\n                    seq_data[col] = le.fit_transform(seq_data[col].astype(str))\n            \n            # 获取当前序列的统计特征\n            if seq_id in train_stats.index:\n                seq_stat = train_stats.loc[seq_id].values.reshape(1, -1)  # (1, n_stats)\n            else:\n                # 如果找不到对应的统计特征，使用0填充\n                seq_stat = np.zeros((1, train_stats.shape[1]))\n                print(f\"警告：序列 {seq_id} 未找到统计特征，使用默认值\")\n            \n            # 确保统计特征是有效的数值数组\n            seq_stat = np.nan_to_num(seq_stat.astype(np.float32))\n            \n            # 广播到每个时间步\n            seq_stat_broadcast = np.repeat(seq_stat, len(seq_data), axis=0)  # (seq_len, n_stats)\n            \n            # 确保原始特征是有效的数值数组\n            seq_data_values = np.nan_to_num(seq_data.values.astype(np.float32))\n            \n            # 拼接特征\n            seq_values = np.hstack([seq_data_values, seq_stat_broadcast])\n            \n            # 填充/截断到固定长度\n            if len(seq_values) < max_seq_length:\n                pad_length = max_seq_length - len(seq_values)\n                seq_values = np.pad(seq_values, ((0, pad_length), (0, 0)), mode='edge')\n            else:\n                seq_values = seq_values[:max_seq_length]\n                \n            train_sequences.append(seq_values)\n            train_gestures.append(group['gesture_encoded'].iloc[0])\n            train_binary.append(1 if 'sequence_type' in group.columns and group['sequence_type'].iloc[0] == 'target' else 0)\n            train_ids.append(seq_id)\n        except Exception as e:\n            print(f\"处理训练序列 {seq_id} 时出错: {e}\")\n            # 创建一个默认序列避免中断\n            default_seq = np.zeros((max_seq_length, len(common_feature_cols) + train_stats.shape[1]), dtype=np.float32)\n            train_sequences.append(default_seq)\n            train_gestures.append(0)\n            train_binary.append(0)\n            train_ids.append(seq_id)\n    \n    # 4. 处理测试序列\n    test_sequences = []\n    test_ids = []\n    \n    print(\"处理测试序列...\")\n    for seq_id, group in tqdm(test.groupby('sequence_id'), desc=\"处理测试序列\"):\n        try:\n            seq_data = group[common_feature_cols].copy()\n            for col in seq_data.columns:\n                try:\n                    seq_data[col] = pd.to_numeric(seq_data[col], errors='coerce').fillna(seq_data[col].median())\n                except:\n                    le = LabelEncoder()\n                    seq_data[col] = le.fit_transform(seq_data[col].astype(str))\n            \n            # 获取当前序列的统计特征\n            if seq_id in test_stats.index:\n                seq_stat = test_stats.loc[seq_id].values.reshape(1, -1)  # (1, n_stats)\n            else:\n                seq_stat = np.zeros((1, test_stats.shape[1]))\n                print(f\"警告：测试序列 {seq_id} 未找到统计特征，使用默认值\")\n            \n            # 确保统计特征是有效的数值数组\n            seq_stat = np.nan_to_num(seq_stat.astype(np.float32))\n            \n            # 广播到每个时间步\n            seq_stat_broadcast = np.repeat(seq_stat, len(seq_data), axis=0)  # (seq_len, n_stats)\n            \n            # 确保原始特征是有效的数值数组\n            seq_data_values = np.nan_to_num(seq_data.values.astype(np.float32))\n            \n            # 拼接特征\n            seq_values = np.hstack([seq_data_values, seq_stat_broadcast])\n            \n            # 填充/截断\n            if len(seq_values) < max_seq_length:\n                pad_length = max_seq_length - len(seq_values)\n                seq_values = np.pad(seq_values, ((0, pad_length), (0, 0)), mode='edge')\n            else:\n                seq_values = seq_values[:max_seq_length]\n                \n            test_sequences.append(seq_values)\n            test_ids.append(seq_id)\n        except Exception as e:\n            print(f\"处理测试序列 {seq_id} 时出错: {e}\")\n            # 创建一个默认序列避免中断\n            default_seq = np.zeros((max_seq_length, len(common_feature_cols) + test_stats.shape[1]), dtype=np.float32)\n            test_sequences.append(default_seq)\n            test_ids.append(seq_id)\n    \n    # 5. 后续处理\n    train_sequences = np.array(train_sequences, dtype=np.float32)\n    train_gestures = np.array(train_gestures)\n    train_binary = np.array(train_binary)\n    test_sequences = np.array(test_sequences, dtype=np.float32)\n    \n    print(f\"训练序列形状: {train_sequences.shape}\")\n    print(f\"测试序列形状: {test_sequences.shape}\")\n    \n    # 特征标准化\n    if len(train_sequences) > 0 and train_sequences.ndim == 3 and train_sequences.shape[2] > 0:\n        scaler = StandardScaler()\n        n_samples, n_timesteps, n_features = train_sequences.shape\n        train_reshaped = train_sequences.reshape(n_samples * n_timesteps, n_features)\n        train_reshaped = scaler.fit_transform(train_reshaped)\n        train_sequences = train_reshaped.reshape(n_samples, n_timesteps, n_features)\n        \n        if len(test_sequences) > 0 and test_sequences.ndim == 3 and test_sequences.shape[2] > 0:\n            n_samples_test, n_timesteps_test, n_features_test = test_sequences.shape\n            test_reshaped = test_sequences.reshape(n_samples_test * n_timesteps_test, n_features_test)\n            test_reshaped = scaler.transform(test_reshaped)\n            test_sequences = test_reshaped.reshape(n_samples_test, n_timesteps_test, n_features_test)\n    \n    n_features = train_sequences.shape[2] if (len(train_sequences.shape) > 2 and train_sequences.shape[2] > 0) else 0\n    print(f\"最终特征数量（原始+统计）: {n_features}\")\n    \n    return (train_sequences, train_gestures, train_binary, train_ids,\n            test_sequences, test_ids, n_features)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.780478Z","iopub.execute_input":"2025-08-28T01:25:54.780651Z","iopub.status.idle":"2025-08-28T01:25:54.811364Z","shell.execute_reply.started":"2025-08-28T01:25:54.780638Z","shell.execute_reply":"2025-08-28T01:25:54.810796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 修改：SensorSequenceDataset（加入数据增强）\n# ----------------------\nclass SensorSequenceDataset(Dataset):\n    def __init__(self, sequences, gestures=None, binary_labels=None, is_train=False, aug_prob=0.5):\n        self.sequences = sequences\n        self.gestures = gestures\n        self.binary_labels = binary_labels\n        self.is_train = is_train  # 标记是否为训练集（仅训练时增强）\n        self.aug_prob = aug_prob  # 增强概率\n    \n    def __len__(self):\n        return len(self.sequences)\n    \n    def _augment_sequence(self, seq):\n        \"\"\"时序数据增强逻辑\"\"\"\n        seq = seq.copy()\n        n_timesteps, n_features = seq.shape\n        \n        # 1. 高斯噪声注入（概率50%，噪声强度0.05倍标准差）\n        if np.random.random() < self.aug_prob:\n            noise = np.random.normal(0, 0.05 * seq.std(axis=0), seq.shape)\n            seq += noise\n        \n        # 2. 时间轴拉伸/压缩（概率40%，缩放比例0.8-1.2）\n        if np.random.random() < self.aug_prob * 0.8:\n            scale = np.random.uniform(0.8, 1.2)\n            new_timesteps = int(n_timesteps * scale)\n            # 线性插值调整长度\n            seq_stretched = np.zeros((new_timesteps, n_features))\n            for i in range(n_features):\n                seq_stretched[:, i] = np.interp(\n                    np.linspace(0, 1, new_timesteps),\n                    np.linspace(0, 1, n_timesteps),\n                    seq[:, i]\n                )\n            # 恢复原长度（截断/填充）\n            if new_timesteps > n_timesteps:\n                seq = seq_stretched[:n_timesteps]\n            else:\n                seq = np.pad(seq_stretched, ((0, n_timesteps - new_timesteps), (0, 0)), mode='edge')\n        \n        # 3. 常数偏移（概率30%，偏移量±0.1倍均值）\n        if np.random.random() < self.aug_prob * 0.6:\n            offset = np.random.uniform(-0.1, 0.1) * seq.mean(axis=0)\n            seq += offset\n        \n        return seq.astype(np.float32)\n    \n    def __getitem__(self, idx):\n        sequence = self.sequences[idx]\n        # 训练时应用增强\n        if self.is_train:\n            sequence = self._augment_sequence(sequence)\n        \n        sequence = torch.FloatTensor(sequence)\n        \n        if self.gestures is not None and self.binary_labels is not None:\n            gesture = torch.LongTensor([self.gestures[idx]])[0]\n            binary_label = torch.FloatTensor([self.binary_labels[idx]])[0]\n            return sequence, gesture, binary_label\n        else:\n            return sequence","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.812187Z","iopub.execute_input":"2025-08-28T01:25:54.812443Z","iopub.status.idle":"2025-08-28T01:25:54.836183Z","shell.execute_reply.started":"2025-08-28T01:25:54.812419Z","shell.execute_reply":"2025-08-28T01:25:54.835502Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 替换：CNN+BiLSTM+Self-Attention模型\n# ----------------------\nclass CNNBiLSTMAttentionModel(nn.Module):\n    def __init__(self, input_dim, hidden_dim, num_layers, num_gestures, dropout=0.3):\n        super(CNNBiLSTMAttentionModel, self).__init__()\n        \n        # 1. 1D CNN层（提取局部特征）\n        self.cnn = nn.Sequential(\n            nn.Conv1d(in_channels=input_dim, out_channels=hidden_dim//2, kernel_size=3, padding=1),\n            nn.BatchNorm1d(hidden_dim//2),  # BatchNorm加速训练\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Conv1d(in_channels=hidden_dim//2, out_channels=hidden_dim, kernel_size=3, padding=1),\n            nn.BatchNorm1d(hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(dropout)\n        )\n        \n        # 2. 双向LSTM层（输入维度=CNN输出维度）\n        self.lstm = nn.LSTM(\n            input_size=hidden_dim,\n            hidden_size=hidden_dim,\n            num_layers=num_layers,\n            batch_first=True,\n            bidirectional=True,\n            dropout=dropout if num_layers > 1 else 0\n        )\n        \n        # 3. Self-Attention层（关注关键时间步）\n        self.attention = nn.Sequential(\n            nn.Linear(hidden_dim * 2, hidden_dim),  # 输入：BiLSTM输出（2*hidden_dim）\n            nn.Tanh(),\n            nn.Linear(hidden_dim, 1),\n            nn.Softmax(dim=1)  # 对时间步加权\n        )\n        \n        # 4. 全连接层 - 手势分类\n        self.fc_gesture = nn.Sequential(\n            nn.Linear(hidden_dim * 2, hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(hidden_dim, num_gestures)\n        )\n        \n        # 5. 全连接层 - 二元分类\n        self.fc_binary = nn.Sequential(\n            nn.Linear(hidden_dim * 2, hidden_dim // 2),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(hidden_dim // 2, 1)\n        )\n    \n    def forward(self, x):\n        # x: (batch_size, seq_len, input_dim)\n        batch_size = x.size(0)\n        \n        # 1. CNN前向（需转置：(batch, input_dim, seq_len) → 符合Conv1d输入格式）\n        x_cnn = x.permute(0, 2, 1)  # (batch, input_dim, seq_len)\n        x_cnn = self.cnn(x_cnn)      # (batch, hidden_dim, seq_len)\n        x_cnn = x_cnn.permute(0, 2, 1)  # 转回：(batch, seq_len, hidden_dim)\n        \n        # 2. BiLSTM前向\n        lstm_out, _ = self.lstm(x_cnn)  # (batch, seq_len, 2*hidden_dim)\n        \n        # 3. Self-Attention：对每个时间步加权\n        attn_weights = self.attention(lstm_out)  # (batch, seq_len, 1)\n        attn_weights = attn_weights.repeat(1, 1, lstm_out.size(-1))  # (batch, seq_len, 2*hidden_dim)\n        attn_out = torch.mul(lstm_out, attn_weights).sum(dim=1)  # (batch, 2*hidden_dim) → 加权求和\n        \n        # 4. 分类输出\n        gesture_out = self.fc_gesture(attn_out)  # 手势分类\n        binary_out = self.fc_binary(attn_out).squeeze(1)  # 二元分类\n        \n        return gesture_out, binary_out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.836983Z","iopub.execute_input":"2025-08-28T01:25:54.837157Z","iopub.status.idle":"2025-08-28T01:25:54.863898Z","shell.execute_reply.started":"2025-08-28T01:25:54.837143Z","shell.execute_reply":"2025-08-28T01:25:54.863354Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 6. 训练和验证函数\n# ----------------------\ndef train_epoch(model, train_loader, criterion_gesture, criterion_binary, optimizer, device):\n    model.train()\n    total_loss = 0\n    total_gesture_correct = 0\n    total_binary_correct = 0\n    total_samples = 0\n    \n    for sequences, gestures, binary_labels in tqdm(train_loader, desc=\"训练\"):\n        sequences = sequences.to(device)\n        gestures = gestures.to(device)\n        binary_labels = binary_labels.to(device)\n        \n        optimizer.zero_grad()\n        gesture_out, binary_logits = model(sequences)  # 接收logits而非概率\n        \n        # 计算损失（BCEWithLogitsLoss会自动应用sigmoid）\n        loss_gesture = criterion_gesture(gesture_out, gestures)\n        loss_binary = criterion_binary(binary_logits, binary_labels)  # 直接使用logits\n        loss = loss_gesture + loss_binary\n        \n        loss.backward()\n        optimizer.step()\n        \n        total_loss += loss.item() * sequences.size(0)\n        _, predicted_gesture = torch.max(gesture_out.data, 1)\n        total_gesture_correct += (predicted_gesture == gestures).sum().item()\n        \n        # 预测时才应用sigmoid和阈值\n        predicted_binary = (torch.sigmoid(binary_logits) > 0.5).float()\n        total_binary_correct += (predicted_binary == binary_labels).sum().item()\n        \n        total_samples += sequences.size(0)\n    \n    avg_loss = total_loss / total_samples\n    gesture_acc = total_gesture_correct / total_samples\n    binary_acc = total_binary_correct / total_samples\n    \n    return avg_loss, gesture_acc, binary_acc\n\ndef validate(model, val_loader, criterion_gesture, criterion_binary, device):\n    model.eval()\n    total_loss = 0\n    total_gesture_correct = 0\n    total_binary_correct = 0\n    total_samples = 0\n    \n    all_gesture_preds = []\n    all_gesture_labels = []\n    all_binary_preds = []\n    all_binary_labels = []\n    \n    with torch.no_grad():\n        for sequences, gestures, binary_labels in tqdm(val_loader, desc=\"验证\"):\n            sequences = sequences.to(device)\n            gestures = gestures.to(device)\n            binary_labels = binary_labels.to(device)\n            \n            gesture_out, binary_out = model(sequences)\n            \n            loss_gesture = criterion_gesture(gesture_out, gestures)\n            loss_binary = criterion_binary(binary_out, binary_labels)\n            loss = loss_gesture + loss_binary\n            \n            total_loss += loss.item() * sequences.size(0)\n            _, predicted_gesture = torch.max(gesture_out.data, 1)\n            total_gesture_correct += (predicted_gesture == gestures).sum().item()\n            \n            predicted_binary = (binary_out > 0.5).float()\n            total_binary_correct += (predicted_binary == binary_labels).sum().item()\n            \n            total_samples += sequences.size(0)\n            \n            all_gesture_preds.extend(predicted_gesture.cpu().numpy())\n            all_gesture_labels.extend(gestures.cpu().numpy())\n            all_binary_preds.extend(predicted_binary.cpu().numpy())\n            all_binary_labels.extend(binary_labels.cpu().numpy())\n    \n    avg_loss = total_loss / total_samples\n    gesture_acc = total_gesture_correct / total_samples\n    binary_acc = total_binary_correct / total_samples\n    \n    macro_f1 = f1_score(all_gesture_labels, all_gesture_preds, average='macro')\n    binary_f1 = f1_score(all_binary_labels, all_binary_preds)\n    avg_f1 = (macro_f1 + binary_f1) / 2\n    \n    return avg_loss, gesture_acc, binary_acc, macro_f1, binary_f1, avg_f1, \\\n           all_gesture_preds, all_gesture_labels, all_binary_preds, all_binary_labels","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.865714Z","iopub.execute_input":"2025-08-28T01:25:54.865890Z","iopub.status.idle":"2025-08-28T01:25:54.890836Z","shell.execute_reply.started":"2025-08-28T01:25:54.865876Z","shell.execute_reply":"2025-08-28T01:25:54.890276Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 新增：LabelSmoothingCrossEntropyLoss\n# ----------------------\nclass LabelSmoothingCrossEntropyLoss(nn.Module):\n    def __init__(self, smoothing=0.1, weight=None):\n        super(LabelSmoothingCrossEntropyLoss, self).__init__()\n        self.smoothing = smoothing\n        self.weight = weight  # 类别权重（兼容之前的类别平衡）\n    \n    def forward(self, logits, targets):\n        n_classes = logits.size(-1)\n        # 软化标签：均匀分布 + 真实标签权重\n        smooth_label = torch.full_like(logits, self.smoothing / (n_classes - 1))\n        smooth_label.scatter_(1, targets.unsqueeze(1), 1 - self.smoothing)\n        \n        # 计算交叉熵（支持类别权重）\n        log_probs = torch.log_softmax(logits, dim=1)\n        if self.weight is not None:\n            smooth_label = smooth_label * self.weight.unsqueeze(0)  # 应用类别权重\n        loss = -torch.sum(log_probs * smooth_label, dim=1).mean()\n        return loss\n\n# ----------------------\n# 7. 模型训练与验证主函数\n# ----------------------\ndef train_models(train_sequences, train_gestures, train_binary, train_ids, \n                test_sequences, n_features, num_gestures, n_splits=5):\n    \"\"\"训练多折模型并进行预测\"\"\"\n    print(\"\\n===== 训练模型（优化版） =====\")\n    \n    # 处理参与者信息\n    subjects = []\n    for id in train_ids:\n        try:\n            if '_' in id:\n                parts = id.split('_')\n                if len(parts) >= 2:\n                    subject = parts[1].split('-')[0]\n                    subjects.append(subject)\n                    continue\n            subjects.append(str(hash(id) % 1000))\n        except:\n            subjects.append(str(hash(id) % 1000))\n    \n    subjects = np.array(subjects)\n    \n    # 新增1：计算手势类别权重（解决类别不平衡）\n    class_counts = np.bincount(train_gestures)\n    class_weights = 1.0 / class_counts  # 反比于样本数\n    class_weights = class_weights / class_weights.sum() * num_gestures  # 归一化\n    class_weights = torch.FloatTensor(class_weights).to(device)\n    print(f\"手势类别权重: {class_weights.cpu().numpy()}\")\n    \n    # 交叉验证\n    gkf = GroupKFold(n_splits=n_splits)\n    fold_results = []\n    test_preds_gesture = np.zeros((len(test_sequences), num_gestures))\n    \n    for fold, (train_idx, val_idx) in enumerate(gkf.split(train_sequences, groups=subjects)):\n        print(f\"\\n===== 折 {fold+1}/{n_splits} =====\")\n        \n        # 划分训练集和验证集\n        X_train, X_val = train_sequences[train_idx], train_sequences[val_idx]\n        y_gesture_train, y_gesture_val = train_gestures[train_idx], train_gestures[val_idx]\n        y_binary_train, y_binary_val = train_binary[train_idx], train_binary[val_idx]\n        \n        # 创建数据集和数据加载器（加入数据增强标记）\n        train_dataset = SensorSequenceDataset(X_train, y_gesture_train, y_binary_train, is_train=True, aug_prob=0.5)\n        val_dataset = SensorSequenceDataset(X_val, y_gesture_val, y_binary_val, is_train=False)\n        \n        # 根据GPU内存调整batch size\n        batch_size = 64\n        if torch.cuda.is_available():\n            gpu_mem = torch.cuda.get_device_properties(0).total_memory / (1024 ** 3)  # GB\n            if gpu_mem < 8:\n                print(f\"检测到小内存GPU ({gpu_mem:.1f}GB)，调整batch size为32\")\n                batch_size = 32\n        \n        train_loader = DataLoader(\n            train_dataset, \n            batch_size=batch_size, \n            shuffle=True, \n            num_workers=4,\n            pin_memory=True if torch.cuda.is_available() else False\n        )\n        \n        val_loader = DataLoader(\n            val_dataset, \n            batch_size=batch_size, \n            shuffle=False, \n            num_workers=4,\n            pin_memory=True if torch.cuda.is_available() else False\n        )\n        \n        # 初始化模型（使用新的CNN+BiLSTM+Attention模型）\n        hidden_dim = 128 if n_features < 100 else 256  # 特征数增加，适当调大hidden_dim\n        num_layers = 2 if n_features < 100 else 3\n        \n        model = CNNBiLSTMAttentionModel(\n            input_dim=n_features,\n            hidden_dim=hidden_dim,\n            num_layers=num_layers,\n            num_gestures=num_gestures,\n            dropout=0.3\n        )\n        \n        # 如果有多个GPU，使用DataParallel\n        if torch.cuda.device_count() > 1:\n            print(f\"使用 {torch.cuda.device_count()} 个GPU进行训练!\")\n            model = nn.DataParallel(model)\n        \n        model = model.to(device)\n        \n        # 定义损失函数和优化器（优化部分）\n        criterion_gesture = LabelSmoothingCrossEntropyLoss(\n            smoothing=0.1,  # 软化强度\n            weight=class_weights\n        )\n        criterion_binary = nn.BCEWithLogitsLoss()\n        optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-5)  # 替换为AdamW\n        # 学习率调度：CosineAnnealingWarmRestarts\n        scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(\n            optimizer, T_0=10, T_mult=2, eta_min=1e-6\n        )\n        \n        # 训练模型\n        best_avg_f1 = 0\n        patience = 8  # 调整早停耐心值\n        counter = 0\n        \n        for epoch in range(50):\n            print(f\"\\n epoch {epoch+1}/50 | 当前学习率: {optimizer.param_groups[0]['lr']:.6f}\")\n            \n            # 训练阶段（加入梯度裁剪）\n            model.train()\n            train_loss, train_gesture_acc, train_binary_acc = 0, 0, 0\n            total_samples = 0\n            \n            for sequences, gestures, binary_labels in tqdm(train_loader, desc=\"训练\"):\n                sequences = sequences.to(device)\n                gestures = gestures.to(device)\n                binary_labels = binary_labels.to(device)\n                \n                optimizer.zero_grad()\n                gesture_out, binary_logits = model(sequences)\n                \n                loss_gesture = criterion_gesture(gesture_out, gestures)\n                loss_binary = criterion_binary(binary_logits, binary_labels)\n                loss = loss_gesture + loss_binary\n                \n                loss.backward()\n                # 梯度裁剪（阈值5.0，防止梯度爆炸）\n                torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)\n                optimizer.step()\n                \n                # 统计指标\n                train_loss += loss.item() * sequences.size(0)\n                _, predicted_gesture = torch.max(gesture_out.data, 1)\n                train_gesture_acc += (predicted_gesture == gestures).sum().item()\n                predicted_binary = (torch.sigmoid(binary_logits) > 0.5).float()\n                train_binary_acc += (predicted_binary == binary_labels).sum().item()\n                total_samples += sequences.size(0)\n            \n            train_loss /= total_samples\n            train_gesture_acc /= total_samples\n            train_binary_acc /= total_samples\n            \n            # 验证\n            val_loss, val_gesture_acc, val_binary_acc, val_macro_f1, val_binary_f1, val_avg_f1, \\\n            gesture_preds, gesture_labels, binary_preds, binary_labels = validate(\n                model, val_loader, criterion_gesture, criterion_binary, device\n            )\n            \n            # 打印 epoch 结果\n            print(f\"训练损失: {train_loss:.4f}, 手势准确率: {train_gesture_acc:.4f}, 二元准确率: {train_binary_acc:.4f}\")\n            print(f\"验证损失: {val_loss:.4f}, 手势准确率: {val_gesture_acc:.4f}, 二元准确率: {val_binary_acc:.4f}\")\n            print(f\"验证宏F1: {val_macro_f1:.4f}, 二元F1: {val_binary_f1:.4f}, 平均F1: {val_avg_f1:.4f}\")\n            \n            # 学习率调度\n            scheduler.step()\n            \n            # 早停机制\n            if val_avg_f1 > best_avg_f1:\n                best_avg_f1 = val_avg_f1\n                torch.save(model.state_dict(), f\"best_model_fold_{fold+1}.pth\")\n                counter = 0\n            else:\n                counter += 1\n                if counter >= patience:\n                    print(f\"早停在第 {epoch+1} 轮\")\n                    break\n        \n        # 加载最佳模型\n        model.load_state_dict(torch.load(f\"best_model_fold_{fold+1}.pth\"))\n        \n        # 再次验证\n        _, _, _, val_macro_f1, val_binary_f1, val_avg_f1, \\\n        gesture_preds, gesture_labels, binary_preds, binary_labels = validate(\n            model, val_loader, criterion_gesture, criterion_binary, device\n        )\n        \n        print(f\"\\n折 {fold+1} 最佳结果:\")\n        print(f\"宏F1: {val_macro_f1:.4f}, 二元F1: {val_binary_f1:.4f}, 平均F1: {val_avg_f1:.4f}\")\n        \n        # 打印分类报告\n        print(\"\\n手势分类报告:\")\n        print(classification_report(gesture_labels, gesture_preds))\n        \n        print(\"\\n二元分类报告:\")\n        print(classification_report(binary_labels, binary_preds))\n        \n        fold_results.append({\n            'fold': fold+1,\n            'macro_f1': val_macro_f1,\n            'binary_f1': val_binary_f1,\n            'avg_f1': val_avg_f1\n        })\n        \n        # 测试集预测\n        test_dataset = SensorSequenceDataset(test_sequences)\n        test_loader = DataLoader(\n            test_dataset, \n            batch_size=batch_size, \n            shuffle=False, \n            num_workers=4,\n            pin_memory=True if torch.cuda.is_available() else False\n        )\n        \n        model.eval()\n        fold_test_preds = []\n        \n        with torch.no_grad():\n            for sequences in tqdm(test_loader, desc=\"测试集预测\"):\n                sequences = sequences.to(device)\n                gesture_out, _ = model(sequences)\n                fold_test_preds.append(torch.softmax(gesture_out, dim=1).cpu().numpy())\n        \n        # 聚合测试集预测\n        fold_test_preds = np.vstack(fold_test_preds)\n        test_preds_gesture += fold_test_preds / n_splits\n    \n    # 打印交叉验证汇总结果\n    print(\"\\n===== 交叉验证汇总 =====\")\n    results_df = pd.DataFrame(fold_results)\n    print(results_df.mean())\n    \n    return test_preds_gesture","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.891573Z","iopub.execute_input":"2025-08-28T01:25:54.891784Z","iopub.status.idle":"2025-08-28T01:25:54.914864Z","shell.execute_reply.started":"2025-08-28T01:25:54.891768Z","shell.execute_reply":"2025-08-28T01:25:54.914328Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 8. 生成提交文件\n# ----------------------\ndef create_submission(test_preds_gesture, test_ids, gesture_le):\n    \"\"\"生成符合竞赛要求的提交文件\"\"\"\n    print(\"\\n===== 生成提交文件 =====\")\n    \n    # 从预测概率获取类别\n    test_preds_gesture_class = np.argmax(test_preds_gesture, axis=1)\n    \n    # 解码为原始手势名称\n    gestures = gesture_le.inverse_transform(test_preds_gesture_class)\n    \n    # 生成提交文件\n    submission = pd.DataFrame({\n        'sequence_id': test_ids,\n        'gesture': gestures\n    })\n    \n    submission.to_csv('submission.csv', index=False)\n    \n    print(f\"提交文件已生成，形状: {submission.shape}\")\n    print(\"前5行预览:\")\n    print(submission.head())\n    \n    return submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.915459Z","iopub.execute_input":"2025-08-28T01:25:54.915650Z","iopub.status.idle":"2025-08-28T01:25:54.936590Z","shell.execute_reply.started":"2025-08-28T01:25:54.915636Z","shell.execute_reply":"2025-08-28T01:25:54.935913Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 9. 主函数\n# ----------------------\ndef main():\n    try:\n        # 加载数据并探索\n        train, test, train_demo, test_demo = load_and_explore_data()\n        \n        # 预处理数据\n        train, test, imu_cols, thm_cols, tof_cols, demo_cols, gesture_le = preprocess_data(\n            train, test, train_demo, test_demo\n        )\n        \n        # 自动确定合适的序列长度\n        seq_lengths = train.groupby('sequence_id').size()\n        print(f\"\\n序列长度分布: 平均={seq_lengths.mean():.1f}, 中位数={seq_lengths.median()}, 最大={seq_lengths.max()}\")\n        max_seq_length = int(seq_lengths.median() * 1.5)\n        print(f\"自动设置最大序列长度为: {max_seq_length}\")\n        \n        # 准备序列数据\n        (train_sequences, train_gestures, train_binary, train_ids,\n         test_sequences, test_ids, n_features) = prepare_sequences(train, test, max_seq_length)\n        \n        # 获取手势类别数量\n        num_gestures = len(np.unique(train_gestures))\n        print(f\"手势类别数量: {num_gestures}\")\n        \n        # 如果特征数量为0，说明数据处理有问题\n        if n_features == 0:\n            raise ValueError(\"未找到任何特征列，请检查数据格式\")\n        \n        # 训练模型并预测\n        test_preds_gesture = train_models(\n            train_sequences, train_gestures, train_binary, train_ids,\n            test_sequences, n_features, num_gestures, n_splits=5\n        )\n        \n        # 生成提交文件\n        submission = create_submission(test_preds_gesture, test_ids, gesture_le)\n        \n        print(\"\\n所有任务完成!\")\n        \n    except Exception as e:\n        print(f\"\\n执行过程中出错: {str(e)}\")\n        import traceback\n        traceback.print_exc()\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T01:25:54.937353Z","iopub.execute_input":"2025-08-28T01:25:54.937593Z","iopub.status.idle":"2025-08-28T04:19:11.012357Z","shell.execute_reply.started":"2025-08-28T01:25:54.937569Z","shell.execute_reply":"2025-08-28T04:19:11.011473Z"}},"outputs":[],"execution_count":null}]}