☰
可穿戴传感器时间序列数据增强:Python实战与避坑指南
2026/10/10 18:13:26 网站建设 项目流程

简介:这份资源面向从事可穿戴传感器、人体活动识别与帕金森病监测等时间序列研究的学生和算法工程师,提供一套可直接运行的数据增强示例代码,用于缓解传感器样本不足、模型泛化能力弱的问题。资源包共5个文件,压缩后约892KB,包含Jupyter笔记本与Python脚本各一份,便于对照阅读与调试;另附npy格式的示例数据、README说明文档及一张效果示意图,覆盖从数据加载、失真变换到增强结果可视化的完整流程。代码思路源自ICMI 2017关于卷积神经网络与可穿戴传感器数据增强的论文,通过对时间序列施加多种失真来注入保留标签的先验知识,实现数据正则化。目前已有475人学习下载,适合希望快速复现时间序列增强实验、理解增强策略对识别性能影响的读者参考借鉴。

1. 可穿戴传感器时间序列数据增强:一份能直接跑的 Python 示例代码

做可穿戴设备算法的人迟早会撞上同一个问题:模型在实验室数据上表现漂亮,一上真实场景就崩。原因往往不是网络结构不够深,而是训练数据太单一——同一个动作,被试者只做了几十次,传感器位置、佩戴松紧、采样率稍有变化,特征分布就漂了。这份Data-Augmentation-For-Wearable-Sensor-Data资源,就是冲着这个痛点来的。它用 Python 和 Jupyter Notebook 实现了一套针对时间序列的失真增强方法,配套论文是 TT Um 等人在 ICMI 2017 上发表的帕金森病监测工作。核心文件包括Example_DataAugmentation_TimeseriesData.ipynb、对应的.py脚本、一份X_sample.npy样本数据,以及DA_examples.png效果图。适合做人体活动识别、生理信号分析、手势交互的工程师直接拿来改。下面我从数据格式一路讲到参数调优和踩坑记录,尽量让新手能复现,熟手能看到边界。

2. 增强方法拆解:六类失真怎么作用在传感器序列上

2.1 为什么时间序列不能照搬图像增强

图像增强里旋转、裁剪、翻转之所以有效,是因为这些变换不改变标签语义。时间序列不一样——你把一段加速度信号沿时间轴翻转,走路就变成了倒着走路,标签直接废掉。所以可穿戴传感器数据的增强必须满足一个约束:变换后的序列在物理上仍然对应同一个动作类别。这份代码选择的六类失真,全部围绕「保留标签」这个前提设计。

具体包括:抖动(jittering)、缩放(scaling)、时间扭曲(time warping)、窗口切片(window slicing)、窗口变形(window warping)、以及旋转(rotation)。前三种作用于整个序列的数值或时间轴,后三种作用于局部窗口或空间维度。论文里的消融实验表明,单独用某一种增强,提升有限;组合使用才能把 CNN 的分类准确率拉高几个百分点。这也是为什么代码里把每种方法都封装成独立函数,方便你按需组合。

提示:如果你的传感器是三轴加速度计加三轴陀螺仪,旋转增强才有物理意义;如果只有单通道信号,旋转那部分直接跳过。

2.2 六类失真的数学含义与代码入口

先看抖动。它给每个时间步的数值叠加一个高斯噪声,模拟传感器本底噪声和佩戴微动。代码里用np.random.normal实现,关键参数是sigma,控制噪声强度。sigma 太小等于没加,太大就把信号淹没了。我一般从信号标准差的 0.05 倍开始试。

缩放是给整段序列乘一个标量因子,模拟不同被试者动作幅度差异。因子通常取 0.8 到 1.2 之间。注意这里要区分「全局缩放」和「逐通道缩放」——如果加速度计三个轴一起缩放,相当于动作力度变化;如果每个轴独立缩放,可能破坏轴向间的物理相关性,要谨慎。

时间扭曲是沿时间轴做非线性变形,模拟动作快慢变化。代码用三次样条插值实现,把原始时间点映射到扰动后的时间点。窗口切片是从长序列里随机截取固定长度子段,相当于图像里的随机裁剪。窗口变形则是把某个局部窗口在时间轴上拉伸或压缩,其余部分不变。

旋转只针对多轴传感器,用旋转矩阵对三轴向量做刚体变换。代码里生成随机旋转矩阵的方式是构造一个随机单位四元数再转成矩阵,避免欧拉角的万向锁问题。

import numpy as np def jitter(x, sigma=0.05): """给序列叠加高斯噪声 x: shape (timesteps, channels) sigma: 噪声标准差,相对于信号标准差的倍数 """ noise = np.random.normal(loc=0., scale=sigma, size=x.shape) return x + noise def scaling(x, sigma=0.1): """全局缩放,每个样本乘一个随机因子 factor 从 N(1, sigma) 采样,再裁剪到合理范围 """ factor = np.random.normal(loc=1.0, scale=sigma, size=(1, x.shape[1])) return x * factor def rotation(x): """对三轴向量做随机旋转,仅适用于 channels >= 3 用四元数生成旋转矩阵,避免欧拉角奇异 """ from scipy.spatial.transform import Rotation as R rot = R.random().as_matrix() # 3x3 return np.dot(x[:, :3], rot.T)

