多模态传感数据的标注成本一直很高。尤其是可穿戴设备采集的 IMU、PPG、心电、肌电信号,人工逐段打标签既费时间,又容易因为个体差异出现标注不一致。自监督学习恰好能解决这个问题:先在大规模无标签传感器数据上做预训练,再用少量标注数据微调下游任务。把自监督、多模态融合、智能可穿戴这三个方向放在一起,可以组成一套完整的技术链路,用来做活动识别和疲劳预测。
这篇文章不是讲某个现成的一键部署工具,而是梳理一套可落地的系统设计方案。我会从数据预处理、自监督预训练、多模态融合、下游任务训练,到边缘部署和接口调用,把关键环节拆开讲,并给出可运行的参考代码。无论你是做工业安全监测、驾驶员疲劳检测,还是健康管理,这套思路都能直接作为系统骨架。
如果你关心这几个问题:无标签数据怎么利用、多模态信号怎么融合、活动识别和疲劳预测能不能共用同一个预训练模型、部署到边缘设备有多大的计算开销,那这篇文章值得看完。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 方案类型 | 自监督预训练 + 多模态融合 + 可穿戴传感下游任务 |
| 主要功能 | 人体活动识别、疲劳状态预测、多模态传感器融合 |
| 输入数据 | IMU、PPG、ECG、EMG、温度等可穿戴传感器时序数据 |
| 核心技术 | 对比学习、掩码重建、跨模态对齐、时序 Transformer / CNN |
| 硬件需求 | 训练阶段推荐 NVIDIA GPU,推理阶段可运行在边缘设备 |
| 部署方式 | PyTorch 训练、ONNX 导出、服务化接口 |
| 是否支持批量任务 | 支持,数据批处理和批量推理均可设计 |
| 是否需要大量标注数据 | 预训练阶段不需要,微调阶段仅需少量标注 |
| 适合场景 | 工业安全、驾驶员疲劳监测、运动健康分析、医疗辅助评估 |
| 合规要求 | 涉及个人生理数据,必须做隐私脱敏和授权管理 |
这个方案的核心价值在于:把自监督预训练作为特征提取底座,多模态融合作为中间层,下游活动识别和疲劳预测共享同一个预训练特征空间。这样既能降低标注成本,又能提升模型的泛化能力。
2. 适用场景与使用边界
2.1 适用场景
第一类场景是工业安全。工人佩戴智能手环或安全帽,系统实时采集动作数据和生理数据,识别弯腰、搬运、攀爬等动作,同时结合心率变异性、皮肤电导等指标预测疲劳程度,及时预警。
第二类场景是驾驶员监测。通过方向盘上的传感器或者智能手表,采集手部动作、心率、加速度数据,判断驾驶员是否疲劳、注意力是否分散。
第三类场景是运动健康分析。跑步、骑行、力量训练时,利用 IMU 数据识别动作类型,利用 PPG 数据估算心率变化,结合疲劳模型给出休息建议。
第四类场景是康复评估。帕金森患者的运动障碍评估、脑卒中患者的康复训练监测,都可以用可穿戴传感器加自监督模型降低数据标注压力。
2.2 使用边界
这类系统涉及个人生理数据,必须遵守数据最小化原则。采集之前要明确告知用户,数据存储要加密,删除要可执行,不能用于超出授权范围的分析。
模型预测的是统计相关性,不是医学诊断结果。疲劳预测只能作为辅助参考,不能替代医生评估。如果应用在医疗场景,需要有临床验证流程。
3. 系统架构与整体流程
一套完整的智能可穿戴多模态系统分为五个环节:
数据采集与预处理、自监督预训练、多模态融合、下游任务训练、部署与接口服务。
这个链路和图像大模型的做法非常相似:先用大量无标注数据做预训练,再用少量标注数据做微调。唯一的区别是,这里的数据是时间序列信号,模型需要捕捉时序依赖和跨模态关系。
整体架构可以这么理解:
- 底层是传感器数据流,负责原始信号的采集和清洗。
- 中间层是自监督预训练模型,用无标签数据学习通用的生理与运动表征。
- 融合层把不同类型传感器的特征对齐到统一空间。
- 任务层通过一个轻量分类头或回归头完成活动识别和疲劳预测。
- 服务层将训练好的模型导出,通过 API 或边缘推理提供能力。
4. 环境准备与前置条件
4.1 软件环境
推荐使用 Linux 系统作为训练环境,Windows 也能跑,但建议使用 WSL2。Python 版本建议 3.9 或 3.10,PyTorch 版本建议 2.0 以上。CUDA 需要根据显卡驱动选择合适的版本,一般 11.8 或 12.1 都可以。
# 创建虚拟环境 python -m venv wearables_env source wearables_env/bin/activate # 安装核心依赖 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install numpy pandas scikit-learn matplotlib pip install einops timm tensorboard pip install onnx onnxruntime如果是纯推理环境,不需要训练,可以用 CPU 运行 ONNX 模型,这样对硬件的要求会低很多。
4.2 硬件建议
训练阶段,显存建议 8G 以上。实际上,模型规模控制在一千万参数以内时,6G 显存也能跑小 batch 训练。推理阶段可以完全脱离 GPU,用树莓派、Jetson Nano 或手机端都能运行轻量模型。
4.3 数据准备
你需要准备两类数据:
第一类是无标签数据,用于自监督预训练。可以直接从设备原始日志中切窗获得,不需要人工标注。获取成本低,数量越大越好。
第二类是少量有标签数据,用于下游任务微调。包括活动类别标签,比如走路、跑步、静坐、上下楼;以及疲劳标签,可以是等级评分,也可以是二分类指标。
5. 数据预处理与特征工程
5.1 数据清洗
可穿戴设备采集到的信号噪声很大。常见的处理方式包括中值滤波去除脉冲噪声、带通滤波去除基线漂移、去除运动伪影。IMU 数据一般用滑动窗口切分,窗口大小可以取 2 到 5 秒,重叠率设为 50%。
import numpy as np from scipy.signal import medfilt, butter, filtfilt def clean_signal(data, fs=50, lowcut=0.5, highcut=20): """对单通道信号做中值滤波和带通滤波""" # 中值滤波去除脉冲噪声 filtered = medfilt(data, kernel_size=5) # 带通滤波去除基线漂移和高频噪声 nyquist = 0.5 * fs low = lowcut / nyquist high = highcut / nyquist b, a = butter(4, [low, high], btype='band') return filtfilt(b, a, filtered) def sliding_window(data, window_size=250, step_size=125): """滑动窗口切分,窗口大小 250 点,步长 125 点(50% 重叠)""" windows = [] for start in range(0, len(data) - window_size + 1, step_size): windows.append(data[start:start + window_size]) return np.array(windows)5.2 多模态数据对齐
不同传感器有不同的采样率。IMU 可能是 50Hz,PPG 可能是 25Hz,ECG 可能是 125Hz。做多模态融合之前,需要把数据重采样到统一的频率,或者分别处理每个模态再对齐特征。重采样一般用线性插值或傅里叶插值。
from scipy import interpolate def resample_signal(signal, old_fs, new_fs): """线性插值重采样""" old_time = np.arange(len(signal)) / old_fs new_time = np.arange(0, old_time[-1], 1 / new_fs) f = interpolate.interp1d(old_time, signal, kind='linear') return f(new_time)5.3 数据增强
自监督学习对数据增强非常敏感。对传感器数据,可以设计三种增强策略:
- 幅度扰动:对信号乘以随机缩放系数,模拟传感器佩戴松紧变化。
- 时间扭曲:对信号做小幅度的时间轴伸缩,模拟不同运动速度。
- 通道遮蔽:随机遮蔽部分传感器通道,迫使模型从多模态冗余中学习。
增强策略可以直接复用 SimCLR 的思路,把同一段信号的两个增强版本视为正样本对。
6. 自监督预训练模型设计
6.1 预训练任务选择
传感器时序数据的自监督预训练,最常用的两个任务是对比学习和掩码重建。
对比学习的做法是:同一段原始信号生成两个增强视角,模型把它们的特征拉近;不同信号的特征推远。这样学到的特征对传感器噪声和个体差异更鲁棒。
掩码重建的做法是:把输入信号的一部分通道或时间片段遮盖,让模型从剩余部分重建原始信号。这和 BERT 的掩码语言模型类似,适合建模时序依赖。
两者可以结合。先用对比学习对齐整体语义,再用掩码重建捕捉细粒度时序结构。
6.2 模型结构
编码器可以选择 1D CNN、TCN 或者轻量 Transformer。考虑到可穿戴设备的数据长度有限,2 到 5 秒的窗口通常在 100 到 500 个采样点,所以模型不需要太深。一个 6 层的 Transformer 编码器,嵌入维度 128,已经足够。
import torch import torch.nn as nn class SensorEncoder(nn.Module): """轻量传感器时序编码器,输入多通道信号,输出特征向量""" def __init__(self, input_channels=16, embed_dim=128, depth=4, num_heads=4): super().__init__() self.input_proj = nn.Conv1d(input_channels, embed_dim, kernel_size=7, stride=2, padding=3) self.encoder_layer = nn.TransformerEncoderLayer( d_model=embed_dim, nhead=num_heads, dim_feedforward=512, activation='gelu', batch_first=True ) self.transformer = nn.TransformerEncoder(self.encoder_layer, num_layers=depth) self.norm = nn.LayerNorm(embed_dim) self.embed_dim = embed_dim def forward(self, x): # x: (batch, channels, time) x = self.input_proj(x) x = x.transpose(1, 2) x = self.transformer(x) x = x.mean(dim=1) return self.norm(x)6.3 对比学习损失
对比学习用 InfoNCE 损失。每一批样本中,把同一个样本的两个增强视图作为正样本对,其余样本对作为负样本。温度系数设为 0.1 到 0.2。
import torch.nn.functional as F class ContrastiveLoss(nn.Module): def __init__(self, temperature=0.1): super().__init__() self.temperature = temperature def forward(self, z1, z2): # z1, z2: (batch, embed_dim) z1 = F.normalize(z1, dim=-1) z2 = F.normalize(z2, dim=-1) batch_size = z1.size(0) # 计算所有样本对之间的相似度 logits = torch.matmul(z1, z2.T) / self.temperature # 对角线上的样本是正样本对 labels = torch.arange(batch_size, device=z1.device) loss = F.cross_entropy(logits, labels) return loss预训练完成后,编码器输出的特征向量就可以作为通用传感器表征。对活动识别,这个特征可以区分走路、跑步、静坐等动作;对疲劳预测,这个特征包含了心率变异性、动作幅度变化等信息。
7. 多模态融合策略
可穿戴系统通常同时采集多个模态的数据,融合方式直接决定模型上限。
7.1 早期融合
把多个传感器通道拼接在一起,作为模型的输入。实现最简单,但要求不同模态采样率一致,且对噪声鲁棒性较差。
7.2 晚期融合
每个模态分别用一个编码器提取特征,最后拼接特征向量再做分类。好处是各模态可以独立优化,坏处是失去了模态间的交互信息。
7.3 跨模态注意力融合
用注意力机制让不同模态的特征相互对齐。比如用 IMU 特征作为 query,用 PPG 特征作为 key 和 value,模型会自动学到运动信息与生理信息之间的关联。
如果观察到走路时心率变化,就能把“动作类型”和“生理响应”关联起来,这对疲劳预测非常有价值。
import torch import torch.nn as nn class CrossModalFusion(nn.Module): """跨模态注意力融合层:imu_feat 作为 query,ppg_feat 作为 key/value""" def __init__(self, embed_dim=128, num_heads=4): super().__init__() self.attention = nn.MultiheadAttention(embed_dim, num_heads, batch_first=True) self.norm = nn.LayerNorm(embed_dim) def forward(self, imu_feat, ppg_feat): # imu_feat: (batch, seq_len, embed_dim) # ppg_feat: (batch, seq_len, embed_dim) attn_out, _ = self.attention(imu_feat, ppg_feat, ppg_feat) return self.norm(attn_out + imu_feat)跨模态注意力融合适合数据质量较好、模态之间有互补关系的场景。工业场景中,IMU 反映动作,PPG 反映生理负荷,融合后对疲劳预测的准确率会有明显提升。
8. 下游任务训练与效果验证
8.1 活动识别任务
活动识别是分类任务。预训练编码器加上一个全连接分类头,在少量标注数据上微调。
class ActivityClassifier(nn.Module): def __init__(self, encoder, num_classes=6, embed_dim=128): super().__init__() self.encoder = encoder self.classifier = nn.Linear(embed_dim, num_classes) def forward(self, x): feat = self.encoder(x) return self.classifier(feat)微调时,可以先冻结编码器,只训练分类头,观察效果。如果效果不够,再解冻编码器,用小学习率继续微调。这个流程能最大程度保留预训练学到的通用特征。
8.2 疲劳预测任务
疲劳预测可以做分类,也可以做回归。二分类是“疲劳”与“不疲劳”,回归是输出疲劳等级数值或风险概率。推荐使用回归加阈值的方式,这样可以根据不同场景调整判断标准。
疲劳标签的来源通常有两种:一种是通过心理学量表或专家打分获得的标签,另一种是通过反应时长测试等客观指标生成的标签。无论哪种,都要保证标签的可靠性和一致性。
class FatiguePredictor(nn.Module): def __init__(self, encoder, embed_dim=128): super().__init__() self.encoder = encoder self.regressor = nn.Sequential( nn.Linear(embed_dim, 64), nn.ReLU(), nn.Linear(64, 1), nn.Sigmoid() ) def forward(self, x): feat = self.encoder(x) return self.regressor(feat)输出经过 Sigmoid 后,值域在 0 到 1 之间。可以把 0.5 作为疲劳风险的默认阈值,实际应用中根据误报和漏报的代价调整。
8.3 验证指标与测试流程
活动识别用准确率、F1 分数、混淆矩阵评估。多类别活动识别中,单纯准确率可能掩盖少数类性能差的问题,建议重点看每个类别的 F1。
疲劳预测用 AUC、F1 分数评估二分类效果,用均方误差评估回归效果。同时要画出按时间分布的预测曲线,观察疲劳状态的变化趋势是否平滑。
测试维度建议
- 单模态与多模态对比:验证融合是否有效。
- 有预训练与无预训练对比:验证自监督预训练是否带来提升。
- 交叉用户验证:把部分用户的全部数据作为验证集,测试跨个体泛化能力。
- 数据量消融:标注数据分别取 10%、25%、50%、100%,观察微调数据量对效果的影响。
9. 模型导出与接口调用
9.1 ONNX 导出
训练完成后,把模型导出为 ONNX 格式,方便边缘设备部署。
import torch import onnx import onnxruntime as ort def export_onnx(model, input_shape, output_path): model.eval() dummy_input = torch.randn(1, input_shape[0], input_shape[1]) torch.onnx.export( model, dummy_input, output_path, input_names=['sensor_input'], output_names=['output'], dynamic_axes={'sensor_input': {0: 'batch_size'}}, opset_version=13 ) print(f"ONNX model saved to {output_path}") # 导出示例 # export_onnx(model, input_shape=(16, 250), output_path="wearable_model.onnx")导出后启动 ONNX Runtime 即可跨平台推理。
import onnxruntime as ort import numpy as np sess = ort.InferenceSession("wearable_model.onnx") input_name = sess.get_inputs()[0].name def infer(sensor_data): # sensor_data: (channels, time) data = sensor_data[np.newaxis, :, :].astype(np.float32) result = sess.run(None, {input_name: data}) return result[0]9.2 接口 API 设计
用 Flask 或 FastAPI 把推理封装成 HTTP 接口,方便与其他系统集成。
from flask import Flask, request, jsonify import numpy as np app = Flask(__name__) # 实际使用中在这里加载 ONNX session # sess = ort.InferenceSession("wearable_model.onnx") @app.route("/predict", methods=["POST"]) def predict(): data = request.get_json() sensor_input = np.array(data["sensor_data"], dtype=np.float32) sensor_input = sensor_input[np.newaxis, :, :] # result = sess.run(None, {"sensor_input": sensor_input}) result = [[0.85]] # 占位返回 return jsonify({"activity": "walking", "fatigue_score": float(result[0][0])}) if __name__ == "__main__": app.run(host="0.0.0.0", port=8000)curl 调用示例:
curl -X POST http://127.0.0.1:8000/predict \ -H "Content-Type: application/json" \ -d '{"sensor_data": [[0.1, 0.2, 0.3], [0.5, 0.6, 0.7]]}'接口服务启动后,可以接入工控平台、手机 APP 或后端管理系统。
10. 资源占用与性能观察
10.1 训练阶段资源占用
训练阶段主要看 GPU 显存。模型参数规模在一千万以内,batch size 设为 32,输入 16 通道 250 个采样点,显存占用通常在 6G 到 10G 之间。显存不够时可以降低 batch size、缩小嵌入维度或减少 Transformer 层数。
10.2 推理阶段资源占用
推理阶段模型很小。ONNX 模型大小通常在几十兆字节以内。在 CPU 上单次推理延迟可以控制在 10 到 50 毫秒,取决于序列长度和模型深度。在 Jetson Nano 或树莓派上需要做量化,把 FP32 转为 FP16 或 INT8,进一步降低延迟。
10.3 如何观察资源占用
- 训练时用
nvidia-smi看显存占用。 - 推理时可以用
top或htop看 CPU 和内存。 - 接口服务要关注延迟百分位数,比如 P95 延迟,而不是只看平均延迟。
- 批量推理时要关注吞吐量,即每秒处理多少条窗口数据。
# 训练时定时监控 GPU watch -n 2 nvidia-smi # 查看 CPU 内存 htop10.4 降低资源占用的方法
- 输入序列裁剪。把 5 秒窗口缩到 3 秒,如果性能不明显下降,可以降低计算量。
- 模型剪枝。去掉不重要的注意力头。
- 知识蒸馏。用完整模型作为教师,训练一个小模型作为学生。
- 量化。ONNX Runtime 支持动态量化,代码改动量很小。
import onnxruntime as ort # 打开量化后的模型 sess = ort.InferenceSession("wearable_model_int8.onnx")11. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 预训练 loss 不下降 | 数据增强太强或太弱 | 打印增强前后的数据分布 | 调整增强幅度,检查归一化 |
| 对比学习训练不稳定 | 温度系数设置不当 | 观察 loss 曲线是否震荡 | 尝试 temperature 0.05 到 0.2 |
| 活动识别准确率低 | 微调数据太少或冻结层数过多 | 画出混淆矩阵 | 解冻更多层,增加增强,增加标注数据 |
| 疲劳预测过拟合 | 标签噪声大或数据量过少 | 检查训练集和验证集指标差距 | 增加正则化,降低模型容量 |
| 多模态融合没有提升 | 模态间相关性低或对齐不到位 | 单独测试各模态性能 | 检查采样率对齐,调整融合位置 |
| ONNX 推理结果不一致 | 预处理逻辑未对齐 | 对比 PyTorch 与 ONNX 输出 | 确认归一化、滑动窗口参数一致 |
| 接口请求超时 | 推理端排队严重 | 查看服务日志和并发数 | 加请求排队机制,或用 Message Queue |
| 内存持续增长 | 推理服务存在资源泄漏 | 长时间压测观察 | 限制线程池,定期重启,排查缓存 |
12. 最佳实践与合规建议
第一,预训练数据量一定要足够大。自监督学习的效果和数据量直接正相关。如果只有几十个小时的数据,预训练收益有限,可以退一步使用有监督训练。
第二,新用户接入时,先做模板适配。可穿戴设备跨个体差异很大,建议保存一个用户通用基线模型,再为每位用户做短时间校准。
第三,批量任务要加日志和失败重试。无论训练任务还是推理任务,都建议用一个任务队列管理,记录每条任务的输入、输出、耗时和错误信息。
第四,涉及个人生理数据,必须做隐私脱敏。传感器数据去除身份标识,数据库加密,访问权限分级,用户有权删除自己的数据。
第五,疲劳预测结果只作为辅助判断。如果用于工业场景,需要说明模型的不确定性,并设置人工复核流程。
第六,部署前做一次完整回归测试。包括不同用户、不同佩戴位置、不同活动强度下的性能表现,确保不会出现个别场景下的系统性失效。
13. 总结与下一步
自监督学习解决的是标签稀缺问题。多模态融合解决的是信号互补问题。两者结合在一起,能让智能可穿戴系统的数据利用率明显提升。
最值得先验证的是预训练加微调的流程。先采集一段无标签数据,用对比学习训练编码器,再用少量标注数据微调活动识别,对比一下有预训练和没有预训练的准确率差异。如果数据量和标签量都比较充分,再引入跨模态注意力融合,观察疲劳预测指标的提升幅度。
最容易踩的坑有三个:一是数据不对齐,不同传感器采样率不一致直接拼接,导致模型学到错误关联;二是增强策略不匹配,IMU 数据的时间扭曲尺度需要小心设置;三是疲劳标签质量差,标签不稳定会直接拉低模型上限。
下一步可以从三个方向延伸:引入更细粒度的健康状态分析,比如压力识别、睡眠分期;增加跨设备迁移能力,让模型适配不同品牌的可穿戴硬件;结合大模型的多模态处理能力,将传感器序列映射到语言空间,实现可解释的自然语言报告输出。