據(jù)增強:Python實現(xiàn)與避坑指南)
簡介針對可穿戴傳感器時間序列數(shù)據(jù)的數(shù)據(jù)增強需求提供一套完整的 Jupyter Notebook 示例代碼面向從事移動健康、帕金森病監(jiān)測等方向的研究者與算法工程師。方法參考 ICMI 2017 論文通過對原始信號施加多種失真變換生成更多訓(xùn)練樣本可提升卷積神經(jīng)網(wǎng)絡(luò)等模型的效果并降低過擬合風(fēng)險。包體為 ZIP 壓縮格式共 5 個文件約 892KB包含可直接運行的 ipynb 筆記本、配套 py 腳本、示例數(shù)據(jù) npy、說明文檔 md 及效果圖 png方便按演示—運行—理解流程學(xué)習(xí)。已有 475 人學(xué)習(xí)下載。下載后可獲得帶注釋的增強代碼、真實傳感器樣本與 README 使用說明。資源結(jié)構(gòu)簡潔適合快速遷移到自己的時間序列項目中也可作為數(shù)據(jù)擴充預(yù)處理的參考起點。1. 可穿戴傳感器時間序列數(shù)據(jù)增強跑通一套Python增強代碼前先想清楚的事做可穿戴跌倒檢測那陣子我最大的瓶頸不是模型選型而是數(shù)據(jù)不夠用。一塊IMU掛在手腕上一個動作類別攢了一千多條有效樣本另一個類別只有兩百多條直接拿去訓(xùn)練時間序列分類器過擬合幾乎是必然的。可穿戴傳感器數(shù)據(jù)本質(zhì)上就是時間序列數(shù)據(jù)數(shù)據(jù)增強要做的事是在不破壞動作語義的前提下用Python生成更多形狀合理、標(biāo)簽可靠的樣本讓模型在小樣本和不平衡場景下也能泛化。這套方法在Jupyter里跑通很容易但跑通和有效之間隔著不少參數(shù)和邊界。這篇文章就是從切窗、選變換、寫代碼到驗證收益的完整路徑適合手里握著加速度計、陀螺儀或心率數(shù)據(jù)的開發(fā)者想用數(shù)據(jù)增強把模型效果和穩(wěn)定性頂上去而不是只在訓(xùn)練日志里多看一個過擬合的數(shù)字。2. 增強策略怎么選可穿戴時序的四個現(xiàn)實約束與前置處理圖像增強可以對整張圖做翻轉(zhuǎn)、裁剪、色彩擾動因為語義在像素層面是冗余的可穿戴時間序列不行。加速度計、陀螺儀和心率的每一個采樣點背后都有物理含義增強做過頭會直接改掉動作類別。這一章先把四個影響成敗的現(xiàn)實約束講清楚再給你一張選型表。很多人在Jupyter里復(fù)制別人的增強代碼跑出奇怪結(jié)果通常不是代碼本身錯而是沒想清楚這些前提。2.1 樣本長度怎么定窗口、重疊率與采樣率可穿戴設(shè)備吐出來的是連續(xù)流增強的第一步是把流切成樣本。常見做法是固定窗口加滑動重疊先確定窗口長度再按步長滑動切窗。窗口長度由動作周期決定日?;顒幼R別里的單個動作比如站立、坐下、走路、揮手周期大概在 0.5 到 3 秒之間所以 2 到 5 秒的窗口是大多數(shù)項目的起步值。如果采樣率是 50Hz2 秒窗口就是 100 個采樣點如果是 125Hz就是 250 個點。窗口太短會把動作切碎增強后學(xué)不到上下文窗口太長又會把兩個相鄰動作混進(jìn)同一個標(biāo)簽增強做得越花哨標(biāo)簽越不可信。切窗的代碼在 Jupyter 里就是個循環(huán)import numpy as np def sliding_window(x, window_size, step_size): 把一維或多通道的連續(xù)信號切成固定長度窗口。 x: (n_samples, n_channels) 的原始傳感器流 window_size: 每個窗口的采樣點數(shù) 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%。重疊率高會顯著放大樣本量但相鄰樣本高度相似訓(xùn)練集和驗證集如果按時間順序切很容易出現(xiàn)泄漏。我給可穿戴項目切窗時通常先設(shè) 50% 重疊拿一版基線再視類別不平衡程度往下調(diào)。注意這里切的是“訓(xùn)練集和測試集各自內(nèi)部的流”等做好增強后再統(tǒng)一劃分組別是為了避免同一段物理信號同時出現(xiàn)在訓(xùn)練和驗證兩側(cè)。2.2 離線增強還是在線增強什么時候做、怎么做增強有兩種落地方式。離線增強是在預(yù)處理階段把所有變換一次性跑完把生成的樣本存成 numpy 數(shù)組或 npz 文件之后再訓(xùn)練在線增強是在訓(xùn)練循環(huán)里對每個 batch 做隨機變換每次 epoch 看到的樣本都略不一樣??纱┐鲿r序數(shù)據(jù)集的規(guī)模通常不大百萬級樣本已經(jīng)很夸張離線增強的內(nèi)存壓力完全可控。我在 Jupyter 里探索階段一律先用離線增強因為它能讓你把增強前后的樣本畫出來看能檢查標(biāo)簽有沒有跟著變能比較分布差異這些事在訓(xùn)練腳本里做起來很別扭。在線增強的優(yōu)勢是省內(nèi)存、天然防過擬合但它要求增強函數(shù)是純隨機且可復(fù)用的。如果你在 Jupyter 里先離線把參數(shù)試好再搬到 PyTorch 的 Dataset 或 TensorFlow 的 tf.data 里重寫一遍兩種優(yōu)勢都能拿到。要注意的是無論哪種方式增強只應(yīng)該作用在訓(xùn)練集上驗證集和測試集必須保持原始數(shù)據(jù)否則你評估的“增強帶來的提升”很可能是數(shù)據(jù)泄漏帶來的幻覺。2.3 通道、軸與重力分量增強前的傳感器先驗處理可穿戴數(shù)據(jù)的典型形狀是 (樣本數(shù), 時間步數(shù), 通道數(shù))最常見的是加速度計三軸加陀螺儀三軸一共六個通道。增強時有一個很容易被忽略的前提三個加速度軸不是獨立無關(guān)的通道它們之間有重力分量和方向語義。如果直接對加速度計的 XYZ 軸做隨機大幅旋轉(zhuǎn)動作的物理含義就變了跌倒檢測里“面朝下”和“側(cè)倒”可能被旋轉(zhuǎn)成同一個樣本標(biāo)簽已經(jīng)不可靠。常見的處理是先去掉重力分量再做增強尤其對加速度計數(shù)據(jù)。用一個高通濾波器把靜息重力去掉是加速度計預(yù)處理里的常規(guī)操作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: 對哪個軸做濾波三軸數(shù)據(jù)默認(rèn)時間是第 1 維 b, a signal.butter(2, cutoff_hz / (fs / 2), btypehigh) return signal.filtfilt(b, a, x, axisaxis)截止頻率取 0.5Hz 是因為靜態(tài)重力是直流附近的分量動作信號的頻率通常更高。做完這一步再對樣本做縮放、加噪或時間扭曲語義風(fēng)險會小得多。如果你手里的數(shù)據(jù)已經(jīng)做過歸一化不知道原始單位建議先畫幾段原始波形確認(rèn)重力分量是否存在再決定要不要做這步。設(shè)備采樣率不一致的情況也常遇到兩臺手表一臺 50Hz 一臺 100Hz 拼在一起增強前先統(tǒng)一重采樣到低頻那臺的采樣率避免窗口長度對不上。2.4 常見增強方法的分層選型表把可穿戴時序上常用的方法整理成一張表選型能省很多時間。核心原則是樣本少先用輕量時域變換類別不平衡嚴(yán)重就疊加采樣策略頻域方法專門對付模型對相位和周期性不魯棒的情況生成式方法放在最后數(shù)據(jù)量不夠時很容易翻車。方法操作對象關(guān)鍵參數(shù)主要風(fēng)險白噪聲原始信號加噪noise_level0.02~0.05g信噪比下降特征被淹沒幅值縮放整段幅度縮放系數(shù)區(qū)間 0.9~1.1縮放過大改變動作強度語義時間扭曲時間軸拉伸/壓縮max_stretch0.1~0.2破壞瞬時動作的節(jié)律窗口平移按時間偏移重切窗偏移量與重疊率相關(guān)標(biāo)簽和時間戳錯位頻域相位擾動FFT 相位隨機旋轉(zhuǎn)shift_range0.05~0.15 弧度信號邊界出現(xiàn)振鈴Mixup同類樣本線性插值λ~Beta(0.2, 0.2)生成樣本偏均值多樣性下降DBA 重心平均同類樣本 DTW 平均參與平均的樣本數(shù)過平滑樣本同質(zhì)化VAE/GAN 合成潛空間采樣再重構(gòu)潛變量維數(shù)、訓(xùn)練輪數(shù)模式崩潰、多樣性差這里所有的參數(shù)都不是拍腦袋定的。我給加速度計數(shù)據(jù)加噪聲時默認(rèn)從 0.02g 試起因為多數(shù)動作信號的能量在 0.1g 到 2g 之間0.02g 足夠擾動又不會蓋過動作??s放系數(shù) 0.9 到 1.1 意味著最多把動作幅度拉伸或壓縮 10%超過這個范圍就會讓“輕碰”和“重?fù)簟边@兩個類別開始混淆。選型時不要把所有方法疊在一起常見誤用是先用噪聲再縮放再做時間扭曲再來一次頻域擾動最后生成的樣本面目全非模型學(xué)到的全是變換帶來的偽特征。3. 在Jupyter里復(fù)現(xiàn)四類增強方法時域、頻域、互類與生成式這一章給出可以直接復(fù)制的四組 Python 函數(shù)。默認(rèn)輸入是 (n_samples, n_timesteps, n_channels) 的 numpy 數(shù)組標(biāo)簽是 (n_samples,) 的整數(shù)數(shù)組。不管你是用瀏覽器開 Jupyter notebook還是在 PyCharm 里調(diào) Jupyter或者用 VS Code 接入 Jupyter 內(nèi)核代碼都通用只要內(nèi)核是 Python3。缺庫的時候在單元格里跑!pip install numpy scikit-learn scipy matplotlib就能補齊。3.1 時域變換加噪、縮放、時間扭曲與漂移時域變換是最容易理解也最容易寫對的一類增強。它對每個樣本直接修改波形。下面這組函數(shù)覆蓋了四種最常見的操作import numpy as np def add_noise(X, noise_level0.02, seedNone): 加性白噪聲。noise_level 是噪聲標(biāo)準(zhǔn)差。 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): 幅值縮放每個樣本隨機乘一個系數(shù)保留通道間比例。 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四組函數(shù)的設(shè)計思路是加噪和漂移讓模型對傳感器自身的噪聲和溫度變化不敏感縮放讓模型對佩戴松緊、動作力度變化不敏感時間扭曲模擬的是同一個動作做得快或慢。參數(shù)落在 0.9 到 1.1 的縮放區(qū)間對多數(shù)活動識別任務(wù)比較安全因為動作幅度差異通常遠(yuǎn)小于這個范圍。seed參數(shù)建議在可復(fù)現(xiàn)實驗里固定尤其當(dāng)你要對比增強前后效果時隨機種子一變結(jié)論可能也跟著變。使用時要特別注意add_noise的noise_level如果你不清楚輸入數(shù)據(jù)的單位先看波形量級。如果數(shù)據(jù)歸一化到 [-1,1]0.02 可能太小可以試 0.05如果數(shù)據(jù)是原始的加速度 g 值0.02g 是個穩(wěn)當(dāng)起點。時間扭曲里np.interp(old_idx, new_idx, X[i, :, ch])的含義是把拉伸后的時間軸重新映射回原來的長度這樣輸出形狀不變可以直接拼進(jìn)訓(xùn)練集。3.2 頻域增強FFT相位擾動與低通濾波可穿戴信號里的走路、跑步、揮手都有較明顯的節(jié)律頻域結(jié)構(gòu)是動作類別的重要特征。頻域增強的思路是保留幅度譜只對相位做隨機擾動這樣能量分布和頻率成分基本不變但波形細(xì)節(jié)會有變化模型不容易記住特定樣本。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是這段代碼最需要謹(jǐn)慎調(diào)的參數(shù)0.1 表示相位偏移最大是 0.1 倍圓周也就是正負(fù) 36 度。大于 0.2 時波形會出現(xiàn)明顯的振鈴效應(yīng)也就是在信號突變的位置產(chǎn)生來回震蕩的假波這種假波在加速度計數(shù)據(jù)里表現(xiàn)得像高頻抖動模型很容易把它當(dāng)成動作特征學(xué)進(jìn)去。更穩(wěn)的用法是只擾動中頻段的相位直流分量和最高頻分量保持不動這樣既保留基線又增加多樣性。如果你只是想濾掉傳感器隨機抖動直接用 scipy 的 butter 低通濾波器即可頻率截止設(shè)在動作信號帶寬的 1.5 倍附近。3.3 互類增強Mixup與DBA的輕量實現(xiàn)互類增強的思想是從同類樣本里生成新的混合樣本。時序 Mixup 在可穿戴分類任務(wù)里性價比極高實現(xiàn)只要十行def mixup_same_class(X, y, alpha0.2, seedNone): 同類樣本線性插值。返回增強后的樣本和對應(yīng)標(biāo)簽。 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生成的是兩個樣本的中間形態(tài)。我這里特意強調(diào)“同類”因為可穿戴動作標(biāo)簽天然互斥跨類插值會造出語義不明確的樣本比如“站立”和“跑步”的中間狀態(tài)標(biāo)簽沒法定義。同類 Mixup 能擴大類內(nèi)多樣性又不會污染類別邊界。比 Mixup 更復(fù)雜的是 DBADTW Barycenter Averaging它用動態(tài)時間規(guī)整找到一組同類樣本的“平均重心”生成一個代表性的模板樣本。DBA 對小樣本類別特別有效但它會把一批樣本平均成一個如果參與平均的樣本本身形態(tài)差異大得到的是過平滑的中間形態(tài)反而壓掉類內(nèi)差異。tslearn 庫里有現(xiàn)成的 barycenter 模塊使用時注意輸入樣本長度必須一致否則要預(yù)先 padding 或截斷。我的經(jīng)驗是Mixup 先上DBA 作為備選不要兩個同時用。3.4 生成式增強PCA潛空間噪聲注入的輕量方案真正的生成式增強比如 VAE 或 GAN在可穿戴時序上最容易遇到的問題就是訓(xùn)練不穩(wěn)定數(shù)據(jù)量太小的時候模式崩潰很常見。有一個更穩(wěn)的低門檻做法先用 PCA 把訓(xùn)練樣本壓縮到低維潛空間在潛空間里加擾動再重構(gòu)回原始空間。這相當(dāng)于一個線性 VAE不涉及對抗訓(xùn)練在 Jupyter 里能穩(wěn)定產(chǎn)出看起來合理的樣本。from sklearn.decomposition import PCA def pca_augment(X_train, n_components8, noise_std0.2, seedNone): PCA潛空間加噪重構(gòu)生成合成樣本。 X_train: (n_samples, n_timesteps, n_channels) n_components: 潛空間維數(shù)通常取 4~16 noise_std: 潛空間加噪標(biāo)準(zhǔn)差 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 對大多數(shù) 6 通道可穿戴信號夠用太小會丟失通道間的相關(guān)性重構(gòu)出來的樣本像被磨平了太大等于沒壓縮加噪后基本就是原始樣本加噪聲。noise_std從 0.1 起步不要超過 0.5否則潛空間里的擾動會重構(gòu)出幅度異常的信號。這一招適合在樣本量少到 Mixup 都不太夠用時補一刀但它的多樣性有限只能作為組合增強里的一個組成部分不能獨挑大梁。如果你后續(xù)要用 LSTM 做時間序列預(yù)測或分類增強數(shù)據(jù)記得保持 (n, t, c) 的固定形狀PCA 重構(gòu)出的樣本形狀不會變可以直接拼接進(jìn)訓(xùn)練數(shù)組。4. 增強不是玄學(xué)用分布對比與Baseline分類器驗證效果增強做完不驗證等于白做。這一章講兩件事增強前后的樣本分布是否合理以及增強到底有沒有提升分類效果。驗證用的代碼都是輕量的在 Jupyter 里跑幾分鐘就能出結(jié)果。4.1 增強前后分布可視化PCA降維與樣本波形對照先把原始樣本和增強樣本合并用 PCA 降維到二維看它們在特征空間里的分布是否有重疊。如果增強樣本完全落在原始樣本的分布之外說明變換參數(shù)過強如果完全重疊說明增強太平淡多樣性不夠。注意這里用的“分布”不是原始波形而是統(tǒng)計特征我習(xí)慣提取均值、標(biāo)準(zhǔn)差和 FFT 幅值均值def extract_features(X): 從時序樣本里提取統(tǒng)計特征用于分類和可視化。 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ài)是增強樣本散落在原始樣本周圍而不是聚集在一個角落。如果看到增強樣本形成獨立的小團就要懷疑過強的噪聲或過大的縮放系數(shù)。分布圖還能暴露另一個問題當(dāng)原始數(shù)據(jù)類別極少t-SNE 或 PCA 的投影會給出誤導(dǎo)性的視覺結(jié)論這時候要結(jié)合波形圖看。隨機抽幾個增強樣本畫波形和原始樣本并排比對確認(rèn)波形形態(tài)沒有被破壞。如果在 Jupyter 里畫圖時橫坐標(biāo)刻度太密集用plt.MaxNLocator(5)限制坐標(biāo)軸刻度數(shù)量否則標(biāo)簽會疊成一團。4.2 用Baseline分類器量化增強收益分布圖是定性驗證真正要說服自己和團隊的是數(shù)字。用一個輕量分類器做 baseline分別跑不增強和增強兩組實驗對比準(zhǔn)確率或 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): # 原始數(shù)據(jù)做 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) # 增強數(shù)據(jù)拼進(jìn)訓(xùn)練集測試集仍舊用原始數(shù)據(jù) 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 # 注意這里測試集是原始數(shù)據(jù)避免泄漏 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這段代碼有個關(guān)鍵點測試集始終是原始數(shù)據(jù)的隨機劃分增強樣本只進(jìn)訓(xùn)練集。如果你把增強樣本也放進(jìn)劃分再隨機切同一個原始樣本的多個增強版本很可能同時出現(xiàn)在訓(xùn)練和測試?yán)餃?zhǔn)確率虛高。這里的評估方式雖然簡單但能告訴你兩個信息增強把準(zhǔn)確率往上頂了還是壓下去了以及它在驗證集上的穩(wěn)定性。數(shù)字跑出來如果增強后準(zhǔn)確率反而下降先不要急著調(diào)參回到 4.1 看分布圖大概率是變換參數(shù)過強。4.3 評估指標(biāo)準(zhǔn)確率、F1與類別召回準(zhǔn)確率在可穿戴分類任務(wù)里經(jīng)常騙人。比如“正常行走”這類樣本量大的類別把準(zhǔn)確率抬得很高但“跌倒”這類小樣本類別表現(xiàn)極差準(zhǔn)確率數(shù)字卻看不出來。增強最應(yīng)該解決的恰恰是小樣本類別的召回率。所以在 baseline 評估里不要讓準(zhǔn)確率當(dāng)唯一指標(biāo)至少要同時記錄 macro F1 和每個類別的召回率。指標(biāo)適合場景說明準(zhǔn)確率類別均衡時直觀但小樣本類別失效時看不出來macro F1類別不平衡每個類別 F1 平均小類被同等對待每類召回率跌倒檢測等安全場景漏報比誤報代價更高直接看這個可穿戴任務(wù)里如果增強只帶來準(zhǔn)確率上升 1 個百分點但跌倒類別的召回率從 0.6 漲到 0.85這個增強就是有效的。我在實際項目里會把這三項指標(biāo)打出來再決定要不要調(diào)整增強參數(shù)。單獨追求準(zhǔn)確率的提升很容易被增強帶來的過擬合掩蓋。5. 可穿戴時序增強的避坑記錄五個親身踩過的邊界這一章是我自己在可穿戴項目里真實踩過的坑按“現(xiàn)象-原因-解決”的格式記錄希望能省掉你幾天的排查時間。5.1 增強后標(biāo)簽錯位樣本和標(biāo)簽尾部對不上現(xiàn)象是訓(xùn)練時準(zhǔn)確率忽高忽低畫混淆矩陣發(fā)現(xiàn)很多預(yù)測標(biāo)簽比真實標(biāo)簽慢了一個或幾個位置。原因是滑動窗口切窗和增強用了不同的索引體系。比如先對整條傳感器流做了噪聲增強再切窗增強后的數(shù)組長度沒變但內(nèi)存布局變了或者你用sliding_window切完窗后又對重疊區(qū)域單獨做縮放導(dǎo)致某個樣本實際上覆蓋了相鄰動作的波形但標(biāo)簽還是原來的。解決方法是把“切窗 標(biāo)簽對齊 增強”放在同一個函數(shù)里每生成一個窗口就立刻確認(rèn)窗口的起始時間戳和標(biāo)簽對應(yīng)。增強函數(shù)只接受已經(jīng)帶標(biāo)簽的樣本數(shù)組不要對完整傳感器流先增強再切窗。另外離線增強后用np.savez保存時把 X_aug 和 y_aug 同時寫進(jìn)去下次加載時先檢查len(X_aug) len(y_aug)這個斷言能攔住大多數(shù)索引錯位問題。5.2 歸一化順序反了增強白做甚至有害現(xiàn)象是增強后的樣本在驗證集上表現(xiàn)還不如不增強畫波形發(fā)現(xiàn)增強樣本的幅度整體偏小且與原始樣本的量綱不一致。原因是先做了標(biāo)準(zhǔn)化歸一化再對標(biāo)準(zhǔn)化后的樣本加噪聲或縮放而標(biāo)準(zhǔn)化時用的均值方差來自原始訓(xùn)練集增強樣本等于被二次縮放分布整體漂移。正確的順序是先做數(shù)據(jù)增強再擬合標(biāo)準(zhǔn)化器。也就是把StandardScaler放在增強之后只擬合增強后的訓(xùn)練集驗證集和測試集用同一個 scaler 做變換。如果你希望增強前后的樣本都處在同一量綱可以單獨畫增強前后每個通道的均值和標(biāo)準(zhǔn)差對比偏差超過 10% 就說明順序或參數(shù)有問題。這個小檢查成本很低值得養(yǎng)成習(xí)慣。5.3 頻域相位擾動過頭振鈴偽影變成新特征現(xiàn)象是頻域增強后準(zhǔn)確率提升但換一個設(shè)備驗證時效果暴跌模型對新設(shè)備的信號完全不魯棒。原因是shift_range設(shè)得太大相位擾動在信號突變點附近產(chǎn)生了明顯的振鈴模型把振鈴當(dāng)成了動作特征而真實設(shè)備里沒有這個偽影。解決方法是把shift_range控制在 0.05 到 0.15 之間并且擾動時不動直流分量和最高頻分量只擾動能量占比靠前的 30% 到 50% 的頻率分量。還可以直接拿增強樣本和原始樣本的頻譜做對比畫在一張圖上如果頻譜包絡(luò)形狀明顯改變就調(diào)小范圍。我自己在加速度計數(shù)據(jù)上一般用 0.1再大就出偽影。5.4 生成式增強把樣本變同質(zhì)多樣性不升反降現(xiàn)象是 PCA 增廣后訓(xùn)練集的分類準(zhǔn)確率上升但測試集準(zhǔn)確率沒有同步上升甚至下降畫分布圖發(fā)現(xiàn)增強樣本全部集中在原始樣本中心附近的一小片區(qū)域。原因是潛空間加噪的標(biāo)準(zhǔn)差太小PCA 重構(gòu)時把噪聲壓縮掉了產(chǎn)出的樣本幾乎就是原始樣本的均值形態(tài)多樣性幾乎沒有增加反而讓模型在訓(xùn)練時反復(fù)看到同質(zhì)的平均樣本變相加重了對訓(xùn)練分布的過度擬合。解決辦法是先畫出潛空間投影的分布Z的每一維標(biāo)準(zhǔn)差大概是多少noise_std取到這些標(biāo)準(zhǔn)差的一半到等量級。比如Z第一維標(biāo)準(zhǔn)差是 3.2noise_std從 1.0 試起而不是拍腦袋設(shè)個 0.2。檢查合成樣本和原始樣本的類內(nèi)距離增強后的類內(nèi)平均距離如果比原始類內(nèi)距離還小說明多樣性不足加大噪聲或換用 Mixup。5.5 內(nèi)存不夠與隨機種子不一致實驗結(jié)果沒法復(fù)現(xiàn)現(xiàn)象是在 Jupyter 里增強跑得正歡突然內(nèi)核崩了或者同一個 notebook 重復(fù)跑兩遍出來的準(zhǔn)確率不一樣。原因有二一是離線增強把所有方法、所有倍率一次性全部生成數(shù)組數(shù)量翻了十幾倍直接爆內(nèi)存二是增強函數(shù)里的隨機種子沒有固定每次跑的樣本都不同自然不能復(fù)現(xiàn)。解決方式是分批生成、及時釋放。增強數(shù)據(jù)可以每生成一批就np.savez追加寫入不要一直攢在變量里單批樣本量控制在幾萬以內(nèi)。每個增強函數(shù)都接受seed參數(shù)在 notebook 開頭定義一個全局的SEED 42所有函數(shù)傳進(jìn)去。如果你在一個單元格里連續(xù)跑多個增強函數(shù)還要注意每個函數(shù)內(nèi)部如果不重置numpy.random的全局狀態(tài)后面的隨機序列會被前面的調(diào)用影響所以顯式用np.random.default_rng(seed)是更穩(wěn)的寫法。Jupyter 本身對長任務(wù)也不友好數(shù)據(jù)量超過幾十萬樣本后建議把增強腳本抽成.py在終端跑跑完存盤再回 notebook 做分析。6. 進(jìn)階按受試者分組交叉驗證把增強放進(jìn)嚴(yán)格評估管線可穿戴實驗里最容易被忽略的泄漏不是增強樣本進(jìn)了測試集而是同一個受試者的數(shù)據(jù)同時出現(xiàn)在訓(xùn)練和測試集中。模型會記住佩戴者的個體特征而不是動作類別本身。這個問題在加了增強之后會被放大因為增強能讓同一個人的樣本出現(xiàn)幾十次泄漏效果更強。解決方法是按受試者分組做交叉驗證。先把數(shù)據(jù)按受試者 ID 組織然后用GroupKFold劃分確保同一個人的所有窗口只出現(xiàn)在同一個 foldfrom sklearn.model_selection import GroupKFold def evaluate_grouped(X, y, groups, X_aug, y_aug, aug_groups): 按受試者分組評估增強效果。 groups: 每個樣本所屬的受試者 ID X_aug: 增強后的樣本 aug_groups: 增強樣本對應(yīng)的受試者 ID gkf GroupKFold(n_splits5) scores [] for train_idx, test_idx in gkf.split(X.reshape(X.shape[0], -1), y, groups): # 只把 train_idx 對應(yīng)的訓(xùn)練樣本與增強樣本拼接 X_train np.concatenate([X[train_idx], X_aug], axis0) y_train np.concatenate([y[train_idx], y_aug], axis0) # 注意增強樣本必須只來自訓(xùn)練 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的來源。增強樣本只能由訓(xùn)練 fold 內(nèi)的原始樣本生成不能拿測試 fold 的樣本來造否則 leaking。嚴(yán)謹(jǐn)?shù)淖龇ㄊ窃诿總€ fold 內(nèi)部重新對訓(xùn)練部分做增強而不是在 fold 外提前生成一份增強數(shù)組。上面的寫法為了示意簡化了這一點實際落地時把增強代碼放進(jìn) fold 循環(huán)內(nèi)部只接收X[train_idx]。交叉驗證跑完你會得到五組得分。我的習(xí)慣是同時跑一版不帶增強的GroupKFold兩邊的均值差才是增強的真實收益。如果增強后均值上升但方差也變大說明增強對某些受試者效果好、對另一些不穩(wěn)定這時要按類別和受試者分別看召回率不要只盯著平均分。我自己做可穿戴項目時有個堅持了很久的習(xí)慣每次增強調(diào)參前先跑一版嚴(yán)格分組的 baseline任何增強方案如果在這個 baseline 上沒有提升就不讓它進(jìn)訓(xùn)練管線。這個做法擋掉過很多看起來漂亮的增強方法。數(shù)據(jù)增強的價值最終要體現(xiàn)在沒見過的人戴上傳感器的表現(xiàn)上而不是 notebook 里的訓(xùn)練曲線。希望幫到你。本文還有配套的精品資源點擊獲取