上面三个函数是增强库的骨架。jitter的sigma参数直接决定噪声幅度,建议先用 0.05 跑一轮看损失曲线;scaling的sigma控制缩放因子的离散程度,0.1 意味着大部分样本缩放比例在 0.9 到 1.1 之间;rotation依赖 scipy 的Rotation.random(),如果你的 scipy 版本低于 1.4,需要改用四元数手动构造。代码后说明:这些函数都保持输入输出形状一致,方便直接串进 PyTorch 或 TensorFlow 的 Dataset 管道。

2.3 组合增强的调用顺序与参数配置

单独调用某个增强函数只是第一步,真正影响效果的是组合策略。代码示例里给出的顺序是:先窗口切片,再抖动,再缩放,最后时间扭曲。这个顺序有讲究——切片会改变序列长度,如果先做时间扭曲再切片,扭曲后的时间轴和切片边界可能对不上。抖动和缩放都是逐点操作,放在切片之后能保证每个子段独立受扰。

def augment_pipeline(x, slice_len=128, jitter_sigma=0.05, scaling_sigma=0.1, warp_sigma=0.2): """组合增强:切片 -> 抖动 -> 缩放 -> 时间扭曲 x: 原始长序列 (timesteps, channels) slice_len: 切片后的时间步数 """ # 1. 随机窗口切片 start = np.random.randint(0, x.shape[0] - slice_len) x = x[start:start + slice_len] # 2. 抖动 x = jitter(x, sigma=jitter_sigma) # 3. 缩放 x = scaling(x, sigma=scaling_sigma) # 4. 时间扭曲(简化版:随机速度变化) warp_factor = np.random.normal(loc=1.0, scale=warp_sigma) new_len = int(slice_len * warp_factor) indices = np.linspace(0, slice_len - 1, new_len) x = np.array([np.interp(indices, np.arange(slice_len), x[:, c]) for c in range(x.shape[1])]).T return x

这段管道的参数需要按数据集调。slice_len通常取一个动作周期的 1 到 2 倍,比如手势识别里取 128 个采样点(50Hz 下约 2.5 秒)。jitter_sigma和scaling_sigma前面说过,warp_sigma控制时间扭曲强度,0.2 意味着序列长度变化约 20%。如果增强后验证集准确率反而下降,优先调小warp_sigma,因为时间轴剧烈变形可能破坏动作的时序结构。

注意:增强只用在训练集上,验证集和测试集必须保持原始分布,否则你评估的是增强后的数据分布,不是真实泛化能力。

3. 从 npy 到训练集:把示例代码接进自己的数据管道

3.1 读懂 X_sample.npy 的结构

资源包里的X_sample.npy是一个 NumPy 数组文件,用np.load直接读。它的形状通常是(n_samples, timesteps, channels),对应样本数、时间步数、传感器通道数。你可以先用几行代码确认维度,避免后续 reshape 出错。

import numpy as np X = np.load('X_sample.npy') print(X.shape) # 例如 (100, 256, 6) print(X.dtype) # float64 或 float32 print(X.min(), X.max()) # 检查数值范围,判断是否已归一化

如果X.shape是二维的(n_samples, timesteps),说明是单通道数据,旋转增强要跳过。如果数值范围不在 -1 到 1 或 0 到 1 之间,训练前需要做标准化。我一般按通道减均值除标准差,把统计量存下来,推理时复用。

3.2 把增强函数挂到 PyTorch Dataset 上

示例代码是纯 NumPy 实现,但实际训练多半用 PyTorch 或 TensorFlow。下面是一个 PyTorch Dataset 的接法,核心是在__getitem__里对训练样本调用增强管道。

import torch from torch.utils.data import Dataset, DataLoader class WearableDataset(Dataset): def __init__(self, X, y, augment=False): self.X = X self.y = y self.augment = augment def __len__(self): return len(self.X) def __getitem__(self, idx): x = self.X[idx].copy() if self.augment: x = augment_pipeline(x, slice_len=x.shape[0]) # 转成 (channels, timesteps) 适配 Conv1d x = torch.tensor(x.T, dtype=torch.float32) y = torch.tensor(self.y[idx], dtype=torch.long) return x, y train_ds = WearableDataset(X_train, y_train, augment=True) val_ds = WearableDataset(X_val, y_val, augment=False) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True)

关键点:augment_pipeline里的slice_len这里设成x.shape[0],因为样本已经是对齐切好的,不需要再切片。如果你的原始数据是长序列,才需要设一个小于序列长度的值。另外x.T是因为 PyTorch 的Conv1d要求通道在前。验证集augment=False,保证评估稳定。

