{"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":"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 gc\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}\")\n\n# ----------------------\n# 1. 数据加载与详细探索（新增前5行预览+数据可视化）\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    print(f\"训练集人口数据形状: {train_demo.shape}\")\n    print(f\"测试集人口数据形状: {test_demo.shape}\")\n    \n    # 新增：打印各数据前5行，直观查看数据结构\n    print(\"\\n----- 训练集前5行 -----\")\n    print(train.head())\n    print(\"\\n----- 测试集前5行 -----\")\n    print(test.head())\n    print(\"\\n----- 训练集人口数据前5行 -----\")\n    print(train_demo.head())\n    print(\"\\n----- 测试集人口数据前5行 -----\")\n    print(test_demo.head())\n    \n    print(\"\\n训练集列名及数据类型（前10列）:\")\n    print(train.dtypes.head(10))\n    print(\"\\n测试集列名及数据类型（前10列）:\")\n    print(test.dtypes.head(10))\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    print(\"\\n----- 数据分布可视化 -----\")\n    # 1. 目标变量（gesture）分布（仅训练集）\n    if 'gesture' in train.columns:\n        plt.figure(figsize=(12, 5))\n        # 子图1：手势类别数量分布\n        plt.subplot(1, 2, 1)\n        gesture_count = train['gesture'].value_counts()\n        sns.barplot(x=gesture_count.index, y=gesture_count.values, palette='viridis')\n        plt.title('Distribution of Gesture Categories', fontsize=12)\n        plt.xlabel('Gesture Category')\n        plt.ylabel('Number of Samples')\n        plt.xticks(rotation=45)\n        # 子图2：序列长度分布\n        plt.subplot(1, 2, 2)\n        seq_lengths = train.groupby('sequence_id').size()\n        sns.histplot(seq_lengths, bins=20, kde=True, color='skyblue')\n        plt.title('Distribution of Sequence Lengths', fontsize=12)\n        plt.xlabel('Sequence Length (time steps)')\n        plt.ylabel('Number of Sequences')\n        plt.tight_layout()\n        plt.show()\n    \n    # 2. 传感器特征分布（以IMU特征为例，取前3个加速度特征）\n    imu_cols = [col for col in train.columns if col.startswith(('acc_', 'gyro_'))][:3]\n    if len(imu_cols) >= 3:\n        plt.figure(figsize=(15, 5))\n        for i, col in enumerate(imu_cols):\n            plt.subplot(1, 3, i+1)\n            sns.histplot(train[col].dropna(), bins=30, kde=True, color=f'C{i}')\n            plt.title(f'Distribution of {col}', fontsize=10)\n            plt.xlabel(col)\n            plt.ylabel('Frequency')\n        plt.tight_layout()\n        plt.show()\n    \n    # 3. 人口统计学数据分布（如年龄、体重）\n    demo_cols = [col for col in train_demo.columns if col in ['age', 'weight', 'height_cm']]\n    if len(demo_cols) > 0:\n        plt.figure(figsize=(10, 4))\n        for i, col in enumerate(demo_cols):\n            plt.subplot(1, len(demo_cols), i+1)\n            sns.histplot(train_demo[col].dropna(), bins=15, kde=True, color='orange')\n            plt.title(f'Distribution of {col}', fontsize=10)\n            plt.xlabel(col)\n        plt.tight_layout()\n        plt.show()\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\n\n# ----------------------\n# 2. 数据预处理（核心优化：裁剪TOF特征+类别样本平衡+特征增强）\n# ----------------------\ndef preprocess_data(train, test, train_demo, test_demo):\n    \"\"\"数据预处理：灵活处理不同结构的数据 + 类别平衡 + 特征增强 + TOF特征裁剪\"\"\"\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    # 核心优化1：裁剪TOF冗余特征（每5列保留1列，减少70%+维度）\n    # ----------------------\n    tof_cols_train = [col for col in train.columns if col.startswith('tof_')]\n    tof_cols_test = [col for col in test.columns if col.startswith('tof_')]\n    print(f\"原始TOF特征数量（训练集）: {len(tof_cols_train)}\")\n    \n    # 每5列保留1列（按索引步长筛选，兼顾物理含义连续性）\n    keep_tof_cols = tof_cols_train[::5] if len(tof_cols_train) > 0 else []\n    # 确保测试集与训练集保留的TOF列一致\n    keep_tof_cols_test = [col for col in keep_tof_cols if col in tof_cols_test]\n    \n    # 删除冗余TOF列\n    if len(tof_cols_train) > 0:\n        drop_tof_train = [col for col in tof_cols_train if col not in keep_tof_cols]\n        train = train.drop(columns=drop_tof_train)\n    if len(tof_cols_test) > 0:\n        drop_tof_test = [col for col in tof_cols_test if col not in keep_tof_cols_test]\n        test = test.drop(columns=drop_tof_test)\n    \n    print(f\"裁剪后TOF特征数量（训练集）: {len(keep_tof_cols)}\")\n    print(f\"裁剪后TOF特征数量（测试集）: {len(keep_tof_cols_test)}\")\n    \n    # 确保训练集和测试集有相同的特征列\n    train_cols = set(train.columns)\n    test_cols = set(test.columns)\n    common_cols = train_cols.intersection(test_cols)\n    train_only_cols = ['gesture', 'gesture_encoded', 'is_target']  # 新增is_target列\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    def add_sensor_derived_features(df):\n        # 识别IMU传感器列（加速度、陀螺仪等）\n        imu_cols = [col for col in df.columns if col.startswith(('acc_', 'gyro_', 'rot_', 'mag_'))]\n        if len(imu_cols) >=3:\n            # 计算加速度向量模长（物理意义：总加速度大小）\n            if all(col in df.columns for col in ['acc_x', 'acc_y', 'acc_z']):\n                df['acc_magnitude'] = np.sqrt(df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2)\n            # 计算陀螺仪向量模长（旋转强度）\n            if all(col in df.columns for col in ['gyro_x', 'gyro_y', 'gyro_z']):\n                df['gyro_magnitude'] = np.sqrt(df['gyro_x']**2 + df['gyro_y']**2 + df['gyro_z']**2)\n            # 计算传感器数据一阶差分（变化率）- 限制数量，避免特征爆炸\n            for col in imu_cols[:6]:  # 仅保留前6个IMU列的差分\n                df[f'{col}_diff1'] = df.groupby('sequence_id')[col].diff().fillna(0)\n        # 人口统计学衍生特征（如BMI）\n        if all(col in df.columns for col in ['weight', 'height_cm']):\n            df['bmi'] = df['weight'] / ((df['height_cm']/100)**2)\n            df['bmi'] = df['bmi'].fillna(df['bmi'].median())\n        return df\n    \n    # 为训练集和测试集添加衍生特征\n    train = add_sensor_derived_features(train)\n    test = add_sensor_derived_features(test)\n    print(\"已添加传感器衍生特征（模长、一阶差分）和人口统计学衍生特征（BMI）\")\n    \n    # 重新对齐特征列（包含新增的衍生特征）\n    train_features = list(common_cols) + train_only_cols\n    test_features = list(common_cols)\n    for col in ['sequence_id', 'row_id', 'subject', 'acc_magnitude', 'gyro_magnitude', 'bmi']:\n        if col in train.columns and col not in train_features:\n            train_features.append(col)\n        if col in test.columns and col not in test_features:\n            test_features.append(col)\n    \n    # 应用特征选择\n    train = train[train_features]\n    test = test[test_features]\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    imu_cols = [col for col in train.columns if col.startswith(('acc_', 'rot_', 'gyro_', 'mag_')) or col.endswith('_diff1')]\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    demo_cols = [col for col in train.columns if col in ['age', 'height', 'height_cm', 'weight', 'bmi', 'shoulder_to_wrist', 'shoulder_to_wrist_cm', 'elbow_to_wrist', 'elbow_to_wrist_cm']]\n    \n    print(f\"找到 {len(imu_cols)} 个IMU相关列（含衍生特征）\")\n    print(f\"找到 {len(thm_cols)} 个热传感器列\")\n    print(f\"找到 {len(tof_cols)} 个飞行时间传感器列\")\n    print(f\"找到 {len(demo_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').fillna(x.median()))\n        test[imu_cols] = test.groupby('sequence_id')[imu_cols].transform(\n            lambda x: x.interpolate(method='linear', limit_direction='both').fillna(x.median()))\n    if thm_cols:\n        train[thm_cols] = train.groupby('sequence_id')[thm_cols].transform(\n            lambda x: x.fillna(x.mode()[0] if not x.mode().empty else x.mean()))\n        test[thm_cols] = test.groupby('sequence_id')[thm_cols].transform(\n            lambda x: x.fillna(x.mode()[0] if not x.mode().empty else x.mean()))\n    if tof_cols:\n        train[tof_cols] = train.groupby('sequence_id')[tof_cols].transform(\n            lambda x: x.fillna(x.quantile(0.5)))  # 中位数填充，抗异常值\n        test[tof_cols] = test.groupby('sequence_id')[tof_cols].transform(\n            lambda x: x.fillna(x.quantile(0.5)))\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    gesture_le = LabelEncoder()\n    train['gesture_encoded'] = gesture_le.fit_transform(train['gesture'])\n    \n    # 定义目标类型（根据竞赛要求，这里假设需要确定哪些是目标手势）\n    unique_gestures = train['gesture'].unique()\n    target_gestures = unique_gestures[:len(unique_gestures)//2]\n    train['is_target'] = train['gesture'].isin(target_gestures).astype(int)\n    \n    print(f\"已编码 {len(gesture_le.classes_)} 种手势类别\")\n    print(f\"目标手势: {len(target_gestures)}种, 非目标手势: {len(unique_gestures)-len(target_gestures)}种\")\n    \n    # 分析类别分布\n    class_distribution = train['gesture_encoded'].value_counts().sort_index()\n    print(\"\\n手势类别分布（原始）:\")\n    for cls, count in class_distribution.items():\n        print(f\"类别 {cls}: {count} 样本 ({count/len(train):.2%})\")\n    \n    # 二元类别分布\n    target_distribution = train['is_target'].value_counts().sort_index()\n    print(\"\\n二元目标分布（原始）:\")\n    for cls, count in target_distribution.items():\n        print(f\"{'目标' if cls == 1 else '非目标'}: {count} 样本 ({count/len(train):.2%})\")\n    \n    # ----------------------\n    # 类别样本平衡（序列级SMOTE过采样，避免单样本重复）\n    # ----------------------\n    print(\"\\n===== 类别样本平衡（序列级SMOTE过采样） =====\")\n    max_count = class_distribution.max()\n    seq_class_map = train.groupby('sequence_id')['gesture_encoded'].first().to_dict()\n    seq_list = train['sequence_id'].unique()\n    \n    # 按类别分组序列\n    seq_by_class = {cls: [] for cls in class_distribution.index}\n    for seq_id in seq_list:\n        cls = seq_class_map[seq_id]\n        seq_by_class[cls].append(seq_id)\n    \n    # 序列级SMOTE过采样（生成\"相似但不重复\"的序列）\n    def generate_smote_seq(base_seq_df, n_generate, seq_id_prefix):\n        generated_seqs = []\n        for i in range(n_generate):\n            # 对传感器特征添加微小扰动（模拟真实数据变异）\n            imu_feat_cols = [col for col in base_seq_df.columns if col.startswith(('acc_', 'gyro_', 'rot_', 'mag_')) or col.endswith('_diff1')]\n            perturb_df = base_seq_df.copy()\n            if len(imu_feat_cols) > 0:\n                perturb_scale = 0.03  # 扰动强度（控制在3%以内，保证物理意义）\n                perturb = np.random.normal(0, perturb_scale, size=(len(perturb_df), len(imu_feat_cols)))\n                perturb_df[imu_feat_cols] += perturb * perturb_df[imu_feat_cols].std(axis=0).values\n            # 生成新序列ID\n            new_seq_id = f\"{seq_id_prefix}_smote_{i}\"\n            perturb_df['sequence_id'] = new_seq_id\n            generated_seqs.append(perturb_df)\n        return pd.concat(generated_seqs, ignore_index=True) if generated_seqs else pd.DataFrame()\n    \n    # 对少数类生成新序列\n    oversampled_train = []\n    for cls in class_distribution.index:\n        cls_seqs = seq_by_class[cls]\n        current_seq_count = len(cls_seqs)\n        \n        # 若当前序列数小于最大序列数，生成新序列（限制生成量，避免数据膨胀）\n        if current_seq_count < max_count:\n            need_seq_count = min(max_count - current_seq_count, current_seq_count * 2)  # 最多生成2倍原始序列\n            base_seq_ids = np.random.choice(cls_seqs, size=min(need_seq_count, current_seq_count), replace=True)\n            generated_samples = []\n            for base_seq_id in base_seq_ids:\n                base_seq_df = train[train['sequence_id'] == base_seq_id].copy()\n                gen_seq = generate_smote_seq(base_seq_df, n_generate=1, seq_id_prefix=base_seq_id)\n                if not gen_seq.empty:\n                    generated_samples.append(gen_seq)\n            # 合并原始序列和生成序列\n            cls_original = train[train['sequence_id'].isin(cls_seqs)]\n            cls_generated = pd.concat(generated_samples, ignore_index=True) if generated_samples else pd.DataFrame()\n            oversampled_train.append(pd.concat([cls_original, cls_generated], ignore_index=True))\n        else:\n            # 多数类直接保留\n            oversampled_train.append(train[train['sequence_id'].isin(cls_seqs)])\n    \n    # 合并过采样后的数据\n    train = pd.concat(oversampled_train, ignore_index=True)\n    # 重新计算类别分布\n    oversampled_dist = train['gesture_encoded'].value_counts().sort_index()\n    print(f\"过采样前训练集总样本数: {len(seq_list) * train.groupby('sequence_id').size().mean():.0f}\")\n    print(f\"过采样后训练集总样本数: {len(train)}\")\n    print(f\"过采样后类别分布:\")\n    for cls, count in oversampled_dist.items():\n        print(f\"类别 {cls}: {count} 样本 ({count/len(train):.2%})\")\n    \n    # 新增：预处理后数据预览和可视化\n    print(\"\\n----- 预处理后数据预览 -----\")\n    print(\"训练集前5行:\")\n    print(train.head())\n    print(\"测试集前5行:\")\n    print(test.head())\n    \n    # 新增：预处理后数据分布可视化\n    print(\"\\n----- 预处理后数据分布验证 -----\")\n    # 1. 过采样后的类别分布对比\n    plt.figure(figsize=(12, 5))\n    plt.subplot(1, 2, 1)\n    sns.barplot(x=class_distribution.index, y=class_distribution.values, palette='viridis')\n    plt.title('Class Distribution Before Oversampling', fontsize=12)\n    plt.xlabel('Gesture Class')\n    plt.ylabel('Number of Samples')\n    plt.subplot(1, 2, 2)\n    sns.barplot(x=oversampled_dist.index, y=oversampled_dist.values, palette='viridis')\n    plt.title('Class Distribution After Oversampling', fontsize=12)\n    plt.xlabel('Gesture Class')\n    plt.ylabel('Number of Samples')\n    plt.tight_layout()\n    plt.show()\n    \n    # 2. 衍生特征分布验证（以加速度模长为例）\n    if 'acc_magnitude' in train.columns:\n        plt.figure(figsize=(10, 4))\n        sns.histplot(train['acc_magnitude'], bins=30, kde=True, color='green')\n        plt.title('Distribution of Acceleration Magnitude', fontsize=12)\n        plt.xlabel('Acceleration Magnitude')\n        plt.ylabel('Frequency')\n        plt.show()\n    \n    # 3. 二元目标分布\n    plt.figure(figsize=(8, 4))\n    target_dist = train['is_target'].value_counts()\n    sns.barplot(x=target_dist.index, y=target_dist.values, palette=['red', 'blue'])\n    plt.title('Distribution of Target Classes', fontsize=12)\n    plt.xlabel('Class')\n    plt.ylabel('Number of Samples')\n    plt.xticks([0, 1], ['Non-target', 'Target'])\n    plt.show()\n    \n    return train, test, imu_cols, thm_cols, tof_cols, demo_cols, gesture_le, class_distribution, target_gestures\n\n# ----------------------\n# 3. 优化：时序统计特征提取函数（简化计算+分批处理）\n# ----------------------\ndef extract_temporal_stats(group, feature_cols):\n    \"\"\"简化版时序统计特征提取，仅保留核心统计量，减少计算量\"\"\"\n    stats = {}\n    # 1. 仅保留核心统计量（删除自相关、高阶差分等复杂特征）\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    \n    # 2. 一阶差分的基础统计（捕捉变化率）\n    if len(group) > 1:\n        diff1 = group[feature_cols].diff().fillna(0)\n        stats['diff1_mean'] = diff1.mean()\n        stats['diff1_std'] = diff1.std()\n    \n    # 拼接为DataFrame（每行对应一个序列的统计特征）\n    stats_df = pd.DataFrame([stats])\n    \n    # 确保所有值都是数值类型，并填充任何NaN\n    for col in stats_df.columns:\n        stats_df[col] = pd.to_numeric(stats_df[col], errors='coerce').fillna(0.0)\n    \n    # 重命名列（避免与原始特征冲突）\n    stats_df.columns = [f'{col}_stat' for col in stats_df.columns]\n    return stats_df\n\n# 分批提取时序统计特征（避免内存溢出）\ndef extract_temporal_stats_batch(data, feature_cols, batch_size=1000):\n    \"\"\"分批处理时序统计特征提取，降低内存占用\"\"\"\n    sequence_ids = data['sequence_id'].unique()\n    stats_list = []\n    # 按批次处理序列\n    for i in range(0, len(sequence_ids), batch_size):\n        batch_seq_ids = sequence_ids[i:i+batch_size]\n        batch_data = data[data['sequence_id'].isin(batch_seq_ids)]\n        # 按序列计算统计特征\n        batch_stats = batch_data.groupby('sequence_id').apply(\n            lambda x: extract_temporal_stats(x, feature_cols)\n        ).reset_index(drop=True)\n        stats_list.append(batch_stats)\n        # 释放批次内存\n        del batch_data\n        gc.collect()\n    \n    return pd.concat(stats_list, ignore_index=True) if stats_list else pd.DataFrame()\n\n# ----------------------\n# 4. 优化：prepare_sequences函数（动态调整序列长度+内存优化）\n# ----------------------\ndef prepare_sequences(train, test, max_seq_length=None):\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', 'subject', 'is_target']\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 = extract_temporal_stats_batch(train, common_feature_cols, batch_size=500)\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 = extract_temporal_stats_batch(test, common_feature_cols, batch_size=500)\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_is_target = []  # 新增：二元目标标签\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_is_target.append(group['is_target'].iloc[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_is_target.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_is_target = np.array(train_is_target)  # 转换为numpy数组\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    del train_stats, test_stats\n    gc.collect()\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_is_target, train_ids,\n            test_sequences, test_ids, n_features)\n\n# ----------------------\n# 5. 修改：SensorSequenceDataset（加入针对少数类的增强）\n# ----------------------\nclass SensorSequenceDataset(Dataset):\n    def __init__(self, sequences, gestures=None, is_target=None, is_train=False, aug_prob=0.5, class_counts=None):\n        self.sequences = sequences\n        self.gestures = gestures\n        self.is_target = is_target  # 新增：二元目标标签\n        self.is_train = is_train  # 标记是否为训练集（仅训练时增强）\n        self.aug_prob = aug_prob  # 基础增强概率\n        self.class_counts = class_counts  # 类别分布，用于调整少数类的增强概率\n        \n        # 计算每个类别的增强权重（少数类增强概率更高）\n        if class_counts is not None:\n            max_count = class_counts.max()\n            self.aug_weights = np.sqrt(max_count / class_counts)  # 少数类权重更高\n            self.aug_weights = self.aug_weights / self.aug_weights.max()  # 归一化到[0,1]\n        else:\n            self.aug_weights = None\n    \n    def __len__(self):\n        return len(self.sequences)\n    \n    def _augment_sequence(self, seq, aug_strength=1.0):\n        \"\"\"时序数据增强逻辑，支持调整增强强度\"\"\"\n        seq = seq.copy()\n        n_timesteps, n_features = seq.shape\n        \n        # 1. 高斯噪声注入（概率随增强强度增加）\n        if np.random.random() < self.aug_prob * aug_strength:\n            noise_scale = 0.05 * aug_strength  # 噪声强度随增强强度增加\n            noise = np.random.normal(0, noise_scale * seq.std(axis=0), seq.shape)\n            seq += noise\n        \n        # 2. 时间轴拉伸/压缩（概率随增强强度增加）\n        if np.random.random() < self.aug_prob * 0.8 * aug_strength:\n            scale_range = 0.2 * aug_strength  # 缩放范围随增强强度增加\n            scale = np.random.uniform(1 - scale_range, 1 + scale_range)\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        return seq.astype(np.float32)\n    \n    def __getitem__(self, idx):\n        sequence = self.sequences[idx]\n        \n        # 训练时应用增强，少数类增强强度更高\n        if self.is_train and self.gestures is not None:\n            gesture = self.gestures[idx]\n            # 根据类别调整增强强度（少数类增强更强）\n            if self.aug_weights is not None:\n                aug_strength = 1.0 + self.aug_weights[gesture]  # 基础1.0 + 权重\n            else:\n                aug_strength = 1.0\n            \n            sequence = self._augment_sequence(sequence, aug_strength)\n        \n        sequence = torch.FloatTensor(sequence)\n        \n        if self.gestures is not None and self.is_target is not None:\n            gesture = torch.LongTensor([self.gestures[idx]])[0]\n            is_target = torch.LongTensor([self.is_target[idx]])[0]\n            return sequence, gesture, is_target\n        elif self.gestures is not None:\n            gesture = torch.LongTensor([self.gestures[idx]])[0]\n            return sequence, gesture\n        else:\n            return sequence\n\n# ----------------------\n# 6. 替换：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.shared_fc = nn.Sequential(\n            nn.Linear(hidden_dim * 2, hidden_dim),\n            nn.ReLU(),\n            nn.Dropout(dropout)\n        )\n        \n        # 5. 手势分类头\n        self.fc_gesture = nn.Linear(hidden_dim, num_gestures)\n        \n        # 6. 二元分类头（目标vs非目标）\n        self.fc_target = nn.Linear(hidden_dim, 2)\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        shared_features = self.shared_fc(attn_out)  # (batch, hidden_dim)\n        \n        # 5. 分类输出\n        gesture_out = self.fc_gesture(shared_features)  # 手势分类\n        target_out = self.fc_target(shared_features)    # 二元分类（目标vs非目标）\n        \n        return gesture_out, target_out\n\n# ----------------------\n# 7. 训练和验证函数（新增二元分类评估）\n# ----------------------\ndef train_epoch(model, train_loader, gesture_criterion, target_criterion, optimizer, device):\n    model.train()\n    total_loss = 0\n    total_gesture_correct = 0\n    total_target_correct = 0\n    total_samples = 0\n    \n    for sequences, gestures, is_target in tqdm(train_loader, desc=\"训练\"):\n        sequences = sequences.to(device)\n        gestures = gestures.to(device)\n        is_target = is_target.to(device)\n        \n        optimizer.zero_grad()\n        gesture_out, target_out = model(sequences)\n        \n        # 计算两个损失并加权求和\n        loss_gesture = gesture_criterion(gesture_out, gestures)\n        loss_target = target_criterion(target_out, is_target)\n        loss = loss_gesture + loss_target  # 同等权重\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        total_loss += loss.item() * sequences.size(0)\n        \n        # 手势分类准确率\n        _, predicted_gesture = torch.max(gesture_out.data, 1)\n        total_gesture_correct += (predicted_gesture == gestures).sum().item()\n        \n        # 二元分类准确率\n        _, predicted_target = torch.max(target_out.data, 1)\n        total_target_correct += (predicted_target == is_target).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    target_acc = total_target_correct / total_samples\n    \n    return avg_loss, gesture_acc, target_acc\n\ndef validate(model, val_loader, gesture_criterion, target_criterion, device, target_gestures=None, gesture_le=None):\n    model.eval()\n    total_loss = 0\n    total_gesture_correct = 0\n    total_target_correct = 0\n    total_samples = 0\n    \n    all_gesture_preds = []\n    all_gesture_labels = []\n    all_target_preds = []\n    all_target_labels = []\n    \n    with torch.no_grad():\n        for sequences, gestures, is_target in tqdm(val_loader, desc=\"验证\"):\n            sequences = sequences.to(device)\n            gestures = gestures.to(device)\n            is_target = is_target.to(device)\n            \n            gesture_out, target_out = model(sequences)\n            \n            # 计算两个损失并加权求和\n            loss_gesture = gesture_criterion(gesture_out, gestures)\n            loss_target = target_criterion(target_out, is_target)\n            loss = loss_gesture + loss_target  # 同等权重\n            \n            total_loss += loss.item() * sequences.size(0)\n            \n            # 手势分类准确率\n            _, predicted_gesture = torch.max(gesture_out.data, 1)\n            total_gesture_correct += (predicted_gesture == gestures).sum().item()\n            \n            # 二元分类准确率\n            _, predicted_target = torch.max(target_out.data, 1)\n            total_target_correct += (predicted_target == is_target).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_target_preds.extend(predicted_target.cpu().numpy())\n            all_target_labels.extend(is_target.cpu().numpy())\n    \n    avg_loss = total_loss / total_samples\n    gesture_acc = total_gesture_correct / total_samples\n    target_acc = total_target_correct / total_samples\n    \n    # 计算竞赛评估指标\n    # 1. 二元F1（目标vs非目标）\n    binary_f1 = f1_score(all_target_labels, all_target_preds, average='binary')\n    \n    # 2. 多类宏F1（仅对非目标手势）\n    # 找出非目标样本的索引\n    non_target_indices = [i for i, label in enumerate(all_target_labels) if label == 0]\n    non_target_gesture_preds = [all_gesture_preds[i] for i in non_target_indices]\n    non_target_gesture_labels = [all_gesture_labels[i] for i in non_target_indices]\n    \n    if len(non_target_indices) > 0 and len(np.unique(non_target_gesture_labels)) > 1:\n        non_target_macro_f1 = f1_score(non_target_gesture_labels, non_target_gesture_preds, average='macro')\n    else:\n        non_target_macro_f1 = 0.0  # 处理特殊情况\n    \n    # 3. 最终评分：二元F1和非目标宏F1的平均值\n    final_score = (binary_f1 + non_target_macro_f1) / 2\n    \n    # 4. 所有手势的宏F1（用于参考）\n    all_gesture_macro_f1 = f1_score(all_gesture_labels, all_gesture_preds, average='macro')\n    \n    return (avg_loss, gesture_acc, target_acc, binary_f1, non_target_macro_f1, final_score,\n            all_gesture_preds, all_gesture_labels, all_target_preds, all_target_labels)\n\n# ----------------------\n# 8. 新增：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# 9. 模型训练与验证主函数（支持竞赛评估指标+Loss-Epoch曲线）\n# ----------------------\ndef train_models(train_sequences, train_gestures, train_is_target, train_ids, \n                test_sequences, n_features, num_gestures, class_counts, \n                target_gestures, gesture_le, 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    # 计算手势类别权重（解决类别不平衡）\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    target_counts = np.bincount(train_is_target)\n    target_weights = torch.FloatTensor([1.0 / (count / len(train_is_target)) for count in target_counts]).to(device)\n    print(f\"二元分类权重: {target_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    test_preds_target = np.zeros((len(test_sequences), 2))  # 二元分类预测\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_target_train, y_target_val = train_is_target[train_idx], train_is_target[val_idx]\n        \n        # 创建数据集和数据加载器（加入数据增强标记和类别分布）\n        train_dataset = SensorSequenceDataset(\n            X_train, y_gesture_train, y_target_train, is_train=True, \n            aug_prob=0.5, class_counts=class_counts\n        )\n        val_dataset = SensorSequenceDataset(\n            X_val, y_gesture_val, y_target_val, is_train=False\n        )\n        \n        # 根据GPU内存调整batch size（双T4优化）\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 < 16:  # T4通常为16GB，小内存时调整\n                print(f\"检测到GPU内存 {gpu_mem:.1f}GB，调整batch size为32\")\n                batch_size = 32\n        \n        # 优化DataLoader参数（双GPU环境）\n        num_workers = min(4, os.cpu_count() // 2)  # CPU核心的一半\n        train_loader = DataLoader(\n            train_dataset, \n            batch_size=batch_size, \n            shuffle=True, \n            num_workers=num_workers,\n            pin_memory=True if torch.cuda.is_available() else False,\n            drop_last=True  # 丢弃不完整批次，避免计算异常\n        )\n        \n        val_loader = DataLoader(\n            val_dataset, \n            batch_size=batch_size * 2,  # 验证集batch_size翻倍（无反向传播）\n            shuffle=False, \n            num_workers=num_workers//2,  # 验证集减少worker\n            pin_memory=True if torch.cuda.is_available() else False\n        )\n        \n        # 初始化模型（轻量化）\n        hidden_dim = 64 if n_features < 100 else 128  # 轻量化模型\n        num_layers = 2  # 减少层数，防止过拟合\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并行（核心优化）\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        gesture_criterion = LabelSmoothingCrossEntropyLoss(\n            smoothing=0.1,  # 软化强度\n            weight=class_weights\n        )\n        \n        target_criterion = LabelSmoothingCrossEntropyLoss(\n            smoothing=0.05,  # 二元分类轻微软化\n            weight=target_weights\n        )\n        \n        optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-5)\n        # 学习率调度：CosineAnnealing\n        scheduler = optim.lr_scheduler.CosineAnnealingLR(\n            optimizer, T_max=50, eta_min=1e-6\n        )\n        \n        # 训练模型\n        best_final_score = 0\n        patience = 10  # 延长早停耐心值\n        counter = 0\n        \n        # 新增：记录训练过程的Loss和指标，用于绘图\n        train_losses = []\n        val_losses = []\n        train_gesture_accs = []\n        val_gesture_accs = []\n        train_target_accs = []\n        val_target_accs = []\n        \n        for epoch in range(50):\n            print(f\"\\n epoch {epoch+1}/50 | 当前学习率: {optimizer.param_groups[0]['lr']:.6f}\")\n            \n            # 训练阶段\n            train_loss, train_gesture_acc, train_target_acc = train_epoch(\n                model, train_loader, gesture_criterion, target_criterion, optimizer, device\n            )\n            \n            # 验证\n            (val_loss, val_gesture_acc, val_target_acc, binary_f1, \n             non_target_macro_f1, final_score, gesture_preds, \n             gesture_labels, target_preds, target_labels) = validate(\n                model, val_loader, gesture_criterion, target_criterion, \n                device, target_gestures, gesture_le\n            )\n            \n            # 记录指标\n            train_losses.append(train_loss)\n            val_losses.append(val_loss)\n            train_gesture_accs.append(train_gesture_acc)\n            val_gesture_accs.append(val_gesture_acc)\n            train_target_accs.append(train_target_acc)\n            val_target_accs.append(val_target_acc)\n            \n            # 打印 epoch 结果\n            print(f\"训练损失: {train_loss:.4f}, 手势准确率: {train_gesture_acc:.4f}, 目标准确率: {train_target_acc:.4f}\")\n            print(f\"验证损失: {val_loss:.4f}, 手势准确率: {val_gesture_acc:.4f}, 目标准确率: {val_target_acc:.4f}\")\n            print(f\"二元F1: {binary_f1:.4f}, 非目标宏F1: {non_target_macro_f1:.4f}\")\n            print(f\"最终评分: {final_score:.4f}\")\n            \n            # 学习率调度\n            scheduler.step()\n            \n            # 早停机制：监控最终评分\n            if final_score > best_final_score:\n                best_final_score = final_score\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        # 新增：绘制Loss-Epoch曲线\n        plt.figure(figsize=(14, 6))\n        # 损失曲线\n        plt.subplot(1, 2, 1)\n        plt.plot(train_losses, label='Training Loss', color='blue')\n        plt.plot(val_losses, label='Validation Loss', color='red')\n        plt.title(f'Fold {fold+1} - Loss Curves', fontsize=12)\n        plt.xlabel('Epoch')\n        plt.ylabel('Loss')\n        plt.legend()\n        # 准确率曲线\n        plt.subplot(1, 2, 2)\n        plt.plot(train_gesture_accs, label='Training Gesture Accuracy', color='blue')\n        plt.plot(val_gesture_accs, label='Validation Gesture Accuracy', color='red')\n        plt.plot(train_target_accs, label='Training Target Accuracy', color='green', linestyle='--')\n        plt.plot(val_target_accs, label='Validation Target Accuracy', color='orange', linestyle='--')\n        plt.title(f'Fold {fold+1} - Accuracy Curves', fontsize=12)\n        plt.xlabel('Epoch')\n        plt.ylabel('Accuracy')\n        plt.legend()\n        plt.tight_layout()\n        plt.show()\n        \n        # 加载最佳模型\n        model.load_state_dict(torch.load(f\"best_model_fold_{fold+1}.pth\"))\n        \n        # 再次验证\n        (_, _, _, binary_f1, non_target_macro_f1, final_score, \n         gesture_preds, gesture_labels, target_preds, target_labels) = validate(\n            model, val_loader, gesture_criterion, target_criterion, \n            device, target_gestures, gesture_le\n        )\n        \n        print(f\"\\n折 {fold+1} 最佳结果:\")\n        print(f\"二元F1: {binary_f1:.4f}, 非目标宏F1: {non_target_macro_f1:.4f}\")\n        print(f\"最终评分: {final_score:.4f}\")\n        \n        # 打印分类报告\n        print(\"\\n手势分类报告:\")\n        print(classification_report(gesture_labels, gesture_preds))\n        \n        print(\"\\n二元分类报告:\")\n        print(classification_report(target_labels, target_preds))\n        \n        fold_results.append({\n            'fold': fold+1,\n            'binary_f1': binary_f1,\n            'non_target_macro_f1': non_target_macro_f1,\n            'final_score': final_score\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=num_workers,\n            pin_memory=True if torch.cuda.is_available() else False\n        )\n        \n        model.eval()\n        fold_test_gesture_preds = []\n        fold_test_target_preds = []\n        \n        with torch.no_grad():\n            for sequences in tqdm(test_loader, desc=\"测试集预测\"):\n                sequences = sequences.to(device)\n                gesture_out, target_out = model(sequences)\n                fold_test_gesture_preds.append(torch.softmax(gesture_out, dim=1).cpu().numpy())\n                fold_test_target_preds.append(torch.softmax(target_out, dim=1).cpu().numpy())\n        \n        # 聚合测试集预测\n        fold_test_gesture_preds = np.vstack(fold_test_gesture_preds)\n        fold_test_target_preds = np.vstack(fold_test_target_preds)\n        \n        test_preds_gesture += fold_test_gesture_preds / n_splits\n        test_preds_target += fold_test_target_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, test_preds_target\n\n# ----------------------\n# 10. 生成提交文件\n# ----------------------\ndef create_submission(test_preds_gesture, test_preds_target, test_ids, gesture_le, target_gestures):\n    \"\"\"生成符合竞赛要求的提交文件\"\"\"\n    print(\"\\n===== 生成提交文件 =====\")\n    \n    # 从预测概率获取类别\n    test_preds_gesture_class = np.argmax(test_preds_gesture, axis=1)\n    test_preds_target_class = np.argmax(test_preds_target, 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        'is_target': test_preds_target_class  # 1表示目标，0表示非目标\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\n\n# ----------------------\n# 11. 主函数\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, class_distribution, target_gestures = preprocess_data(\n            train, test, train_demo, test_demo\n        )\n        \n        # 自动确定合适的序列长度（核心优化：使用中位数而非1.5倍中位数）\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())  # 直接使用中位数，减少30%序列长度\n        print(f\"自动设置最大序列长度为: {max_seq_length}\")\n        \n        # 准备序列数据\n        (train_sequences, train_gestures, train_is_target, train_ids,\n         test_sequences, test_ids, n_features) = prepare_sequences(train, test, max_seq_length)\n        \n        # 清理内存\n        del train, test\n        gc.collect()\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, test_preds_target = train_models(\n            train_sequences, train_gestures, train_is_target, train_ids,\n            test_sequences, n_features, num_gestures, \n            class_counts=class_distribution, target_gestures=target_gestures,\n            gesture_le=gesture_le, n_splits=5\n        )\n        \n        # 生成提交文件\n        submission = create_submission(test_preds_gesture, test_preds_target, test_ids, gesture_le, target_gestures)\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-09-11T05:13:01.675737Z","iopub.execute_input":"2025-09-11T05:13:01.676299Z","iopub.status.idle":"2025-09-11T06:22:53.523184Z","shell.execute_reply.started":"2025-09-11T05:13:01.676260Z","shell.execute_reply":"2025-09-11T06:22:53.522245Z"}},"outputs":[],"execution_count":null}]}