简介面向可穿戴传感器时间序列数据分析与深度学习实践者适用于活动识别、健康监测、帕金森病量化评估等时序建模场景这份Python示例代码演示了针对传感器数据的数据增强方法。方法源自ICMI 2017一篇帕金森病监测论文通过对原始时间序列施加多种失真变换在不改变标签的前提下扩充样本量有助于提升CNN等模型的泛化能力与训练稳定性。压缩包共5个文件其中包含Jupyter Notebook演示脚本、可直接运行的.py脚本文件、示例数据.npy、说明文档及效果预览图整体仅892KB轻量易上手。已有475人学习。下载后先阅读说明文档可了解各文件用途、参数设置与运行方式Notebook与脚本配套使用方便边看边跑、快速迁移到自己的传感器数据场景获得完整可复现示例与扩展思路。1. 可穿戴传感器数据时间序列数据的数据增强先把物理约束摆到代码前面从腕上设备导出一段三轴加速度计数据训练一个动作识别模型换一个人戴同一只手准确率掉十个点。这是可穿戴传感器数据时间序列数据应用里最常见的翻车现场样本量不小但个体差异、佩戴松紧、表盘朝向一变化模型立刻失灵。数据增强的思路不是简单地把样本“复制一份”而是用有物理约束的变换把真实存在的变化合成出来。围绕这类时间序列数据的增强方法一般在 Jupyter Notebook 里用 Python 写示例代码跑通覆盖加噪、缩放、平移、时间扭曲、旋转和通道置换。适合正在做活动识别、跌倒检测、步态分析、睡眠分期、手势识别又不满足于把数据直接塞进 LSTM 的工程师和研究者。2. 先想清楚再写代码可穿戴时序增强的选型原则2.1 合成增强与采样增强先分清你的数据缺什么可穿戴设备里最常见的信号不是图像而是加速度计、陀螺仪、心率 PPG、EMG/EEG 这类时间序列数据。加速度计输出的是设备坐标系下三轴的比力重力分量一直混在里面所以它对设备朝向极其敏感陀螺仪输出角速度对“转动”敏感PPG 对幅值和运动伪影敏感。不同传感器的采样率差异很大IMU 常见 50~200 HzPPG 一般是 20~100 HzEMG 往往上千 Hz。这决定了增强操作的最小粒度也决定了你在 Jupyter 里写增强函数时能不能用线性插值而不引入过多伪影。在动手写增强前我一般会把数据翻出来看三个东西采样率是多少、每类样本有多少个、类内方差大不大。然后按操作方式把增强分成两类。一类是合成增强就是对已有窗口做逐点变换向量的长度和类别标签保持不变典型操作是加噪、缩放、平移、旋转。另一类是采样增强在不改变信号物理定义的前提下从长序列里按不同起点切窗口或者对多通道做通道置换。合成增强适合类内样本少、类别边界明显的情况采样增强适合个体差异大、一个 session 里波形变化多的情况。增强操作适用信号是否改变标签主要风险高斯加噪IMU/PPG否噪声幅值过大真实特征被淹没幅度缩放IMU/PPG可能缩放过大走路变成跑步幅值平移IMU否平移量超出传感器偏置范围时间扭曲IMU/EMG否时间轴伸缩跨越动作边界绕重力轴旋转IMU含加速度计否绕错轴会污染重力信息通道置换多轴 IMU否轴间相关结构被破坏这个表里最需要警惕的是“是否改变标签”这一列。比如幅度缩放一个采样把 0.5 倍的走路信号放大到 1.8 倍模型很可能把它学成跑步。所以缩放不是随便调而是要根据加速度的量程和各类别幅值分布来限制。通道置换也一样加速度计三轴的物理含义完全不同z 轴常承担重力x/y 轴承载水平方向运动。如果把轴顺序打乱模型很容易收到“重力方向横着”这种不真实样本。通道置换更适合通道含义对称的信号或者是在模型已经对轴序过拟合时当作正则手段不能默认开。为什么不能把图像数据增强直接搬过来图像水平翻转通常不改变物体类别但时序信号没有“水平翻转”这一说。把一个加速度窗口上下翻转重力就会指向相反方向这在物理上等同于把传感器倒过来戴而倒过来戴产生的信号和原样本很可能属于不同动作。做这类几何变换前必须先确认变换后的样本在真实世界里可不可能出现。数据增强方法示例代码的编写顺序也应该是“先物理后代码”而不是把图像增强库套上就完事。2.2 增强参数不是玄学采样率、窗口长度和物理量纲增强强度怎么定是这里最容易变成拍脑袋的地方。我给参数时的顺序是先定窗口长度再定增强边界最后用可视化做校验。窗口长度由任务决定活动识别常用 2~5 秒50 Hz 采样下就是 100~250 个点步态分析常用一个步态周期大约 1~1.5 秒睡眠分期或心率分析窗口更长可能到 30 秒甚至 1 分钟。先在窗口内部做增强避免窗口边界把两个不同类别的动作拼进同一个样本。增强参数的上限一般来自物理约束。加速度计量程标的是 ±2g / ±4g / ±8g / ±16g缩放后的峰值不能超过这个量程。窗口内加速度的标准差如果只有 0.2g高斯噪声的 sigma 取 0.05g 是合理的如果取 0.5g就把真实波形完全盖住了。我的习惯是用信号标准差的 10%~30% 作为加噪强度的初值再根据训练曲线微调。这里没有什么“万能默认值”只能从信号自身出发。采样率还会影响插值类操作的质量。50 Hz 的数据每个采样点间隔 20 ms时间扭曲如果在一个窗口里生成过于密集的扭曲曲线插值结果会变得非常平坦相当于把原始细节全抹掉了。低频采样数据上时间扭曲的 sigma 我会控制在 0.1~0.2伸缩幅度不超过 20%高频 EMG 数据可以适当调大因为采样点密集插值仍有足够信息。对 100 Hz 以下的可穿戴数据我个人不建议做过于激进的时间伸缩否则波形会失真到不可解释。3. 用 Python 在 Jupyter 里落地 7 个基础增强变换加噪、缩放、平移、扭曲、置换3.1 先造一组可复现的三轴加速度数据为了方便复现我这里用了一个模拟的加速度计信号。真实数据可以从公开数据集拿但模拟信号更适合看清每个增强操作到底改了什么。下面的代码生成两分钟、50 Hz 的三轴数据重力分量放在 z 轴x/y 轴叠加正弦运动分量和高斯噪声import numpy as np fs 50 # 采样率 50Hz t np.arange(0, 120, 1 / fs) g 9.81 # 重力加速度常量 x 0.3 * np.sin(2 * np.pi * 0.5 * t) 0.1 * np.sin(2 * np.pi * 2.1 * t) np.random.normal(0, 0.05, t.shape) y 0.4 * np.sin(2 * np.pi * 0.3 * t 0.5) 0.2 * np.cos(2 * np.pi * 1.7 * t) np.random.normal(0, 0.05, t.shape) z g 0.7 * np.sin(2 * np.pi * 0.15 * t 1.2) 0.3 * np.sin(2 * np.pi * 0.8 * t) np.random.normal(0, 0.08, t.shape) imu np.stack([x, y, z], axis1) print(imu.shape) # (6000, 3)逻辑说明这样设置的好处是z 轴接近 9.81和真实静止状态一致x/y 轴上的正弦分量模拟步行时的手臂摆动。之后所有增强测试都基于这个 imu 数组长度为 6000每个点对应三轴数值。后面的每个增强函数输入形状均为(T, C)T 是窗口长度C 是通道数。参数说明fs 决定后续时间扭曲的插值精度g 只是为了让 z 轴有真实物理意义。真实数据里重力方向不一定落在 z 轴后面旋转增强那一章会专门讨论坐标系问题。3.2 五个可直接调用的增强函数下面给出加噪、缩放、平移三个最简单也最稳定的变换。这三个函数都是逐元素操作不改变窗口长度也不改变时间对齐关系适合放进任何训练管线里做在线增强。def add_noise(x, sigma0.05): 高斯噪声增强模拟传感器硬件噪声与佩戴震动 return x np.random.normal(0, sigma, x.shape) def scaling(x, factor_range(0.9, 1.1)): 幅度缩放增强模拟同一动作不同人的发力差异 factor np.random.uniform(*factor_range) return x * factor def shifting(x, shift_range(-0.2, 0.2)): 幅值平移增强模拟传感器直流偏置漂移 shift np.random.uniform(*shift_range) return x shift逻辑说明add_noise 在原始信号上叠加独立高斯噪声是数据增强里最基础的一招scaling 对整段信号乘一个随机系数保持波形时间结构不变但改变能量shifting 在整段信号上加一个常数偏移模拟温度、电池状态变化带来的偏置漂移。三者都不会改变窗口长度不会给模型引入维度错误。参数说明sigma 的单位是 m/s²推荐用信号标准差的 10%~30% 动态取值不要固定写 0.05。factor_range 的初值设 0.9~1.1 比较稳妥超过 1.2 就要小心标签边界。shift_range 通常不超过 0.2否则静置状态下 z 轴会明显偏离重力值模型可能把偏置当特征。接下来是三个会影响时间波形结构的增强time_warp、magnitude_warp、channel_shuffle。from scipy.interpolate import CubicSpline def time_warp(x, sigma0.2): 时间扭曲对时间轴做非线性伸缩保持窗口长度不变 T x.shape[0] ts np.cumsum(np.random.normal(0, sigma, T)) ts (ts - ts[0]) / (ts[-1] - ts[0]) * (T - 1) mapper CubicSpline(np.arange(T), x, axis0) return mapper(ts) def magnitude_warp(x, sigma0.1, knots4): 幅值扭曲在少数控制点上做幅度调制然后平滑 T x.shape[0] control np.random.normal(1, sigma, knots) warp CubicSpline(np.linspace(0, T - 1, knots), control)(np.arange(T)) return x * warp[:, None] def channel_shuffle(x): 通道置换随机打乱传感器通道谨慎使用 idx np.random.permutation(x.shape[1]) return x[:, idx]逻辑说明time_warp 通过累积随机偏移生成一条时间映射曲线再用立方插值把原信号映射到这条新时间轴上因此输出长度保持 T 不变但动作的节奏被拉伸或压缩。magnitude_warp 则在 4 个控制点上生成平滑幅度调制曲线乘到各通道上模拟佩戴松紧造成的幅值波动。channel_shuffle 把三轴通道顺序打乱用于通道顺序不应影响模型的任务。参数说明time_warp 的 sigma 控制累积偏移方差sigma 越大扭曲越剧烈0.2 是 50 Hz 数据的常用起点。magnitude_warp 的 knots 建议取 3~5太大会把调制曲线变成快速抖动失去物理意义。channel_shuffle 默认不加我在实际项目里只把它当作针对通道轴序过拟合的正则项测试集如果掉点就说明不该开。3.3 用 Jupyter 可视化验证增强前后的差异增强变换写完后第一件事不是直接训练而是把增强前后波形画出来对比。import matplotlib.pyplot as plt fig, axes plt.subplots(3, 1, figsize(12, 8)) orig imu[:200] # 取 4 秒窗口50Hz * 4 200 点 warped time_warp(orig) # 对同一窗口做时间扭曲 axes[0].plot(orig[:, 0], labeloriginal x) axes[1].plot(warped[:, 0], labeltime_warp x) axes[2].plot(orig[:, 2], labeloriginal z) axes[2].plot(warped[:, 2], labeltime_warp z) for ax in axes: ax.legend() ax.xaxis.set_major_locator(plt.MaxNLocator(8)) plt.tight_layout() plt.show()逻辑说明拿同一段 4 秒窗口分别经过原始与 time_warp 增强再绘制 x 轴和 z 轴两个通道可以直接观察时间扭曲是否把节奏拉变形。如果看不到明显差别说明 sigma 太小如果形状完全错乱说明 sigma 太大。这一步验证是后面所有工作的前提。参数说明200 个点对应 4 秒窗口MaxNLocator(8) 把横轴刻度限制到 8 个左右防止横坐标太密集导致标签糊成一片。4. 绕重力轴的旋转增强可穿戴 IMU 最实用的几何变换4.1 为什么旋转增强必须绕重力轴可穿戴设备佩戴时的真实误差来源很大程度上是“绕着佩戴位置转了个角度”手表在手腕上滑了半圈、手机在口袋里歪了 30 度。这些变化在传感器坐标系里体现为绕重力方向旋转了一个偏航角。因此把原始窗口绕着重力方向旋转一定角度是有明确物理依据的增强方式。它不改变重力在传感器坐标系下与运动分量的相对关系生成的新样本在该动作类别的真实分布之内。反过来想象一下绕水平轴旋转会发生什么原本指向地面方向的重力分量被转到水平方向样本变成了“设备横躺着完成动作”的形态。这个形态在大部分可穿戴场景里并不真实模型学到的将是“重力方向等于某个值才能分类”的错误关联。所以旋转增强最大的坑不在代码而在物理轴的选择。这也解释了为什么不能把图像里的旋转增强照搬过来。图像旋转 5 度基本不改变物体语义但加速度计的坐标轴旋转 90 度重力方向就换了一根轴。绕重力轴旋转时 z 轴重力分量不变x/y 轴混合变化才是真实可能的佩戴姿态差异。较早的可穿戴开源增强库比如某些公开的 IMU 增强实现核心也就是这个绕重力方向转的变换可见它在实际项目里的地位。4.2 用旋转矩阵把单个窗口做绕轴旋转先写基于旋转矩阵的最简版本假设设备坐标系里重力落在 z 轴这对应手表平放于桌面、屏幕朝上的常见情形def random_rotate_around_z(x, max_angle_deg30): 绕 z 轴假设为重力轴做随机旋转增强输入形状 (T, 3) theta np.deg2rad(np.random.uniform(-max_angle_deg, max_angle_deg)) c, s np.cos(theta), np.sin(theta) R np.array([[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]]) return x R.T rotated random_rotate_around_z(imu[:200], max_angle_deg30)逻辑说明R 是绕 z 轴的旋转矩阵x R.T 表示对每个时间点的三轴向量做旋转。旋转后 z 轴分量不发生变化这正是绕重力轴旋转的关键x/y 轴按角度混合模拟表盘在手腕上转动。使用示例里取 4 秒窗口旋转 30 度后波形整体仍保持原有能量分布只有轴分量发生混合。参数说明max_angle_deg 通常取 20~45 度过大会旋转出远超佩戴极限的姿态。random.uniform 在对称区间采样保证旋转方向对称。如果设备坐标系不是 z 轴朝上作为替代我一般用四元数绕任意重力方向旋转。考虑到真实设备重力方向不确定我推荐用 scipy 的 Rotation 配合预先标定的重力方向实现通用版本from scipy.spatial.transform import Rotation def rotate_around_gravity(x, gravity_vecnp.array([0.0, 0.0, 1.0]), max_angle_deg30): 绕任意重力方向旋转重力方向需在设备坐标系下标定 theta_deg np.random.uniform(-max_angle_deg, max_angle_deg) g gravity_vec / np.linalg.norm(gravity_vec) r Rotation.from_rotvec(g * np.deg2rad(theta_deg)) return r.apply(x)逻辑说明gravity_vec 是设备坐标系下静止时加速度的归一化方向可从每段数据前几百毫秒静止区的均值估算。Rotation.from_rotvec 用该向量作为旋转轴生成的旋转矩阵绕任意轴作用到样本上不再受“z 轴必须是重力方向”的限制。这个版本对真实数据更可靠因为它把坐标系先校准了。参数说明对运动段数据gravity_vec 取整段均值更稳对姿态变化快的动作可以把窗口切短分别估算各窗口内重力方向再用对应旋转矩阵做旋转。不同手机、手表厂家的坐标系定义不一致先做这一步标定是血泪教训换来的。4.3 旋转增强的正确性校验重力方向是底线旋转增强做好后我会额外做一个校验把增强前后的样本分别沿重力方向投影数值应保持一致再对增强后样本的 z 轴统计均值和方差旋转前后如果 z 轴变化明显说明重力轴估算错了。g_orig np.mean(imu[:6000], axis0) g_rot np.mean(rotated, axis0) print(np.abs(g_orig - g_rot)) # 检查重力投影是否变化逻辑说明真实重力方向在静止段上的投影旋转前后应保持稳定。打印两个均值向量的逐轴绝对差如果某个分量差异超过 0.1g说明旋转轴和真实重力轴不一致。对旋转增强这种几何变换这一步能省下大量排查时间。参数说明0.1g 这个阈值不是绝对的量程 ±2g 的传感器可以收紧到 0.05g±16g 量程下可以适当放宽。校验代码不进入训练管线放在 Jupyter 里观察一次即可。5. 把增强接进训练管线5 条避坑记录与排查清单5.1 在线增强 vs 离线增强增强应该放在管线的哪一层离线增强的做法是预先对全部数据生成增强副本优点是每个 epoch 都用相同样本、可复现、调试方便但代价是数据存储和内存占用翻好几倍。我通常的做法是把增强写进 Dataset 的__getitem__里做在线增强每个 epoch 每个样本被取到时随机做一次变换等于每个 epoch 都看到不同版本存储只保留原始数据。对训练集与验证集增强必须只作用于训练集验证集保留真实分布作为评估基线。在线增强的最小实现import torch from torch.utils.data import Dataset class IMUDataset(Dataset): def __init__(self, X, y, trainTrue): self.X X # (N, T, C) self.y y # (N,) self.train train def __len__(self): return len(self.X) def __getitem__(self, idx): x self.X[idx].copy() if self.train: x add_noise(x, sigma0.05) x scaling(x, factor_range(0.95, 1.05)) if np.random.rand() 0.5: x time_warp(x, sigma0.15) return torch.from_numpy(x.astype(np.float32)), torch.tensor(self.y[idx])逻辑说明__getitem__的训练分支依次调用加噪、缩放、随机时间扭曲每个 epoch 取同一 idx 时因 np.random 随机过程生成的是不同增强版本。验证分支不调用增强数据泄漏被拦在源头。真实的可穿戴传感器数据往往长度不齐Dataset 里可以先按索引裁出固定窗口再做增强。参数说明sigma0.05 来自第 3 章的经验初值time_warp 概率设 0.5 避免每个样本都被过度扭曲factor_range 0.95~1.05 在标签安全范围内。可以在训练后对验证集分数做显著性检验看增强是否带来收益。5.2 五条避坑记录坑 1训练集和验证集都用了增强评估分数虚高现象训练曲线正常验证集准确率高达 98%换真实场景数据后准确率掉到 80%。原因验证集也经过增强相当于对同一个分布做了插值模型在增强分布上过拟合真实分布上泛化不了。解决增强只写在 trainTrue 分支验证/测试集一律走原始数据。坑 2时间扭曲后窗口长度变了现象进入模型前维度报错或某个 batch 的样本和标签错位。原因部分实现使用 np.random.permutation 或者插值后没有固定输出长度我的 time_warp 用累积偏移归一化保证长度 T但直接对整段长序列操作时尾部被裁剪后就会错位。解决只对定长窗口做时间扭曲窗口长度固定为 T若在大序列上做时间扭曲重采样后必须 resize 成指定长度。坑 3channel_shuffle 导致重力轴被换掉模型掉点现象加上 channel_shuffle 后训练 loss 正常但验证集掉 3~5 个点。原因加速度计三轴物理含义不同打乱后“重力方向”随机指向任意轴模型被逼着忽视轴语义反而学坏了。解决去掉该增强或仅在对轴序不敏感的信号上使用若一定要用保持已知通道顺序只在对称通道间交换。坑 4旋转增强绕错了轴生成了一堆“设备横躺着”的样本现象增强样本的 z 轴均值明显偏离重力值训练曲线抖动分类边界混乱。原因默认绕 z 轴但真实设备重力方向不在 z 轴或静止标定没做。解决先用静止段估算 gravity_vec再用四元数绕该向量旋转旋转后在样本上打印重力投影差值做校验。这个校验就是我上一章讲的那个 0.1g 的判断逻辑。坑 5Jupyter 里一次性增强全量数据内核直接崩掉现象代码执行到一半 Notebook 内核重启所有中间变量丢失甚至出现 password or token 失效的提示。原因先全量生成增强副本再喂给模型数据驻留内存且多进程下没有回收内核和 token 只是 Jupyter 会话管理的表象根因是内存被撑爆。解决改用在线增强若必须离线生成用分批生成、及时将临时变量 del 并调 gc.collect()在 VS Code 里接入 Jupyter 时也一样先排查内存占用再考虑环境问题。这里顺带提一个工程习惯Jupyter notebook 默认保存路径如果不在项目目录里生成的 npy 增强文件散落各处后面很难复现。我一般会把数据持久化路径固定到项目根目录下的 data/augmented 文件夹每次生成都带时间戳跑完训练后还能回去看当时用了哪些增强参数。6. 增强效果怎么验证三个指标决定该不该加大强度6.1 波形与频谱合理性检查增强做得对不对第一眼靠可视化。拿增强前后样本画时域波形和 FFT 频谱增强样本的频谱能量应与原始样本在同一量级不能出现原始信号没有的高能量尖峰。这一步在 Jupyter 里最方便失败时回到参数表调小 sigma 或旋转角度。6.2 留一受试者评估量化增强的泛化收益更可靠的增益验证是留一受试者Leave-One-Subject-Out评估。把其中一个受试者的数据整体作为验证集其余数据做训练记录增强前后的验证集准确率差。我实际项目里的常见结果不加增强时跨受试者掉 8~10 个点加上加噪、缩放、旋转后能收回 3~5 个点如果增强后准确率反而下降了说明参数超出物理边界往回退到初值的 70%。from sklearn.metrics import accuracy_score acc_raw accuracy_score(val_true, val_pred_raw) acc_aug accuracy_score(val_true, val_pred_aug) print(fraw{acc_raw:.3f} aug{acc_aug:.3f} gain{acc_aug - acc_raw:.3f})逻辑说明比较两次运行的验证集准确率只看 gain 为正且幅度大于 2 个百分点的方案。这 2 个点不是绝对阈值数据量大时应该配合统计显著性检验但作为日常判断已经够用。6.3 三类任务分别看增强重点活动识别重点看旋转、缩放、时间扭曲步态分析看时间扭曲和加噪睡眠分期更看重通道不变性和幅值稳定性缩放不宜过大。这个对应关系来自信号本身的物理属性动作类别对姿态敏感所以旋转增益大步态节奏对时间轴敏感所以时间扭曲增益大睡眠中幅值本身不稳定所以偏移类增强容易伤特征。我早期做活动识别时把增强强度调得很高训练曲线一路往下走以为效果好结果留一受试者验证里反而掉点。后来才学会把增强参数当作物理约束而非自由参数每次加大强度前先跑一次 LOSO。先做深度的原型验证再逐步上强度是对的希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?