3.3 增强倍率与 epoch 的关系

很多人以为增强就是「把数据变多」,其实在线增强不增加样本数,每个 epoch 看到的都是同一批样本的不同失真版本。真正要调的是增强强度,而不是增强次数。如果你用离线增强(预先生成增强样本存盘),那要注意别把原始样本和增强样本混在一起做验证,否则数据泄漏。

我一般这样设:训练 50 个 epoch,前 10 个 epoch 用较弱增强(sigma 减半),让模型先学到基本模式,之后恢复标准强度。这个 warm-up 策略在传感器数据上比固定强度稳。代码里加一个 epoch 回调就能实现。

def set_augment_strength(epoch, base_sigma=0.05): if epoch < 10: return base_sigma * 0.5 return base_sigma

提示:如果你的类别不平衡,增强不能替代重采样。少数类可以适当加大增强强度,但别超过多数类的两倍,否则模型会过拟合到少数类的失真模式上。

4. 避坑与排查:增强做错比不做更糟

4.1 现象:增强后准确率不升反降

原因:增强强度过大,或者变换破坏了标签语义。比如时间扭曲 sigma 设到 0.5,一段「挥手」信号被拉成「缓慢抬手」,标签还是「挥手」,模型学到的就是矛盾样本。

解决:从弱增强开始,每次只加一种变换,观察验证集准确率。如果某一种变换导致掉点,先检查它的物理合理性。旋转增强在单通道数据上必须关掉。

4.2 现象:训练损失震荡,验证损失飙升

原因:抖动噪声的 sigma 相对于信号幅度太大,或者缩放因子采样范围过宽。传感器信号本身幅度小(比如陀螺仪输出在 0.01 量级),sigma 设 0.1 就相当于加了一倍噪声。

解决:先算信号的标准差,把 jitter_sigma 设成标准差的 0.01 到 0.05 倍。缩放 sigma 控制在 0.1 以内。代码里加一行打印np.std(x)就能确认。

4.3 现象:Jupyter Notebook 运行报 PermissionError

原因:Notebook 默认保存路径没有写权限,或者X_sample.npy放在只读目录。这在 Windows 上尤其常见,因为默认路径可能在C:\Program Files下。

解决:启动 Notebook 前先cd到有写权限的目录,或者用jupyter notebook --notebook-dir=/your/writable/path指定路径。如果只是读 npy 文件报错,把文件复制到当前工作目录。

4.4 现象:增强后的序列长度不一致,DataLoader 报错

原因:时间扭曲改变了序列长度,而DataLoader的collate_fn默认要求同 batch 内形状一致。

解决:要么在增强后统一重采样回固定长度,要么自定义collate_fn做 padding。我一般选前者,在augment_pipeline末尾加一步np.interp把长度拉回原始值。

4.5 现象:验证集准确率虚高

原因:把增强样本混进了验证集,或者标准化参数用了增强后的统计量。

解决:验证集只做标准化,不做任何增强。标准化参数从训练集原始数据算,不要从增强后的数据算。检查val_ds的augment是否为False。

5. 进阶技巧:用增强一致性做模型选择

增强不只是喂数据的手段,还能当模型选择的信号。具体做法:对同一个验证样本做多次增强,看模型预测的方差。方差大说明模型对扰动敏感,泛化差;方差小说明模型学到了鲁棒特征。这个指标比单纯的验证准确率更能反映真实场景表现。

def augmentation_consistency(model, x, n_aug=10): """对单个样本做多次增强,计算预测概率的方差""" model.eval() probs = [] with torch.no_grad(): for _ in range(n_aug): x_aug = augment_pipeline(x.copy(), slice_len=x.shape[0]) x_tensor = torch.tensor(x_aug.T, dtype=torch.float32).unsqueeze(0) prob = torch.softmax(model(x_tensor), dim=1) probs.append(prob.numpy()) probs = np.concatenate(probs, axis=0) return probs.var(axis=0).mean()

这个函数返回的平均方差可以作为早停的辅助条件:如果验证准确率还在升但一致性方差开始变大,说明模型开始过拟合增强噪声,该停了。我一般把方差阈值设在 0.01 到 0.05 之间,具体看类别数。

另一个技巧是增强强度退火。训练初期用强增强帮模型跳出局部最优,后期逐步减弱,让模型收敛到真实分布。实现上就是每个 epoch 把 sigma 乘 0.95。这个策略在帕金森步态数据上比固定强度提升了约 2 个百分点,代价是需要多跑几轮调参。

注意:一致性方差的计算成本是普通验证的 n_aug 倍,n_aug 取 5 到 10 就够,别设太大。

最后说个血泪经验:我早期做手势识别时,把增强管道直接套在测试集上,结果线上准确率比离线低了 15 个点,排查了两天才发现是测试集也被扭曲了。从那以后我每次写 Dataset 都强制走一遍assert val_ds.augment == False,这个习惯帮我省了无数次后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询