{"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":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 基于多模态传感器数据的身体聚焦重复性行为识别研究  \n**罗春梅**","metadata":{}},{"cell_type":"markdown","source":"CMI - Detect Behavior with Sensor Data             \nhttps://www.kaggle.com/competitions/cmi-detect-behavior-with-sensor-data/data                    \n","metadata":{}},{"cell_type":"markdown","source":"# 第1章 绪论                \n## 1.1 研究背景和意义                      \n\n    身体聚焦的重复性行为（Body-focused Repetitive Behaviors, BFRBs），如拔毛癖（Trichotillomania）、抠皮症（Excoriation）等，是强迫症及相关障碍谱系中的常见症状。这些行为不仅可能导致组织损伤和感染，更会给患者带来严重的心理痛苦和社会功能损害，显著降低其生活质量。当前，BFRBs的临床评估主要依赖于患者的主观报告和短暂的临床观察，这种方法存在回忆偏差、社会期望效应和无法进行连续监测等固有缺陷，严重制约了诊断的准确性和干预效果的评估。                        \n    随着可穿戴计算技术的普及，利用腕戴式传感器进行客观、连续的行为监测为解决这一难题提供了新的途径。目前主流的消费级设备主要依赖惯性测量单元（Inertial Measurement Unit, IMU）数据。然而，BFRBs（如揪头发）与许多日常动作（如调整眼镜）在运动轨迹上高度相似，仅凭IMU数据难以实现高精度的区分，导致基于单一模态的检测系统特异性不足，误报率高，无法满足临床应用的可靠性要求。                      \n    因此，本研究直面一个关键的临床技术瓶颈：​​如何利用多模态传感器数据提升BFRB识别的准确性与特异性​​。本研究源于Child Mind Institute (CMI)在Kaggle平台上举办的算法竞赛，其核心目标是利用Helios设备采集的IMU、热成像（Thermopile）和飞行时间（Time-of-Flight, ToF）等多模态传感器数据，构建高性能分类模型。研究的成功不仅将验证新型传感器在精神健康监测中的“附加价值”，为下一代医疗级可穿戴设备的传感器选型提供关键的成本-效益决策依据，更将推动精神健康障碍的诊疗向数字化、客观化和精准化迈进，具有重大的产业应用价值和临床转化前景。\n\n    从学术角度看，本研究触及了医疗人工智能领域的几个核心挑战：1) 高维度、小样本学习：数据特征维度极高（特别是64像素5的ToF数据），而相对于18类细粒度手势，被试者数量有限，对模型的泛化能力提出极高要求。2) 多模态异构数据融合：如何有效融合物理特性、采样率和维度迥异的运动、热感和距离信息，是一个前沿的机器学习挑战。3) 可解释性：理解模型决策依据，识别出对区分BFRB最关键传感器和特征，既能增强临床信任度，也能加深对行为本身生物力学特征的理解。","metadata":{}},{"cell_type":"markdown","source":"## 1.2 研究内容                 \n    本研究的总目标是开发一个能够精准区分BFRB类行为与非BFRB类日常手势的自动分类模型，并定量评估新型热敏与测距传感器相对于传统IMU传感器在分类性能上的提升程度。具体任务定义为一项多分类时序信号识别任务：\n\n    1.主要任务（二元分类）：将输入的传感器序列分类为“BFRB类手势”（Target）或“非BFRB类手势”（Non-Target）。\n\n    2.次要任务（多分类）：在BFRB类手势内部，进一步识别出8种具体的细分类别（如“Above ear - Pull hair”、“Cheek - Pinch skin”等）；所有10种非BFRB类手势被归为统一的non_target类。\n\n    3.传感器贡献度评估任务：模型需能处理两种测试场景：一是仅包含IMU数据的序列（其他传感器通道为空），二是包含全部传感器（IMU+Thermopile+ToF）数据的序列。通过对比模型在两类数据上的性能，定量分析新增传感器的贡献。\n\n    最终模型性能的评价体系综合考量以上任务，采用竞赛定义的​​综合宏F1分数（Macro F1），即二元分类F1分数与细粒度多分类（9类）宏F1分数的平均值。","metadata":{}},{"cell_type":"markdown","source":"## 1.3 国内外研究现状            \n    国内在基于可穿戴传感器的人体活动识别（HAR）领域已有广泛研究，主要集中在利用智能手机或手环的IMU数据识别步行、跑步等日常宏观活动。近年来，研究开始向精细手势识别（如手语）和医疗康复（如帕金森病运动评估）延伸。然而，针对BFRB这类细微且与正常行为高度相似的异常行为识别研究尚处于起步阶段。大多数研究仍局限于单一IMU模态，对热敏、测距等多模态传感器的融合探索较为缺乏。所使用的数据多来自于公开的宏观活动数据集，缺乏针对BFRB的专业、精细标注数据集。                  \n    国际上，将可穿戴技术用于精神健康监测是一个活跃的研究方向。早期研究（如2015年左右）尝试使用腕带加速度计检测抠皮、拔毛行为的雏形，通过分析运动信号的周期性、幅度等特征进行识别。近年来，研究趋势明显向多模态融合方向发展。CMI此次竞赛提供的Helios设备及数据集代表了该方向的最新进展。类似地，斯坦福大学、哈佛医学院等机构的研究者也尝试集成肌电（EMG）、皮肤电（EDA）等传感器以提升特异性。在技术上，深度学习模型（如CNN、LSTM、Transformer）已成为处理传感器时序数据的主流方法，取代了早期依赖手工特征的传统机器学习模型（如SVM、随机森林）。\n    尽管已有研究取得进展，但​​SVM、随机森林等传统模型​​在处理此类高维、非线性时序数据时，往往依赖复杂的特征工程，且难以捕捉深层的时序依赖关系，性能存在瓶颈。当前研究仍存在三大空白：1) 缺乏公开、大规模、多模态的BFRB基准数据集；2) 对非IMU传感器的实际效用缺乏严格的量化评估；3) 针对细粒度、小样本行为的泛化模型架构探索不足。本研究将直接针对这些空白，利用CMI提供的独特多模态数据集，构建先进模型，并重点量化评估新型传感器的贡献。","metadata":{}},{"cell_type":"markdown","source":"# 第2章 相关理论与技术\n## 2.1 深度学习与神经网络\n### 2.1.1 算法核心思想\n    深度学习是机器学习的一个子领域，其核心思想是通过构建多层（“深度”）神经网络来学习数据的层次化特征表示。较低层学习基础特征（如图像的边缘、传感器的基本波动），而较高层将这些基础特征组合成更抽象、更复杂的模式（如特定的手势形状、行为序列）。本研究处理的是高维时序传感器数据，深度学习模型能够自动从原始数据中学习有效的特征表示，避免了复杂且依赖专家知识的手工特征工程，非常适合解决本任务中的复杂模式识别问题。","metadata":{}},{"cell_type":"markdown","source":"### 2.1.2 数学模型推导\n我们采用的全连接前馈神经网络（Fully Connected Feedforward Neural Network）由多个层组成，每一层的计算可表示为：                                  \n    \n$\\mathbf{z}^{(l)} = \\mathbf{W}^{(l)} \\mathbf{a}^{(l-1)} + \\mathbf{b}^{(l)}$\n\n$\\mathbf{a}^{(l)} = f(\\mathbf{z}^{(l)})$\n\n其中，$l$表示层数，$\\mathbf{W}^{(l)}$和$\\mathbf{b}^{(l)}$是该层的权重矩阵和偏置向量，$\\mathbf{a}^{(l-1)}$是上一层的输出（第一层的输入为原始特征$\\mathbf{x}$），$f$是激活函数（本研究中使用ReLU, $f(x) = \\max(0, x)$）。\n\n网络的最终输出层使用Softmax函数将输出转换为概率分布：\n\n$\\mathbf{a}_i^{(L)} = \\text{Softmax}(\\mathbf{z}^{(L)})_i = \\frac{e^{z_i^{(L)}}}{\\sum_{j=1}^{C} e^{z_j^{(L)}}}$\n\n\n其中，$C$ 是类别数（本研究中为18）。\n\n模型通过最小化交叉熵损失函数来学习参数：\n\n$$\\mathcal{L} = -\\frac{1}{N} \\sum_{i=1}^{N} \\sum_{c=1}^{C} y_{i,c} \\log(\\hat{y}_{i,c})$$\n\n其中，$N$ 是样本数，$y_{i,c}$ 是样本$i$在类别$c$上的真实标签（one-hot编码），$\\hat{y}_{i,c}$ 是模型预测的样本$i$属于类别$c$的概率。","metadata":{}},{"cell_type":"markdown","source":"### 2.1.3 算法优势                \n相比于竞赛任务中也可能用到的SVM或随机森林等传统机器学习算法，深度学习模型在本任务中具有显著优势：\n\n1.自动特征工程：能够直接从高维原始数据中学习最有效的特征表示，省去了繁琐且依赖领域知识的手工特征设计环节。\n\n2.强大的非线性拟合能力：通过多层非线性的堆叠，能够建模极其复杂的输入-输出关系，非常适合捕捉多模态传感器数据中复杂的模式。\n\n3.端到端学习：整个系统（从特征提取到分类）可以联合优化，通常能获得比分离式（先特征提取再分类）方法更好的性能。","metadata":{}},{"cell_type":"markdown","source":"## 2.2 PyTorch框架与GPU加速              \n本研究使用PyTorch作为核心深度学习框架。PyTorch是一个由Facebook开源的Python优先的深度学习库，以其动态计算图、直观的接口和强大的调试能力而深受研究人员喜爱。它提供了丰富的模块（如torch.nn, torch.optim）来快速构建和训练神经网络。\n\n为了高效处理大规模数据和高复杂度的模型，本研究利用GPU（图形处理器）进行并行加速计算。GPU拥有数千个核心，非常适合并行执行神经网络训练中大量的矩阵和向量运算。通过PyTorch可以轻松地将模型和数据转移到GPU（如model.to(‘cuda’)），从而将训练时间从数小时缩短至数分钟，极大地提高了实验迭代效率。","metadata":{}},{"cell_type":"markdown","source":"# 第3章 数据预处理\n## 3.1 数据描述\n本研究数据来源于Kaggle平台“CMI - Detect Behavior with Sensor Data”竞赛提供的公开数据集。该数据由Child Mind Institute通过其开发的Helios腕戴设备采集。数据集基本信息如下表所示：","metadata":{}},{"cell_type":"code","source":"# 导入必要的库\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import f1_score, classification_report, confusion_matrix\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom scipy.stats import pointbiserialr\n\n# 设置随机种子确保可重复性\ntorch.manual_seed(42)\nnp.random.seed(42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:38:45.447898Z","iopub.execute_input":"2025-09-10T03:38:45.448195Z","iopub.status.idle":"2025-09-10T03:38:45.453892Z","shell.execute_reply.started":"2025-09-10T03:38:45.448175Z","shell.execute_reply":"2025-09-10T03:38:45.453317Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== 数据描述 ====================\n# 加载数据\ntrain = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ntest = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv')\ntrain_demo = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\ntest_demo = pd.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv')\n\n# 合并demographics数据\ntrain = train.merge(train_demo, on='subject', how='left')\ntest = test.merge(test_demo, on='subject', how='left')\n\n# 查看数据基本信息\nprint(\"训练集形状:\", train.shape)\nprint(\"测试集形状:\", test.shape)\nprint(\"训练demo形状:\", train_demo.shape)\nprint(\"测试demo形状:\", test_demo.shape)\nprint(\"\\n训练集列名:\", len(train.columns.tolist()))\nprint(\"测试集列名:\", len(test.columns.tolist()))\nprint(\"\\n训练集前5行:\")\nprint(train.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:38:45.770588Z","iopub.execute_input":"2025-09-10T03:38:45.771025Z","iopub.status.idle":"2025-09-10T03:39:02.074590Z","shell.execute_reply.started":"2025-09-10T03:38:45.771005Z","shell.execute_reply":"2025-09-10T03:39:02.073935Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"数据描述可视化","metadata":{}},{"cell_type":"code","source":"# ==================== 描述性分析 ====================\n# 手势类型分布\nplt.figure(figsize=(14, 6))\ngesture_counts = train['gesture'].value_counts()\nplt.bar(gesture_counts.index, gesture_counts.values)\nplt.title('Gesture Type Distribution in Training Set')\nplt.xlabel('Gesture Type')\nplt.ylabel('Count')\nplt.xticks(rotation=45, ha='right')\nplt.tight_layout()\nplt.show()\nprint(f\"\\n手势类别分布:\\n{gesture_counts}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:02.076030Z","iopub.execute_input":"2025-09-10T03:39:02.076567Z","iopub.status.idle":"2025-09-10T03:39:02.378809Z","shell.execute_reply.started":"2025-09-10T03:39:02.076549Z","shell.execute_reply":"2025-09-10T03:39:02.378130Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 数值特征分布\nnumeric_features = ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z']\nfig, axes = plt.subplots(2, 4, figsize=(16, 8))\naxes = axes.flatten()\n\nfor i, feature in enumerate(numeric_features):\n    axes[i].hist(train[feature].dropna(), bins=50, alpha=0.7, color='skyblue', edgecolor='black')\n    axes[i].set_title(f'Distribution of {feature}')\n    axes[i].set_xlabel(feature)\n    axes[i].set_ylabel('Frequency')\n\n# 移除多余的子图\nfor i in range(len(numeric_features), 8):\n    fig.delaxes(axes[i])\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:02.379757Z","iopub.execute_input":"2025-09-10T03:39:02.380331Z","iopub.status.idle":"2025-09-10T03:39:03.812874Z","shell.execute_reply.started":"2025-09-10T03:39:02.380306Z","shell.execute_reply":"2025-09-10T03:39:03.812056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# demographic特征分布\ndemo_features = ['age', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']\nfig, axes = plt.subplots(2, 2, figsize=(12, 8))\naxes = axes.flatten()\n\nfor i, feature in enumerate(demo_features):\n    axes[i].hist(train[feature].dropna(), bins=30, alpha=0.7, color='lightgreen', edgecolor='black')\n    axes[i].set_title(f'Distribution of {feature}')\n    axes[i].set_xlabel(feature)\n    axes[i].set_ylabel('Frequency')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:03.813766Z","iopub.execute_input":"2025-09-10T03:39:03.814023Z","iopub.status.idle":"2025-09-10T03:39:04.855624Z","shell.execute_reply.started":"2025-09-10T03:39:03.814005Z","shell.execute_reply":"2025-09-10T03:39:04.855005Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3.2 数据清洗\n1.缺失值处理：对于Thermopile和IMU传感器的少量缺失值，采用均值填充策略，使用训练集上计算得到的各特征均值进行填充。对于ToF传感器，原始数据中用-1表示无信号反射，我们将这些-1值视为缺失值，并使用np.nan替换，后续在特征工程中对其进行聚合统计（如求均值、最大值时自动忽略NaN值）。\n\n2.异常值处理：鉴于传感器数据的复杂性，未采用简单的3σ原则进行剔除，而是通过后续的标准化和模型正则化（如Dropout、权重衰减）来增强模型对潜在异常值的鲁棒性。\n\n3.重复数据：经检查，数据集中无完全重复的行。","metadata":{}},{"cell_type":"code","source":"# ==================== 数据清洗 ====================\n# 处理TOF传感器的-1值\ntof_cols = [col for col in train.columns if col.startswith('tof_')]\nfor col in tof_cols:\n    train[col] = train[col].replace(-1, np.nan)\n    test[col] = test[col].replace(-1, np.nan)\n\n# 填充缺失值 - 只使用训练集统计量\nnumeric_cols = train.select_dtypes(include=np.number).columns.drop(['gesture'], errors='ignore')\nfor col in numeric_cols:\n    if train[col].isnull().any():\n        mean_val = train[col].mean()\n        train[col] = train[col].fillna(mean_val)\n        if col in test.columns:\n            # 使用训练集的统计量填充测试集，避免数据泄露\n            test[col] = test[col].fillna(mean_val)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:04.857795Z","iopub.execute_input":"2025-09-10T03:39:04.858026Z","iopub.status.idle":"2025-09-10T03:39:10.343587Z","shell.execute_reply.started":"2025-09-10T03:39:04.858002Z","shell.execute_reply":"2025-09-10T03:39:10.342951Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 数值型列\nnumeric_cols = train.select_dtypes(include=np.number).columns\n# 非数值型列\nnon_numeric_cols = train.select_dtypes(exclude=np.number).columns\n\nprint(\"数值型列数量：\", len(numeric_cols))\nprint(\"非数值型列数量：\", len(non_numeric_cols))\nprint(\"\\n非数值型列名称：\", list(non_numeric_cols))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:10.344344Z","iopub.execute_input":"2025-09-10T03:39:10.344597Z","iopub.status.idle":"2025-09-10T03:39:11.654480Z","shell.execute_reply.started":"2025-09-10T03:39:10.344570Z","shell.execute_reply":"2025-09-10T03:39:11.653840Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(numeric_cols)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:11.655354Z","iopub.execute_input":"2025-09-10T03:39:11.655624Z","iopub.status.idle":"2025-09-10T03:39:11.660308Z","shell.execute_reply.started":"2025-09-10T03:39:11.655601Z","shell.execute_reply":"2025-09-10T03:39:11.659710Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3.3 特征工程\n### 3.3.1 特征变换          \n归一化：对四元数（rot_w, rot_x, rot_y, rot_z）进行了向量归一化，确保其模长为1，符合其数学性质。对所有数值特征（包括新构造的特征）在后续步骤中进行了标准化（StandardScaler），即转换为均值为0、标准差为1的分布。\n\n时序位置特征：创建了sequence_counter_norm特征，表示每个数据点在其所属序列中的相对位置（0到1），为模型提供时序上下文信息。","metadata":{}},{"cell_type":"code","source":"# ==================== 特征变换 ====================\n# 四元数归一化\nquat_cols = ['rot_w', 'rot_x', 'rot_y', 'rot_z']\nfor df in [train, test]:\n    quat_norm = np.sqrt(df[quat_cols].pow(2).sum(axis=1))\n    # 避免除以零\n    quat_norm[quat_norm == 0] = 1e-10\n    df[quat_cols] = df[quat_cols].div(quat_norm.values.reshape(-1,1), axis=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:11.661069Z","iopub.execute_input":"2025-09-10T03:39:11.661337Z","iopub.status.idle":"2025-09-10T03:39:11.775884Z","shell.execute_reply.started":"2025-09-10T03:39:11.661314Z","shell.execute_reply":"2025-09-10T03:39:11.775037Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<!-- 特征前后个数清理对比 -->\n","metadata":{}},{"cell_type":"code","source":"# ==================== 数据扩增 ====================\n# 时间特征\nfor df in [train, test]:\n    df['sequence_counter_norm'] = df.groupby('sequence_id')['sequence_counter'].transform(\n        lambda x: (x - x.min()) / (x.max() - x.min() + 1e-10))  # 避免除以零\n\n# 增强的特征工程\ndef create_advanced_features(df):\n    # 基础特征\n    df['acc_mag'] = np.sqrt(df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2)\n    df['rot_mag'] = np.sqrt(df['rot_x']**2 + df['rot_y']**2 + df['rot_z']**2)\n    \n    # 角速度特征 (使用四元数差分)\n    df['acc_diff'] = df.groupby('sequence_id')['acc_mag'].diff().fillna(0)\n    df['rot_diff'] = df.groupby('sequence_id')['rot_mag'].diff().fillna(0)\n    # TOF特征统计\n    tof_features = []\n    for i in range(1, 6):\n        sensor_cols = [col for col in tof_cols if col.startswith(f'tof_{i}_')]\n        if sensor_cols:\n            df[f'tof_{i}_mean'] = df[sensor_cols].mean(axis=1)\n            df[f'tof_{i}_std'] = df[sensor_cols].std(axis=1)\n            df[f'tof_{i}_max'] = df[sensor_cols].max(axis=1)\n            df[f'tof_{i}_min'] = df[sensor_cols].min(axis=1)\n            tof_features.extend([f'tof_{i}_mean', f'tof_{i}_std', f'tof_{i}_max', f'tof_{i}_min'])\n    \n    # 温度传感器特征\n    thm_cols = [f'thm_{i}' for i in range(1, 6)]\n    df['thm_mean'] = df[thm_cols].mean(axis=1)\n    df['thm_std'] = df[thm_cols].std(axis=1)\n    df['thm_max'] = df[thm_cols].max(axis=1)\n    df['thm_min'] = df[thm_cols].min(axis=1)\n    # 序列统计特征\n    seq_group = df.groupby('sequence_id')\n    df['seq_acc_mean'] = seq_group['acc_mag'].transform('mean')\n    df['seq_acc_std'] = seq_group['acc_mag'].transform('std')\n    df['seq_rot_mean'] = seq_group['rot_mag'].transform('mean')\n    df['seq_rot_std'] = seq_group['rot_mag'].transform('std')\n    \n    return df, tof_features\n\ntrain, tof_features = create_advanced_features(train)\ntest, _ = create_advanced_features(test)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:11.776699Z","iopub.execute_input":"2025-09-10T03:39:11.776922Z","iopub.status.idle":"2025-09-10T03:39:22.664942Z","shell.execute_reply.started":"2025-09-10T03:39:11.776905Z","shell.execute_reply":"2025-09-10T03:39:22.664209Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 探索性分析\n特征相关性分析：计算了所有工程化特征之间的相关系数，并绘制热力图。发现部分高度相关的特征对（如不同ToF传感器的统计量之间），这为理解数据和模型提供了 insights，但并未立即删除，因其可能包含互补信息，交由模型自行判断其重要性。","metadata":{}},{"cell_type":"code","source":"# ==================== 探索性分析 ====================\n# 相关性分析热图\nplt.figure(figsize=(20, 16))\nselected_features = ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z', \n                    'acc_mag', 'rot_mag', 'acc_diff', 'rot_diff', 'thm_mean', \n                    'age', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']\ncorr_matrix = train[selected_features].corr()\n\n# 绘制热图\nsns.heatmap(corr_matrix, annot=True, fmt=\".2f\", cmap='coolwarm', \n            center=0, linewidths=0.5, cbar_kws={\"shrink\": 0.8})\nplt.title('Feature Correlation Heatmap', fontsize=16)\nplt.xticks(rotation=45, ha='right')\nplt.yticks(rotation=0)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:22.665819Z","iopub.execute_input":"2025-09-10T03:39:22.666115Z","iopub.status.idle":"2025-09-10T03:39:23.888268Z","shell.execute_reply.started":"2025-09-10T03:39:22.666094Z","shell.execute_reply":"2025-09-10T03:39:23.887560Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 特征与目标变量的相关性分析\ntarget_corr = {}\ny_encoded = LabelEncoder().fit_transform(train['gesture'])\nfor feature in selected_features:\n    corr, _ = pointbiserialr(train[feature], y_encoded)\n    target_corr[feature] = corr\n\n# 转换为DataFrame并排序\ntarget_corr_df = pd.DataFrame.from_dict(target_corr, orient='index', columns=['Correlation'])\ntarget_corr_df.sort_values('Correlation', ascending=False, inplace=True)\n\n# 绘制相关性条形图\nplt.figure(figsize=(12, 8))\nsns.barplot(x=target_corr_df['Correlation'], y=target_corr_df.index, palette='viridis')\nplt.title('Feature Correlation with Target Variable', fontsize=16)\nplt.xlabel('Point-Biserial Correlation Coefficient')\nplt.ylabel('Features')\nplt.grid(axis='x', linestyle='--', alpha=0.7)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:23.889172Z","iopub.execute_input":"2025-09-10T03:39:23.889420Z","iopub.status.idle":"2025-09-10T03:39:24.423562Z","shell.execute_reply.started":"2025-09-10T03:39:23.889400Z","shell.execute_reply":"2025-09-10T03:39:24.422792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 类别与特征的箱线图分析\nselected_gestures = ['Text on phone', 'Neck - scratch', 'Eyebrow - pull hair', \n                    'Forehead - scratch', 'Wave hello', 'Write name in air']\nselected_features_for_boxplot = ['acc_mag', 'rot_mag', 'thm_mean', 'age']\n\nfor feature in selected_features_for_boxplot:\n    plt.figure(figsize=(14, 6))\n    sns.boxplot(x='gesture', y=feature, data=train[train['gesture'].isin(selected_gestures)], \n                palette='Set2')\n    plt.title(f'Distribution of {feature} by Gesture Type', fontsize=14)\n    plt.xlabel('Gesture Type')\n    plt.ylabel(feature)\n    plt.xticks(rotation=45, ha='right')\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:24.424350Z","iopub.execute_input":"2025-09-10T03:39:24.424538Z","iopub.status.idle":"2025-09-10T03:39:28.567449Z","shell.execute_reply.started":"2025-09-10T03:39:24.424523Z","shell.execute_reply":"2025-09-10T03:39:28.566752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(tof_features)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:28.568147Z","iopub.execute_input":"2025-09-10T03:39:28.568326Z","iopub.status.idle":"2025-09-10T03:39:28.572880Z","shell.execute_reply.started":"2025-09-10T03:39:28.568310Z","shell.execute_reply":"2025-09-10T03:39:28.572353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(numeric_cols)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:28.575359Z","iopub.execute_input":"2025-09-10T03:39:28.575589Z","iopub.status.idle":"2025-09-10T03:39:28.592360Z","shell.execute_reply.started":"2025-09-10T03:39:28.575574Z","shell.execute_reply":"2025-09-10T03:39:28.591573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== 特征选择 ====================\n# 选择最终特征列\nbase_features = ['age', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm']\nengineered_features = ['acc_mag', 'rot_mag', 'acc_diff', 'rot_diff', \n                       'thm_mean', 'thm_std', 'thm_max', 'thm_min',\n                       'seq_acc_mean', 'seq_acc_std', 'seq_rot_mean', 'seq_rot_std',\n                       'sequence_counter_norm']\nfeature_cols = base_features + engineered_features + tof_features\n\n# 特征数量对比\nplt.figure(figsize=(10, 6))\nbars = plt.bar(['Original features', 'Engineered features'], [len(numeric_cols), len(feature_cols)], \n               color=['skyblue', 'lightgreen'], alpha=0.8)\n\nfor bar in bars:\n    height = bar.get_height()\n    plt.text(bar.get_x() + bar.get_width()/2., height,\n             f'{int(height)}', ha='center', va='bottom')\nplt.title('Number of Features Comparison')\nplt.ylabel('Number of features')\nplt.grid(axis='y', linestyle='--', alpha=0.7)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:28.593085Z","iopub.execute_input":"2025-09-10T03:39:28.593247Z","iopub.status.idle":"2025-09-10T03:39:28.762581Z","shell.execute_reply.started":"2025-09-10T03:39:28.593233Z","shell.execute_reply":"2025-09-10T03:39:28.761765Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.3.4 特征选择\n","metadata":{}},{"cell_type":"code","source":"# ==================== 数据集划分 ====================\n# 编码目标变量\nle_gesture = LabelEncoder()\ny = le_gesture.fit_transform(train['gesture'])\n\n# 数据标准化\nscaler = StandardScaler()\nX_scaled = scaler.fit_transform(train[feature_cols].values)\nX_test_scaled = scaler.transform(test[feature_cols].values)\n\n# 划分训练验证集\nX_train, X_val, y_train, y_val = train_test_split(X_scaled, y, test_size=0.2, random_state=42, stratify=y)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:28.763419Z","iopub.execute_input":"2025-09-10T03:39:28.763672Z","iopub.status.idle":"2025-09-10T03:39:29.661912Z","shell.execute_reply.started":"2025-09-10T03:39:28.763647Z","shell.execute_reply":"2025-09-10T03:39:29.661313Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3.4 数据集划分\n将训练集按8:2的比例随机划分为训练子集和验证集，划分时采用​​分层抽样策略，确保训练集和验证集中各类别手势的比例与原始训练集一致。测试集使用竞赛方提供的隐藏测试集，其真实标签不可见，用于最终评估模型泛化性能。此划分比例在保证足够训练数据的同时，预留了足够的样本进行可靠的验证评估。","metadata":{}},{"cell_type":"markdown","source":"<!-- 特征相关性图 -->","metadata":{}},{"cell_type":"markdown","source":"# 第4章 模型构建与训练\n## 4.1 实现细节\n开发环境：Python 3.10+, PyTorch 1.13+, scikit-learn, pandas, numpy, matplotlib, seaborn。\n\n硬件：使用NVIDIA Tesla P100 GPU进行模型训练，利用CUDA进行并行加速计算。\n\n核心代码框架：使用PyTorch自定义了数据集类（GestureDataset）、神经网络模型（ImprovedNeuralNetwork）和数据加载器（DataLoader）。","metadata":{}},{"cell_type":"markdown","source":"## 4.2 训练策略\n模型结构：采用了一个包含三个隐藏层（256-128-64个神经元）的全连接神经网络。每个隐藏层后接Batch Normalization、ReLU激活函数和Dropout概率为0.4的丢弃层。\n\n优化器：使用Adam优化器，初始学习率为0.001，并应用权重衰减（L2正则化，系数1e-4）以防止过拟合。\n\n学习率调度：使用ReduceLROnPlateau调度器，当验证集指标（Competition Metric）连续3个epoch不再提升时，将学习率减半。\n\n批量大小：设置为512，以在内存效率和训练稳定性之间取得平衡。\n\n训练轮数：设置为30个epoch，并采用早停法（Early Stopping）的变种——在训练过程中持续监控验证集性能，并始终保存性能最佳的模型状态，以避免过拟合。","metadata":{}},{"cell_type":"markdown","source":"比赛评估指标\n","metadata":{}},{"cell_type":"markdown","source":"模型构建","metadata":{}},{"cell_type":"markdown","source":"训练","metadata":{}},{"cell_type":"code","source":"# ==================== 模型构建与训练 ====================\n# 定义数据集类\nclass GestureDataset(Dataset):\n    def __init__(self, X, y=None):\n        self.X = torch.FloatTensor(X)\n        self.y = torch.LongTensor(y) if y is not None else None\n        \n    def __len__(self):\n        return len(self.X)\n    \n    def __getitem__(self, idx):\n        if self.y is not None:\n            return self.X[idx], self.y[idx]\n        return self.X[idx]\n\n# 改进的神经网络模型\nclass ImprovedNeuralNetwork(nn.Module):\n    def __init__(self, input_size, num_classes):\n        super(ImprovedNeuralNetwork, self).__init__()\n        self.layer1 = nn.Linear(input_size, 512)\n        self.bn1 = nn.BatchNorm1d(512)\n        self.layer2 = nn.Linear(512, 256)\n        self.bn2 = nn.BatchNorm1d(256)\n        self.layer3 = nn.Linear(256, 128)\n        self.bn3 = nn.BatchNorm1d(128)\n        self.layer4 = nn.Linear(128, 64)\n        self.bn4 = nn.BatchNorm1d(64)\n        self.output = nn.Linear(64, num_classes)\n        self.relu = nn.ReLU()\n        self.dropout = nn.Dropout(0.5)\n        \n    def forward(self, x):\n        x = self.relu(self.bn1(self.layer1(x)))\n        x = self.dropout(x)\n        x = self.relu(self.bn2(self.layer2(x)))\n        x = self.dropout(x)\n        x = self.relu(self.bn3(self.layer3(x)))\n        x = self.dropout(x)\n        x = self.relu(self.bn4(self.layer4(x)))\n        x = self.output(x)\n        return x\n\n# 定义竞赛指标\ndef competition_metric(y_true, y_pred):\n    \"\"\"计算竞赛指标：(Binary F1 + Macro F1)/2\"\"\"\n    binary_f1 = f1_score(np.where(y_true <= 7, 1, 0),\n                         np.where(y_pred <= 7, 1, 0),\n                         zero_division=0.0)\n    macro_f1 = f1_score(np.where(y_true <= 7, y_true, 99),\n                        np.where(y_pred <= 7, y_pred, 99),\n                        average=\"macro\", \n                        zero_division=0.0)\n    return 0.5 * (binary_f1 + macro_f1), binary_f1, macro_f1\n\n# 设置设备\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"使用设备: {device}\")\n\n# 计算类别权重以处理不平衡问题\nclass_weights = compute_class_weight('balanced', classes=np.unique(y), y=y)\nclass_weights = torch.FloatTensor(class_weights).to(device)\n\n# 创建数据加载器\ntrain_dataset = GestureDataset(X_train, y_train)\nval_dataset = GestureDataset(X_val, y_val)\ntrain_loader = DataLoader(train_dataset, batch_size=1024, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=1024, shuffle=False)\n\n# 初始化模型\ninput_dim = X_train.shape[1]\nnum_classes = len(le_gesture.classes_)\nmodel = ImprovedNeuralNetwork(input_dim, num_classes)\nmodel.to(device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:29.662704Z","iopub.execute_input":"2025-09-10T03:39:29.662959Z","iopub.status.idle":"2025-09-10T03:39:29.767314Z","shell.execute_reply.started":"2025-09-10T03:39:29.662941Z","shell.execute_reply":"2025-09-10T03:39:29.766482Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 第5章 模型评估与分析\n## 5.1 评估指标\n本研究采用竞赛定义的综合宏F1分数（Competition Metric）作为核心评估指标，它是二元分类F1分数（Target vs. Non-Target）和细粒度多分类宏F1分数（9个类：8个BFRB类 + 1个non_target类）的平均值。该指标能全面衡量模型在核心二元分类任务和细粒度分类任务上的综合性能。此外，在训练过程中监控交叉熵损失。","metadata":{}},{"cell_type":"code","source":"# 使用加权损失函数处理类别不平衡\ncriterion = nn.CrossEntropyLoss(weight=class_weights)\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'max', patience=3, factor=0.5)\n\n# 训练模型\nbest_metric = 0\nnum_epochs = 50\ntrain_loss_history = []\nval_loss_history = []\nmetric_history = []\nbinary_f1_history = []\nmacro_f1_history = []\n\nfor epoch in range(num_epochs):\n    # 训练阶段\n    model.train()\n    train_loss = 0\n    for batch_X, batch_y in train_loader:\n        batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(batch_X)\n        loss = criterion(outputs, batch_y)\n        loss.backward()\n        optimizer.step()\n        \n        train_loss += loss.item()\n    \n    # 验证阶段\n    model.eval()\n    val_loss = 0\n    all_preds = []\n    all_true = []\n    with torch.no_grad():\n        for batch_X, batch_y in val_loader:\n            batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            \n            outputs = model(batch_X)\n            loss = criterion(outputs, batch_y)\n            val_loss += loss.item()\n            \n            _, predicted = torch.max(outputs.data, 1)\n            all_preds.extend(predicted.cpu().numpy())\n            all_true.extend(batch_y.cpu().numpy())\n    \n    # 计算指标\n    metric_value, binary_f1, macro_f1 = competition_metric(np.array(all_true), np.array(all_preds))\n    scheduler.step(metric_value)\n    \n    # 记录历史\n    train_loss_history.append(train_loss/len(train_loader))\n    val_loss_history.append(val_loss/len(val_loader))\n    metric_history.append(metric_value)\n    binary_f1_history.append(binary_f1)\n    macro_f1_history.append(macro_f1)\n    \n    # 保存最佳模型\n    if metric_value > best_metric:\n        best_metric = metric_value\n        torch.save(model.state_dict(), 'best_model.pth')\n    \n    print(f'Epoch {epoch+1}/{num_epochs}, '\n          f'Train Loss: {train_loss/len(train_loader):.4f}, '\n          f'Val Loss: {val_loss/len(val_loader):.4f}, '\n          f'Competition Metric: {metric_value:.4f} (Binary F1: {binary_f1:.4f}, Macro F1: {macro_f1:.4f})')\n\n# ==================== 模型评估 ====================\n# 加载最佳模型并评估\nmodel.load_state_dict(torch.load('best_model.pth'))\nmodel.eval()\nall_preds = []\nall_true = []\n\nwith torch.no_grad():\n    for batch_X, batch_y in val_loader:\n        batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n        outputs = model(batch_X)\n        _, predicted = torch.max(outputs.data, 1)\n        all_preds.extend(predicted.cpu().numpy())\n        all_true.extend(batch_y.cpu().numpy())\n\nmetric_value, binary_f1, macro_f1 = competition_metric(np.array(all_true), np.array(all_preds))\nprint(f\"\\n最佳模型验证集表现:\")\nprint(f\"Competition Metric: {metric_value:.4f}\")\nprint(f\"Binary F1: {binary_f1:.4f}\")\nprint(f\"Macro F1: {macro_f1:.4f}\")\n\n# 绘制训练曲线\nplt.figure(figsize=(16, 6))\n\n# 损失曲线\nplt.subplot(1, 2, 1)\nplt.plot(train_loss_history, label='Train Loss', color='blue')\nplt.plot(val_loss_history, label='Validation Loss', color='orange')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Training and Validation Loss')\nplt.legend()\nplt.grid(True)\n\n# 指标曲线\nplt.subplot(1, 2, 2)\nplt.plot(metric_history, label='Competition Metric', color='green')\nplt.plot(binary_f1_history, label='Binary F1', color='red', linestyle='--')\nplt.plot(macro_f1_history, label='Macro F1', color='purple', linestyle='--')\nplt.xlabel('Epoch')\nplt.ylabel('Score')\nplt.title('Validation Metrics')\nplt.legend()\nplt.grid(True)\n\nplt.tight_layout()\nplt.show()\n\n# 生成详细评估报告\nprint(\"\\n详细分类报告:\")\nprint(classification_report(all_true, all_preds, target_names=le_gesture.classes_))\n\n# 绘制混淆矩阵\nplt.figure(figsize=(12, 10))\ncm = confusion_matrix(all_true, all_preds)\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=le_gesture.classes_, \n            yticklabels=le_gesture.classes_)\nplt.title('Confusion Matrix')\nplt.xlabel('Predicted')\nplt.ylabel('True')\nplt.xticks(rotation=45, ha='right')\nplt.yticks(rotation=0)\nplt.tight_layout()\nplt.show()\n\n# ==================== 预测与提交 ====================\n# 预测测试集\ntest_dataset = GestureDataset(X_test_scaled)\ntest_loader = DataLoader(test_dataset, batch_size=1024, shuffle=False)\n\nmodel.eval()\ntest_pred_encoded = []\nwith torch.no_grad():\n    for batch_X in test_loader:\n        batch_X = batch_X.to(device)\n        outputs = model(batch_X)\n        _, predicted = torch.max(outputs.data, 1)\n        test_pred_encoded.extend(predicted.cpu().numpy())\n\n# 标签解码\ntest_pred = le_gesture.inverse_transform(test_pred_encoded)\n\n# 创建提交文件\nsubmission_df = test[['sequence_id']].copy()\nsubmission_df['gesture'] = test_pred\nsubmission_df.to_csv('/kaggle/working/submission.csv', index=False)\n\nprint(\"\\n提交文件已保存: /kaggle/working/submission.csv\")\nprint(submission_df.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T03:39:29.768342Z","iopub.execute_input":"2025-09-10T03:39:29.768861Z","iopub.status.idle":"2025-09-10T03:44:13.839576Z","shell.execute_reply.started":"2025-09-10T03:39:29.768832Z","shell.execute_reply":"2025-09-10T03:44:13.839007Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5.2 评估结果","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}