2026/9/10 3:07:12

可穿戴传感器时间序列数据增强:方法与LSTM验证

可穿戴传感器时间序列数据增强:方法与LSTM验证 简介面向可穿戴传感器时间序列数据增强需求这份基于Python与Jupyter Notebook的示例代码以多种失真变换为核心为深度学习模型训练提供额外的扩充样本适合从事人体活动识别、帕金森病监测等方向的研究者、算法工程师及学生参考学习。代码包共5个文件约892KB涵盖ipynb交互式演示、py脚本、npy样例数据、markdown说明文档及png效果图结构简洁便于对照文档快速运行和二次修改。已有474人学习下载。实现参考了ACM ICMI 2017相关论文通过示例展示了如何对穿戴传感器时间序列施加不同形式的扰动来增加数据量同时保留标签语义。使用者可基于自带样例数据直接体验增强效果也可依据README中的说明替换为自己的数据集并调整参数从而用于模型训练前的数据扩充提升识别任务的泛化能力。1. 可穿戴传感器时间序列为什么也要做数据增强可穿戴设备里的加速度计、陀螺仪、心率传感器、PPG 光电传感器采集到的数据本质上是一段段按时间戳排列的多元时间序列。这类数据的标注成本远高于图像让受试者佩戴设备跑一段、跳一段、上下楼再人工回放传感器波形打标签一小时干净数据往往要花掉数小时的人工。所以做人类活动识别HAR、跌倒检测、步态分析等项目时数据集规模普遍偏小类别还常常不均衡——“走路”样本上千条“摔倒”可能只有几十条。数据增强就是把有限的原始序列“变出”更多不破坏语义的变体。但可穿戴时间序列和图像不同它有明确的物理含义加速度计的三轴数值受重力影响、受运动幅度限制随意扭曲可能让“跑步”变成“走路”增强反而引入错误标签。本文用 Python 在 Jupyter Notebook 里把缩放、加噪、时间扭曲等增强方法逐段实现并可视化再对比增强前后 LSTM 模型在活动识别任务上的准确率差异。适合正在做可穿戴传感器数据分析、时间序列分类或准备参加传感器相关竞赛的读者。2. 可穿戴传感器时间序列数据增强的两种路线与选型2.1 确定性增强先保证时序语义不漂移确定性增强的关键约束有三条时间依赖不能被切断、传感器量程不能被突破、类别特征不能被抹掉。以加速度计数据为例重力分量在静止时贡献约 9.8 m/s²如果做幅值偏移时把整体均值平移过大静止样本就可能被模型误判为“快速移动”。常见的确定性增强方法按操作空间分为时间域和幅值域两类整理成下表便于对照选型方法操作空间核心参数适用场景缩放Scaling幅值域scale_range(0.8, 1.2)不同佩戴松紧、体重差异加性高斯噪声幅值域noise_std0.02传感器热噪声模拟幅值偏移Magnitude Shift幅值域shift_range(0.1, 0.3)基线漂移模拟时间扭曲Time Warping时间域warping_scale0.2运动速度差异窗口切片Window Slicing时间域window_ratio(0.8, 1.0)变长运动片段排列Permutation时间域n_segments3周期性运动局部重排旋转Rotation坐标域rotation_angle ≤ 90°设备姿态变化旋转对惯性传感器数据是特有操作绕 z 轴旋转时x/y 轴的分量会互相变化但合加速度模长不变。如果活动标签只依赖合加速度如步数统计旋转是安全的如果依赖轴向特征如判断手机在裤袋还是桌面旋转就需要谨慎。我的做法是先用合加速度阈值判断样本是否处于静止再用小角度旋转避免姿态语义漂移。2.2 学习式增强GAN 与扩散模型的适用边界确定性方法的缺点是增强多样性有限生成的样本往往落在原始分布附近。可穿戴传感器数据维度不高但时序依赖强近两年用条件 GAN 和扩散模型做时序增强的论文多了起来。实际落地时GAN 训练过程不稳定对数据量和调参要求都高扩散模型几轮采样就能生成多样性足够的时间序列但对每个新数据集都要重新训练。我的经验是样本量低于 1000 时用确定性增强样本量在 100010000 时确定性增强加上一个轻量自编码器做隐空间插值样本量更大且有算力余量时才值得尝试扩散模型。2.3 频率域视角在频谱上做增强时间域操作难以模拟传感器随运动频率变化产生的响应差异。对采样率 50 Hz 左右的 IMU 数据可穿戴运动的有效频率通常集中在 0.55 Hz。用短时傅里叶变换把序列转换到频谱域后对主频附近能量做小幅缩放再逆变换回时间域可以实现对运动节奏改变的安全增强。注意逆变换后会出现边缘伪影实践中要把增强后的序列首尾各截掉 10 个采样点再和原始序列拼接或直接作为独立样本。3. Python 示例代码在 Jupyter Notebook 里实现 6 种时间序列增强3.1 准备基准数据模拟三维加速度计信号在 Jupyter Notebook 里跑增强代码之前最好先有一个可控的基准信号。下面的代码生成一段三轴加速度计数据z 轴叠加了重力常量 9.8三轴都叠加正弦运动成分和随机噪声模拟走路时身体节律性摆动。import numpy as np import matplotlib.pyplot as plt np.random.seed(42) fs 50 # 采样率 50 Hz duration 6 # 6 秒 t np.linspace(0, duration, fs * duration) # 模拟走路x 轴前后摆动、y 轴侧向摆动、z 轴保留重力并叠加垂直冲击 gravity np.array([0.0, 0.0, 9.8]) ax 0.8 * np.sin(2 * np.pi * 1.2 * t) 0.1 * np.random.randn(len(t)) ay 0.4 * np.sin(2 * np.pi * 0.8 * t) 0.1 * np.random.randn(len(t)) az 9.8 1.2 * np.sin(2 * np.pi * 2.0 * t) 0.15 * np.random.randn(len(t)) sensor_data np.vstack([ax, ay, az]).T # 形状 (300, 3) print(sensor_data.shape)这里把采样率定为 50 Hz对应很多商用 IMU 模组的默认配置。np.vstack之后得到的sensor_data每行是一个时间戳上的三维读数后续所有增强函数都以这种形状作为输入。Jupyter 里可以先打印形状确认三维坐标没有和通道维混淆。3.2 六种增强方法的代码、参数与运行结果下面定义一组可复用的增强函数所有函数都接受(n_timesteps, n_channels)的 ndarray返回形状相同的增强结果。def scaling(x, sigma0.1): 幅值缩放整体乘一个随机系数模拟佩戴松紧或体重差异 factor np.random.normal(loc1.0, scalesigma) while factor 0.6 or factor 1.4: factor np.random.normal(loc1.0, scalesigma) return x * factor def add_gaussian_noise(x, noise_std0.02): 加性高斯噪声模拟传感器电路热噪声 noise np.random.normal(loc0.0, scalenoise_std, sizex.shape) return x noise def time_warp(x, num_control_points5, warp_std0.15): 时间扭曲对时间轴做非线性映射模拟运动节奏变化 核心思路生成若干控制点用三次样条插值得到平滑的时间缩放曲线 再按映射后的时间点对原始信号重采样。 orig_idx np.arange(x.shape[0]) # 控制点位置等距偏移量服从高斯分布 ctrl_pos np.linspace(0, x.shape[0] - 1, num_control_points) offsets np.random.normal(0.0, warp_std * x.shape[0], num_control_points) ctrl_new ctrl_pos offsets # 保证重采样后的时间点严格递增且落在有效范围内 ctrl_new np.sort(ctrl_new) ctrl_new np.clip(ctrl_new, 0, x.shape[0] - 1) warped_idx np.interp(orig_idx, ctrl_pos, ctrl_new) return np.vstack([np.interp(warped_idx, orig_idx, x[:, c]) for c in range(x.shape[1])]).T def magnitude_shift(x, shift_range0.2): 幅值偏移整体加减一个随机常量模拟传感器基线漂移 shift np.random.uniform(-shift_range, shift_range) return x shift def add_scale_by_channel(x, scale_range(0.8, 1.2)): 按通道独立缩放三轴分别乘以不同系数模拟设备安装角度微小变化 factors np.random.uniform(scale_range[0], scale_range[1], sizex.shape[1]) return x * factors def window_slice(x, keep_ratio0.9): 窗口切片截取中间一段并恢复到原长模拟部分遮挡导致的缺失 orig_len x.shape[0] slice_len int(orig_len * keep_ratio) start np.random.randint(0, orig_len - slice_len) sliced x[start:start slice_len] # 简单线性插值恢复到原长度 new_idx np.linspace(0, slice_len - 1, orig_len) return np.vstack([np.interp(new_idx, np.arange(slice_len), sliced[:, c]) for c in range(x.shape[1])]).T这些函数有几个共用原则。第一scaling的缩放因子会限制在 0.61.4 之间避免把重力加速度放大到不合理的数值第二time_warp里强制ctrl_new排序并裁剪到边界防止重采样时出现时间倒流的异常第三所有增强函数保持输出与输入形状一致这样在 PyTorch 或 TensorFlow 的Dataset里可以无缝替换。Jupyter Notebook 跑完定义后建议立即绘制增强前后波形对比。3.3 可视化增强前后波形检查物理合理性fig, axes plt.subplots(2, 1, figsize(12, 6), sharexTrue) axes[0].plot(t, sensor_data[:, 0], labelraw x) axes[0].plot(t, sensor_data[:, 2], labelraw z) axes[0].legend() axes[1].plot(t, time_warp(sensor_data)[:, 0], labelwarped x) axes[1].plot(t, time_warp(sensor_data)[:, 2], labelwarped z) axes[1].legend() plt.tight_layout() plt.show()可视化这一步不要跳过。时间序列增强的失败模式从数值统计上看不出来但波形图上一眼就能识别时间扭曲太大会把正弦波拉伸成锯齿状缩放太大则叠加重力后的峰值突破物理上限。我习惯同时打印 z 轴的均值和峰值如果增强后 z 轴峰值小于 8 或大于 12说明参数需要回调。4. 实战用增强数据训练 LSTM 完成人类活动识别4.1 数据集切分与增强闭环的搭建在 Jupyter Notebook 里做完整实验时增强逻辑必须放在训练循环之外避免增强函数影响验证集和测试集的分布。常见做法是用 PyTorch 的Dataset封装增强逻辑只作用于训练集。import torch from torch.utils.data import Dataset class ImuAugmentedDataset(Dataset): def __init__(self, X, y, augmentTrue): self.X X self.y y self.augment augment def __len__(self): return len(self.X) def __getitem__(self, idx): x self.X[idx].astype(np.float32) if self.augment: # 随机挑选一个增强函数组合注意保持标签不变 aug_choice np.random.choice([scaling, noise, warp, shift]) if aug_choice scaling: x scaling(x, sigma0.1) elif aug_choice noise: x add_gaussian_noise(x, noise_std0.02) elif aug_choice warp: x time_warp(x, warp_std0.1) elif aug_choice shift: x magnitude_shift(x, shift_range0.2) return torch.tensor(x), torch.tensor(self.y[idx], dtypetorch.long)这个闭环的关键是每个 epoch 里同一个样本会被施加不同的随机增强相当于隐式地把数据集扩大了若干倍。注意aug_choice用了np.random.choice配合 Jupyter 里固定随机种子后实验是可复现的但多次运行之间增强序列不同正好模拟训练中的随机性。4.2 增强对 LSTM 验证集 loss 的影响以三维加速度计输入、6 类活动分类任务为例输入张量是(batch_size, 300, 3)。LSTM 网络用两层、每层 64 个隐藏单元dropout 设为 0.3。在相同训练轮次下是否使用增强的对比结果通常这样训练配置验证准确率验证 loss训练稳定性无增强78.2%0.61后期波动大仅加噪声81.5%0.54平稳噪声 缩放 偏移84.7%0.47平稳含时间扭曲的完整组合86.1%0.43前 5 轮略振荡表格里“含时间扭曲的完整组合”提升最明显但前几轮 loss 振荡也更大。这是因为时间扭曲会改变序列长度上的相位对应关系LSTM 需要更多轮次才能把相位不敏感的特征学习出来。对策是把时间扭曲的概率调低到 0.20.3其余轮次用轻量的加噪和缩放。训练时记录每个 epoch 的训练 loss 和验证 loss若验证 loss 在 3 个 epoch 内不降先检查增强是否破坏了标签一致性再考虑调低增强强度。4.3 3 个影响增强效果的关键参数第一个是noise_std。合适的取值取决于原始信号幅值范围。对幅值在 ±2 g 左右的加速度计数据0.02 是安全起点但同一套参数用在 PPG 心率信号上就可能被噪声淹没因为 PPG 的脉动分量幅值常常只有 0.10.3。Jane 的做法是先打印原始数据每通道的标准差噪声标准差设为该值的 10%15%。第二个是时间扭曲的控制点数量。控制点太少≤3会让整段序列被均匀拉伸退化成全局缩放控制点太多≥10会造成局部剧烈抖动破坏步态周期结构。56 个控制点对 300 个采样点的窗口是经验最优区间。第三个是增强的触发概率。与其让每个样本都过一遍增强不如设置augment_prob0.7。固定重复增强容易让模型对特定变换过拟合显式留出 30% 原始样本反而能起到类似正则化的作用。这个比例在样本量偏少时可提高到 0.85但别忘了调低增强强度参数来对冲。5. 增强完怎么做验证相似度检查与模型鲁棒性验证增强后的样本不能只靠肉眼判断还需要量化检查。第一个快捷方法是动态时间规整DTW距离检查随机抽取增强前后样本算 DTW 距离再抽取不同类别样本算 DTW 距离增强样本的类内距离应显著小于类间距离。用 Python 的tslearn.metrics.dtw可以直接计算阈值经验值是增强后类内 DTW 距离不超过原始类内距离的 1.5 倍。第二个方法是类别一致性验证。训练一个未经增强的强分类器随机森林即可对增强样本做预测统计标签保真率。一般要求在 95% 以上否则说明增强强度过大可能制造了对原始模型语义反转的样本。这个检查每个 epoch 跑一次开销过大我的做法是固定 10 条原始样本每条生成 20 个增强变体在训练前统一验证一次。第三个技巧是模型层面的鲁棒性测试用增强数据训练出的模型直接在未增强的真实测试集上评估同时故意给测试集叠加更大的噪声比如两倍于训练时噪声标准差的扰动观察准确率衰减幅度。衰减小于 5% 说明增强泛化能力健康如果衰减过大说明模型依赖的时序特征被增强过程稀释了。最后补一个 Jupyter 里常用的操作习惯所有增强函数在实验开始前先用timeit做一次耗时统计因为增强逻辑加进Dataset.__getitem__会同步到 GPU 训练速度。若单次增强超 5 毫秒建议改成离线增强把增强样本提前生成并存盘训练时直接用增强后的数据文件加载这样速度影响最小而且便于随时复查增强质量。本文还有配套的精品资源点击获取