
简介针对可穿戴传感器时间序列数据的数据增强需求提供一套完整的 Jupyter Notebook 示例代码面向从事移动健康、帕金森病监测等方向的研究者与算法工程师。方法参考 ICMI 2017 论文通过对原始信号施加多种失真变换生成更多训练样本可提升卷积神经网络等模型的效果并降低过拟合风险。包体为 ZIP 压缩格式共 5 个文件约 892KB包含可直接运行的 ipynb 笔记本、配套 py 脚本、示例数据 npy、说明文档 md 及效果图 png方便按演示—运行—理解流程学习。已有 475 人学习下载。下载后可获得带注释的增强代码、真实传感器样本与 README 使用说明。资源结构简洁适合快速迁移到自己的时间序列项目中也可作为数据扩充预处理的参考起点。1. 可穿戴传感器时间序列数据增强跑通一套Python增强代码前先想清楚的事做可穿戴跌倒检测那阵子我最大的瓶颈不是模型选型而是数据不够用。一块IMU挂在手腕上一个动作类别攒了一千多条有效样本另一个类别只有两百多条直接拿去训练时间序列分类器过拟合几乎是必然的。可穿戴传感器数据本质上就是时间序列数据数据增强要做的事是在不破坏动作语义的前提下用Python生成更多形状合理、标签可靠的样本让模型在小样本和不平衡场景下也能泛化。这套方法在Jupyter里跑通很容易但跑通和有效之间隔着不少参数和边界。这篇文章就是从切窗、选变换、写代码到验证收益的完整路径适合手里握着加速度计、陀螺仪或心率数据的开发者想用数据增强把模型效果和稳定性顶上去而不是只在训练日志里多看一个过拟合的数字。2. 增强策略怎么选可穿戴时序的四个现实约束与前置处理图像增强可以对整张图做翻转、裁剪、色彩扰动因为语义在像素层面是冗余的可穿戴时间序列不行。加速度计、陀螺仪和心率的每一个采样点背后都有物理含义增强做过头会直接改掉动作类别。这一章先把四个影响成败的现实约束讲清楚再给你一张选型表。很多人在Jupyter里复制别人的增强代码跑出奇怪结果通常不是代码本身错而是没想清楚这些前提。2.1 样本长度怎么定窗口、重叠率与采样率可穿戴设备吐出来的是连续流增强的第一步是把流切成样本。常见做法是固定窗口加滑动重叠先确定窗口长度再按步长滑动切窗。窗口长度由动作周期决定日常活动识别里的单个动作比如站立、坐下、走路、挥手周期大概在 0.5 到 3 秒之间所以 2 到 5 秒的窗口是大多数项目的起步值。如果采样率是 50Hz2 秒窗口就是 100 个采样点如果是 125Hz就是 250 个点。窗口太短会把动作切碎增强后学不到上下文窗口太长又会把两个相邻动作混进同一个标签增强做得越花哨标签越不可信。切窗的代码在 Jupyter 里就是个循环import numpy as np def sliding_window(x, window_size, step_size): 把一维或多通道的连续信号切成固定长度窗口。 x: (n_samples, n_channels) 的原始传感器流 window_size: 每个窗口的采样点数 step_size: 滑动步长控制重叠率 n_samples, n_channels x.shape windows [] for start in range(0, n_samples - window_size 1, step_size): window x[start:start window_size] windows.append(window) return np.stack(windows) # (n_windows, window_size, n_channels)步长和窗口大小共同决定重叠率。步长等于窗口大小时样本完全不重叠步长取窗口的一半重叠率就是 50%。重叠率高会显著放大样本量但相邻样本高度相似训练集和验证集如果按时间顺序切很容易出现泄漏。我给可穿戴项目切窗时通常先设 50% 重叠拿一版基线再视类别不平衡程度往下调。注意这里切的是“训练集和测试集各自内部的流”等做好增强后再统一划分组别是为了避免同一段物理信号同时出现在训练和验证两侧。2.2 离线增强还是在线增强什么时候做、怎么做增强有两种落地方式。离线增强是在预处理阶段把所有变换一次性跑完把生成的样本存成 numpy 数组或 npz 文件之后再训练在线增强是在训练循环里对每个 batch 做随机变换每次 epoch 看到的样本都略不一样。可穿戴时序数据集的规模通常不大百万级样本已经很夸张离线增强的内存压力完全可控。我在 Jupyter 里探索阶段一律先用离线增强因为它能让你把增强前后的样本画出来看能检查标签有没有跟着变能比较分布差异这些事在训练脚本里做起来很别扭。在线增强的优势是省内存、天然防过拟合但它要求增强函数是纯随机且可复用的。如果你在 Jupyter 里先离线把参数试好再搬到 PyTorch 的 Dataset 或 TensorFlow 的 tf.data 里重写一遍两种优势都能拿到。要注意的是无论哪种方式增强只应该作用在训练集上验证集和测试集必须保持原始数据否则你评估的“增强带来的提升”很可能是数据泄漏带来的幻觉。2.3 通道、轴与重力分量增强前的传感器先验处理可穿戴数据的典型形状是 (样本数, 时间步数, 通道数)最常见的是加速度计三轴加陀螺仪三轴一共六个通道。增强时有一个很容易被忽略的前提三个加速度轴不是独立无关的通道它们之间有重力分量和方向语义。如果直接对加速度计的 XYZ 轴做随机大幅旋转动作的物理含义就变了跌倒检测里“面朝下”和“侧倒”可能被旋转成同一个样本标签已经不可靠。常见的处理是先去掉重力分量再做增强尤其对加速度计数据。用一个高通滤波器把静息重力去掉是加速度计预处理里的常规操作from scipy import signal def remove_gravity(x, fs50.0, cutoff_hz0.5, axis2): 可穿戴加速度计去重力。 x: (n_samples, n_channels) 或 (n_windows, n_timesteps, n_channels) fs: 采样率单位 Hz cutoff_hz: 高通截止频率通常 0.5Hz 以下 axis: 对哪个轴做滤波三轴数据默认时间是第 1 维 b, a signal.butter(2, cutoff_hz / (fs / 2), btypehigh) return signal.filtfilt(b, a, x, axisaxis)截止频率取 0.5Hz 是因为静态重力是直流附近的分量动作信号的频率通常更高。做完这一步再对样本做缩放、加噪或时间扭曲语义风险会小得多。如果你手里的数据已经做过归一化不知道原始单位建议先画几段原始波形确认重力分量是否存在再决定要不要做这步。设备采样率不一致的情况也常遇到两台手表一台 50Hz 一台 100Hz 拼在一起增强前先统一重采样到低频那台的采样率避免窗口长度对不上。2.4 常见增强方法的分层选型表把可穿戴时序上常用的方法整理成一张表选型能省很多时间。核心原则是样本少先用轻量时域变换类别不平衡严重就叠加采样策略频域方法专门对付模型对相位和周期性不鲁棒的情况生成式方法放在最后数据量不够时很容易翻车。方法操作对象关键参数主要风险白噪声原始信号加噪noise_level0.02~0.05g信噪比下降特征被淹没幅值缩放整段幅度缩放系数区间 0.9~1.1缩放过大改变动作强度语义时间扭曲时间轴拉伸/压缩max_stretch0.1~0.2破坏瞬时动作的节律窗口平移按时间偏移重切窗偏移量与重叠率相关标签和时间戳错位频域相位扰动FFT 相位随机旋转shift_range0.05~0.15 弧度信号边界出现振铃Mixup同类样本线性插值λ~Beta(0.2, 0.2)生成样本偏均值多样性下降DBA 重心平均同类样本 DTW 平均参与平均的样本数过平滑样本同质化VAE/GAN 合成潜空间采样再重构潜变量维数、训练轮数模式崩溃、多样性差这里所有的参数都不是拍脑袋定的。我给加速度计数据加噪声时默认从 0.02g 试起因为多数动作信号的能量在 0.1g 到 2g 之间0.02g 足够扰动又不会盖过动作。缩放系数 0.9 到 1.1 意味着最多把动作幅度拉伸或压缩 10%超过这个范围就会让“轻碰”和“重击”这两个类别开始混淆。选型时不要把所有方法叠在一起常见误用是先用噪声再缩放再做时间扭曲再来一次频域扰动最后生成的样本面目全非模型学到的全是变换带来的伪特征。3. 在Jupyter里复现四类增强方法时域、频域、互类与生成式这一章给出可以直接复制的四组 Python 函数。默认输入是 (n_samples, n_timesteps, n_channels) 的 numpy 数组标签是 (n_samples,) 的整数数组。不管你是用浏览器开 Jupyter notebook还是在 PyCharm 里调 Jupyter或者用 VS Code 接入 Jupyter 内核代码都通用只要内核是 Python3。缺库的时候在单元格里跑!pip install numpy scikit-learn scipy matplotlib就能补齐。3.1 时域变换加噪、缩放、时间扭曲与漂移时域变换是最容易理解也最容易写对的一类增强。它对每个样本直接修改波形。下面这组函数覆盖了四种最常见的操作import numpy as np def add_noise(X, noise_level0.02, seedNone): 加性白噪声。noise_level 是噪声标准差。 rng np.random.default_rng(seed) return X rng.normal(0.0, noise_level, sizeX.shape) def scale_signal(X, low0.9, high1.1, seedNone): 幅值缩放每个样本随机乘一个系数保留通道间比例。 rng np.random.default_rng(seed) factors rng.uniform(low, high, size(X.shape[0], 1, 1)) return X * factors def add_drift(X, max_drift0.01, seedNone): 基线漂移每个通道叠加一条线性趋势模拟传感器温度漂移。 rng np.random.default_rng(seed) n, t, c X.shape slope rng.uniform(-max_drift, max_drift, size(n, 1, c)) time_axis np.linspace(0, 1, t).reshape(1, -1, 1) return X slope * time_axis def time_warp(X, max_stretch0.2, seedNone): 时间扭曲对每个通道做轻微的时间轴拉伸或压缩。 rng np.random.default_rng(seed) n, t, c X.shape out np.empty_like(X) old_idx np.arange(t) for i in range(n): for ch in range(c): k 1.0 rng.uniform(-max_stretch, max_stretch) new_len max(2, int(t * k)) new_idx np.linspace(0, t - 1, new_len) out[i, :, ch] np.interp(old_idx, new_idx, X[i, :, ch]) return out四组函数的设计思路是加噪和漂移让模型对传感器自身的噪声和温度变化不敏感缩放让模型对佩戴松紧、动作力度变化不敏感时间扭曲模拟的是同一个动作做得快或慢。参数落在 0.9 到 1.1 的缩放区间对多数活动识别任务比较安全因为动作幅度差异通常远小于这个范围。seed参数建议在可复现实验里固定尤其当你要对比增强前后效果时随机种子一变结论可能也跟着变。使用时要特别注意add_noise的noise_level如果你不清楚输入数据的单位先看波形量级。如果数据归一化到 [-1,1]0.02 可能太小可以试 0.05如果数据是原始的加速度 g 值0.02g 是个稳当起点。时间扭曲里np.interp(old_idx, new_idx, X[i, :, ch])的含义是把拉伸后的时间轴重新映射回原来的长度这样输出形状不变可以直接拼进训练集。3.2 频域增强FFT相位扰动与低通滤波可穿戴信号里的走路、跑步、挥手都有较明显的节律频域结构是动作类别的重要特征。频域增强的思路是保留幅度谱只对相位做随机扰动这样能量分布和频率成分基本不变但波形细节会有变化模型不容易记住特定样本。def fft_phase_shift(X, shift_range0.1, seedNone): 频域相位扰动。shift_range 是相位偏移的弧度范围。 rng np.random.default_rng(seed) n, t, c X.shape out np.empty_like(X) for i in range(n): for ch in range(c): spectrum np.fft.rfft(X[i, :, ch]) freqs spectrum.shape[0] phase rng.uniform(-shift_range * 2 * np.pi, shift_range * 2 * np.pi, size(freqs,)) shifted spectrum * np.exp(1j * phase) out[i, :, ch] np.fft.irfft(shifted, nt) return outshift_range是这段代码最需要谨慎调的参数0.1 表示相位偏移最大是 0.1 倍圆周也就是正负 36 度。大于 0.2 时波形会出现明显的振铃效应也就是在信号突变的位置产生来回震荡的假波这种假波在加速度计数据里表现得像高频抖动模型很容易把它当成动作特征学进去。更稳的用法是只扰动中频段的相位直流分量和最高频分量保持不动这样既保留基线又增加多样性。如果你只是想滤掉传感器随机抖动直接用 scipy 的 butter 低通滤波器即可频率截止设在动作信号带宽的 1.5 倍附近。3.3 互类增强Mixup与DBA的轻量实现互类增强的思想是从同类样本里生成新的混合样本。时序 Mixup 在可穿戴分类任务里性价比极高实现只要十行def mixup_same_class(X, y, alpha0.2, seedNone): 同类样本线性插值。返回增强后的样本和对应标签。 rng np.random.default_rng(seed) n X.shape[0] # 为每个样本随机选一个同类样本 indices np.empty(n, dtypeint) for label in np.unique(y): cls_idx np.where(y label)[0] for i in cls_idx: candidates cls_idx[cls_idx ! i] indices[i] rng.choice(candidates) if len(candidates) 0 else i lam rng.beta(alpha, alpha, size(n, 1, 1)) X_aug lam * X (1 - lam) * X[indices] return X_aug, y.copy()Mixup 在图像上常用lambda从 Beta 分布采样时序里同样适用。alpha0.2时采样值更偏向 0 或 1生成的样本更接近两个原始样本之一alpha1.0时偏向 0.5生成的是两个样本的中间形态。我这里特意强调“同类”因为可穿戴动作标签天然互斥跨类插值会造出语义不明确的样本比如“站立”和“跑步”的中间状态标签没法定义。同类 Mixup 能扩大类内多样性又不会污染类别边界。比 Mixup 更复杂的是 DBADTW Barycenter Averaging它用动态时间规整找到一组同类样本的“平均重心”生成一个代表性的模板样本。DBA 对小样本类别特别有效但它会把一批样本平均成一个如果参与平均的样本本身形态差异大得到的是过平滑的中间形态反而压掉类内差异。tslearn 库里有现成的 barycenter 模块使用时注意输入样本长度必须一致否则要预先 padding 或截断。我的经验是Mixup 先上DBA 作为备选不要两个同时用。3.4 生成式增强PCA潜空间噪声注入的轻量方案真正的生成式增强比如 VAE 或 GAN在可穿戴时序上最容易遇到的问题就是训练不稳定数据量太小的时候模式崩溃很常见。有一个更稳的低门槛做法先用 PCA 把训练样本压缩到低维潜空间在潜空间里加扰动再重构回原始空间。这相当于一个线性 VAE不涉及对抗训练在 Jupyter 里能稳定产出看起来合理的样本。from sklearn.decomposition import PCA def pca_augment(X_train, n_components8, noise_std0.2, seedNone): PCA潜空间加噪重构生成合成样本。 X_train: (n_samples, n_timesteps, n_channels) n_components: 潜空间维数通常取 4~16 noise_std: 潜空间加噪标准差 rng np.random.default_rng(seed) n, t, c X_train.shape X_flat X_train.reshape(n, -1) pca PCA(n_componentsn_components) pca.fit(X_flat) Z pca.transform(X_flat) Z_aug Z rng.normal(0.0, noise_std, sizeZ.shape) X_aug_flat pca.inverse_transform(Z_aug) return X_aug_flat.reshape(n, t, c)n_components决定潜空间保留多少信息。取 4 到 16 对大多数 6 通道可穿戴信号够用太小会丢失通道间的相关性重构出来的样本像被磨平了太大等于没压缩加噪后基本就是原始样本加噪声。noise_std从 0.1 起步不要超过 0.5否则潜空间里的扰动会重构出幅度异常的信号。这一招适合在样本量少到 Mixup 都不太够用时补一刀但它的多样性有限只能作为组合增强里的一个组成部分不能独挑大梁。如果你后续要用 LSTM 做时间序列预测或分类增强数据记得保持 (n, t, c) 的固定形状PCA 重构出的样本形状不会变可以直接拼接进训练数组。4. 增强不是玄学用分布对比与Baseline分类器验证效果增强做完不验证等于白做。这一章讲两件事增强前后的样本分布是否合理以及增强到底有没有提升分类效果。验证用的代码都是轻量的在 Jupyter 里跑几分钟就能出结果。4.1 增强前后分布可视化PCA降维与样本波形对照先把原始样本和增强样本合并用 PCA 降维到二维看它们在特征空间里的分布是否有重叠。如果增强样本完全落在原始样本的分布之外说明变换参数过强如果完全重叠说明增强太平淡多样性不够。注意这里用的“分布”不是原始波形而是统计特征我习惯提取均值、标准差和 FFT 幅值均值def extract_features(X): 从时序样本里提取统计特征用于分类和可视化。 X: (n_samples, n_timesteps, n_channels) 返回: (n_samples, n_channels * 3) features [] for i in range(X.shape[0]): sample X[i] f np.concatenate([ sample.mean(axis0), sample.std(axis0), np.abs(np.fft.rfft(sample, axis0)).mean(axis0) ]) features.append(f) return np.array(features)然后在 Jupyter 里运行import matplotlib.pyplot as plt from sklearn.decomposition import PCA def plot_augmentation_distribution(X_orig, X_aug): feats_orig extract_features(X_orig) feats_aug extract_features(X_aug) feats_cat np.concatenate([feats_orig, feats_aug], axis0) pca PCA(n_components2) emb pca.fit_transform(feats_cat) fig, ax plt.subplots(figsize(6, 5)) ax.scatter(emb[:len(X_orig), 0], emb[:len(X_orig), 1], s6, alpha0.5, labeloriginal) ax.scatter(emb[len(X_orig):, 0], emb[len(X_orig):, 1], s6, alpha0.5, labelaugmented) ax.set_xlabel(PC1) ax.set_ylabel(PC2) ax.legend() plt.show()理想状态是增强样本散落在原始样本周围而不是聚集在一个角落。如果看到增强样本形成独立的小团就要怀疑过强的噪声或过大的缩放系数。分布图还能暴露另一个问题当原始数据类别极少t-SNE 或 PCA 的投影会给出误导性的视觉结论这时候要结合波形图看。随机抽几个增强样本画波形和原始样本并排比对确认波形形态没有被破坏。如果在 Jupyter 里画图时横坐标刻度太密集用plt.MaxNLocator(5)限制坐标轴刻度数量否则标签会叠成一团。4.2 用Baseline分类器量化增强收益分布图是定性验证真正要说服自己和团队的是数字。用一个轻量分类器做 baseline分别跑不增强和增强两组实验对比准确率或 F1。因为这里验证的是增强的价值不是模型的天花板我用逻辑回归加特征提取就能看出趋势。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.model_selection import train_test_split def evaluate_augmentation(X_orig, y, X_aug, y_aug): # 原始数据做 baseline X_feat_orig extract_features(X_orig) X_train, X_test, y_train, y_test train_test_split( X_feat_orig, y, test_size0.2, stratifyy, random_state42) baseline make_pipeline(StandardScaler(), LogisticRegression(max_iter500)) baseline.fit(X_train, y_train) base_acc baseline.score(X_test, y_test) # 增强数据拼进训练集测试集仍旧用原始数据 X_train_aug np.concatenate([X_feat_orig, extract_features(X_aug)], axis0) y_train_aug np.concatenate([y, y_aug], axis0) X_test X_feat_orig # 注意这里测试集是原始数据避免泄漏 y_test y aug_model make_pipeline(StandardScaler(), LogisticRegression(max_iter500)) aug_model.fit(X_train_aug, y_train_aug) aug_acc aug_model.score(X_test, y_test) print(fbaseline acc: {base_acc:.4f}) print(faugmented acc: {aug_acc:.4f}) return base_acc, aug_acc这段代码有个关键点测试集始终是原始数据的随机划分增强样本只进训练集。如果你把增强样本也放进划分再随机切同一个原始样本的多个增强版本很可能同时出现在训练和测试里准确率虚高。这里的评估方式虽然简单但能告诉你两个信息增强把准确率往上顶了还是压下去了以及它在验证集上的稳定性。数字跑出来如果增强后准确率反而下降先不要急着调参回到 4.1 看分布图大概率是变换参数过强。4.3 评估指标准确率、F1与类别召回准确率在可穿戴分类任务里经常骗人。比如“正常行走”这类样本量大的类别把准确率抬得很高但“跌倒”这类小样本类别表现极差准确率数字却看不出来。增强最应该解决的恰恰是小样本类别的召回率。所以在 baseline 评估里不要让准确率当唯一指标至少要同时记录 macro F1 和每个类别的召回率。指标适合场景说明准确率类别均衡时直观但小样本类别失效时看不出来macro F1类别不平衡每个类别 F1 平均小类被同等对待每类召回率跌倒检测等安全场景漏报比误报代价更高直接看这个可穿戴任务里如果增强只带来准确率上升 1 个百分点但跌倒类别的召回率从 0.6 涨到 0.85这个增强就是有效的。我在实际项目里会把这三项指标打出来再决定要不要调整增强参数。单独追求准确率的提升很容易被增强带来的过拟合掩盖。5. 可穿戴时序增强的避坑记录五个亲身踩过的边界这一章是我自己在可穿戴项目里真实踩过的坑按“现象-原因-解决”的格式记录希望能省掉你几天的排查时间。5.1 增强后标签错位样本和标签尾部对不上现象是训练时准确率忽高忽低画混淆矩阵发现很多预测标签比真实标签慢了一个或几个位置。原因是滑动窗口切窗和增强用了不同的索引体系。比如先对整条传感器流做了噪声增强再切窗增强后的数组长度没变但内存布局变了或者你用sliding_window切完窗后又对重叠区域单独做缩放导致某个样本实际上覆盖了相邻动作的波形但标签还是原来的。解决方法是把“切窗 标签对齐 增强”放在同一个函数里每生成一个窗口就立刻确认窗口的起始时间戳和标签对应。增强函数只接受已经带标签的样本数组不要对完整传感器流先增强再切窗。另外离线增强后用np.savez保存时把 X_aug 和 y_aug 同时写进去下次加载时先检查len(X_aug) len(y_aug)这个断言能拦住大多数索引错位问题。5.2 归一化顺序反了增强白做甚至有害现象是增强后的样本在验证集上表现还不如不增强画波形发现增强样本的幅度整体偏小且与原始样本的量纲不一致。原因是先做了标准化归一化再对标准化后的样本加噪声或缩放而标准化时用的均值方差来自原始训练集增强样本等于被二次缩放分布整体漂移。正确的顺序是先做数据增强再拟合标准化器。也就是把StandardScaler放在增强之后只拟合增强后的训练集验证集和测试集用同一个 scaler 做变换。如果你希望增强前后的样本都处在同一量纲可以单独画增强前后每个通道的均值和标准差对比偏差超过 10% 就说明顺序或参数有问题。这个小检查成本很低值得养成习惯。5.3 频域相位扰动过头振铃伪影变成新特征现象是频域增强后准确率提升但换一个设备验证时效果暴跌模型对新设备的信号完全不鲁棒。原因是shift_range设得太大相位扰动在信号突变点附近产生了明显的振铃模型把振铃当成了动作特征而真实设备里没有这个伪影。解决方法是把shift_range控制在 0.05 到 0.15 之间并且扰动时不动直流分量和最高频分量只扰动能量占比靠前的 30% 到 50% 的频率分量。还可以直接拿增强样本和原始样本的频谱做对比画在一张图上如果频谱包络形状明显改变就调小范围。我自己在加速度计数据上一般用 0.1再大就出伪影。5.4 生成式增强把样本变同质多样性不升反降现象是 PCA 增广后训练集的分类准确率上升但测试集准确率没有同步上升甚至下降画分布图发现增强样本全部集中在原始样本中心附近的一小片区域。原因是潜空间加噪的标准差太小PCA 重构时把噪声压缩掉了产出的样本几乎就是原始样本的均值形态多样性几乎没有增加反而让模型在训练时反复看到同质的平均样本变相加重了对训练分布的过度拟合。解决办法是先画出潜空间投影的分布Z的每一维标准差大概是多少noise_std取到这些标准差的一半到等量级。比如Z第一维标准差是 3.2noise_std从 1.0 试起而不是拍脑袋设个 0.2。检查合成样本和原始样本的类内距离增强后的类内平均距离如果比原始类内距离还小说明多样性不足加大噪声或换用 Mixup。5.5 内存不够与随机种子不一致实验结果没法复现现象是在 Jupyter 里增强跑得正欢突然内核崩了或者同一个 notebook 重复跑两遍出来的准确率不一样。原因有二一是离线增强把所有方法、所有倍率一次性全部生成数组数量翻了十几倍直接爆内存二是增强函数里的随机种子没有固定每次跑的样本都不同自然不能复现。解决方式是分批生成、及时释放。增强数据可以每生成一批就np.savez追加写入不要一直攒在变量里单批样本量控制在几万以内。每个增强函数都接受seed参数在 notebook 开头定义一个全局的SEED 42所有函数传进去。如果你在一个单元格里连续跑多个增强函数还要注意每个函数内部如果不重置numpy.random的全局状态后面的随机序列会被前面的调用影响所以显式用np.random.default_rng(seed)是更稳的写法。Jupyter 本身对长任务也不友好数据量超过几十万样本后建议把增强脚本抽成.py在终端跑跑完存盘再回 notebook 做分析。6. 进阶按受试者分组交叉验证把增强放进严格评估管线可穿戴实验里最容易被忽略的泄漏不是增强样本进了测试集而是同一个受试者的数据同时出现在训练和测试集中。模型会记住佩戴者的个体特征而不是动作类别本身。这个问题在加了增强之后会被放大因为增强能让同一个人的样本出现几十次泄漏效果更强。解决方法是按受试者分组做交叉验证。先把数据按受试者 ID 组织然后用GroupKFold划分确保同一个人的所有窗口只出现在同一个 foldfrom sklearn.model_selection import GroupKFold def evaluate_grouped(X, y, groups, X_aug, y_aug, aug_groups): 按受试者分组评估增强效果。 groups: 每个样本所属的受试者 ID X_aug: 增强后的样本 aug_groups: 增强样本对应的受试者 ID gkf GroupKFold(n_splits5) scores [] for train_idx, test_idx in gkf.split(X.reshape(X.shape[0], -1), y, groups): # 只把 train_idx 对应的训练样本与增强样本拼接 X_train np.concatenate([X[train_idx], X_aug], axis0) y_train np.concatenate([y[train_idx], y_aug], axis0) # 注意增强样本必须只来自训练 fold 的受试者 X_test, y_test X[test_idx], y[test_idx] model make_pipeline(StandardScaler(), LogisticRegression(max_iter500)) model.fit(extract_features(X_train), y_train) acc model.score(extract_features(X_test), y_test) scores.append(acc) return scores这段代码里最需要注意的是X_aug的来源。增强样本只能由训练 fold 内的原始样本生成不能拿测试 fold 的样本来造否则 leaking。严谨的做法是在每个 fold 内部重新对训练部分做增强而不是在 fold 外提前生成一份增强数组。上面的写法为了示意简化了这一点实际落地时把增强代码放进 fold 循环内部只接收X[train_idx]。交叉验证跑完你会得到五组得分。我的习惯是同时跑一版不带增强的GroupKFold两边的均值差才是增强的真实收益。如果增强后均值上升但方差也变大说明增强对某些受试者效果好、对另一些不稳定这时要按类别和受试者分别看召回率不要只盯着平均分。我自己做可穿戴项目时有个坚持了很久的习惯每次增强调参前先跑一版严格分组的 baseline任何增强方案如果在这个 baseline 上没有提升就不让它进训练管线。这个做法挡掉过很多看起来漂亮的增强方法。数据增强的价值最终要体现在没见过的人戴上传感器的表现上而不是 notebook 里的训练曲线。希望帮到你。本文还有配套的精品资源点击获取